Skip to content

Commit

Permalink
refactor(sdk)!: dynamic fee calculation and improvements (#30)
Browse files Browse the repository at this point in the history
* chore(lint): disable semicolons

* chore: lint OrdTransaction file

* refactor: remove unnecessary try..catch block

* refactor: clean up inscribe example

* feat: add instant buy example

* chore: lint commit.ts file

* chore: lint psbt.ts file

* fix: remove unsafe way of parsing numbers

* fix: retrieve hex from GetTransaction RPC

* feat: add dynamic approach to calculate tx fee based on tx type

* chore: lint utils/index.ts file

* refactor: rename method

* fix: update incorrect calculation and parse possible decimal value to int

* refactor(BREAKING)!: update options for wallet.signPsbt fn

* refactor: add new options to wallet.signPsbt arg interface

* docs: update code examples per latest changes

* refactor: remove file import from example and base64 decode fn

also, remove PII from examples

* fix: update hardcoded value w/ variable

* chore: remove hardcoded phrase

* refactor: replace hardcoded fee calculation w/ dynamic calculation using fn

* chore: update create-psbt example

* refactor: update fee calculator to take multiple witness scripts in account

* refactor: replace createPsbt implementation

* refactor: update partially to use new generate input fn

also, lint instant-buy file

* fix: add pagination params to GetUnspents API

* refactor: replace duplicate code to generate inputs

also, update fn that generates input

* refactor: rename dummy to refundable utxos

* refactor: remove default value for pubKeyType

* refactor: enable UTXOs w/ sats > 1000 to be considered as refundable

* feat: move hardcoded dust value to constants

* feat: add `finalize` option to Unisat signPsbt fn

* refactor: replace old fn of fee calculation w/ new fn

also, remove old fn

* refcator: add default value to nested object arg prop

* fix: add missing import

* fix: add missing import x(
  • Loading branch information
iamcrazycoder authored Aug 6, 2023
1 parent 496f370 commit fb952f4
Show file tree
Hide file tree
Showing 21 changed files with 908 additions and 828 deletions.
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"printWidth": 120,
"trailingComma": "none"
"trailingComma": "none",
"semi": false
}
24 changes: 11 additions & 13 deletions examples/node/collections.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Ordit } from "@sadoprotocol/ordit-sdk";
import { base64_encode } from "./utils.js";

const WORDS = "<MNEMONIC PHRASE>";
const IMG_BASE64 = base64_encode("./assets/collection/cover.png"); // relative path to image

