Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bitcoin): Added support for Bitcoin transfers #389

Merged
merged 93 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
9b40663
feat: btc interfaces
wainola Jun 17, 2024
dcbf033
chore: addressing comments
wainola Jun 17, 2024
2ae5510
chore: rebase with v3
wainola Jun 18, 2024
51990f3
feat: base implementation for btc deposits
wainola Jun 19, 2024
f185139
chore: small change to findResource method on base transfer file
wainola Jun 19, 2024
cb782ea
chore: base files for btc to evm example
wainola Jun 19, 2024
13b51d7
feat: base implementation for btc example
wainola Jun 20, 2024
addaf8c
chore: update env sample file
wainola Jun 20, 2024
20f916b
feat: evm to btc example
wainola Jun 26, 2024
5cd9e4a
feat: adding some updates to btc to evm example
wainola Jun 26, 2024
c40b08f
chore: update env sample file
wainola Jun 26, 2024
ab25db5
chore: update to btc to evm example
wainola Jun 26, 2024
affdcb8
chore: update to btc to evm example
wainola Jun 26, 2024
6647e6d
chore: using fee value
wainola Jun 26, 2024
f1da6f1
feat: adding readme, removing utility function and updating env sampl…
wainola Jul 2, 2024
9691dad
chore: small adjustment to calculate fee
wainola Jul 2, 2024
1585793
chore: update transfer script to use caipId and also update to the sa…
wainola Jul 2, 2024
d21682b
chore: update evm to btc example script
wainola Jul 2, 2024
2de20e6
chore: readme for evm to btc example
wainola Jul 2, 2024
aefdbf2
chore: pr comments
wainola Jul 3, 2024
bb0791d
chore: merge with v3
wainola Jul 4, 2024
0249613
chore: update lock file
wainola Jul 4, 2024
7cdbf7a
chore: fixing lint issues
wainola Jul 4, 2024
f1533ef
feat: returning psbt to sign outside, changes on example and env vars…
wainola Jul 15, 2024
c487cab
chore: remove log
wainola Jul 15, 2024
e229338
chore: p2tr and p2wpkh examples separated
wainola Jul 15, 2024
acba225
chore: remove bip21 method
wainola Jul 17, 2024
9cb72f8
fix: passing correct public key
wainola Jul 19, 2024
213dc31
chore: test for p2tr transaction, fixing linter issues, adding some c…
wainola Jul 24, 2024
9a5a284
chore: update lock file
wainola Jul 24, 2024
cc09767
chore: p2pwkh test
wainola Jul 24, 2024
e44051e
chore: solving conflicts
wainola Jul 24, 2024
0069fc4
chore: update address to hext method, adding test cases and test case…
wainola Jul 29, 2024
00e2ac0
chore: docs for function helpers
wainola Jul 29, 2024
bfa82ee
chore: removing branch condition for fee output
wainola Jul 29, 2024
01f95de
chore: removing table log
wainola Jul 30, 2024
ebbdd9e
chore: removing unused method and types
wainola Jul 30, 2024
19a8269
chore: better env.sample description
wainola Jul 30, 2024
24aa725
chore: small change
wainola Jul 30, 2024
fd52679
chore: remove unused utils method from example
wainola Jul 30, 2024
24b1ca5
chore: typo
wainola Jul 30, 2024
f51eae9
chore: remove log
wainola Jul 30, 2024
df4360e
chore: removing comment
wainola Jul 30, 2024
8a292c9
chore: removing miner fee
wainola Jul 30, 2024
f0956d8
chore: miner free removed from test
wainola Jul 30, 2024
20226a1
chore: removing comment and if branch
wainola Jul 30, 2024
1af4531
chore: pr comments
wainola Jul 31, 2024
d448123
chore: amount and removing duplicate properties from fungible class
wainola Jul 31, 2024
88e3e9f
chore: removed utils file and updated imports
wainola Jul 31, 2024
bb4ca1f
chore: removing utils
wainola Jul 31, 2024
5dc0062
chore: changing name of the type
wainola Jul 31, 2024
3107ecd
chore: removing build:all command
wainola Jul 31, 2024
88090bd
chore: update readme, explaining env vars, minor changes on examples …
wainola Jul 31, 2024
c728966
chore: changing type from number to bigint
wainola Jul 31, 2024
eb55fbc
chore: addressing comments + processing either one or multiple inputs…
wainola Aug 6, 2024
2e7b501
feat: calculating size on example to have reference to pass to the pa…
wainola Aug 7, 2024
4fc94ba
chore: update env sample file
wainola Aug 7, 2024
6e3ad03
chore: addressing last comment from Saad
wainola Aug 7, 2024
b57a8fb
chore: update on comment + update on readme
wainola Aug 7, 2024
06053b3
chore: merge with v3
wainola Aug 8, 2024
083e148
chore: update lock file
wainola Aug 8, 2024
7da382e
chore: fix linter and using base transfer class from core + some modi…
wainola Aug 9, 2024
d0cecb4
chore: update package.json to build with topological flag
wainola Aug 9, 2024
2a18d3d
chore: update lock file
wainola Aug 9, 2024
6a5b637
chore: update test
wainola Aug 9, 2024
e6f6700
chore: update lock file
wainola Aug 9, 2024
dda238b
chore: fixing test
wainola Aug 9, 2024
ccf0c7b
chore: fixing import to validate btc address
wainola Aug 12, 2024
fd672f8
chore: update on package.json file
wainola Aug 14, 2024
17e69aa
chore: adding comment to config class for btc parameter
wainola Aug 14, 2024
7f32d73
chore: change on blockstream utils for example
wainola Aug 14, 2024
35339d5
chore: removing unncessary class + other minor changes
wainola Aug 14, 2024
931f4e9
chore: pr comments, removing clean all packages script
wainola Aug 20, 2024
ea10762
chore: interface for calculate size function + addressing some commen…
wainola Aug 20, 2024
a5ee7d6
chore: inline return + small changes to sample env file
wainola Aug 20, 2024
3a7263c
chore: passing object as parameter into the calculate size function
wainola Aug 20, 2024
6e869cc
chore: pr comments and test update
wainola Aug 21, 2024
f2a1736
chore: ternary operator
wainola Aug 21, 2024
fb71f2b
chore: moving var again to the top
wainola Aug 21, 2024
93c33c9
chore: merge with main
wainola Aug 21, 2024
2497a1a
chore: homologating tsconfig with the other ones in the package and o…
wainola Aug 21, 2024
659b43c
feat: moving some functions to the utils package and make use of them…
wainola Aug 22, 2024
6b4ce47
chore: release workflow file + manifest config files
wainola Aug 22, 2024
b5793e5
chore: renamed examples and addressing some comments from Saad
wainola Aug 27, 2024
ccb9491
chore: update lock file
wainola Aug 27, 2024
2b2ef9e
chore: more pr comments + readme + fixing test
wainola Aug 27, 2024
db0e63f
chore: small adjustments to the example
wainola Aug 27, 2024
b7ab0f0
chore: addressing last pr comment
wainola Aug 27, 2024
9c21334
chore: fixing issue with test
wainola Aug 27, 2024
040fc3c
chore: last pr comments
wainola Sep 4, 2024
b94b67e
chore: merge with main
wainola Sep 4, 2024
f87fdde
chore; fix tests
wainola Sep 4, 2024
dfbbbf0
chore: last comments
wainola Sep 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/btc-to-evm-fungible-transfer/.env.sample
wainola marked this conversation as resolved.
Show resolved Hide resolved
wainola marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
SYGMA_ENV=devnet
BLOCKSTREAM_URL=https://blockstream.info/testnet/api
DESTINATION_ADDRESS=
PRIVATE_KEY=
DESTINATION_DOMAIN_ID=
RESOURCE_ID=
SOURCE_DOMAIN_CAIPID=
EXPLORER_URL=https://blockstream.info/testnet/tx
77 changes: 77 additions & 0 deletions examples/btc-to-evm-fungible-transfer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Sygma SDK BTC to EVM example

