mirror of
https://github.com/Brotocol-xyz/bro-sdk.git
synced 2026-01-12 06:44:18 +08:00
docs: Moved exampels into code-snippets, adressed comments in #20
This commit is contained in:
280
README.md
280
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.
|
||||
|
||||
<!-- The following functions must be implemented by developer:
|
||||
|
||||
- `reselectSpendableNetworkFeeUTXOs`: Selects UTXOs from the sender wallet.
|
||||
- `signPsbt`: Signs the PSBT (Partially Signed Bitcoin Transaction).
|
||||
- `sendTransaction`: Broadcasts the final transaction to the Bitcoin network. -->
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
161
examples/code-snippets/bridgeFrom/BRC20.ts
Normal file
161
examples/code-snippets/bridgeFrom/BRC20.ts
Normal file
@@ -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<string> => {
|
||||
// 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<string> => {
|
||||
// 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)
|
||||
146
examples/code-snippets/bridgeFrom/Bitcoin.ts
Normal file
146
examples/code-snippets/bridgeFrom/Bitcoin.ts
Normal file
@@ -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<string> => {
|
||||
// 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<string> => {
|
||||
// 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)
|
||||
80
examples/code-snippets/bridgeFrom/EVM.ts
Normal file
80
examples/code-snippets/bridgeFrom/EVM.ts
Normal file
@@ -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)
|
||||
189
examples/code-snippets/bridgeFrom/Runes.ts
Normal file
189
examples/code-snippets/bridgeFrom/Runes.ts
Normal file
@@ -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<string> => {
|
||||
// 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<string> => {
|
||||
// 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)
|
||||
80
examples/code-snippets/bridgeFrom/Stacks.ts
Normal file
80
examples/code-snippets/bridgeFrom/Stacks.ts
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user