Skip to content

Commit

Permalink
Add Blink backend
Browse files Browse the repository at this point in the history
  • Loading branch information
chill117 committed Jul 29, 2024
1 parent 8b11cf9 commit e6f1139
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lib/LightningBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class LightningBackend {
const requiredOptions = this.requiredOptions.concat(classOptions.requiredOptions);
this.checkRequiredOptions(options, requiredOptions);
this.checkOptions && this.checkOptions(options);
this.options = options;
this.options = JSON.parse(JSON.stringify(options || {}));
this.prepareCheckMethodErrorRegEx();
}

Expand Down
144 changes: 144 additions & 0 deletions lib/backends/blink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const assert = require('assert');
const HttpLightningBackend = require('../HttpLightningBackend');

// https://dev.blink.sv/

class Backend extends HttpLightningBackend {

static name = 'blink';

constructor(options) {
options = options || {};
super(Backend.name, options, {
defaultOptions: {
baseUrl: null,
hostname: null,
protocol: 'https',
requestContentType: 'json',
},
requiredOptions: ['connectionString'],
});
Object.assign(this.options, this.parseConnectionString(this.options.connectionString));
this.options.headers['X-API-KEY'] = encodeURIComponent(this.options.apiKey);
}

checkOptions(options) {
assert.strictEqual(typeof options.connectionString, 'string', 'Invalid option ("connectionString"): String expected');
Object.assign(options, this.parseConnectionString(options.connectionString));
HttpLightningBackend.prototype.checkOptions.call(this, options);
}

parseConnectionString(connectionString) {
let values = {};
connectionString.split(';').forEach(line => {
const [ key, value ] = line.split('=');
values[key] = value;
});
const baseUrl = values['server'] || null;
const apiKey = values['api-key'] || null;
const walletId = values['wallet-id'] || null;
try {
assert.ok(values['type'], 'Missing "type"');
assert.strictEqual(values['type'], 'blink', 'Invalid type: Expected "blink"');
assert.ok(baseUrl, 'Missing "server"');
assert.ok(apiKey, 'Missing "api-key"');
assert.ok(walletId, 'Missing "wallet-id"');
} catch (error) {
throw new Error(`Invalid option ("connectionString"): ${error.message}`);
}
return { baseUrl, apiKey, walletId };
}

payInvoice(invoice) {
const query = 'mutation LnInvoicePaymentSend($input: LnInvoicePaymentInput!) {\n lnInvoicePaymentSend(input: $input) {\n status\n errors {\n message\n path\n code\n }\n }\n}';
const variables = {
input: {
paymentRequest: invoice,
walletId: this.options.walletId,
},
};
return this.doGraphQLQuery(query, variables).then(result => {
const { preimage } = Object.values(result.data)[0].blah || {};
return { id: null, preimage };
});
}

addInvoice(amount, extra) {
const query = 'mutation LnInvoiceCreate($input: LnInvoiceCreateInput!) {\n lnInvoiceCreate(input: $input) {\n invoice {\n paymentRequest\n paymentHash\n paymentSecret\n satoshis\n }\n errors {\n message\n }\n }\n}';
const variables = {
input: {
amount: Math.floor(amount / 1000).toString(),// sats
walletId: this.options.walletId,
},
};
return this.doGraphQLQuery(query, variables).then(result => {
const { paymentRequest } = Object.values(result.data)[0].invoice || {};
return { id: null, invoice: paymentRequest };
});
}

getInvoiceStatus(paymentHash) {
return Promise.reject(new Error('Not supported by this LN service.'));
}

getBalance() {
const query = 'query me { me { defaultAccount { wallets { id walletCurrency balance }}}}';
const variables = {};
return this.doGraphQLQuery(query, variables).then(result => {
const wallets = result.data.me && result.data.me.defaultAccount && result.data.me.defaultAccount.wallets || [];
assert.ok(wallets, 'Unexpected JSON response');
const wallet = wallets.find(_wallet => _wallet.id === this.options.walletId);
assert.ok(wallet, 'Wallet info not found');
return parseInt(wallet.balance) * 1000;// msat
});
}

getNodeUri() {
return Promise.reject(new Error('Not supported by this LN service.'));
}

openChannel(remoteId, localAmt, pushAmt, makePrivate) {
return Promise.reject(new Error('Not supported by this LN service.'));
}

doGraphQLQuery(query, variables) {
console.log('doGraphQLQuery', {query, variables});
return this.request('post', '', { query, variables }).then(result => {
console.log(JSON.stringify({result}, null, 2));
assert.ok(result && result.data, 'Unexpected JSON response: ' + JSON.stringify(result));
const { errors } = Object.values(result.data)[0] || {};
assert.ok(!errors || errors.length === 0, JSON.stringify(errors));
return result;
});
}
};

Backend.prototype.checkMethodErrorMessages = {
payInvoice: {
ok: [
'Unable to find a route for payment.',
'ROUTE_FINDING_ERROR',
],
notOk: [
'INSUFFICIENT_BALANCE',
'Authorization Required',
],
},
};

Backend.form = {
label: 'Blink',
inputs: [
{
name: 'connectionString',
label: 'BTCPay Connection String',
help: 'Sign-in to your Blink account in a browser. Go to API Keys. Click the plus symbol (+) in the upper right corner to create a new API key. Copy the BTCPay connection string for your wallet.',
type: 'password',
placeholder: 'xxx',
default: '',
required: true,
},
],
};

module.exports = Backend;
2 changes: 1 addition & 1 deletion lib/checkBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module.exports = function(backend, config, options) {
const millisatoshis = 1000;
const { network } = options;
let paymentSecret;
if (backend === 'lnbits') {
if (['blink', 'lnbits'].includes(backend)) {
// !! IMPORTANT !!
// "payment_secret" must be 32 bytes.
paymentSecret = crypto.randomBytes(32);
Expand Down
6 changes: 6 additions & 0 deletions test/e2e/example.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#TEST_BLINK_CONFIG={"connectionString":"type=blink;server=https://api.blink.sv/graphql;api-key=blink_XXX;wallet-id=xxx-yyyy-zzzz-0000-xyz123"}
#TEST_BLINK_BAD_CONFIG={"connectionString":"type=blink;server=https://api.blink.sv/graphql;api-key=blink_XXX;wallet-id=xxx-yyyy-zzzz-0000-xyz123"}
#TEST_BLINK_PAYINVOICE={"invoice":"xxx"}
#TEST_BLINK_ADDINVOICE={"amount":50000}
#TEST_BLINK_GETBALANCE={}

#TEST_CLIGHTNING_CONFIG={"unixSockPath":"xxx"}
#TEST_CLIGHTNING_GETNODEURI={"result":"[email protected]:9735"}
#TEST_CLIGHTNING_OPENCHANNEL={"remoteId":"xxx","localAmt":20000,"pushAmt":0,"makePrivate":0}
Expand Down

0 comments on commit e6f1139

Please sign in to comment.