Skip to content

Commit

Permalink
feat(sdk): introduce injectable datasource dependency (#67)
Browse files Browse the repository at this point in the history
* feat: add abstract class to define datasource base

* feat: add JSON RPC datasource class

* refactor: replace OrditApi w/ JsonRpc datasource

* refasctor: move datasource to very top in inheritance chain

* refactor: accept & bind datasource in instant-trade classes

* refactor: replace OrditApi w/ datasource

* feat: decouple utils to separate class

* fix: pass exact fee set by user

* refactor: set ds type to abstract class

* refactor: deprecate OrditApi & replace w/ datasource

* fix: fetch unspents w/ pagination

* refactor!: rename class to BaseDatasource and convert to abtract class

* refactor: pass datasource to processInput

* fix: rename derived fn wrt. abstract class
  • Loading branch information
iamcrazycoder authored Sep 27, 2023
1 parent 32770c2 commit eb8f19c
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 45 deletions.
3 changes: 3 additions & 0 deletions packages/sdk/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
RelayTxOptions
} from "./types"

/**
* @deprecated `OrditApi` has been deprecated and will be removed in future release. Use `JsonRpcDatasource` instead
*/
export class OrditApi {
static transformInscriptions(inscriptions: Inscription[] | undefined) {
if (!inscriptions) return []
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface FetchUnspentUTXOsOptions {
rarity?: Rarity[]
decodeMetadata?: boolean
sort?: "asc" | "desc"
limit?: number
next?: string | null
}

export interface FetchUnspentUTXOsResponse {
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 @@ -35,6 +35,7 @@ export * from "./config"
export * from "./inscription"
export { InstantTradeBuilder, InstantTradeBuyerTxBuilder, InstantTradeSellerTxBuilder } from "./instant-trade"
export * from "./keys"
export { BaseDatasource, JsonRpcDatasource } from "./modules"
export * from "./signatures"
export * from "./transactions"
export { PSBTBuilder } from "./transactions/PSBTBuilder"
Expand Down
12 changes: 4 additions & 8 deletions packages/sdk/src/inscription/collection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GetWalletOptions, Inscriber, OrditApi, verifyMessage } from ".."
import { BaseDatasource, GetWalletOptions, Inscriber, JsonRpcDatasource, verifyMessage } from ".."
import { Network } from "../config/types"

export async function publishCollection({
Expand Down Expand Up @@ -62,13 +62,8 @@ export async function mintFromCollection(options: MintFromCollectionOptions) {
if (!colTxId || colVOut === false) {
throw new Error("Invalid collection outpoint supplied.")
}

const { tx } = await OrditApi.fetchTx({ txId: colTxId, network: options.network })
if (!tx) {
throw new Error("Failed to get raw transaction for id: " + colTxId)
}

const collection = tx.vout[colVOut].inscriptions[0]
const datasource = options.datasource || new JsonRpcDatasource({ network: options.network })
const collection = await datasource.getInscription(options.collectionOutpoint)
if (!collection) {
throw new Error("Invalid collection")
}
Expand Down Expand Up @@ -180,6 +175,7 @@ export type MintFromCollectionOptions = Pick<GetWalletOptions, "safeMode"> & {
traits?: any
encodeMetadata?: boolean
enableRBF?: boolean
datasource?: BaseDatasource
}

type Outputs = Array<{ address: string; value: number }>
19 changes: 13 additions & 6 deletions packages/sdk/src/instant-trade/InstantTradeBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
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" | "autoAdjustment" | "feeRate"> {
extends Pick<PSBTBuilderOptions, "publicKey" | "network" | "address" | "autoAdjustment" | "feeRate" | "datasource"> {
inscriptionOutpoint?: string
}

Expand All @@ -13,10 +12,19 @@ export default class InstantTradeBuilder extends PSBTBuilder {
protected postage = 0
protected royalty = 0

constructor({ address, network, publicKey, inscriptionOutpoint, autoAdjustment }: InstantTradeBuilderArgOptions) {
constructor({
address,
datasource,
feeRate,
network,
publicKey,
inscriptionOutpoint,
autoAdjustment
}: InstantTradeBuilderArgOptions) {
super({
address,
feeRate: 0,
datasource,
feeRate,
network,
publicKey,
outputs: [],
Expand All @@ -42,9 +50,8 @@ export default class InstantTradeBuilder extends PSBTBuilder {
throw new Error("set inscription outpoint to the class")
}

const { totalUTXOs, unspendableUTXOs } = await OrditApi.fetchUnspentUTXOs({
const { totalUTXOs, unspendableUTXOs } = await this.datasource.getUnspents({
address: address || this.address,
network: this.network,
type: "all"
})
if (!totalUTXOs) {
Expand Down
28 changes: 13 additions & 15 deletions packages/sdk/src/instant-trade/InstantTradeBuyerTxBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { Psbt } from "bitcoinjs-lib"
import reverseBuffer from "buffer-reverse"

import {
decodePSBT,
generateTxUniqueIdentifier,
getScriptType,
INSTANT_BUY_SELLER_INPUT_INDEX,
OrditApi,
processInput
} from ".."
import { decodePSBT, generateTxUniqueIdentifier, getScriptType, INSTANT_BUY_SELLER_INPUT_INDEX, processInput } from ".."
import { MINIMUM_AMOUNT_IN_SATS } from "../constants"
import { InjectableInput, InjectableOutput } from "../transactions/PSBTBuilder"
import { Output } from "../transactions/types"
Expand All @@ -30,10 +23,12 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {
publicKey,
receiveAddress,
sellerPSBT,
feeRate
feeRate,
datasource
}: InstantTradeBuyerTxBuilderArgOptions) {
super({
address,
datasource,
network,
publicKey,
feeRate
Expand Down Expand Up @@ -117,9 +112,8 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {

private async findUTXOs() {
const utxos = (
await OrditApi.fetchUnspentUTXOs({
await this.datasource.getUnspents({
address: this.address,
network: this.network,
sort: "asc" // sort by ascending order to use low amount utxos as refundable utxos
})
).spendableUTXOs.filter((utxo) => utxo.sats >= MINIMUM_AMOUNT_IN_SATS)
Expand Down Expand Up @@ -168,16 +162,20 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {
}

async splitUTXOsForTrade(destinationAddress: string) {
const { totalUTXOs, spendableUTXOs } = await OrditApi.fetchUnspentUTXOs({
address: this.address,
network: this.network
const { totalUTXOs, spendableUTXOs } = await this.datasource.getUnspents({
address: this.address
})
if (!totalUTXOs) {
throw new Error("No UTXOs found")
}

const utxo = spendableUTXOs.sort((a, b) => b.sats - a.sats)[0] // Largest UTXO
const input = await processInput({ utxo, pubKey: this.publicKey, network: this.network })
const input = await processInput({
utxo,
pubKey: this.publicKey,
network: this.network,
datasource: this.datasource
})
const totalOutputs = 3
const outputs: Output[] = []
this.inputs = [input]
Expand Down
12 changes: 6 additions & 6 deletions packages/sdk/src/instant-trade/InstantTradeSellerTxBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as bitcoin from "bitcoinjs-lib"

import { OrditApi, processInput } from ".."
import { processInput } from ".."
import { MINIMUM_AMOUNT_IN_SATS } from "../constants"
import { UTXO } from "../transactions/types"
import InstantTradeBuilder, { InstantTradeBuilderArgOptions } from "./InstantTradeBuilder"
Expand All @@ -15,13 +15,15 @@ export default class InstantTradeSellerTxBuilder extends InstantTradeBuilder {

constructor({
address,
datasource,
network,
publicKey,
inscriptionOutpoint,
receiveAddress
}: InstantTradeSellerTxBuilderArgOptions) {
super({
address,
datasource,
network,
publicKey,
inscriptionOutpoint,
Expand All @@ -41,7 +43,8 @@ export default class InstantTradeSellerTxBuilder extends InstantTradeBuilder {
utxo: this.utxo,
pubKey: this.publicKey,
network: this.network,
sighashType: bitcoin.Transaction.SIGHASH_SINGLE | bitcoin.Transaction.SIGHASH_ANYONECANPAY
sighashType: bitcoin.Transaction.SIGHASH_SINGLE | bitcoin.Transaction.SIGHASH_ANYONECANPAY,
datasource: this.datasource
})

this.inputs = [input]
Expand All @@ -64,10 +67,7 @@ export default class InstantTradeSellerTxBuilder extends InstantTradeBuilder {
return
}

const collection = await OrditApi.fetchInscription({
id: `${this.utxo.inscriptions[0].meta.col}i0`,
network: this.network
})
const collection = await this.datasource.getInscription(`${this.utxo.inscriptions[0].meta.col}i0`)
const royalty = collection.meta?.royalty
if (!royalty || !royalty.address || !royalty.pct) {
return
Expand Down
33 changes: 33 additions & 0 deletions packages/sdk/src/modules/BaseDatasource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Transaction as BTCTransaction } from "bitcoinjs-lib"

import { Inscription } from ".."
import { FetchSpendablesOptions, FetchTxOptions, FetchUnspentUTXOsOptions } from "../api/types"
import { Network } from "../config/types"
import { Transaction, UTXOLimited } from "../transactions/types"
import { DatasourceUtility } from "."

interface BaseDatasourceOptions {
network: Network
}

export default abstract class BaseDatasource {
protected readonly network: Network

constructor({ network }: BaseDatasourceOptions) {
this.network = network
}

abstract getBalance(address?: string): Promise<number>

abstract getInscription(id?: string, decodeMetadata?: boolean): Promise<Inscription>

abstract getInscriptions(outpoint?: string, decodeMetadata?: boolean): Promise<Inscription[]>

abstract getSpendables(args?: FetchSpendablesOptions): Promise<UTXOLimited[]>

abstract getTransaction(args: FetchTxOptions): Promise<{ tx: Transaction; rawTx?: BTCTransaction }>

abstract getUnspents(
args: FetchUnspentUTXOsOptions
): Promise<ReturnType<typeof DatasourceUtility.segregateUTXOsBySpendStatus>>
}
44 changes: 44 additions & 0 deletions packages/sdk/src/modules/DatasourceUtility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { decodeObject, Inscription } from ".."
import { UTXO } from "../transactions/types"

interface SegregateUTXOsBySpendStatusArgOptions {
utxos: UTXO[]
decodeMetadata?: boolean
}

export default class DatasourceUtility {
static transformInscriptions(inscriptions?: Inscription[]) {
if (!inscriptions) return []

return inscriptions.map((inscription) => {
inscription.meta = inscription.meta ? decodeObject(inscription.meta) : inscription.meta
return inscription
})
}

static segregateUTXOsBySpendStatus({ utxos, decodeMetadata }: SegregateUTXOsBySpendStatusArgOptions) {
const { spendableUTXOs, unspendableUTXOs } = utxos.reduce(
(acc, utxo) => {
if (utxo.inscriptions?.length && !utxo.safeToSpend) {
utxo.inscriptions = decodeMetadata ? this.transformInscriptions(utxo.inscriptions) : utxo.inscriptions

acc.unspendableUTXOs.push(utxo)
} else {
acc.spendableUTXOs.push(utxo)
}

return acc
},
{
spendableUTXOs: [],
unspendableUTXOs: []
} as Record<string, UTXO[]>
)

return {
totalUTXOs: utxos.length,
spendableUTXOs,
unspendableUTXOs
}
}
}
Loading

0 comments on commit eb8f19c

Please sign in to comment.