2018-07-15 20:56:28 +01:00
import { LegacyWallet } from './legacy-wallet' ;
import Frisbee from 'frisbee' ;
2019-12-21 16:44:35 -03:00
import bolt11 from 'bolt11' ;
2020-05-24 06:27:08 -04:00
import { BitcoinUnit , Chain } from '../../models/bitcoinUnits' ;
2023-10-29 11:48:54 -04:00
2018-07-15 20:56:28 +01:00
export class LightningCustodianWallet extends LegacyWallet {
2024-03-15 23:05:15 +03:00
static readonly type = 'lightningCustodianWallet' ;
static readonly typeReadable = 'Lightning' ;
// @ts-ignore: override
public readonly type = LightningCustodianWallet . type ;
// @ts-ignore: override
public readonly typeReadable = LightningCustodianWallet . typeReadable ;
baseURI? : string ;
refresh_token : string = '' ;
access_token : string = '' ;
_refresh_token_created_ts : number = 0 ;
_access_token_created_ts : number = 0 ;
refill_addressess : string [ ] = [ ] ;
pending_transactions_raw : any [ ] = [ ] ;
transactions_raw : any [ ] = [ ] ;
user_invoices_raw : any [ ] = [ ] ;
info_raw = false ;
preferredBalanceUnit = BitcoinUnit . SATS ;
chain = Chain . OFFCHAIN ;
private _api? : Frisbee ;
last_paid_invoice_result? : any ;
decoded_invoice_raw? : any ;
constructor ( ) {
super ( ) ;
2018-09-01 00:28:19 +01:00
this . init ( ) ;
}
2018-11-04 21:21:07 +00:00
/ * *
* requires calling init ( ) after setting
*
* @param URI
* /
2024-03-15 23:05:15 +03:00
setBaseURI ( URI : string | undefined ) {
2021-07-08 13:39:03 -04:00
this . baseURI = URI ;
2018-11-04 21:21:07 +00:00
}
getBaseURI() {
return this . baseURI ;
}
2018-10-09 00:25:36 -04:00
allowSend() {
2019-02-18 19:37:34 -05:00
return true ;
2018-10-09 00:25:36 -04:00
}
2024-03-15 23:05:15 +03:00
getAddress ( ) : string | false {
2019-09-30 18:13:22 -04:00
if ( this . refill_addressess . length > 0 ) {
return this . refill_addressess [ 0 ] ;
} else {
2024-03-15 23:05:15 +03:00
return false ;
2019-09-30 18:13:22 -04:00
}
2018-09-01 00:28:19 +01:00
}
2019-03-08 21:54:47 +00:00
getSecret() {
return this . secret + '@' + this . baseURI ;
}
2018-09-01 00:28:19 +01:00
timeToRefreshBalance() {
2019-06-01 21:45:01 +01:00
return ( + new Date ( ) - this . _lastBalanceFetch ) / 1000 > 300 ; // 5 min
2018-09-01 00:28:19 +01:00
}
timeToRefreshTransaction() {
2019-06-01 21:45:01 +01:00
return ( + new Date ( ) - this . _lastTxFetch ) / 1000 > 300 ; // 5 min
2018-09-01 00:28:19 +01:00
}
2024-03-15 23:05:15 +03:00
static fromJson ( param : any ) {
2020-06-01 15:54:23 +03:00
const obj = super . fromJson ( param ) ;
2024-03-15 23:05:15 +03:00
// @ts-ignore: local init
2018-09-01 00:28:19 +01:00
obj . init ( ) ;
return obj ;
}
2021-10-03 22:53:50 -04:00
async init() {
2023-06-10 15:19:14 +01:00
// un-cache refill onchain addresses on cold start. should help for cases when certain lndhub
// is turned off permanently, so users cant pull refill address from cache and send money to a black hole
this . refill_addressess = [ ] ;
2018-07-15 20:56:28 +01:00
this . _api = new Frisbee ( {
2018-11-04 21:21:07 +00:00
baseURI : this.baseURI ,
2018-07-15 20:56:28 +01:00
} ) ;
}
2018-09-01 00:28:19 +01:00
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
}
2024-03-15 23:05:15 +03:00
generate ( ) : Promise < void > {
2018-09-01 00:28:19 +01:00
// nop
2024-03-15 23:05:15 +03:00
return Promise . resolve ( ) ;
2018-09-01 00:28:19 +01:00
}
2024-03-15 23:05:15 +03:00
async createAccount ( isTest : boolean = false ) {
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . post ( '/create' , {
2018-09-03 23:44:45 +01:00
body : { partnerid : 'bluewallet' , accounttype : ( isTest && 'test' ) || 'common' } ,
2018-09-01 00:28:19 +01:00
headers : { 'Access-Control-Allow-Origin' : '*' , 'Content-Type' : 'application/json' } ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
2018-07-15 20:56:28 +01:00
2018-09-01 00:28:19 +01:00
if ( json && json . error ) {
2018-12-11 22:52:46 +00:00
throw new Error ( 'API error: ' + ( json . message ? json.message : json.error ) + ' (code ' + json . code + ')' ) ;
2018-09-01 00:28:19 +01:00
}
2018-07-15 20:56:28 +01:00
2018-09-01 00:28:19 +01:00
if ( ! json . login || ! json . password ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
2018-07-15 20:56:28 +01:00
2018-12-11 22:52:46 +00:00
this . secret = 'lndhub://' + json . login + ':' + json . password ;
2018-09-01 00:28:19 +01:00
}
2018-07-15 20:56:28 +01:00
2024-03-15 23:05:15 +03:00
async payInvoice ( invoice : string , freeAmount : number = 0 ) {
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . post ( '/payinvoice' , {
2022-10-31 12:25:26 +00:00
body : { invoice , amount : freeAmount } ,
2018-09-01 00:28:19 +01:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2019-02-13 23:19:59 +00:00
if ( response . originalResponse && typeof response . originalResponse === 'string' ) {
try {
response . originalResponse = JSON . parse ( response . originalResponse ) ;
} catch ( _ ) { }
}
if ( response . originalResponse && response . originalResponse . status && response . originalResponse . status === 503 ) {
throw new Error ( 'Payment is in transit' ) ;
}
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . originalResponse ) ) ;
}
2018-07-15 20:56:28 +01:00
2018-09-01 00:28:19 +01:00
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
2018-07-15 20:56:28 +01:00
2018-09-01 00:28:19 +01:00
this . last_paid_invoice_result = json ;
}
2018-09-03 23:44:45 +01:00
/ * *
* Returns list of LND invoices created by user
*
* @return { Promise . < Array > }
* /
2024-03-15 23:05:15 +03:00
async getUserInvoices ( limit : number | false = false ) {
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2019-01-10 17:50:33 +00:00
let limitString = '' ;
2024-03-15 23:05:15 +03:00
if ( limit ) limitString = '?limit=' + parseInt ( limit as unknown as string , 10 ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . get ( '/getuserinvoices' + limitString , {
2018-09-03 23:44:45 +01:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-03 23:44:45 +01:00
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . originalResponse ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
2018-12-25 19:07:43 +00:00
2019-01-10 17:50:33 +00:00
if ( limit ) {
// need to merge existing invoices with the ones that arrived
// but the ones received later should overwrite older ones
2020-06-01 15:54:23 +03:00
for ( const oldInvoice of this . user_invoices_raw ) {
2019-01-10 17:50:33 +00:00
// iterate all OLD invoices
let found = false ;
2020-06-01 15:54:23 +03:00
for ( const newInvoice of json ) {
2019-01-10 17:50:33 +00:00
// iterate all NEW invoices
if ( newInvoice . payment_request === oldInvoice . payment_request ) found = true ;
}
if ( ! found ) {
// if old invoice is not found in NEW array, we simply add it:
json . push ( oldInvoice ) ;
}
}
}
2024-03-15 23:05:15 +03:00
this . user_invoices_raw = json . sort ( function ( a : { timestamp : number } , b : { timestamp : number } ) {
2019-01-10 17:50:33 +00:00
return a . timestamp - b . timestamp ;
} ) ;
return this . user_invoices_raw ;
2018-09-03 23:44:45 +01:00
}
2018-12-25 19:07:43 +00:00
/ * *
* Basically the same as this . getUserInvoices ( ) but saves invoices list
* to internal variable
*
* @returns { Promise < void > }
* /
async fetchUserInvoices() {
await this . getUserInvoices ( ) ;
}
2024-03-15 23:05:15 +03:00
isInvoiceGeneratedByWallet ( paymentRequest : string ) {
2019-08-24 21:14:26 -04:00
return this . user_invoices_raw . some ( invoice = > invoice . payment_request === paymentRequest ) ;
}
2024-03-15 23:05:15 +03:00
weOwnAddress ( address : string ) {
2020-03-30 14:13:48 -04:00
return this . refill_addressess . some ( refillAddress = > address === refillAddress ) ;
}
2024-03-15 23:05:15 +03:00
async addInvoice ( amt : number , memo : string ) {
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . post ( '/addinvoice' , {
2022-10-31 12:25:26 +00:00
body : { amt : amt + '' , memo } ,
2018-09-03 23:44:45 +01:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-03 23:44:45 +01:00
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . originalResponse ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
2018-09-22 12:40:47 +01:00
if ( ! json . r_hash || ! json . pay_req ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
return json . pay_req ;
2018-09-03 23:44:45 +01:00
}
2018-09-01 00:28:19 +01:00
/ * *
* Uses login & pass stored in ` this.secret ` to authorize
* and set internal ` access_token ` & ` refresh_token `
*
* @return { Promise . < void > }
* /
async authorize() {
2024-03-15 23:05:15 +03:00
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2018-12-11 22:52:46 +00:00
let login , password ;
if ( this . secret . indexOf ( 'blitzhub://' ) !== - 1 ) {
login = this . secret . replace ( 'blitzhub://' , '' ) . split ( ':' ) [ 0 ] ;
password = this . secret . replace ( 'blitzhub://' , '' ) . split ( ':' ) [ 1 ] ;
} else {
login = this . secret . replace ( 'lndhub://' , '' ) . split ( ':' ) [ 0 ] ;
password = this . secret . replace ( 'lndhub://' , '' ) . split ( ':' ) [ 1 ] ;
}
2020-06-01 15:54:23 +03:00
const response = await this . _api . post ( '/auth?type=auth' , {
2022-10-31 12:25:26 +00:00
body : { login , password } ,
2018-09-01 00:28:19 +01:00
headers : { 'Access-Control-Allow-Origin' : '*' , 'Content-Type' : 'application/json' } ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
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 ( ) ;
}
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() {
2024-03-15 23:05:15 +03:00
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . post ( '/auth?type=refresh_token' , {
2018-09-01 00:28:19 +01:00
body : { refresh_token : this.refresh_token } ,
headers : { 'Access-Control-Allow-Origin' : '*' , 'Content-Type' : 'application/json' } ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
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 ( ) ;
}
async fetchBtcAddress() {
2024-03-15 23:05:15 +03:00
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . get ( '/getbtc' , {
2018-09-01 00:28:19 +01:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
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 = [ ] ;
2020-06-01 15:54:23 +03:00
for ( const arr of json ) {
2018-09-01 00:28:19 +01:00
this . refill_addressess . push ( arr . address ) ;
}
}
2018-07-15 20:56:28 +01:00
2019-09-30 18:13:22 -04:00
async getAddressAsync() {
2020-04-17 15:41:35 +01:00
await this . fetchBtcAddress ( ) ;
return this . getAddress ( ) ;
2019-09-30 18:13:22 -04:00
}
2019-12-14 01:52:14 -05:00
async allowOnchainAddress() {
2021-09-30 12:05:51 -04:00
if ( this . getAddress ( ) !== undefined && this . getAddress ( ) !== null ) {
2019-12-14 01:52:14 -05:00
return true ;
} else {
await this . fetchBtcAddress ( ) ;
2021-09-30 12:05:51 -04:00
return this . getAddress ( ) !== undefined && this . getAddress ( ) !== null ;
2019-12-14 01:52:14 -05:00
}
}
2018-08-19 15:54:15 +01:00
getTransactions() {
2024-03-15 23:05:15 +03:00
let txs : any = [ ] ;
2018-12-25 19:07:43 +00:00
txs = txs . concat ( this . pending_transactions_raw . slice ( ) , this . transactions_raw . slice ( ) . reverse ( ) , this . user_invoices_raw . slice ( ) ) ; // slice so array is cloned
2024-03-15 23:05:15 +03:00
2020-06-01 15:54:23 +03:00
for ( const tx of txs ) {
2021-06-17 17:24:00 -04:00
tx . walletID = this . getID ( ) ;
2018-09-26 22:33:51 +01:00
if ( tx . amount ) {
// pending tx
tx . amt = tx . amount * - 100000000 ;
tx . fee = 0 ;
tx . timestamp = tx . time ;
tx . memo = 'On-chain transaction' ;
}
2018-09-04 23:18:24 +01:00
2018-09-22 12:40:47 +01:00
if ( typeof tx . amt !== 'undefined' && typeof tx . fee !== 'undefined' ) {
2018-09-04 23:18:24 +01:00
// lnd tx outgoing
2024-03-20 21:20:43 +03:00
tx . value = ( tx . amt * 1 + tx . fee * 1 ) * - 1 ;
2018-09-22 12:40:47 +01:00
}
2018-09-26 22:16:52 +01:00
if ( tx . type === 'paid_invoice' ) {
tx . memo = tx . memo || 'Lightning payment' ;
2019-01-09 13:31:34 +00:00
if ( tx . value > 0 ) tx . value = tx . value * - 1 ; // value already includes fee in it (see lndhub)
2018-12-28 23:36:47 +00:00
// outer code expects spending transactions to of negative value
2018-09-26 22:16:52 +01:00
}
if ( tx . type === 'bitcoind_tx' ) {
tx . memo = 'On-chain transaction' ;
2018-09-04 23:18:24 +01:00
}
2018-09-22 12:40:47 +01:00
2018-12-25 19:07:43 +00:00
if ( tx . type === 'user_invoice' ) {
// incoming ln tx
2023-07-25 14:50:04 +01:00
tx . value = parseInt ( tx . amt , 10 ) ;
2018-12-25 19:07:43 +00:00
tx . memo = tx . description || 'Lightning invoice' ;
}
tx . received = new Date ( tx . timestamp * 1000 ) . toString ( ) ;
2018-09-01 00:28:19 +01:00
}
2024-03-15 23:05:15 +03:00
return txs . sort ( function ( a : { timestamp : number } , b : { timestamp : number } ) {
2018-09-26 22:16:52 +01:00
return b . timestamp - a . timestamp ;
} ) ;
2018-08-19 15:54:15 +01:00
}
2018-09-01 00:28:19 +01:00
async fetchPendingTransactions() {
2024-03-15 23:05:15 +03:00
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . get ( '/getpending' , {
2018-09-01 00:28:19 +01:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
if ( typeof json === 'undefined' ) {
2018-09-25 22:23:56 +01:00
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response ) ) ;
2018-09-01 00:28:19 +01:00
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
this . pending_transactions_raw = json ;
2018-08-19 15:54:15 +01:00
}
2018-07-15 20:56:28 +01:00
2018-09-01 00:28:19 +01:00
async fetchTransactions() {
2024-03-15 23:05:15 +03:00
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2018-09-01 00:28:19 +01:00
// TODO: iterate over all available pages
const limit = 10 ;
let queryRes = '' ;
2020-06-01 15:54:23 +03:00
const offset = 0 ;
2018-09-01 00:28:19 +01:00
queryRes += '?limit=' + limit ;
queryRes += '&offset=' + offset ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . get ( '/gettxs' + queryRes , {
2018-09-01 00:28:19 +01:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
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 + ')' ) ;
}
2018-09-22 12:40:47 +01:00
if ( ! Array . isArray ( json ) ) {
2018-09-01 00:28:19 +01:00
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
2019-02-10 13:20:04 +00:00
this . _lastTxFetch = + new Date ( ) ;
2018-09-22 12:40:47 +01:00
this . transactions_raw = json ;
2018-09-01 00:28:19 +01:00
}
2018-07-15 20:56:28 +01:00
2018-08-19 15:54:15 +01:00
getBalance() {
2019-05-02 16:33:03 -04:00
return this . balance ;
2018-09-01 00:28:19 +01:00
}
2024-03-15 23:05:15 +03:00
async fetchBalance ( noRetry? : boolean ) : Promise < void > {
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2018-09-01 00:28:19 +01:00
await this . checkLogin ( ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . get ( '/balance' , {
2018-09-01 00:28:19 +01:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
2018-09-22 12:40:47 +01:00
if ( json . code * 1 === 1 && ! noRetry ) {
await this . authorize ( ) ;
return this . fetchBalance ( true ) ;
}
2018-09-01 00:28:19 +01:00
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 = json . BTC . AvailableBalance ;
this . _lastBalanceFetch = + new Date ( ) ;
}
/ * *
* Example return :
* { destination : '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f' ,
* payment_hash : 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4' ,
2019-12-21 16:44:35 -03:00
* num_satoshis : '100' ,
2018-09-01 00:28:19 +01:00
* timestamp : '1535116657' ,
* expiry : '3600' ,
* description : 'hundredSatoshis blitzhub' ,
* description_hash : '' ,
* fallback_addr : '' ,
* cltv_expiry : '10' ,
* route_hints : [ ] }
*
* @param invoice BOLT invoice string
2021-09-09 12:00:11 +01:00
* @return { payment_hash : string }
2018-09-01 00:28:19 +01:00
* /
2024-03-15 23:05:15 +03:00
decodeInvoice ( invoice : string ) {
2020-06-01 15:54:23 +03:00
const { payeeNodeKey , tags , satoshis , millisatoshis , timestamp } = bolt11 . decode ( invoice ) ;
2019-12-21 16:44:35 -03:00
2024-03-15 23:05:15 +03:00
const decoded : any = {
2019-12-21 16:44:35 -03:00
destination : payeeNodeKey ,
2020-01-12 00:00:33 +00:00
num_satoshis : satoshis ? satoshis . toString ( ) : '0' ,
num_millisatoshis : millisatoshis ? millisatoshis . toString ( ) : '0' ,
2024-03-15 23:05:15 +03:00
timestamp : timestamp?.toString ( ) ? ? '0' ,
2019-12-21 16:44:35 -03:00
fallback_addr : '' ,
2020-01-12 00:00:33 +00:00
route_hints : [ ] ,
2019-12-21 16:44:35 -03:00
} ;
for ( let i = 0 ; i < tags . length ; i ++ ) {
2020-06-01 15:54:23 +03:00
const { tagName , data } = tags [ i ] ;
2019-12-21 16:44:35 -03:00
switch ( tagName ) {
case 'payment_hash' :
2020-01-12 00:00:33 +00:00
decoded . payment_hash = data ;
break ;
2019-12-21 16:44:35 -03:00
case 'purpose_commit_hash' :
2020-01-12 00:00:33 +00:00
decoded . description_hash = data ;
break ;
2019-12-21 16:44:35 -03:00
case 'min_final_cltv_expiry' :
2020-01-12 00:00:33 +00:00
decoded . cltv_expiry = data . toString ( ) ;
break ;
2019-12-21 16:44:35 -03:00
case 'expire_time' :
2020-01-12 00:00:33 +00:00
decoded . expiry = data . toString ( ) ;
break ;
2019-12-21 16:44:35 -03:00
case 'description' :
2020-01-12 00:00:33 +00:00
decoded . description = data ;
break ;
2019-12-21 16:44:35 -03:00
}
2018-09-01 00:28:19 +01:00
}
2020-01-12 18:08:56 +00:00
if ( ! decoded . expiry ) decoded . expiry = '3600' ; // default
2023-07-25 14:50:04 +01:00
if ( parseInt ( decoded . num_satoshis , 10 ) === 0 && decoded . num_millisatoshis > 0 ) {
2020-01-19 20:55:10 +00:00
decoded . num_satoshis = ( decoded . num_millisatoshis / 1000 ) . toString ( ) ;
}
2019-12-21 16:44:35 -03:00
return ( this . decoded_invoice_raw = decoded ) ;
2018-09-01 00:28:19 +01:00
}
async fetchInfo() {
2024-03-15 23:05:15 +03:00
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . get ( '/getinfo' , {
2018-09-01 00:28:19 +01:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2018-09-01 00:28:19 +01:00
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 . identity_pubkey ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
2018-08-19 15:54:15 +01:00
}
2018-07-15 20:56:28 +01:00
2024-03-15 23:05:15 +03:00
static async isValidNodeAddress ( address : string ) {
2023-12-15 11:56:41 -04:00
const apiCall = new Frisbee ( {
baseURI : address ,
} ) ;
2020-06-01 15:54:23 +03:00
const response = await apiCall . get ( '/getinfo' , {
2019-03-08 21:54:47 +00:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2019-03-08 21:54:47 +00:00
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . code && json . code !== 1 ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
return true ;
}
2018-09-01 00:28:19 +01:00
allowReceive() {
2018-12-25 11:34:51 -05:00
return true ;
2018-09-01 00:28:19 +01:00
}
2020-01-12 00:00:33 +00:00
2021-03-23 15:16:32 +03:00
allowSignVerifyMessage() {
return false ;
}
2020-01-12 00:00:33 +00:00
/ * *
* Example return :
* { destination : '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f' ,
* payment_hash : 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4' ,
* num_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 . < Object > }
* /
2024-03-15 23:05:15 +03:00
async decodeInvoiceRemote ( invoice : string ) {
if ( ! this . _api ) throw new Error ( 'Internal error: _api is not initialized' ) ;
2020-01-12 00:00:33 +00:00
await this . checkLogin ( ) ;
2020-06-01 15:54:23 +03:00
const response = await this . _api . get ( '/decodeinvoice?invoice=' + invoice , {
2020-01-12 00:00:33 +00:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access_token ,
} ,
} ) ;
2020-06-01 15:54:23 +03:00
const json = response . body ;
2020-01-12 00:00:33 +00:00
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 . payment_hash ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
return ( this . decoded_invoice_raw = json ) ;
}
2020-08-10 15:17:50 +01:00
2024-03-15 23:05:15 +03:00
weOwnTransaction ( txid : string ) {
2020-08-10 15:17:50 +01:00
for ( const tx of this . getTransactions ( ) ) {
if ( tx && tx . payment_hash && tx . payment_hash === txid ) return true ;
}
return false ;
}
2022-02-11 14:18:56 +00:00
2024-03-15 23:05:15 +03:00
authenticate ( lnurl : any ) {
2022-02-11 14:18:56 +00:00
return lnurl . authenticate ( this . secret ) ;
}
2018-07-15 20:56:28 +01:00
}
2018-09-01 00:28:19 +01:00
/ *
pending tx :
[ { amount : 0.00078061 ,
account : '521172' ,
address : '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF' ,
category : 'receive' ,
confirmations : 0 ,
blockhash : '' ,
blockindex : 0 ,
blocktime : 0 ,
txid : '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62' ,
walletconflicts : [ ] ,
time : 1535024434 ,
timereceived : 1535024434 } ]
tx :
[ { amount : 0.00078061 ,
account : '521172' ,
address : '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF' ,
category : 'receive' ,
confirmations : 5 ,
blockhash : '0000000000000000000edf18e9ece18e449c6d8eed1f729946b3531c32ee9f57' ,
blockindex : 693 ,
blocktime : 1535024914 ,
txid : '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62' ,
walletconflicts : [ ] ,
time : 1535024434 ,
timereceived : 1535024434 } ]
* /