Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 1f428b1

Browse files
authored
feat(bitcoin): Added support for Bitcoin transfers (#389)
<!--- Provide a general summary of your changes in the Title above --> ## Description <!--- Describe your changes in detail --> This PR adds integration of BTC transfer into the SDK. It allows developers to either use P2WPKH or P2TR address to relay funds from BTC to EVM. It also ads some small utility functions to broadcast transactions into the network. ## Related Issue Or Context <!--- If suggesting a new feature or change, please discuss it in an issue first --> <!--- If fixing a bug, there should be an issue describing it with steps to reproduce --> <!--- Otherwise, describe context and motivation for change herre --> Closes: #369 #411 ## How Has This Been Tested? Testing details. <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> ## Types of changes <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation ## Checklist: <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [ ] I have commented my code, particularly in hard-to-understand areas. - [ ] I have ensured that all acceptance criteria (or expected behavior) from issue are met - [ ] I have updated the documentation locally and in chainbridge-docs. - [ ] I have added tests to cover my changes. - [ ] I have ensured that all the checks are passing and green, I've signed the CLA bot
1 parent fd80266 commit 1f428b1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2102
-73
lines changed

.github/workflows/release-bitcoin.yml

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Publish Sygma SDK Bitcoin package to GitHub Package Registry
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
paths: ["packages/bitcoin/**"]
7+
8+
jobs:
9+
maybe-release:
10+
name: release
11+
runs-on: ubuntu-latest
12+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
13+
steps:
14+
# you should probably do this after your regular CI checks passes
15+
# it will analyze commits and create PR with new version and updated CHANGELOG:md file. On merging it will create github release page with changelog
16+
- uses: google-github-actions/release-please-action@v3
17+
id: release
18+
with:
19+
command: manifest
20+
release-type: node
21+
token: ${{secrets.RELEASE_TOKEN}}
22+
config-file: "release-please/rp-bitcoin-config.json"
23+
manifest-file: "release-please/rp-bitcoin-manifest.json"
24+
monorepo-tags: true
25+
default-branch: main
26+
path: "packages/bitcoin"
27+
changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":false},{"type":"revert","hidden":true}]'
28+
29+
- uses: actions/checkout@v4
30+
# these if statements ensure that a publication only occurs when
31+
# a new release is created:
32+
if: ${{ steps.release.outputs.releases_created }}
33+
34+
- uses: actions/setup-node@v4
35+
with:
36+
cache: "yarn"
37+
node-version: 18
38+
registry-url: "https://registry.npmjs.org"
39+
scope: "@buildwithsygma"
40+
env:
41+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
42+
if: ${{ steps.release.outputs.releases_created }}
43+
44+
- name: Enable corepack
45+
run: corepack enable
46+
if: ${{ steps.release.outputs.releases_created }}
47+
48+
- name: Install dependencies
49+
run: yarn install --immutable
50+
if: ${{ steps.release.outputs.releases_created }}
51+
52+
- run: yarn build
53+
if: ${{ steps.release.outputs.releases_created }}
54+
55+
- env:
56+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
57+
if: ${{ steps.release.outputs.releases_created }}
58+
run: |
59+
echo -e "\nnpmAuthToken: \"$NODE_AUTH_TOKEN\"" >> ./.yarnrc.yml
60+
61+
- run: yarn workspace @buildwithsygma/bitcoin npm publish --access public
62+
if: ${{ steps.release.outputs.releases_created }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
SYGMA_ENV=testnet
2+
BLOCKSTREAM_URL="your blockstream url"
3+
DESTINATION_ADDRESS="your evm destination address"
4+
RESOURCE_ID="resource id"
5+
SOURCE_CAIPID="source domain caip id"
6+
EXPLORER_URL="your bitcoin explorer url"
7+
MNEMONIC="your 12 or 24 mnemonic"
8+
DERIVATION_PATH="your derivation path"
9+
ADDRESS="your bitcoin address to use and send change"
10+
AMOUNT="your amount to transfer"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Sygma SDK Bitcoin to EVM example
2+
3+
## Sygma SDK ERC20 Example
4+
5+
This is an example that demonstrates functionality of the protocol using Sygma SDK. The src/transfer.ts script showcases how bitcoins can transferred over to an EVM Sepolia address utilizing `@buildwithsygma/bitcoin` package.
6+
7+
## Prerequisites
8+
9+
Before running the script, ensure that you have the following:
10+
11+
- Node.js
12+
- Yarn (version 3.4.1 or higher)
13+
- A development wallet with some testnet BTC
14+
- The private key of a Taproot address to sign the transaction
15+
- Valid UTXO information of your taproot address. You can get the UTXO's of your address by querying some public APIS like blockstream one and passing your address as parameter
16+
17+
## Getting started
18+
19+
### 1. Clone the repository
20+
21+
To get started, clone this repository to your local machine with:
22+
23+
```bash
24+
git clone [email protected]:sygmaprotocol/sygma-sdk.git
25+
cd sygma-sdk/
26+
```
27+
28+
### 2. Install dependencies
29+
30+
Install the project dependencies by running:
31+
32+
```bash
33+
yarn install
34+
```
35+
36+
### 3. Build the sdk
37+
38+
To start the example you need to build the sdk first with:
39+
40+
```bash
41+
yarn build:all
42+
```
43+
44+
## Usage
45+
46+
This example uses the `dotenv` module to manage private keys and also to define env variables needed for this example to work. To run the example, you will need to configure your environment variables A `.env.sample` is provided as a template.
47+
48+
**DO NOT COMMIT PRIVATE KEYS WITH REAL FUNDS TO GITHUB. DOING SO COULD RESULT IN COMPLETE LOSS OF YOUR FUNDS.**
49+
50+
Create a `.env` file in the btc-to-evm example folder:
51+
52+
```bash
53+
cd examples/btc-to-evm-fungible-transfer
54+
touch .env
55+
```
56+
57+
Replace the values that are defined in the `.env.sample` file:
58+
59+
```bash
60+
SYGMA_ENV=testnet
61+
BLOCKSTREAM_URL="your blockstream url"
62+
DESTINATION_ADDRESS="your evm destination address"
63+
DESTINATION_CHAIN_ID="destination domain chain id"
64+
RESOURCE_ID="resource id"
65+
SOURCE_CAIPID="source domain caip id"
66+
EXPLORER_URL="your bitcoin explorer url"
67+
MNEMONIC="your 12 or 24 mnemonic"
68+
DERIVATION_PATH="your derivation path"
69+
ADDRESS="your change address"
70+
AMOUNT="your amount to transfer"
71+
```
72+
73+
* `DESTINATION_ADDRESS`: your `evm` destination address where you want your funds to be relayed
74+
* `DESTINATION_CHAIN_ID`: this is the chainId of the network where you want to receive the funds
75+
* `RESOURCE_ID`: the bitcoin resource id that can be found in our `shared-config` repository
76+
* `SOURCE_CAIPID`: caipId of the bitcoin domain
77+
* `MNEMONIC`: your testnet wallet mnemonic
78+
* `DERIVATION_PATH`: derivation path for your mnemonic. Use derivation path for either P2TR address or P2WPKH one
79+
* `ADDRESS`: the address from which we get the UTXO's and to which we send the change
80+
* `AMOUNT`: the actual amount to transfer
81+
82+
Take into consideration that a typical response when query the utxos of your address look like this:
83+
84+
```json
85+
{
86+
"txid": "7bdf2ce472ee3c9cba6d2944b0ca6bcdceb4b893c7d2163678a0b688a8315d74",
87+
"vout": 3,
88+
"status": {
89+
"confirmed": true,
90+
"block_height": 2869535,
91+
"block_hash": "",
92+
"block_time": 1721666904
93+
},
94+
"value": 936396
95+
}
96+
```
97+
98+
Where `value` is the amount you have at your disposal and `vout` is the transaction output index.
99+
100+
To send Testnet BTC to your EVM account on Sepolia using Taproot address run:
101+
102+
```bash
103+
yarn run transfer:p2tr
104+
```
105+
106+
To send Testnet BTC to your EVM account on Sepolia using P2WPKH address run:
107+
108+
```bash
109+
yarn run transfer:p2wpkh
110+
```
111+
112+
Replace the placeholder values in the `.env` file with your own Testnet BTC Taproot private key as well as the other env variables needed such as DESTINATION_ADDRESS, DESTINATION_DOMAIN_ID, RESOURCE_ID, DERIVATION_PATH, and SOURCE_DOMAIN_ID.
113+
114+
## Script Functionality
115+
116+
This example script performs the following steps:
117+
- I creates a signer to sign the Bitcoin Testnet transaction using your provider private key from your taproot address.
118+
- it gets the fee for 5 confirmations blocks. You can change that following this [reference](https://github.com/Blockstream/esplora/blob/master/API.md#get-fee-estimates)
119+
- it then encodes the provided EVM address + the destination domain id needed to relay the funds
120+
- Once you have provided with the UTXO information needed, it will calculate the fee of the transaction based on some aproximation value
121+
- it then instantiate a PSBT class to be able to provide the inputs and outputs needed to relay the assets
122+
- It signs the transaction and the broadcasted into the Bitcoin testnet network. You will get an url with the transaction id to follow the confirmation of the transaction.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "@buildwithsygma/sygma-sdk-bitcoin-to-evm-fungible-transfer-example",
3+
"version": "0.1.0",
4+
"type": "module",
5+
"description": "Sygma sdk examples",
6+
"sideEffects": false,
7+
"repository": {
8+
"type": "git",
9+
"url": "https://github.com/sygmaprotocol/sygma-sdk"
10+
},
11+
"keywords": [
12+
"sygma",
13+
"sygmaprotocol",
14+
"buildwithsygma",
15+
"web3",
16+
"bridge",
17+
"bitcoin"
18+
],
19+
"scripts": {
20+
"transfer:p2tr": "tsx src/transfer.p2tr.ts",
21+
"transfer:p2wpkh": "tsx src/transfer.p2wpkh.ts"
22+
},
23+
"author": "Sygmaprotocol Product Team",
24+
"license": "LGPL-3.0-or-later",
25+
"devDependencies": {
26+
"dotenv": "^16.3.1",
27+
"eslint": "8",
28+
"ts-node": "10.9.1",
29+
"typescript": "5.0.4"
30+
},
31+
"dependencies": {
32+
"@buildwithsygma/bitcoin": "workspace:^",
33+
"@buildwithsygma/core": "workspace:^",
34+
"@buildwithsygma/utils": "workspace:^",
35+
"bip32": "^4.0.0",
36+
"bip39": "^3.1.0",
37+
"bitcoinjs-lib": "^6.1.6",
38+
"ecpair": "^2.1.0",
39+
"tiny-secp256k1": "^2.2.3",
40+
"tsx": "^4.15.4"
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { TypeOfAddress } from "@buildwithsygma/bitcoin";
2+
import type { BitcoinTransferParams } from "@buildwithsygma/bitcoin";
3+
import type { Network, Signer } from "bitcoinjs-lib";
4+
import { payments, Psbt } from "bitcoinjs-lib";
5+
6+
type SizeCalculationParams = {
7+
utxoData: BitcoinTransferParams["utxoData"];
8+
network: Network;
9+
publicKey: Buffer;
10+
depositAddress: string;
11+
domainId: number;
12+
amount: bigint;
13+
feeValue: bigint;
14+
changeAddress: string;
15+
signer: Signer;
16+
typeOfAddress: TypeOfAddress;
17+
};
18+
19+
/**
20+
* Ee calculate the size of the transaction by using a tx with zero fee => input amount == output amount
21+
* Correctnes of the data is not relevant here, we need to know what's the size is going to be for the amount of inputs passed and the 4 outputs (deposit, change, fee, encoded data) we use to relay the funds
22+
*/
23+
export const calculateSize = ({
24+
utxoData,
25+
network,
26+
publicKey,
27+
depositAddress,
28+
domainId,
29+
amount,
30+
feeValue,
31+
changeAddress,
32+
signer,
33+
typeOfAddress,
34+
}: SizeCalculationParams): number => {
35+
const pstb = new Psbt({ network: network });
36+
37+
const scriptPubKey: Buffer = (typeOfAddress !== TypeOfAddress.P2TR)
38+
? payments.p2wpkh({ pubkey: publicKey, network: network }).output as Buffer
39+
: payments.p2tr({ internalPubkey: publicKey, network: network }).output as Buffer;
40+
41+
utxoData.forEach((utxo) => {
42+
const input = {
43+
hash: utxo.utxoTxId,
44+
index: utxo.utxoOutputIndex,
45+
witnessUtxo: {
46+
value: Number(utxo.utxoAmount),
47+
script: scriptPubKey,
48+
},
49+
};
50+
51+
if (typeOfAddress === TypeOfAddress.P2TR) {
52+
(input as any).tapInternalKey = publicKey;
53+
}
54+
55+
pstb.addInput(input);
56+
});
57+
58+
59+
const outputs = [
60+
{
61+
script: payments.embed({
62+
data: [Buffer.from(`${depositAddress}_${domainId}`)],
63+
}).output as Buffer,
64+
value: 0,
65+
},
66+
{
67+
address: changeAddress,
68+
value: Number(amount),
69+
},
70+
{
71+
address: changeAddress,
72+
value: Number(feeValue),
73+
},
74+
{
75+
address: changeAddress,
76+
value: 0,
77+
}
78+
];
79+
80+
outputs.forEach(output => pstb.addOutput(output));
81+
82+
pstb.signAllInputs(signer);
83+
pstb.finalizeAllInputs();
84+
return pstb.extractTransaction(true).virtualSize();
85+
};

0 commit comments

Comments
 (0)