diff --git a/sdk/src/ordinal-api/index.ts b/sdk/src/ordinal-api/index.ts index 81f53586..ab91e1cd 100644 --- a/sdk/src/ordinal-api/index.ts +++ b/sdk/src/ordinal-api/index.ts @@ -477,18 +477,18 @@ export class OrdinalsClient { } /** - * Retrieves inscriptions based on the address. + * Retrieves outputs based on the address. * @param {String} address - The Bitcoin address to check. * @param {('cardinal' | 'inscribed' | 'runic' | 'any')} [type] - Optional type of UTXOs to be returned. If omitted returns all UTXOs. - * @returns {Promise} A Promise that resolves to the inscription data. + * @returns {Promise} A Promise that resolves to the inscription data. * * @example * ```typescript * const client = new OrdinalsClient("regtest"); * const address: string = "enter_address_here"; * const type: 'cardinal' | 'inscribed' | 'runic' = "enter_type_here"; - * const output = await client.getOutputsFromAddress(address, type?); - * console.log("Output:", output); + * const outputs = await client.getOutputsFromAddress(address, type?); + * console.log("Outputs:", outputs); * ``` */ getOutputsFromAddress(address: string, type?: 'cardinal' | 'inscribed' | 'runic' | 'any'): Promise { @@ -498,6 +498,24 @@ export class OrdinalsClient { return this.getJson(`${this.basePath}/outputs/${address}?${searchParams}`); } + /** + * Retrieves outputs based on the out points list. + * @param {string[]} data - The list of out points to check. + * @returns {Promise} A Promise that resolves to the inscription data. + * + * @example + * ```typescript + * const client = new OrdinalsClient("regtest"); + * const outpoints: string[] = "enter_outpoints_here"; + * const outputs = await client.getOutputsFromOutPoints(outpoints); + * console.log("Outputs:", outputs); + * ``` + */ + getOutputsFromOutPoints(outpoints: string[]): Promise { + // https://docs.ordinals.com/guides/api.html#description-19 + return this.postJson(`${this.basePath}/outputs/`, outpoints); + } + /** * Retrieves an inscription based on its sat (something specific to your use case). * @param {number} sat - The sat of the inscription to retrieve. @@ -554,6 +572,24 @@ export class OrdinalsClient { return (await response.json()) as Promise; } + /** + * @ignore + */ + private async postJson(url: string, body: object): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify(body), + }); + if (!response.ok) { + throw new Error(response.statusText); + } + return (await response.json()) as Promise; + } + /** * @ignore */ diff --git a/sdk/src/wallet/utxo.ts b/sdk/src/wallet/utxo.ts index bda8bd7b..0f86e1d2 100644 --- a/sdk/src/wallet/utxo.ts +++ b/sdk/src/wallet/utxo.ts @@ -27,18 +27,24 @@ class TreeNode { } } -const createUtxoNodes = (utxos: UTXO[], cardinalOutputsSet: Set) => - utxos.reduce( - (acc, utxo) => { - if (!cardinalOutputsSet.has(OutPoint.toString(utxo))) - // mark node as containing ordinals - acc.push(new TreeNode({ ...utxo, cardinal: false })); - else acc.push(null); - - return acc; - }, - [] as (TreeNode | null)[] - ); +const isCardinal = (output: OutputJson) => output.inscriptions.length === 0 && Object.keys(output.runes).length === 0; + +const createUtxoNodes = async (utxos: UTXO[], cardinalOutputsSet: Set, ordinalsClient: OrdinalsClient) => { + const outputs = await ordinalsClient.getOutputsFromOutPoints(utxos.map(OutPoint.toString)); + + return utxos.map((utxo, index) => { + const output = outputs[index]; + + if (!cardinalOutputsSet.has(OutPoint.toString(utxo))) + return new TreeNode({ + ...utxo, + cardinal: isCardinal(output), + indexed: output.indexed, + }); + + return null; + }); +}; const processNodes = async ( rootNodes: (TreeNode | null)[], @@ -58,22 +64,22 @@ const processNodes = async ( if (transaction.status.confirmed) { // if confirmed check if it contains ordinals childNode.val.cardinal = cardinalOutputsSet.has(OutPoint.toString(childNode.val)); - } else { - const response = await ordinalsClient.getInscriptionsFromOutPoint(childNode.val); - - if (Object.keys(response.runes).length === 0 && response.inscriptions.length === 0) { - // if not confirmed check inputs for current utxo - childNode.children = transaction.vin.map((vin) => { - return new TreeNode({ - vout: vin.vout, - txid: vin.txid, - // mark node as containing ordinals - cardinal: false, - }); + } else if (!childNode.val.indexed || childNode.val.cardinal) { + const outputs = await ordinalsClient.getOutputsFromOutPoints(transaction.vin.map(OutPoint.toString)); + + // if not confirmed check inputs for current utxo + childNode.children = transaction.vin.map((vin, index) => { + const output = outputs[index]; + + return new TreeNode({ + vout: vin.vout, + txid: vin.txid, + cardinal: isCardinal(output), + indexed: output.indexed, }); + }); - queue.push(...childNode.children); - } + queue.push(...childNode.children); } } }; @@ -85,7 +91,7 @@ const checkUtxoNode = (node: TreeNode) => { return node.children.reduce((acc, child) => acc && checkUtxoNode(child), true); }; -type OutputNodeData = Pick & { cardinal: boolean }; +type OutputNodeData = Pick & { cardinal: boolean; indexed: boolean }; export interface Input { txid: string; @@ -177,7 +183,7 @@ export async function createBitcoinPsbt( const cardinalOutputsSet = new Set(cardinalOutputs.map((output) => output.outpoint)); - const rootUtxoNodes = createUtxoNodes(utxos, cardinalOutputsSet); + const rootUtxoNodes = await createUtxoNodes(utxos, cardinalOutputsSet, ordinalsClient); await processNodes(rootUtxoNodes, cardinalOutputsSet, esploraClient, ordinalsClient); @@ -393,7 +399,7 @@ export async function estimateTxFee( const cardinalOutputsSet = new Set(cardinalOutputs.map((output) => output.outpoint)); - const rootUtxoNodes = createUtxoNodes(utxos, cardinalOutputsSet); + const rootUtxoNodes = await createUtxoNodes(utxos, cardinalOutputsSet, ordinalsClient); await processNodes(rootUtxoNodes, cardinalOutputsSet, esploraClient, ordinalsClient); @@ -509,7 +515,7 @@ export async function getBalance(address?: string) { const cardinalOutputsSet = new Set(cardinalOutputs.map((output) => output.outpoint)); - const rootUtxoNodes = createUtxoNodes(utxos, cardinalOutputsSet); + const rootUtxoNodes = await createUtxoNodes(utxos, cardinalOutputsSet, ordinalsClient); await processNodes(rootUtxoNodes, cardinalOutputsSet, esploraClient, ordinalsClient);