async function publish() {
// Load wallet
Expand All @@ -16,15 +14,15 @@ async function publish() {

//publish
const transaction = await Ordit.collection.publish({
title: "Elemental",
description: "Azuki Elementals are a collection of 20,000 characters within the four domains of the Garden.",
slug: "elemental",
title: "Collection Name",
description: "Lorem ipsum something else",
slug: "collection-name",
creator: {
address: wallet.selectedAddress,
email: "iamsaikranthi@gmail.com",
name: "Sai Kranthi"
email: "your-email@example.com",
name: "Your Name"
},
publishers: ["n4PnWbQRkn4XxcYjsSao97D5Xx96SYAvLw"],
publishers: ["<publisher-legacy-address>"],
inscriptions: [
{
iid: "el-01",
Expand All @@ -37,13 +35,13 @@ async function publish() {
sri: "sha256-zjQXDuk++5sICrObmfWqAM5EibidXd2emZoUcU2l5Pg="
}
],
url: "https://google.com",
url: "https://example.com",
publicKey: wallet.publicKey,
destination: wallet.selectedAddress,
changeAddress: wallet.selectedAddress,
postage: 1000,
mediaContent: IMG_BASE64,
mediaType: "image/png"
mediaContent: 'Collection Name', // this will be inscribed on-chain as primary content
mediaType: "text/plain"
});

const depositDetails = transaction.generateCommit();
Expand All @@ -58,7 +56,7 @@ async function publish() {

// sign transaction
const psbtHex = transaction.toHex();
const sig = wallet.signPsbt(psbtHex, { finalized: true });
const sig = wallet.signPsbt(psbtHex, { isRevealTx: true });
// console.log(JSON.stringify(sig, null, 2))
// Broadcast transaction
const submittedTx = await wallet.relayTx(sig, "testnet");
Expand Down Expand Up @@ -117,7 +115,7 @@ async function mint() {

// sign transaction
const psbtHex = transaction.toHex();
const sig = userWallet.signPsbt(psbtHex, { finalized: true });
const sig = userWallet.signPsbt(psbtHex, { isRevealTx: true });
// console.log(JSON.stringify(sig, null, 2))
// Broadcast transaction
const submittedTx = await userWallet.relayTx(sig, "testnet");
Expand Down
22 changes: 12 additions & 10 deletions examples/node/create-psbt.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Ordit, ordit } from '@sadoprotocol/ordit-sdk'

async function main() {
const MNEMONIC = "<MNEMONIC>"; // Generated HD wallet seed phrase
const wallet = new Ordit({
bip39: MNEMONIC,
network: "testnet"
});

wallet.setDefaultAddress('taproot')

const psbt = await ordit.transactions.createPsbt({
pubKey: '039ce27aa7666731648421004ba943b90b8273e23a175d9c58e3ec2e643a9b01d1',
ins: [{
Expand All @@ -10,18 +18,12 @@ async function main() {
address: 'tb1qatkgzm0hsk83ysqja5nq8ecdmtwl73zwurawww',
cardinals: 1200
}],
network: 'testnet'
network: 'testnet',
satsPerByte: 9,
format: 'p2tr'
})

const WORDS = "caution curtain there off know kit market gather slim april dutch sister"; // Generated HD wallet seed phrase
const wallet = new Ordit({
bip39: WORDS,
network: "testnet"
});

wallet.setDefaultAddress('taproot')

const signature = await wallet.signPsbt(psbt.hex, { finalized: true, tweak: true })
const signature = await wallet.signPsbt(psbt.hex)
const txResponse = await wallet.relayTx(signature, 'testnet')

console.log("tx >>", txResponse)
Expand Down
78 changes: 27 additions & 51 deletions examples/node/inscribe.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,55 @@
import { Ordit } from "@sadoprotocol/ordit-sdk"; //import Ordit
import { base64_encode } from "./utils";
import { Ordit } from "@sadoprotocol/ordit-sdk"

const WORDS = "<MNEMONIC WORDS>"; // Generated HD wallet seed phrase
const IMG_BASE64 = base64_encode("./azuki-small-compressed.png"); // relative path to image
const MNEMONIC = "<MNEMONIC>"

async function main() {
// Load wallet
// init wallet
const wallet = new Ordit({
bip39: WORDS,
network: "mainnet"
bip39: MNEMONIC,
network: "testnet"
});

// new ordinal transaction
wallet.setDefaultAddress('taproot')

// new inscription tx
const transaction = Ordit.inscription.new({
publicKey: wallet.taprootPublicKey,
network: "testnet",
publicKey: wallet.publicKey,
changeAddress: wallet.selectedAddress,
destination: wallet.selectedAddress,
mediaContent: IMG_BASE64,
mediaContent: 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAADGElEQVR4nO2by04UQRSGvw34CjoGDAQ3XhDXRnTBAiG4ExMn8QlMmHh5ghE2OOMTsOPmgkSJuhiIuNCVvgFGRia6YFxoNPEGjClyOjlpZyKMU101dn1Jp5Pups7pw+mqv07VQCAQCARaTg9wDcgDc8AjYEXOc3I9C/TyH3EamAbKQO0Ah3n+HtBPmzIIlBq83DawAbwGXsp5Q67Xe74k7bUFR4CF2AvsAGtADjgLdDb42065n5Pnd2LtLAIZPOYSsKUc/grcB4412V43UAS+qDarwAgechPYVY4utPC/ZdqZV20bO7fwiLxy7jNw1ZKdcWk/sjWFB9xQDn0Azli2dwLYVDbv4Pib3xVH3ss4nwSmT6moz2EUR739lkr7UwnbP6k+h6qL0WFRpaFRby64EhsiE2Mw1tu7RI8OF5MyWlLjvGthclT8MP6sJqXta3IYkeIDReXTgG1j00reNqvwWk23ks0F28bKYshodZ94Jn69s2mkR6WambD4RE751mfLSFYZMbM2nxhQvl23rfm3gUP4RQfwU/y7a8vIrBh4i5+8Ef+MNrDCshgwFRwfeSX+PbYtgF7gJ8/FPzMiWGE57Rkwm/Y+IK9GgUaFTR9GgUlbRrJp1wG9baIEj9s0VE7zXCA+GzSzMB/oUrNBsw5hlX6VatannvukkGQ9wLeKUEZVhBL7LC+oiFsbc5uoCZ53VRUex31VeMlF6lXVuoCp0yeJWSH6JPY/AodxwIhaGaokWCPsUstjpvcfwyG3VRpWEsiE+NrgDB4wFVsdHrf4zUdpHx3fXa0N1ssEvT9gvsX7A+ZiO05m5OW9CsKo6hgjnVD8B8XYJSInGuejDm9M2fMuCBngQYM9QhOyf8BMXevRIfcnRNvH9wgt1entvQxCJJZWG+z6+gWsS2VpRc7rcr3e82t/ETneBgHR5gWZpdUOcGzKxGa/2t7rIET0SbFiUjrJJ5IBT6WTm5T7zc7n2yIIthkGvkkQfgCXSSHDIQiEIBhCJhCCsEfIBEIQ9giZwJ9iaYiUB+EhKWVIfqx1zrUjgUAggG/8BsfNc0SX+zvYAAAAAElFTkSuQmCC',
mediaType: "image/png",
feeRate: 15,
meta: {
title: "Elemental",
desc: "Azuki Elementals are a collection of 20,000 characters within the four domains of the Garden.",
slug: "elemental",
traits: [
{
traitType: "Hair",
value: "Electrified Long - Black"
},
{
traitType: "Offhand",
value: "Elemental Blade - Lightning"
},
{
traitType: "Eyes",
value: "Enticing"
},
{
traitType: "Type",
value: "Blue"
}
],
meta: { // Flexible object: Record<string, any>
title: "Example title",
desc: "Lorem ipsum",
slug: "cool-digital-artifact",
creator: {
name: "TheArtist",
name: "Your Name",
email: "[email protected]",
address: wallet.selectedAddress
}
},
network: "mainnet",
postage: 1500
});
postage: 1500 // base value of the inscription in sats
})

// // Get deposit address and fee for inscription
const depositDetails = transaction.generateCommit();
console.log(depositDetails);
// // {
// // address: "<DEPOSIT_ADDRESS>",
// // revealFee: 23456,
// // }
// generate deposit address and fee for inscription
const revealed = transaction.generateCommit();
console.log(revealed) // deposit revealFee to address

// // confirm if deposit address has been funded
const ready = await transaction.isReady(); //- true/false
// confirm if deposit address has been funded
const ready = await transaction.isReady();

if (ready || transaction.ready) {
// build transaction
transaction.build();

// sign transaction
const psbtHex = transaction.toHex();
const sig = wallet.signPsbt(psbtHex);
// console.log(JSON.stringify(sig, null, 2))
const signature = wallet.signPsbt(transaction.toHex());

// Broadcast transaction
const submittedTx = await wallet.relayTx(sig.hex, "mainnet");
console.log(submittedTx);
//{"txid": "<TX_ID>"}
const tx = await wallet.relayTx(signature, "testnet");
console.log(tx);
}
}

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

const BUYER_MNEMONIC = `<12-WORDS-PHRASE>`
const SELLER_MNEMONIC = `<12-WORDS-PHRASE>`

// Initialise seller wallet
const sellerWallet = new Ordit({
bip39: SELLER_MNEMONIC,
network: 'testnet'
})
sellerWallet.setDefaultAddress('taproot') // Switch to address that owns inscription

// Initialise buyer wallet
const buyerWallet = new Ordit({
bip39: BUYER_MNEMONIC,
network: 'testnet'
})

// Switch to address that has enough BTC to cover the sell price + network fees
buyerWallet.setDefaultAddress('taproot')

async function createSellOrder() {
// replace w/ inscription outputpoint you'd like to sell, price, and address to receive sell proceeds
const 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,
publicKey: sellerWallet.publicKey,
network: 'testnet'
})

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

return signedSellerPSBT // hex
}

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

const 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
network: 'testnet',
inscriptionOutPoint: '0f3891f61b944c31fb48b0d9e770dc9e66a4b49097027be53b078be67aca72d4:0'
})

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

return tx
}

