Skip to content

Commit

Permalink
chore: improve tree traversal
Browse files Browse the repository at this point in the history
  • Loading branch information
slavastartsev committed Dec 20, 2024
1 parent 11f3a3e commit a4d0683
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 34 deletions.
44 changes: 40 additions & 4 deletions sdk/src/ordinal-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OutputJson>} A Promise that resolves to the inscription data.
* @returns {Promise<OutputJson[]>} 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<OutputJson[]> {
Expand All @@ -498,6 +498,24 @@ export class OrdinalsClient {
return this.getJson<OutputJson[]>(`${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<OutputJson[]>} 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<OutputJson[]> {
// https://docs.ordinals.com/guides/api.html#description-19
return this.postJson<OutputJson[]>(`${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.
Expand Down Expand Up @@ -554,6 +572,24 @@ export class OrdinalsClient {
return (await response.json()) as Promise<T>;
}

/**
* @ignore
*/
private async postJson<T>(url: string, body: object): Promise<T> {
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);

Check failure on line 588 in sdk/src/ordinal-api/index.ts

View workflow job for this annotation

GitHub Actions / SDK

test/gateway.test.ts > Gateway Tests > should start order

Error: Not Found ❯ OrdinalsClient.postJson src/ordinal-api/index.ts:588:19 ❯ createUtxoNodes src/wallet/utxo.ts:33:21 ❯ Module.createBitcoinPsbt src/wallet/utxo.ts:186:27 ❯ GatewayApiClient.startOrder src/gateway/client.ts:200:26 ❯ test/gateway.test.ts:168:24

Check failure on line 588 in sdk/src/ordinal-api/index.ts

View workflow job for this annotation

GitHub Actions / SDK

test/utxo.test.ts > UTXO Tests > should spend from address to create a transaction with an OP return output

Error: Not Found ❯ OrdinalsClient.postJson src/ordinal-api/index.ts:588:19 ❯ createUtxoNodes src/wallet/utxo.ts:33:21 ❯ Module.createBitcoinPsbt src/wallet/utxo.ts:186:27 ❯ test/utxo.test.ts:102:44 ❯ test/utxo.test.ts:86:17 ❯ test/utxo.test.ts:84:9

Check failure on line 588 in sdk/src/ordinal-api/index.ts

View workflow job for this annotation

GitHub Actions / SDK

test/utxo.test.ts > UTXO Tests > should estimate the fee for a transaction

Error: Not Found ❯ OrdinalsClient.postJson src/ordinal-api/index.ts:588:19 ❯ createUtxoNodes src/wallet/utxo.ts:33:21 ❯ Module.estimateTxFee src/wallet/utxo.ts:402:27 ❯ test/utxo.test.ts:357:45 ❯ test/utxo.test.ts:337:9

Check failure on line 588 in sdk/src/ordinal-api/index.ts

View workflow job for this annotation

GitHub Actions / SDK

test/utxo.test.ts > UTXO Tests > should not spend outputs with inscriptions

Error: Not Found ❯ OrdinalsClient.postJson src/ordinal-api/index.ts:588:19 ❯ createUtxoNodes src/wallet/utxo.ts:33:21 ❯ Module.createBitcoinPsbt src/wallet/utxo.ts:186:27 ❯ test/utxo.test.ts:391:9

Check failure on line 588 in sdk/src/ordinal-api/index.ts

View workflow job for this annotation

GitHub Actions / SDK

test/utxo.test.ts > UTXO Tests > should return address balance

Error: Not Found ❯ OrdinalsClient.postJson src/ordinal-api/index.ts:588:19 ❯ createUtxoNodes src/wallet/utxo.ts:33:21 ❯ Module.getBalance src/wallet/utxo.ts:518:27 ❯ test/utxo.test.ts:429:25

Check failure on line 588 in sdk/src/ordinal-api/index.ts

View workflow job for this annotation

GitHub Actions / SDK

test/utxo.test.ts > UTXO Tests > outputs could be spent if not confirmed by ord service

Error: Not Found ❯ OrdinalsClient.postJson src/ordinal-api/index.ts:588:19 ❯ createUtxoNodes src/wallet/utxo.ts:33:21 ❯ Module.getBalance src/wallet/utxo.ts:518:27 ❯ test/utxo.test.ts:472:29
}
return (await response.json()) as Promise<T>;
}

/**
* @ignore
*/
Expand Down
66 changes: 36 additions & 30 deletions sdk/src/wallet/utxo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,24 @@ class TreeNode<T> {
}
}

const createUtxoNodes = (utxos: UTXO[], cardinalOutputsSet: Set<string>) =>
utxos.reduce(
(acc, utxo) => {
if (!cardinalOutputsSet.has(OutPoint.toString(utxo)))
// mark node as containing ordinals
acc.push(new TreeNode<OutputNodeData>({ ...utxo, cardinal: false }));
else acc.push(null);

return acc;
},
[] as (TreeNode<OutputNodeData> | null)[]
);
const isCardinal = (output: OutputJson) => output.inscriptions.length === 0 && Object.keys(output.runes).length === 0;

const createUtxoNodes = async (utxos: UTXO[], cardinalOutputsSet: Set<string>, 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<OutputNodeData>({
...utxo,
cardinal: isCardinal(output),
indexed: output.indexed,
});

return null;
});
};

const processNodes = async (
rootNodes: (TreeNode<OutputNodeData> | null)[],
Expand All @@ -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<OutputNodeData>({
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<OutputNodeData>({
vout: vin.vout,
txid: vin.txid,
cardinal: isCardinal(output),
indexed: output.indexed,
});
});

queue.push(...childNode.children);
}
queue.push(...childNode.children);
}
}
};
Expand All @@ -85,7 +91,7 @@ const checkUtxoNode = (node: TreeNode<OutputNodeData>) => {
return node.children.reduce((acc, child) => acc && checkUtxoNode(child), true);
};

type OutputNodeData = Pick<UTXO, 'txid' | 'vout'> & { cardinal: boolean };
type OutputNodeData = Pick<UTXO, 'txid' | 'vout'> & { cardinal: boolean; indexed: boolean };

export interface Input {
txid: string;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit a4d0683

Please sign in to comment.