Skip to content

Commit

Permalink
feat(bitcoin): Added support for Bitcoin transfers (#389)
Browse files Browse the repository at this point in the history
<!--- 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
  • Loading branch information
wainola authored Sep 9, 2024
1 parent fd80266 commit 1f428b1
Show file tree
Hide file tree
Showing 48 changed files with 2,102 additions and 73 deletions.
62 changes: 62 additions & 0 deletions .github/workflows/release-bitcoin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Publish Sygma SDK Bitcoin package to GitHub Package Registry

on:
push:
branches: ["main"]
paths: ["packages/bitcoin/**"]

jobs:
maybe-release:
name: release
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
# you should probably do this after your regular CI checks passes
# 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
- uses: google-github-actions/release-please-action@v3
id: release
with:
command: manifest
release-type: node
token: ${{secrets.RELEASE_TOKEN}}
config-file: "release-please/rp-bitcoin-config.json"
manifest-file: "release-please/rp-bitcoin-manifest.json"
monorepo-tags: true
default-branch: main
path: "packages/bitcoin"
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}]'

- uses: actions/checkout@v4
# these if statements ensure that a publication only occurs when
# a new release is created:
if: ${{ steps.release.outputs.releases_created }}

- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version: 18
registry-url: "https://registry.npmjs.org"
scope: "@buildwithsygma"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
if: ${{ steps.release.outputs.releases_created }}

- name: Enable corepack
run: corepack enable
if: ${{ steps.release.outputs.releases_created }}

- name: Install dependencies
run: yarn install --immutable
if: ${{ steps.release.outputs.releases_created }}

- run: yarn build
if: ${{ steps.release.outputs.releases_created }}

- env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
if: ${{ steps.release.outputs.releases_created }}
run: |
echo -e "\nnpmAuthToken: \"$NODE_AUTH_TOKEN\"" >> ./.yarnrc.yml
- run: yarn workspace @buildwithsygma/bitcoin npm publish --access public
if: ${{ steps.release.outputs.releases_created }}
10 changes: 10 additions & 0 deletions examples/bitcoin-to-evm-fungible-transfer/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
SYGMA_ENV=testnet
BLOCKSTREAM_URL="your blockstream url"
DESTINATION_ADDRESS="your evm destination address"
RESOURCE_ID="resource id"
SOURCE_CAIPID="source domain caip id"
EXPLORER_URL="your bitcoin explorer url"
MNEMONIC="your 12 or 24 mnemonic"
DERIVATION_PATH="your derivation path"
ADDRESS="your bitcoin address to use and send change"
AMOUNT="your amount to transfer"
122 changes: 122 additions & 0 deletions examples/bitcoin-to-evm-fungible-transfer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Sygma SDK Bitcoin to EVM example

## Sygma SDK ERC20 Example

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.

## Prerequisites

Before running the script, ensure that you have the following:

- Node.js
- Yarn (version 3.4.1 or higher)
- A development wallet with some testnet BTC
- The private key of a Taproot address to sign the transaction
- 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

## Getting started

### 1. Clone the repository

To get started, clone this repository to your local machine with:

```bash
git clone [email protected]:sygmaprotocol/sygma-sdk.git
cd sygma-sdk/
```

### 2. Install dependencies

Install the project dependencies by running:

```bash
yarn install
```

### 3. Build the sdk

To start the example you need to build the sdk first with:

```bash
yarn build:all
```

## Usage

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.

**DO NOT COMMIT PRIVATE KEYS WITH REAL FUNDS TO GITHUB. DOING SO COULD RESULT IN COMPLETE LOSS OF YOUR FUNDS.**

Create a `.env` file in the btc-to-evm example folder:

```bash
cd examples/btc-to-evm-fungible-transfer
touch .env
```

Replace the values that are defined in the `.env.sample` file:

```bash
SYGMA_ENV=testnet
BLOCKSTREAM_URL="your blockstream url"
DESTINATION_ADDRESS="your evm destination address"
DESTINATION_CHAIN_ID="destination domain chain id"
RESOURCE_ID="resource id"
SOURCE_CAIPID="source domain caip id"
EXPLORER_URL="your bitcoin explorer url"
MNEMONIC="your 12 or 24 mnemonic"
DERIVATION_PATH="your derivation path"
ADDRESS="your change address"
AMOUNT="your amount to transfer"
```

* `DESTINATION_ADDRESS`: your `evm` destination address where you want your funds to be relayed
* `DESTINATION_CHAIN_ID`: this is the chainId of the network where you want to receive the funds
* `RESOURCE_ID`: the bitcoin resource id that can be found in our `shared-config` repository
* `SOURCE_CAIPID`: caipId of the bitcoin domain
* `MNEMONIC`: your testnet wallet mnemonic
* `DERIVATION_PATH`: derivation path for your mnemonic. Use derivation path for either P2TR address or P2WPKH one
* `ADDRESS`: the address from which we get the UTXO's and to which we send the change
* `AMOUNT`: the actual amount to transfer