## Sygma SDK ERC20 Example

This is an example script that demonstrates the functionality of the SDK using the Sygma ecosystem. The script showcases a asset transfer between a BTC testnet account and a EVM Sepolia account.
wainola marked this conversation as resolved.
Show resolved Hide resolved

## 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
wainola marked this conversation as resolved.
Show resolved Hide resolved

## 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 between the quotation marks your taproot address private key:

`PRIVATE_KEY="YOUR_PRIVATE_KEY_HERE"`

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

```bash
yarn run transfer
```

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 and SOURCE_DOMAIN_ID.
wainola marked this conversation as resolved.
Show resolved Hide resolved

## 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.
38 changes: 38 additions & 0 deletions examples/btc-to-evm-fungible-transfer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@buildwithsygma/sygma-sdk-btc-to-evm-fungible-transfer-example",
wainola marked this conversation as resolved.
Show resolved Hide resolved
"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": "tsx src/transfer.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/btc": "workspace:^",
"@buildwithsygma/core": "workspace:^",
"bitcoinjs-lib": "^6.1.6",
"ecpair": "^2.1.0",
"tiny-secp256k1": "^2.2.3",
"tsx": "^4.15.4"
}
}
139 changes: 139 additions & 0 deletions examples/btc-to-evm-fungible-transfer/src/transfer.ts
wainola marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import dotenv from 'dotenv';
import { createBitcoinFungibleTransfer } from '@buildwithsygma/btc';
import { ECPairFactory, ECPairAPI } from 'ecpair';
import {
initEccLib,
networks,
payments,
Psbt
} from "bitcoinjs-lib";
import * as tinysecp from 'tiny-secp256k1';
import { broadcastTransaction, calculateFee, getFeeEstimates, getTweakedSigner, toXOnly } from './utils';

