From 6edd246a063406c8589d531745452076d40235a1 Mon Sep 17 00:00:00 2001 From: KevinK <31565174+kevzzsk@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:46:28 +0800 Subject: [PATCH] feat(inscriberV2): support delegates (#123) * feat(inscriberV2): support delegates * check for empty string --- packages/sdk/src/inscription/collection.ts | 8 +- packages/sdk/src/inscription/encoding.ts | 4 + packages/sdk/src/inscription/witness.ts | 12 ++- packages/sdk/src/transactions/InscriberV2.ts | 86 ++++++++++---------- packages/sdk/src/utils/index.ts | 6 +- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/packages/sdk/src/inscription/collection.ts b/packages/sdk/src/inscription/collection.ts index 030a83de..96ff783a 100644 --- a/packages/sdk/src/inscription/collection.ts +++ b/packages/sdk/src/inscription/collection.ts @@ -131,7 +131,7 @@ export async function bulkMintFromCollection({ inscriptionList: EnvelopeOpts[] }>( (acc, insc) => { - const { nonce, mediaContent, mediaType, receiverAddress, postage, iid,signature } = insc + const { nonce, mediaContent, mediaType, delegateInscriptionId, receiverAddress, postage, iid, signature } = insc const meta = buildMeta({ collectionGenesis, @@ -152,6 +152,7 @@ export async function bulkMintFromCollection({ const inscriptionEnvelope: EnvelopeOpts = { mediaContent, mediaType, + delegateInscriptionId, pointer: currentPointer === 0 ? undefined : currentPointer.toString(), receiverAddress, postage @@ -271,8 +272,9 @@ export type BulkMintFromCollectionOptions = { export type InscriptionsToMint = { nonce: number - mediaContent: string - mediaType: string + mediaContent?: string + mediaType?: string + delegateInscriptionId?: string receiverAddress: string postage: number iid: string diff --git a/packages/sdk/src/inscription/encoding.ts b/packages/sdk/src/inscription/encoding.ts index 6a8c4cec..c81074b1 100644 --- a/packages/sdk/src/inscription/encoding.ts +++ b/packages/sdk/src/inscription/encoding.ts @@ -8,6 +8,10 @@ export function trimTrailingZeroBytes(buffer: Buffer): Buffer { trimmedBuffer = buffer.subarray(0, i + 1) break } + if (i === 0) { + // if all bytes are zero, return an empty buffer + trimmedBuffer = Buffer.alloc(0) + } } return trimmedBuffer } diff --git a/packages/sdk/src/inscription/witness.ts b/packages/sdk/src/inscription/witness.ts index 0c0d3673..88beddec 100644 --- a/packages/sdk/src/inscription/witness.ts +++ b/packages/sdk/src/inscription/witness.ts @@ -132,10 +132,14 @@ export const buildEnvelope = function ({delegateInscriptionId,mediaContent,media ] if (pointer) { - baseStackElements.push( - INSCRIPTION_FIELD_TAG.Pointer, - encodePointer(pointer) - ) + const encodedPointer = encodePointer(pointer) + if (!encodedPointer.equals(Buffer.alloc(0))) { + // only push pointer tag if it is not 0 + baseStackElements.push( + INSCRIPTION_FIELD_TAG.Pointer, + encodedPointer + ) + } } if (delegateInscriptionId) { diff --git a/packages/sdk/src/transactions/InscriberV2.ts b/packages/sdk/src/transactions/InscriberV2.ts index 08c1450b..a6ff574c 100644 --- a/packages/sdk/src/transactions/InscriberV2.ts +++ b/packages/sdk/src/transactions/InscriberV2.ts @@ -21,6 +21,8 @@ import { SkipStrictSatsCheckOptions, UTXOLimited } from "./types" bitcoin.initEccLib(ecc) +type BuildWitnessParams = { xKey: string; inscriptions: EnvelopeOpts[]; metaInscriptions: EnvelopeOpts[] } + export class InscriberV2 extends PSBTBuilder { protected taptreeVersion?: TaptreeVersion = "3" @@ -35,16 +37,7 @@ export class InscriberV2 extends PSBTBuilder { readonly metaInscriptions: EnvelopeOpts[] readonly inscriptions: EnvelopeOpts[] - private witnessScripts: Record< - "recovery" | "inscriptions" | "metaInscriptions" | "inscriptionLegacy" | "metaInscriptionLegacy", - Buffer | null - > = { - inscriptions: null, - metaInscriptions: null, - inscriptionLegacy: null, - metaInscriptionLegacy: null, - recovery: null - } + private witnessScripts: ReturnType private taprootTree!: Taptree constructor({ @@ -77,6 +70,8 @@ export class InscriberV2 extends PSBTBuilder { this.metaInscriptions = metaInscriptions ?? [] this.inscriptions = inscriptions ?? [] this.isStandard = isStandard ?? true + + this.witnessScripts = this.buildWitnessScripts({ xKey: this.xKey, inscriptions, metaInscriptions }) } get data() { @@ -148,54 +143,57 @@ export class InscriberV2 extends PSBTBuilder { } } - buildWitness() { - this.witnessScripts = { - inscriptions: buildWitnessScriptV2({ - xkey: this.xKey, - envelopes: this.inscriptions - }), - metaInscriptions: buildWitnessScriptV2({ - xkey: this.xKey, - envelopes: this.metaInscriptions - }), - inscriptionLegacy: buildWitnessScript({ - mediaContent: this.inscriptions[0].mediaContent!, - mediaType: this.inscriptions[0].mediaType!, - meta: false, - xkey: this.xKey - }), - metaInscriptionLegacy: buildWitnessScript({ - mediaContent: this.inscriptions[0].mediaContent!, - mediaType: this.inscriptions[0].mediaType!, - meta: JSON.parse(this.metaInscriptions[0].mediaContent!), - xkey: this.xKey - }), - recovery: buildRecoverWitnessScript(this.xKey) + buildWitnessScripts({ xKey, inscriptions, metaInscriptions }: BuildWitnessParams) { + return { + inscriptions: () => + buildWitnessScriptV2({ + xkey: xKey, + envelopes: inscriptions + }), + metaInscriptions: () => + buildWitnessScriptV2({ + xkey: xKey, + envelopes: metaInscriptions + }), + inscriptionLegacy: () => + buildWitnessScript({ + mediaContent: inscriptions[0].mediaContent!, + mediaType: inscriptions[0].mediaType!, + meta: false, + xkey: xKey + }), + metaInscriptionLegacy: () => + buildWitnessScript({ + mediaContent: inscriptions[0].mediaContent!, + mediaType: inscriptions[0].mediaType!, + meta: JSON.parse(metaInscriptions[0].mediaContent!), + xkey: xKey + }), + recovery: () => buildRecoverWitnessScript(xKey) } } buildTaprootTree() { - this.buildWitness() switch (this.taptreeVersion) { case "3": // v3 allows for multiple/single inscription minting (without meta) and remains unique based on the meta (OIP-2 specs) this.taprootTree = [ - [{ output: this.witnessScripts.recovery! }, { output: this.witnessScripts.metaInscriptions! }], - { output: this.witnessScripts.inscriptions! } + [{ output: this.witnessScripts.recovery() }, { output: this.witnessScripts.metaInscriptions() }], + { output: this.witnessScripts.inscriptions() } ] break case "2": // v2 allows for inscription only minting (without meta) and remains unique based on the meta (OIP-2 specs) this.taprootTree = [ - [{ output: this.witnessScripts.recovery! }, { output: this.witnessScripts.metaInscriptionLegacy! }], - { output: this.witnessScripts.inscriptionLegacy! } + [{ output: this.witnessScripts.recovery() }, { output: this.witnessScripts.metaInscriptionLegacy() }], + { output: this.witnessScripts.inscriptionLegacy() } ] break case "1": // v1 allows for inscription (with meta) and recovery minting (OIP-2 specs) this.taprootTree = [ - { output: this.witnessScripts.metaInscriptionLegacy! }, - { output: this.witnessScripts.recovery! } + { output: this.witnessScripts.metaInscriptionLegacy() }, + { output: this.witnessScripts.recovery() } ] break default: @@ -207,17 +205,17 @@ export class InscriberV2 extends PSBTBuilder { switch (this.taptreeVersion) { case "3": return { - output: this.witnessScripts.inscriptions!, + output: this.witnessScripts.inscriptions(), redeemVersion: 192 } case "2": return { - output: this.witnessScripts.inscriptionLegacy!, + output: this.witnessScripts.inscriptionLegacy(), redeemVersion: 192 } case "1": return { - output: this.witnessScripts.metaInscriptionLegacy!, + output: this.witnessScripts.metaInscriptionLegacy(), redeemVersion: 192 } default: @@ -227,7 +225,7 @@ export class InscriberV2 extends PSBTBuilder { getRecoveryRedeemScript(): bitcoin.payments.Payment["redeem"] { return { - output: this.witnessScripts.recovery!, + output: this.witnessScripts.recovery(), redeemVersion: 192 } } diff --git a/packages/sdk/src/utils/index.ts b/packages/sdk/src/utils/index.ts index 6a8a2eef..5215ae03 100644 --- a/packages/sdk/src/utils/index.ts +++ b/packages/sdk/src/utils/index.ts @@ -261,12 +261,12 @@ export function getDummyP2TRInput(): UTXO { } export const splitInscriptionId = (inscriptionId: string) => { - const [txId, index] = inscriptionId.split(":") + const [txId, index] = inscriptionId.split("i") if (txId.length !== 64) { throw new OrditSDKError(`Invalid inscriptionId: ${inscriptionId}`) } - const indexNum = parseInt(index, 10) - if (Number.isNaN(indexNum) || indexNum < 0) { + const indexNum = Number(index) + if (Number.isNaN(indexNum) || indexNum < 0 || index !== "") { throw new OrditSDKError(`Invalid inscriptionId: ${inscriptionId}`) }