From d56c39db8e12940a5646e3fc796da80b91773b76 Mon Sep 17 00:00:00 2001 From: c4605 Date: Sun, 27 Apr 2025 11:35:28 +0200 Subject: [PATCH] feat: add option `extraOutputs` to bitcoin/brc20/runes routes --- src/sdkUtils/bridgeFromBRC20.ts | 48 ++++++++++- src/sdkUtils/bridgeFromBitcoin.ts | 81 ++++++++++++++++--- src/sdkUtils/bridgeFromRunes.ts | 44 +++++++++- .../estimateBridgeTransactionFromBRC20.ts | 14 +++- .../estimateBridgeTransactionFromBitcoin.ts | 9 +++ .../estimateBridgeTransactionFromRunes.ts | 14 +++- src/utils/objectHelper.ts | 4 + 7 files changed, 198 insertions(+), 16 deletions(-) diff --git a/src/sdkUtils/bridgeFromBRC20.ts b/src/sdkUtils/bridgeFromBRC20.ts index 44806ab..5f0ae35 100644 --- a/src/sdkUtils/bridgeFromBRC20.ts +++ b/src/sdkUtils/bridgeFromBRC20.ts @@ -109,6 +109,11 @@ export interface BridgeFromBRC20Input { networkFeeChangeAddress: string networkFeeChangeAddressScriptPubKey: Uint8Array + extraOutputs?: { + address: BitcoinAddress + satsAmount: bigint + }[] + signPsbt: BridgeFromBRC20Input_signPsbtFn sendTransaction: (tx: { hex: string @@ -124,6 +129,10 @@ export interface BridgeFromBRC20Input { export interface BridgeFromBRC20Output { txid: string + extraOutputs: { + index: number + satsAmount: bigint + }[] } export async function bridgeFromBRC20( @@ -395,6 +404,7 @@ async function bridgeFromBRC20_toStacks( withHardLinkageOutput: false, bridgeFeeOutput, swapRoute: info.swapRoute, + extraOutputs: info.extraOutputs ?? [], }, createdOrder, ) @@ -444,6 +454,7 @@ async function bridgeFromBRC20_toEVM( withHardLinkageOutput: false, bridgeFeeOutput, swapRoute: info.swapRoute, + extraOutputs: info.extraOutputs ?? [], }, createdOrder, ) @@ -506,6 +517,7 @@ async function bridgeFromBRC20_toBitcoin( withHardLinkageOutput: true, bridgeFeeOutput, swapRoute: info.swapRoute, + extraOutputs: info.extraOutputs ?? [], }, createdOrder, ) @@ -568,6 +580,7 @@ async function bridgeFromBRC20_toMeta( withHardLinkageOutput: true, bridgeFeeOutput, swapRoute: info.swapRoute, + extraOutputs: info.extraOutputs ?? [], }, createdOrder, ) @@ -583,7 +596,13 @@ async function broadcastBRC20Transaction( sendTransaction: BridgeFromBRC20Input["sendTransaction"] }, createdOrder: CreateBridgeOrderResult, -): Promise<{ txid: string }> { +): Promise<{ + txid: string + extraOutputs: { + index: number + satsAmount: bigint + }[] +}> { const pegInAddress = getMetaPegInAddress(info.fromChain, info.toChain) if (pegInAddress == null) { throw new UnsupportedBridgeRouteError( @@ -665,7 +684,10 @@ async function broadcastBRC20Transaction( ) } - return { txid: delegateBroadcastedTxId } + return { + txid: delegateBroadcastedTxId, + extraOutputs: tx.extraOutputs, + } } type ConstructBRC20TransactionInput = PrepareBRC20TransactionInput & { @@ -694,6 +716,10 @@ async function constructBRC20Transaction( index: number satsAmount: bigint } + extraOutputs: { + index: number + satsAmount: bigint + }[] }> { const txOptions = await prepareBRC20Transaction(sdkContext, info) @@ -749,6 +775,7 @@ async function constructBRC20Transaction( return { hex: signedTx.hex, revealOutput: txOptions.revealOutput, + extraOutputs: txOptions.extraOutputs, } } @@ -771,6 +798,10 @@ export type PrepareBRC20TransactionInput = KnownRoute_FromBRC20 & { satsAmount: BigNumber } hardLinkageOutput: null | BitcoinAddress + extraOutputs: { + address: BitcoinAddress + satsAmount: bigint + }[] } /** * Bitcoin Tx Structure: @@ -807,6 +838,10 @@ export async function prepareBRC20Transaction( index: number satsAmount: bigint } + extraOutputs: { + index: number + satsAmount: bigint + }[] } > { const bitcoinNetwork = @@ -858,6 +893,10 @@ export async function prepareBRC20Transaction( satsAmount: BITCOIN_OUTPUT_MINIMUM_AMOUNT, }, ]), + ...info.extraOutputs.map(o => ({ + addressScriptPubKey: o.address.scriptPubKey, + satsAmount: o.satsAmount, + })), ], changeAddressScriptPubKey: info.networkFeeChangeAddressScriptPubKey, feeRate: info.networkFeeRate, @@ -872,6 +911,7 @@ export async function prepareBRC20Transaction( pegInOrderDataCausedOffset + (info.bridgeFeeOutput == null ? 0 : 1) const hardLinkageCausedOffset = bridgeFeeCausedOffset + (info.hardLinkageOutput == null ? 0 : 1) + const extraOutputsStartOffset = hardLinkageCausedOffset return { ...result, @@ -899,5 +939,9 @@ export async function prepareBRC20Transaction( index: hardLinkageCausedOffset - 1, satsAmount: BITCOIN_OUTPUT_MINIMUM_AMOUNT, }, + extraOutputs: info.extraOutputs.map((o, i) => ({ + index: extraOutputsStartOffset + i, + satsAmount: o.satsAmount, + })), } } diff --git a/src/sdkUtils/bridgeFromBitcoin.ts b/src/sdkUtils/bridgeFromBitcoin.ts index eb1a084..5c08321 100644 --- a/src/sdkUtils/bridgeFromBitcoin.ts +++ b/src/sdkUtils/bridgeFromBitcoin.ts @@ -89,9 +89,16 @@ export interface BridgeFromBitcoinInput { toAddressScriptPubKey?: Uint8Array amount: SDKNumber - networkFeeRate: bigint swapRoute?: SwapRoute_WithMinimumAmountsToReceive_Public + + networkFeeRate: bigint reselectSpendableUTXOs: BridgeFromBitcoinInput_reselectSpendableUTXOs + + extraOutputs?: { + address: BitcoinAddress + satsAmount: bigint + }[] + signPsbt: BridgeFromBitcoinInput_signPsbtFn sendTransaction: (tx: { hex: string @@ -107,6 +114,10 @@ export interface BridgeFromBitcoinInput { export interface BridgeFromBitcoinOutput { txid: string + extraOutputs: { + index: number + satsAmount: bigint + }[] } export async function bridgeFromBitcoin( @@ -289,7 +300,12 @@ async function bridgeFromBitcoin_toStacks( return broadcastBitcoinTransaction( sdkContext, - { ...info, withHardLinkageOutput: false, swapRoute: info.swapRoute }, + { + ...info, + withHardLinkageOutput: false, + swapRoute: info.swapRoute, + extraOutputs: info.extraOutputs ?? [], + }, createdOrder, ) } @@ -331,7 +347,12 @@ async function bridgeFromBitcoin_toEVM( return broadcastBitcoinTransaction( sdkContext, - { ...info, withHardLinkageOutput: false, swapRoute: info.swapRoute }, + { + ...info, + withHardLinkageOutput: false, + swapRoute: info.swapRoute, + extraOutputs: info.extraOutputs ?? [], + }, createdOrder, ) } @@ -386,7 +407,12 @@ async function bridgeFromBitcoin_toMeta( return broadcastBitcoinTransaction( sdkContext, - { ...info, withHardLinkageOutput: true, swapRoute: info.swapRoute }, + { + ...info, + withHardLinkageOutput: true, + swapRoute: info.swapRoute, + extraOutputs: info.extraOutputs ?? [], + }, createdOrder, ) } @@ -401,7 +427,13 @@ async function broadcastBitcoinTransaction( sendTransaction: BridgeFromBitcoinInput["sendTransaction"] }, createdOrder: CreateBridgeOrderResult, -): Promise<{ txid: string }> { +): Promise<{ + txid: string + extraOutputs: { + index: number + satsAmount: bigint + }[] +}> { const pegInAddress = getBTCPegInAddress(info.fromChain, info.toChain) if (pegInAddress == null) { throw new UnsupportedBridgeRouteError( @@ -486,7 +518,10 @@ async function broadcastBitcoinTransaction( ) } - return { txid: delegateBroadcastedTxId } + return { + txid: delegateBroadcastedTxId, + extraOutputs: tx.extraOutputs, + } } type ConstructBitcoinTransactionInput = PrepareBitcoinTransactionInput & { @@ -495,7 +530,6 @@ type ConstructBitcoinTransactionInput = PrepareBitcoinTransactionInput & { | SwapRouteViaALEX_WithMinimumAmountsToReceive_Public | SwapRouteViaEVMDexAggregator_WithMinimumAmountsToReceive_Public signPsbt: BridgeFromBitcoinInput["signPsbt"] - pegInAddress: BitcoinAddress validateBridgeOrder: ( pegInTx: Uint8Array, revealTx: undefined | Uint8Array, @@ -511,6 +545,10 @@ async function constructBitcoinTransaction( index: number satsAmount: bigint } + extraOutputs: { + index: number + satsAmount: bigint + }[] }> { const txOptions = await prepareBitcoinTransaction(sdkContext, info) @@ -565,6 +603,7 @@ async function constructBitcoinTransaction( return { hex: signedTx.hex, revealOutput: txOptions.revealOutput, + extraOutputs: txOptions.extraOutputs, } } @@ -578,6 +617,10 @@ export type PrepareBitcoinTransactionInput = KnownRoute_FromBitcoin & { orderData: Uint8Array hardLinkageOutput: null | BitcoinAddress pegInAddress: BitcoinAddress + extraOutputs: { + address: BitcoinAddress + satsAmount: bigint + }[] } /** * Bitcoin Tx Structure: @@ -603,6 +646,10 @@ export async function prepareBitcoinTransaction( index: number satsAmount: bigint } + extraOutputs: { + index: number + satsAmount: bigint + }[] } > { const bitcoinNetwork = @@ -642,6 +689,10 @@ export async function prepareBitcoinTransaction( satsAmount: BITCOIN_OUTPUT_MINIMUM_AMOUNT, }, ]), + ...info.extraOutputs.map(o => ({ + addressScriptPubKey: o.address.scriptPubKey, + satsAmount: o.satsAmount, + })), ], changeAddressScriptPubKey: info.fromAddressScriptPubKey, feeRate: info.networkFeeRate, @@ -650,20 +701,30 @@ export async function prepareBitcoinTransaction( ), }) + const pegInOrderDataCausedOffset = 1 + const pegInBitcoinTokenCausedOffset = pegInOrderDataCausedOffset + 1 + const hardLinkageCausedOffset = + pegInBitcoinTokenCausedOffset + (info.hardLinkageOutput == null ? 0 : 1) + const extraOutputsStartOffset = hardLinkageCausedOffset + return { ...result, bitcoinNetwork, revealOutput: { - index: 0, + index: pegInOrderDataCausedOffset - 1, satsAmount: recipient.satsAmount, }, hardLinkageOutput: - info.hardLinkageOutput == null + hardLinkageCausedOffset == null ? undefined : { - index: 2, + index: hardLinkageCausedOffset - 1, satsAmount: BITCOIN_OUTPUT_MINIMUM_AMOUNT, }, + extraOutputs: info.extraOutputs.map((o, i) => ({ + index: extraOutputsStartOffset + i, + satsAmount: o.satsAmount, + })), } } diff --git a/src/sdkUtils/bridgeFromRunes.ts b/src/sdkUtils/bridgeFromRunes.ts index 27bd07f..2734c3a 100644 --- a/src/sdkUtils/bridgeFromRunes.ts +++ b/src/sdkUtils/bridgeFromRunes.ts @@ -120,6 +120,11 @@ export interface BridgeFromRunesInput { networkFeeChangeAddressScriptPubKey: Uint8Array reselectSpendableNetworkFeeUTXOs: BridgeFromRunesInput_reselectSpendableNetworkFeeUTXOs + extraOutputs?: { + address: BitcoinAddress + satsAmount: bigint + }[] + signPsbt: BridgeFromRunesInput_signPsbtFn sendTransaction: (tx: { hex: string @@ -135,6 +140,10 @@ export interface BridgeFromRunesInput { export interface BridgeFromRunesOutput { txid: string + extraOutputs: { + index: number + satsAmount: bigint + }[] } export async function bridgeFromRunes( @@ -336,6 +345,7 @@ async function bridgeFromRunes_toStacks( ...info, withHardLinkageOutput: false, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], swapRoute: info.swapRoute, }, createdOrder, @@ -385,6 +395,7 @@ async function bridgeFromRunes_toEVM( ...info, withHardLinkageOutput: false, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], swapRoute: info.swapRoute, }, createdOrder, @@ -447,6 +458,7 @@ async function bridgeFromRunes_toBitcoin( ...info, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], swapRoute: info.swapRoute, }, createdOrder, @@ -509,6 +521,7 @@ async function bridgeFromRunes_toMeta( ...info, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], swapRoute: info.swapRoute, }, createdOrder, @@ -525,7 +538,13 @@ async function broadcastRunesTransaction( sendTransaction: BridgeFromRunesInput["sendTransaction"] }, createdOrder: CreateBridgeOrderResult, -): Promise<{ txid: string }> { +): Promise<{ + txid: string + extraOutputs: { + index: number + satsAmount: bigint + }[] +}> { const pegInAddress = getMetaPegInAddress(info.fromChain, info.toChain) if (pegInAddress == null) { throw new UnsupportedBridgeRouteError( @@ -607,7 +626,10 @@ async function broadcastRunesTransaction( ) } - return { txid: delegateBroadcastedTxId } + return { + txid: delegateBroadcastedTxId, + extraOutputs: tx.extraOutputs, + } } type ConstructRunesTransactionInput = PrepareRunesTransactionInput & { @@ -636,6 +658,10 @@ async function constructRunesTransaction( index: number satsAmount: bigint } + extraOutputs: { + index: number + satsAmount: bigint + }[] }> { const txOptions = await prepareRunesTransaction( sdkContext, @@ -695,6 +721,7 @@ async function constructRunesTransaction( return { hex: signedTx.hex, revealOutput: txOptions.revealOutput, + extraOutputs: txOptions.extraOutputs, } } @@ -718,6 +745,10 @@ export type PrepareRunesTransactionInput = KnownRoute_FromRunes & { satsAmount: BigNumber } hardLinkageOutput: null | BitcoinAddress + extraOutputs: { + address: BitcoinAddress + satsAmount: bigint + }[] } /** * Bitcoin Tx Structure: @@ -757,6 +788,10 @@ export async function prepareRunesTransaction( index: number satsAmount: bigint } + extraOutputs: { + index: number + satsAmount: bigint + }[] } > { const bitcoinNetwork = @@ -845,6 +880,7 @@ export async function prepareRunesTransaction( const hardLinkageCausedOffset = bridgeFeeCausedOffset + (info.hardLinkageOutput == null ? 0 : 1) const pegInRuneTokensCausedOffset = hardLinkageCausedOffset + 1 + const extraOutputsStartOffset = pegInRuneTokensCausedOffset const runesOpReturnScript = toBitcoinOpReturnScript({ edicts: [ @@ -942,6 +978,10 @@ export async function prepareRunesTransaction( index: hardLinkageCausedOffset - 1, satsAmount: BITCOIN_OUTPUT_MINIMUM_AMOUNT, }, + extraOutputs: info.extraOutputs.map((o, i) => ({ + index: extraOutputsStartOffset + i, + satsAmount: o.satsAmount, + })), } } diff --git a/src/sdkUtils/estimateBridgeTransactionFromBRC20.ts b/src/sdkUtils/estimateBridgeTransactionFromBRC20.ts index ee3dad6..6d2712f 100644 --- a/src/sdkUtils/estimateBridgeTransactionFromBRC20.ts +++ b/src/sdkUtils/estimateBridgeTransactionFromBRC20.ts @@ -1,5 +1,8 @@ import { UTXOSpendable } from "../bitcoinHelpers" -import { getBitcoinHardLinkageAddress } from "../bitcoinUtils/btcAddresses" +import { + BitcoinAddress, + getBitcoinHardLinkageAddress, +} from "../bitcoinUtils/btcAddresses" import { SDK_NAME } from "../bitcoinUtils/constants" import { getMetaPegInAddress } from "../metaUtils/btcAddresses" import { isSupportedBRC20Route } from "../metaUtils/peggingHelpers" @@ -65,6 +68,11 @@ export interface EstimateBridgeTransactionFromBRC20Input { networkFeeChangeAddress: string networkFeeChangeAddressScriptPubKey: Uint8Array reselectSpendableNetworkFeeUTXOs: BridgeFromBRC20Input_reselectSpendableNetworkFeeUTXOs + + extraOutputs?: { + address: BitcoinAddress + satsAmount: bigint + }[] } export interface EstimateBridgeTransactionFromBRC20Output { @@ -203,6 +211,7 @@ async function estimateFromBRC20_toStacks( orderData: createdOrder.data, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], }) } @@ -246,6 +255,7 @@ async function estimateFromBRC20_toEVM( orderData: createdOrder.data, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], }) } @@ -303,6 +313,7 @@ async function estimateFromBRC20_toBitcoin( orderData: createdOrder.data, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], }) } @@ -360,6 +371,7 @@ async function estimateFromBRC20_toMeta( orderData: createdOrder.data, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], }) } diff --git a/src/sdkUtils/estimateBridgeTransactionFromBitcoin.ts b/src/sdkUtils/estimateBridgeTransactionFromBitcoin.ts index 372bfe3..5a088fb 100644 --- a/src/sdkUtils/estimateBridgeTransactionFromBitcoin.ts +++ b/src/sdkUtils/estimateBridgeTransactionFromBitcoin.ts @@ -1,4 +1,5 @@ import { + BitcoinAddress, getBitcoinHardLinkageAddress, getBTCPegInAddress, } from "../bitcoinUtils/btcAddresses" @@ -65,6 +66,11 @@ export interface EstimateBridgeTransactionFromBitcoinInput { | SwapRouteViaEVMDexAggregator_WithMinimumAmountsToReceive_Public networkFeeRate: bigint reselectSpendableUTXOs: BridgeFromBitcoinInput_reselectSpendableUTXOs + + extraOutputs?: { + address: BitcoinAddress + satsAmount: bigint + }[] } export interface EstimateBridgeTransactionFromBitcoinOutput { @@ -187,6 +193,7 @@ async function estimateFromBitcoin_toStacks( ...info, orderData: createdOrder.data, withHardLinkageOutput: true, + extraOutputs: info.extraOutputs ?? [], }) } @@ -227,6 +234,7 @@ async function estimateFromBitcoin_toEVM( ...info, orderData: createdOrder.data, withHardLinkageOutput: true, + extraOutputs: info.extraOutputs ?? [], }) } @@ -281,6 +289,7 @@ async function estimateFromBitcoin_toMeta( ...info, orderData: createdOrder.data, withHardLinkageOutput: true, + extraOutputs: info.extraOutputs ?? [], }) } diff --git a/src/sdkUtils/estimateBridgeTransactionFromRunes.ts b/src/sdkUtils/estimateBridgeTransactionFromRunes.ts index 6c9c806..9e3a821 100644 --- a/src/sdkUtils/estimateBridgeTransactionFromRunes.ts +++ b/src/sdkUtils/estimateBridgeTransactionFromRunes.ts @@ -1,4 +1,7 @@ -import { getBitcoinHardLinkageAddress } from "../bitcoinUtils/btcAddresses" +import { + BitcoinAddress, + getBitcoinHardLinkageAddress, +} from "../bitcoinUtils/btcAddresses" import { SDK_NAME } from "../bitcoinUtils/constants" import { getMetaPegInAddress } from "../metaUtils/btcAddresses" import { isSupportedRunesRoute } from "../metaUtils/peggingHelpers" @@ -66,6 +69,11 @@ export interface EstimateBridgeTransactionFromRunesInput { networkFeeChangeAddress: string networkFeeChangeAddressScriptPubKey: Uint8Array reselectSpendableNetworkFeeUTXOs: BridgeFromRunesInput_reselectSpendableNetworkFeeUTXOs + + extraOutputs?: { + address: BitcoinAddress + satsAmount: bigint + }[] } export interface EstimateBridgeTransactionFromRunesOutput { @@ -204,6 +212,7 @@ async function estimateFromRunes_toStacks( orderData: createdOrder.data, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], }) } @@ -247,6 +256,7 @@ async function estimateFromRunes_toEVM( orderData: createdOrder.data, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], }) } @@ -304,6 +314,7 @@ async function estimateFromRunes_toBitcoin( orderData: createdOrder.data, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], }) } @@ -361,6 +372,7 @@ async function estimateFromRunes_toMeta( orderData: createdOrder.data, withHardLinkageOutput: true, bridgeFeeOutput, + extraOutputs: info.extraOutputs ?? [], }) } diff --git a/src/utils/objectHelper.ts b/src/utils/objectHelper.ts index 4c1a463..b40b557 100644 --- a/src/utils/objectHelper.ts +++ b/src/utils/objectHelper.ts @@ -4,3 +4,7 @@ export function entries(obj: T): [keyof T, T[keyof T]][] { export function fromEntries(entries: [keyof T, T[keyof T]][]): T { return Object.fromEntries(entries) as T } + +export function pick(obj: T, keys: K[]): Pick { + return Object.fromEntries(keys.map(key => [key, obj[key]])) as Pick +}