async function checkForExistingRefundableUTXOs(address) {
const response = await OrditApi.fetch('/utxo/unspents', {
data: {
address: address,
options: {
txhex: true,
notsafetospend: false,
allowedrarity: ["common"]
}
},
network: 'testnet'
})

const utxos = response.rdata
const filteredUTXOs = utxos
.filter(utxo => utxo.safeToSpend && !utxo.inscriptions.length && utxo.sats > 600 && utxo.sats <= 1000)
.sort((a, b) => a.sats - b.sats) // Sort by lowest value utxo to highest such that we spend only the ones that are lowest

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

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

console.log(tx) // 6dc768015dda40c3752bfc011077ae9b1445d0c9cb5b385fda6ee26dab6cb267
}

;(async() => {
await main()
})()
3 changes: 2 additions & 1 deletion examples/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"inscribe": "node inscribe",
"read": "node read",
"send": "node send",
"create-psbt": "node create-psbt"
"create-psbt": "node create-psbt",
"instant-buy": "node instant-buy"
},
"author": "",
"license": "ISC",
Expand Down
9 changes: 0 additions & 9 deletions examples/node/utils.js

This file was deleted.

22 changes: 15 additions & 7 deletions packages/sdk/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ export class OrditApi {
throw new Error('Invalid address')
}

