|
|
@ -1,22 +1,14 @@ |
|
|
|
// @flow
|
|
|
|
|
|
|
|
/* eslint-disable no-bitwise */ |
|
|
|
|
|
|
|
import axios from 'axios' |
|
|
|
import bitcoin from 'bitcoinjs-lib' |
|
|
|
import bs58check from 'bs58check' |
|
|
|
import Btc from '@ledgerhq/hw-app-btc' |
|
|
|
|
|
|
|
import { computeTransaction } from 'helpers/btc' |
|
|
|
import { getAccount, getHDNode, networks } from 'helpers/btc' |
|
|
|
|
|
|
|
export const networks = [ |
|
|
|
{ |
|
|
|
...bitcoin.networks.bitcoin, |
|
|
|
family: 1, |
|
|
|
}, |
|
|
|
{ |
|
|
|
...bitcoin.networks.testnet, |
|
|
|
family: 1, |
|
|
|
}, |
|
|
|
] |
|
|
|
type Coin = 0 | 1 |
|
|
|
|
|
|
|
function getCompressPublicKey(publicKey) { |
|
|
|
let compressedKeyIndex |
|
|
@ -29,7 +21,7 @@ function getCompressPublicKey(publicKey) { |
|
|
|
return result |
|
|
|
} |
|
|
|
|
|
|
|
function parseHexString(str) { |
|
|
|
function parseHexString(str: any) { |
|
|
|
const result = [] |
|
|
|
while (str.length >= 2) { |
|
|
|
result.push(parseInt(str.substring(0, 2), 16)) |
|
|
@ -40,10 +32,10 @@ function parseHexString(str) { |
|
|
|
|
|
|
|
function createXpub({ depth, fingerprint, childnum, chainCode, publicKey, network }) { |
|
|
|
return [ |
|
|
|
network.toString(16).padStart(8, 0), |
|
|
|
depth.toString(16).padStart(2, 0), |
|
|
|
fingerprint.toString(16).padStart(8, 0), |
|
|
|
childnum.toString(16).padStart(8, 0), |
|
|
|
network.toString(16).padStart(8, '0'), |
|
|
|
depth.toString(16).padStart(2, '0'), |
|
|
|
fingerprint.toString(16).padStart(8, '0'), |
|
|
|
childnum.toString(16).padStart(8, '0'), |
|
|
|
chainCode, |
|
|
|
publicKey, |
|
|
|
].join('') |
|
|
@ -55,77 +47,23 @@ function encodeBase58Check(vchIn) { |
|
|
|
return bs58check.encode(Buffer.from(vchIn)) |
|
|
|
} |
|
|
|
|
|
|
|
function getPath({ coin, account, segwit }) { |
|
|
|
function getPath({ coin, account, segwit }: { coin: Coin, account?: any, segwit: boolean }) { |
|
|
|
return `${segwit ? 49 : 44}'/${coin}'${account !== undefined ? `/${account}'` : ''}` |
|
|
|
} |
|
|
|
|
|
|
|
function pubKeyToSegwitAddress(pubKey, scriptVersion) { |
|
|
|
const script = [0x00, 0x14].concat(Array.from(bitcoin.crypto.hash160(pubKey))) |
|
|
|
const hash160 = bitcoin.crypto.hash160(new Uint8Array(script)) |
|
|
|
return bitcoin.address.toBase58Check(hash160, scriptVersion) |
|
|
|
} |
|
|
|
|
|
|
|
function getPublicAddress(hdnode, path, script, segwit) { |
|
|
|
hdnode = hdnode.derivePath(path) |
|
|
|
if (!segwit) { |
|
|
|
return hdnode.getAddress().toString() |
|
|
|
} |
|
|
|
return pubKeyToSegwitAddress(hdnode.getPublicKeyBuffer(), script) |
|
|
|
} |
|
|
|
|
|
|
|
function getTransactions(addresses) { |
|
|
|
return axios.get( |
|
|
|
`http://api.ledgerwallet.com/blockchain/v2/btc_testnet/addresses/${addresses.join( |
|
|
|
',', |
|
|
|
)}/transactions?noToken=true`,
|
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
export async function getAccount({ hdnode, segwit, network }) { |
|
|
|
const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10) |
|
|
|
|
|
|
|
let transactions = [] |
|
|
|
|
|
|
|
const nextPath = start => { |
|
|
|
const count = 20 |
|
|
|
const getAddress = path => getPublicAddress(hdnode, path, script, segwit) |
|
|
|
|
|
|
|
return Promise.all( |
|
|
|
Array.from(Array(count).keys()).map(v => |
|
|
|
Promise.all([ |
|
|
|
getAddress(`0/${v + start}`), // external chain
|
|
|
|
getAddress(`1/${v + start}`), // internal chain
|
|
|
|
]), |
|
|
|
), |
|
|
|
).then(async results => { |
|
|
|
const currentAddresses = results.reduce((result, v) => [...result, ...v], []) |
|
|
|
|
|
|
|
const { data: { txs } } = await getTransactions(currentAddresses) |
|
|
|
|
|
|
|
transactions = [...transactions, ...txs.map(computeTransaction(currentAddresses))] |
|
|
|
|
|
|
|
if (txs.length > 0) { |
|
|
|
return nextPath(start + (count - 1)) |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
balance: transactions.reduce((result, v) => { |
|
|
|
result += v.balance |
|
|
|
return result |
|
|
|
}, 0), |
|
|
|
transactions, |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
return nextPath(0) |
|
|
|
} |
|
|
|
|
|
|
|
export function getHDNode({ xpub58, network }) { |
|
|
|
return bitcoin.HDNode.fromBase58(xpub58, network) |
|
|
|
} |
|
|
|
|
|
|
|
export default async ({ transport, currentAccounts, onProgress, coin = 1, segwit = true }) => { |
|
|
|
export default async ({ |
|
|
|
transport, |
|
|
|
currentAccounts, |
|
|
|
onProgress, |
|
|
|
coin = 1, |
|
|
|
segwit = true, |
|
|
|
}: { |
|
|
|
transport: Object, |
|
|
|
currentAccounts: Array<*>, |
|
|
|
onProgress: Function, |
|
|
|
coin?: Coin, |
|
|
|
segwit?: boolean, |
|
|
|
}) => { |
|
|
|
const btc = new Btc(transport) |
|
|
|
|
|
|
|
const network = networks[coin] |
|
|
@ -171,7 +109,7 @@ export default async ({ transport, currentAccounts, onProgress, coin = 1, segwit |
|
|
|
const xpub58 = await getXpub58ByAccount({ account: currentAccount, network }) |
|
|
|
|
|
|
|
if (currentAccounts.includes(xpub58)) { |
|
|
|
return getAllAccounts(currentAccount + 1, accounts) // skip existing account
|
|
|
|
return getAllAccounts(currentAccount + 1, accounts) // Skip existing account
|
|
|
|
} |
|
|
|
|
|
|
|
const hdnode = getHDNode({ xpub58, network }) |
|
|
@ -182,12 +120,18 @@ export default async ({ transport, currentAccounts, onProgress, coin = 1, segwit |
|
|
|
transactions: transactions.length, |
|
|
|
}) |
|
|
|
|
|
|
|
if (transactions.length > 0) { |
|
|
|
const hasTransactions = transactions.length > 0 |
|
|
|
|
|
|
|
// If the first account is empty we still add it
|
|
|
|
if (currentAccount === 0 || hasTransactions) { |
|
|
|
accounts[currentAccount] = { |
|
|
|
id: xpub58, |
|
|
|
balance, |
|
|
|
transactions, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (hasTransactions) { |
|
|
|
return getAllAccounts(currentAccount + 1, accounts) |
|
|
|
} |
|
|
|
|
|
|
|