diff --git a/BlueComponents.js b/BlueComponents.js
index bd9f46362..62055cc91 100644
--- a/BlueComponents.js
+++ b/BlueComponents.js
@@ -37,7 +37,6 @@ export class BlueButton extends Component {
marginTop: 20,
borderWidth: 0.7,
borderColor: 'transparent',
- borderLeftColor: 'transparent',
}}
buttonStyle={Object.assign(
{
@@ -184,7 +183,18 @@ export class BlueCard extends Component {
export class BlueText extends Component {
render() {
- return ;
+ return (
+
+ );
}
}
export class BlueTextCentered extends Component {
@@ -599,6 +609,27 @@ export class BlueTransactionPendingIcon extends Component {
}
}
+export class BlueTransactionOnchainIcon extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
export class BlueTransactionOutgoingIcon extends Component {
render() {
return (
@@ -728,6 +759,66 @@ export class BlueSendButtonIcon extends Component {
}
}
+export class ManageFundsBigButton extends Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ manage funds
+
+
+
+
+ );
+ }
+}
+
export class BluePlusIconDimmed extends Component {
render() {
return (
diff --git a/HDWallet.test.js b/HDWallet.test.js
index 3fd59ea37..a991df4c5 100644
--- a/HDWallet.test.js
+++ b/HDWallet.test.js
@@ -69,9 +69,9 @@ it('can generate Segwit HD (BIP49)', async () => {
assert.ok(hd2.validateMnemonic());
});
-it('HD (BIP49)can create TX', async () => {
+it('HD (BIP49) can create TX', async () => {
if (!process.env.HD_MNEMONIC) {
- console.log('process.env.HD_MNEMONIC not set, skipped');
+ console.warn('process.env.HD_MNEMONIC not set, skipped');
return;
}
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90 * 1000;
@@ -85,7 +85,7 @@ it('HD (BIP49)can create TX', async () => {
let txhex = hd.createTx(hd.utxo, 0.000014, 0.000001, '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK');
assert.equal(
txhex,
- '01000000000102ee7a13faf14dd004c6fa403c3073fbb6e0d7389ffa45e879fd96b5e21fd8989d00000000171600142f18e8406c9d210f30c901b24e5feeae78784eb7ffffffff22cde2709a2774a008fd0513e94edde4fdc71195ce0fd408e524df10f386fb67000000001716001468dde644410cc789d91a7f36b823f38369755a1cffffffff02780500000000000017a914a3a65daca3064280ae072b9d6773c027b30abace87dc0500000000000017a914850f4dbc255654de2c12c6f6d79cf9cb756cad038702473044022025e2a280e77691804ef3aa8039dceb5b7e454fb97edd2088f32858e86115bb030220553c21f7c9026a833ad9582a119cd6b24227fc45ed84fd18115ae71e5a8975f5012102edd141c5a27a726dda66be10a38b0fd3ccbb40e7c380034aaa43a1656d5f4dd60247304402207c9b7b0b7767e7bb37388fbfb865402ca58d2d7b88a7110244fc5d7881ae3cce022037874f10db854df4bfdc9ef2b02a9e2919a238eac6aad82bd82e528585084e3b0121030db3c49461a5e539e97bab62ab2b8f88151d1c2376493cf73ef1d02ef60637fd00000000',
+ '010000000001029d98d81fe2b596fd79e845fa9f38d7e0b6fb73303c40fac604d04df1fa137aee00000000171600142f18e8406c9d210f30c901b24e5feeae78784eb7ffffffff67fb86f310df24e508d40fce9511c7fde4dd4ee91305fd08a074279a70e2cd22000000001716001468dde644410cc789d91a7f36b823f38369755a1cffffffff02780500000000000017a914a3a65daca3064280ae072b9d6773c027b30abace87dc0500000000000017a914850f4dbc255654de2c12c6f6d79cf9cb756cad038702483045022100dc8390a9fd34c31259fa47f9fc182f20d991110ecfd5b58af1cf542fe8de257a022004c2d110da7b8c4127675beccc63b46fd65c706951f090fd381fa3b21d3c5c08012102edd141c5a27a726dda66be10a38b0fd3ccbb40e7c380034aaa43a1656d5f4dd60247304402207c0aef8313d55e72474247daad955979f62e56d1cbac5f2d14b8b022c6ce112602205d9aa3804f04624b12ab8a5ab0214b529c531c2f71c27c6f18aba6502a6ea0a80121030db3c49461a5e539e97bab62ab2b8f88151d1c2376493cf73ef1d02ef60637fd00000000',
);
let bitcoin = require('bitcoinjs-lib');
@@ -124,7 +124,7 @@ it('Segwit HD (BIP49) can fetch UTXO', async function() {
let hd = new HDSegwitP2SHWallet();
hd.usedAddresses = ['1Ez69SnzzmePmZX3WpEzMKTrcBF2gpNQ55', '1BiTCHeYzJNMxBLFCMkwYXNdFEdPJP53ZV']; // hacking internals
await hd.fetchUtxo();
- assert.equal(hd.utxo.length, 8);
+ assert.equal(hd.utxo.length, 9);
assert.ok(hd.utxo[0].confirmations);
assert.ok(hd.utxo[0].txid);
assert.ok(hd.utxo[0].vout);
diff --git a/LightningCustodianWallet.test.js b/LightningCustodianWallet.test.js
index 931b31dc0..20b7ba62f 100644
--- a/LightningCustodianWallet.test.js
+++ b/LightningCustodianWallet.test.js
@@ -1,12 +1,143 @@
-/* global it */
+/* global it, describe, jasmine */
+import Frisbee from 'frisbee';
import { LightningCustodianWallet } from './class';
let assert = require('assert');
-it('can generate auth secret', () => {
+describe('LightningCustodianWallet', () => {
let l1 = new LightningCustodianWallet();
- let l2 = new LightningCustodianWallet();
- l1.generate();
- l2.generate();
- assert.ok(l1.getSecret() !== l2.getSecret(), 'generated credentials should not be the same');
+ it('can create, auth and getbtc', async () => {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
+ assert.ok(l1.refill_addressess.length === 0);
+ assert.ok(l1._refresh_token_created_ts === 0);
+ assert.ok(l1._access_token_created_ts === 0);
+ l1.balance = 'FAKE';
+
+ await l1.createAccount();
+ await l1.authorize();
+ await l1.fetchBtcAddress();
+ await l1.fetchBalance();
+ await l1.fetchInfo();
+ await l1.fetchTransactions();
+ await l1.fetchPendingTransactions();
+
+ assert.ok(l1.access_token);
+ assert.ok(l1.refresh_token);
+ assert.ok(l1._refresh_token_created_ts > 0);
+ assert.ok(l1._access_token_created_ts > 0);
+ assert.ok(l1.refill_addressess.length > 0);
+ assert.ok(l1.balance === 0);
+ assert.ok(l1.info_raw);
+ assert.ok(l1.pending_transactions_raw.length === 0);
+ assert.ok(l1.transactions_raw.length === 0);
+ assert.ok(l1.transactions_raw.length === l1.getTransactions().length);
+ });
+
+ it('can refresh token', async () => {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
+ let oldRefreshToken = l1.refresh_token;
+ let oldAccessToken = l1.access_token;
+ await l1.refreshAcessToken();
+ assert.ok(oldRefreshToken !== l1.refresh_token);
+ assert.ok(oldAccessToken !== l1.access_token);
+ assert.ok(l1.access_token);
+ assert.ok(l1.refresh_token);
+ });
+
+ it('can use existing login/pass', async () => {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
+ if (!process.env.BLITZHUB) {
+ console.error('process.env.BLITZHUB not set, skipped');
+ return;
+ }
+ let l2 = new LightningCustodianWallet();
+ l2.setSecret(process.env.BLITZHUB);
+ await l2.authorize();
+ await l2.fetchPendingTransactions();
+ await l2.fetchTransactions();
+ assert.ok(l2.pending_transactions_raw.length === 0);
+ assert.ok(l2.transactions_raw.length > 0);
+ assert.ok(l2.transactions_raw.length === l2.getTransactions().length);
+ await l2.fetchBalance();
+ assert.ok(l2.getBalance() > 0);
+ });
+
+ it('can decode & check invoice', async () => {
+ if (!process.env.BLITZHUB) {
+ console.error('process.env.BLITZHUB not set, skipped');
+ return;
+ }
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 30 * 1000;
+ let l2 = new LightningCustodianWallet();
+ l2.setSecret(process.env.BLITZHUB);
+ await l2.authorize();
+
+ let invoice =
+ 'lnbc1u1pdcqpt3pp5ltuevvq2g69kdrzcegrs9gfqjer45rwjc0w736qjl92yvwtxhn6qdp8dp6kuerjv4j9xct5daeks6tnyp3xc6t50f582cscqp2zrkghzl535xjav52ns0rpskcn20takzdr2e02wn4xqretlgdemg596acq5qtfqhjk4jpr7jk8qfuuka2k0lfwjsk9mchwhxcgxzj3tsp09gfpy';
+ let decoded = await l2.decodeInvoice(invoice);
+
+ assert.ok(decoded.payment_hash);
+ assert.ok(decoded.description);
+ assert.ok(decoded.num_satoshis);
+
+ await l2.checkRouteInvoice(invoice);
+
+ // checking that bad invoice cant be decoded
+ invoice = 'gsom';
+ let error = false;
+ try {
+ await l2.decodeInvoice(invoice);
+ } catch (Err) {
+ error = true;
+ }
+ assert.ok(error);
+ });
+
+ it('can pay invoice', async () => {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
+ if (!process.env.BLITZHUB) {
+ console.error('process.env.BLITZHUB not set, skipped');
+ return;
+ }
+ if (!process.env.STRIKE) {
+ console.error('process.env.STRIKE not set, skipped');
+ return;
+ }
+
+ const api = new Frisbee({
+ baseURI: 'https://api.strike.acinq.co',
+ });
+
+ api.auth(process.env.STRIKE + ':');
+
+ const res = await api.post('/api/v1/charges', {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: 'amount=1¤cy=btc&description=acceptance+test',
+ });
+
+ if (!res.body || !res.body.payment_request) {
+ throw new Error('Strike problem: ' + JSON.stringify(res));
+ }
+
+ let invoice = res.body.payment_request;
+
+ let l2 = new LightningCustodianWallet();
+ l2.setSecret(process.env.BLITZHUB);
+ await l2.authorize();
+
+ let decoded = await l2.decodeInvoice(invoice);
+ assert.ok(decoded.payment_hash);
+ assert.ok(decoded.description);
+
+ await l2.checkRouteInvoice(invoice);
+
+ let start = +new Date();
+ await l2.payInvoice(invoice);
+ let end = +new Date();
+ if ((end - start) / 1000 > 9) {
+ console.warn('payInvoice took', (end - start) / 1000, 'sec');
+ }
+ });
});
diff --git a/class/app-storage.js b/class/app-storage.js
index f4d822c7c..7eeb3ec15 100644
--- a/class/app-storage.js
+++ b/class/app-storage.js
@@ -8,6 +8,7 @@ import {
SegwitP2SHWallet,
SegwitBech32Wallet,
} from './';
+import { LightningCustodianWallet } from './lightning-custodian-wallet';
let encryption = require('../encryption');
export class AppStorage {
@@ -147,6 +148,9 @@ export class AppStorage {
case new HDLegacyBreadwalletWallet().type:
unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key);
break;
+ case new LightningCustodianWallet().type:
+ unserializedWallet = LightningCustodianWallet.fromJson(key);
+ break;
case 'legacy':
default:
unserializedWallet = LegacyWallet.fromJson(key);
diff --git a/class/hd-segwit-p2sh-wallet.js b/class/hd-segwit-p2sh-wallet.js
index d83ad7968..1ed59289d 100644
--- a/class/hd-segwit-p2sh-wallet.js
+++ b/class/hd-segwit-p2sh-wallet.js
@@ -22,6 +22,10 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
return 'HD SegWit (BIP49 P2SH)';
}
+ allowSend() {
+ return this.getBalance() > 0;
+ }
+
generate() {
let c = 32;
let totalhex = '';
@@ -303,7 +307,7 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
for (let unspent of json.unspent_outputs) {
// a lil transform for signer module
- unspent.txid = unspent.tx_hash;
+ unspent.txid = unspent.tx_hash_big_endian;
unspent.vout = unspent.tx_output_n;
unspent.amount = unspent.value;
diff --git a/class/legacy-wallet.js b/class/legacy-wallet.js
index c9a65338c..0edf8bd30 100644
--- a/class/legacy-wallet.js
+++ b/class/legacy-wallet.js
@@ -397,10 +397,15 @@ export class LegacyWallet extends AbstractWallet {
}
getLatestTransactionTime() {
- for (let tx of this.getTransactions()) {
- return tx.received;
+ if (this.getTransactions().length === 0) {
+ return 0;
}
- return 0;
+ let max = 0;
+ for (let tx of this.getTransactions()) {
+ max = Math.max(new Date(tx.received) * 1, max);
+ }
+
+ return new Date(max).toString();
}
getRandomBlockcypherToken() {
diff --git a/class/lightning-custodian-wallet.js b/class/lightning-custodian-wallet.js
index 4aed4b54b..69401db0a 100644
--- a/class/lightning-custodian-wallet.js
+++ b/class/lightning-custodian-wallet.js
@@ -1,55 +1,464 @@
import { LegacyWallet } from './legacy-wallet';
import Frisbee from 'frisbee';
+let BigNumber = require('bignumber.js');
export class LightningCustodianWallet extends LegacyWallet {
constructor() {
super();
+ this.init();
this.type = 'lightningCustodianWallet';
- this.pendingTransactions = [];
- this.token = false;
- this.tokenRefreshedOn = 0;
+ this.refresh_token = '';
+ this.access_token = '';
+ this._refresh_token_created_ts = 0;
+ this._access_token_created_ts = 0;
+ this.refill_addressess = [];
+ this.pending_transactions_raw = [];
+ this.info_raw = false;
+ }
+
+ getAddress() {
+ return '';
+ }
+
+ timeToRefreshBalance() {
+ // blitzhub calls are cheap, so why not refresh constantly
+ return true;
+ }
+
+ timeToRefreshTransaction() {
+ // blitzhub calls are cheap, so why not refresh the list constantly
+ return true;
+ }
+
+ static fromJson(param) {
+ let obj = super.fromJson(param);
+ obj.init();
+ return obj;
+ }
+
+ init() {
this._api = new Frisbee({
- baseURI: 'https://api.blockcypher.com/v1/btc/main/addrs/',
+ baseURI: 'https://api.blitzhub.io/',
});
}
+ accessTokenExpired() {
+ return (+new Date() - this._access_token_created_ts) / 1000 >= 3600 * 2; // 2h
+ }
+
+ refreshTokenExpired() {
+ return (+new Date() - this._refresh_token_created_ts) / 1000 >= 3600 * 24 * 7; // 7d
+ }
+
+ generate() {
+ // nop
+ }
+
getTypeReadable() {
return 'Lightning (custodian)';
}
- async createAccount() {}
+ async createAccount() {
+ let response = await this._api.post('/create', {
+ body: { partnerid: 'bluewallet', test: true },
+ headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' },
+ });
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
+ }
- async authorize() {}
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
- async getToken() {}
+ if (!json.login || !json.password) {
+ throw new Error('API unexpected response: ' + JSON.stringify(response.body));
+ }
- async getBtcAddress() {}
+ this.secret = 'blitzhub://' + json.login + ':' + json.password;
- async newBtcAddress() {}
+ console.log(response.body);
+ }
- async getPendngBalance() {}
+ async payInvoice(invoice) {
+ let response = await this._api.post('/payinvoice', {
+ body: { invoice: invoice },
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer' + ' ' + this.access_token,
+ },
+ });
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse));
+ }
- async decodeInvoice() {}
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
- async checkRoute() {}
+ console.log(response.body);
- async payInvoice() {}
+ this.last_paid_invoice_result = json;
- async sendCoins() {}
+ if (json.payment_preimage) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ async checkRouteInvoice(invoice) {
+ let response = await this._api.get('/checkrouteinvoice?invoice=' + invoice, {
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer' + ' ' + this.access_token,
+ },
+ });
+
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
+ }
+
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
+
+ console.log(json);
+ }
+
+ /**
+ * Uses login & pass stored in `this.secret` to authorize
+ * and set internal `access_token` & `refresh_token`
+ *
+ * @return {Promise.}
+ */
+ async authorize() {
+ let login = this.secret.replace('blitzhub://', '').split(':')[0];
+ let password = this.secret.replace('blitzhub://', '').split(':')[1];
+ console.log('auth uses login:pass', login, password);
+ let response = await this._api.post('/auth?type=auth', {
+ body: { login: login, password: password },
+ headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' },
+ });
+
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
+ }
+
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
+
+ if (!json.access_token || !json.refresh_token) {
+ throw new Error('API unexpected response: ' + JSON.stringify(response.body));
+ }
+
+ this.refresh_token = json.refresh_token;
+ this.access_token = json.access_token;
+ this._refresh_token_created_ts = +new Date();
+ this._access_token_created_ts = +new Date();
+
+ console.log(json);
+ }
+
+ async checkLogin() {
+ if (this.accessTokenExpired() && this.refreshTokenExpired()) {
+ // all tokens expired, only option is to login with login and password
+ return this.authorize();
+ }
+
+ if (this.accessTokenExpired()) {
+ // only access token expired, so only refreshing it
+ let refreshedOk = true;
+ try {
+ await this.refreshAcessToken();
+ } catch (Err) {
+ refreshedOk = false;
+ }
+
+ if (!refreshedOk) {
+ // something went wrong, lets try to login regularly
+ return this.authorize();
+ }
+ }
+ }
+
+ async refreshAcessToken() {
+ let response = await this._api.post('/auth?type=refresh_token', {
+ body: { refresh_token: this.refresh_token },
+ headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' },
+ });
+
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
+ }
+
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
+
+ if (!json.access_token || !json.refresh_token) {
+ throw new Error('API unexpected response: ' + JSON.stringify(response.body));
+ }
+
+ this.refresh_token = json.refresh_token;
+ this.access_token = json.access_token;
+ this._refresh_token_created_ts = +new Date();
+ this._access_token_created_ts = +new Date();
+
+ console.log(json);
+ }
+
+ async fetchBtcAddress() {
+ let response = await this._api.get('/getbtc', {
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer' + ' ' + this.access_token,
+ },
+ });
+
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
+ }
+
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
+
+ this.refill_addressess = [];
+
+ for (let arr of json) {
+ this.refill_addressess.push(arr.address);
+ }
+
+ console.log(json);
+ }
getTransactions() {
- return [];
+ let txs = [];
+ this.pending_transactions_raw = this.pending_transactions_raw || [];
+ this.transactions_raw = this.transactions_raw || [];
+ txs = txs.concat(this.pending_transactions_raw, this.transactions_raw.slice().reverse()); // slice so array is cloned
+ // transforming to how wallets/list screen expects it
+ for (let tx of txs) {
+ tx.value = tx.amount * 100000000;
+ tx.received = new Date(tx.time * 1000).toString();
+ tx.memo = 'Refill';
+ }
+ return txs;
+ }
+
+ async fetchPendingTransactions() {
+ let response = await this._api.get('/getpending', {
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer' + ' ' + this.access_token,
+ },
+ });
+
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
+ }
+
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
+
+ this.pending_transactions_raw = json;
+
+ console.log(json);
}
async fetchTransactions() {
- return [];
- }
+ // TODO: iterate over all available pages
+ const limit = 10;
+ let queryRes = '';
+ let offset = 0;
+ queryRes += '?limit=' + limit;
+ queryRes += '&offset=' + offset;
- async getTransaction() {}
+ let response = await this._api.get('/gettxs' + queryRes, {
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer' + ' ' + this.access_token,
+ },
+ });
+
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
+ }
+
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
+
+ if (typeof json.btc_txs === 'undefined' || typeof json.paid_invoices === 'undefined' || typeof json.sended_coins === 'undefined') {
+ throw new Error('API unexpected response: ' + JSON.stringify(response.body));
+ }
+
+ this.transactions_raw = [].concat(json.btc_txs || [], json.paid_invoices || [], json.sended_coins || []);
+
+ console.log(json);
+ }
getBalance() {
- return 0;
+ return new BigNumber(this.balance).div(100000000).toString(10);
}
- async getInfo() {}
+ async fetchBalance() {
+ await this.checkLogin();
+
+ let response = await this._api.get('/balance', {
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer' + ' ' + this.access_token,
+ },
+ });
+
+ let json = response.body;
+ if (typeof json === 'undefined') {
+ throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body));
+ }
+
+ if (json && json.error) {
+ throw new Error('API error: ' + json.message + ' (code ' + json.code + ')');
+ }
+
+ if (!json.BTC || typeof json.BTC.AvailableBalance === 'undefined') {
+ throw new Error('API unexpected response: ' + JSON.stringify(response.body));
+ }
+
+ this.balance_raw = json;
+ this.balance = json.BTC.AvailableBalance;
+ this._lastBalanceFetch = +new Date();
+
+ console.log(json);
+ }
+
+ /**
+ * Example return:
+ * { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f',
+ * payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4',
+ * num_satoshisnum_satoshis: '100',
+ * timestamp: '1535116657',
+ * expiry: '3600',
+ * description: 'hundredSatoshis blitzhub',
+ * description_hash: '',
+ * fallback_addr: '',
+ * cltv_expiry: '10',
+ * route_hints: [] }
+ *
+ * @param invoice BOLT invoice string
+ * @return {Promise.