Skip to content

Commit

Permalink
cursed inscription avoidance
Browse files Browse the repository at this point in the history
  • Loading branch information
victorkirov committed Aug 25, 2023
1 parent 855e6a4 commit 4cc7f1c
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 6 deletions.
14 changes: 10 additions & 4 deletions api/ordinals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,18 @@ export async function fetchBtcOrdinalsData(btcAddress: string, network: NetworkT
return ordinals.sort(sortOrdinalsByConfirmationTime);
}

export async function getOrdinalIdFromUtxo(utxo: UTXO) {
export async function getOrdinalIdsFromUtxo(utxo: UTXO): Promise<string[]> {
const ordinalContentUrl = `${XVERSE_INSCRIBE_URL}/v1/inscriptions/utxo/${utxo.txid}/${utxo.vout}`;

const ordinalIds = await axios.get<string[]>(ordinalContentUrl);
if (ordinalIds.data.length > 0) {
return ordinalIds.data[ordinalIds.data.length - 1];
const { data: ordinalIds } = await axios.get<string[]>(ordinalContentUrl);

return ordinalIds;
}

export async function getOrdinalIdFromUtxo(utxo: UTXO) {
const ordinalIds = await getOrdinalIdsFromUtxo(utxo);
if (ordinalIds.length > 0) {
return ordinalIds[ordinalIds.length - 1];
} else {
return null;
}
Expand Down
65 changes: 65 additions & 0 deletions tests/transactions/inscriptionMint.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { getOrdinalIdsFromUtxo } from '../../api/ordinals';
import xverseInscribeApi from '../../api/xverseInscribe';
import { generateSignedBtcTransaction, selectUtxosForSend } from '../../transactions/btc';
import {
Expand All @@ -10,6 +11,7 @@ import {
import { getBtcPrivateKey } from '../../wallet';

vi.mock('../../api/xverseInscribe');
vi.mock('../../api/ordinals');
vi.mock('../../transactions/btc');
vi.mock('../../wallet');

Expand Down Expand Up @@ -237,6 +239,7 @@ describe('inscriptionMintExecute', () => {
vi.mocked(xverseInscribeApi.executeInscriptionOrder).mockResolvedValue({
revealTransactionId: 'revealTxnId',
} as any);
vi.mocked(getOrdinalIdsFromUtxo).mockResolvedValue([]);

const result = await inscriptionMintExecute({
addressUtxos: [
Expand Down Expand Up @@ -264,6 +267,68 @@ describe('inscriptionMintExecute', () => {
expect(result).toBe('revealTxnId');
});

it('fails on no non-ordinal UTXOS', async () => {
vi.mocked(getBtcPrivateKey).mockResolvedValue('dummyPrivateKey');
vi.mocked(xverseInscribeApi.createInscriptionOrder).mockResolvedValue({
commitAddress: 'dummyCommitAddress',
commitValue: 1000,
commitValueBreakdown: {
chainFee: 1000,
inscriptionValue: 546,
serviceFee: 2000,
},
});
vi.mocked(selectUtxosForSend).mockReturnValue({
fee: 1100,
change: 1200,
feeRate: 8,
selectedUtxos: [
{
address: 'dummyAddress',
status: { confirmed: true },
txid: 'dummyTxId',
vout: 0,
value: 1000,
},
],
});
vi.mocked(generateSignedBtcTransaction).mockResolvedValue({
hex: 'dummyHex',
} as any);
vi.mocked(xverseInscribeApi.executeInscriptionOrder).mockResolvedValue({
revealTransactionId: 'revealTxnId',
} as any);
vi.mocked(getOrdinalIdsFromUtxo).mockResolvedValue(['ordinalId']);

await expect(() =>
inscriptionMintExecute({
addressUtxos: [
{
address: 'dummyAddress',
status: { confirmed: true },
txid: 'dummyTxId',
vout: 0,
value: 1000,
},
],
contentString: 'dummyContent',
contentType: 'text/plain',
feeRate: 8,
revealAddress: 'dummyRevealAddress',
finalInscriptionValue: 1000,
serviceFee: 5000,
serviceFeeAddress: 'dummyServiceFeeAddress',
accountIndex: 0,
changeAddress: 'dummyChangeAddress',
network: 'Mainnet',
seedPhrase: 'dummySeedPhrase',
}),
).rejects.toThrowCoreError(
'Must have at least one non-inscribed UTXO for inscription',
InscriptionErrorCode.NO_NON_ORDINAL_UTXOS,
);
});

it('should fail on no UTXOs', async () => {
await expect(() =>
inscriptionMintExecute({
Expand Down
24 changes: 22 additions & 2 deletions transactions/inscriptionMint.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import BigNumber from 'bignumber.js';

import { NetworkType, UTXO } from 'types';
import { getOrdinalIdsFromUtxo } from '../api/ordinals';
import xverseInscribeApi from '../api/xverseInscribe';
import { CoreError } from '../utils/coreError';
import { getBtcPrivateKey } from '../wallet';
Expand All @@ -16,6 +17,7 @@ export enum InscriptionErrorCode {
INVALID_CONTENT = 'INVALID_CONTENT',
CONTENT_TOO_BIG = 'CONTENT_TOO_BIG',
INSCRIPTION_VALUE_TOO_LOW = 'INSCRIPTION_VALUE_TOO_LOW',
NO_NON_ORDINAL_UTXOS = 'NO_NON_ORDINAL_UTXOS',
FAILED_TO_FINALIZE = 'FAILED_TO_FINALIZE',
SERVER_ERROR = 'SERVER_ERROR',
}
Expand Down Expand Up @@ -169,7 +171,6 @@ export async function inscriptionMintExecute(executeProps: ExecuteProps): Promis
finalInscriptionValue,
} = executeProps;

// TODO: ensure first UTXO is not inscribed
if (!addressUtxos.length) {
throw new CoreError('No available UTXOs', InscriptionErrorCode.INSUFFICIENT_FUNDS);
}
Expand Down Expand Up @@ -240,11 +241,30 @@ export async function inscriptionMintExecute(executeProps: ExecuteProps): Promis
throw new CoreError('Not enough funds at selected fee rate', InscriptionErrorCode.INSUFFICIENT_FUNDS);
}

const selectedOrdinalUtxos = [];
const selectedNonOrdinalUtxos = [];

for (const utxo of bestUtxoData.selectedUtxos) {
const ordinalIds = await getOrdinalIdsFromUtxo(utxo);
if (ordinalIds.length > 0) {
selectedOrdinalUtxos.push(utxo);
} else {
selectedNonOrdinalUtxos.push(utxo);
}
}

if (selectedNonOrdinalUtxos.length === 0) {
throw new CoreError(
'Must have at least one non-inscribed UTXO for inscription',
InscriptionErrorCode.NO_NON_ORDINAL_UTXOS,
);
}

const commitChainFees = bestUtxoData.fee;

const commitTransaction = await generateSignedBtcTransaction(
privateKey,
bestUtxoData.selectedUtxos,
[...selectedNonOrdinalUtxos, ...selectedOrdinalUtxos],
new BigNumber(commitValue),
recipients,
changeAddress,
Expand Down

0 comments on commit 4cc7f1c

Please sign in to comment.