const utxos = await rpc[network].call<UTXO[]>('GetUnspents', {
address,
options: {
allowedrarity: rarity,
safetospend: type === "spendable",
}
}, rpc.id)
const utxos = await rpc[network].call<UTXO[]>(
"GetUnspents",
{
address,
options: {
allowedrarity: rarity,
safetospend: type === "spendable"
},
pagination: {
page: 1,
limit: 25
}
},
rpc.id
)

const { spendableUTXOs, unspendableUTXOs } = utxos.reduce((acc, utxo) => {
if(utxo.inscriptions?.length && !utxo.safeToSpend) {
Expand Down
6 changes: 3 additions & 3 deletions packages/sdk/src/browser-wallets/unisat/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./addresses";
export * from "./signatures";
export * from "./utils";
export * from "./addresses"
export * from "./signatures"
export * from "./utils"
7 changes: 4 additions & 3 deletions packages/sdk/src/browser-wallets/unisat/signatures.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Psbt } from "bitcoinjs-lib";

import { isUnisatInstalled } from "./utils";
import { UnisatSignPSBTOptions } from "./types"
import { isUnisatInstalled } from "./utils"

export async function signPsbt(psbt: Psbt) {
export async function signPsbt(psbt: Psbt, { finalize = true }: UnisatSignPSBTOptions = {}) {
if (!isUnisatInstalled()) {
throw new Error("Unisat not installed.");
}

const psbtHex = psbt.toHex();

const signedPsbtHex = await window.unisat.signPsbt(psbtHex);
const signedPsbtHex = await window.unisat.signPsbt(psbtHex, { autoFinalized: finalize })

if (!signedPsbtHex) {
throw new Error("Failed to sign psbt hex using Unisat.");
Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/src/browser-wallets/unisat/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface UnisatSignPSBTOptions {
finalize?: boolean
}
3 changes: 3 additions & 0 deletions packages/sdk/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// amount lower than this is considered as dust value
// and majority of the miners don't pick txs w/ the following output value or lower
export const MINIMUM_AMOUNT_IN_SATS = 600
Loading

0 comments on commit fb952f4

Please sign in to comment.