From 10adba443ac52c1b2bc49d4d3b0d2eee7e66284a Mon Sep 17 00:00:00 2001 From: Nishant Ghodke <64554492+iamcrazycoder@users.noreply.github.com> Date: Wed, 27 Sep 2023 19:44:07 +0530 Subject: [PATCH] feat(sdk): add sats and ownership check (#71) * restore: add flag to skip strict sats check originally added in #65 * refactor: set decodeMetadata to false as default * fix: use genesis txid to get inscription utxo * fix: use different approach to get inscription utxo in buyer flow * feat: add validation for ownership in seller flow * fix: update legacy input size * fix: don't fetch more utxos only when its external request * feat: add customAmount to work w/ skipStrictSatsCheck --- packages/sdk/src/api/index.ts | 8 ++++---- packages/sdk/src/fee/FeeEstimator.ts | 2 +- .../sdk/src/instant-trade/InstantTradeBuilder.ts | 2 +- .../instant-trade/InstantTradeBuyerTxBuilder.ts | 15 ++++++++++++++- .../instant-trade/InstantTradeSellerTxBuilder.ts | 7 +++++++ packages/sdk/src/modules/JsonRpcDatasource.ts | 4 ++-- packages/sdk/src/transactions/Inscriber.ts | 14 +++++++++----- packages/sdk/src/transactions/PSBTBuilder.ts | 8 +++++--- packages/sdk/src/transactions/types.ts | 5 +++++ 9 files changed, 48 insertions(+), 17 deletions(-) diff --git a/packages/sdk/src/api/index.ts b/packages/sdk/src/api/index.ts index a4dbdb09..202bc5c2 100644 --- a/packages/sdk/src/api/index.ts +++ b/packages/sdk/src/api/index.ts @@ -34,7 +34,7 @@ export class OrditApi { network = "testnet", type = "spendable", rarity = ["common"], - decodeMetadata = true, + decodeMetadata = false, sort = "desc" }: FetchUnspentUTXOsOptions): Promise { if (!address) { @@ -88,7 +88,7 @@ export class OrditApi { ordinals = true, hex = false, witness = true, - decodeMetadata = true + decodeMetadata = false }: FetchTxOptions): Promise { if (!txId) { throw new Error("Invalid txId") @@ -118,7 +118,7 @@ export class OrditApi { } } - static async fetchInscriptions({ outpoint, network = "testnet", decodeMetadata = true }: GetInscriptionsOptions) { + static async fetchInscriptions({ outpoint, network = "testnet", decodeMetadata = false }: GetInscriptionsOptions) { if (!outpoint) { throw new Error("Invalid options provided.") } @@ -139,7 +139,7 @@ export class OrditApi { return inscriptions } - static async fetchInscription({ id, network = "testnet", decodeMetadata = true }: FetchInscriptionOptions) { + static async fetchInscription({ id, network = "testnet", decodeMetadata = false }: FetchInscriptionOptions) { if (!id) { throw new Error("Invalid options provided.") } diff --git a/packages/sdk/src/fee/FeeEstimator.ts b/packages/sdk/src/fee/FeeEstimator.ts index e149b295..b15b697c 100644 --- a/packages/sdk/src/fee/FeeEstimator.ts +++ b/packages/sdk/src/fee/FeeEstimator.ts @@ -138,7 +138,7 @@ export default class FeeEstimator { return { input: 64, output: 32, txHeader: 10, witness: 105 } case "legacy": - return { input: 149, output: 34, txHeader: 10, witness: 0 } + return { input: 148, output: 34, txHeader: 10, witness: 0 } default: throw new Error("Invalid type") diff --git a/packages/sdk/src/instant-trade/InstantTradeBuilder.ts b/packages/sdk/src/instant-trade/InstantTradeBuilder.ts index 0ac288a2..b1c458a0 100644 --- a/packages/sdk/src/instant-trade/InstantTradeBuilder.ts +++ b/packages/sdk/src/instant-trade/InstantTradeBuilder.ts @@ -71,7 +71,7 @@ export default class InstantTradeBuilder extends PSBTBuilder { throw new Error("Inscription not found") } - const utxo = await this.datasource.getInscriptionUTXO(this.inscription.outpoint) + const utxo = await this.datasource.getInscriptionUTXO(this.inscription.genesis) if (!utxo) { throw new Error(`Unable to find UTXO: ${this.inscription.outpoint}`) } diff --git a/packages/sdk/src/instant-trade/InstantTradeBuyerTxBuilder.ts b/packages/sdk/src/instant-trade/InstantTradeBuyerTxBuilder.ts index faf7b41b..2ed6c141 100644 --- a/packages/sdk/src/instant-trade/InstantTradeBuyerTxBuilder.ts +++ b/packages/sdk/src/instant-trade/InstantTradeBuyerTxBuilder.ts @@ -127,7 +127,20 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder { } async isEligible() { - const [utxos] = await Promise.all([this.findUTXOs(), this.verifyAndFindInscriptionUTXO()]) + if (!this.inscriptionOutpoint) { + throw new Error("decode seller PSBT to check eligiblity") + } + + const [utxos, [inscription]] = await Promise.all([ + this.findUTXOs(), + this.datasource.getInscriptions({ outpoint: this.inscriptionOutpoint }) + ]) + if (!inscription) { + throw new Error("Inscription no longer available for trade") + } + const inscriptionUTXO = await this.datasource.getInscriptionUTXO(inscription.id) + this.postage = inscriptionUTXO.sats + const sortedUTXOs = utxos.sort((a, b) => a.sats - b.sats) const [refundableUTXOOne, refundableUTXOTwo, ...restUTXOs] = sortedUTXOs const refundables = [refundableUTXOOne, refundableUTXOTwo].reduce((acc, curr) => (acc += curr.sats), 0) diff --git a/packages/sdk/src/instant-trade/InstantTradeSellerTxBuilder.ts b/packages/sdk/src/instant-trade/InstantTradeSellerTxBuilder.ts index 1d58bd45..658f8d2d 100644 --- a/packages/sdk/src/instant-trade/InstantTradeSellerTxBuilder.ts +++ b/packages/sdk/src/instant-trade/InstantTradeSellerTxBuilder.ts @@ -80,12 +80,19 @@ export default class InstantTradeSellerTxBuilder extends InstantTradeBuilder { } } + private validateOwnership() { + if (this.inscription?.owner !== this.address) { + throw new Error(`Inscription does not belong to the address: ${this.address}`) + } + } + async build() { if (isNaN(this.price) || this.price < MINIMUM_AMOUNT_IN_SATS) { throw new Error("Invalid price") } this.utxo = await this.verifyAndFindInscriptionUTXO() + this.validateOwnership() await this.generatSellerInputs() await this.generateSellerOutputs() diff --git a/packages/sdk/src/modules/JsonRpcDatasource.ts b/packages/sdk/src/modules/JsonRpcDatasource.ts index 6a3a679e..1337050d 100644 --- a/packages/sdk/src/modules/JsonRpcDatasource.ts +++ b/packages/sdk/src/modules/JsonRpcDatasource.ts @@ -92,7 +92,7 @@ export default class JsonRpcDatasource extends BaseDatasource { ordinals = true, hex = false, witness = true, - decodeMetadata = true + decodeMetadata = false }: FetchTxOptions) { if (!txId) { throw new Error("Invalid request") @@ -128,7 +128,7 @@ export default class JsonRpcDatasource extends BaseDatasource { address, // TODO rename interface type = "spendable", rarity = ["common"], - decodeMetadata = true, + decodeMetadata = false, sort = "desc", limit = 50, next = null diff --git a/packages/sdk/src/transactions/Inscriber.ts b/packages/sdk/src/transactions/Inscriber.ts index 5d736614..52b659e4 100644 --- a/packages/sdk/src/transactions/Inscriber.ts +++ b/packages/sdk/src/transactions/Inscriber.ts @@ -14,7 +14,7 @@ import { import { Network } from "../config/types" import { NestedObject } from "../utils/types" import { PSBTBuilder } from "./PSBTBuilder" -import { UTXOLimited } from "./types" +import { SkipStrictSatsCheckOptions, UTXOLimited } from "./types" bitcoin.initEccLib(ecc) @@ -241,12 +241,12 @@ export class Inscriber extends PSBTBuilder { await this.calculateNetworkFeeUsingPreviewMode() } - async isReady() { + async isReady({ skipStrictSatsCheck, customAmount }: SkipStrictSatsCheckOptions = {}) { this.isBuilt() if (!this.ready) { try { - await this.fetchAndSelectSuitableUnspent() + await this.fetchAndSelectSuitableUnspent({ skipStrictSatsCheck, customAmount }) } catch (error) { return false } @@ -255,11 +255,15 @@ export class Inscriber extends PSBTBuilder { return this.ready } - async fetchAndSelectSuitableUnspent() { + async fetchAndSelectSuitableUnspent({ skipStrictSatsCheck, customAmount }: SkipStrictSatsCheckOptions = {}) { this.restrictUsageInPreviewMode() this.isBuilt() - const amount = this.recovery ? this.outputAmount - this.fee : this.outputAmount + this.fee + const amount = this.recovery + ? this.outputAmount - this.fee + : skipStrictSatsCheck && customAmount && !isNaN(customAmount) + ? customAmount + : this.outputAmount + this.fee const [utxo] = await this.retrieveSelectedUTXOs(this.commitAddress!, amount) this.suitableUnspent = utxo this.ready = true diff --git a/packages/sdk/src/transactions/PSBTBuilder.ts b/packages/sdk/src/transactions/PSBTBuilder.ts index 16ce4c2e..066bf0d1 100644 --- a/packages/sdk/src/transactions/PSBTBuilder.ts +++ b/packages/sdk/src/transactions/PSBTBuilder.ts @@ -311,13 +311,14 @@ export class PSBTBuilder extends FeeEstimator { private async retrieveUTXOs(address?: string, amount?: number) { if (!this.autoAdjustment && !address) return - amount = amount && amount > 0 ? amount : this.changeAmount < 0 ? this.changeAmount * -1 : this.outputAmount + const amountToRequest = + amount && amount > 0 ? amount : this.changeAmount < 0 ? this.changeAmount * -1 : this.outputAmount - if (this.getRetrievedUTXOsValue() > amount) return + if (amount && this.getRetrievedUTXOsValue() > amount) return const utxos = await this.datasource.getSpendables({ address: address || this.address, - value: convertSatoshisToBTC(amount), + value: convertSatoshisToBTC(amountToRequest), filter: this.getReservedUTXOs() }) @@ -329,6 +330,7 @@ export class PSBTBuilder extends FeeEstimator { protected async retrieveSelectedUTXOs(address: string, amount: number) { await this.retrieveUTXOs(address, amount) const selectedUTXOs = this.utxos.find((utxo) => utxo.sats >= amount) + this.utxos = selectedUTXOs ? [selectedUTXOs] : [] return this.utxos diff --git a/packages/sdk/src/transactions/types.ts b/packages/sdk/src/transactions/types.ts index b556ce5b..e022aa83 100644 --- a/packages/sdk/src/transactions/types.ts +++ b/packages/sdk/src/transactions/types.ts @@ -76,3 +76,8 @@ export interface Output { address: string value: number } + +export interface SkipStrictSatsCheckOptions { + skipStrictSatsCheck?: boolean + customAmount?: number +}