2022-09-03 00:48:07 +01:00
/* eslint react/prop-types: "off", @typescript-eslint/ban-ts-comment: "off", camelcase: "off" */
2024-02-23 07:32:47 +00:00
import BIP47Factory , { BIP47Interface } from '@spsina/bip47' ;
2019-12-23 23:11:00 +00:00
import BigNumber from 'bignumber.js' ;
2022-09-03 00:48:07 +01:00
import BIP32Factory , { BIP32Interface } from 'bip32' ;
2024-02-23 07:32:47 +00:00
import * as bip39 from 'bip39' ;
import * as bitcoin from 'bitcoinjs-lib' ;
import { Transaction as BTransaction , Psbt } from 'bitcoinjs-lib' ;
import b58 from 'bs58check' ;
2024-04-23 11:25:20 +01:00
import { CoinSelectReturnInput } from 'coinselect' ;
2023-04-10 10:52:10 +01:00
import { ECPairFactory } from 'ecpair' ;
2024-02-23 07:32:47 +00:00
import { ECPairInterface } from 'ecpair/src/ecpair' ;
2021-03-09 14:26:56 +03:00
2024-03-25 00:07:43 +03:00
import * as BlueElectrum from '../../blue_modules/BlueElectrum' ;
2024-02-23 07:32:47 +00:00
import { ElectrumHistory } from '../../blue_modules/BlueElectrum' ;
import ecc from '../../blue_modules/noble_ecc' ;
2020-05-24 06:27:08 -04:00
import { randomBytes } from '../rng' ;
2019-12-23 23:11:00 +00:00
import { AbstractHDWallet } from './abstract-hd-wallet' ;
2024-04-23 11:25:20 +01:00
import { CreateTransactionResult , CreateTransactionTarget , CreateTransactionUtxo , Transaction , Utxo } from './types' ;
2024-04-26 23:49:19 +01:00
import assert from 'assert' ;
2022-09-03 00:48:07 +01:00
2021-12-20 17:11:07 +00:00
const ECPair = ECPairFactory ( ecc ) ;
2022-01-17 15:22:15 +00:00
const bip32 = BIP32Factory ( ecc ) ;
2022-12-11 21:34:50 -05:00
const bip47 = BIP47Factory ( ecc ) ;
2019-12-23 23:11:00 +00:00
2022-09-10 12:11:28 +01:00
type BalanceByIndex = {
2022-09-03 00:48:07 +01:00
c : number ;
u : number ;
2022-09-10 12:11:28 +01:00
} ;
2022-09-03 00:48:07 +01:00
2020-04-16 15:40:23 +01:00
/ * *
* Electrum - means that it utilizes Electrum protocol for blockchain data
* /
2019-12-23 23:11:00 +00:00
export class AbstractHDElectrumWallet extends AbstractHDWallet {
2024-03-15 23:05:15 +03:00
static readonly type = 'abstract' ;
static readonly typeReadable = 'abstract' ;
2019-12-23 23:11:00 +00:00
static defaultRBFSequence = 2147483648 ; // 1 << 31, minimum for replaceable transactions as per BIP68
static finalRBFSequence = 4294967295 ; // 0xFFFFFFFF
2024-03-15 23:05:15 +03:00
// @ts-ignore: override
public readonly type = AbstractHDElectrumWallet . type ;
// @ts-ignore: override
public readonly typeReadable = AbstractHDElectrumWallet . typeReadable ;
2019-12-23 23:11:00 +00:00
2022-09-03 00:48:07 +01:00
_balances_by_external_index : Record < number , BalanceByIndex > ;
_balances_by_internal_index : Record < number , BalanceByIndex > ;
// @ts-ignore
_txs_by_external_index : Record < number , Transaction [ ] > ;
// @ts-ignore
_txs_by_internal_index : Record < number , Transaction [ ] > ;
_utxo : any [ ] ;
2022-12-11 21:34:50 -05:00
// BIP47
_enable_BIP47 : boolean ;
_payment_code : string ;
2024-04-26 23:49:19 +01:00
/ * *
* payment codes of people who can pay us
* /
_receive_payment_codes : string [ ] ;
/ * *
* payment codes of people whom we can pay
* /
_send_payment_codes : string [ ] ;
/ * *
* joint addresses with remote counterparties , to receive funds
* /
_addresses_by_payment_code_receive : Record < string , string [ ] > ;
/ * *
* receive index
* /
_next_free_payment_code_address_index_receive : Record < string , number > ;
/ * *
* joint addresses with remote counterparties , whom we can send funds
* /
_addresses_by_payment_code_send : Record < string , string [ ] > ;
/ * *
* send index
* /
_next_free_payment_code_address_index_send : Record < string , number > ;
/ * *
* this is where we put transactions related to our PC receive addresses . this is both
* incoming transactions AND outgoing transactions ( when we spend those funds )
*
* /
_txs_by_payment_code_index : Record < string , Transaction [ ] [ ] > ;
2022-12-11 21:34:50 -05:00
_balances_by_payment_code_index : Record < string , BalanceByIndex > ;
2023-02-09 21:01:26 -05:00
_bip47_instance? : BIP47Interface ;
2022-12-11 21:34:50 -05:00
2019-12-23 23:11:00 +00:00
constructor ( ) {
super ( ) ;
this . _balances_by_external_index = { } ; // 0 => { c: 0, u: 0 } // confirmed/unconfirmed
this . _balances_by_internal_index = { } ;
this . _txs_by_external_index = { } ;
this . _txs_by_internal_index = { } ;
this . _utxo = [ ] ;
2022-12-11 21:34:50 -05:00
// BIP47
this . _enable_BIP47 = false ;
this . _payment_code = '' ;
2024-04-26 23:49:19 +01:00
this . _receive_payment_codes = [ ] ;
this . _send_payment_codes = [ ] ;
this . _next_free_payment_code_address_index_receive = { } ;
2022-12-11 21:34:50 -05:00
this . _txs_by_payment_code_index = { } ;
2024-04-26 23:49:19 +01:00
this . _addresses_by_payment_code_send = { } ;
this . _next_free_payment_code_address_index_send = { } ;
2022-12-11 21:34:50 -05:00
this . _balances_by_payment_code_index = { } ;
2024-04-26 23:49:19 +01:00
this . _addresses_by_payment_code_receive = { } ;
2019-12-23 23:11:00 +00:00
}
/ * *
* @inheritDoc
* /
getBalance() {
let ret = 0 ;
2020-06-01 15:54:23 +03:00
for ( const bal of Object . values ( this . _balances_by_external_index ) ) {
2019-12-23 23:11:00 +00:00
ret += bal . c ;
}
2020-06-01 15:54:23 +03:00
for ( const bal of Object . values ( this . _balances_by_internal_index ) ) {
2019-12-23 23:11:00 +00:00
ret += bal . c ;
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2022-12-11 21:34:50 -05:00
ret += this . _getBalancesByPaymentCodeIndex ( pc ) . c ;
}
2019-12-23 23:11:00 +00:00
return ret + ( this . getUnconfirmedBalance ( ) < 0 ? this . getUnconfirmedBalance ( ) : 0 ) ;
}
2020-04-22 16:13:18 +01:00
/ * *
*
* @inheritDoc
* /
2019-12-23 23:11:00 +00:00
getUnconfirmedBalance() {
let ret = 0 ;
2020-06-01 15:54:23 +03:00
for ( const bal of Object . values ( this . _balances_by_external_index ) ) {
2019-12-23 23:11:00 +00:00
ret += bal . u ;
}
2020-06-01 15:54:23 +03:00
for ( const bal of Object . values ( this . _balances_by_internal_index ) ) {
2019-12-23 23:11:00 +00:00
ret += bal . u ;
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2022-12-11 21:34:50 -05:00
ret += this . _getBalancesByPaymentCodeIndex ( pc ) . u ;
}
2019-12-23 23:11:00 +00:00
return ret ;
}
async generate() {
2020-11-12 15:19:25 +00:00
const buf = await randomBytes ( 16 ) ;
2020-05-01 08:37:28 +08:00
this . secret = bip39 . entropyToMnemonic ( buf . toString ( 'hex' ) ) ;
2019-12-23 23:11:00 +00:00
}
2022-09-03 00:48:07 +01:00
async generateFromEntropy ( user : Buffer ) {
2020-06-29 15:58:43 +03:00
const random = await randomBytes ( user . length < 32 ? 32 - user.length : 0 ) ;
const buf = Buffer . concat ( [ user , random ] , 32 ) ;
this . secret = bip39 . entropyToMnemonic ( buf . toString ( 'hex' ) ) ;
}
2022-09-03 00:48:07 +01:00
_getExternalWIFByIndex ( index : number ) : string | false {
2019-12-23 23:11:00 +00:00
return this . _getWIFByIndex ( false , index ) ;
}
2022-09-03 00:48:07 +01:00
_getInternalWIFByIndex ( index : number ) : string | false {
2019-12-23 23:11:00 +00:00
return this . _getWIFByIndex ( true , index ) ;
}
/ * *
* Get internal / external WIF by wallet index
* @param { Boolean } internal
* @param { Number } index
* @returns { string | false } Either string WIF or FALSE if error happened
* @private
* /
2022-09-03 00:48:07 +01:00
_getWIFByIndex ( internal : boolean , index : number ) : string | false {
2019-12-23 23:11:00 +00:00
if ( ! this . secret ) return false ;
2021-04-15 20:52:48 +03:00
const seed = this . _getSeed ( ) ;
2022-01-17 15:22:15 +00:00
const root = bip32 . fromSeed ( seed ) ;
2021-09-23 16:05:10 +03:00
const path = ` ${ this . getDerivationPath ( ) } / ${ internal ? 1 : 0 } / ${ index } ` ;
2019-12-23 23:11:00 +00:00
const child = root . derivePath ( path ) ;
return child . toWIF ( ) ;
}
2023-03-15 21:30:00 +00:00
_getNodeAddressByIndex ( node : number , index : number ) : string {
2019-12-23 23:11:00 +00:00
index = index * 1 ; // cast to int
if ( node === 0 ) {
if ( this . external_addresses_cache [ index ] ) return this . external_addresses_cache [ index ] ; // cache hit
}
if ( node === 1 ) {
if ( this . internal_addresses_cache [ index ] ) return this . internal_addresses_cache [ index ] ; // cache hit
}
if ( node === 0 && ! this . _node0 ) {
2022-09-10 12:11:28 +01:00
const xpub = this . _zpubToXpub ( this . getXpub ( ) ) ;
2022-01-17 15:22:15 +00:00
const hdNode = bip32 . fromBase58 ( xpub ) ;
2019-12-23 23:11:00 +00:00
this . _node0 = hdNode . derive ( node ) ;
}
if ( node === 1 && ! this . _node1 ) {
2022-09-10 12:11:28 +01:00
const xpub = this . _zpubToXpub ( this . getXpub ( ) ) ;
2022-01-17 15:22:15 +00:00
const hdNode = bip32 . fromBase58 ( xpub ) ;
2019-12-23 23:11:00 +00:00
this . _node1 = hdNode . derive ( node ) ;
}
2023-03-15 21:30:00 +00:00
let address : string ;
2019-12-23 23:11:00 +00:00
if ( node === 0 ) {
2022-09-03 00:48:07 +01:00
// @ts-ignore
2023-03-15 20:42:25 +00:00
address = this . _hdNodeToAddress ( this . _node0 . derive ( index ) ) ;
2023-03-15 21:30:00 +00:00
} else {
// tbh the only possible else is node === 1
2022-09-03 00:48:07 +01:00
// @ts-ignore
2023-03-15 20:42:25 +00:00
address = this . _hdNodeToAddress ( this . _node1 . derive ( index ) ) ;
2019-12-23 23:11:00 +00:00
}
if ( node === 0 ) {
return ( this . external_addresses_cache [ index ] = address ) ;
2023-03-15 21:30:00 +00:00
} else {
// tbh the only possible else option is node === 1
2019-12-23 23:11:00 +00:00
return ( this . internal_addresses_cache [ index ] = address ) ;
}
}
2022-09-03 00:48:07 +01:00
_getNodePubkeyByIndex ( node : number , index : number ) {
2019-12-23 23:11:00 +00:00
index = index * 1 ; // cast to int
if ( node === 0 && ! this . _node0 ) {
2022-09-10 12:11:28 +01:00
const xpub = this . _zpubToXpub ( this . getXpub ( ) ) ;
2022-01-17 15:22:15 +00:00
const hdNode = bip32 . fromBase58 ( xpub ) ;
2019-12-23 23:11:00 +00:00
this . _node0 = hdNode . derive ( node ) ;
}
if ( node === 1 && ! this . _node1 ) {
2022-09-10 12:11:28 +01:00
const xpub = this . _zpubToXpub ( this . getXpub ( ) ) ;
2022-01-17 15:22:15 +00:00
const hdNode = bip32 . fromBase58 ( xpub ) ;
2019-12-23 23:11:00 +00:00
this . _node1 = hdNode . derive ( node ) ;
}
2022-09-03 00:48:07 +01:00
if ( node === 0 && this . _node0 ) {
2019-12-23 23:11:00 +00:00
return this . _node0 . derive ( index ) . publicKey ;
}
2022-09-03 00:48:07 +01:00
if ( node === 1 && this . _node1 ) {
2019-12-23 23:11:00 +00:00
return this . _node1 . derive ( index ) . publicKey ;
}
2022-09-03 00:48:07 +01:00
throw new Error ( 'Internal error: this._node0 or this._node1 is undefined' ) ;
2019-12-23 23:11:00 +00:00
}
2023-03-15 21:30:00 +00:00
_getExternalAddressByIndex ( index : number ) : string {
2019-12-23 23:11:00 +00:00
return this . _getNodeAddressByIndex ( 0 , index ) ;
}
2022-09-03 00:48:07 +01:00
_getInternalAddressByIndex ( index : number ) {
2019-12-23 23:11:00 +00:00
return this . _getNodeAddressByIndex ( 1 , index ) ;
}
/ * *
* Returning zpub actually , not xpub . Keeping same method name
* for compatibility .
*
* @return { String } zpub
* /
getXpub() {
if ( this . _xpub ) {
return this . _xpub ; // cache hit
}
// first, getting xpub
2021-04-15 20:52:48 +03:00
const seed = this . _getSeed ( ) ;
2022-01-17 15:22:15 +00:00
const root = bip32 . fromSeed ( seed ) ;
2019-12-23 23:11:00 +00:00
2021-09-23 16:05:10 +03:00
const path = this . getDerivationPath ( ) ;
2022-09-03 00:48:07 +01:00
if ( ! path ) {
throw new Error ( 'Internal error: no path' ) ;
}
2019-12-23 23:11:00 +00:00
const child = root . derivePath ( path ) . neutered ( ) ;
const xpub = child . toBase58 ( ) ;
// bitcoinjs does not support zpub yet, so we just convert it from xpub
let data = b58 . decode ( xpub ) ;
data = data . slice ( 4 ) ;
data = Buffer . concat ( [ Buffer . from ( '04b24746' , 'hex' ) , data ] ) ;
this . _xpub = b58 . encode ( data ) ;
return this . _xpub ;
}
/ * *
* @inheritDoc
* /
async fetchTransactions() {
// if txs are absent for some internal address in hierarchy - this is a sign
// we should fetch txs for that address
// OR if some address has unconfirmed balance - should fetch it's txs
// OR some tx for address is unconfirmed
// OR some tx has < 7 confirmations
// fetching transactions in batch: first, getting batch history for all addresses,
// then batch fetching all involved txids
// finally, batch fetching txids of all inputs (needed to see amounts & addresses of those inputs)
// then we combine it all together
2020-06-01 15:54:23 +03:00
const addresses2fetch = [ ] ;
2019-12-23 23:11:00 +00:00
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
// external addresses first
let hasUnconfirmed = false ;
this . _txs_by_external_index [ c ] = this . _txs_by_external_index [ c ] || [ ] ;
2020-06-01 15:54:23 +03:00
for ( const tx of this . _txs_by_external_index [ c ] ) hasUnconfirmed = hasUnconfirmed || ! tx . confirmations || tx . confirmations < 7 ;
2019-12-23 23:11:00 +00:00
if ( hasUnconfirmed || this . _txs_by_external_index [ c ] . length === 0 || this . _balances_by_external_index [ c ] . u !== 0 ) {
addresses2fetch . push ( this . _getExternalAddressByIndex ( c ) ) ;
}
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
// next, internal addresses
let hasUnconfirmed = false ;
this . _txs_by_internal_index [ c ] = this . _txs_by_internal_index [ c ] || [ ] ;
2020-06-01 15:54:23 +03:00
for ( const tx of this . _txs_by_internal_index [ c ] ) hasUnconfirmed = hasUnconfirmed || ! tx . confirmations || tx . confirmations < 7 ;
2019-12-23 23:11:00 +00:00
if ( hasUnconfirmed || this . _txs_by_internal_index [ c ] . length === 0 || this . _balances_by_internal_index [ c ] . u !== 0 ) {
addresses2fetch . push ( this . _getInternalAddressByIndex ( c ) ) ;
}
}
2022-12-11 21:34:50 -05:00
// next, bip47 addresses
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + this . gap_limit ; c ++ ) {
2022-12-11 21:34:50 -05:00
let hasUnconfirmed = false ;
this . _txs_by_payment_code_index [ pc ] = this . _txs_by_payment_code_index [ pc ] || { } ;
this . _txs_by_payment_code_index [ pc ] [ c ] = this . _txs_by_payment_code_index [ pc ] [ c ] || [ ] ;
for ( const tx of this . _txs_by_payment_code_index [ pc ] [ c ] )
hasUnconfirmed = hasUnconfirmed || ! tx . confirmations || tx . confirmations < 7 ;
if ( hasUnconfirmed || this . _txs_by_payment_code_index [ pc ] [ c ] . length === 0 || this . _balances_by_payment_code_index [ pc ] . u !== 0 ) {
2024-04-26 23:49:19 +01:00
addresses2fetch . push ( this . _getBIP47AddressReceive ( pc , c ) ) ;
2022-12-11 21:34:50 -05:00
}
}
}
2019-12-23 23:11:00 +00:00
// first: batch fetch for all addresses histories
2020-06-01 15:54:23 +03:00
const histories = await BlueElectrum . multiGetHistoryByAddress ( addresses2fetch ) ;
2022-09-10 12:11:28 +01:00
const txs : Record < string , ElectrumHistory > = { } ;
2020-06-01 15:54:23 +03:00
for ( const history of Object . values ( histories ) ) {
2022-09-03 00:48:07 +01:00
for ( const tx of history as ElectrumHistory [ ] ) {
2019-12-23 23:11:00 +00:00
txs [ tx . tx_hash ] = tx ;
}
}
// next, batch fetching each txid we got
2024-03-25 00:07:43 +03:00
const txdatas = await BlueElectrum . multiGetTransactionByTxid ( Object . keys ( txs ) , true ) ;
2019-12-23 23:11:00 +00:00
// now, tricky part. we collect all transactions from inputs (vin), and batch fetch them too.
// then we combine all this data (we need inputs to see source addresses and amounts)
2020-06-01 15:54:23 +03:00
const vinTxids = [ ] ;
2022-09-11 11:59:00 +01:00
for ( const txdata of Object . values ( txdatas ) ) {
2020-06-01 15:54:23 +03:00
for ( const vin of txdata . vin ) {
2023-12-24 22:27:50 +00:00
vin . txid && vinTxids . push ( vin . txid ) ;
// ^^^^ not all inputs have txid, some of them are Coinbase (newly-created coins)
2019-12-23 23:11:00 +00:00
}
}
2024-03-25 00:07:43 +03:00
const vintxdatas = await BlueElectrum . multiGetTransactionByTxid ( vinTxids , true ) ;
2019-12-23 23:11:00 +00:00
// fetched all transactions from our inputs. now we need to combine it.
// iterating all _our_ transactions:
2020-06-01 15:54:23 +03:00
for ( const txid of Object . keys ( txdatas ) ) {
2019-12-23 23:11:00 +00:00
// iterating all inputs our our single transaction:
for ( let inpNum = 0 ; inpNum < txdatas [ txid ] . vin . length ; inpNum ++ ) {
2020-06-01 15:54:23 +03:00
const inpTxid = txdatas [ txid ] . vin [ inpNum ] . txid ;
const inpVout = txdatas [ txid ] . vin [ inpNum ] . vout ;
2019-12-23 23:11:00 +00:00
// got txid and output number of _previous_ transaction we shoud look into
2023-04-06 19:29:34 +03:00
if ( vintxdatas [ inpTxid ] ? . vout [ inpVout ] ) {
2019-12-23 23:11:00 +00:00
// extracting amount & addresses from previous output and adding it to _our_ input:
txdatas [ txid ] . vin [ inpNum ] . addresses = vintxdatas [ inpTxid ] . vout [ inpVout ] . scriptPubKey . addresses ;
txdatas [ txid ] . vin [ inpNum ] . value = vintxdatas [ inpTxid ] . vout [ inpVout ] . value ;
}
}
}
// now purge all unconfirmed txs from internal hashmaps, since some may be evicted from mempool because they became invalid
// or replaced. hashmaps are going to be re-populated anyways, since we fetched TXs for addresses with unconfirmed TXs
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
this . _txs_by_external_index [ c ] = this . _txs_by_external_index [ c ] . filter ( tx = > ! ! tx . confirmations ) ;
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
this . _txs_by_internal_index [ c ] = this . _txs_by_internal_index [ c ] . filter ( tx = > ! ! tx . confirmations ) ;
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + this . gap_limit ; c ++ ) {
2022-12-11 21:34:50 -05:00
this . _txs_by_payment_code_index [ pc ] [ c ] = this . _txs_by_payment_code_index [ pc ] [ c ] . filter ( tx = > ! ! tx . confirmations ) ;
}
}
2019-12-23 23:11:00 +00:00
2024-04-26 23:49:19 +01:00
// now, we need to put transactions in all relevant `cells` of internal hashmaps:
// this._txs_by_internal_index, this._txs_by_external_index & this._txs_by_payment_code_index
2019-12-23 23:11:00 +00:00
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
2022-09-11 11:59:00 +01:00
for ( const tx of Object . values ( txdatas ) ) {
2020-06-01 15:54:23 +03:00
for ( const vin of tx . vin ) {
2019-12-23 23:11:00 +00:00
if ( vin . addresses && vin . addresses . indexOf ( this . _getExternalAddressByIndex ( c ) ) !== - 1 ) {
// this TX is related to our address
this . _txs_by_external_index [ c ] = this . _txs_by_external_index [ c ] || [ ] ;
2022-09-10 12:11:28 +01:00
const { vin : txVin , vout : txVout , . . . txRest } = tx ;
const clonedTx = { . . . txRest , inputs : txVin.slice ( 0 ) , outputs : txVout.slice ( 0 ) } ;
2019-12-23 23:11:00 +00:00
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false ;
for ( let cc = 0 ; cc < this . _txs_by_external_index [ c ] . length ; cc ++ ) {
if ( this . _txs_by_external_index [ c ] [ cc ] . txid === clonedTx . txid ) {
replaced = true ;
this . _txs_by_external_index [ c ] [ cc ] = clonedTx ;
}
}
if ( ! replaced ) this . _txs_by_external_index [ c ] . push ( clonedTx ) ;
}
}
2020-06-01 15:54:23 +03:00
for ( const vout of tx . vout ) {
2020-05-19 13:58:30 +01:00
if ( vout . scriptPubKey . addresses && vout . scriptPubKey . addresses . indexOf ( this . _getExternalAddressByIndex ( c ) ) !== - 1 ) {
2019-12-23 23:11:00 +00:00
// this TX is related to our address
this . _txs_by_external_index [ c ] = this . _txs_by_external_index [ c ] || [ ] ;
2022-09-10 12:11:28 +01:00
const { vin : txVin , vout : txVout , . . . txRest } = tx ;
const clonedTx = { . . . txRest , inputs : txVin.slice ( 0 ) , outputs : txVout.slice ( 0 ) } ;
2019-12-23 23:11:00 +00:00
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false ;
for ( let cc = 0 ; cc < this . _txs_by_external_index [ c ] . length ; cc ++ ) {
if ( this . _txs_by_external_index [ c ] [ cc ] . txid === clonedTx . txid ) {
replaced = true ;
this . _txs_by_external_index [ c ] [ cc ] = clonedTx ;
}
}
if ( ! replaced ) this . _txs_by_external_index [ c ] . push ( clonedTx ) ;
}
}
}
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
2022-09-11 11:59:00 +01:00
for ( const tx of Object . values ( txdatas ) ) {
2020-06-01 15:54:23 +03:00
for ( const vin of tx . vin ) {
2019-12-23 23:11:00 +00:00
if ( vin . addresses && vin . addresses . indexOf ( this . _getInternalAddressByIndex ( c ) ) !== - 1 ) {
// this TX is related to our address
this . _txs_by_internal_index [ c ] = this . _txs_by_internal_index [ c ] || [ ] ;
2022-09-10 12:11:28 +01:00
const { vin : txVin , vout : txVout , . . . txRest } = tx ;
const clonedTx = { . . . txRest , inputs : txVin.slice ( 0 ) , outputs : txVout.slice ( 0 ) } ;
2019-12-23 23:11:00 +00:00
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false ;
for ( let cc = 0 ; cc < this . _txs_by_internal_index [ c ] . length ; cc ++ ) {
if ( this . _txs_by_internal_index [ c ] [ cc ] . txid === clonedTx . txid ) {
replaced = true ;
this . _txs_by_internal_index [ c ] [ cc ] = clonedTx ;
}
}
if ( ! replaced ) this . _txs_by_internal_index [ c ] . push ( clonedTx ) ;
}
}
2020-06-01 15:54:23 +03:00
for ( const vout of tx . vout ) {
2020-05-19 13:58:30 +01:00
if ( vout . scriptPubKey . addresses && vout . scriptPubKey . addresses . indexOf ( this . _getInternalAddressByIndex ( c ) ) !== - 1 ) {
2019-12-23 23:11:00 +00:00
// this TX is related to our address
this . _txs_by_internal_index [ c ] = this . _txs_by_internal_index [ c ] || [ ] ;
2022-09-10 12:11:28 +01:00
const { vin : txVin , vout : txVout , . . . txRest } = tx ;
const clonedTx = { . . . txRest , inputs : txVin.slice ( 0 ) , outputs : txVout.slice ( 0 ) } ;
2019-12-23 23:11:00 +00:00
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false ;
for ( let cc = 0 ; cc < this . _txs_by_internal_index [ c ] . length ; cc ++ ) {
if ( this . _txs_by_internal_index [ c ] [ cc ] . txid === clonedTx . txid ) {
replaced = true ;
this . _txs_by_internal_index [ c ] [ cc ] = clonedTx ;
}
}
if ( ! replaced ) this . _txs_by_internal_index [ c ] . push ( clonedTx ) ;
}
}
}
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + this . gap_limit ; c ++ ) {
2022-12-11 21:34:50 -05:00
for ( const tx of Object . values ( txdatas ) ) {
for ( const vin of tx . vin ) {
2024-04-26 23:49:19 +01:00
if ( vin . addresses && vin . addresses . indexOf ( this . _getBIP47AddressReceive ( pc , c ) ) !== - 1 ) {
2022-12-11 21:34:50 -05:00
// this TX is related to our address
this . _txs_by_payment_code_index [ pc ] = this . _txs_by_payment_code_index [ pc ] || { } ;
this . _txs_by_payment_code_index [ pc ] [ c ] = this . _txs_by_payment_code_index [ pc ] [ c ] || [ ] ;
const { vin : txVin , vout : txVout , . . . txRest } = tx ;
const clonedTx = { . . . txRest , inputs : txVin.slice ( 0 ) , outputs : txVout.slice ( 0 ) } ;
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false ;
for ( let cc = 0 ; cc < this . _txs_by_payment_code_index [ pc ] [ c ] . length ; cc ++ ) {
if ( this . _txs_by_payment_code_index [ pc ] [ c ] [ cc ] . txid === clonedTx . txid ) {
replaced = true ;
this . _txs_by_payment_code_index [ pc ] [ c ] [ cc ] = clonedTx ;
}
}
if ( ! replaced ) this . _txs_by_payment_code_index [ pc ] [ c ] . push ( clonedTx ) ;
}
}
for ( const vout of tx . vout ) {
2024-04-26 23:49:19 +01:00
if ( vout . scriptPubKey . addresses && vout . scriptPubKey . addresses . indexOf ( this . _getBIP47AddressReceive ( pc , c ) ) !== - 1 ) {
2022-12-11 21:34:50 -05:00
// this TX is related to our address
this . _txs_by_payment_code_index [ pc ] = this . _txs_by_payment_code_index [ pc ] || { } ;
this . _txs_by_payment_code_index [ pc ] [ c ] = this . _txs_by_payment_code_index [ pc ] [ c ] || [ ] ;
const { vin : txVin , vout : txVout , . . . txRest } = tx ;
const clonedTx = { . . . txRest , inputs : txVin.slice ( 0 ) , outputs : txVout.slice ( 0 ) } ;
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false ;
2024-04-26 23:49:19 +01:00
for ( let cc = 0 ; cc < this . _txs_by_payment_code_index [ pc ] [ c ] . length ; cc ++ ) {
if ( this . _txs_by_payment_code_index [ pc ] [ c ] [ cc ] . txid === clonedTx . txid ) {
2022-12-11 21:34:50 -05:00
replaced = true ;
2024-04-26 23:49:19 +01:00
this . _txs_by_payment_code_index [ pc ] [ c ] [ cc ] = clonedTx ;
2022-12-11 21:34:50 -05:00
}
}
2024-04-26 23:49:19 +01:00
if ( ! replaced ) this . _txs_by_payment_code_index [ pc ] [ c ] . push ( clonedTx ) ;
2022-12-11 21:34:50 -05:00
}
}
}
}
}
2019-12-23 23:11:00 +00:00
this . _lastTxFetch = + new Date ( ) ;
}
getTransactions() {
2022-09-03 00:48:07 +01:00
let txs : Transaction [ ] = [ ] ;
2019-12-23 23:11:00 +00:00
2020-06-01 15:54:23 +03:00
for ( const addressTxs of Object . values ( this . _txs_by_external_index ) ) {
2019-12-23 23:11:00 +00:00
txs = txs . concat ( addressTxs ) ;
}
2020-06-01 15:54:23 +03:00
for ( const addressTxs of Object . values ( this . _txs_by_internal_index ) ) {
2019-12-23 23:11:00 +00:00
txs = txs . concat ( addressTxs ) ;
}
2024-04-26 23:49:19 +01:00
if ( this . _receive_payment_codes ) {
for ( const pc of this . _receive_payment_codes ) {
2022-12-11 21:34:50 -05:00
if ( this . _txs_by_payment_code_index [ pc ] )
for ( const addressTxs of Object . values ( this . _txs_by_payment_code_index [ pc ] ) ) {
txs = txs . concat ( addressTxs ) ;
}
}
}
2019-12-23 23:11:00 +00:00
2020-10-14 18:14:16 +01:00
if ( txs . length === 0 ) return [ ] ; // guard clause; so we wont spend time calculating addresses
2020-06-30 17:47:14 +01:00
// its faster to pre-build hashmap of owned addresses than to query `this.weOwnAddress()`, which in turn
// iterates over all addresses in hierarchy
2022-09-03 00:48:07 +01:00
const ownedAddressesHashmap : Record < string , boolean > = { } ;
2020-09-03 21:01:03 +01:00
for ( let c = 0 ; c < this . next_free_address_index + 1 ; c ++ ) {
2020-06-30 17:47:14 +01:00
ownedAddressesHashmap [ this . _getExternalAddressByIndex ( c ) ] = true ;
}
2020-09-03 21:01:03 +01:00
for ( let c = 0 ; c < this . next_free_change_address_index + 1 ; c ++ ) {
2020-06-30 17:47:14 +01:00
ownedAddressesHashmap [ this . _getInternalAddressByIndex ( c ) ] = true ;
}
2024-04-26 23:49:19 +01:00
if ( this . _receive_payment_codes )
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + 1 ; c ++ ) {
2024-04-26 23:49:19 +01:00
ownedAddressesHashmap [ this . _getBIP47AddressReceive ( pc , c ) ] = true ;
2022-12-11 22:13:36 -05:00
}
}
2020-06-30 17:47:14 +01:00
// hack: in case this code is called from LegacyWallet:
2022-09-03 00:48:07 +01:00
if ( this . getAddress ( ) ) ownedAddressesHashmap [ String ( this . getAddress ( ) ) ] = true ;
2020-06-30 17:47:14 +01:00
2022-09-03 00:48:07 +01:00
const ret : Transaction [ ] = [ ] ;
2020-06-01 15:54:23 +03:00
for ( const tx of txs ) {
2019-12-23 23:11:00 +00:00
tx . received = tx . blocktime * 1000 ;
if ( ! tx . blocktime ) tx . received = + new Date ( ) - 30 * 1000 ; // unconfirmed
tx . confirmations = tx . confirmations || 0 ; // unconfirmed
tx . hash = tx . txid ;
tx . value = 0 ;
2020-06-01 15:54:23 +03:00
for ( const vin of tx . inputs ) {
2019-12-23 23:11:00 +00:00
// if input (spending) goes from our address - we are loosing!
2023-04-06 19:29:34 +03:00
if ( ( vin . address && ownedAddressesHashmap [ vin . address ] ) || ( vin . addresses ? . [ 0 ] && ownedAddressesHashmap [ vin . addresses [ 0 ] ] ) ) {
2022-09-03 00:48:07 +01:00
tx . value -= new BigNumber ( vin . value ? ? 0 ) . multipliedBy ( 100000000 ) . toNumber ( ) ;
2019-12-23 23:11:00 +00:00
}
}
2020-06-01 15:54:23 +03:00
for ( const vout of tx . outputs ) {
2019-12-23 23:11:00 +00:00
// when output goes to our address - this means we are gaining!
2023-04-06 19:29:34 +03:00
if ( vout . scriptPubKey . addresses ? . [ 0 ] && ownedAddressesHashmap [ vout . scriptPubKey . addresses [ 0 ] ] ) {
2019-12-23 23:11:00 +00:00
tx . value += new BigNumber ( vout . value ) . multipliedBy ( 100000000 ) . toNumber ( ) ;
}
}
2024-05-05 20:27:43 +01:00
if ( this . allowBIP47 ( ) && this . isBIP47Enabled ( ) ) {
tx . counterparty = this . getBip47CounterpartyByTx ( tx ) ;
}
2019-12-23 23:11:00 +00:00
ret . push ( tx ) ;
}
// now, deduplication:
2022-09-03 00:48:07 +01:00
const usedTxIds : Record < string , number > = { } ;
2020-06-01 15:54:23 +03:00
const ret2 = [ ] ;
for ( const tx of ret ) {
2019-12-23 23:11:00 +00:00
if ( ! usedTxIds [ tx . txid ] ) ret2 . push ( tx ) ;
usedTxIds [ tx . txid ] = 1 ;
}
2020-06-01 15:54:23 +03:00
return ret2 . sort ( function ( a , b ) {
2022-09-03 00:48:07 +01:00
return Number ( b . received ) - Number ( a . received ) ;
2019-12-23 23:11:00 +00:00
} ) ;
}
2022-09-03 00:48:07 +01:00
async _binarySearchIterationForInternalAddress ( index : number ) {
const gerenateChunkAddresses = ( chunkNum : number ) = > {
2020-06-01 15:54:23 +03:00
const ret = [ ] ;
2019-12-23 23:11:00 +00:00
for ( let c = this . gap_limit * chunkNum ; c < this . gap_limit * ( chunkNum + 1 ) ; c ++ ) {
ret . push ( this . _getInternalAddressByIndex ( c ) ) ;
}
return ret ;
} ;
let lastChunkWithUsedAddressesNum = null ;
let lastHistoriesWithUsedAddresses = null ;
for ( let c = 0 ; c < Math . round ( index / this . gap_limit ) ; c ++ ) {
2020-06-01 15:54:23 +03:00
const histories = await BlueElectrum . multiGetHistoryByAddress ( gerenateChunkAddresses ( c ) ) ;
2022-09-03 00:48:07 +01:00
// @ts-ignore
2019-12-23 23:11:00 +00:00
if ( this . constructor . _getTransactionsFromHistories ( histories ) . length > 0 ) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c ;
lastHistoriesWithUsedAddresses = histories ;
} else {
// empty chunk. no sense searching more chunks
break ;
}
}
let lastUsedIndex = 0 ;
if ( lastHistoriesWithUsedAddresses ) {
// now searching for last used address in batch lastChunkWithUsedAddressesNum
for (
2022-09-03 00:48:07 +01:00
let c = Number ( lastChunkWithUsedAddressesNum ) * this . gap_limit ;
c < Number ( lastChunkWithUsedAddressesNum ) * this . gap_limit + this . gap_limit ;
2019-12-23 23:11:00 +00:00
c ++
) {
2020-06-01 15:54:23 +03:00
const address = this . _getInternalAddressByIndex ( c ) ;
2019-12-23 23:11:00 +00:00
if ( lastHistoriesWithUsedAddresses [ address ] && lastHistoriesWithUsedAddresses [ address ] . length > 0 ) {
2022-12-12 22:09:04 -05:00
lastUsedIndex = Math . max ( c , lastUsedIndex ) + 1 ; // point to next, which is supposed to be unused
2019-12-23 23:11:00 +00:00
}
}
}
return lastUsedIndex ;
}
2022-09-03 00:48:07 +01:00
async _binarySearchIterationForExternalAddress ( index : number ) {
const gerenateChunkAddresses = ( chunkNum : number ) = > {
2020-06-01 15:54:23 +03:00
const ret = [ ] ;
2019-12-23 23:11:00 +00:00
for ( let c = this . gap_limit * chunkNum ; c < this . gap_limit * ( chunkNum + 1 ) ; c ++ ) {
ret . push ( this . _getExternalAddressByIndex ( c ) ) ;
}
return ret ;
} ;
let lastChunkWithUsedAddressesNum = null ;
let lastHistoriesWithUsedAddresses = null ;
for ( let c = 0 ; c < Math . round ( index / this . gap_limit ) ; c ++ ) {
2020-06-01 15:54:23 +03:00
const histories = await BlueElectrum . multiGetHistoryByAddress ( gerenateChunkAddresses ( c ) ) ;
2022-09-03 00:48:07 +01:00
// @ts-ignore
2019-12-23 23:11:00 +00:00
if ( this . constructor . _getTransactionsFromHistories ( histories ) . length > 0 ) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c ;
lastHistoriesWithUsedAddresses = histories ;
} else {
// empty chunk. no sense searching more chunks
break ;
}
}
let lastUsedIndex = 0 ;
if ( lastHistoriesWithUsedAddresses ) {
// now searching for last used address in batch lastChunkWithUsedAddressesNum
for (
2022-09-03 00:48:07 +01:00
let c = Number ( lastChunkWithUsedAddressesNum ) * this . gap_limit ;
c < Number ( lastChunkWithUsedAddressesNum ) * this . gap_limit + this . gap_limit ;
2019-12-23 23:11:00 +00:00
c ++
) {
2020-06-01 15:54:23 +03:00
const address = this . _getExternalAddressByIndex ( c ) ;
2019-12-23 23:11:00 +00:00
if ( lastHistoriesWithUsedAddresses [ address ] && lastHistoriesWithUsedAddresses [ address ] . length > 0 ) {
2022-12-12 22:09:04 -05:00
lastUsedIndex = Math . max ( c , lastUsedIndex ) + 1 ; // point to next, which is supposed to be unused
2019-12-23 23:11:00 +00:00
}
}
}
return lastUsedIndex ;
}
2022-12-11 21:34:50 -05:00
async _binarySearchIterationForBIP47Address ( paymentCode : string , index : number ) {
const generateChunkAddresses = ( chunkNum : number ) = > {
const ret = [ ] ;
for ( let c = this . gap_limit * chunkNum ; c < this . gap_limit * ( chunkNum + 1 ) ; c ++ ) {
2024-04-26 23:49:19 +01:00
ret . push ( this . _getBIP47AddressReceive ( paymentCode , c ) ) ;
2022-12-11 21:34:50 -05:00
}
return ret ;
} ;
let lastChunkWithUsedAddressesNum = null ;
let lastHistoriesWithUsedAddresses = null ;
for ( let c = 0 ; c < Math . round ( index / this . gap_limit ) ; c ++ ) {
const histories = await BlueElectrum . multiGetHistoryByAddress ( generateChunkAddresses ( c ) ) ;
// @ts-ignore
if ( this . constructor . _getTransactionsFromHistories ( histories ) . length > 0 ) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c ;
lastHistoriesWithUsedAddresses = histories ;
} else {
// empty chunk. no sense searching more chunks
break ;
}
}
let lastUsedIndex = 0 ;
if ( lastHistoriesWithUsedAddresses ) {
// now searching for last used address in batch lastChunkWithUsedAddressesNum
for (
let c = Number ( lastChunkWithUsedAddressesNum ) * this . gap_limit ;
c < Number ( lastChunkWithUsedAddressesNum ) * this . gap_limit + this . gap_limit ;
c ++
) {
2024-04-26 23:49:19 +01:00
const address = this . _getBIP47AddressReceive ( paymentCode , c ) ;
2022-12-11 21:34:50 -05:00
if ( lastHistoriesWithUsedAddresses [ address ] && lastHistoriesWithUsedAddresses [ address ] . length > 0 ) {
2022-12-12 22:09:04 -05:00
lastUsedIndex = Math . max ( c , lastUsedIndex ) + 1 ; // point to next, which is supposed to be unused
2019-12-23 23:11:00 +00:00
}
}
}
return lastUsedIndex ;
}
async fetchBalance() {
try {
if ( this . next_free_change_address_index === 0 && this . next_free_address_index === 0 ) {
// doing binary search for last used address:
this . next_free_change_address_index = await this . _binarySearchIterationForInternalAddress ( 1000 ) ;
this . next_free_address_index = await this . _binarySearchIterationForExternalAddress ( 1000 ) ;
2024-04-26 23:49:19 +01:00
if ( this . _receive_payment_codes ) {
for ( const pc of this . _receive_payment_codes ) {
this . _next_free_payment_code_address_index_receive [ pc ] = await this . _binarySearchIterationForBIP47Address ( pc , 1000 ) ;
2022-12-11 21:34:50 -05:00
}
}
2019-12-23 23:11:00 +00:00
} // end rescanning fresh wallet
// finally fetching balance
await this . _fetchBalance ( ) ;
} catch ( err ) {
console . warn ( err ) ;
}
}
async _fetchBalance() {
// probing future addressess in hierarchy whether they have any transactions, in case
// our 'next free addr' pointers are lagging behind
2020-09-20 21:22:22 +01:00
// for that we are gona batch fetch history for all addresses between last used and last used + gap_limit
2019-12-23 23:11:00 +00:00
2020-09-20 21:22:22 +01:00
const lagAddressesToFetch = [ ] ;
for ( let c = this . next_free_address_index ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
lagAddressesToFetch . push ( this . _getExternalAddressByIndex ( c ) ) ;
}
for ( let c = this . next_free_change_address_index ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
lagAddressesToFetch . push ( this . _getInternalAddressByIndex ( c ) ) ;
2019-12-23 23:11:00 +00:00
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2023-02-09 21:02:30 -05:00
for (
2024-04-26 23:49:19 +01:00
let c = this . _next_free_payment_code_address_index_receive [ pc ] ;
c < this . _next_free_payment_code_address_index_receive [ pc ] + this . gap_limit ;
2023-02-09 21:02:30 -05:00
c ++
) {
2024-04-26 23:49:19 +01:00
lagAddressesToFetch . push ( this . _getBIP47AddressReceive ( pc , c ) ) ;
2022-12-11 21:34:50 -05:00
}
}
2019-12-23 23:11:00 +00:00
2020-09-20 21:22:22 +01:00
const txs = await BlueElectrum . multiGetHistoryByAddress ( lagAddressesToFetch ) ; // <------ electrum call
2019-12-23 23:11:00 +00:00
2020-09-20 21:22:22 +01:00
for ( let c = this . next_free_address_index ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
const address = this . _getExternalAddressByIndex ( c ) ;
if ( txs [ address ] && Array . isArray ( txs [ address ] ) && txs [ address ] . length > 0 ) {
// whoa, someone uses our wallet outside! better catch up
this . next_free_address_index = c + 1 ;
}
}
for ( let c = this . next_free_change_address_index ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
const address = this . _getInternalAddressByIndex ( c ) ;
if ( txs [ address ] && Array . isArray ( txs [ address ] ) && txs [ address ] . length > 0 ) {
// whoa, someone uses our wallet outside! better catch up
this . next_free_change_address_index = c + 1 ;
}
}
2019-12-23 23:11:00 +00:00
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2023-02-09 21:02:30 -05:00
for (
2024-04-26 23:49:19 +01:00
let c = this . _next_free_payment_code_address_index_receive [ pc ] ;
c < this . _next_free_payment_code_address_index_receive [ pc ] + this . gap_limit ;
2023-02-09 21:02:30 -05:00
c ++
) {
2024-04-26 23:49:19 +01:00
const address = this . _getBIP47AddressReceive ( pc , c ) ;
2022-12-11 21:34:50 -05:00
if ( txs [ address ] && Array . isArray ( txs [ address ] ) && txs [ address ] . length > 0 ) {
// whoa, someone uses our wallet outside! better catch up
2024-04-26 23:49:19 +01:00
this . _next_free_payment_code_address_index_receive [ pc ] = c + 1 ;
2022-12-11 21:34:50 -05:00
}
}
}
2019-12-23 23:11:00 +00:00
// next, business as usuall. fetch balances
2020-06-01 15:54:23 +03:00
const addresses2fetch = [ ] ;
2019-12-23 23:11:00 +00:00
// generating all involved addresses.
// basically, refetch all from index zero to maximum. doesnt matter
// since we batch them 100 per call
// external
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
addresses2fetch . push ( this . _getExternalAddressByIndex ( c ) ) ;
}
// internal
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
addresses2fetch . push ( this . _getInternalAddressByIndex ( c ) ) ;
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
for ( let c = 0 ; c < this . _next_free_payment_code_address_index_receive [ pc ] + this . gap_limit ; c ++ ) {
addresses2fetch . push ( this . _getBIP47AddressReceive ( pc , c ) ) ;
2022-12-11 21:34:50 -05:00
}
}
2020-06-01 15:54:23 +03:00
const balances = await BlueElectrum . multiGetBalanceByAddress ( addresses2fetch ) ;
2019-12-23 23:11:00 +00:00
// converting to a more compact internal format
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
2020-06-01 15:54:23 +03:00
const addr = this . _getExternalAddressByIndex ( c ) ;
2019-12-23 23:11:00 +00:00
if ( balances . addresses [ addr ] ) {
// first, if balances differ from what we store - we delete transactions for that
// address so next fetchTransactions() will refetch everything
if ( this . _balances_by_external_index [ c ] ) {
if (
this . _balances_by_external_index [ c ] . c !== balances . addresses [ addr ] . confirmed ||
this . _balances_by_external_index [ c ] . u !== balances . addresses [ addr ] . unconfirmed
) {
delete this . _txs_by_external_index [ c ] ;
}
}
// update local representation of balances on that address:
this . _balances_by_external_index [ c ] = {
c : balances.addresses [ addr ] . confirmed ,
u : balances.addresses [ addr ] . unconfirmed ,
} ;
}
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
2020-06-01 15:54:23 +03:00
const addr = this . _getInternalAddressByIndex ( c ) ;
2019-12-23 23:11:00 +00:00
if ( balances . addresses [ addr ] ) {
// first, if balances differ from what we store - we delete transactions for that
// address so next fetchTransactions() will refetch everything
if ( this . _balances_by_internal_index [ c ] ) {
if (
this . _balances_by_internal_index [ c ] . c !== balances . addresses [ addr ] . confirmed ||
this . _balances_by_internal_index [ c ] . u !== balances . addresses [ addr ] . unconfirmed
) {
delete this . _txs_by_internal_index [ c ] ;
}
}
// update local representation of balances on that address:
this . _balances_by_internal_index [ c ] = {
c : balances.addresses [ addr ] . confirmed ,
u : balances.addresses [ addr ] . unconfirmed ,
} ;
}
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2022-12-12 22:09:04 -05:00
let confirmed = 0 ;
let unconfirmed = 0 ;
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + this . gap_limit ; c ++ ) {
2024-04-26 23:49:19 +01:00
const addr = this . _getBIP47AddressReceive ( pc , c ) ;
2022-12-12 22:09:04 -05:00
if ( balances . addresses [ addr ] . confirmed || balances . addresses [ addr ] . unconfirmed ) {
confirmed = confirmed + balances . addresses [ addr ] . confirmed ;
unconfirmed = unconfirmed + balances . addresses [ addr ] . unconfirmed ;
2022-12-11 21:34:50 -05:00
}
}
2022-12-12 22:09:04 -05:00
this . _balances_by_payment_code_index [ pc ] = {
c : confirmed ,
u : unconfirmed ,
} ;
2022-12-11 21:34:50 -05:00
}
2019-12-23 23:11:00 +00:00
this . _lastBalanceFetch = + new Date ( ) ;
}
2024-02-23 07:32:47 +00:00
async fetchUtxo ( ) : Promise < void > {
2020-03-12 13:08:17 +00:00
// fetching utxo of addresses that only have some balance
2019-12-23 23:11:00 +00:00
let addressess = [ ] ;
2020-03-12 13:08:17 +00:00
// considering confirmed balance:
2019-12-23 23:11:00 +00:00
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
2023-04-06 19:29:34 +03:00
if ( this . _balances_by_external_index ? . [ c ] ? . c > 0 ) {
2019-12-23 23:11:00 +00:00
addressess . push ( this . _getExternalAddressByIndex ( c ) ) ;
}
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
2023-04-06 19:29:34 +03:00
if ( this . _balances_by_internal_index ? . [ c ] ? . c > 0 ) {
2019-12-23 23:11:00 +00:00
addressess . push ( this . _getInternalAddressByIndex ( c ) ) ;
}
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
for ( let c = 0 ; c < this . _next_free_payment_code_address_index_receive [ pc ] + this . gap_limit ; c ++ ) {
2023-04-06 19:29:34 +03:00
if ( this . _balances_by_payment_code_index ? . [ pc ] ? . c > 0 ) {
2024-04-26 23:49:19 +01:00
addressess . push ( this . _getBIP47AddressReceive ( pc , c ) ) ;
2022-12-16 18:55:31 -05:00
}
}
}
2020-03-12 13:08:17 +00:00
// considering UNconfirmed balance:
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
2023-04-06 19:29:34 +03:00
if ( this . _balances_by_external_index ? . [ c ] ? . u > 0 ) {
2020-03-12 13:08:17 +00:00
addressess . push ( this . _getExternalAddressByIndex ( c ) ) ;
}
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
2023-04-06 19:29:34 +03:00
if ( this . _balances_by_internal_index ? . [ c ] ? . u > 0 ) {
2020-03-12 13:08:17 +00:00
addressess . push ( this . _getInternalAddressByIndex ( c ) ) ;
}
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
for ( let c = 0 ; c < this . _next_free_payment_code_address_index_receive [ pc ] + this . gap_limit ; c ++ ) {
2023-04-06 19:29:34 +03:00
if ( this . _balances_by_payment_code_index ? . [ pc ] ? . u > 0 ) {
2024-04-26 23:49:19 +01:00
addressess . push ( this . _getBIP47AddressReceive ( pc , c ) ) ;
2022-12-16 18:55:31 -05:00
}
}
}
2020-03-12 13:08:17 +00:00
// note: we could remove checks `.c` and `.u` to simplify code, but the resulting `addressess` array would be bigger, thus bigger batch
// to fetch (or maybe even several fetches), which is not critical but undesirable.
// anyway, result has `.confirmations` property for each utxo, so outside caller can easily filter out unconfirmed if he wants to
addressess = [ . . . new Set ( addressess ) ] ; // deduplicate just for any case
2020-10-05 18:53:47 +01:00
const fetchedUtxo = await BlueElectrum . multiGetUtxoByAddress ( addressess ) ;
2019-12-23 23:11:00 +00:00
this . _utxo = [ ] ;
2020-10-05 18:53:47 +01:00
for ( const arr of Object . values ( fetchedUtxo ) ) {
2019-12-23 23:11:00 +00:00
this . _utxo = this . _utxo . concat ( arr ) ;
}
// this belongs in `.getUtxo()`
2024-02-25 17:28:44 +00:00
for ( const u of this . _utxo ) {
2019-12-23 23:11:00 +00:00
u . wif = this . _getWifForAddress ( u . address ) ;
2021-01-23 20:38:14 -05:00
if ( ! u . confirmations && u . height ) u . confirmations = BlueElectrum . estimateCurrentBlockheight ( ) - u . height ;
2019-12-23 23:11:00 +00:00
}
2020-03-09 18:44:42 +00:00
2024-03-24 00:31:49 +03:00
this . _utxo = this . _utxo . sort ( ( a , b ) = > Number ( a . value ) - Number ( b . value ) ) ;
2020-03-09 18:44:42 +00:00
// more consistent, so txhex in unit tests wont change
2019-12-23 23:11:00 +00:00
}
2020-03-12 13:08:17 +00:00
/ * *
* Getter for previously fetched UTXO . For example :
* [ { height : 0 ,
* value : 666 ,
* address : 'string' ,
* vout : 1 ,
* txid : 'string' ,
* wif : 'string' ,
* confirmations : 0 } ]
*
2020-11-22 10:43:11 +03:00
* @param respectFrozen { boolean } Add Frozen outputs
2020-03-12 13:08:17 +00:00
* @returns { [ ] }
* /
2020-11-22 10:43:11 +03:00
getUtxo ( respectFrozen = false ) {
2020-10-23 13:49:17 +03:00
let ret = [ ] ;
if ( this . _utxo . length === 0 ) {
ret = this . getDerivedUtxoFromOurTransaction ( ) ; // oy vey, no stored utxo. lets attempt to derive it from stored transactions
} else {
ret = this . _utxo ;
}
2020-11-22 10:43:11 +03:00
if ( ! respectFrozen ) {
2020-10-25 12:04:04 +03:00
ret = ret . filter ( ( { txid , vout } ) = > ! this . getUTXOMetadata ( txid , vout ) . frozen ) ;
2020-10-23 13:49:17 +03:00
}
return ret ;
2019-12-23 23:11:00 +00:00
}
2022-09-03 00:48:07 +01:00
getDerivedUtxoFromOurTransaction ( returnSpentUtxoAsWell = false ) : Utxo [ ] {
const utxos : Utxo [ ] = [ ] ;
2020-09-21 20:32:20 +01:00
// its faster to pre-build hashmap of owned addresses than to query `this.weOwnAddress()`, which in turn
// iterates over all addresses in hierarchy
2022-09-03 00:48:07 +01:00
const ownedAddressesHashmap : Record < string , boolean > = { } ;
2020-09-21 20:32:20 +01:00
for ( let c = 0 ; c < this . next_free_address_index + 1 ; c ++ ) {
ownedAddressesHashmap [ this . _getExternalAddressByIndex ( c ) ] = true ;
}
for ( let c = 0 ; c < this . next_free_change_address_index + 1 ; c ++ ) {
ownedAddressesHashmap [ this . _getInternalAddressByIndex ( c ) ] = true ;
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + 1 ; c ++ ) {
2024-04-26 23:49:19 +01:00
ownedAddressesHashmap [ this . _getBIP47AddressReceive ( pc , c ) ] = true ;
2022-12-11 21:34:50 -05:00
}
}
2020-09-21 20:32:20 +01:00
2020-06-01 15:54:23 +03:00
for ( const tx of this . getTransactions ( ) ) {
for ( const output of tx . outputs ) {
2022-09-03 00:48:07 +01:00
let address : string | false = false ;
2020-04-03 13:10:53 +01:00
if ( output . scriptPubKey && output . scriptPubKey . addresses && output . scriptPubKey . addresses [ 0 ] ) {
address = output . scriptPubKey . addresses [ 0 ] ;
}
2022-09-03 00:48:07 +01:00
if ( ownedAddressesHashmap [ String ( address ) ] ) {
2020-06-01 15:54:23 +03:00
const value = new BigNumber ( output . value ) . multipliedBy ( 100000000 ) . toNumber ( ) ;
2020-04-03 13:10:53 +01:00
utxos . push ( {
txid : tx.txid ,
vout : output.n ,
2022-09-03 00:48:07 +01:00
address : String ( address ) ,
2020-04-03 13:10:53 +01:00
value ,
confirmations : tx.confirmations ,
2020-10-05 18:53:47 +01:00
wif : false ,
2022-09-03 00:48:07 +01:00
height : BlueElectrum.estimateCurrentBlockheight ( ) - ( tx . confirmations ? ? 0 ) ,
2020-04-03 13:10:53 +01:00
} ) ;
}
}
}
2020-09-21 20:32:20 +01:00
if ( returnSpentUtxoAsWell ) return utxos ;
2020-04-03 13:10:53 +01:00
// got all utxos we ever had. lets filter out the ones that are spent:
2020-06-01 15:54:23 +03:00
const ret = [ ] ;
for ( const utxo of utxos ) {
2020-04-03 13:10:53 +01:00
let spent = false ;
2020-06-01 15:54:23 +03:00
for ( const tx of this . getTransactions ( ) ) {
for ( const input of tx . inputs ) {
2020-04-03 13:10:53 +01:00
if ( input . txid === utxo . txid && input . vout === utxo . vout ) spent = true ;
// utxo we got previously was actually spent right here ^^
}
}
2020-10-05 18:53:47 +01:00
if ( ! spent ) {
// filling WIFs only for legit unspent UTXO, as it is a slow operation
utxo . wif = this . _getWifForAddress ( utxo . address ) ;
ret . push ( utxo ) ;
}
2020-04-03 13:10:53 +01:00
}
return ret ;
}
2022-09-03 00:48:07 +01:00
_getDerivationPathByAddress ( address : string ) : string | false {
2021-09-23 16:05:10 +03:00
const path = this . getDerivationPath ( ) ;
2019-12-23 23:11:00 +00:00
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
if ( this . _getExternalAddressByIndex ( c ) === address ) return path + '/0/' + c ;
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
if ( this . _getInternalAddressByIndex ( c ) === address ) return path + '/1/' + c ;
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + this . gap_limit ; c ++ ) {
2023-03-18 18:42:55 +00:00
// not technically correct but well, to have at least somethign in PSBT...
2024-04-26 23:49:19 +01:00
if ( this . _getBIP47AddressReceive ( pc , c ) === address ) return "m/47'/0'/0'/" + c ;
2023-03-18 18:42:55 +00:00
}
}
2019-12-23 23:11:00 +00:00
return false ;
}
2020-04-22 16:13:18 +01:00
/ * *
*
* @param address { string } Address that belongs to this wallet
2022-09-03 00:48:07 +01:00
* @returns { Buffer | false } Either buffer with pubkey or false
2020-04-22 16:13:18 +01:00
* /
2022-09-03 00:48:07 +01:00
_getPubkeyByAddress ( address : string ) : Buffer | false {
2019-12-23 23:11:00 +00:00
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
if ( this . _getExternalAddressByIndex ( c ) === address ) return this . _getNodePubkeyByIndex ( 0 , c ) ;
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
if ( this . _getInternalAddressByIndex ( c ) === address ) return this . _getNodePubkeyByIndex ( 1 , c ) ;
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + this . gap_limit ; c ++ ) {
2024-04-26 23:49:19 +01:00
if ( this . _getBIP47AddressReceive ( pc , c ) === address ) return this . _getBIP47PubkeyByIndex ( pc , c ) ;
2022-12-16 18:55:31 -05:00
}
}
2019-12-23 23:11:00 +00:00
return false ;
}
2021-03-11 13:45:49 +03:00
/ * *
* Finds WIF corresponding to address and returns it
*
* @param address { string } Address that belongs to this wallet
* @returns { string | false } WIF or false
* /
2022-09-03 00:48:07 +01:00
_getWIFbyAddress ( address : string ) : string | false {
2021-03-09 14:26:56 +03:00
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
2021-03-11 13:45:49 +03:00
if ( this . _getExternalAddressByIndex ( c ) === address ) return this . _getWIFByIndex ( false , c ) ;
2021-03-09 14:26:56 +03:00
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
2021-03-11 13:45:49 +03:00
if ( this . _getInternalAddressByIndex ( c ) === address ) return this . _getWIFByIndex ( true , c ) ;
2021-03-09 14:26:56 +03:00
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + this . gap_limit ; c ++ ) {
2024-04-26 23:49:19 +01:00
if ( this . _getBIP47AddressReceive ( pc , c ) === address ) return this . _getBIP47WIF ( pc , c ) ;
2022-12-16 18:55:31 -05:00
}
}
2022-09-03 00:48:07 +01:00
return false ;
2021-03-09 14:26:56 +03:00
}
2022-09-03 00:48:07 +01:00
weOwnAddress ( address : string ) {
2021-05-20 09:49:40 +01:00
if ( ! address ) return false ;
2021-05-19 12:52:56 -03:00
let cleanAddress = address ;
if ( this . segwitType === 'p2wpkh' ) {
cleanAddress = address . toLowerCase ( ) ;
}
2019-12-23 23:11:00 +00:00
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
2021-05-19 12:52:56 -03:00
if ( this . _getExternalAddressByIndex ( c ) === cleanAddress ) return true ;
2019-12-23 23:11:00 +00:00
}
for ( let c = 0 ; c < this . next_free_change_address_index + this . gap_limit ; c ++ ) {
2021-05-19 12:52:56 -03:00
if ( this . _getInternalAddressByIndex ( c ) === cleanAddress ) return true ;
2019-12-23 23:11:00 +00:00
}
2024-04-26 23:49:19 +01:00
for ( const pc of this . _receive_payment_codes ) {
2024-04-29 23:43:04 +01:00
for ( let c = 0 ; c < this . _getNextFreePaymentCodeIndexReceive ( pc ) + this . gap_limit ; c ++ ) {
2024-04-26 23:49:19 +01:00
if ( this . _getBIP47AddressReceive ( pc , c ) === address ) return true ;
2022-12-16 18:55:31 -05:00
}
}
2019-12-23 23:11:00 +00:00
return false ;
}
/ * *
*
2024-03-24 00:31:49 +03:00
* @param utxos { Array . < { vout : Number , value : Number , txid : String , address : String } > } List of spendable utxos
2019-12-23 23:11:00 +00:00
* @param targets { Array . < { value : Number , address : String } > } Where coins are going . If theres only 1 target and that target has no value - this will send MAX to that address ( respecting fee rate )
* @param feeRate { Number } satoshi per byte
* @param changeAddress { String } Excessive coins will go back to that address
* @param sequence { Number } Used in RBF
* @param skipSigning { boolean } Whether we should skip signing , use returned ` psbt ` in that case
2020-02-24 21:45:14 +00:00
* @param masterFingerprint { number } Decimal number of wallet ' s master fingerprint
2019-12-23 23:11:00 +00:00
* @returns { { outputs : Array , tx : Transaction , inputs : Array , fee : Number , psbt : Psbt } }
* /
2022-09-03 00:48:07 +01:00
createTransaction (
utxos : CreateTransactionUtxo [ ] ,
2024-04-23 11:25:20 +01:00
targets : CreateTransactionTarget [ ] ,
2022-09-03 00:48:07 +01:00
feeRate : number ,
changeAddress : string ,
2024-04-29 23:43:04 +01:00
sequence : number = AbstractHDElectrumWallet . defaultRBFSequence ,
2022-09-03 00:48:07 +01:00
skipSigning = false ,
2024-04-29 23:43:04 +01:00
masterFingerprint : number = 0 ,
2022-09-03 00:48:07 +01:00
) : CreateTransactionResult {
2020-09-14 18:24:27 +01:00
if ( targets . length === 0 ) throw new Error ( 'No destination provided' ) ;
2021-09-14 13:30:41 +01:00
// compensating for coinselect inability to deal with segwit inputs, and overriding script length for proper vbytes calculation
for ( const u of utxos ) {
// this is a hacky way to distinguish native/wrapped segwit, but its good enough for our case since we have only
// those 2 wallet types
if ( this . _getExternalAddressByIndex ( 0 ) . startsWith ( 'bc1' ) ) {
u . script = { length : 27 } ;
} else if ( this . _getExternalAddressByIndex ( 0 ) . startsWith ( '3' ) ) {
u . script = { length : 50 } ;
}
}
2022-03-13 16:25:37 +00:00
for ( const t of targets ) {
2024-04-23 11:25:20 +01:00
if ( t . address && t . address . startsWith ( 'bc1' ) ) {
2022-03-13 16:25:37 +00:00
// in case address is non-typical and takes more bytes than coinselect library anticipates by default
t . script = { length : bitcoin.address.toOutputScript ( t . address ) . length + 3 } ;
}
2024-04-23 11:25:20 +01:00
if ( t . script ? . hex ) {
// setting length for coinselect lib manually as it is not aware of our field `hex`
t . script . length = t . script . hex . length / 2 - 4 ;
}
2022-03-13 16:25:37 +00:00
}
2024-04-23 11:25:20 +01:00
const { inputs , outputs , fee } = this . coinselect ( utxos , targets , feeRate ) ;
2019-12-23 23:11:00 +00:00
2020-09-14 13:49:08 +03:00
sequence = sequence || AbstractHDElectrumWallet . defaultRBFSequence ;
2019-12-23 23:11:00 +00:00
let psbt = new bitcoin . Psbt ( ) ;
let c = 0 ;
2022-09-03 00:48:07 +01:00
const keypairs : Record < number , ECPairInterface > = { } ;
const values : Record < number , number > = { } ;
2019-12-23 23:11:00 +00:00
inputs . forEach ( input = > {
let keyPair ;
if ( ! skipSigning ) {
// skiping signing related stuff
2022-09-03 00:48:07 +01:00
keyPair = ECPair . fromWIF ( this . _getWifForAddress ( String ( input . address ) ) ) ;
2019-12-23 23:11:00 +00:00
keypairs [ c ] = keyPair ;
}
values [ c ] = input . value ;
c ++ ;
if ( ! skipSigning ) {
// skiping signing related stuff
if ( ! input . address || ! this . _getWifForAddress ( input . address ) ) throw new Error ( 'Internal error: no address or WIF to sign input' ) ;
}
2020-04-22 16:13:18 +01:00
2019-12-31 21:31:04 -06:00
let masterFingerprintBuffer ;
if ( masterFingerprint ) {
2020-02-24 21:45:14 +00:00
let masterFingerprintHex = Number ( masterFingerprint ) . toString ( 16 ) ;
if ( masterFingerprintHex . length < 8 ) masterFingerprintHex = '0' + masterFingerprintHex ; // conversion without explicit zero might result in lost byte
const hexBuffer = Buffer . from ( masterFingerprintHex , 'hex' ) ;
2024-02-25 21:57:04 +00:00
masterFingerprintBuffer = Buffer . from ( hexBuffer ) . reverse ( ) ;
2019-12-31 21:31:04 -06:00
} else {
masterFingerprintBuffer = Buffer . from ( [ 0x00 , 0x00 , 0x00 , 0x00 ] ) ;
}
2019-12-23 23:11:00 +00:00
// this is not correct fingerprint, as we dont know real fingerprint - we got zpub with 84/0, but fingerpting
// should be from root. basically, fingerprint should be provided from outside by user when importing zpub
2020-04-22 16:13:18 +01:00
psbt = this . _addPsbtInput ( psbt , input , sequence , masterFingerprintBuffer ) ;
2019-12-23 23:11:00 +00:00
} ) ;
outputs . forEach ( output = > {
2024-04-23 11:25:20 +01:00
// if output has no address - this is change output or a custom script output
2019-12-23 23:11:00 +00:00
let change = false ;
2024-04-23 11:25:20 +01:00
// @ts-ignore
if ( ! output . address && ! output . script ? . hex ) {
2019-12-23 23:11:00 +00:00
change = true ;
output . address = changeAddress ;
}
2022-09-03 00:48:07 +01:00
const path = this . _getDerivationPathByAddress ( String ( output . address ) ) ;
const pubkey = this . _getPubkeyByAddress ( String ( output . address ) ) ;
2019-12-31 21:31:04 -06:00
let masterFingerprintBuffer ;
if ( masterFingerprint ) {
2020-02-24 21:45:14 +00:00
let masterFingerprintHex = Number ( masterFingerprint ) . toString ( 16 ) ;
if ( masterFingerprintHex . length < 8 ) masterFingerprintHex = '0' + masterFingerprintHex ; // conversion without explicit zero might result in lost byte
const hexBuffer = Buffer . from ( masterFingerprintHex , 'hex' ) ;
2024-02-25 21:57:04 +00:00
masterFingerprintBuffer = Buffer . from ( hexBuffer ) . reverse ( ) ;
2020-01-02 14:54:34 -06:00
} else {
2019-12-31 21:31:04 -06:00
masterFingerprintBuffer = Buffer . from ( [ 0x00 , 0x00 , 0x00 , 0x00 ] ) ;
}
2019-12-23 23:11:00 +00:00
// this is not correct fingerprint, as we dont know realfingerprint - we got zpub with 84/0, but fingerpting
// should be from root. basically, fingerprint should be provided from outside by user when importing zpub
2024-04-29 23:43:04 +01:00
if ( output . address ? . startsWith ( 'PM' ) ) {
// ok its BIP47 payment code, so we need to unwrap a joint address for the receiver and use it instead:
output . address = this . _getNextFreePaymentCodeAddressSend ( output . address ) ;
}
2022-09-10 12:11:28 +01:00
psbt . addOutput ( {
2019-12-23 23:11:00 +00:00
address : output.address ,
2024-04-23 11:25:20 +01:00
// @ts-ignore types from bitcoinjs are not exported so we cant define outputData separately and add fields conditionally (either address or script should be present)
script : output.script?.hex ? Buffer . from ( output . script . hex , 'hex' ) : undefined ,
2019-12-23 23:11:00 +00:00
value : output.value ,
2022-09-10 12:11:28 +01:00
bip32Derivation :
change && path && pubkey
? [
{
masterFingerprint : masterFingerprintBuffer ,
path ,
pubkey ,
} ,
]
: [ ] ,
} ) ;
2019-12-23 23:11:00 +00:00
} ) ;
if ( ! skipSigning ) {
// skiping signing related stuff
for ( let cc = 0 ; cc < c ; cc ++ ) {
psbt . signInput ( cc , keypairs [ cc ] ) ;
}
}
let tx ;
if ( ! skipSigning ) {
tx = psbt . finalizeAllInputs ( ) . extractTransaction ( ) ;
}
return { tx , inputs , outputs , fee , psbt } ;
}
2022-09-11 11:59:00 +01:00
_addPsbtInput ( psbt : Psbt , input : CoinSelectReturnInput , sequence : number , masterFingerprintBuffer : Buffer ) {
2022-09-03 00:48:07 +01:00
if ( ! input . address ) {
throw new Error ( 'Internal error: no address on Utxo during _addPsbtInput()' ) ;
}
2022-09-10 12:11:28 +01:00
const pubkey = this . _getPubkeyByAddress ( input . address ) ;
const path = this . _getDerivationPathByAddress ( input . address ) ;
if ( ! pubkey || ! path ) {
throw new Error ( 'Internal error: pubkey or path are invalid' ) ;
}
2020-04-22 16:13:18 +01:00
const p2wpkh = bitcoin . payments . p2wpkh ( { pubkey } ) ;
2024-02-23 07:32:47 +00:00
if ( ! p2wpkh . output ) {
throw new Error ( 'Internal error: could not create p2wpkh output during _addPsbtInput' ) ;
}
2020-04-22 16:13:18 +01:00
psbt . addInput ( {
2024-03-24 00:31:49 +03:00
hash : input.txid ,
2020-04-22 16:13:18 +01:00
index : input.vout ,
sequence ,
bip32Derivation : [
{
masterFingerprint : masterFingerprintBuffer ,
path ,
pubkey ,
} ,
] ,
witnessUtxo : {
script : p2wpkh.output ,
value : input.value ,
} ,
} ) ;
return psbt ;
}
2019-12-23 23:11:00 +00:00
/ * *
* Combines 2 PSBTs into final transaction from which you can
* get HEX and broadcast
*
2020-08-24 12:53:07 +01:00
* @param base64one { string | Psbt }
* @param base64two { string | Psbt }
2019-12-23 23:11:00 +00:00
* @returns { Transaction }
* /
2022-09-03 00:48:07 +01:00
combinePsbt ( base64one : string | Psbt , base64two : string | Psbt ) {
2020-08-24 12:53:07 +01:00
const final1 = typeof base64one === 'string' ? bitcoin . Psbt . fromBase64 ( base64one ) : base64one ;
const final2 = typeof base64two === 'string' ? bitcoin . Psbt . fromBase64 ( base64two ) : base64two ;
2019-12-23 23:11:00 +00:00
final1 . combine ( final2 ) ;
2020-08-24 12:53:07 +01:00
let extractedTransaction ;
try {
extractedTransaction = final1 . finalizeAllInputs ( ) . extractTransaction ( ) ;
} catch ( _ ) {
// probably already finalized
extractedTransaction = final1 . extractTransaction ( ) ;
}
return extractedTransaction ;
2019-12-23 23:11:00 +00:00
}
/ * *
* Creates Segwit Bech32 Bitcoin address
* /
2023-03-15 21:30:00 +00:00
_nodeToBech32SegwitAddress ( hdNode : BIP32Interface ) : string {
2024-02-23 07:32:47 +00:00
const { address } = bitcoin . payments . p2wpkh ( {
2019-12-23 23:11:00 +00:00
pubkey : hdNode.publicKey ,
2024-02-23 07:32:47 +00:00
} ) ;
if ( ! address ) {
throw new Error ( 'Could not create address in _nodeToBech32SegwitAddress' ) ;
}
return address ;
2019-12-23 23:11:00 +00:00
}
2023-03-15 21:30:00 +00:00
_nodeToLegacyAddress ( hdNode : BIP32Interface ) : string {
2024-02-23 07:32:47 +00:00
const { address } = bitcoin . payments . p2pkh ( {
2020-06-23 17:52:25 +01:00
pubkey : hdNode.publicKey ,
2024-02-23 07:32:47 +00:00
} ) ;
if ( ! address ) {
throw new Error ( 'Could not create address in _nodeToLegacyAddress' ) ;
}
return address ;
2020-06-23 17:52:25 +01:00
}
2023-03-15 21:30:00 +00:00
/ * *
* Creates Segwit P2SH Bitcoin address
* /
_nodeToP2shSegwitAddress ( hdNode : BIP32Interface ) : string {
const { address } = bitcoin . payments . p2sh ( {
redeem : bitcoin.payments.p2wpkh ( { pubkey : hdNode.publicKey } ) ,
} ) ;
2024-02-23 07:32:47 +00:00
if ( ! address ) {
throw new Error ( 'Could not create address in _nodeToP2shSegwitAddress' ) ;
}
2023-03-15 21:30:00 +00:00
return address ;
}
2022-09-03 00:48:07 +01:00
static _getTransactionsFromHistories ( histories : Record < string , ElectrumHistory [ ] > ) {
2020-06-01 15:54:23 +03:00
const txs = [ ] ;
for ( const history of Object . values ( histories ) ) {
for ( const tx of history ) {
2019-12-23 23:11:00 +00:00
txs . push ( tx ) ;
}
}
return txs ;
}
2020-04-16 15:40:23 +01:00
/ * *
* Probes zero address in external hierarchy for transactions , if there are any returns TRUE .
2023-04-06 19:29:34 +03:00
* Zero address is a pretty good indicator , since its a first one to fund the wallet .
* Q : How can you use the wallet and not fund it first ?
* A : You can if it is a BIP47 wallet !
2020-04-16 15:40:23 +01:00
*
* @returns { Promise < boolean > }
* /
2023-04-06 19:29:34 +03:00
async wasEverUsed ( ) : Promise < boolean > {
const txs1 = await BlueElectrum . getTransactionsByAddress ( this . _getExternalAddressByIndex ( 0 ) ) ;
if ( txs1 . length > 0 ) {
return true ;
}
if ( ! this . allowBIP47 ( ) ) {
return false ;
}
// only check BIP47 if derivation path is regular, otherwise too many wallets will be found
if ( ! [ "m/84'/0'/0'" , "m/44'/0'/0'" , "m/49'/0'/0'" ] . includes ( this . getDerivationPath ( ) as string ) ) {
return false ;
}
const bip47_instance = this . getBIP47FromSeed ( ) ;
const address = bip47_instance . getNotificationAddress ( ) ;
const txs2 = await BlueElectrum . getTransactionsByAddress ( address ) ;
return txs2 . length > 0 ;
2020-04-16 15:40:23 +01:00
}
2020-07-29 21:00:00 +01:00
/ * *
* @inheritDoc
* /
getAllExternalAddresses() {
const ret = [ ] ;
for ( let c = 0 ; c < this . next_free_address_index + this . gap_limit ; c ++ ) {
ret . push ( this . _getExternalAddressByIndex ( c ) ) ;
}
return ret ;
}
2020-10-29 21:42:40 +03:00
2020-11-22 11:19:52 +03:00
/ * *
* Check if address is a Change address . Needed for Coin control .
*
* @param address
* @returns { Boolean } Either address is a change or not
* /
2022-09-03 00:48:07 +01:00
addressIsChange ( address : string ) {
2020-10-29 21:42:40 +03:00
for ( let c = 0 ; c < this . next_free_change_address_index + 1 ; c ++ ) {
if ( address === this . _getInternalAddressByIndex ( c ) ) return true ;
}
return false ;
}
2021-02-18 16:37:43 +03:00
2022-09-03 00:48:07 +01:00
calculateHowManySignaturesWeHaveFromPsbt ( psbt : Psbt ) {
2021-02-18 16:37:43 +03:00
let sigsHave = 0 ;
for ( const inp of psbt . data . inputs ) {
if ( inp . finalScriptSig || inp . finalScriptWitness || inp . partialSig ) sigsHave ++ ;
}
return sigsHave ;
}
/ * *
* Tries to signs passed psbt object ( by reference ) . If there are enough signatures - tries to finalize psbt
* and returns Transaction ( ready to extract hex )
*
* @param psbt { Psbt }
* @returns { { tx : Transaction } }
* /
2022-09-03 00:48:07 +01:00
cosignPsbt ( psbt : Psbt ) {
2021-04-15 20:52:48 +03:00
const seed = this . _getSeed ( ) ;
2022-01-17 15:22:15 +00:00
const hdRoot = bip32 . fromSeed ( seed ) ;
2021-02-18 16:37:43 +03:00
for ( let cc = 0 ; cc < psbt . inputCount ; cc ++ ) {
try {
psbt . signInputHD ( cc , hdRoot ) ;
} catch ( e ) { } // protects agains duplicate cosignings
if ( ! psbt . inputHasHDKey ( cc , hdRoot ) ) {
for ( const derivation of psbt . data . inputs [ cc ] . bip32Derivation || [ ] ) {
const splt = derivation . path . split ( '/' ) ;
const internal = + splt [ splt . length - 2 ] ;
const index = + splt [ splt . length - 1 ] ;
2022-09-03 00:48:07 +01:00
const wif = this . _getWIFByIndex ( ! ! internal , index ) ;
if ( ! wif ) {
throw new Error ( 'Internal error: cant get WIF by index during cosingPsbt' ) ;
}
2021-11-25 15:50:40 +00:00
const keyPair = ECPair . fromWIF ( wif ) ;
2021-02-18 16:37:43 +03:00
try {
psbt . signInput ( cc , keyPair ) ;
} catch ( e ) { } // protects agains duplicate cosignings or if this output can't be signed with current wallet
}
}
}
2022-09-03 00:48:07 +01:00
let tx : BTransaction | false = false ;
2021-02-18 16:37:43 +03:00
if ( this . calculateHowManySignaturesWeHaveFromPsbt ( psbt ) === psbt . inputCount ) {
tx = psbt . finalizeAllInputs ( ) . extractTransaction ( ) ;
}
return { tx } ;
}
2021-03-23 18:58:13 +03:00
/ * *
2021-04-15 20:52:48 +03:00
* @param seed { Buffer } Buffer object with seed
2024-03-22 05:42:12 +08:00
* @returns { string } Hex string of fingerprint derived from mnemonics . Always has length of 8 chars and correct leading zeroes . All caps
2021-03-23 18:58:13 +03:00
* /
2022-09-03 00:48:07 +01:00
static seedToFingerprint ( seed : Buffer ) {
2022-01-17 15:22:15 +00:00
const root = bip32 . fromSeed ( seed ) ;
2021-03-23 18:58:13 +03:00
let hex = root . fingerprint . toString ( 'hex' ) ;
while ( hex . length < 8 ) hex = '0' + hex ; // leading zeroes
return hex . toUpperCase ( ) ;
}
2021-05-03 17:19:54 +01:00
/ * *
* @param mnemonic { string } Mnemonic phrase ( 12 or 24 words )
* @returns { string } Hex fingerprint
* /
2024-02-18 14:42:42 +00:00
static mnemonicToFingerprint ( mnemonic : string , passphrase? : string ) {
2022-01-09 17:38:56 +03:00
const seed = bip39 . mnemonicToSeedSync ( mnemonic , passphrase ) ;
2021-05-03 17:19:54 +01:00
return AbstractHDElectrumWallet . seedToFingerprint ( seed ) ;
}
2021-03-23 18:58:13 +03:00
/ * *
2024-03-22 05:42:12 +08:00
* @returns { string } Hex string of fingerprint derived from wallet mnemonics . Always has length of 8 chars and correct leading zeroes
2021-03-23 18:58:13 +03:00
* /
getMasterFingerprintHex() {
2021-04-15 20:52:48 +03:00
const seed = this . _getSeed ( ) ;
return AbstractHDElectrumWallet . seedToFingerprint ( seed ) ;
2021-03-23 18:58:13 +03:00
}
2022-12-11 21:34:50 -05:00
/ * *
2023-03-15 20:42:25 +00:00
* Whether BIP47 is enabled . This is per - wallet setting that can be changed , NOT a feature - flag
2022-12-11 21:34:50 -05:00
* @returns boolean
* /
isBIP47Enabled ( ) : boolean {
return this . _enable_BIP47 ;
}
switchBIP47 ( value : boolean ) : void {
this . _enable_BIP47 = value ;
}
getBIP47FromSeed ( ) : BIP47Interface {
2023-03-26 19:43:25 +01:00
if ( ! this . _bip47_instance || ! this . _bip47_instance . getNotificationAddress ) {
2023-03-15 20:42:25 +00:00
this . _bip47_instance = bip47 . fromBip39Seed ( this . secret , undefined , this . passphrase ) ;
}
2022-12-11 21:34:50 -05:00
2023-03-15 20:42:25 +00:00
return this . _bip47_instance ;
2022-12-11 21:34:50 -05:00
}
2024-04-29 23:43:04 +01:00
/ * *
* this method goes over all our txs and checks if we sent a notification tx in the past to the given PC
* /
needToNotifyBIP47 ( receiverPaymentCode : string ) : boolean {
const publicBip47 = BIP47Factory ( ecc ) . fromPaymentCode ( receiverPaymentCode ) ;
const remoteNotificationAddress = publicBip47 . getNotificationAddress ( ) ;
for ( const tx of this . getTransactions ( ) ) {
for ( const output of tx . outputs ) {
if ( output . scriptPubKey ? . addresses ? . includes ( remoteNotificationAddress ) ) return false ;
// ^^^ if in the past we sent a tx to his notification address - most likely that was a proper notification
// transaction with OP_RETURN.
// but not gona verify it here, will just trust it
}
}
return true ;
}
/ * *
2024-05-05 20:27:43 +01:00
* return BIP47 payment code of the counterparty of this transaction ( someone who paid us , or someone we paid )
* or undefined if it was a non - BIP47 transaction
* /
getBip47CounterpartyByTxid ( txid : string ) : string | undefined {
const foundTx = this . getTransactions ( ) . find ( tx = > tx . txid === txid ) ;
if ( foundTx ) {
return this . getBip47CounterpartyByTx ( foundTx ) ;
}
return undefined ;
}
/ * *
* return BIP47 payment code of the counterparty of this transaction ( someone who paid us , or someone we paid )
* or undefined if it was a non - BIP47 transaction
2024-04-29 23:43:04 +01:00
* /
2024-05-05 20:27:43 +01:00
getBip47CounterpartyByTx ( tx : Transaction ) : string | undefined {
2024-04-29 23:43:04 +01:00
for ( const pc of Object . keys ( this . _txs_by_payment_code_index ) ) {
// iterating all payment codes
for ( const txs of Object . values ( this . _txs_by_payment_code_index [ pc ] ) ) {
2024-05-05 20:27:43 +01:00
for ( const tx2 of txs ) {
if ( tx2 . txid === tx . txid ) {
2024-04-29 23:43:04 +01:00
return pc ; // found it!
}
}
}
}
// checking txs we sent to counterparties
for ( const pc of this . _send_payment_codes ) {
2024-05-05 20:27:43 +01:00
for ( const out of tx . outputs ) {
for ( const address of out . scriptPubKey ? . addresses ? ? [ ] ) {
if ( this . _addresses_by_payment_code_send [ pc ] && Object . values ( this . _addresses_by_payment_code_send [ pc ] ) . includes ( address ) ) {
// found it!
return pc ;
2024-04-29 23:43:04 +01:00
}
}
}
}
2024-05-05 20:27:43 +01:00
return undefined ; // found nothing
2024-04-29 23:43:04 +01:00
}
2024-04-26 23:49:19 +01:00
createBip47NotificationTransaction ( utxos : CreateTransactionUtxo [ ] , receiverPaymentCode : string , feeRate : number , changeAddress : string ) {
const aliceBip47 = BIP47Factory ( ecc ) . fromBip39Seed ( this . getSecret ( ) , undefined , this . getPassphrase ( ) ) ;
const bobBip47 = BIP47Factory ( ecc ) . fromPaymentCode ( receiverPaymentCode ) ;
assert ( utxos [ 0 ] ) ;
assert ( utxos [ 0 ] . wif ) ;
// constructing targets: notification address, _dummy_ payload (+potential change might be added later)
const targetsTemp : CreateTransactionTarget [ ] = [ ] ;
targetsTemp . push ( {
address : bobBip47.getNotificationAddress ( ) ,
value : 546 , // minimum permissible utxo size
} ) ;
targetsTemp . push ( {
value : 0 ,
script : {
hex : Buffer.alloc ( 83 ) . toString ( 'hex' ) , // no `address` here, its gonabe op_return. but we pass dummy data here with a correct size just to choose utxo
} ,
} ) ;
// creating temp transaction so that utxo can be selected:
const { inputs : inputsTemp } = this . createTransaction (
utxos ,
targetsTemp ,
feeRate ,
changeAddress ,
AbstractHDElectrumWallet . defaultRBFSequence ,
false ,
0 ,
) ;
assert ( inputsTemp ? . [ 0 ] ? . wif ) ;
// utxo selected. lets create op_return payload using the correct (first!) utxo and correct targets with that payload
const keyPair = ECPair . fromWIF ( inputsTemp [ 0 ] . wif ) ;
const outputNumber = Buffer . from ( '00000000' , 'hex' ) ;
outputNumber . writeUInt32LE ( inputsTemp [ 0 ] . vout ) ;
const blindedPaymentCode = aliceBip47 . getBlindedPaymentCode (
bobBip47 ,
keyPair . privateKey as Buffer ,
// txid is reversed, as well as output number
Buffer . from ( inputsTemp [ 0 ] . txid , 'hex' ) . reverse ( ) . toString ( 'hex' ) + outputNumber . toString ( 'hex' ) ,
) ;
// targets:
const targets : CreateTransactionTarget [ ] = [ ] ;
targets . push ( {
address : bobBip47.getNotificationAddress ( ) ,
value : 546 , // minimum permissible utxo size
} ) ;
targets . push ( {
value : 0 ,
script : {
hex : '6a4c50' + blindedPaymentCode , // no `address` here, only script (which is OP_RETURN + data payload)
} ,
} ) ;
// finally a transaction:
const { tx , outputs , inputs , fee , psbt } = this . createTransaction (
utxos ,
targets ,
feeRate ,
changeAddress ,
AbstractHDElectrumWallet . defaultRBFSequence ,
false ,
0 ,
) ;
assert ( inputs && inputs [ 0 ] && inputs [ 0 ] . wif ) ;
assert ( inputs [ 0 ] . txid === inputsTemp [ 0 ] . txid ) ; // making sure that no funky business happened under the hood (its supposed to stay the same)
return { tx , inputs , outputs , fee , psbt } ;
}
2022-12-11 21:34:50 -05:00
getBIP47PaymentCode ( ) : string {
2023-03-15 20:42:25 +00:00
if ( ! this . _payment_code ) {
this . _payment_code = this . getBIP47FromSeed ( ) . getSerializedPaymentCode ( ) ;
}
2022-12-11 21:34:50 -05:00
return this . _payment_code ;
}
2023-04-06 19:29:34 +03:00
getBIP47NotificationAddress ( ) : string {
2023-07-25 14:50:04 +01:00
const bip47Local = this . getBIP47FromSeed ( ) ;
return bip47Local . getNotificationAddress ( ) ;
2023-04-06 19:29:34 +03:00
}
2022-12-12 22:09:04 -05:00
async fetchBIP47SenderPaymentCodes ( ) : Promise < void > {
2023-03-26 19:43:25 +01:00
const bip47_instance = this . getBIP47FromSeed ( ) ;
2022-12-12 22:09:04 -05:00
const address = bip47_instance . getNotificationAddress ( ) ;
2022-12-11 21:34:50 -05:00
const histories = await BlueElectrum . multiGetHistoryByAddress ( [ address ] ) ;
const txHashes = histories [ address ] . map ( ( { tx_hash } ) = > tx_hash ) ;
2024-03-25 00:07:43 +03:00
const txHexs = await BlueElectrum . multiGetTransactionByTxid ( txHashes , false ) ;
2023-02-09 22:13:47 -05:00
for ( const txHex of Object . values ( txHexs ) ) {
try {
const paymentCode = bip47_instance . getPaymentCodeFromRawNotificationTransaction ( txHex ) ;
2024-04-26 23:49:19 +01:00
if ( this . _receive_payment_codes . includes ( paymentCode ) ) continue ; // already have it
2023-03-17 12:56:35 +00:00
// final check if PC is even valid (could've been constructed by a buggy code, and our code would crash with that):
try {
2023-04-10 11:32:20 +01:00
bip47 . fromPaymentCode ( paymentCode ) ;
2023-03-17 12:56:35 +00:00
} catch ( _ ) {
continue ;
}
2024-04-26 23:49:19 +01:00
this . _receive_payment_codes . push ( paymentCode ) ;
this . _next_free_payment_code_address_index_receive [ paymentCode ] = 0 ; // initialize
2023-02-09 22:13:47 -05:00
this . _balances_by_payment_code_index [ paymentCode ] = { c : 0 , u : 0 } ;
} catch ( e ) {
// do nothing
}
}
2022-12-11 21:34:50 -05:00
}
2024-04-26 23:49:19 +01:00
/ * *
* for counterparties we can pay , we sync shared addresses to find the one we havent used yet .
* this method could benefit from rewriting in batch requests , but not necessary - its only going to be called
2024-04-29 23:43:04 +01:00
* once in a while ( when user decides to pay a given counterparty again )
2024-04-26 23:49:19 +01:00
* /
async syncBip47ReceiversAddresses ( receiverPaymentCode : string ) {
this . _next_free_payment_code_address_index_send [ receiverPaymentCode ] =
this . _next_free_payment_code_address_index_send [ receiverPaymentCode ] || 0 ; // init
for ( let c = this . _next_free_payment_code_address_index_send [ receiverPaymentCode ] ; c < 999999 ; c ++ ) {
const address = this . _getBIP47AddressSend ( receiverPaymentCode , c ) ;
this . _addresses_by_payment_code_send [ receiverPaymentCode ] = this . _addresses_by_payment_code_send [ receiverPaymentCode ] || { } ; // init
this . _addresses_by_payment_code_send [ receiverPaymentCode ] [ c ] = address ;
const histories = await BlueElectrum . multiGetHistoryByAddress ( [ address ] ) ;
if ( histories ? . [ address ] ? . length > 0 ) {
// address is used;
continue ;
}
// empty address, stop here, we found our latest index and filled array with shared addresses
2024-04-29 23:43:04 +01:00
this . _next_free_payment_code_address_index_send [ receiverPaymentCode ] = c ;
2024-04-26 23:49:19 +01:00
break ;
}
}
2022-12-11 21:34:50 -05:00
getBIP47SenderPaymentCodes ( ) : string [ ] {
2024-04-26 23:49:19 +01:00
return this . _receive_payment_codes ;
2022-12-11 21:34:50 -05:00
}
2024-04-29 23:43:04 +01:00
getBIP47ReceiverPaymentCodes ( ) : string [ ] {
return this . _send_payment_codes ;
}
/ * *
* adding counterparty whom we can pay . trusting that notificaton transaction is in place already
* /
addBIP47Receiver ( paymentCode : string ) {
if ( this . _send_payment_codes . includes ( paymentCode ) ) return ; // duplicates
this . _send_payment_codes . push ( paymentCode ) ;
}
2023-03-15 20:42:25 +00:00
_hdNodeToAddress ( hdNode : BIP32Interface ) : string {
2023-03-15 21:30:00 +00:00
return this . _nodeToBech32SegwitAddress ( hdNode ) ;
2023-03-15 20:42:25 +00:00
}
2024-04-26 23:49:19 +01:00
/ * *
* returns joint addresses to receive coins with a given counterparty
* /
_getBIP47AddressReceive ( paymentCode : string , index : number ) : string {
if ( ! this . _addresses_by_payment_code_receive [ paymentCode ] ) this . _addresses_by_payment_code_receive [ paymentCode ] = [ ] ;
2022-12-11 21:34:50 -05:00
2024-04-26 23:49:19 +01:00
if ( this . _addresses_by_payment_code_receive [ paymentCode ] [ index ] ) {
return this . _addresses_by_payment_code_receive [ paymentCode ] [ index ] ;
2022-12-11 21:34:50 -05:00
}
2022-12-14 22:39:07 -05:00
const bip47_instance = this . getBIP47FromSeed ( ) ;
2022-12-11 21:34:50 -05:00
const senderBIP47_instance = bip47 . fromPaymentCode ( paymentCode ) ;
const remotePaymentNode = senderBIP47_instance . getPaymentCodeNode ( ) ;
const hdNode = bip47_instance . getPaymentWallet ( remotePaymentNode , index ) ;
2023-03-15 20:42:25 +00:00
const address = this . _hdNodeToAddress ( hdNode ) ;
2022-12-16 18:55:31 -05:00
this . _address_to_wif_cache [ address ] = hdNode . toWIF ( ) ;
2024-04-26 23:49:19 +01:00
this . _addresses_by_payment_code_receive [ paymentCode ] [ index ] = address ;
return address ;
}
/ * *
* returns joint addresses to send coins to
* /
_getBIP47AddressSend ( paymentCode : string , index : number ) : string {
if ( ! this . _addresses_by_payment_code_send [ paymentCode ] ) this . _addresses_by_payment_code_send [ paymentCode ] = [ ] ;
if ( this . _addresses_by_payment_code_send [ paymentCode ] [ index ] ) {
2024-04-29 23:43:04 +01:00
// cache hit
2024-04-26 23:49:19 +01:00
return this . _addresses_by_payment_code_send [ paymentCode ] [ index ] ;
}
const hdNode = this . getBIP47FromSeed ( ) . getReceiveWallet ( BIP47Factory ( ecc ) . fromPaymentCode ( paymentCode ) . getPaymentCodeNode ( ) , index ) ;
const address = this . _hdNodeToAddress ( hdNode ) ;
this . _addresses_by_payment_code_send [ paymentCode ] [ index ] = address ;
2022-12-11 21:34:50 -05:00
return address ;
}
2024-04-29 23:43:04 +01:00
_getNextFreePaymentCodeIndexReceive ( paymentCode : string ) {
2024-04-26 23:49:19 +01:00
return this . _next_free_payment_code_address_index_receive [ paymentCode ] || 0 ;
2022-12-11 21:34:50 -05:00
}
2024-04-29 23:43:04 +01:00
/ * *
* when sending funds to a payee , this method will return next unused joint address for him .
* this method assumes that we synced our payee via ` syncBip47ReceiversAddresses() `
* /
_getNextFreePaymentCodeAddressSend ( paymentCode : string ) {
this . _next_free_payment_code_address_index_send [ paymentCode ] = this . _next_free_payment_code_address_index_send [ paymentCode ] || 0 ;
return this . _getBIP47AddressSend ( paymentCode , this . _next_free_payment_code_address_index_send [ paymentCode ] ) ;
}
2022-12-11 21:34:50 -05:00
_getBalancesByPaymentCodeIndex ( paymentCode : string ) : BalanceByIndex {
return this . _balances_by_payment_code_index [ paymentCode ] || { c : 0 , u : 0 } ;
}
2022-12-16 18:55:31 -05:00
_getBIP47WIF ( paymentCode : string , index : number ) : string {
const bip47_instance = this . getBIP47FromSeed ( ) ;
const senderBIP47_instance = bip47 . fromPaymentCode ( paymentCode ) ;
const remotePaymentNode = senderBIP47_instance . getPaymentCodeNode ( ) ;
const hdNode = bip47_instance . getPaymentWallet ( remotePaymentNode , index ) ;
return hdNode . toWIF ( ) ;
}
_getBIP47PubkeyByIndex ( paymentCode : string , index : number ) : Buffer {
const bip47_instance = this . getBIP47FromSeed ( ) ;
const senderBIP47_instance = bip47 . fromPaymentCode ( paymentCode ) ;
const remotePaymentNode = senderBIP47_instance . getPaymentCodeNode ( ) ;
const hdNode = bip47_instance . getPaymentWallet ( remotePaymentNode , index ) ;
return hdNode . publicKey ;
}
2019-12-23 23:11:00 +00:00
}