From 23f725bfa90a67763a41740e974460f65cc9e97f Mon Sep 17 00:00:00 2001 From: Facundo Lerena Date: Tue, 29 Apr 2025 16:08:55 -0300 Subject: [PATCH] docs: Moved exampels into code-snippets, adressed comments in #20 --- README.md | 280 ++----------------- examples/code-snippets/bridgeFrom/BRC20.ts | 161 +++++++++++ examples/code-snippets/bridgeFrom/Bitcoin.ts | 146 ++++++++++ examples/code-snippets/bridgeFrom/EVM.ts | 80 ++++++ examples/code-snippets/bridgeFrom/Runes.ts | 189 +++++++++++++ examples/code-snippets/bridgeFrom/Stacks.ts | 80 ++++++ 6 files changed, 672 insertions(+), 264 deletions(-) create mode 100644 examples/code-snippets/bridgeFrom/BRC20.ts create mode 100644 examples/code-snippets/bridgeFrom/Bitcoin.ts create mode 100644 examples/code-snippets/bridgeFrom/EVM.ts create mode 100644 examples/code-snippets/bridgeFrom/Runes.ts create mode 100644 examples/code-snippets/bridgeFrom/Stacks.ts diff --git a/README.md b/README.md index 3389391..b554210 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - [Supported Chains](#supported-chains) - [Mainnet Chains](#mainnet-chains) - [Testnet Chains](#testnet-chains) + - [Helpers](#helpers) - [Supported Tokens](#supported-tokens) - [Retrieve a `TokenId`](#retrieve-a-tokenid) - [Available Routes](#available-routes) @@ -21,11 +22,6 @@ - [`bridgeInfoFrom` methods](#bridgeinfofrom-methods) - [`estimateBridgeTransactionFrom` methods](#estimatebridgetransactionfrom-methods) - [`bridgeFrom` methods](#bridgefrom-methods) - - [Bridge From Stacks](#bridge-from-stacks) - - [Bridge From EVM](#bridge-from-evm) - - [Bridge From Bitcoin](#bridge-from-bitcoin) - - [Bridge From BRC20](#bridge-from-brc20) - - [Bridge From Runes](#bridge-from-runes) - [License](#license) ## Features @@ -56,6 +52,20 @@ For the full API reference, including a full list of available methods and their ### Supported Chains +#### Mainnet Chains + +- **Bitcoin**, **Runes** & **BRC20** +- **Stacks** +- **EVM**: Ethereum, BSC, CoreDAO, Bsquared, BOB, Bitlayer, Lorenzo, Merlin, AILayer, Mode, XLayer, Arbitrum, Aurora, Manta, Linea, Base + +#### Testnet Chains + +- **Bitcoin**, **Runes** & **BRC20** +- **Stacks** +- **EVM**: Sepolia, BSC Testnet, CoreDAO Testnet, Blife Testnet, Bitboy Testnet, Bera Testnet + +#### Helpers + The [`KnownChainId`](https://releases-latest.xlink-sdk.pages.dev/modules/index.KnownChainId) namespace defines types and utility functions for all supported mainnet and testnet networks. Usage example: @@ -76,18 +86,6 @@ KnownChainId.isEVMTestnetChain(KnownChainId.EVM.Sepolia); // Returns true KnownChainId.isEVMMainnetChain(KnownChainId.EVM.Sepolia); // Returns false ``` -#### Mainnet Chains - -- **Bitcoin**, **Runes** & **BRC20** -- **Stacks** -- **EVM**: Ethereum, BSC, CoreDAO, Bsquared, BOB, Bitlayer, Lorenzo, Merlin, AILayer, Mode, XLayer, Arbitrum, Aurora, Manta, Linea, Base - -#### Testnet Chains - -- **Bitcoin**, **Runes** & **BRC20** -- **Stacks** -- **EVM**: Sepolia, BSC Testnet, CoreDAO Testnet, Blife Testnet, Bitboy Testnet, Bera Testnet - > [!NOTE] > Runes and BRC20 metaprotocols are treated as distinct chains within the SDK, even though they share Bitcoin as the underlying blockchain. @@ -225,253 +223,7 @@ Once the route is validated, the cross-chain transfer can be initiated. These me > [!IMPORTANT] > The SDK does not broadcast transactions—it provides the data required to sign and send them. The `sendTransaction` function parameter must be implemented by the developer using their preferred web3 library. The SDK provides the necessary arguments, including contract addresses, function to call and call data. -### Bridge From Stacks - -```ts -import { BridgeFromStacksInput, toSDKNumberOrUndefined, KnownChainId, KnownTokenId } from "@brotocol-xyz/bro-sdk"; -import { makeContractCall, broadcastTransaction, deserializeCV } from "@stacks/transactions"; - -// Define bridge operation input -const bridgeFromStacksInput: BridgeFromStacksInput = { - fromChain: KnownChainId.Stacks.Mainnet, - toChain: KnownChainId.EVM.Ethereum, - fromToken: stacksToken as KnownTokenId.StacksToken, - toToken: evmToken as KnownTokenId.EVMToken, - // Sender Stacks principal - fromAddress: "SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE", - // Receiver EVM address - toAddress: "0x31751a152F1e95F966C041291644129144233b0B", - amount: toSDKNumberOrUndefined(100), - sendTransaction: async tx => { - /** - * Implementation for sending transaction on Stacks mainnet. - * Refer to: - * - https://github.com/hirosystems/stacks.js/tree/main/packages/transactions#smart-contract-function-call - * - https://stacks.js.org/functions/_stacks_transactions.makeContractCall - * - https://stacks.js.org/functions/_stacks_transactions.broadcastTransaction - */ - const transaction = await makeContractCall({ - contractAddress: tx.contractAddress, - contractName: tx.contractName, - functionName: tx.functionName, - // Deserialize each element of functionArgs and convert it into ClarityValue - functionArgs: tx.functionArgs.map(arg => deserializeCV(arg)), - postConditions: [] /* Add post conditions */, - validateWithAbi: true, - senderKey: "b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01", - network: "mainnet", - }); - - const broadcastResponse = await broadcastTransaction({ transaction, network: "mainnet" }); - return { txid: broadcastResponse.txid }; - }; -}; - -// Perform the bridge operation -const result = await sdk.bridgeFromStacks(bridgeFromStacksInput); -console.log("Stacks Transaction ID:", result.txid); -``` - -> [!NOTE] -> The `tx.functionArgs` field provided by the SDK is of type `SerializedClarityValue[]`. -> This differs from the `functionArgs` expected by the Stacks.js `makeContractCall` method, which requires `ClarityValue[]`. -> -> This design decision was intentional to maintain compatibility with both [Stacks.js](https://github.com/hirosystems/stacks.js) v6 and v7, as the implementation details of `ClarityValue` type differ between both versions. By using a serialized format, the SDK ensures flexibility and avoids locking users into a specific version of the Stacks.js library. - -### Bridge From EVM - -> [!IMPORTANT] -> Before initiating a bridge transaction, ensure that you have approved the Bridge Endpoint contract to spend `amount` tokens from `fromAddress`. Without this approval, the transaction will fail. - -```ts -import { BridgeFromEVMInput, EVMAddress, SDKNumber, KnownChainId, KnownTokenId } from "@brotocol-xyz/bro-sdk"; -// Choose your preferred web3 lib here -import { ethers } from "ethers"; - -// Example signer setup using ethers.js -const provider = new ethers.JsonRpcProvider("https://mainnet.someprovider.io/YOUR_PROJECT_ID"); -const signer = new ethers.Wallet("000000000000000000000000000000000000000000000000000000000000002d", provider); -const signerAddress = signer.address as `0x${string}`; - -const bridgeFromEVMInput: BridgeFromEVMInput = { - fromChain: KnownChainId.EVM.Ethereum, - toChain: KnownChainId.Stacks.Mainnet, - fromToken: evmToken as KnownTokenId.EVMToken, - toToken: stacksToken as KnownTokenId.StacksToken, - // Sender Ethereum address - fromAddress: signerAddress, - // Receiver Stacks principal - toAddress: "SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE", - amount: toSDKNumberOrUndefined(100), - sendTransaction: async (tx: { - from: EVMAddress; // Sender Ethereum address - to: EVMAddress; // Bridge Endpoint address - data: Uint8Array; - recommendedGasLimit: SDKNumber; - value?: SDKNumber; - }): Promise<{ txHash: string }> => { - /** - * Implementation for sending transaction on Ethereum mainnet - * See https://docs.ethers.org/v6/ for reference - */ - const txRequest = { - from: tx.from, - to: tx.to, - data: ethers.hexlify(tx.data), - // Convert SDKNumber into BigNumber - gasLimit: ethers.toBigInt(tx.recommendedGasLimit.split(" ")[0]), - }; - - const sentTx = await signer.sendTransaction(txRequest); - const receipt = await sentTx.wait(); - if (receipt === null) throw new Error("Transaction receipt is null"); - return { txHash: receipt.hash }; - }, -}; - -// Perform the bridge operation -const result = await sdk.bridgeFromEVM(bridgeFromEVMInput); -console.log("Ethereum Transaction ID:", result.txHash); -``` - -### Bridge From Bitcoin - -The following functions must be implemented by developer: - -- `reselectSpendableUTXOs`: Selects UTXOs from the sender wallet. -- `signPsbt`: Signs the PSBT (Partially Signed Bitcoin Transaction). -- `sendTransaction`: Broadcasts the final transaction to the Bitcoin network. - -```ts -import { BridgeFromBitcoinInput, KnownChainId, KnownTokenId } from "@brotocol-xyz/bro-sdk"; -import { - GetConfirmedSpendableUTXOFn, - reselectSpendableUTXOsFactory, - UTXOBasic, -} from "@brotocol-xyz/bro-sdk/bitcoinHelpers"; -import { ECPairFactory } from "ecpair"; -import { Psbt, payments, Signer, networks } from "bitcoinjs-lib"; -import axios from "axios"; -import { randomBytes } from "crypto"; -import * as ecc from "tiny-secp256k1"; - -const rng = (size?: number) => { - const buffer = randomBytes(size || 0); - return Buffer.from(buffer); -}; - -// Create ECPair instance -const ECPair = ECPairFactory(ecc); - -// Generate a new random key pair -const keyPair = ECPair.makeRandom({ rng }); - -// Get sender address and scriptPubKey -const { address: senderAddress, output: scriptPubKey } = payments.p2wpkh({ - pubkey: Buffer.from(keyPair.publicKey), - network: networks.bitcoin, -}); - -// Select UTXOs to spend -const reselectSpendableUTXOs: BridgeFromBitcoinInput["reselectSpendableUTXOs"] = - async (satsToSend, lastTimeSelectedUTXOs) => { - /** - * This function should fetch UTXOs from a Bitcoin node or API. - * Replace the placeholder logic below with your actual UTXO source. - */ - const availableUTXOs: UTXOBasic[] = []; - const getUTXOSpendable: GetConfirmedSpendableUTXOFn = async ( - utxo: UTXOBasic, - ) => { - // Placeholder for implementation - It should return a valid UTXOSpendable & UTXOConfirmed object - return undefined; - }; - - // Create the reselect function with factory helper - const reselectFn = reselectSpendableUTXOsFactory( - availableUTXOs, - getUTXOSpendable, - ); - - return reselectFn(satsToSend, lastTimeSelectedUTXOs); - }; - -// Sign a Bitcoin PSBT -const signPsbt: BridgeFromBitcoinInput["signPsbt"] = async tx => { - /** - * Implementation example for signing a Bitcoin PSBT (Partially Signed Bitcoin Transaction) - */ - const signer: Signer = { - publicKey: Buffer.from(keyPair.publicKey), - sign: hash => Buffer.from(keyPair.sign(hash)), - }; - const psbt = Psbt.fromBuffer(Buffer.from(tx.psbt)); - tx.signInputs.forEach(index => { - psbt.signInput(index, signer); - }); - psbt.finalizeAllInputs(); - return { psbt: psbt.toBuffer() }; -}; - -// Broadcast the signed transaction -const sendTransaction: BridgeFromBitcoinInput["sendTransaction"] = async tx => { - /** - * Implementation example for broadcasting a Bitcoin transaction with Axios - */ - const response = await axios.post("https://blockstream.info/api/tx", tx.hex, { - headers: { "Content-Type": "text/plain" }, - }); - return { txid: response.data }; -}; - -// Estimate transaction fee and virtual size before performing the bridge operation -const estimateTransaction = await sdk.estimateBridgeTransactionFromBitcoin({ - fromChain: KnownChainId.Bitcoin.Mainnet, - fromToken: KnownTokenId.Bitcoin.BTC, - toChain: KnownChainId.EVM.Ethereum, - toToken: evmToken as KnownTokenId.EVMToken, - fromAddressScriptPubKey: scriptPubKey!, - fromAddress: senderAddress!, - toAddress: "0x31751a152F1e95F966C041291644129144233b0B", - amount: toSDKNumberOrUndefined(1), - networkFeeRate: 10n, // Expressed in satoshis per virtual byte (sat/vbyte). - reselectSpendableUTXOs: reselectSpendableUTXOs, -}); - -const bridgeFromBitcoinInput: BridgeFromBitcoinInput = { - fromChain: KnownChainId.Bitcoin.Mainnet, - fromToken: KnownTokenId.Bitcoin.BTC, - toChain: KnownChainId.EVM.Ethereum, - toToken: evmToken as KnownTokenId.EVMToken, - fromAddressScriptPubKey: scriptPubKey!, - fromAddress: senderAddress!, - toAddress: "0x31751a152F1e95F966C041291644129144233b0B", - amount: toSDKNumberOrUndefined(1), - networkFeeRate: 10n, - reselectSpendableUTXOs, - signPsbt, - sendTransaction, -}; - -// Perform the bridge operation -const result = await sdk.bridgeFromBitcoin(bridgeFromBitcoinInput); -console.log("Bitcoin Transaction ID:", result.txid); -``` - -### Bridge From BRC20 - -Comming soon. - - - -### Bridge From Runes - -Comming soon. - +Examples on how to use the `bridgeFrom` methods can be found in the [examples folder](examples/code-snippets/bridgeFrom/). ## License diff --git a/examples/code-snippets/bridgeFrom/BRC20.ts b/examples/code-snippets/bridgeFrom/BRC20.ts new file mode 100644 index 0000000..250f689 --- /dev/null +++ b/examples/code-snippets/bridgeFrom/BRC20.ts @@ -0,0 +1,161 @@ +// Bridge From BRC20 + +import { + GetConfirmedSpendableUTXOFn, + reselectSpendableUTXOsFactory, + UTXOBasic, +} from "../../../src/bitcoinHelpers" +import { + BroSDK, + KnownTokenId, + KnownChainId, + BridgeFromBRC20Input, +} from "../../../src/index" + +import { Psbt, payments, networks } from "bitcoinjs-lib" +import axios from "axios" + +const sdk = new BroSDK() + +// For BRC20 provide the tick symbol +const brc20Token: KnownTokenId.BRC20Token = (await sdk.brc20TickToBRC20Token( + KnownChainId.BRC20.Mainnet, + "alex$", +))! + +// For EVM tokens provide the contract address +const evmToken: KnownTokenId.EVMToken = (await sdk.evmAddressToEVMToken( + KnownChainId.EVM.Ethereum, + "0x31761a152F1e96F966C041291644129144233b0B", +))! + +// Mock functions for key management +const getPublicKey = async (): Promise => { + // This is a mock implementation + // In a real implementation, this would return the user's public key + return "02a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7" +} + +const signTx = async (hex: string): Promise => { + // This is a mock implementation + // In a real implementation, this would sign the transaction with the user's private key + return "mock_signature" +} + +const publicKey = await getPublicKey() +const { address: senderAddress, output: scriptPubKey } = payments.p2wpkh({ + pubkey: Buffer.from(publicKey, "hex"), + network: networks.bitcoin, +}) + +// Select UTXOs to spend +const reselectSpendableNetworkFeeUTXOs: BridgeFromBRC20Input["reselectSpendableNetworkFeeUTXOs"] = + async (satsToSend, lastTimeSelectedUTXOs) => { + // Example of available UTXOs from a Bitcoin node or API + const availableUTXOs: UTXOBasic[] = [ + { + txId: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + index: 0, + amount: 5000n, + }, + { + txId: "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + index: 1, + amount: 3000n, + }, + { + txId: "7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456", + index: 2, + amount: 2000n, + }, + ] + + const getUTXOSpendable: GetConfirmedSpendableUTXOFn = async ( + utxo: UTXOBasic, + ) => { + // For this example, we'll create a simple UTXOSpendable object + return { + ...utxo, + scriptPubKey: scriptPubKey!, + addressType: "p2wpkh", + blockHeight: 800000n, // Example block height + } + } + + // Create the reselect function with factory helper + const reselectFn = reselectSpendableUTXOsFactory( + availableUTXOs, + getUTXOSpendable, + ) + + return reselectFn(satsToSend, lastTimeSelectedUTXOs) + } + +const sendTransaction: BridgeFromBRC20Input["sendTransaction"] = async tx => { + /** + * Implementation example for broadcasting a Bitcoin transaction with Axios + */ + const response = await axios.post("https://blockstream.info/api/tx", tx.hex, { + headers: { "Content-Type": "text/plain" }, + }) + return { txid: response.data } +} + +const bridgeFromBRC20Input: BridgeFromBRC20Input = { + fromChain: KnownChainId.BRC20.Mainnet, + fromToken: brc20Token, + toChain: KnownChainId.EVM.Ethereum, + toToken: evmToken, + fromAddress: senderAddress!, + fromAddressScriptPubKey: scriptPubKey!, + toAddress: "0x31751a152F1e95F966C041291644129144233b0B", + inputInscriptionUTXO: { + txId: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + index: 0, + amount: 1000n, + scriptPubKey: scriptPubKey!, + addressType: "p2wpkh", + }, + networkFeeRate: 10n, + reselectSpendableNetworkFeeUTXOs, + networkFeeChangeAddress: senderAddress!, + networkFeeChangeAddressScriptPubKey: scriptPubKey!, + signPsbt: async tx => { + const psbt = Psbt.fromBuffer(Buffer.from(tx.psbt)) + + for (const index of tx.signBitcoinInputs) { + const signature = await signTx(psbt.toHex()) + + // Add the signature to the PSBT + psbt.updateInput(index, { + partialSig: [ + { + pubkey: Buffer.from(await getPublicKey(), "hex"), + signature: Buffer.from(signature, "hex"), + }, + ], + }) + } + + for (const index of tx.signInscriptionInputs) { + const signature = await signTx(psbt.toHex()) + + // Add the signature to the PSBT + psbt.updateInput(index, { + partialSig: [ + { + pubkey: Buffer.from(await getPublicKey(), "hex"), + signature: Buffer.from(signature, "hex"), + }, + ], + }) + } + + psbt.finalizeAllInputs() + return { psbt: psbt.toBuffer() } + }, + sendTransaction, +} + +const result = await sdk.bridgeFromBRC20(bridgeFromBRC20Input) +console.log("Bitcoin Transaction ID:", result.txid) diff --git a/examples/code-snippets/bridgeFrom/Bitcoin.ts b/examples/code-snippets/bridgeFrom/Bitcoin.ts new file mode 100644 index 0000000..b06be88 --- /dev/null +++ b/examples/code-snippets/bridgeFrom/Bitcoin.ts @@ -0,0 +1,146 @@ +// Bridge From BTC + +import { + GetConfirmedSpendableUTXOFn, + reselectSpendableUTXOsFactory, + UTXOBasic, +} from "../../../src/bitcoinHelpers" +import { + BroSDK, + KnownTokenId, + KnownChainId, + toSDKNumberOrUndefined, + BridgeFromBitcoinInput, +} from "../../../src/index" +import { Psbt, payments, Signer, networks } from "bitcoinjs-lib" +import axios from "axios" + +const sdk = new BroSDK() + +// For EVM tokens provide the contract address +const evmToken = await sdk.evmAddressToEVMToken( + KnownChainId.EVM.Ethereum, + "0x31761a152F1e96F966C041291644129144233b0B", +) + +// Mock functions for key management +// In a real implementation, these would be provided by the user's environment +const getPublicKey = async (): Promise => { + // This is a mock implementation + // In a real implementation, this would return the user's public key + return "02a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7" +} + +const signTx = async (hex: string): Promise => { + // This is a mock implementation + // In a real implementation, this would sign the transaction with the user's private key + return "mock_signature" +} + +// Get address and scriptPubKey +const publicKey = await getPublicKey() +const { address: senderAddress, output: scriptPubKey } = payments.p2wpkh({ + pubkey: Buffer.from(publicKey, "hex"), + network: networks.bitcoin, +}) + +// Select UTXOs to spend +const reselectSpendableUTXOs: BridgeFromBitcoinInput["reselectSpendableUTXOs"] = + async (satsToSend, lastTimeSelectedUTXOs) => { + /** + * This function should fetch UTXOs from a Bitcoin node or API. + * Replace the placeholder logic below with your actual UTXO source. + */ + const availableUTXOs: UTXOBasic[] = [] + const getUTXOSpendable: GetConfirmedSpendableUTXOFn = async ( + utxo: UTXOBasic, + ) => { + // Placeholder for implementation - It should return a valid UTXOSpendable & UTXOConfirmed object + return undefined + } + + // Create the reselect function with factory helper + const reselectFn = reselectSpendableUTXOsFactory( + availableUTXOs, + getUTXOSpendable, + ) + + return reselectFn(satsToSend, lastTimeSelectedUTXOs) + } + +// Sign a Bitcoin PSBT +const signPsbt: BridgeFromBitcoinInput["signPsbt"] = async tx => { + /** + * Implementation example for signing a Bitcoin PSBT (Partially Signed Bitcoin Transaction) + */ + const psbt = Psbt.fromBuffer(Buffer.from(tx.psbt)) + + // For each input that needs to be signed + for (const index of tx.signInputs) { + // Get the input's sighash + const input = psbt.data.inputs[index] + const sighash = input.sighashType + + // Sign the transaction using the mocked signTx function + const signature = await signTx(psbt.toHex()) + + // Add the signature to the PSBT + psbt.updateInput(index, { + partialSig: [ + { + pubkey: Buffer.from(await getPublicKey(), "hex"), + signature: Buffer.from(signature, "hex"), + }, + ], + }) + } + + psbt.finalizeAllInputs() + return { psbt: psbt.toBuffer() } +} + +// Broadcast the signed transaction +const sendTransaction: BridgeFromBitcoinInput["sendTransaction"] = async tx => { + /** + * Implementation example for broadcasting a Bitcoin transaction with Axios + */ + const response = await axios.post("https://blockstream.info/api/tx", tx.hex, { + headers: { "Content-Type": "text/plain" }, + }) + return { txid: response.data } +} + +// Estimate transaction fee and virtual size before performing the bridge operation +const estimateTransaction = await sdk.estimateBridgeTransactionFromBitcoin({ + fromChain: KnownChainId.Bitcoin.Mainnet, + fromToken: KnownTokenId.Bitcoin.BTC, + toChain: KnownChainId.EVM.Ethereum, + toToken: evmToken as KnownTokenId.EVMToken, + fromAddressScriptPubKey: scriptPubKey!, + fromAddress: senderAddress!, + toAddress: "0x31751a152F1e95F966C041291644129144233b0B", + amount: toSDKNumberOrUndefined(1), + networkFeeRate: 10n, + reselectSpendableUTXOs: reselectSpendableUTXOs, +}) + +console.log("Estimated Transaction: ", estimateTransaction) + +const bridgeFromBitcoinInput: BridgeFromBitcoinInput = { + fromChain: KnownChainId.Bitcoin.Mainnet, + fromToken: KnownTokenId.Bitcoin.BTC, + toChain: KnownChainId.EVM.Ethereum, + toToken: evmToken as KnownTokenId.EVMToken, + fromAddressScriptPubKey: scriptPubKey!, + fromAddress: senderAddress!, + toAddress: "0x31751a152F1e95F966C041291644129144233b0B", + amount: toSDKNumberOrUndefined(1), + networkFeeRate: 10n, // Expressed in satoshis per virtual byte (sat/vbyte). + reselectSpendableUTXOs, + signPsbt, + sendTransaction, +} + +// Perform the bridge operation +const result = await sdk.bridgeFromBitcoin(bridgeFromBitcoinInput) +console.log("Bitcoin Transaction ID:", result.txid) diff --git a/examples/code-snippets/bridgeFrom/EVM.ts b/examples/code-snippets/bridgeFrom/EVM.ts new file mode 100644 index 0000000..d72262b --- /dev/null +++ b/examples/code-snippets/bridgeFrom/EVM.ts @@ -0,0 +1,80 @@ +// Bridge From EVM + +import { + BroSDK, + KnownTokenId, + KnownChainId, + toSDKNumberOrUndefined, + BridgeFromEVMInput, + EVMAddress, + SDKNumber, + formatSDKNumber, +} from "../../../src/index" + +// Choose your preferred web3 lib here +import { ethers } from "ethers" + +const sdk = new BroSDK() + +// For Stacks provide the contract address +const stacksToken: KnownTokenId.StacksToken = + (await sdk.stacksAddressToStacksToken(KnownChainId.Stacks.Mainnet, { + deployerAddress: "SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK", + contractName: "token-abtc", + }))! + +// For EVM tokens provide the contract address +const evmToken: KnownTokenId.EVMToken = (await sdk.evmAddressToEVMToken( + KnownChainId.EVM.Ethereum, + "0x31761a152F1e96F966C041291644129144233b0B", +))! + +// Example signer setup using ethers.js +const provider = new ethers.JsonRpcProvider( + "https://mainnet.someprovider.io/YOUR_PROJECT_ID", +) +const signer = new ethers.Wallet( + "000000000000000000000000000000000000000000000000000000000000002d", + provider, +) +const signerAddress = signer.address as `0x${string}` + +const bridgeFromEVMInput: BridgeFromEVMInput = { + fromChain: KnownChainId.EVM.Ethereum, + toChain: KnownChainId.Stacks.Mainnet, + fromToken: evmToken, + toToken: stacksToken, + // Sender Ethereum address + fromAddress: signerAddress, + // Receiver Stacks principal + toAddress: "SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE", + amount: toSDKNumberOrUndefined(100), + sendTransaction: async (tx: { + from: EVMAddress // Sender Ethereum address + to: EVMAddress // Bridge Endpoint address + data: Uint8Array + recommendedGasLimit: SDKNumber + value?: SDKNumber + }): Promise<{ txHash: string }> => { + /** + * Implementation for sending transaction on Ethereum mainnet + * See https://docs.ethers.org/v6/api/contract/ for reference + */ + const txRequest = { + from: tx.from, + to: tx.to, + data: ethers.hexlify(tx.data), + // Convert SDKNumber into BigNumber + gasLimit: formatSDKNumber(tx.recommendedGasLimit), + } + + const sentTx = await signer.sendTransaction(txRequest) + const receipt = await sentTx.wait() + if (receipt === null) throw new Error("Transaction receipt is null") + return { txHash: receipt.hash } + }, +} + +// Perform the bridge operation +const result = await sdk.bridgeFromEVM(bridgeFromEVMInput) +console.log("Transaction ID:", result.txHash) diff --git a/examples/code-snippets/bridgeFrom/Runes.ts b/examples/code-snippets/bridgeFrom/Runes.ts new file mode 100644 index 0000000..78de73f --- /dev/null +++ b/examples/code-snippets/bridgeFrom/Runes.ts @@ -0,0 +1,189 @@ +// Bridge From Runes +import { + GetConfirmedSpendableUTXOFn, + reselectSpendableUTXOsFactory, + UTXOBasic, +} from "../../../src/bitcoinHelpers" +import { + BroSDK, + KnownTokenId, + KnownChainId, + toSDKNumberOrUndefined, + BridgeFromRunesInput, + RunesUTXOSpendable, +} from "../../../src/index" +import { Psbt, payments, networks } from "bitcoinjs-lib" +import axios from "axios" + +const sdk = new BroSDK() + +// For Runes provide the runes ID +const runesToken: KnownTokenId.RunesToken = (await sdk.runesIdToRunesToken( + KnownChainId.Runes.Mainnet, + "500:20", +))! + +// For EVM tokens provide the contract address +const evmToken: KnownTokenId.EVMToken = (await sdk.evmAddressToEVMToken( + KnownChainId.EVM.Ethereum, + "0x31761a152F1e96F966C041291644129144233b0B", +))! + +// Mock functions for key management +// In a real implementation, these would be provided by the user's environment +const getPublicKey = async (): Promise => { + // This is a mock implementation + // In a real implementation, this would return the user's public key + return "02a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7" +} + +const signTx = async (hex: string): Promise => { + // This is a mock implementation + // In a real implementation, this would sign the transaction with the user's private key + return "mock_signature" +} + +// Get address and scriptPubKey +const publicKey = await getPublicKey() +const { address: senderAddress, output: scriptPubKey } = payments.p2wpkh({ + pubkey: Buffer.from(publicKey, "hex"), + network: networks.bitcoin, +}) + +// Select UTXOs to spend for network fees +const reselectSpendableNetworkFeeUTXOsForRunes: BridgeFromRunesInput["reselectSpendableNetworkFeeUTXOs"] = + async (satsToSend, lastTimeSelectedUTXOs) => { + // Example of available UTXOs from a Bitcoin node or API + const availableUTXOs: UTXOBasic[] = [ + { + txId: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + index: 0, + amount: 5000n, + }, + { + txId: "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + index: 1, + amount: 3000n, + }, + { + txId: "7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456", + index: 2, + amount: 2000n, + }, + ] + + // Function to convert basic UTXOs to spendable UTXOs with scriptPubKey + const getUTXOSpendable: GetConfirmedSpendableUTXOFn = async ( + utxo: UTXOBasic, + ) => { + // For this example, we'll create a simple UTXOSpendable object + return { + ...utxo, + scriptPubKey: scriptPubKey!, + addressType: "p2wpkh", + blockHeight: 800000n, // Example block height + } + } + + // Create the reselect function with factory helper + const reselectFn = reselectSpendableUTXOsFactory( + availableUTXOs, + getUTXOSpendable, + ) + + return reselectFn(satsToSend, lastTimeSelectedUTXOs) + } + +// Example Runes UTXOs +const runesUTXOs: RunesUTXOSpendable[] = [ + { + txId: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + index: 0, + amount: 1000n, + scriptPubKey: scriptPubKey!, + addressType: "p2wpkh", + runes: [ + { + runeId: "500:20", + runeDivisibility: 8, + runeAmount: 100000000n, // 1.0 rune + }, + ], + }, +] + +// Sign a Bitcoin PSBT for Runes +const signPsbtForRunes: BridgeFromRunesInput["signPsbt"] = async tx => { + const psbt = Psbt.fromBuffer(Buffer.from(tx.psbt)) + + // For each Bitcoin input that needs to be signed + for (const index of tx.signBitcoinInputs) { + // Sign the transaction using the mocked signTx function + const signature = await signTx(psbt.toHex()) + + // Add the signature to the PSBT + psbt.updateInput(index, { + partialSig: [ + { + pubkey: Buffer.from(await getPublicKey(), "hex"), + signature: Buffer.from(signature, "hex"), + }, + ], + }) + } + + // For each Runes input that needs to be signed + for (const index of tx.signRunesInputs) { + // Sign the transaction using the mocked signTx function + const signature = await signTx(psbt.toHex()) + + // Add the signature to the PSBT + psbt.updateInput(index, { + partialSig: [ + { + pubkey: Buffer.from(await getPublicKey(), "hex"), + signature: Buffer.from(signature, "hex"), + }, + ], + }) + } + + psbt.finalizeAllInputs() + return { psbt: psbt.toBuffer() } +} + +// Broadcast the signed transaction +const sendTransactionForRunes: BridgeFromRunesInput["sendTransaction"] = + async tx => { + const response = await axios.post( + "https://blockstream.info/api/tx", + tx.hex, + { + headers: { "Content-Type": "text/plain" }, + }, + ) + return { txid: response.data } + } + +// Create the bridge input +const bridgeFromRunesInput: BridgeFromRunesInput = { + fromChain: KnownChainId.Runes.Mainnet, + fromToken: runesToken, + toChain: KnownChainId.EVM.Ethereum, + toToken: evmToken, + fromAddress: senderAddress!, + fromAddressScriptPubKey: scriptPubKey!, + toAddress: "0x31751a152F1e95F966C041291644129144233b0B", + amount: toSDKNumberOrUndefined(1), // 1.0 rune + inputRuneUTXOs: runesUTXOs, + networkFeeRate: 10n, + reselectSpendableNetworkFeeUTXOs: reselectSpendableNetworkFeeUTXOsForRunes, + networkFeeChangeAddress: senderAddress!, + networkFeeChangeAddressScriptPubKey: scriptPubKey!, + signPsbt: signPsbtForRunes, + sendTransaction: sendTransactionForRunes, +} + +// Perform the bridge operation +const result = await sdk.bridgeFromRunes(bridgeFromRunesInput) +console.log("Bitcoin Transaction ID:", result.txid) diff --git a/examples/code-snippets/bridgeFrom/Stacks.ts b/examples/code-snippets/bridgeFrom/Stacks.ts new file mode 100644 index 0000000..9d1cd54 --- /dev/null +++ b/examples/code-snippets/bridgeFrom/Stacks.ts @@ -0,0 +1,80 @@ +// Bridge From Stacks +import { + BroSDK, + KnownTokenId, + KnownChainId, + BridgeFromStacksInput, + toSDKNumberOrUndefined, +} from "../../../src/index" +import { + makeContractCall, + broadcastTransaction, + deserializeCV, +} from "@stacks/transactions" +import { ContractCallOptions } from "../../../src/stacksUtils/contractHelpers" + +const sdk = new BroSDK() + +// For Stacks provide the contract address +const stacksToken: KnownTokenId.StacksToken = + (await sdk.stacksAddressToStacksToken(KnownChainId.Stacks.Mainnet, { + deployerAddress: "SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK", + contractName: "token-abtc", + }))! + +// For EVM tokens provide the contract address +const evmToken: KnownTokenId.EVMToken = (await sdk.evmAddressToEVMToken( + KnownChainId.EVM.Ethereum, + "0x31761a152F1e96F966C041291644129144233b0B", +))! + +const bridgeInfo = await sdk.bridgeInfoFromStacks({ + fromChain: KnownChainId.Stacks.Mainnet, + toChain: KnownChainId.EVM.Ethereum, + fromToken: stacksToken, + toToken: evmToken, + amount: toSDKNumberOrUndefined(100_000_000), +}) + +console.log("Bridge Info:", bridgeInfo) + +const bridgeFromStacksInput: BridgeFromStacksInput = { + fromChain: KnownChainId.Stacks.Mainnet, + toChain: KnownChainId.EVM.Ethereum, + fromToken: stacksToken, + toToken: evmToken, + fromAddress: "SP2ZD731ANQZT6J4K3F5N8A40ZXWXC1XFXHVVQFKE", + // Receiver EVM address + toAddress: "0x31751a152F1e95F966C041291644129144233b0B", + amount: toSDKNumberOrUndefined(100), + sendTransaction: async (tx: ContractCallOptions) => { + /** + * Implementation for sending transaction on Stacks mainnet. + * Refer to: + * - https://github.com/hirosystems/stacks.js/tree/main/packages/transactions#smart-contract-function-call + * - https://stacks.js.org/functions/_stacks_transactions.makeContractCall + * - https://stacks.js.org/functions/_stacks_transactions.broadcastTransaction + */ + const transaction = await makeContractCall({ + contractAddress: tx.contractAddress, + contractName: tx.contractName, + functionName: tx.functionName, + // Deserialize each element of functionArgs and convert it into ClarityValue[] + functionArgs: tx.functionArgs.map(arg => deserializeCV(arg)), + postConditions: [] /* Add post conditions */, + validateWithAbi: true, + senderKey: + "b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01", + network: "mainnet", + }) + + const broadcastResponse = await broadcastTransaction({ + transaction, + network: "mainnet", + }) + return { txid: broadcastResponse.txid } + }, +} + +const result = await sdk.bridgeFromStacks(bridgeFromStacksInput) +console.log("Transaction ID:", result.txid)