Take into consideration that a typical response when query the utxos of your address look like this:

```json
{
"txid": "7bdf2ce472ee3c9cba6d2944b0ca6bcdceb4b893c7d2163678a0b688a8315d74",
"vout": 3,
"status": {
"confirmed": true,
"block_height": 2869535,
"block_hash": "",
"block_time": 1721666904
},
"value": 936396
}
```

Where `value` is the amount you have at your disposal and `vout` is the transaction output index.

To send Testnet BTC to your EVM account on Sepolia using Taproot address run:

```bash
yarn run transfer:p2tr
```

To send Testnet BTC to your EVM account on Sepolia using P2WPKH address run:

```bash
yarn run transfer:p2wpkh
```

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.

## Script Functionality

This example script performs the following steps:
- I creates a signer to sign the Bitcoin Testnet transaction using your provider private key from your taproot address.
- 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)
- it then encodes the provided EVM address + the destination domain id needed to relay the funds
- Once you have provided with the UTXO information needed, it will calculate the fee of the transaction based on some aproximation value
- it then instantiate a PSBT class to be able to provide the inputs and outputs needed to relay the assets
- 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.
42 changes: 42 additions & 0 deletions examples/bitcoin-to-evm-fungible-transfer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@buildwithsygma/sygma-sdk-bitcoin-to-evm-fungible-transfer-example",
"version": "0.1.0",
"type": "module",
"description": "Sygma sdk examples",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/sygmaprotocol/sygma-sdk"
},
"keywords": [
"sygma",
"sygmaprotocol",
"buildwithsygma",
"web3",
"bridge",
"bitcoin"
],
"scripts": {
"transfer:p2tr": "tsx src/transfer.p2tr.ts",
"transfer:p2wpkh": "tsx src/transfer.p2wpkh.ts"
},
"author": "Sygmaprotocol Product Team",
"license": "LGPL-3.0-or-later",
"devDependencies": {
"dotenv": "^16.3.1",
"eslint": "8",
"ts-node": "10.9.1",
"typescript": "5.0.4"
},
"dependencies": {
"@buildwithsygma/bitcoin": "workspace:^",
"@buildwithsygma/core": "workspace:^",
"@buildwithsygma/utils": "workspace:^",
"bip32": "^4.0.0",
"bip39": "^3.1.0",
"bitcoinjs-lib": "^6.1.6",
"ecpair": "^2.1.0",
"tiny-secp256k1": "^2.2.3",
"tsx": "^4.15.4"
}
}
85 changes: 85 additions & 0 deletions examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { TypeOfAddress } from "@buildwithsygma/bitcoin";
import type { BitcoinTransferParams } from "@buildwithsygma/bitcoin";
import type { Network, Signer } from "bitcoinjs-lib";
import { payments, Psbt } from "bitcoinjs-lib";

type SizeCalculationParams = {
utxoData: BitcoinTransferParams["utxoData"];
network: Network;
publicKey: Buffer;
depositAddress: string;
domainId: number;
amount: bigint;
feeValue: bigint;
changeAddress: string;
signer: Signer;
typeOfAddress: TypeOfAddress;
};

/**
* Ee calculate the size of the transaction by using a tx with zero fee => input amount == output amount
* 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
*/
export const calculateSize = ({
utxoData,
network,
publicKey,
depositAddress,
domainId,
amount,
feeValue,
changeAddress,
signer,
typeOfAddress,
}: SizeCalculationParams): number => {
const pstb = new Psbt({ network: network });

const scriptPubKey: Buffer = (typeOfAddress !== TypeOfAddress.P2TR)
? payments.p2wpkh({ pubkey: publicKey, network: network }).output as Buffer
: payments.p2tr({ internalPubkey: publicKey, network: network }).output as Buffer;

utxoData.forEach((utxo) => {
const input = {
hash: utxo.utxoTxId,
index: utxo.utxoOutputIndex,
witnessUtxo: {
value: Number(utxo.utxoAmount),
script: scriptPubKey,
},
};

if (typeOfAddress === TypeOfAddress.P2TR) {
(input as any).tapInternalKey = publicKey;
}

pstb.addInput(input);
});


const outputs = [
{
script: payments.embed({
data: [Buffer.from(`${depositAddress}_${domainId}`)],
}).output as Buffer,
value: 0,
},
{
address: changeAddress,
value: Number(amount),
},
{
address: changeAddress,
value: Number(feeValue),
},
{
address: changeAddress,
value: 0,
}
];

outputs.forEach(output => pstb.addOutput(output));

pstb.signAllInputs(signer);
pstb.finalizeAllInputs();
return pstb.extractTransaction(true).virtualSize();
};
Loading

0 comments on commit 1f428b1

Please sign in to comment.