Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SW-1331] Use own middleware to retrieve Bitcoin UTXOs #2380

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/protocols/bitcoin/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const DUST_AMOUNT = 546;

export const BTC_NETWORK_DEFAULT_SETTINGS: INetworkTypeProtocolDefaultSettings = {
[NETWORK_TYPE_MAINNET]: {
nodeUrl: 'https://blockstream.info/api',
nodeUrl: 'https://bitcoin.prd.service.aepps.com/api/BTC/mainnet',
},
[NETWORK_TYPE_TESTNET]: {
nodeUrl: 'https://blockstream.info/testnet/api',
Expand All @@ -23,7 +23,7 @@ export const BTC_NETWORK_DEFAULT_SETTINGS: INetworkTypeProtocolDefaultSettings =

export const BTC_NETWORK_ADDITIONAL_SETTINGS: IDefaultNetworkTypeData<any> = { // TODO - type
[NETWORK_TYPE_MAINNET]: {
explorerUrl: 'https://blockstream.info',
explorerUrl: 'https://bitcoin.prd.service.aepps.com/api/BTC/mainnet',
},
[NETWORK_TYPE_TESTNET]: {
explorerUrl: 'https://blockstream.info/testnet',
Expand Down
4 changes: 2 additions & 2 deletions src/protocols/bitcoin/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export function normalizeTransactionStructure(
transactionOwner: transactionOwner as any,
hash: txid, // TODO: we can go with additional field
microTime: status.block_time * 1000,
pending: !status.confirmed,
pending: transaction.confirmations === -1,
tx: {
amount: satoshiToBtc(vout[0].value),
amount: satoshiToBtc(transaction.value),
fee: satoshiToBtc(fee),
senderId: vin[0].prevout.scriptpubkey_address,
recipientId: vout[0].scriptpubkey_address,
Expand Down
87 changes: 30 additions & 57 deletions src/protocols/bitcoin/libs/BitcoinAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
networks,
Psbt,
Transaction,
script as ScriptCompiler,
} from 'bitcoinjs-lib';
import { toBitcoin, toSatoshi } from 'satoshi-bitcoin';

Expand Down Expand Up @@ -112,21 +113,16 @@ export class BitcoinAdapter extends BaseProtocolAdapter {
const { activeNetwork } = useNetworks();

const { nodeUrl } = activeNetwork.value.protocols.bitcoin;
const {
chain_stats: { funded_txo_sum: chainFunded, spent_txo_sum: chainSpent },
mempool_stats: { funded_txo_sum: mempoolFunded, spent_txo_sum: mempoolSpent },
} = await fetchJson(`${nodeUrl}/address/${address}`);
return toBitcoin(
Number(chainFunded) - Number(chainSpent) + Number(mempoolFunded) - Number(mempoolSpent),
).toString();
const { confirmed, unconfirmed } = await fetchJson(`${nodeUrl}/address/${address}/balance`);
return toBitcoin(confirmed + unconfirmed).toString();
}

override async isAccountUsed(address: string): Promise<boolean> {
const { activeNetwork } = useNetworks();

const { nodeUrl } = activeNetwork.value.protocols.bitcoin;
const { chain_stats: { funded_txo_sum: chainFunded } } = await fetchJson(`${nodeUrl}/address/${address}`);
return !!chainFunded;
const txs = await fetchJson(`${nodeUrl}/address/${address}/txs`);
return txs.length > 0;
}

override getHdWalletAccountFromMnemonicSeed(
Expand Down Expand Up @@ -166,7 +162,7 @@ export class BitcoinAdapter extends BaseProtocolAdapter {

const { nodeUrl } = activeNetwork.value.protocols.bitcoin;
const rawTransactions = await fetchJson(lastTxId
? `${nodeUrl}/address/${address}/txs/chain/${lastTxId}`
? `${nodeUrl}/address/tx/${lastTxId}`
: `${nodeUrl}/address/${address}/txs`);
return rawTransactions.map((t: any) => normalizeTransactionStructure(t, address));
}
Expand Down Expand Up @@ -207,24 +203,7 @@ export class BitcoinAdapter extends BaseProtocolAdapter {
const psbt = new Psbt({ network });

// Fetch all the Unspent transaction outputs
const utxos = await fetchJson(`${nodeUrl}/address/${options.address}/utxo`);

/**
* Fetch raw transaction in hex of only confirmed UTXOs.
* Filter UTXos from mempool/unconfirmed
*/
const fetchHexPromises = utxos
.map(async ({ txid, vout, value }: { txid: string, vout: number, value: number }) => {
const rawTransactionBody = await fetch(`${nodeUrl}/tx/${txid}/hex`);
return {
txid,
vout, // Output vector index
value, // Amount
transactionInHex: await rawTransactionBody.text(),
};
});

const utxoDetails = await Promise.all(fetchHexPromises);
const utxos = await fetchJson(`${nodeUrl}/address/${options.address}/?unspent=true`);

const amountInSatoshi = toSatoshi(amountInBtc);
const feeInSatoshi = toSatoshi(options.fee);
Expand All @@ -233,8 +212,8 @@ export class BitcoinAdapter extends BaseProtocolAdapter {

// eslint-disable-next-line no-restricted-syntax
for (const {
txid, vout, value, transactionInHex,
} of utxoDetails) {
mintTxid, mintIndex, value, script,
} of utxos) {
/**
* Use minimum number of UTXOs for this transaction
* TODO: Select minimum number of UTXOs based on the amount and the input size
Expand All @@ -243,28 +222,20 @@ export class BitcoinAdapter extends BaseProtocolAdapter {
break;
}

const parsedTransaction = Transaction.fromHex(transactionInHex);
const input = parsedTransaction.outs.at(vout);
const isSegwit = parsedTransaction.hasWitnesses();

if (isSegwit) {
psbt.addInput({
hash: txid,
index: vout,
witnessUtxo: {
script: input!.script,
value,
},
});
} else {
psbt.addInput({
hash: txid,
index: vout,
nonWitnessUtxo: Buffer.from(transactionInHex, 'hex'),
});
}
// Get the witness script for the unspent output.
const witnessScript = ScriptCompiler.fromASM(script);

psbt.addInput({
hash: mintTxid,
index: mintIndex,
witnessUtxo: {
script: witnessScript,
value,
},
});

totalBalance += value;

if (totalBalance >= amountInSatoshi + feeInSatoshi) {
hasSufficientBalance = true;
}
Expand Down Expand Up @@ -306,27 +277,29 @@ export class BitcoinAdapter extends BaseProtocolAdapter {
const { activeNetwork } = useNetworks();
const { nodeUrl } = activeNetwork.value.protocols.bitcoin;

const rawTransaction = (await this.constructAndSignTx(amount, recipient, options)).toHex();
const requestBody = {
rawTx: (await this.constructAndSignTx(amount, recipient, options)).toHex(),
};

// Broadcast raw transaction
const requestOptions = {
method: 'POST',
headers: new Headers({
'Content-Type': 'text/plain',
}),
body: rawTransaction,
body: JSON.stringify(requestBody),
redirect: 'follow' as RequestRedirect,
};

const transactionId = await fetch(`${nodeUrl}/tx`, requestOptions)
.then(async (response) => {
const response = await fetch(`${nodeUrl}/tx/send`, requestOptions)
.then(async (rawResponse) => {
if (response.status !== 200) {
throw new Error(await response.text());
throw new Error(await rawResponse.text());
}
return response.text();
return rawResponse.json();
});
return {
hash: transactionId,
hash: response.txid,
};
}
}
Loading