Skip to content

Commit

Permalink
refactor: make ordinals com as main source of data for double checkin…
Browse files Browse the repository at this point in the history
…g utxos
  • Loading branch information
alter-eggo committed Feb 22, 2024
1 parent 690bb51 commit a27e281
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 22 deletions.
43 changes: 42 additions & 1 deletion src/app/query/bitcoin/bitcoin-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,37 @@ export interface UtxoWithDerivationPath extends UtxoResponseItem {
derivationPath: string;
}

interface BestinslotInscription {
inscription_name: string | null;
inscription_id: string;
inscription_number: number;
metadata: any | null;
wallet: string;
mime_type: string;
media_length: number;
genesis_ts: number;
genesis_height: number;
genesis_fee: number;
output_value: number;
satpoint: string;
collection_name: string | null;
collection_slug: string | null;
last_transfer_block_height: number;
content_url: string;
bis_url: string;
byte_size: number;
}

export interface BestinslotInscriptionByIdResponse {
data: BestinslotInscription;
block_height: number;
}

export interface BestinslotInscriptionsByTxIdResponse {
data: { inscription_id: string }[];
blockHeight: number;
}

class BestinslotInscriptionsApi {
private defaultOptions = {
headers: {
Expand All @@ -29,7 +60,7 @@ class BestinslotInscriptionsApi {
constructor(public configuration: Configuration) {}

async getInscriptionsByTransactionId(id: string) {
const resp = await axios.get<{ data: { inscription_id: string }[]; blockHeight: number }>(
const resp = await axios.get<BestinslotInscriptionsByTxIdResponse>(
`https://api.bestinslot.xyz/v3/inscription/in_transaction?tx_id=${id}`,
{
...this.defaultOptions,
Expand All @@ -38,6 +69,16 @@ class BestinslotInscriptionsApi {

return resp.data;
}

async getInscriptionById(id: string) {
const resp = await axios.get<BestinslotInscriptionByIdResponse>(
`https://api.bestinslot.xyz/v3/inscription/single_info_id?inscription_id=${id}`,
{
...this.defaultOptions,
}
);
return resp.data;
}
}

class AddressApi {
Expand Down
88 changes: 67 additions & 21 deletions src/app/query/bitcoin/transaction/use-check-utxos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { useBitcoinClient } from '@app/store/common/api-clients.hooks';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';

import type {
BestinslotInscriptionByIdResponse,
BestinslotInscriptionsByTxIdResponse,
} from '../bitcoin-client';
import { getNumberOfInscriptionOnUtxoUsingOrdinalsCom } from '../ordinals/inscriptions.query';

class PreventTransactionError extends Error {
Expand All @@ -22,6 +26,7 @@ interface FilterOutIntentionalInscriptionsSpendArgs {
inputs: btc.TransactionInput[];
intentionalSpendUtxoIds: string[];
}

export function filterOutIntentionalUtxoSpend({
inputs,
intentionalSpendUtxoIds,
Expand All @@ -36,6 +41,42 @@ export function filterOutIntentionalUtxoSpend({
});
}

interface CheckInscribedUtxosByBestinslotArgs {
inputs: btc.TransactionInput[];
txids: string[];
getInscriptionsByTransactionId: (id: string) => Promise<BestinslotInscriptionsByTxIdResponse>;

Check failure on line 47 in src/app/query/bitcoin/transaction/use-check-utxos.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

Function property signature is forbidden. Use a method shorthand instead
getInscriptionById: (id: string) => Promise<BestinslotInscriptionByIdResponse>;

Check failure on line 48 in src/app/query/bitcoin/transaction/use-check-utxos.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

Function property signature is forbidden. Use a method shorthand instead
}

async function checkInscribedUtxosByBestinslot({
inputs,
txids,
getInscriptionsByTransactionId,
getInscriptionById,
}: CheckInscribedUtxosByBestinslotArgs): Promise<boolean> {
/**
* @description Get the list of inscriptions moving on a transaction
* @see https://docs.bestinslot.xyz/reference/api-reference/ordinals-and-brc-20-and-bitmap-v3-api-mainnet+testnet/inscriptions
*/
const inscriptionIdsList = await Promise.all(txids.map(id => getInscriptionsByTransactionId(id)));

const inscriptionIds = inscriptionIdsList.flatMap(inscription =>
inscription.data.map(data => data.inscription_id)
);

const inscriptionsList = await Promise.all(inscriptionIds.map(id => getInscriptionById(id)));

const hasInscribedUtxos = inscriptionsList.some(resp => {
return inputs.some(input => {
if (!input.txid) throw new Error('Transaction ID is missing in the input');
const idWithIndex = `${bytesToHex(input.txid)}:${input.index}`;
return resp.data.satpoint.includes(idWithIndex);
});
});

return hasInscribedUtxos;
}

export function useCheckInscribedUtxos(blockTxAction?: () => void) {
const client = useBitcoinClient();
const analytics = useAnalytics();
Expand Down Expand Up @@ -67,14 +108,20 @@ export function useCheckInscribedUtxos(blockTxAction?: () => void) {
throw new Error('Utxos list cannot be empty');
}

const responses = await Promise.all(
txids.map(id => client.bestinslotInscriptionsApi.getInscriptionsByTransactionId(id))
const ordinalsComResponses = await Promise.all(
txids.map(async (id, index) => {
const inscriptionIndex = inputs[index].index;
if (isUndefined(inscriptionIndex)) {
throw new Error('Inscription index is missing in the input');
}
const num = await getNumberOfInscriptionOnUtxoUsingOrdinalsCom(id, inscriptionIndex);
return num > 0;
})
);

const hasInscribedUtxo = responses.some(resp => {
return resp.data.length > 0;
});
const hasInscribedUtxo = ordinalsComResponses.some(resp => resp);

// if there are inscribed utxos in the transaction, and no error => prevent the transaction
if (hasInscribedUtxo) {
void analytics.track('utxos_includes_inscribed_one', {
txids,
Expand All @@ -83,41 +130,40 @@ export function useCheckInscribedUtxos(blockTxAction?: () => void) {
return true;
}

// if there are no inscribed utxos in the transaction => allow the transaction
return false;
} catch (e) {
if (e instanceof PreventTransactionError) {
throw e;
}

void analytics.track('error_checking_utxos_from_bestinslot', {
void analytics.track('error_checking_utxos_from_ordinalscom', {
txids,
});

const ordinalsComResponses = await Promise.all(
txids.map(async (id, index) => {
const inscriptionIndex = inputs[index].index;
if (isUndefined(inscriptionIndex)) {
throw new Error('Inscription index is missing in the input');
}
const num = await getNumberOfInscriptionOnUtxoUsingOrdinalsCom(id, inscriptionIndex);
return num > 0;
})
);

const hasInscribedUtxo = ordinalsComResponses.some(resp => resp);
const hasInscribedUtxo = await checkInscribedUtxosByBestinslot({
inputs,
txids,
getInscriptionsByTransactionId:
client.bestinslotInscriptionsApi.getInscriptionsByTransactionId,
getInscriptionById: client.bestinslotInscriptionsApi.getInscriptionById,
});

// if there are inscribed utxos in the transaction, and no error => prevent the transaction
if (hasInscribedUtxo) {
void analytics.track('utxos_includes_inscribed_one', {
txids,
});
preventTransaction();
return true;
}

return true;
// if there are no inscribed utxos in the transaction => allow the transaction
return false;
} finally {
setIsLoading(false);
}
},
[analytics, client.bestinslotInscriptionsApi, isTestnet, preventTransaction]
[analytics, client, isTestnet, preventTransaction]
);

return {
Expand Down

0 comments on commit a27e281

Please sign in to comment.