fetchPaymentUtxos.ts•3.17 kB
import { P2PKH, Transaction, Utils } from "@bsv/sdk";
import type { Utxo } from "js-1sat-ord";
const { toBase64 } = Utils;
/**
* Type definition for WhatsOnChain UTXO response
*/
interface WhatsOnChainUtxo {
tx_hash: string;
tx_pos: number;
value: number;
height: number;
address?: string;
}
/**
* Fetches unspent transaction outputs (UTXOs) for a given address.
* Only returns confirmed unspent outputs.
*
* @param address - The address to fetch UTXOs for
* @returns Array of UTXOs or undefined if an error occurs
*/
export async function fetchPaymentUtxos(
address: string,
): Promise<Utxo[] | undefined> {
if (!address) {
console.error("fetchPaymentUtxos: No address provided");
return undefined;
}
try {
// Fetch UTXOs from WhatsOnChain API
const response = await fetch(
`https://api.whatsonchain.com/v1/bsv/main/address/${address}/unspent`,
);
if (!response.ok) {
console.error(
`WhatsOnChain API error: ${response.status} ${response.statusText}`,
);
return undefined;
}
const data = (await response.json()) as WhatsOnChainUtxo[];
// Validate response format
if (!Array.isArray(data)) {
console.error("Invalid response format from WhatsOnChain API");
return undefined;
}
// For testing purposes (FOR TESTING ONLY - REMOVE IN PRODUCTION)
// const limitUTXOs = data.slice(0, 2);
// Process each UTXO
const utxos: (Utxo | null)[] = await Promise.all(
data.map(async (utxo: WhatsOnChainUtxo) => {
// Get the transaction hex to extract the correct script
const script = await getScriptFromTransaction(
utxo.tx_hash,
utxo.tx_pos,
);
if (!script) {
console.warn(
`Could not get script for UTXO: ${utxo.tx_hash}:${utxo.tx_pos}`,
);
return null;
}
return {
txid: utxo.tx_hash,
vout: utxo.tx_pos,
satoshis: utxo.value,
script: script,
};
}),
);
// Filter out any null entries from failed processing
const validUtxos = utxos.filter((utxo) => utxo !== null) as Utxo[];
return validUtxos;
} catch (error) {
console.error("Error fetching payment UTXOs:", error);
return undefined;
}
}
/**
* Gets the script from a transaction for a specific output index
*
* @param txid - The transaction ID
* @param vout - The output index
* @returns The script as hex string or undefined if an error occurs
*/
async function getScriptFromTransaction(
txid: string,
vout: number,
): Promise<string | undefined> {
try {
const response = await fetch(
`https://api.whatsonchain.com/v1/bsv/main/tx/${txid}/hex`,
);
if (!response.ok) {
console.error(
`WhatsOnChain API error fetching tx hex: ${response.status} ${response.statusText}`,
);
return undefined;
}
const txHex = await response.text();
const tx = Transaction.fromHex(txHex);
const output = tx.outputs[vout];
if (!output) {
console.error(`Output index ${vout} not found in transaction ${txid}`);
return undefined;
}
return toBase64(output.lockingScript.toBinary());
} catch (error) {
console.error(
`Error getting script for transaction ${txid}:${vout}:`,
error,
);
return undefined;
}
}