2018-03-24 21:24:20 +00:00
import { AbstractWallet } from './abstract-wallet' ;
2020-03-09 18:51:34 +00:00
import { HDSegwitBech32Wallet } from './' ;
2018-12-11 22:52:46 +00:00
import { NativeModules } from 'react-native' ;
2019-09-14 07:15:59 +09:00
const bitcoin = require ( 'bitcoinjs-lib' ) ;
2018-12-11 22:52:46 +00:00
const { RNRandomBytes } = NativeModules ;
2018-03-24 21:24:20 +00:00
const BigNumber = require ( 'bignumber.js' ) ;
2018-05-20 10:38:50 +01:00
const signer = require ( '../models/signer' ) ;
2019-02-27 21:50:31 -05:00
const BlueElectrum = require ( '../BlueElectrum' ) ;
2018-03-20 22:41:07 +02:00
/ * *
2018-12-22 11:51:07 -05:00
* Has private key and single address like "1ABCD....."
2018-03-20 22:41:07 +02:00
* ( legacy P2PKH compressed )
* /
export class LegacyWallet extends AbstractWallet {
2018-12-28 16:52:06 +01:00
static type = 'legacy' ;
static typeReadable = 'Legacy (P2PKH)' ;
2018-03-20 22:41:07 +02:00
2018-07-14 21:32:36 +01:00
/ * *
* Simple function which says that we havent tried to fetch balance
* for a long time
*
* @ return { boolean }
* /
timeToRefreshBalance ( ) {
2018-07-14 20:52:27 +01:00
if ( + new Date ( ) - this . _lastBalanceFetch >= 5 * 60 * 1000 ) {
2018-07-02 14:51:24 +01:00
return true ;
}
2018-07-14 21:32:36 +01:00
return false ;
}
2018-07-12 00:38:24 +01:00
2018-07-14 21:32:36 +01:00
/ * *
* Simple function which says if we hve some low - confirmed transactions
* and we better fetch them
*
* @ return { boolean }
* /
timeToRefreshTransaction ( ) {
2020-03-09 18:51:34 +00:00
for ( let tx of this . getTransactions ( ) ) {
2018-07-12 00:38:24 +01:00
if ( tx . confirmations < 7 ) {
return true ;
}
}
2018-07-14 21:32:36 +01:00
return false ;
2018-07-02 14:51:24 +01:00
}
2018-12-11 22:52:46 +00:00
async generate ( ) {
let that = this ;
return new Promise ( function ( resolve ) {
if ( typeof RNRandomBytes === 'undefined' ) {
// CLI/CI environment
// crypto should be provided globally by test launcher
return crypto . randomBytes ( 32 , ( err , buf ) => { // eslint-disable-line
if ( err ) throw err ;
that . secret = bitcoin . ECPair . makeRandom ( {
rng : function ( length ) {
return buf ;
} ,
} ) . toWIF ( ) ;
resolve ( ) ;
} ) ;
2018-03-20 22:41:07 +02:00
}
2018-12-11 22:52:46 +00:00
// RN environment
RNRandomBytes . randomBytes ( 32 , ( err , bytes ) => {
if ( err ) throw new Error ( err ) ;
that . secret = bitcoin . ECPair . makeRandom ( {
rng : function ( length ) {
let b = Buffer . from ( bytes , 'base64' ) ;
return b ;
} ,
} ) . toWIF ( ) ;
resolve ( ) ;
} ) ;
} ) ;
2018-03-20 22:41:07 +02:00
}
/ * *
*
* @ returns { string }
* /
getAddress ( ) {
if ( this . _address ) return this . _address ;
let address ;
try {
let keyPair = bitcoin . ECPair . fromWIF ( this . secret ) ;
2019-09-14 07:15:59 +09:00
address = bitcoin . payments . p2pkh ( {
pubkey : keyPair . publicKey ,
} ) . address ;
2018-03-20 22:41:07 +02:00
} catch ( err ) {
return false ;
}
this . _address = address ;
return this . _address ;
}
2018-05-20 10:38:50 +01:00
/ * *
2018-06-17 11:46:19 +01:00
* Fetches balance of the Wallet via API .
* Returns VOID . Get the actual balance via getter .
2018-05-20 10:38:50 +01:00
*
* @ returns { Promise . < void > }
* /
2018-03-20 22:41:07 +02:00
async fetchBalance ( ) {
try {
2020-03-09 18:51:34 +00:00
let balance = await BlueElectrum . getBalanceByAddress ( this . getAddress ( ) ) ;
this . balance = Number ( balance . confirmed ) ;
this . unconfirmed _balance = new BigNumber ( balance . unconfirmed ) ;
this . unconfirmed _balance = this . unconfirmed _balance . dividedBy ( 100000000 ) . toString ( ) * 1 ; // wtf
2018-07-02 10:48:40 +01:00
this . _lastBalanceFetch = + new Date ( ) ;
2020-03-09 18:51:34 +00:00
} catch ( Error ) {
console . warn ( Error ) ;
2018-03-20 22:41:07 +02:00
}
}
2018-06-17 11:46:19 +01:00
/ * *
* Fetches UTXO from API . Returns VOID .
*
* @ return { Promise . < void > }
* /
2018-03-20 22:41:07 +02:00
async fetchUtxo ( ) {
try {
2020-03-09 18:51:34 +00:00
let utxos = await BlueElectrum . multiGetUtxoByAddress ( [ this . getAddress ( ) ] ) ;
for ( let arr of Object . values ( utxos ) ) {
this . utxo = this . utxo . concat ( arr ) ;
}
} catch ( Error ) {
console . warn ( Error ) ;
}
2018-03-20 22:41:07 +02:00
2020-03-09 18:51:34 +00:00
// backward compatibility
for ( let u of this . utxo ) {
u . tx _output _n = u . vout ;
u . tx _hash = u . txId ;
u . confirmations = u . height ? 1 : 0 ;
2018-03-20 22:41:07 +02:00
}
}
2020-03-09 18:51:34 +00:00
getUtxo ( ) {
return this . utxo ;
}
2018-06-17 11:46:19 +01:00
/ * *
2020-03-09 18:51:34 +00:00
* Fetches transactions via Electrum . Returns VOID .
* Use getter to get the actual list . *
* @ see AbstractHDElectrumWallet . fetchTransactions ( )
2018-06-17 11:46:19 +01:00
*
* @ return { Promise . < void > }
* /
2018-03-20 22:41:07 +02:00
async fetchTransactions ( ) {
2020-03-09 18:51:34 +00:00
// Below is a simplified copypaste from HD electrum wallet
this . _txs _by _external _index = [ ] ;
let addresses2fetch = [ this . getAddress ( ) ] ;
// first: batch fetch for all addresses histories
let histories = await BlueElectrum . multiGetHistoryByAddress ( addresses2fetch ) ;
let txs = { } ;
for ( let history of Object . values ( histories ) ) {
for ( let tx of history ) {
txs [ tx . tx _hash ] = tx ;
2018-03-20 22:41:07 +02:00
}
2020-03-09 18:51:34 +00:00
}
2018-03-20 22:41:07 +02:00
2020-03-09 18:51:34 +00:00
// next, batch fetching each txid we got
let txdatas = await BlueElectrum . multiGetTransactionByTxid ( Object . keys ( txs ) ) ;
2018-07-25 00:27:21 +01:00
2020-03-09 18:51:34 +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)
let vinTxids = [ ] ;
for ( let txdata of Object . values ( txdatas ) ) {
for ( let vin of txdata . vin ) {
vinTxids . push ( vin . txid ) ;
}
}
let vintxdatas = await BlueElectrum . multiGetTransactionByTxid ( vinTxids ) ;
// fetched all transactions from our inputs. now we need to combine it.
// iterating all _our_ transactions:
for ( let txid of Object . keys ( txdatas ) ) {
// iterating all inputs our our single transaction:
for ( let inpNum = 0 ; inpNum < txdatas [ txid ] . vin . length ; inpNum ++ ) {
let inpTxid = txdatas [ txid ] . vin [ inpNum ] . txid ;
let inpVout = txdatas [ txid ] . vin [ inpNum ] . vout ;
// got txid and output number of _previous_ transaction we shoud look into
if ( vintxdatas [ inpTxid ] && vintxdatas [ inpTxid ] . vout [ inpVout ] ) {
// 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 ;
2018-03-20 22:41:07 +02:00
}
}
}
2020-03-09 18:51:34 +00:00
// now, we need to put transactions in all relevant `cells` of internal hashmaps: this.transactions_by_internal_index && this.transactions_by_external_index
2018-10-11 18:25:39 +01:00
2020-03-09 18:51:34 +00:00
for ( let tx of Object . values ( txdatas ) ) {
for ( let vin of tx . vin ) {
if ( vin . addresses && vin . addresses . indexOf ( this . getAddress ( ) ) !== - 1 ) {
// this TX is related to our address
let clonedTx = Object . assign ( { } , tx ) ;
clonedTx . inputs = tx . vin . slice ( 0 ) ;
clonedTx . outputs = tx . vout . slice ( 0 ) ;
delete clonedTx . vin ;
delete clonedTx . vout ;
2018-10-11 18:25:39 +01:00
2020-03-09 18:51:34 +00:00
this . _txs _by _external _index . push ( clonedTx ) ;
}
2018-10-11 18:25:39 +01:00
}
2020-03-09 18:51:34 +00:00
for ( let vout of tx . vout ) {
if ( vout . scriptPubKey . addresses . indexOf ( this . getAddress ( ) ) !== - 1 ) {
// this TX is related to our address
let clonedTx = Object . assign ( { } , tx ) ;
clonedTx . inputs = tx . vin . slice ( 0 ) ;
clonedTx . outputs = tx . vout . slice ( 0 ) ;
delete clonedTx . vin ;
delete clonedTx . vout ;
this . _txs _by _external _index . push ( clonedTx ) ;
}
2018-10-11 18:25:39 +01:00
}
2020-03-09 18:51:34 +00:00
}
2018-10-11 18:25:39 +01:00
2020-03-09 18:51:34 +00:00
this . _lastTxFetch = + new Date ( ) ;
2018-10-11 18:25:39 +01:00
}
2020-03-09 18:51:34 +00:00
getTransactions ( ) {
// a hacky code reuse from electrum HD wallet:
this . _txs _by _external _index = this . _txs _by _external _index || [ ] ;
this . _txs _by _internal _index = [ ] ;
2018-10-11 18:25:39 +01:00
2020-03-09 18:51:34 +00:00
let hd = new HDSegwitBech32Wallet ( ) ;
return hd . getTransactions . apply ( this ) ;
2018-10-11 18:25:39 +01:00
}
2018-03-20 22:41:07 +02:00
async broadcastTx ( txhex ) {
2019-02-27 21:50:31 -05:00
try {
const broadcast = await BlueElectrum . broadcast ( txhex ) ;
return broadcast ;
} catch ( error ) {
return error ;
2018-06-11 20:17:16 +01:00
}
}
2018-05-20 10:38:50 +01:00
/ * *
2020-03-09 18:51:34 +00:00
* Takes UTXOs , transforms them into
2018-05-20 10:38:50 +01:00
* format expected by signer module , creates tx and returns signed string txhex .
*
* @ param utxos Unspent outputs , expects blockcypher format
* @ param amount
* @ param fee
* @ param toAddress
* @ param memo
* @ return string Signed txhex ready for broadcast
* /
createTx ( utxos , amount , fee , toAddress , memo ) {
// transforming UTXOs fields to how module expects it
for ( let u of utxos ) {
u . confirmations = 6 ; // hack to make module accept 0 confirmations
u . txid = u . tx _hash ;
u . vout = u . tx _output _n ;
u . amount = new BigNumber ( u . value ) ;
2018-10-20 22:10:21 +01:00
u . amount = u . amount . dividedBy ( 100000000 ) ;
2018-05-20 10:38:50 +01:00
u . amount = u . amount . toString ( 10 ) ;
}
2018-07-22 15:49:59 +01:00
// console.log('creating legacy tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
2018-10-20 22:10:21 +01:00
let amountPlusFee = parseFloat ( new BigNumber ( amount ) . plus ( fee ) . toString ( 10 ) ) ;
2018-07-07 14:04:32 +01:00
return signer . createTransaction ( utxos , toAddress , amountPlusFee , fee , this . getSecret ( ) , this . getAddress ( ) ) ;
2018-05-20 10:38:50 +01:00
}
2018-06-17 11:46:44 +01:00
2018-06-24 23:22:46 +01:00
getLatestTransactionTime ( ) {
2018-09-01 00:28:19 +01:00
if ( this . getTransactions ( ) . length === 0 ) {
return 0 ;
}
let max = 0 ;
2018-07-22 15:49:59 +01:00
for ( let tx of this . getTransactions ( ) ) {
2018-09-01 00:28:19 +01:00
max = Math . max ( new Date ( tx . received ) * 1 , max ) ;
2018-06-24 23:22:46 +01:00
}
2018-09-01 00:28:19 +01:00
return new Date ( max ) . toString ( ) ;
2018-06-24 23:22:46 +01:00
}
2020-03-09 18:51:34 +00:00
/ * *
* Validates any address , including legacy , p2sh and bech32
*
* @ param address
* @ returns { boolean }
* /
2018-07-12 00:38:24 +01:00
isAddressValid ( address ) {
try {
bitcoin . address . toOutputScript ( address ) ;
return true ;
} catch ( e ) {
return false ;
}
}
2020-03-09 18:51:34 +00:00
weOwnAddress ( address ) {
return this . getAddress ( ) === address || this . _address === address ;
}
2018-03-20 22:41:07 +02:00
}