Skip to content

Commit

Permalink
fix: make getInscriptions to return txid and vout object (#15)
Browse files Browse the repository at this point in the history
* fix: make getInscriptions to return txid and vout object
  • Loading branch information
jrwbabylonlab authored Jul 30, 2024
1 parent 3014c7e commit df46fcc
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 66 deletions.
56 changes: 40 additions & 16 deletions docs/WalletIntegration.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ export interface UTXO {
scriptPubKey: string;
}

export interface Inscription {
// output of the inscription in the format of `txid:vout`
output: string;
export interface InscriptionIdentifier {
// hash of transaction that holds the ordinals/brc-2-/runes etc in the UTXO
txid: string;
// index of the output in the transaction
vout: number;
}

// supported networks
Expand Down Expand Up @@ -342,25 +344,47 @@ export class OKXWallet extends WalletProvider {
return await getTipHeight();
};

// Inscriptions(Ordinal/Runes/BRC-20 etc)
getInscriptions = async (): Promise<Inscription[]> => {
getInscriptions = async (): Promise<InscriptionIdentifier[]> => {
if (!this.okxWalletInfo) {
throw new Error("OKX Wallet not connected");
}
const inscriptions: Inscription[] = [];
// max num of iterations to prevent infinite loop
const MAX_ITERATIONS = 100;
// Fetch inscriptions in batches of 100
const limit = 100;
const inscriptionIdentifiers: InscriptionIdentifier[] = [];
let cursor = 0;
while (true) {
const { list } = await this.bitcoinNetworkProvider.getInscriptions(
cursor,
DEFAULT_INSCRIPTION_LIMIT,
);
inscriptions.push(...list);
if (list.length < DEFAULT_INSCRIPTION_LIMIT) {
break;
let iterations = 0;
try {
while (iterations < MAX_ITERATIONS) {
const { list } = await this.bitcoinNetworkProvider.getInscriptions(
cursor,
limit,
);
const identifiers = list.map((i: { output: string }) => {
const [txid, vout] = i.output.split(":");
return {
txid,
vout,
};
});
inscriptionIdentifiers.push(...identifiers);
if (list.length < limit) {
break;
}
cursor += limit;
iterations++;
if (iterations >= MAX_ITERATIONS) {
throw new Error(
"Exceeded maximum iterations when fetching inscriptions",
);
}
}
cursor += DEFAULT_INSCRIPTION_LIMIT;
} catch (error) {
throw new Error("Failed to get inscriptions from OKX Wallet");
}
return inscriptions;

return inscriptionIdentifiers;
};
}
```
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "simple-staking",
"version": "0.2.20",
"version": "0.2.21",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
12 changes: 9 additions & 3 deletions src/utils/utxo/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { postVerifyUtxoOrdinals, UtxoInfo } from "@/app/api/postFilterOrdinals";

import { Inscription, UTXO } from "../wallet/wallet_provider";
import { InscriptionIdentifier, UTXO } from "../wallet/wallet_provider";

/**
* Filters out UTXOs that contain ordinals.
Expand All @@ -17,19 +17,25 @@ import { Inscription, UTXO } from "../wallet/wallet_provider";
export const filterOrdinals = async (
utxos: UTXO[],
address: string,
getInscriptionsFromWalletCb: () => Promise<Inscription[]>,
getInscriptionsFromWalletCb?: () => Promise<InscriptionIdentifier[]>,
): Promise<UTXO[]> => {
if (!utxos.length) {
return [];
}
// fallback to Babylon API if the wallet does not support getting inscriptions
if (!getInscriptionsFromWalletCb) {
return filterFromApi(utxos, address);
}
// try to get the ordinals from the wallet first, if the wallet supports it
// otherwise fallback to the Babylon API
try {
const inscriptions = await getInscriptionsFromWalletCb();
// filter out the utxos that contains ordinals
return utxos.filter(
(utxo) =>
!inscriptions.find((i) => i.output === `${utxo.txid}:${utxo.vout}`),
!inscriptions.find((i) => {
return i.txid === utxo.txid && i.vout === utxo.vout;
}),
);
} catch (error) {
return filterFromApi(utxos, address);
Expand Down
4 changes: 2 additions & 2 deletions src/utils/wallet/providers/bitget_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from "../../mempool_api";
import {
Fees,
Inscription,
InscriptionIdentifier,
Network,
UTXO,
WalletProvider,
Expand Down Expand Up @@ -210,7 +210,7 @@ export class BitgetWallet extends WalletProvider {
return await getTipHeight();
};

getInscriptions(): Promise<Inscription[]> {
getInscriptions(): Promise<InscriptionIdentifier[]> {
throw new Error("Method not implemented.");
}
}
4 changes: 2 additions & 2 deletions src/utils/wallet/providers/keystone/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
import { WalletError, WalletErrorType } from "../../errors";
import {
Fees,
Inscription,
InscriptionIdentifier,
Network,
UTXO,
WalletProvider,
Expand Down Expand Up @@ -292,7 +292,7 @@ export class KeystoneWallet extends WalletProvider {
return await getTipHeight();
};

getInscriptions(): Promise<Inscription[]> {
getInscriptions(): Promise<InscriptionIdentifier[]> {
throw new Error("Method not implemented.");
}
}
Expand Down
55 changes: 41 additions & 14 deletions src/utils/wallet/providers/okx_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import {
pushTx,
} from "../../mempool_api";
import {
DEFAULT_INSCRIPTION_LIMIT,
Fees,
Inscription,
InscriptionIdentifier,
Network,
UTXO,
WalletInfo,
Expand Down Expand Up @@ -176,23 +175,51 @@ export class OKXWallet extends WalletProvider {
};

// Inscriptions are only available on OKX Wallet BTC mainnet (i.e okxWallet.bitcoin)
getInscriptions = async (): Promise<Inscription[]> => {
getInscriptions = async (): Promise<InscriptionIdentifier[]> => {
if (!this.okxWalletInfo) {
throw new Error("OKX Wallet not connected");
}
const inscriptions: Inscription[] = [];
let cursor = 0;
while (true) {
const { list } = await this.bitcoinNetworkProvider.getInscriptions(
cursor,
DEFAULT_INSCRIPTION_LIMIT,
if (this.networkEnv !== Network.MAINNET) {
throw new Error(
"Inscriptions are only available on OKX Wallet BTC mainnet",
);
inscriptions.push(...list);
if (list.length < DEFAULT_INSCRIPTION_LIMIT) {
break;
}
// max num of iterations to prevent infinite loop
const MAX_ITERATIONS = 100;
// Fetch inscriptions in batches of 100
const limit = 100;
const inscriptionIdentifiers: InscriptionIdentifier[] = [];
let cursor = 0;
let iterations = 0;
try {
while (iterations < MAX_ITERATIONS) {
const { list } = await this.bitcoinNetworkProvider.getInscriptions(
cursor,
limit,
);
const identifiers = list.map((i: { output: string }) => {
const [txid, vout] = i.output.split(":");
return {
txid,
vout,
};
});
inscriptionIdentifiers.push(...identifiers);
if (list.length < limit) {
break;
}
cursor += limit;
iterations++;
if (iterations >= MAX_ITERATIONS) {
throw new Error(
"Exceeded maximum iterations when fetching inscriptions",
);
}
}
cursor += DEFAULT_INSCRIPTION_LIMIT;
} catch (error) {
throw new Error("Failed to get inscriptions from OKX Wallet");
}
return inscriptions;

return inscriptionIdentifiers;
};
}
53 changes: 39 additions & 14 deletions src/utils/wallet/providers/onekey_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import {
pushTx,
} from "../../mempool_api";
import {
DEFAULT_INSCRIPTION_LIMIT,
Fees,
Inscription,
InscriptionIdentifier,
Network,
UTXO,
WalletInfo,
Expand Down Expand Up @@ -165,20 +164,46 @@ export class OneKeyWallet extends WalletProvider {
}

// Inscriptions are only available on oneKey Wallet BTC mainnet
getInscriptions = async (): Promise<Inscription[]> => {
const inscriptions: Inscription[] = [];
getInscriptions = async (): Promise<InscriptionIdentifier[]> => {
if (this.networkEnv !== Network.MAINNET) {
throw new Error("Inscriptions are only available on OneKey mainnet");
}
// max num of iterations to prevent infinite loop
const MAX_ITERATIONS = 100;
// Fetch inscriptions in batches of 100
const limit = 100;
const inscriptionIdentifiers: InscriptionIdentifier[] = [];
let cursor = 0;
while (true) {
const { list } = await this.bitcoinNetworkProvider.getInscriptions(
cursor,
DEFAULT_INSCRIPTION_LIMIT,
);
inscriptions.push(...list);
if (list.length < DEFAULT_INSCRIPTION_LIMIT) {
break;
let iterations = 0;
try {
while (iterations < MAX_ITERATIONS) {
const { list } = await this.bitcoinNetworkProvider.getInscriptions(
cursor,
limit,
);
const identifiers = list.map((i: { output: string }) => {
const [txid, vout] = i.output.split(":");
return {
txid,
vout,
};
});
inscriptionIdentifiers.push(...identifiers);
if (list.length < limit) {
break;
}
cursor += limit;
iterations++;
if (iterations >= MAX_ITERATIONS) {
throw new Error(
"Exceeded maximum iterations when fetching inscriptions",
);
}
}
cursor += DEFAULT_INSCRIPTION_LIMIT;
} catch (error) {
throw new Error("Failed to get inscriptions from OKX Wallet");
}
return inscriptions;

return inscriptionIdentifiers;
};
}
4 changes: 2 additions & 2 deletions src/utils/wallet/providers/tomo_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {

import {
Fees,
Inscription,
InscriptionIdentifier,
Network,
UTXO,
WalletInfo,
Expand Down Expand Up @@ -171,7 +171,7 @@ export class TomoWallet extends WalletProvider {
return await getTipHeight();
};

getInscriptions(): Promise<Inscription[]> {
getInscriptions(): Promise<InscriptionIdentifier[]> {
throw new Error("Method not implemented.");
}
}
14 changes: 6 additions & 8 deletions src/utils/wallet/wallet_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ export interface UTXO {
scriptPubKey: string;
}

export interface Inscription {
// output of the inscription in the format of `txid:vout`
output: string;
export interface InscriptionIdentifier {
// hash of transaction that holds the ordinals/brc-2-/runes etc in the UTXO
txid: string;
// index of the output in the transaction
vout: number;
}

// supported networks
export enum Network {
MAINNET = "mainnet",
Expand All @@ -41,9 +42,6 @@ export type WalletInfo = {
address: string;
};

// Default number of inscriptions to fetch in a single method call
export const DEFAULT_INSCRIPTION_LIMIT = 100;

/**
* Abstract class representing a wallet provider.
* Provides methods for connecting to a wallet, retrieving wallet information, signing transactions, and more.
Expand Down Expand Up @@ -153,5 +151,5 @@ export abstract class WalletProvider {
* Retrieves the inscriptions for the connected wallet.
* @returns A promise that resolves to an array of inscriptions.
*/
abstract getInscriptions(): Promise<Inscription[]>;
abstract getInscriptions(): Promise<InscriptionIdentifier[]>;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { postVerifyUtxoOrdinals } from "@/app/api/postFilterOrdinals";
import { filterOrdinals } from "@/utils/utxo";
import { Inscription, UTXO } from "@/utils/wallet/wallet_provider";
import { InscriptionIdentifier, UTXO } from "@/utils/wallet/wallet_provider";

// Mock the dependencies
jest.mock("@/app/api/postFilterOrdinals");
Expand All @@ -23,7 +23,12 @@ describe("filterOrdinals", () => {
});

it("should filter out UTXOs that contain ordinals from the wallet", async () => {
const mockInscriptions: Inscription[] = [{ output: "txid1:0" }];
const mockInscriptions: InscriptionIdentifier[] = [
{
txid: "txid1",
vout: 0,
},
];
const getInscriptionsFromWalletCb = jest
.fn()
.mockResolvedValue(mockInscriptions);
Expand Down

0 comments on commit df46fcc

Please sign in to comment.