Skip to content

Commit

Permalink
feat(sdk): add custom OrditSDKError class (sadoprotocol#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevzzsk authored Dec 7, 2023
1 parent 7775dbd commit ae1670d
Show file tree
Hide file tree
Showing 16 changed files with 94 additions and 72 deletions.
13 changes: 7 additions & 6 deletions packages/sdk/src/fee/FeeEstimator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Psbt } from "bitcoinjs-lib"
import { AddressFormats, getNetwork, getScriptType } from ".."
import { Network } from "../config/types"
import { MAXIMUM_FEE } from "../constants"
import { OrditSDKError } from "../utils/errors"
import { FeeEstimatorOptions } from "./types"

export default class FeeEstimator {
Expand All @@ -16,7 +17,7 @@ export default class FeeEstimator {

constructor({ feeRate, network, psbt, witness }: FeeEstimatorOptions) {
if (feeRate < 0 || !Number.isSafeInteger(feeRate)) {
throw new Error("Invalid feeRate")
throw new OrditSDKError("Invalid feeRate")
}

this.feeRate = +feeRate // convert decimal to whole number that might have passed Number.isSafeInteger check due to precision loss
Expand All @@ -35,7 +36,7 @@ export default class FeeEstimator {

private sanityCheckFee() {
if (this.fee > MAXIMUM_FEE) {
throw new Error("Error while calculating fees")
throw new OrditSDKError("Error while calculating fees")
}
}

Expand All @@ -53,18 +54,18 @@ export default class FeeEstimator {
const outputTypes: AddressFormats[] = []

if (inputs.length === 0) {
throw new Error("PSBT must have at least one input")
throw new OrditSDKError("PSBT must have at least one input")
}

if (outputs.length === 0) {
throw new Error("PSBT must have at least one output")
throw new OrditSDKError("PSBT must have at least one output")
}

inputs.forEach((input) => {
const script = input.witnessUtxo && input.witnessUtxo.script ? input.witnessUtxo.script : null

if (!script) {
throw new Error("Invalid script")
throw new OrditSDKError("Invalid script")
}

inputTypes.push(getScriptType(script, this.network).format)
Expand Down Expand Up @@ -141,7 +142,7 @@ export default class FeeEstimator {
return { input: 148, output: 34, txHeader: 10, witness: 0 }

default:
throw new Error("Invalid type")
throw new OrditSDKError("Invalid type")
}
}
}
15 changes: 8 additions & 7 deletions packages/sdk/src/inscription/collection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BaseDatasource, GetWalletOptions, Inscriber, JsonRpcDatasource, verifyMessage } from ".."
import { Network } from "../config/types"
import { MAXIMUM_ROYALTY_PERCENTAGE } from "../constants"
import { OrditSDKError } from "../utils/errors"

export async function publishCollection({
title,
Expand All @@ -14,13 +15,13 @@ export async function publishCollection({
...options
}: PublishCollectionOptions) {
if (!validateInscriptions(inscriptions)) {
throw new Error("Invalid inscriptions supplied.")
throw new OrditSDKError("Invalid inscriptions supplied.")
}

if (royalty) {
// 0 = 0%, 0.1 = 10%
if (isNaN(royalty.pct) || royalty.pct < 0 || royalty.pct > MAXIMUM_ROYALTY_PERCENTAGE) {
throw new Error("Invalid royalty %")
throw new OrditSDKError("Invalid royalty %")
}

royalty.pct = +new Intl.NumberFormat("en", {
Expand Down Expand Up @@ -50,7 +51,7 @@ export async function publishCollection({

export async function mintFromCollection(options: MintFromCollectionOptions) {
if (!options.collectionOutpoint || !options.inscriptionIid || !options.destinationAddress) {
throw new Error("Invalid options supplied.")
throw new OrditSDKError("Invalid options supplied.")
}

const [colTxId, colVOut] = options.collectionOutpoint.split(":").map((v, i) => {
Expand All @@ -61,12 +62,12 @@ export async function mintFromCollection(options: MintFromCollectionOptions) {
}) as [string, number | false]

if (!colTxId || colVOut === false) {
throw new Error("Invalid collection outpoint supplied.")
throw new OrditSDKError("Invalid collection outpoint supplied.")
}
const datasource = options.datasource || new JsonRpcDatasource({ network: options.network })
const collection = await datasource.getInscription({ id: options.collectionOutpoint })
if (!collection) {
throw new Error("Invalid collection")
throw new OrditSDKError("Invalid collection")
}

const colMeta = collection.meta
Expand All @@ -83,7 +84,7 @@ export async function mintFromCollection(options: MintFromCollectionOptions) {
}

if (!validInscription) {
throw new Error("Invalid inscription iid supplied.")
throw new OrditSDKError("Invalid inscription iid supplied.")
}

const meta: any = {
Expand All @@ -101,7 +102,7 @@ export async function mintFromCollection(options: MintFromCollectionOptions) {
const validSignature = verifyMessage({ address: meta.publ, message: message, signature: options.signature })

if (!validSignature) {
throw new Error("Invalid signature supplied.")
throw new OrditSDKError("Invalid signature supplied.")
}

meta.sig = options.signature
Expand Down
5 changes: 3 additions & 2 deletions packages/sdk/src/inscription/witness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import * as ecc from "@bitcoinerlab/secp256k1"
import * as bitcoin from "bitcoinjs-lib"

import { MAXIMUM_SCRIPT_ELEMENT_SIZE } from "../constants"
import { OrditSDKError } from "../utils/errors"

export function buildWitnessScript({ recover = false, ...options }: WitnessScriptOptions) {
bitcoin.initEccLib(ecc)
if (!options.mediaType || !options.mediaContent || !options.xkey) {
throw new Error("Failed to build witness script")
throw new OrditSDKError("Failed to build witness script")
}

if (recover) {
Expand Down Expand Up @@ -61,7 +62,7 @@ export function buildWitnessScript({ recover = false, ...options }: WitnessScrip
function opPush(data: string | Buffer) {
const buff = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf8")
if (buff.byteLength > MAXIMUM_SCRIPT_ELEMENT_SIZE)
throw new Error("Data is too large to push. Use chunkContent to split data into smaller chunks")
throw new OrditSDKError("Data is too large to push. Use chunkContent to split data into smaller chunks")

return Buffer.concat([buff])
}
Expand Down
9 changes: 5 additions & 4 deletions packages/sdk/src/instant-trade/InstantTradeBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Inscription } from ".."
import { MINIMUM_AMOUNT_IN_SATS } from "../constants"
import { PSBTBuilder, PSBTBuilderOptions } from "../transactions/PSBTBuilder"
import { OrditSDKError } from "../utils/errors"

export interface InstantTradeBuilderArgOptions
extends Pick<PSBTBuilderOptions, "publicKey" | "network" | "address" | "autoAdjustment" | "feeRate" | "datasource"> {
Expand Down Expand Up @@ -90,18 +91,18 @@ export default class InstantTradeBuilder extends PSBTBuilder {

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

const inscriptions = await this.datasource.getInscriptions({ outpoint: this.inscriptionOutpoint })
this.inscription = inscriptions.find((inscription) => inscription.outpoint === this.inscriptionOutpoint)
if (!this.inscription) {
throw new Error("Inscription not found")
throw new OrditSDKError("Inscription not found")
}

const utxo = await this.datasource.getInscriptionUTXO({ id: this.inscription.genesis })
if (!utxo) {
throw new Error(`Unable to find UTXO: ${this.inscription.outpoint}`)
throw new OrditSDKError(`Unable to find UTXO: ${this.inscription.outpoint}`)
}

this.postage = utxo.sats
Expand All @@ -110,7 +111,7 @@ export default class InstantTradeBuilder extends PSBTBuilder {

protected validatePrice(price: number) {
if (isNaN(price) || price < MINIMUM_AMOUNT_IN_SATS) {
throw new Error("Invalid price")
throw new OrditSDKError("Invalid price")
}
}
}
13 changes: 7 additions & 6 deletions packages/sdk/src/instant-trade/InstantTradeBuyerTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import reverseBuffer from "buffer-reverse"
import { decodePSBT, generateTxUniqueIdentifier, getScriptType, INSTANT_BUY_SELLER_INPUT_INDEX } from ".."
import { MINIMUM_AMOUNT_IN_SATS } from "../constants"
import { InjectableInput, InjectableOutput } from "../transactions/PSBTBuilder"
import { OrditSDKError } from "../utils/errors"
import InstantTradeBuilder, { InstantTradeBuilderArgOptions } from "./InstantTradeBuilder"

interface InstantTradeBuyerTxBuilderArgOptions extends InstantTradeBuilderArgOptions {
Expand Down Expand Up @@ -46,13 +47,13 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {

const [input] = this.sellerPSBT.data.inputs
if (!input?.witnessUtxo) {
throw new Error("invalid seller psbt")
throw new OrditSDKError("invalid seller psbt")
}

const data = getScriptType(input.witnessUtxo.script, this.network)
this.sellerAddress = data.payload && data.payload.address ? data.payload.address : undefined
if (!this.sellerAddress) {
throw new Error("invalid seller psbt")
throw new OrditSDKError("invalid seller psbt")
}
}

Expand Down Expand Up @@ -126,7 +127,7 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {

// 3 = 2 refundables + (at least) 1 to cover for purchase
if (utxos.length < 3) {
throw new Error("No suitable UTXOs found")
throw new OrditSDKError("No suitable UTXOs found")
}

// bind minimum utxos. PSBTBuilder will add more if needed
Expand All @@ -135,15 +136,15 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {

private async isEligible() {
if (!this.inscriptionOutpoint) {
throw new Error("decode seller PSBT to check eligiblity")
throw new OrditSDKError("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")
throw new OrditSDKError("Inscription no longer available for trade")
}
const inscriptionUTXO = await this.datasource.getInscriptionUTXO({ id: inscription.id })
this.postage = inscriptionUTXO.sats
Expand All @@ -163,7 +164,7 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {
async build() {
const eligible = await this.isEligible()
if (!eligible) {
throw new Error("Not eligible")
throw new OrditSDKError("Not eligible")
}

this.decodePrice()
Expand Down
7 changes: 4 additions & 3 deletions packages/sdk/src/instant-trade/InstantTradeSellerTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as bitcoin from "bitcoinjs-lib"
import { processInput } from ".."
import { MINIMUM_AMOUNT_IN_SATS } from "../constants"
import { UTXO } from "../transactions/types"
import { OrditSDKError } from "../utils/errors"
import InstantTradeBuilder, { InstantTradeBuilderArgOptions } from "./InstantTradeBuilder"

interface InstantTradeSellerTxBuilderArgOptions extends InstantTradeBuilderArgOptions {
Expand Down Expand Up @@ -36,7 +37,7 @@ export default class InstantTradeSellerTxBuilder extends InstantTradeBuilder {

private async generatSellerInputs() {
if (!this.utxo) {
throw new Error("UTXO not found")
throw new OrditSDKError("UTXO not found")
}

const input = await processInput({
Expand Down Expand Up @@ -85,13 +86,13 @@ 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}`)
throw new OrditSDKError(`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")
throw new OrditSDKError("Invalid price")
}

this.utxo = await this.verifyAndFindInscriptionUTXO()
Expand Down
17 changes: 9 additions & 8 deletions packages/sdk/src/modules/JsonRpcDatasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "../api/types"
import { Network } from "../config/types"
import { Transaction, UTXO, UTXOLimited } from "../transactions/types"
import { OrditSDKError } from "../utils/errors"
import { BaseDatasource, DatasourceUtility } from "."
import { JsonRpcPagination } from "./types"

Expand All @@ -29,15 +30,15 @@ export default class JsonRpcDatasource extends BaseDatasource {

async getBalance({ address }: GetBalanceOptions) {
if (!address) {
throw new Error("Invalid request")
throw new OrditSDKError("Invalid request")
}

return rpc[this.network].call<number>("Address.GetBalance", { address }, rpc.id)
}

async getInscription({ id, decodeMetadata }: GetInscriptionOptions) {
if (!id) {
throw new Error("Invalid request")
throw new OrditSDKError("Invalid request")
}

id = id.includes(":") ? id.replace(":", "i") : !id.includes("i") ? `${id}i0` : id
Expand All @@ -52,7 +53,7 @@ export default class JsonRpcDatasource extends BaseDatasource {

async getInscriptionUTXO({ id }: GetInscriptionUTXOOptions) {
if (!id) {
throw new Error("Invalid request")
throw new OrditSDKError("Invalid request")
}

id = id.includes(":") ? id.replace(":", "i") : !id.includes("i") ? `${id}i0` : id
Expand Down Expand Up @@ -100,7 +101,7 @@ export default class JsonRpcDatasource extends BaseDatasource {
type = "spendable"
}: GetSpendablesOptions) {
if (!address || isNaN(value) || !value) {
throw new Error("Invalid request")
throw new OrditSDKError("Invalid request")
}

return rpc[this.network].call<UTXOLimited[]>(
Expand All @@ -119,7 +120,7 @@ export default class JsonRpcDatasource extends BaseDatasource {

async getTransaction({ txId, ordinals = true, hex = false, witness = true, decodeMetadata = true }: GetTxOptions) {
if (!txId) {
throw new Error("Invalid request")
throw new OrditSDKError("Invalid request")
}

const tx = await rpc[this.network].call<Transaction>(
Expand Down Expand Up @@ -157,7 +158,7 @@ export default class JsonRpcDatasource extends BaseDatasource {
next = null
}: GetUnspentsOptions): Promise<GetUnspentsResponse> {
if (!address) {
throw new Error("Invalid request")
throw new OrditSDKError("Invalid request")
}

let utxos: UTXO[] = []
Expand Down Expand Up @@ -191,11 +192,11 @@ export default class JsonRpcDatasource extends BaseDatasource {

async relay({ hex, maxFeeRate, validate = true }: RelayOptions) {
if (!hex) {
throw new Error("Invalid request")
throw new OrditSDKError("Invalid request")
}

if (maxFeeRate && (maxFeeRate < 0 || isNaN(maxFeeRate))) {
throw new Error("Invalid max fee rate")
throw new OrditSDKError("Invalid max fee rate")
}

return rpc[this.network].call<string>("Transactions.Relay", { hex, maxFeeRate, validate }, rpc.id)
Expand Down
5 changes: 3 additions & 2 deletions packages/sdk/src/signatures/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { sign, verify } from "bitcoinjs-message"
import { Network } from "../config/types"
import { getDerivedNode } from "../keys"
import { createTransaction, getNetwork } from "../utils"
import { OrditSDKError } from "../utils/errors"

export async function signMessage(options: SignMessageOptions) {
const network = getNetwork(options.network)
options.format = "core"

if (!options.message || !(options.bip39 || options.seed)) {
throw new Error("Invalid options provided.")
throw new OrditSDKError("Invalid options provided.")
}

const seedValue = options.bip39 || options.seed
Expand All @@ -31,7 +32,7 @@ export async function signMessage(options: SignMessageOptions) {
address
}
} catch (error) {
throw new Error("Unable to sign message.")
throw new OrditSDKError("Unable to sign message.")
}
}

Expand Down
Loading

0 comments on commit ae1670d

Please sign in to comment.