Skip to content

Commit

Permalink
feat(sdk)!: integrate InstantTradeBuilder w/ PSBTBuilder (#64)
Browse files Browse the repository at this point in the history
* refactor: export arg-options type

* feat: add base builder for instant trade

* feat: add module to build trade tx for seller

* feat: pass missing arg

* refactor: update seller module

* feat: add buyer module

* feat: add getter to get inputs to sign

* feat: add instant trade mode

* feat: add method to split UTXO

* feat: export instant-trade modules

* refactor: add method to set price

* refactor: change fn scope

* refactor: remove fn cal

* refactor: update import path

* feat: add constructor signature for buffer-reverse

* refactor: update script detector util

* refactor: check inscription for external address

* feat: decode seller psbt and extract outpoint+seller address

* refactor: bind missing fee rate

* feat: merge PSBTs using injectable inputs

* fix: remove double multiplication of witness size

* feat: merge PSBTs using injectable outputs

* refactor: improvise injectable inputs

* feat: add injectable inputs & outputs amount

* revert: add instant trade restrictions

* feat: add method to decode price from seller psbt

* fix: attach bare minimum utxos

* fix: improvise eligibility implementation

* feat: pass injectable outputs

* feat: update instant-buy example

* fix: update build-psbt example

* chore: add deprecation warning to instant-buy fns

* fix: remove duplicate member from child class
  • Loading branch information
iamcrazycoder committed Sep 21, 2023
1 parent 6833414 commit ee32996
Show file tree
Hide file tree
Showing 15 changed files with 560 additions and 88 deletions.
2 changes: 1 addition & 1 deletion examples/node/build-psbt.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async function buildPSBT() {

await psbt.prepare()

const hex = psbt.build().toHex()
const hex = psbt.toHex()
const signature = wallet.signPsbt(hex)
const txId = await OrditApi.relayTx({ hex: signature, network: 'testnet' })

Expand Down
56 changes: 21 additions & 35 deletions examples/node/instant-buy.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OrditApi, Ordit } from '@sadoprotocol/ordit-sdk'
import { Ordit, InstantTradeBuyerTxBuilder, InstantTradeSellerTxBuilder } from '@sadoprotocol/ordit-sdk'

const BUYER_MNEMONIC = `<12-WORDS-PHRASE>`
const SELLER_MNEMONIC = `<12-WORDS-PHRASE>`
Expand All @@ -8,7 +8,7 @@ const sellerWallet = new Ordit({
bip39: SELLER_MNEMONIC,
network: 'testnet'
})
sellerWallet.setDefaultAddress('taproot') // Switch to address that owns inscription
sellerWallet.setDefaultAddress('nested-segwit') // Switch to address that owns inscription

// Initialise buyer wallet
const buyerWallet = new Ordit({
Expand All @@ -21,57 +21,43 @@ buyerWallet.setDefaultAddress('taproot')

async function createSellOrder() {
// replace w/ inscription outputpoint you'd like to sell, price, and address to receive sell proceeds
const { hex: sellerPSBT } = await Ordit.instantBuy.generateSellerPsbt({
inscriptionOutPoint: '8d4a576aecb33b809c208d672a43fd6b175478d9454df4455ed0a2dc7eb7cbf6:0',
price: 4000, // Total sale proceeds will be price + inscription output value (4000 + 2000 = 6000 sats)
receiveAddress: sellerWallet.selectedAddress,
pubKeyType: sellerWallet.selectedAddressType,
const instantTrade = new InstantTradeSellerTxBuilder({
network: 'testnet',
address: sellerWallet.selectedAddress,
publicKey: sellerWallet.publicKey,
network: 'testnet'
inscriptionOutpoint: '58434bd163e5b87c871e5b17c316a3cf141e0e10c3979f0b5ed2530d1d274040:1',
})
instantTrade.setPrice(1234)
await instantTrade.build()

const sellerPSBT = instantTrade.toHex()
const signedSellerPSBT = sellerWallet.signPsbt(sellerPSBT, { finalize: false, extractTx: false })

return signedSellerPSBT // hex
return signedSellerPSBT;
}

async function createBuyOrder({ sellerPSBT }) {
await checkForExistingRefundableUTXOs(buyerWallet.selectedAddress)

const { hex: buyerPSBT } = await Ordit.instantBuy.generateBuyerPsbt({
sellerPsbt: sellerPSBT,
publicKey: buyerWallet.publicKey,
pubKeyType: buyerWallet.selectedAddressType,
feeRate: 10, // set correct rate to prevent tx from getting stuck in mempool
const instantTrade = new InstantTradeBuyerTxBuilder({
network: 'testnet',
inscriptionOutPoint: '0f3891f61b944c31fb48b0d9e770dc9e66a4b49097027be53b078be67aca72d4:0'
})

const signature = buyerWallet.signPsbt(buyerPSBT)
const tx = await buyerWallet.relayTx(signature, 'testnet')

return tx
}

async function checkForExistingRefundableUTXOs(address) {
const { spendableUTXOs } = await OrditApi.fetchUnspentUTXOs({
address,
network: 'testnet'
address: buyerWallet.selectedAddress,
publicKey: buyerWallet.publicKey,
sellerPSBT,
feeRate: 2, // set correct rate to prevent tx from getting stuck in mempool
})
await instantTrade.build()

const filteredUTXOs = spendableUTXOs
.filter(utxo => utxo.sats > 600)
const buyerPSBT = instantTrade.toHex()
const signedTx = buyerWallet.signPsbt(buyerPSBT)
const tx = await buyerWallet.relayTx(signedTx, 'testnet')

if(filteredUTXOs.length < 2) {
throw new Error("Not enough UTXOs in 600-1000 sats range. Use Ordit.instantBuy.generateDummyUtxos() to generate dummy utxos.")
}
return tx
}

async function main() {
const signedSellerPSBT = await createSellOrder()
const tx = await createBuyOrder({ sellerPSBT: signedSellerPSBT })

console.log(tx) // 6dc768015dda40c3752bfc011077ae9b1445d0c9cb5b385fda6ee26dab6cb267
console.log(tx)
}

;(async() => {
Expand Down
10 changes: 3 additions & 7 deletions packages/sdk/src/fee/FeeEstimator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ export default class FeeEstimator {
throw new Error("Invalid script")
}

inputTypes.push(getScriptType(script, this.network))
inputTypes.push(getScriptType(script, this.network).format)
})

outputs.forEach((output) => {
outputTypes.push(getScriptType(output.script, this.network))
outputTypes.push(getScriptType(output.script, this.network).format)
})

return {
Expand Down Expand Up @@ -107,11 +107,7 @@ export default class FeeEstimator {

return {
baseSize: inputVBytes.input + inputVBytes.txHeader + outputVBytes,
witnessSize: this.witness?.length
? witnessSize
: witnessSize > 0
? witnessHeaderSize + witnessSize * inputTypes.length
: 0
witnessSize: this.witness?.length ? witnessSize : witnessSize > 0 ? witnessHeaderSize + witnessSize : 0
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export * as unisat from "./browser-wallets/unisat"
export * as xverse from "./browser-wallets/xverse"
export * from "./config"
export * from "./inscription"
export { InstantTradeBuilder, InstantTradeBuyerTxBuilder, InstantTradeSellerTxBuilder } from "./instant-trade"
export * from "./keys"
export * from "./signatures"
export * from "./transactions"
Expand Down
9 changes: 9 additions & 0 deletions packages/sdk/src/inscription/instant-buy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import { MINIMUM_AMOUNT_IN_SATS } from "../constants"
import FeeEstimator from "../fee/FeeEstimator"
import { InputsToSign } from "./types"

/**
* @deprecated `generateSellerPsbt` has been deprecated and will be removed in future release. Use `InstantTradeSellerTxBuilder` class
*/
export async function generateSellerPsbt({
inscriptionOutPoint,
price,
Expand Down Expand Up @@ -59,6 +62,9 @@ export async function generateSellerPsbt({
}
}

/**
* @deprecated `generateBuyerPsbt` has been deprecated and will be removed in future release. Use `InstantTradeBuyerTxBuilder` class
*/
export async function generateBuyerPsbt({
publicKey,
pubKeyType,
Expand Down Expand Up @@ -204,6 +210,9 @@ export async function generateBuyerPsbt({
}
}

/**
* @deprecated `generateRefundableUTXOs` has been deprecated and will be removed in future release. Use `InstantTradeBuyerTxBuilder.splitUTXOsForTrade`
*/
export async function generateRefundableUTXOs({
publicKey,
pubKeyType,
Expand Down
63 changes: 63 additions & 0 deletions packages/sdk/src/instant-trade/InstantTradeBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { OrditApi } from ".."
import { MINIMUM_AMOUNT_IN_SATS } from "../constants"
import { PSBTBuilder, PSBTBuilderOptions } from "../transactions/PSBTBuilder"

export interface InstantTradeBuilderArgOptions extends Pick<PSBTBuilderOptions, "publicKey" | "network" | "address"> {
inscriptionOutpoint?: string
}

export default class InstantTradeBuilder extends PSBTBuilder {
protected inscriptionOutpoint?: string
protected price = 0
protected postage = 0

constructor({ address, network, publicKey, inscriptionOutpoint }: InstantTradeBuilderArgOptions) {
super({
address,
feeRate: 0,
network,
publicKey,
outputs: [],
instantTradeMode: true
})

this.address = address
this.inscriptionOutpoint = inscriptionOutpoint
}

setPrice(value: number) {
this.validatePrice(value)
this.price = parseInt(value.toString())
}

protected async verifyAndFindInscriptionUTXO(address?: string) {
if (!this.inscriptionOutpoint) {
throw new Error("set inscription outpoint to the class")
}

const { totalUTXOs, unspendableUTXOs } = await OrditApi.fetchUnspentUTXOs({
address: address || this.address,
network: this.network,
type: "all"
})
if (!totalUTXOs) {
throw new Error("No UTXOs found")
}

const utxo = unspendableUTXOs.find((utxo) =>
utxo.inscriptions?.find((i) => i.outpoint === this.inscriptionOutpoint)
)
if (!utxo) {
throw new Error("Inscription not found")
}

this.postage = utxo.sats
return utxo
}

protected validatePrice(price: number) {
if (isNaN(price) || price < MINIMUM_AMOUNT_IN_SATS) {
throw new Error("Invalid price")
}
}
}
Loading

0 comments on commit ee32996

Please sign in to comment.