Skip to content

Commit

Permalink
feat(sdk): account for fractal network (#128)
Browse files Browse the repository at this point in the history
* feat(sdk): account for fractal network

* add missing optional chain parameter

* feat: fractal support

* include optional datasource in publishCollection

* fix: lint

* feat: fractal support for secondary market

* fix:lint

* Update packages/sdk/src/inscription/collection.ts

Co-authored-by: Benedict Pak <[email protected]>

* fix: trim structure

* Update packages/sdk/src/inscription/collection.ts

Co-authored-by: Benedict Pak <[email protected]>

* Update packages/sdk/src/inscription/collection.ts

Co-authored-by: Benedict Pak <[email protected]>

---------

Co-authored-by: Benedict Pak <[email protected]>
  • Loading branch information
veralygit and Nanosync authored Oct 30, 2024
1 parent 72bc27f commit 618fdba
Show file tree
Hide file tree
Showing 18 changed files with 185 additions and 87 deletions.
14 changes: 11 additions & 3 deletions packages/sdk/src/addresses/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as ecc from "@bitcoinerlab/secp256k1"
import BIP32Factory, { BIP32Interface } from "bip32"

import { Network } from "../config/types"
import { Chain, Network } from "../config/types"
import { createTransaction, getDerivationPath, getNetwork, toXOnly } from "../utils"
import { AddressFormats, addressFormats, addressNameToType, AddressTypes, addressTypeToName } from "./formats"

Expand Down Expand Up @@ -40,8 +40,11 @@ export function getAddressesFromPublicKey(
network: Network = "testnet",
format: AddressTypes | "all" = "all",
accountIndex = 0,
addressIndex = 0
addressIndex = 0,
chain: Chain = "bitcoin"
) {
network = chain === "fractal-bitcoin" ? "mainnet" : network

if (!Buffer.isBuffer(pubKey)) {
pubKey = Buffer.from(pubKey, "hex")
}
Expand Down Expand Up @@ -160,7 +163,12 @@ export function getAccountDataFromHdNode({
return accountData
}

export function getAllAccountsFromHdNode({ hdNode, network = "testnet", account = 0, addressIndex = 0 }: GetAllAccountsFromHDNodeOptions) {
export function getAllAccountsFromHdNode({
hdNode,
network = "testnet",
account = 0,
addressIndex = 0
}: GetAllAccountsFromHDNodeOptions) {
const accounts: Account[] = []
const addressTypesList = Object.values(addressTypeToName) as AddressFormats[]

Expand Down
17 changes: 13 additions & 4 deletions packages/sdk/src/api/jsonrpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,19 @@ export const rpc = {
get id() {
return Math.floor(Math.random() * 100000)
},
mainnet: new JsonRpc(getRpcUrl(apiConfig.apis.mainnet.batter)),
testnet: new JsonRpc(getRpcUrl(apiConfig.apis.testnet.batter)),
signet: new JsonRpc(getRpcUrl(apiConfig.apis.signet.batter)),
regtest: new JsonRpc(getRpcUrl(apiConfig.apis.regtest.batter))
bitcoin: {
mainnet: new JsonRpc(getRpcUrl(apiConfig.apis.bitcoin.mainnet)),
testnet: new JsonRpc(getRpcUrl(apiConfig.apis.bitcoin.testnet)),
signet: new JsonRpc(getRpcUrl(apiConfig.apis.bitcoin.signet)),
regtest: new JsonRpc(getRpcUrl(apiConfig.apis.bitcoin.regtest))
},
"fractal-bitcoin": {
mainnet: new JsonRpc(getRpcUrl(apiConfig.apis["fractal-bitcoin"].mainnet)),
testnet: new JsonRpc(getRpcUrl(apiConfig.apis["fractal-bitcoin"].testnet)),
// unused
signet: new JsonRpc(getRpcUrl(apiConfig.apis["fractal-bitcoin"].signet)),
regtest: new JsonRpc(getRpcUrl(apiConfig.apis["fractal-bitcoin"].regtest))
}
} as const

/*
Expand Down
28 changes: 10 additions & 18 deletions packages/sdk/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
export const apiConfig = {
version: "0.0.0.10",
apis: {
mainnet: {
batter: "https://mainnet.ordit.io/",
orderbook: "1H4vvBnr62YWQmvNSt8Z4pDw3Vcv1n5xz7",
ipfs: "http://ipfs-gateway.ordit.io/"
bitcoin: {
mainnet: "https://mainnet.ordit.io/",
regtest: "https://regtest.ordit.io/",
testnet: "https://testnet.ordit.io/",
signet: "https://signet.ordit.io/"
},
regtest: {
batter: "https://regtest.ordit.io/",
orderbook: "bcrt1q2ys7qws8g072dqe3psp92pqz93ac6wmztexkh5",
ipfs: "http://ipfs-gateway.ordit.io/"
},
testnet: {
batter: "https://testnet.ordit.io/",
orderbook: "tb1qfnw26753j7kqu3q099sd48htvtk5wm4e0enmru",
ipfs: "http://ipfs-gateway.ordit.io/"
},
signet: {
batter: "https://signet.ordit.io/",
orderbook: "tb1qfnw26753j7kqu3q099sd48htvtk5wm4e0enmru",
ipfs: "http://ipfs-gateway.ordit.io/"
"fractal-bitcoin": {
mainnet: "https://fractal.ordit.io/",
regtest: "https://fractal-regtest.ordit.io/",
testnet: "https://fractal-testnet.ordit.io/",
signet: "https://fractal-signet.ordit.io/"
}
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export type Network = "mainnet" | "testnet" | "regtest" | "signet"
export type Chain = "bitcoin" | "fractal-bitcoin";
8 changes: 5 additions & 3 deletions packages/sdk/src/fee/FeeEstimator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Psbt } from "bitcoinjs-lib"

import { AddressFormats, getNetwork, getScriptType } from ".."
import { Network } from "../config/types"
import { Chain, Network } from "../config/types"
import { MAXIMUM_FEE } from "../constants"
import { OrditSDKError } from "../utils/errors"
import { FeeEstimatorOptions } from "./types"
Expand All @@ -14,16 +14,18 @@ export default class FeeEstimator {
protected witness?: Buffer[] = []
protected virtualSize = 0
protected weight = 0
protected chain: Chain

constructor({ feeRate, network, psbt, witness }: FeeEstimatorOptions) {
constructor({ feeRate, network, psbt, witness, chain = "bitcoin" }: FeeEstimatorOptions) {
if (feeRate < 0 || !Number.isSafeInteger(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
this.network = network
this.witness = witness || []
this.psbt = psbt || new Psbt({ network: getNetwork(this.network) })
this.psbt = psbt || new Psbt({ network: getNetwork(chain === "fractal-bitcoin" ? "mainnet" : this.network) })
this.chain = chain
}

get data() {
Expand Down
3 changes: 2 additions & 1 deletion packages/sdk/src/fee/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Psbt } from "bitcoinjs-lib"

import { Network } from "../config/types"
import { Chain, Network } from "../config/types"

export interface FeeEstimatorOptions {
feeRate: number
network: Network
psbt?: Psbt
witness?: Buffer[]
chain?: Chain
}
34 changes: 28 additions & 6 deletions packages/sdk/src/inscription/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
TaptreeVersion,
verifyMessage
} from ".."
import { Network } from "../config/types"
import { Chain, Network } from "../config/types"
import { MAXIMUM_ROYALTY_PERCENTAGE } from "../constants"
import { OrditSDKError } from "../utils/errors"
import { buildMeta } from "./meta"
Expand All @@ -22,8 +22,14 @@ export async function publishCollection({
royalty,
publishers,
inscriptions,
chain = "bitcoin",
network,
...options
}: PublishCollectionOptions) {
if (chain !== "bitcoin" && chain !== "fractal-bitcoin") {
throw new OrditSDKError("Invalid chain supplied")
}

if (!validateInscriptions(inscriptions)) {
throw new OrditSDKError("Invalid inscriptions supplied.")
}
Expand All @@ -42,6 +48,8 @@ export async function publishCollection({
}).format(royalty.pct)
}

const datasource = options.datasource || new JsonRpcDatasource({ network: network, chain })

const collectionMeta = {
p: "vord", // protocol
v: 1, // version
Expand All @@ -56,15 +64,19 @@ export async function publishCollection({
insc: inscriptions
}

return new Inscriber({ ...options, meta: collectionMeta })
return new Inscriber({ ...options, meta: collectionMeta, network, chain, datasource })
}

export async function mintFromCollection(options: MintFromCollectionOptions) {
export async function mintFromCollection({ chain = "bitcoin", ...options }: MintFromCollectionOptions) {
if (!options.collectionInscriptionId || !options.inscriptionIid || !options.destinationAddress) {
throw new OrditSDKError("Invalid options supplied.")
}

const datasource = options.datasource || new JsonRpcDatasource({ network: options.network })
if (chain !== "bitcoin" && chain !== "fractal-bitcoin") {
throw new OrditSDKError("Invalid chain supplied")
}

const datasource = options.datasource || new JsonRpcDatasource({ network: options.network, chain })
const collection = await datasource.getInscription({ id: options.collectionInscriptionId })
if (!collection) {
throw new OrditSDKError("Invalid collection")
Expand Down Expand Up @@ -108,7 +120,7 @@ export async function mintFromCollection(options: MintFromCollectionOptions) {

meta.sig = options.signature

return new Inscriber({ ...options, meta })
return new Inscriber({ ...options, meta, chain })
}

export async function bulkMintFromCollection({
Expand All @@ -122,8 +134,13 @@ export async function bulkMintFromCollection({
network,
outputs,
changeAddress,
taptreeVersion
taptreeVersion,
chain = "bitcoin"
}: BulkMintFromCollectionOptions) {
if (chain !== "bitcoin" && chain !== "fractal-bitcoin") {
throw new OrditSDKError("Invalid chain supplied")
}

let currentPointer = 0

const { metaList, inscriptionList } = inscriptions.reduce<{
Expand Down Expand Up @@ -170,6 +187,7 @@ export async function bulkMintFromCollection({

return new InscriberV2({
address,
chain,
publicKey,
feeRate,
datasource,
Expand Down Expand Up @@ -222,6 +240,8 @@ export type PublishCollectionOptions = Pick<GetWalletOptions, "safeMode"> & {
outputs?: Outputs
encodeMetadata?: boolean
enableRBF?: boolean
chain?: Chain
datasource?: BaseDatasource
}

export type CollectionInscription = {
Expand Down Expand Up @@ -253,6 +273,7 @@ export type MintFromCollectionOptions = Pick<GetWalletOptions, "safeMode"> & {
// temporary flag for backward compatibility
includeMintAddress?: boolean
taptreeVersion?: TaptreeVersion
chain?: Chain
}

export type BulkMintFromCollectionOptions = {
Expand All @@ -268,6 +289,7 @@ export type BulkMintFromCollectionOptions = {
taptreeVersion?: TaptreeVersion
collectionGenesis: string
publisherAddress: string
chain?: Chain
}

export type InscriptionsToMint = {
Expand Down
9 changes: 7 additions & 2 deletions packages/sdk/src/instant-trade/InstantTradeBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Inscription } from ".."
import { Chain } from "../config/types"
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"> {
inscriptionOutpoint?: string
chain?: Chain
}

interface RoyaltyAttributes {
Expand All @@ -32,7 +34,8 @@ export default class InstantTradeBuilder extends PSBTBuilder {
network,
publicKey,
inscriptionOutpoint,
autoAdjustment
autoAdjustment,
chain = "bitcoin"
}: InstantTradeBuilderArgOptions) {
super({
address,
Expand All @@ -42,11 +45,13 @@ export default class InstantTradeBuilder extends PSBTBuilder {
publicKey,
outputs: [],
autoAdjustment,
instantTradeMode: true
instantTradeMode: true,
chain
})

this.address = address
this.inscriptionOutpoint = inscriptionOutpoint
this.chain = chain
}

setPrice(value: number) {
Expand Down
23 changes: 18 additions & 5 deletions packages/sdk/src/instant-trade/InstantTradeBuyerTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,26 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {
sellerPSBT,
feeRate,
datasource,
outputs
outputs,
chain = "bitcoin"
}: InstantTradeBuyerTxBuilderArgOptions) {
if (chain !== "bitcoin" && chain !== "fractal-bitcoin") {
throw new OrditSDKError("Invalid chain supplied")
}

super({
address,
datasource,
network,
publicKey,
feeRate,
chain
})

this.receiveAddress = receiveAddress
this.decodeSellerPSBT(sellerPSBT)
this.incomingOutputs = outputs ?? []
this.chain = chain
}

private decodeSellerPSBT(hex: string) {
Expand All @@ -54,7 +61,9 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {
throw new OrditSDKError("invalid seller psbt")
}

const data = getScriptType(input.witnessUtxo.script, this.network)
const _network = this.chain === "fractal-bitcoin" ? "mainnet" : this.network

const data = getScriptType(input.witnessUtxo.script, _network)
this.sellerAddress = data.payload && data.payload.address ? data.payload.address : undefined
if (!this.sellerAddress) {
throw new OrditSDKError("invalid seller psbt")
Expand All @@ -71,18 +80,22 @@ export default class InstantTradeBuyerTxBuilder extends InstantTradeBuilder {
const royaltyOutput = (this.sellerPSBT.data.globalMap.unsignedTx as any).tx.outs[1]
if (!royaltyOutput) return

const scriptPayload = getScriptType(royaltyOutput.script, this.network).payload
const _network = this.chain === "fractal-bitcoin" ? "mainnet" : this.network

const scriptPayload = getScriptType(royaltyOutput.script, _network).payload
const amount = royaltyOutput && royaltyOutput.value >= MINIMUM_AMOUNT_IN_SATS ? royaltyOutput.value : 0
const receiver = scriptPayload ? scriptPayload.address : null

royaltyOutput && receiver && this.setRoyalty({ amount, receiver, price: this.price + amount })
}

private bindRefundableOutput() {
this.outputs = [{
this.outputs = [
{
address: this.address,
value: this.utxos.reduce((acc, curr, index) => (acc += [0, 1].includes(index) ? curr.sats : 0), 0)
}]
}
]
}

private bindInscriptionOutput() {
Expand Down
13 changes: 10 additions & 3 deletions packages/sdk/src/instant-trade/InstantTradeSellerTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,27 @@ export default class InstantTradeSellerTxBuilder extends InstantTradeBuilder {
publicKey,
inscriptionOutpoint,
receiveAddress,
injectRoyalty
injectRoyalty,
chain = "bitcoin"
}: InstantTradeSellerTxBuilderArgOptions) {
if (chain !== "bitcoin" && chain !== "fractal-bitcoin") {
throw new OrditSDKError("Invalid chain supplied")
}

super({
address,
datasource,
network,
publicKey,
inscriptionOutpoint,
autoAdjustment: false, // Prevents PSBTBuilder from adding additional input and change output
feeRate: 0 // seller in instant-trade does not pay network fee
feeRate: 0, // seller in instant-trade does not pay network fee
chain
})

this.receiveAddress = receiveAddress
this.injectRoyalty = injectRoyalty
this.chain = chain
}

private async generatSellerInputs() {
Expand All @@ -47,7 +54,7 @@ export default class InstantTradeSellerTxBuilder extends InstantTradeBuilder {
const input = await processInput({
utxo: this.utxo,
pubKey: this.publicKey,
network: this.network,
network: this.chain === "fractal-bitcoin" ? "mainnet" : this.network,
sighashType: bitcoin.Transaction.SIGHASH_SINGLE | bitcoin.Transaction.SIGHASH_ANYONECANPAY,
datasource: this.datasource
})
Expand Down
Loading

0 comments on commit 618fdba

Please sign in to comment.