dotenv.config();

const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS;
const DESTINATION_DOMAIN_ID = Number(process.env.DESTINATION_DOMAIN_ID);
const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const RESOURCE_ID = process.env.RESOURCE_ID;
const SOURCE_DOMAIN_CAIPID = process.env.SOURCE_DOMAIN_CAIPID
const FEE_ADDRESS = 'tb1p0r2w3ugreaggd7nakw2wd04up6rl8k0cce8eetxwmhnrelgqx87s4zdkd7'
const FEE_AMOUNT = 1000000;
const EXPLORER_URL = process.env.EXPLORER_URL;

if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DESTINATION_DOMAIN_ID || !BLOCKSTREAM_URL || !RESOURCE_ID || !SOURCE_DOMAIN_CAIPID) {
throw new Error('Please provided needed env variavles in .env file');
}

type InputData = {
hash: string;
index: number;
witnessUtxo: { value: number; script: Buffer };
tapInternalKey: Buffer;
};

async function btcToEvmTransfer(): Promise<void> {
// pre setup
const testnet = networks.testnet;
initEccLib(tinysecp as any);
wainola marked this conversation as resolved.
Show resolved Hide resolved
const ECPair: ECPairAPI = ECPairFactory(tinysecp);

console.log('Transfer BTC to EVM');

// tweaking signer
const { tweakedSigner, publicKey } = getTweakedSigner(ECPair, tinysecp, testnet, PRIVATE_KEY!);
wainola marked this conversation as resolved.
Show resolved Hide resolved

// Generate an address from the tweaked public key
const p2pktr = payments.p2tr({
pubkey: toXOnly(tweakedSigner.publicKey),
network: testnet,
});

const p2pktrAdddress = p2pktr.address as string;
// address here should match the one that you generated for your private key
console.log('Taproot address to use', p2pktrAdddress)

const feeEstimatesPerBlockConfirmation = await getFeeEstimates(BLOCKSTREAM_URL!);
wainola marked this conversation as resolved.
Show resolved Hide resolved

/**
* Get UTXO to use for the transfer
* You can get UTXO from any source, for this example we are using Blockstream API
* Add the txid and vout of the UTXO to the inputData. Also add the value of the UTXO to the witnessUtxo value field
*/

const inputData: InputData = {
wainola marked this conversation as resolved.
Show resolved Hide resolved
hash: '', // utxo tx id
index: 0, // utxo index
witnessUtxo: { value: 0, script: p2pktr.output! }, // utxo value
tapInternalKey: toXOnly(publicKey)
};

const params = {
sourceDomain: SOURCE_DOMAIN_CAIPID,
destinationAddress: DESTINATION_ADDRESS,
amount: inputData.witnessUtxo.value,
resource: RESOURCE_ID
}

const transfer = await createBitcoinFungibleTransfer(params);

const transferRequestData = transfer.getBTCTransferRequest();

const psbt = new Psbt({ network: testnet });

// encoded data
const data = Buffer.from(
wainola marked this conversation as resolved.
Show resolved Hide resolved
`${DESTINATION_ADDRESS}_${DESTINATION_DOMAIN_ID}`, // EMV ADDRESS + DESTINATION DOMAIN ID HERE
"utf8",
);

const embed = payments.embed({ data: [data] });

const amount = transferRequestData.amount - 1e4;
wainola marked this conversation as resolved.
Show resolved Hide resolved
const amountMinusBridgeFee = amount - FEE_AMOUNT;

const outputEncodedData = {
script: embed.output!,
value: 0,
};

const outputData = {
address: transferRequestData.depositAddress,
value: amountMinusBridgeFee
};

const outputDataFee = {
address: FEE_ADDRESS,
value: FEE_AMOUNT
};

const feeValue = calculateFee(psbt, feeEstimatesPerBlockConfirmation, inputData, outputEncodedData, outputData, outputDataFee, tweakedSigner);

const psbtWithFee = new Psbt({ network: testnet });

const amountWitFeeApplied = amount - feeValue - FEE_AMOUNT;

psbtWithFee.addInput(inputData);

psbtWithFee.addOutput(outputEncodedData);

psbtWithFee.addOutput(outputDataFee);

psbtWithFee.addOutput({
address: transferRequestData.depositAddress,
value: amountWitFeeApplied
});

psbtWithFee.signInput(0, tweakedSigner);
psbtWithFee.finalizeAllInputs();

const tx = psbtWithFee.extractTransaction(true);

console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`);
const txid = await broadcastTransaction(BLOCKSTREAM_URL!, tx.toHex());

console.log(`Transaction ID: ${EXPLORER_URL}/${txid}`);
}

btcToEvmTransfer().finally(() => { })
91 changes: 91 additions & 0 deletions examples/btc-to-evm-fungible-transfer/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Signer, networks, crypto, Psbt } from "bitcoinjs-lib";
import { ECPairAPI, TinySecp256k1Interface } from "ecpair";


type InputData = { hash: string, index: number, witnessUtxo: { value: number, script: Buffer }, tapInternalKey: Buffer }

type OutputData = { value: number, script: Buffer, address: string }


// shortens the public key to 32 bytes
export function toXOnly(pubkey: Buffer): Buffer {
return pubkey.subarray(1, 33)
}

function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
return crypto.taggedHash(
'TapTweak',
Buffer.concat(h ? [pubKey, h] : [pubKey]),
);
}

// Get the tweaked signer and returns also public key
export function getTweakedSigner(ECPair: ECPairAPI, tinysecp: TinySecp256k1Interface, network: networks.Network, privateKey: string): { tweakedSigner: Signer, publicKey: Buffer } {
const keypair = ECPair.fromWIF(privateKey!, networks.testnet) as Signer;
// @ts-ignore
let privKey: Uint8Array = keypair.privateKey!;

if (keypair.publicKey[0] === 3) {
privKey = tinysecp.privateNegate(privKey);
}

const tweakedPrivateKey = tinysecp.privateAdd(
privKey,
tapTweakHash(toXOnly(keypair.publicKey), undefined),
);

if (!tweakedPrivateKey) {
throw new Error('Invalid tweaked private key!');
}

return {
tweakedSigner: ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
network: networks.testnet,
}), publicKey: keypair.publicKey
};
}

export async function getFeeEstimates(blockstreamUrl: string): Promise<number> {
wainola marked this conversation as resolved.
Show resolved Hide resolved
try {
const response = await fetch(`${blockstreamUrl}/fee-estimates`);

const data = await response.json();

return data['5']; // fee for 5 blocks confirmation
} catch (error) {
throw new Error('Failed to get fee estimates');
}
}

export async function broadcastTransaction(blockstreamUrl: string, txHex: string): Promise<string> {
try {
const response = await fetch(`${blockstreamUrl}/tx`, {
method: 'POST',
body: txHex,
headers: {
'Content-Type': 'text/plain'
}
});

return await response.text();
} catch (error) {
console.log('error', error)
throw new Error('Failed to broadcast transaction');
}
}

export function calculateFee(psbt: Psbt, feeRate: number, inputData: InputData, bridgeOutputData: Pick<OutputData, "script" | "value">, valueOutputData: Pick<OutputData, "address" | "value">, outputFeeData: Pick<OutputData, "address" | "value">, tweakedSigner: Signer): number {
psbt.addInput(inputData);
psbt.addOutput(bridgeOutputData);
psbt.addOutput(valueOutputData);
psbt.addOutput(outputFeeData);

psbt.signInput(0, tweakedSigner);
psbt.finalizeAllInputs();

const tx = psbt.extractTransaction(true);

const virtualSize = tx.virtualSize();

return Math.round(virtualSize * feeRate);
}
21 changes: 21 additions & 0 deletions examples/btc-to-evm-fungible-transfer/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
wainola marked this conversation as resolved.
Show resolved Hide resolved
"compilerOptions": {
"composite": true,
"module": "ES2022",
"allowJs": true,
"declaration": true,
"sourceMap": true,
"declarationMap": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"esModuleInterop": true,
"downlevelIteration": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
},
"include": [
"src"
]
}
4 changes: 4 additions & 0 deletions examples/evm-to-btc-fungible-transfer/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SEPOLIA_RPC_URL=
BTC_DESTINATION_ADDRESS=
PRIVATE_KEY=
wainola marked this conversation as resolved.
Show resolved Hide resolved
SYGMA_ENV=devnet
wainola marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions examples/evm-to-btc-fungible-transfer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# evm-to-btc-fungible-transfer
Loading