From 4b0e73a1a9733ea0f2219ad5cc15b40372580c24 Mon Sep 17 00:00:00 2001 From: Nishant Ghodke <64554492+iamcrazycoder@users.noreply.github.com> Date: Wed, 20 Sep 2023 21:12:56 +0530 Subject: [PATCH] feat(sdk): integrate `Inscriber` w/ `PSBTBuilder` (#63) * refactor: initial integration w/ PSBTBuilder * refactor: remove unncessary try..catch block * refactor: update import path * refactor: bind input payload to derived class member * fix: use await for async fn * fix: ignore precalculated postage also, reduce usage of extra variables * refactor!: make props required * chore: ignore dist directory * refactor: enable `inscriberMode` in OrdTransaction * chore: exclude nod_modules & dist dir from vscode search * feat: rename `OrdTransaction` to `Inscriber` also, deprecate OrdTransaction class * refactor: rename arg options type * refactor: replace deprecated class name w/ new one * refactor: rename file * refactor!: remove defaults, rename & rearrange args * refactor: remove unwanted getter * feat!: remove support for change in Inscriber Not possible to know what original utxo sats would be to calculate change * feat: add preview mode flag and restrict specific operation * refactor: move duplicate code to fn * refactor: update collection publish & mint example * refactor: update create-psbt example * chore: deprecate irrelevant methods from Ordit class * refactor: update property name * refactor: update create collection and mint example * refactor: update publish collection example --- .gitignore | 2 + .vscode/settings.json | 4 + examples/node/collections.js | 124 +++--- examples/node/create-psbt.js | 4 +- packages/sdk/src/inscription/collection.ts | 22 +- packages/sdk/src/inscription/witness.ts | 22 +- packages/sdk/src/transactions/Inscriber.ts | 300 +++++++++++++++ .../sdk/src/transactions/OrdTransaction.ts | 359 ------------------ packages/sdk/src/transactions/index.ts | 2 +- packages/sdk/src/wallet/Ordit.ts | 8 +- 10 files changed, 395 insertions(+), 452 deletions(-) create mode 100644 packages/sdk/src/transactions/Inscriber.ts delete mode 100644 packages/sdk/src/transactions/OrdTransaction.ts diff --git a/.gitignore b/.gitignore index f4261c86..cedc1dd4 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ logs *.log npm-debug.log* lerna-debug.log* + +**/dist diff --git a/.vscode/settings.json b/.vscode/settings.json index f1d8daeb..4e7ccc6b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,5 +23,9 @@ }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "search.exclude": { + "**/dist": true, + "**/node_modules": true } } \ No newline at end of file diff --git a/examples/node/collections.js b/examples/node/collections.js index b005dd96..cc442291 100644 --- a/examples/node/collections.js +++ b/examples/node/collections.js @@ -1,28 +1,46 @@ -import { Ordit } from "@sadoprotocol/ordit-sdk"; +import { Ordit, mintFromCollection, publishCollection } from "@sadoprotocol/ordit-sdk"; -const WORDS = ""; +const mnemonic = ""; +const network = "testnet" + +// User is the party who would mint the assets from a collection +const userWallet = new Ordit({ + bip39: mnemonic, + network +}); + +// Publisher is the marketplace and any party owning the on-chain collection +const publisherWallet = new Ordit({ + bip39: mnemonic, + network +}); + +// set default address types for both wallets +userWallet.setDefaultAddress("taproot"); +publisherWallet.setDefaultAddress("nested-segwit"); async function publish() { - // Load wallet - const wallet = new Ordit({ - bip39: WORDS, - network: "testnet" - }); + const getPublisherLegacyAddress = () => { + publisherWallet.setDefaultAddress("legacy") + const legacyAddress = publisherWallet.selectedAddress + publisherWallet.setDefaultAddress("nested-segwit") // switch back to default - //set default taproot - wallet.setDefaultAddress("taproot"); + return legacyAddress + } //publish - const transaction = await Ordit.collection.publish({ + const transaction = await publishCollection({ + network, + feeRate: 2, title: "Collection Name", description: "Lorem ipsum something else", slug: "collection-name", creator: { - address: wallet.selectedAddress, + address: publisherWallet.selectedAddress, email: "your-email@example.com", name: "Your Name" }, - publishers: [""], + publishers: [getPublisherLegacyAddress()], inscriptions: [ { iid: "el-01", @@ -36,93 +54,73 @@ async function publish() { } ], url: "https://example.com", - publicKey: wallet.publicKey, - destination: wallet.selectedAddress, - changeAddress: wallet.selectedAddress, + publicKey: publisherWallet.publicKey, + destination: publisherWallet.selectedAddress, + changeAddress: publisherWallet.selectedAddress, postage: 1000, mediaContent: 'Collection Name', // this will be inscribed on-chain as primary content mediaType: "text/plain" }); - const depositDetails = transaction.generateCommit(); + const depositDetails = await transaction.generateCommit(); console.log(depositDetails); - // // confirm if deposit address has been funded + // confirm if deposit address has been funded const ready = await transaction.isReady(); //- true/false - if (ready || transaction.ready) { // build transaction - transaction.build(); + await transaction.build(); // sign transaction - const psbtHex = transaction.toHex(); - const sig = wallet.signPsbt(psbtHex, { isRevealTx: true }); - // console.log(JSON.stringify(sig, null, 2)) + const signedTx = publisherWallet.signPsbt(transaction.toHex(), { isRevealTx: true }); + // Broadcast transaction - const submittedTx = await wallet.relayTx(sig, "testnet"); - console.log(submittedTx); - //{"txid": ""} + const txId = await publisherWallet.relayTx(signedTx, network); + console.log({ txId }); } } async function mint() { - // Load wallet - const userWallet = new Ordit({ - bip39: "", - network: "testnet" - }); - - const pubWallet = new Ordit({ - bip39: "", - network: "testnet" - }); - - //set default taproot - userWallet.setDefaultAddress("taproot"); - // pubWallet.setDefaultAddress("taproot"); - - // details of mint - const col = "04a0d2c4215607f2a16a5a458d0bd8e0528de0b7990bd9d52659d7d5c6263a54:0"; - const sigMsg = `${col} el-01 1`; // COLLECTION_OUT INSCRIPTION_IID NONCE - const sig = pubWallet.signMessage(sigMsg); - - pubWallet.setDefaultAddress("taproot"); - //publish - const transaction = await Ordit.collection.mint({ - collectionOutpoint: col, + // replace this w/ the resulting txId:index of above publish() fn + const collectionId = ""; + const message = `${collectionId.split(":")[0]} el-01 1`; // COLLECTION_OUT INSCRIPTION_IID NONCE + const signature = publisherWallet.signMessage(message); + + // publish + const transaction = await mintFromCollection({ + network, + collectionOutpoint: collectionId, inscriptionIid: "el-01", nonce: 1, publisherIndex: 0, - signature: sig, + signature, publicKey: userWallet.publicKey, destination: userWallet.selectedAddress, changeAddress: userWallet.selectedAddress, postage: 1000, + feeRate: 2, mediaContent: 'Sample content', mediaType: "text/plain", - outs: [{address: 'tb1pk6yxhcwzzjg9gwsumnlrh3l9q3ajxk657e7kqwmwpd8mklmnmehsrn3hu2', value: 1000}] + outputs: [], }); - const depositDetails = transaction.generateCommit(); + const depositDetails = await transaction.generateCommit(); console.log(depositDetails); - // // confirm if deposit address has been funded + // confirm if deposit address has been funded const ready = await transaction.isReady(); //- true/false - if (ready || transaction.ready) { // build transaction - transaction.build(); + await transaction.build(); // sign transaction - const psbtHex = transaction.toHex(); - const sig = userWallet.signPsbt(psbtHex, { isRevealTx: true }); - // console.log(JSON.stringify(sig, null, 2)) + const signedTx = userWallet.signPsbt(transaction.toHex(), { isRevealTx: true }); + // Broadcast transaction - const submittedTx = await userWallet.relayTx(sig, "testnet"); - console.log(submittedTx); - //{"txid": ""} + const txId = await userWallet.relayTx(signedTx, network); + console.log({ txId }); } } -publish(); -mint(); +publish(); // comment this after collection is created +// mint(); // uncomment this after collection is created on chain using publish() diff --git a/examples/node/create-psbt.js b/examples/node/create-psbt.js index 798685dc..4af493d7 100644 --- a/examples/node/create-psbt.js +++ b/examples/node/create-psbt.js @@ -14,10 +14,10 @@ async function main() { address: 'tb1p98dv6f5jp5qr4z2dtaljvwrhq34xrr8zuaqgv4ajf36vg2mmsruqt5m3lv', outputs: [{ address: 'tb1qatkgzm0hsk83ysqja5nq8ecdmtwl73zwurawww', - cardinals: 1200 + value: 1200 }], network: 'testnet', - satsPerByte: 9, + satsPerByte: 2, format: 'p2tr' }) diff --git a/packages/sdk/src/inscription/collection.ts b/packages/sdk/src/inscription/collection.ts index 38be4504..814b6ee8 100644 --- a/packages/sdk/src/inscription/collection.ts +++ b/packages/sdk/src/inscription/collection.ts @@ -1,4 +1,4 @@ -import { GetWalletOptions, OrditApi, OrdTransaction, verifyMessage } from ".." +import { GetWalletOptions, Inscriber, OrditApi, verifyMessage } from ".." import { Network } from "../config/types" export async function publishCollection({ @@ -28,7 +28,7 @@ export async function publishCollection({ insc: inscriptions } - return new OrdTransaction({ ...options, meta: collectionMeta }) + return new Inscriber({ ...options, meta: collectionMeta }) } export async function mintFromCollection(options: MintFromCollectionOptions) { @@ -94,7 +94,7 @@ export async function mintFromCollection(options: MintFromCollectionOptions) { meta.sig = options.signature - return new OrdTransaction({ ...options, meta }) + return new Inscriber({ ...options, meta }) } function validateInscriptions(inscriptions: CollectionInscription[] = []) { @@ -110,9 +110,9 @@ function validateInscriptions(inscriptions: CollectionInscription[] = []) { } export type PublishCollectionOptions = Pick & { - feeRate?: number - postage?: number - mediaType?: string + feeRate: number + postage: number + mediaType: string mediaContent: string destination: string changeAddress: string @@ -127,7 +127,7 @@ export type PublishCollectionOptions = Pick & { email?: string address: string } - network?: Network + network: Network publicKey: string outs?: Outputs encodeMetadata?: boolean @@ -141,9 +141,9 @@ export type CollectionInscription = { } export type MintFromCollectionOptions = Pick & { - feeRate?: number - postage?: number - mediaType?: string + feeRate: number + postage: number + mediaType: string mediaContent: string destination: string changeAddress: string @@ -152,7 +152,7 @@ export type MintFromCollectionOptions = Pick & { nonce: number publisherIndex: number signature: string - network?: Network + network: Network publicKey: string outs?: Outputs traits?: any diff --git a/packages/sdk/src/inscription/witness.ts b/packages/sdk/src/inscription/witness.ts index d653e98d..18ce4bcb 100644 --- a/packages/sdk/src/inscription/witness.ts +++ b/packages/sdk/src/inscription/witness.ts @@ -56,22 +56,16 @@ export function buildWitnessScript({ recover = false, ...options }: WitnessScrip }) } - try { - if (recover) { - return bitcoin.script.compile([Buffer.from(options.xkey, "hex"), bitcoin.opcodes.OP_CHECKSIG]) - } - - return bitcoin.script.compile([ - ...baseStackElements, - ...contentStackElements, - bitcoin.opcodes.OP_ENDIF, - ...metaStackElements - ]) - } catch (error) { - //fail silently + if (recover) { + return bitcoin.script.compile([Buffer.from(options.xkey, "hex"), bitcoin.opcodes.OP_CHECKSIG]) } - return false + return bitcoin.script.compile([ + ...baseStackElements, + ...contentStackElements, + bitcoin.opcodes.OP_ENDIF, + ...metaStackElements + ]) } function opPush(str: string, encoding: BufferEncoding = "utf8") { diff --git a/packages/sdk/src/transactions/Inscriber.ts b/packages/sdk/src/transactions/Inscriber.ts new file mode 100644 index 00000000..0a5a3799 --- /dev/null +++ b/packages/sdk/src/transactions/Inscriber.ts @@ -0,0 +1,300 @@ +import * as ecc from "@bitcoinerlab/secp256k1" +import * as bitcoin from "bitcoinjs-lib" +import { Tapleaf } from "bitcoinjs-lib/src/types" + +import { + buildWitnessScript, + createTransaction, + encodeObject, + getAddressesFromPublicKey, + getDummyP2TRInput, + getNetwork, + GetWalletOptions, + OnOffUnion +} from ".." +import { Network } from "../config/types" +import { InputsToSign } from "../inscription/types" +import { NestedObject } from "../utils/types" +import { PSBTBuilder } from "./PSBTBuilder" +import { UTXOLimited } from "./types" + +bitcoin.initEccLib(ecc) + +export class Inscriber extends PSBTBuilder { + network: Network + + mediaType: string + mediaContent: string + meta?: NestedObject + postage: number + + address: string + publicKey: string + destinationAddress: string + changeAddress: string + inputsToSign: InputsToSign + + private ready = false + + private xKey!: string + private commitAddress: string | null = null + private payment: bitcoin.payments.Payment | null = null + private suitableUnspent: UTXOLimited | null = null + private recovery = false + private safeMode: OnOffUnion + private encodeMetadata: boolean + private previewMode = false + + private witnessScripts: Record<"inscription" | "recovery", Buffer | null> = { + inscription: null, + recovery: null + } + private taprootTree!: [Tapleaf, Tapleaf] + + constructor({ + network, + feeRate, + postage, + mediaContent, + mediaType, + publicKey, + outputs = [], + encodeMetadata = false, + changeAddress, + destination, + safeMode, + meta + }: InscriberArgOptions) { + const { xkey, address } = getAddressesFromPublicKey(publicKey, network, "p2tr")[0] + super({ + address: address!, + feeRate, + network, + publicKey, + outputs, + inscriberMode: true + }) + if (!publicKey || !changeAddress || !destination || !mediaContent) { + throw new Error("Invalid options provided") + } + + this.publicKey = publicKey + this.feeRate = feeRate + this.mediaType = mediaType + this.network = network + this.changeAddress = changeAddress + this.destinationAddress = destination + this.mediaContent = mediaContent + this.meta = meta + this.postage = postage + this.outputs = outputs + this.safeMode = !safeMode ? "on" : safeMode + this.encodeMetadata = encodeMetadata + + if (!xkey) { + throw new Error("Failed to derive xKey from the provided public key") + } + + this.xKey = xkey + this.address = address! + this.inputsToSign = { + address: this.address, + signingIndexes: [] + } + } + + private getMetadata() { + return this.meta && this.encodeMetadata ? encodeObject(this.meta) : this.meta + } + + async build() { + if (!this.suitableUnspent || !this.payment) { + throw new Error("Failed to build PSBT. Transaction not ready") + } + + this.inputs = [ + { + type: "taproot", + hash: this.suitableUnspent.txid, + index: this.suitableUnspent.n, + tapInternalKey: Buffer.from(this.xKey, "hex"), + witnessUtxo: { + script: this.payment.output!, + value: this.suitableUnspent.sats + }, + tapLeafScript: [ + { + leafVersion: this.payment.redeemVersion!, + script: this.payment.redeem!.output!, + controlBlock: this.payment.witness![this.payment.witness!.length - 1] + } + ] + } + ] + + this.inputsToSign.signingIndexes.push(0) // hardcoding because there will always be one input + + if (!this.recovery) { + this.outputs.push({ + address: this.destinationAddress, + value: this.postage + }) + } + + await this.prepare() // prepare PSBT using PSBTBuilder + } + + private isBuilt() { + if (!this.commitAddress || !this.fee) { + throw new Error("Invalid tx! Make sure you generate commit address or recover and finally build") + } + } + + buildWitness() { + this.witnessScripts = { + inscription: buildWitnessScript({ + mediaContent: this.mediaContent, + mediaType: this.mediaType, + meta: this.getMetadata(), + xkey: this.xKey + }), + recovery: buildWitnessScript({ + mediaContent: this.mediaContent, + mediaType: this.mediaType, + meta: this.getMetadata(), + xkey: this.xKey, + recover: true + }) + } + } + + buildTaprootTree() { + this.buildWitness() + this.taprootTree = [{ output: this.witnessScripts.inscription! }, { output: this.witnessScripts.recovery! }] + } + + getInscriptionRedeemScript(): bitcoin.payments.Payment["redeem"] { + return { + output: this.witnessScripts.inscription!, + redeemVersion: 192 + } + } + + getRecoveryRedeemScript(): bitcoin.payments.Payment["redeem"] { + return { + output: this.witnessScripts.recovery!, + redeemVersion: 192 + } + } + + private async preview({ activate }: Record<"activate", boolean> = { activate: true }) { + if (activate) { + this.previewMode = true + this.suitableUnspent = getDummyP2TRInput() + this.ready = true + await this.build() + } else { + this.initPSBT() + this.suitableUnspent = null + this.ready = false + this.inputsToSign.signingIndexes.pop() // remove last added index + this.outputs.pop() + this.previewMode = false + } + } + + private restrictUsageInPreviewMode() { + if (this.previewMode) { + throw new Error("Unable to process request in preview mode") + } + } + + private async calculateNetworkFeeUsingPreviewMode() { + await this.preview() + this.calculateNetworkFee() + await this.preview({ activate: false }) + } + + async generateCommit() { + this.buildTaprootTree() + this.payment = bitcoin.payments.p2tr({ + internalPubkey: Buffer.from(this.xKey, "hex"), + network: getNetwork(this.network), + scriptTree: this.taprootTree, + redeem: this.getInscriptionRedeemScript() + }) + this.witness = this.payment.witness + + await this.calculateNetworkFeeUsingPreviewMode() + + this.commitAddress = this.payment.address! + return { + address: this.payment.address!, + revealFee: this.fee + this.outputAmount + } + } + + async recover() { + this.recovery = true + this.buildTaprootTree() + + this.payment = createTransaction(Buffer.from(this.xKey, "hex"), "p2tr", this.network, { + scriptTree: this.taprootTree, + redeem: this.getRecoveryRedeemScript() + }) + + await this.calculateNetworkFeeUsingPreviewMode() + } + + async isReady() { + this.isBuilt() + + if (!this.ready) { + try { + await this.fetchAndSelectSuitableUnspent() + } catch (error) { + return false + } + } + + return this.ready + } + + async fetchAndSelectSuitableUnspent() { + this.restrictUsageInPreviewMode() + this.isBuilt() + + const amount = this.fee + this.outputAmount + const [utxo] = await this.retrieveSelectedUTXOs(this.commitAddress!, amount) + this.suitableUnspent = utxo + this.ready = true + + return this.suitableUnspent + } +} + +/** + * @deprecated `OrdTransaction` class has been renamed to `Inscriber` + */ +export class OrdTransaction extends Inscriber { + constructor(args: InscriberArgOptions) { + super(args) + console.error("DEPRECATION WARNING: 'OrdTransaction' class has been renamed to 'Inscriber'") + } +} + +export type InscriberArgOptions = Pick & { + network: Network + feeRate: number + postage: number + mediaType: string + mediaContent: string + destination: string + changeAddress: string + meta?: NestedObject + publicKey: string + outputs?: Outputs + encodeMetadata?: boolean +} + +type Outputs = Array<{ address: string; value: number }> diff --git a/packages/sdk/src/transactions/OrdTransaction.ts b/packages/sdk/src/transactions/OrdTransaction.ts deleted file mode 100644 index c9a628ee..00000000 --- a/packages/sdk/src/transactions/OrdTransaction.ts +++ /dev/null @@ -1,359 +0,0 @@ -import * as ecc from "@bitcoinerlab/secp256k1" -import * as bitcoin from "bitcoinjs-lib" -import { Tapleaf } from "bitcoinjs-lib/src/types" - -import { - buildWitnessScript, - convertSatoshisToBTC, - createTransaction, - encodeObject, - getAddressesFromPublicKey, - getDummyP2TRInput, - getNetwork, - GetWalletOptions, - OnOffUnion, - OrditApi -} from ".." -import { Network } from "../config/types" -import { MINIMUM_AMOUNT_IN_SATS } from "../constants" -import FeeEstimator from "../fee/FeeEstimator" -import { InputsToSign } from "../inscription/types" -import { NestedObject } from "../utils/types" - -bitcoin.initEccLib(ecc) - -export class OrdTransaction { - publicKey: string - feeRate: number - postage: number - mediaType: string - mediaContent: string - destinationAddress: string - changeAddress: string - meta?: NestedObject - network: Network - psbt: bitcoin.Psbt | null = null - ready = false - address: string - inputsToSign: InputsToSign - #xKey: string - #feeForWitnessData: number | null = null - #commitAddress: string | null = null - #inscribePayTx: ReturnType | null = null - #suitableUnspent: any = null - #recovery = false - #outs: Outputs = [] - #safeMode: OnOffUnion - #encodeMetadata: boolean - #enableRBF: boolean - - constructor({ - feeRate = 10, - postage = 10000, - mediaType = "text/plain;charset=utf-8", - network = "testnet", - publicKey, - outs = [], - encodeMetadata = false, - enableRBF = true, - ...otherOptions - }: OrdTransactionOptions) { - if (!publicKey || !otherOptions.changeAddress || !otherOptions.destination || !otherOptions.mediaContent) { - throw new Error("Invalid options provided.") - } - - this.publicKey = publicKey - this.feeRate = feeRate - this.mediaType = mediaType - this.network = network - this.changeAddress = otherOptions.changeAddress - this.destinationAddress = otherOptions.destination - this.mediaContent = otherOptions.mediaContent - this.meta = otherOptions.meta - this.postage = postage - this.#outs = outs - this.#safeMode = !otherOptions.safeMode ? "on" : otherOptions.safeMode - this.#encodeMetadata = encodeMetadata - this.#enableRBF = enableRBF - - const { xkey, address } = getAddressesFromPublicKey(publicKey, network, "p2tr")[0] - - if (!xkey) { - throw new Error("Failed to derive xKey from the provided public key.") - } - - this.#xKey = xkey - this.address = address! - this.inputsToSign = { - address: this.address, - signingIndexes: [] - } - } - - get outs() { - return this.#outs - } - - build() { - if (!this.#suitableUnspent || !this.#inscribePayTx) { - throw new Error("Failed to build PSBT. Transaction not ready.") - } - - const networkObj = getNetwork(this.network) - - const psbt = new bitcoin.Psbt({ network: networkObj }) - - psbt.addInput({ - sequence: this.#enableRBF ? 0xfffffffd : 0xffffffff, - hash: this.#suitableUnspent.txid, - index: parseInt(this.#suitableUnspent.n), - tapInternalKey: Buffer.from(this.#xKey, "hex"), - witnessUtxo: { - script: this.#inscribePayTx.output!, - value: parseInt(this.#suitableUnspent.sats) - }, - tapLeafScript: [ - { - leafVersion: this.#inscribePayTx.redeemVersion!, - script: this.#inscribePayTx.redeem!.output!, - controlBlock: this.#inscribePayTx.witness![this.#inscribePayTx.witness!.length - 1] - } - ] - }) - - this.inputsToSign.signingIndexes.push(0) // hardcoding because there will always be one input - - if (!this.#recovery) { - psbt.addOutput({ - address: this.destinationAddress, - value: this.postage - }) - - this.#outs.forEach((out) => { - psbt.addOutput(out) - }) - } - - let fee = this.#feeForWitnessData! - if (this.#recovery) { - const feeEstimator = new FeeEstimator({ - psbt, - feeRate: this.feeRate, - network: this.network - }) - fee = feeEstimator.calculateNetworkFee() - } - - const customOutsAmount = this.#outs.reduce((acc, cur) => { - return acc + cur.value - }, 0) - const change = this.#suitableUnspent.sats - fee - customOutsAmount - this.postage - - if (change > MINIMUM_AMOUNT_IN_SATS) { - let changeAddress = this.#inscribePayTx.address - if (this.changeAddress) { - changeAddress = this.changeAddress - } - - psbt.addOutput({ - address: changeAddress!, - value: change - }) - } - - this.psbt = psbt - } - - toPsbt() { - if (!this.psbt) { - throw new Error("No PSBT found. Please build first.") - } - - return this.psbt - } - - toHex() { - if (!this.psbt) { - throw new Error("No PSBT found. Please build first.") - } - - return this.psbt.toHex() - } - - toBase64() { - if (!this.psbt) { - throw new Error("No PSBT found. Please build first.") - } - - return this.psbt.toBase64() - } - - generateCommit() { - const witnessScript = buildWitnessScript({ - mediaContent: this.mediaContent, - mediaType: this.mediaType, - meta: this.meta && this.#encodeMetadata ? encodeObject(this.meta) : this.meta, - xkey: this.#xKey - }) - const recoverScript = buildWitnessScript({ - mediaContent: this.mediaContent, - mediaType: this.mediaType, - meta: this.meta && this.#encodeMetadata ? encodeObject(this.meta) : this.meta, - xkey: this.#xKey, - recover: true - }) - - if (!witnessScript || !recoverScript) { - throw new Error("Failed to build createRevealPsbt") - } - - const scriptTree: [Tapleaf, Tapleaf] = [ - { - output: witnessScript - }, - { - output: recoverScript - } - ] - - const redeemScript = { - output: witnessScript, - redeemVersion: 192 - } - - const inscribePayTx = createTransaction(Buffer.from(this.#xKey, "hex"), "p2tr", this.network, { - scriptTree: scriptTree, - redeem: redeemScript - }) - - this.#suitableUnspent = getDummyP2TRInput() - this.build() - const feeEstimator = new FeeEstimator({ - psbt: this.psbt!, - feeRate: this.feeRate, - network: this.network - }) - const fee = feeEstimator.calculateNetworkFee() - this.psbt = null - this.#suitableUnspent = null - this.inputsToSign.signingIndexes.pop() // remove last added index - - const customOutsAmount = this.#outs.reduce((acc, cur) => { - return acc + cur.value - }, 0) - - this.#feeForWitnessData = fee - this.#commitAddress = inscribePayTx.address! - this.#inscribePayTx = inscribePayTx - - return { - address: inscribePayTx.address!, - revealFee: this.postage + fee + customOutsAmount - } - } - - recover() { - if (!this.#inscribePayTx || !this.ready) { - throw new Error("Transaction not ready.") - } - - const witnessScript = buildWitnessScript({ - mediaContent: this.mediaContent, - mediaType: this.mediaType, - meta: this.meta && this.#encodeMetadata ? encodeObject(this.meta) : this.meta, - xkey: this.#xKey - }) - const recoverScript = buildWitnessScript({ - mediaContent: this.mediaContent, - mediaType: this.mediaType, - meta: this.meta && this.#encodeMetadata ? encodeObject(this.meta) : this.meta, - xkey: this.#xKey, - recover: true - }) - - if (!witnessScript || !recoverScript) { - throw new Error("Failed to build createRevealPsbt") - } - - const scriptTree: [Tapleaf, Tapleaf] = [ - { - output: witnessScript - }, - { - output: recoverScript - } - ] - - const redeemScript = { - output: recoverScript, - redeemVersion: 192 - } - - const inscribePayTx = createTransaction(Buffer.from(this.#xKey, "hex"), "p2tr", this.network, { - scriptTree: scriptTree, - redeem: redeemScript - }) - - this.#inscribePayTx = inscribePayTx - this.#recovery = true - } - - async isReady() { - if (!this.#commitAddress || !this.#feeForWitnessData) { - throw new Error("No commit address found. Please generate a commit address.") - } - - if (!this.ready) { - try { - await this.fetchAndSelectSuitableUnspent() - } catch (error) { - return false - } - } - - return this.ready - } - - async fetchAndSelectSuitableUnspent() { - if (!this.#commitAddress || !this.#feeForWitnessData) { - throw new Error("No commit address found. Please generate a commit address.") - } - - const outAmount = this.#outs.reduce((acc, cur) => (acc += cur.value), 0) - const amount = this.postage + this.#feeForWitnessData! + outAmount - - const utxos = await OrditApi.fetchSpendables({ - address: this.#commitAddress, - value: convertSatoshisToBTC(amount), - network: this.network, - type: this.#safeMode === "on" ? "spendable" : "all" - }) - - const suitableUTXO = utxos.find((utxo) => utxo.sats >= amount) - if (!suitableUTXO) { - throw new Error("No suitable unspent found for reveal.") - } - - this.#suitableUnspent = suitableUTXO - this.ready = true - - return suitableUTXO - } -} - -export type OrdTransactionOptions = Pick & { - feeRate?: number - postage?: number - mediaType?: string - mediaContent: string - destination: string - changeAddress: string - meta?: NestedObject - network?: Network - publicKey: string - outs?: Outputs - encodeMetadata?: boolean - enableRBF?: boolean -} - -type Outputs = Array<{ address: string; value: number }> diff --git a/packages/sdk/src/transactions/index.ts b/packages/sdk/src/transactions/index.ts index 5edc615b..abf1eadd 100644 --- a/packages/sdk/src/transactions/index.ts +++ b/packages/sdk/src/transactions/index.ts @@ -1,3 +1,3 @@ -export * from "./OrdTransaction" +export * from "./Inscriber" export * from "./psbt" export * from "./relay" diff --git a/packages/sdk/src/wallet/Ordit.ts b/packages/sdk/src/wallet/Ordit.ts index 989022df..cf9dab0f 100644 --- a/packages/sdk/src/wallet/Ordit.ts +++ b/packages/sdk/src/wallet/Ordit.ts @@ -24,7 +24,7 @@ import { import { OrditApi } from "../api" import { Network } from "../config/types" import { Inscription } from "../inscription/types" -import { OrdTransaction, OrdTransactionOptions } from "../transactions" +import { Inscriber, InscriberArgOptions } from "../transactions" bitcoin.initEccLib(ecc) const ECPair = ECPairFactory(ecc) @@ -266,8 +266,12 @@ export class Ordit { }, [] as Inscription[]) } + /** + * @deprecated `Ordit.inscription.new` has been deprecated and will be removed in future release. Use `Inscriber` class. + * @deprecated `Ordit.inscription.fetchInscriptions` has been deprecated and will be removed in future release. Use `OrditApi.fetchInscriptions` + */ static inscription = { - new: (options: OrdTransactionOptions) => new OrdTransaction(options), + new: (options: InscriberArgOptions) => new Inscriber(options), fetchInscriptions: (outpoint: string, network: Network = "testnet") => { if (!outpoint) { throw new Error("Outpoint is required.")