multichain-controller
Version:
A Multichain crypto wallet library that supports Ethereum, Bitcoin, Solana, Waves and other EVM compatible blockchains E.g. Binance Smart Chain, Polygon, Avalanche etc.
180 lines (160 loc) • 4.85 kB
text/typescript
import axios from 'axios';
import { URLSearchParams } from 'url';
import { sortUTXOs, UTXO } from '../utils/utxo';
import { DEFAULT_TIMEOUT } from './timeout';
export enum BlockchainNetwork {
Bitcoin = 'btc',
BitcoinCash = 'bch',
BitcoinTestnet = 'btc-testnet',
BitcoinCashTestnet = 'bch-testnet',
}
interface BlockchainTransaction {
txid: string; // "550b293355f5274e513c65f846311fd5817d13bcfcd492ab94ff2725ba94f21e"
size: number; // 124
version: number; // 1
locktime: number; // 0
fee: number; // 0
inputs: [
{
coinbase: boolean; // true
txid: string; // "0000000000000000000000000000000000000000000000000000000000000000"
output: number; // 4294967295
sigscript: string; // "03e3b9162f696d2f"
sequence: number; // 4294967295
pkscript: null;
value: null;
address: null;
witness: unknown[];
}
];
outputs: [
{
address: string; // "bchtest:qp7k5sm9dcmvse2rgmkj2ktylm9fgqcnv5kp2hrs0h"
pkscript: string; // "76a9147d6a43656e36c8654346ed255964feca9403136588ac"
value: number; // 39062500
spent: boolean; // false
spender: null;
},
{
address: null;
pkscript: string; // "6a14883805620000000000000000faee4177fe240000"
value: number; // 0
spent: boolean; // false
spender: null;
}
];
block: {
height?: number; // 1489379
position?: number; // 0
mempool?: number;
};
deleted: boolean; // false
time: number; // 1646186011
rbf: boolean; // false
weight: number; // 496
}
const fetchLatestBlock = async (
network: BlockchainNetwork
): Promise<number> => {
const statsUrl = `https://api.blockchain.info/haskoin-store/${network}/block/best?notx=true`;
const statsResponse = (await axios.get<{ height: number }>(statsUrl)).data;
return statsResponse.height;
};
const fetchUTXO = (network: BlockchainNetwork) => async (
txHash: string,
vOut: number
): Promise<UTXO> => {
const url = `https://api.blockchain.info/haskoin-store/${network}/transaction/${txHash}`;
const response = (
await axios.get<BlockchainTransaction>(`${url}`, {
timeout: DEFAULT_TIMEOUT,
})
).data;
const confirmations =
!response.block || !response.block.height
? 0
: Math.max(
(await fetchLatestBlock(network)) - response.block.height + 1,
0
);
return {
txHash,
block: response.block && response.block.height ? response.block.height : 0,
amount: response.outputs[vOut].value,
confirmations,
};
};
const fetchUTXOs = (network: BlockchainNetwork) => async (
address: string,
confirmations: number,
limit: number = 25,
offset: number = 0
): Promise<readonly UTXO[]> =>
fetchTXs(network)(address, confirmations, limit, offset, true);
const fetchTXs = (network: BlockchainNetwork) => async (
address: string,
confirmations: number = 0,
limit: number = 25,
offset: number = 0,
onlyUnspent: boolean = false
): Promise<readonly UTXO[]> => {
const url = `https://api.blockchain.info/haskoin-store/${network}/address/${address}/transactions/full?limit=${limit}&offset=${offset}`;
const response = (
await axios.get<BlockchainTransaction[]>(url, {
timeout: DEFAULT_TIMEOUT,
})
).data;
let latestBlock: number | undefined;
const received: UTXO[] = [];
for (const tx of response) {
latestBlock = latestBlock || (await fetchLatestBlock(network));
const txConfirmations =
tx.block && tx.block.height
? Math.max(latestBlock - tx.block.height + 1, 0)
: 0;
for (let i = 0; i < tx.outputs.length; i++) {
const vout = tx.outputs[i];
if (
vout.address === address &&
// If the onlyUnspent flag is true, check that the tx is unspent.
(!onlyUnspent || vout.spent === false)
) {
received.push({
txHash: tx.txid,
amount: vout.value,
vOut: i,
confirmations: txConfirmations,
});
}
}
}
return received
.filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations)
.sort(sortUTXOs);
};
export const broadcastTransaction = (network: BlockchainNetwork) => async (
txHex: string
): Promise<string> => {
if (network !== BlockchainNetwork.Bitcoin) {
throw new Error(
`Broadcasting ${network} transactions not supported by endpoint.`
);
}
const url = `https://blockchain.info/pushtx`;
const params = new URLSearchParams();
params.append('tx', txHex);
const response = await axios.post(url, params, {
timeout: DEFAULT_TIMEOUT,
});
if ((response.data as any).error) {
throw new Error((response.data as any).error);
}
return response.data;
};
export const Blockchain = {
networks: BlockchainNetwork,
fetchUTXO,
fetchUTXOs,
broadcastTransaction,
fetchTXs,
};