From 1912af2adcc519537d13aa603d5dde134f83e9ae Mon Sep 17 00:00:00 2001 From: mace Date: Thu, 5 Sep 2024 17:10:47 +0200 Subject: [PATCH 1/8] chore(main): release core 1.1.0 (#511) :robot: I have created a release *beep* *boop* --- ## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/core-v1.0.3...core-v1.1.0) (2024-09-05) ### Features * **evm:** Added support for creating cross chain non fungible asset transfer transactions ([#460](https://github.com/sygmaprotocol/sygma-sdk/issues/460)) ([6c40781](https://github.com/sygmaprotocol/sygma-sdk/commit/6c40781593c927a06a42f593c511520f41d028d5)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/core/CHANGELOG.md | 7 +++++++ packages/core/package.json | 2 +- release-please/rp-core-manifest.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index c5aae606d..67ffc1aa6 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/core-v1.0.3...core-v1.1.0) (2024-09-05) + + +### Features + +* **evm:** Added support for creating cross chain non fungible asset transfer transactions ([#460](https://github.com/sygmaprotocol/sygma-sdk/issues/460)) ([6c40781](https://github.com/sygmaprotocol/sygma-sdk/commit/6c40781593c927a06a42f593c511520f41d028d5)) + ## [1.0.3](https://github.com/sygmaprotocol/sygma-sdk/compare/core-v1.0.2...core-v1.0.3) (2024-09-03) diff --git a/packages/core/package.json b/packages/core/package.json index 6ad4d5662..b8eadebd2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@buildwithsygma/core", - "version": "1.0.3", + "version": "1.1.0", "description": "Core primitives for bridging and message passing", "main": "dist-esm/index.js", "types": "types/index.d.ts", diff --git a/release-please/rp-core-manifest.json b/release-please/rp-core-manifest.json index 09f585cbb..fe106deef 100644 --- a/release-please/rp-core-manifest.json +++ b/release-please/rp-core-manifest.json @@ -1 +1 @@ -{"packages/core":"1.0.3"} +{"packages/core":"1.1.0"} From 1467f727187acf24895180bd632061743d4c0b69 Mon Sep 17 00:00:00 2001 From: mace Date: Thu, 5 Sep 2024 17:15:56 +0200 Subject: [PATCH 2/8] chore(main): release evm 1.1.0 (#513) :robot: I have created a release *beep* *boop* --- ## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/evm-v1.0.5...evm-v1.1.0) (2024-09-05) ### Features * **evm:** Added support for creating cross chain non fungible asset transfer transactions ([#460](https://github.com/sygmaprotocol/sygma-sdk/issues/460)) ([6c40781](https://github.com/sygmaprotocol/sygma-sdk/commit/6c40781593c927a06a42f593c511520f41d028d5)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/evm/CHANGELOG.md | 7 +++++++ packages/evm/package.json | 2 +- release-please/rp-evm-manifest.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/evm/CHANGELOG.md b/packages/evm/CHANGELOG.md index effea1380..307b68342 100644 --- a/packages/evm/CHANGELOG.md +++ b/packages/evm/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/evm-v1.0.5...evm-v1.1.0) (2024-09-05) + + +### Features + +* **evm:** Added support for creating cross chain non fungible asset transfer transactions ([#460](https://github.com/sygmaprotocol/sygma-sdk/issues/460)) ([6c40781](https://github.com/sygmaprotocol/sygma-sdk/commit/6c40781593c927a06a42f593c511520f41d028d5)) + ## [1.0.5](https://github.com/sygmaprotocol/sygma-sdk/compare/evm-v1.0.4...evm-v1.0.5) (2024-08-23) diff --git a/packages/evm/package.json b/packages/evm/package.json index 7839e96ef..230834ff0 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -1,6 +1,6 @@ { "name": "@buildwithsygma/evm", - "version": "1.0.5", + "version": "1.1.0", "description": "Core primitives for bridging and message passing", "main": "dist-esm/index.js", "types": "types/index.d.ts", diff --git a/release-please/rp-evm-manifest.json b/release-please/rp-evm-manifest.json index 030c9d59f..2901e7420 100644 --- a/release-please/rp-evm-manifest.json +++ b/release-please/rp-evm-manifest.json @@ -1 +1 @@ -{"packages/evm":"1.0.5"} +{"packages/evm":"1.1.0"} From fd80266dbd34119e3408706964a3f884895b354f Mon Sep 17 00:00:00 2001 From: mace Date: Thu, 5 Sep 2024 17:20:07 +0200 Subject: [PATCH 3/8] chore(main): release utils 1.1.0 (#512) :robot: I have created a release *beep* *boop* --- ## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/utils-v1.0.2...utils-v1.1.0) (2024-09-05) ### Features * **evm:** Added support for creating cross chain non fungible asset transfer transactions ([#460](https://github.com/sygmaprotocol/sygma-sdk/issues/460)) ([6c40781](https://github.com/sygmaprotocol/sygma-sdk/commit/6c40781593c927a06a42f593c511520f41d028d5)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/utils/CHANGELOG.md | 7 +++++++ packages/utils/package.json | 2 +- release-please/rp-utils-manifest.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 37fed1e26..e4c8f8843 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/utils-v1.0.2...utils-v1.1.0) (2024-09-05) + + +### Features + +* **evm:** Added support for creating cross chain non fungible asset transfer transactions ([#460](https://github.com/sygmaprotocol/sygma-sdk/issues/460)) ([6c40781](https://github.com/sygmaprotocol/sygma-sdk/commit/6c40781593c927a06a42f593c511520f41d028d5)) + ## [1.0.2](https://github.com/sygmaprotocol/sygma-sdk/compare/utils-v1.0.1...utils-v1.0.2) (2024-08-21) diff --git a/packages/utils/package.json b/packages/utils/package.json index 648dd39dd..625ef7edd 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@buildwithsygma/utils", - "version": "1.0.2", + "version": "1.1.0", "description": "Utilities to support bridging and message passing", "main": "dist-esm/index.js", "types": "types/index.d.ts", diff --git a/release-please/rp-utils-manifest.json b/release-please/rp-utils-manifest.json index ac7a0e52d..59c6229ae 100644 --- a/release-please/rp-utils-manifest.json +++ b/release-please/rp-utils-manifest.json @@ -1 +1 @@ -{"packages/utils":"1.0.2"} +{"packages/utils":"1.1.0"} From 1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s?= Date: Mon, 9 Sep 2024 06:49:59 -0300 Subject: [PATCH 4/8] feat(bitcoin): Added support for Bitcoin transfers (#389) ## Description 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 Closes: #369 #411 ## How Has This Been Tested? Testing details. ## Types of changes - [ ] 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: - [ ] 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 --- .github/workflows/release-bitcoin.yml | 62 +++++ .../.env.sample | 10 + .../README.md | 122 +++++++++ .../package.json | 42 ++++ .../src/blockstreamApi.ts | 85 +++++++ .../src/transfer.p2tr.ts | 121 +++++++++ .../src/transfer.p2wpkh.ts | 112 +++++++++ .../tsconfig.json | 21 ++ .../.env.sample | 4 + .../README.md | 81 ++++++ .../package.json | 35 +++ .../src/transfer.ts | 54 ++++ .../tsconfig.json | 21 ++ package.json | 5 + packages/bitcoin/.eslintrc.cjs | 10 + packages/bitcoin/.gitignore | 45 ++++ packages/bitcoin/.npmignore | 26 ++ packages/bitcoin/.prettierrc.json | 7 + packages/bitcoin/README.md | 138 +++++++++++ packages/bitcoin/jest.config.cjs | 19 ++ packages/bitcoin/package.json | 66 +++++ .../bitcoin/src/__test__/fungible.test.ts | 231 ++++++++++++++++++ packages/bitcoin/src/environment.d.ts | 9 + packages/bitcoin/src/fungible.ts | 71 ++++++ packages/bitcoin/src/index.ts | 2 + packages/bitcoin/src/types.ts | 62 +++++ packages/bitcoin/src/utils/helpers.ts | 203 +++++++++++++++ packages/bitcoin/src/utils/index.ts | 1 + packages/bitcoin/test/setupJest.js | 2 + packages/bitcoin/tsconfig.cjs.json | 13 + packages/bitcoin/tsconfig.esm.json | 12 + packages/bitcoin/tsconfig.json | 5 + packages/bitcoin/tsconfig.types.json | 8 + packages/btc/src/base-transfer.ts | 48 ---- packages/btc/src/index.ts | 1 - packages/core/src/baseTransfer.ts | 22 +- packages/core/src/config/config.ts | 16 +- packages/core/src/types.ts | 15 +- packages/core/src/utils.ts | 6 +- .../evm/src/utils/__test__/helpers.test.ts | 31 +++ packages/evm/src/utils/helpers.ts | 17 +- packages/utils/src/bitcoin/blockstream.ts | 102 ++++++++ packages/utils/src/bitcoin/index.ts | 1 + packages/utils/src/index.ts | 1 + packages/utils/src/liquidity.ts | 10 +- release-please/rp-bitcoin-config.json | 16 ++ release-please/rp-bitcoin-manifest.json | 1 + yarn.lock | 183 +++++++++++++- 48 files changed, 2102 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/release-bitcoin.yml create mode 100644 examples/bitcoin-to-evm-fungible-transfer/.env.sample create mode 100644 examples/bitcoin-to-evm-fungible-transfer/README.md create mode 100644 examples/bitcoin-to-evm-fungible-transfer/package.json create mode 100644 examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts create mode 100644 examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts create mode 100644 examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts create mode 100644 examples/bitcoin-to-evm-fungible-transfer/tsconfig.json create mode 100644 examples/evm-to-bitcoin-fungible-transfer/.env.sample create mode 100644 examples/evm-to-bitcoin-fungible-transfer/README.md create mode 100644 examples/evm-to-bitcoin-fungible-transfer/package.json create mode 100644 examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts create mode 100644 examples/evm-to-bitcoin-fungible-transfer/tsconfig.json create mode 100644 packages/bitcoin/.eslintrc.cjs create mode 100644 packages/bitcoin/.gitignore create mode 100644 packages/bitcoin/.npmignore create mode 100644 packages/bitcoin/.prettierrc.json create mode 100644 packages/bitcoin/README.md create mode 100644 packages/bitcoin/jest.config.cjs create mode 100644 packages/bitcoin/package.json create mode 100644 packages/bitcoin/src/__test__/fungible.test.ts create mode 100644 packages/bitcoin/src/environment.d.ts create mode 100644 packages/bitcoin/src/fungible.ts create mode 100644 packages/bitcoin/src/index.ts create mode 100644 packages/bitcoin/src/types.ts create mode 100644 packages/bitcoin/src/utils/helpers.ts create mode 100644 packages/bitcoin/src/utils/index.ts create mode 100644 packages/bitcoin/test/setupJest.js create mode 100644 packages/bitcoin/tsconfig.cjs.json create mode 100644 packages/bitcoin/tsconfig.esm.json create mode 100644 packages/bitcoin/tsconfig.json create mode 100644 packages/bitcoin/tsconfig.types.json delete mode 100644 packages/btc/src/base-transfer.ts delete mode 100644 packages/btc/src/index.ts create mode 100644 packages/utils/src/bitcoin/blockstream.ts create mode 100644 packages/utils/src/bitcoin/index.ts create mode 100644 release-please/rp-bitcoin-config.json create mode 100644 release-please/rp-bitcoin-manifest.json diff --git a/.github/workflows/release-bitcoin.yml b/.github/workflows/release-bitcoin.yml new file mode 100644 index 000000000..b8895cb0c --- /dev/null +++ b/.github/workflows/release-bitcoin.yml @@ -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 }} diff --git a/examples/bitcoin-to-evm-fungible-transfer/.env.sample b/examples/bitcoin-to-evm-fungible-transfer/.env.sample new file mode 100644 index 000000000..a251cf896 --- /dev/null +++ b/examples/bitcoin-to-evm-fungible-transfer/.env.sample @@ -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" \ No newline at end of file diff --git a/examples/bitcoin-to-evm-fungible-transfer/README.md b/examples/bitcoin-to-evm-fungible-transfer/README.md new file mode 100644 index 000000000..2dc96dabd --- /dev/null +++ b/examples/bitcoin-to-evm-fungible-transfer/README.md @@ -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 git@github.com: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. diff --git a/examples/bitcoin-to-evm-fungible-transfer/package.json b/examples/bitcoin-to-evm-fungible-transfer/package.json new file mode 100644 index 000000000..70cec83b2 --- /dev/null +++ b/examples/bitcoin-to-evm-fungible-transfer/package.json @@ -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" + } +} diff --git a/examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts b/examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts new file mode 100644 index 000000000..a9f3f1628 --- /dev/null +++ b/examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts @@ -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(); +}; diff --git a/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts new file mode 100644 index 000000000..e18ee0b6e --- /dev/null +++ b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -0,0 +1,121 @@ +import { + createBitcoinFungibleTransfer, + TypeOfAddress, +} from "@buildwithsygma/bitcoin"; +import type { BitcoinTransferParams, UTXOData } from "@buildwithsygma/bitcoin"; +import { BIP32Factory } from "bip32"; +import { mnemonicToSeed } from "bip39"; +import { crypto, initEccLib, networks } from "bitcoinjs-lib"; +import { toXOnly } from "bitcoinjs-lib/src/psbt/bip371"; +import dotenv from "dotenv"; +import * as tinysecp from "tiny-secp256k1"; +import { broadcastTransaction, fetchUTXOS, getFeeEstimates, processUtxos } from '@buildwithsygma/utils' + +import { + calculateSize, +} from "./blockstreamApi.js"; + +dotenv.config(); + +const DESTINATION_CHAIN_ID = 11155111; +const { + SOURCE_CAIPID, + DESTINATION_ADDRESS, + RESOURCE_ID, + BLOCKSTREAM_URL, + EXPLORER_URL, + MNEMONIC, + DERIVATION_PATH, + ADDRESS, + AMOUNT, +} = process.env; + + +if ( + !SOURCE_CAIPID || + !DESTINATION_ADDRESS || + !RESOURCE_ID || + !MNEMONIC || + !DERIVATION_PATH || + !ADDRESS || + !BLOCKSTREAM_URL || + !AMOUNT +) { + throw new Error( + "Missing required environment variables, please make sure .env file exists." + ); +} + +async function btcToEvmTransfer(): Promise { + // pre setup + initEccLib(tinysecp); + const bip32 = BIP32Factory(tinysecp); + console.log("Transfer BTC to EVM"); + const seed = await mnemonicToSeed(MNEMONIC); + const rootKey = bip32.fromSeed(seed, networks.testnet); + const derivedNode = rootKey.derivePath(DERIVATION_PATH); + + const publicKeyDropedDERHeader = toXOnly(derivedNode.publicKey); + + const tweakedSigner = derivedNode.tweak( + crypto.taggedHash("TapTweak", publicKeyDropedDERHeader), + ); + + const feeRate = await getFeeEstimates('5'); + const utxos = await fetchUTXOS( + ADDRESS as unknown as string); + + const processedUtxos = processUtxos(utxos, AMOUNT); + + const mapedUtxos = processedUtxos.map((utxo) => ({ + utxoTxId: utxo.txid, + utxoOutputIndex: utxo.vout, + utxoAmount: BigInt(utxo.value), + })) as unknown as UTXOData[]; + + const size = calculateSize({ + utxoData: mapedUtxos, + network: networks.testnet, + publicKey: publicKeyDropedDERHeader, + depositAddress: ADDRESS as unknown as string, + domainId: DESTINATION_CHAIN_ID, + amount: AMOUNT, + feeValue: BigInt(0), + changeAddress: ADDRESS as unknown as string, + signer: tweakedSigner, + typeOfAddress: TypeOfAddress.P2TR, + }); + + const transferParams: BitcoinTransferParams = { + source: SOURCE_CAIPID, + destination: DESTINATION_CHAIN_ID, + destinationAddress: DESTINATION_ADDRESS, + amount: AMOUNT, + resource: RESOURCE_ID, + utxoData: mapedUtxos, + feeRate: BigInt(Math.ceil(feeRate)), + publicKey: publicKeyDropedDERHeader, + typeOfAddress: TypeOfAddress.P2TR, + network: networks.testnet, + changeAddress: ADDRESS, + size: BigInt(size), + }; + + const transfer = await createBitcoinFungibleTransfer(transferParams); + + const psbt = transfer.getTransferTransaction(); + + console.log("Signing the transaction"); + + psbt.signAllInputs(tweakedSigner); + psbt.finalizeAllInputs(); + + console.log("Extracting the transaction"); + const tx = psbt.extractTransaction(true); + console.log("Transaction hex", tx.toHex()); + + const txId = await broadcastTransaction(tx.toHex()); + console.log("Transaction broadcasted", `${EXPLORER_URL}/tx/${txId}`); +} + +btcToEvmTransfer().finally(() => { }); diff --git a/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts new file mode 100644 index 000000000..c0e29d7fd --- /dev/null +++ b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -0,0 +1,112 @@ +import type { BitcoinTransferParams, UTXOData } from "@buildwithsygma/bitcoin"; +import { + createBitcoinFungibleTransfer, + TypeOfAddress, +} from "@buildwithsygma/bitcoin"; +import { BIP32Factory } from "bip32"; +import { mnemonicToSeed } from "bip39"; +import { initEccLib, networks } from "bitcoinjs-lib"; +import dotenv from "dotenv"; +import * as tinysecp from "tiny-secp256k1"; + +import { + calculateSize, +} from "./blockstreamApi.js"; +import { broadcastTransaction, fetchUTXOS, getFeeEstimates, processUtxos } from "@buildwithsygma/utils"; + +dotenv.config(); + +const DESTINATION_CHAIN_ID = 11155111; +const { + SOURCE_CAIPID, + DESTINATION_ADDRESS, + RESOURCE_ID, + BLOCKSTREAM_URL, + EXPLORER_URL, + MNEMONIC, + DERIVATION_PATH, + ADDRESS, + AMOUNT, +} = process.env; + +if ( + !SOURCE_CAIPID || + !DESTINATION_ADDRESS || + !RESOURCE_ID || + !MNEMONIC || + !DERIVATION_PATH || + !ADDRESS || + !BLOCKSTREAM_URL || + !AMOUNT +) { + throw new Error( + "Missing required environment variables, please make sure .env file exists." + ); +} + +async function btcToEvmTransfer(): Promise { + // pre setup + initEccLib(tinysecp); + const bip32 = BIP32Factory(tinysecp); + console.log("Transfer BTC to EVM"); + const seed = await mnemonicToSeed(MNEMONIC); + const rootKey = bip32.fromSeed(seed, networks.testnet); + const derivedNode = rootKey.derivePath(DERIVATION_PATH); + + const feeRate = await getFeeEstimates('5'); + const utxos = await fetchUTXOS(ADDRESS as unknown as string); + + const processedUtxos = processUtxos(utxos, AMOUNT); + + const mapedUtxos = processedUtxos.map((utxo) => ({ + utxoTxId: utxo.txid, + utxoOutputIndex: utxo.vout, + utxoAmount: BigInt(utxo.value), + })) as unknown as UTXOData[]; + + const size = calculateSize({ + utxoData: mapedUtxos, + network: networks.testnet, + publicKey: derivedNode.publicKey, + depositAddress: ADDRESS as unknown as string, + domainId: DESTINATION_CHAIN_ID, + amount: AMOUNT, + feeValue: BigInt(0), + changeAddress: ADDRESS as unknown as string, + signer: derivedNode, + typeOfAddress: TypeOfAddress.P2WPKH, + }); // aprox estimation of the size of the tx + + const transferParams: BitcoinTransferParams = { + source: SOURCE_CAIPID, + destination: DESTINATION_CHAIN_ID, + destinationAddress: DESTINATION_ADDRESS, + amount: AMOUNT, + resource: RESOURCE_ID, + utxoData: mapedUtxos, + publicKey: derivedNode.publicKey, + typeOfAddress: TypeOfAddress.P2WPKH, + network: networks.testnet, + changeAddress: ADDRESS, + feeRate: BigInt(Math.ceil(feeRate)), + size: BigInt(size), + }; + + const transfer = await createBitcoinFungibleTransfer(transferParams); + + const psbt = transfer.getTransferTransaction(); + + console.log("Signing the transaction"); + + psbt.signAllInputs(derivedNode); + psbt.finalizeAllInputs(); + + console.log("Extracting the transaction"); + const tx = psbt.extractTransaction(true); + console.log("Transaction hex", tx.toHex()); + + const txId = await broadcastTransaction(tx.toHex()); + console.log("Transaction broadcasted", `${EXPLORER_URL}/tx/${txId}`); +} + +btcToEvmTransfer().finally(() => { }); diff --git a/examples/bitcoin-to-evm-fungible-transfer/tsconfig.json b/examples/bitcoin-to-evm-fungible-transfer/tsconfig.json new file mode 100644 index 000000000..4078b3ff7 --- /dev/null +++ b/examples/bitcoin-to-evm-fungible-transfer/tsconfig.json @@ -0,0 +1,21 @@ +{ + "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" + ] +} \ No newline at end of file diff --git a/examples/evm-to-bitcoin-fungible-transfer/.env.sample b/examples/evm-to-bitcoin-fungible-transfer/.env.sample new file mode 100644 index 000000000..4bb371177 --- /dev/null +++ b/examples/evm-to-bitcoin-fungible-transfer/.env.sample @@ -0,0 +1,4 @@ +SEPOLIA_RPC_URL="SEPOLIA_RPC_URL_HERE" +BTC_DESTINATION_ADDRESS="YOUR_BTC_DESTINATION_ADDRESS" +PRIVATE_KEY="" +SYGMA_ENV=devnet \ No newline at end of file diff --git a/examples/evm-to-bitcoin-fungible-transfer/README.md b/examples/evm-to-bitcoin-fungible-transfer/README.md new file mode 100644 index 000000000..bc19ff05b --- /dev/null +++ b/examples/evm-to-bitcoin-fungible-transfer/README.md @@ -0,0 +1,81 @@ +# Sygma SDK EVM to BTC example + +This is an example that demonstrates functionality of the protocol using Sygma SDK. The src/transfer.ts script showcases how bitcoins can be transferred from an EVM Sepolia account utilizing `@buildwithsygma/evm` package. + +## Prerequisites + +Before running the script, ensure that you have the following: + +- Node.js +- Yarn (version 3.4.1 or higher) +- A development wallet funded with `sygBTC` tokens from the [Sygma faucet](https://faucet-ui-stage.buildwithsygma.com/) +- The [exported private key](https://support.metamask.io/hc/en-us/articles/360015289632-How-to-export-an-account-s-private-key) of your development wallet +- [Sepolia ETH](https://www.alchemy.com/faucets/ethereum-sepolia) for gas +- An Ethereum [provider](https://www.infura.io/) (in case the hardcoded RPC within the script does not work) + +## Getting started + +### 1. Clone the repository + +To get started, clone this repository to your local machine with: + +```bash +git clone git@github.com: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. To run the example, you will need to configure your environment variable to include your test development account's [exported private key](https://support.metamask.io/hc/en-us/articles/360015289632-How-to-export-an-account-s-private-key). 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 evm-to-evm example folder: + +```bash +cd examples/evm-to-btc-fungible-transfer +touch .env +``` + +Replace between the quotation marks your exported private key: + +`PRIVATE_KEY="YOUR_PRIVATE_KEY_HERE"` + +To send an ERC20 example transfer run: + +```bash +yarn run transfer +``` + +The example will use `ethers` in conjuction with the sygma-sdk to +create a transfer from `Sepolia` to `Testnet BTC` with a test sygBTC token. + +Replace the placeholder values in the `.env` file with your own Ethereum wallet private key. + +## Script Functionality + +This example script performs the following steps: +- initializes the SDK and establishes a connection to the Ethereum provider. +- retrieves the list of supported domains and resources from the SDK configuration. +- Searches for the sygBTC token resource with the specified symbol +- Searches for the Bitcoin Testnet and Sepolia domains in the list of supported domains based on their chain IDs and CaipId +- Constructs a transfer object that defines the details of the sygBTC token transfer +- Retrieves the fee required for the transfer from the SDK. +- Builds the necessary approval transactions for the transfer and sends them using the Ethereum wallet. The approval transactions are required to authorize the transfer of ERC20 token. +- Builds the final transfer transaction and sends it using the Ethereum wallet. diff --git a/examples/evm-to-bitcoin-fungible-transfer/package.json b/examples/evm-to-bitcoin-fungible-transfer/package.json new file mode 100644 index 000000000..e52dbefec --- /dev/null +++ b/examples/evm-to-bitcoin-fungible-transfer/package.json @@ -0,0 +1,35 @@ +{ + "name": "@buildwithsygma/sygma-sdk-evm-to-bitcoin-fungible-transfer", + "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/core": "workspace:^", + "@buildwithsygma/evm": "workspace:^", + "tsx": "^4.15.4" + } +} diff --git a/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts b/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts new file mode 100644 index 000000000..d525b620d --- /dev/null +++ b/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts @@ -0,0 +1,54 @@ +import { createEvmFungibleAssetTransfer } from "@buildwithsygma/evm"; +import dotenv from "dotenv"; +import { Wallet, providers } from "ethers"; +import Web3HttpProvider from "web3-providers-http"; + +dotenv.config(); + +const privateKey = process.env.PRIVATE_KEY; + +if (!privateKey) { + throw new Error("Missing environment variable: PRIVATE_KEY"); +} + +const SEPOLIA_CHAIN_ID = 11155111; +const BITCOIN_DOMAIN_CAIPID = "bip122:000000000933ea01ad0ee984209779ba"; +const RESOURCE_ID = "0x0000000000000000000000000000000000000000000000000000000000000700"; +const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "https://eth-sepolia-public.unifra.io" +const BTC_DESTINATION_ADDRESS = process.env.BTC_DESTINATION_ADDRESS; + +const explorerUrls: Record = { [SEPOLIA_CHAIN_ID]: 'https://sepolia.etherscan.io' }; +const getTxExplorerUrl = (params: { txHash: string; chainId: number }): string => `${explorerUrls[params.chainId]}/tx/${params.txHash}`; + +export async function erc20Transfer(): Promise { + const web3Provider = new Web3HttpProvider(SEPOLIA_RPC_URL); + const ethersWeb3Provider = new providers.Web3Provider(web3Provider); + const wallet = new Wallet(privateKey!, ethersWeb3Provider); + + const params = { + source: SEPOLIA_CHAIN_ID, + destination: BITCOIN_DOMAIN_CAIPID, + sourceNetworkProvider: web3Provider, + resource: RESOURCE_ID, + amount: BigInt(1) * BigInt(1e8), // or any amount to send + destinationAddress: BTC_DESTINATION_ADDRESS, + sourceAddress: await wallet.getAddress(), + }; + + const transfer = await createEvmFungibleAssetTransfer(params); + + const approvals = await transfer.getApprovalTransactions(); + console.log(`Approving Tokens (${approvals.length})...`); + for (const approval of approvals) { + const response = await wallet.sendTransaction(approval); + await response.wait(); + console.log(`Approved, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`); + } + + const transferTx = await transfer.getTransferTransaction(); + const response = await wallet.sendTransaction(transferTx); + await response.wait(); + console.log(`Deposited, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`); +} + +erc20Transfer().finally(() => { }); \ No newline at end of file diff --git a/examples/evm-to-bitcoin-fungible-transfer/tsconfig.json b/examples/evm-to-bitcoin-fungible-transfer/tsconfig.json new file mode 100644 index 000000000..4078b3ff7 --- /dev/null +++ b/examples/evm-to-bitcoin-fungible-transfer/tsconfig.json @@ -0,0 +1,21 @@ +{ + "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" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 9f7598972..20826a41f 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,11 @@ "evm:lint": "yarn workspace @buildwithsygma/evm lint", "evm:lint:fix": "yarn workspace @buildwithsygma/evm lint:fix", "evm:test:unit": "yarn workspace @buildwithsygma/evm test:unit", + "bitcoin:build": "yarn workspace @buildwithsygma/bitcoin build:all", + "bitcoin:cleanDist": "yarn workspace @buildwithsygma/bitcoin clean", + "bitcoin:test": "yarn workspace @buildwithsygma/bitcoin test", + "bitcoin:lint": "yarn workspace @buildwithsygma/bitcoin lint", + "bitcoin:lint:fix": "yarn workspace @buildwithsygma/bitcoin lint:fix", "substrate:build": "yarn workspace @buildwithsygma/substrate build:all", "substrate:cleanDist": "yarn workspace @buildwithsygma/substrate clean", "substrate:test": "yarn workspace @buildwithsygma/substrate test", diff --git a/packages/bitcoin/.eslintrc.cjs b/packages/bitcoin/.eslintrc.cjs new file mode 100644 index 000000000..29c27c015 --- /dev/null +++ b/packages/bitcoin/.eslintrc.cjs @@ -0,0 +1,10 @@ +module.exports = { + extends: '../../.eslintrc.cjs', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + sourceType: 'module', + tsconfigRootDir: __dirname, + }, +}; diff --git a/packages/bitcoin/.gitignore b/packages/bitcoin/.gitignore new file mode 100644 index 000000000..15b2f08aa --- /dev/null +++ b/packages/bitcoin/.gitignore @@ -0,0 +1,45 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build +/dist +/dist-cjs +/dist-esm +/types + + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.env +.vscode +.history + +# runtime config +public/chainbridge-runtime-config.js + +# IDE +.idea/ + +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions \ No newline at end of file diff --git a/packages/bitcoin/.npmignore b/packages/bitcoin/.npmignore new file mode 100644 index 000000000..f98c1c55b --- /dev/null +++ b/packages/bitcoin/.npmignore @@ -0,0 +1,26 @@ + + +integration/ +coverage/ +dist/ +keysN1/ +keysN2/ +config/ +src/ + +# Ignore configs +setupTests.ts +.eslintrc.cjs +.prettierrc.json +docker-compose.yml +Dockerfile +jest.config.cjs +tsconfig.json +tsconfig.*.json +tsconfig.tsbuildinfo +rollup.config.js +**/__test__/* + +# Ignore random junk +.DS_Store +node_modules/** \ No newline at end of file diff --git a/packages/bitcoin/.prettierrc.json b/packages/bitcoin/.prettierrc.json new file mode 100644 index 000000000..c50ce2de0 --- /dev/null +++ b/packages/bitcoin/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "singleQuote": true, + "trailingComma": "all", + "useTabs": false, + "arrowParens": "avoid" +} diff --git a/packages/bitcoin/README.md b/packages/bitcoin/README.md new file mode 100644 index 000000000..2cd20cd9a --- /dev/null +++ b/packages/bitcoin/README.md @@ -0,0 +1,138 @@ +## Introduction + +This package provides the latest typescript Bitcoin SDK for building products using Sygma Protocol. + +## Installation + +``` +yarn add @buildwithsygma/bitcoin +``` + +or + +``` +npm install @buildwithsygma/bitcoin +``` + +## Environment Setup + +Make sure to set environment variable `SYGMA_ENV` to either `TESTNET` or `MAINNET` prior to using the SDK. + +## Support. + +Bridge configuration and list of supported networks for each environment can be found at: [Sygma bridge shared configuration github](https://github.com/sygmaprotocol/sygma-shared-configuration) + +## Usage + +### Bitcoin transfer + +#### Preparations + +You can use our utils to fetch the UTXOS needed from your address to transfer the assets: + +```typescript +import { + broadcastTransaction, + fetchUTXOS, + getFeeEstimates, + processUtxos, +} from '@buildwithsygma/utils'; +import type { UTXOData } from '@buildwithsygma/bitcoin'; + +// Either P2TR address or P2WPKH address +const utxos = await fetchUTXOS('tb1...' as unknown as string); + +const processedUtxos = processUtxos(utxos, 1e8); + +// Here we map the UTXOs to match the shape that the SDK expect, to use them as inputs in the transaction +const mapedUtxos = processedUtxos.map(utxo => ({ + utxoTxId: utxo.txid, + utxoOutputIndex: utxo.vout, + utxoAmount: BigInt(utxo.value), +})) as unknown as UTXOData[]; + +// You can also get estimation of the fees per block confirmation in either testnet or mainnet +const feeRate = await getFeeEstimates('5'); +``` + +#### Pay to Taproot transfer + +```typescript +import { createBitcoinFungibleTransfer, TypeOfAddress } from '@buildwithsygma/bitcoin'; +import type { BitcoinTransferParams, UTXOData } from '@buildwithsygma/bitcoin'; +import { networks } from 'bitcoinjs-lib'; +import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371'; + +// Here we create the public key needed for the P2TR transfer. You can reference our bitcoin-to-evm example to check the steps to get the public key and the signer +// derivedNode.publicKey is one of your account given the derivation path that you provide +const publicKeyDropedDERHeader = toXOnly(derivedNode.publicKey); + +const transferParams: BitcoinTransferParams = { + source: 'bip122:000000000933ea01ad0ee984209779ba', + destination: 11155111, + destinationAddress: '0x...', // evm address here + amount: 1e8, + resource: '0x0000000000000000000000000000000000000000000000000000000000000300', + utxoData: mapedUtxos, + feeRate: BigInt(Math.ceil(feeRate)), + publicKey: publicKeyDropedDERHeader, + typeOfAddress: TypeOfAddress.P2TR, + network: networks.testnet, + changeAddress: 'tb1...', // the address to receive the amount that's left after the transaction + size: BigInt(size), // size of the transfer, either based on other transfer, some calculation or the number you want to input here +}; + +const transfer = await createBitcoinFungibleTransfer(transferParams); + +const psbt = transfer.getTransferTransaction(); + +psbt.signAllInputs(signer); // tweaked signer, check our example for further reference on how to get this for P2TR transfers +psbt.finalizeAllInputs(); +const tx = psbt.extractTransaction(true); + +// You can check the hex encoded raw signed transaction +console.log('Transaction hex', tx.toHex()); +``` + +#### Pay to Witness Public Key Hash + +```typescript +import type { BitcoinTransferParams, UTXOData } from '@buildwithsygma/bitcoin'; +import { createBitcoinFungibleTransfer, TypeOfAddress } from '@buildwithsygma/bitcoin'; +import { networks } from 'bitcoinjs-lib'; + +const derivedNode = rootKey.derivePath('your-derivation-path'); + +const transferParams: BitcoinTransferParams = { + source: 'bip122:000000000933ea01ad0ee984209779ba', + destination: 11155111, + destinationAddress: '0x...', // evm address here + amount: 1e8, + resource: '0x0000000000000000000000000000000000000000000000000000000000000300', + utxoData: mapedUtxos, + publicKey: derivedNode.publicKey, + typeOfAddress: TypeOfAddress.P2WPKH, + network: networks.testnet, + changeAddress: 'tb1...', // the address to receive the amount that's left after the transaction + feeRate: BigInt(Math.ceil(feeRate)), + size: BigInt(size), // size of the transfer, either based on other transfer, some calculation or the number you want to input here +}; + +const transfer = await createBitcoinFungibleTransfer(transferParams); + +const psbt = transfer.getTransferTransaction(); + +psbt.signAllInputs(signer); // signer, check our example for further reference on how to get this for P2WPKH transfers +psbt.finalizeAllInputs(); +const tx = psbt.extractTransaction(true); + +// You can check the hex encoded raw signed transaction +console.log('Transaction hex', tx.toHex()); +``` + +## Examples + +The SDK monorepo contains the following examples demonstrating the usage of EVM Package: + +1. [Pay to Taproot address](https://github.com/sygmaprotocol/sygma-sdk/tree/main/examples/bitcoin-to-evm-fungible-transfer) +2. [Pay to Witnes Public Key Hahs](https://github.com/sygmaprotocol/sygma-sdk/tree/main/examples/evm-to-bitcoin-fungible-transfer) diff --git a/packages/bitcoin/jest.config.cjs b/packages/bitcoin/jest.config.cjs new file mode 100644 index 000000000..c85c0e8fd --- /dev/null +++ b/packages/bitcoin/jest.config.cjs @@ -0,0 +1,19 @@ +module.exports = { + roots: ['/src', '/test'], + extensionsToTreatAsEsm: ['.ts', '.tsx'], + verbose: true, + preset: 'ts-jest/presets/default-esm', + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + testEnvironment: 'node', + testTimeout: 15000, + transform: { + '^.+\\.(ts|tsx)?$': ['ts-jest', { useESM: true }], + }, + testPathIgnorePatterns: ['./dist'], + automock: false, + setupFiles: [ + "/test/setupJest.js" + ] + }; diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json new file mode 100644 index 000000000..a27faccbc --- /dev/null +++ b/packages/bitcoin/package.json @@ -0,0 +1,66 @@ +{ + "name": "@buildwithsygma/bitcoin", + "version": "1.0.0", + "description": "Core primitives for bridging and message passing", + "main": "dist-esm/index.js", + "types": "types/index.d.ts", + "exports": { + ".": { + "import": "./dist-esm/index.js", + "require": "./dist-cjs/index.js" + } + }, + "type": "module", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/sygmaprotocol/sygma-sdk" + }, + "scripts": { + "test": "jest --watchAll --detectOpenHandles", + "test:unit": "jest --detectOpenHandles", + "run:all": "concurrently \"yarn run prepareNodes\" \"yarn run test\"", + "build:esm": "tsc -p tsconfig.esm.json && echo '{\"type\": \"module\"}' > ./dist-esm/package.json", + "build:cjs": "tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist-cjs/package.json", + "build:types": "tsc -p tsconfig.types.json", + "build:all": "yarn build:esm && yarn build:cjs && yarn build:types", + "build:typedocs:html": "typedoc --options typedoc.json", + "build:typedocs:markdown": "typedoc --options typedoc.markdown.json", + "build:typedocs:asjson": "typedoc --options typedoc.asjson.json", + "lint": "eslint 'src/**/*.ts'", + "lint:fix": "yarn run lint --fix", + "run:nodes": "docker-compose -f docker-compose.yml up", + "clean": "rm -rf ./dist ./dist-cjs ./dist-esm ./types", + "watch": "tsc -w -p tsconfig.esm.json && echo '{\"type\": \"module\"}' > ./dist-esm/package.json" + }, + "keywords": [ + "sygma", + "sygmaprotocol", + "buildwithsygma", + "web3", + "bridge", + "bitcoin" + ], + "author": "Sygmaprotocol Product Team", + "license": "LGPL-3.0-or-later", + "devDependencies": { + "@types/jest": "^29.4.0", + "concurrently": "7.0.0", + "eslint": "8", + "hardhat": "2.8.2", + "jest": "^29.4.1", + "jest-environment-jsdom": "^29.4.1", + "jest-extended": "1.2.0", + "jest-fetch-mock": "^3.0.3", + "tiny-secp256k1": "2.2.3", + "ts-jest": "^29.0.5", + "ts-node": "10.9.1", + "typedoc": "^0.24.1", + "typedoc-plugin-markdown": "^3.15.1", + "typescript": "5.0.4" + }, + "dependencies": { + "@buildwithsygma/core": "workspace:^", + "bitcoinjs-lib": "^6.1.6" + } +} diff --git a/packages/bitcoin/src/__test__/fungible.test.ts b/packages/bitcoin/src/__test__/fungible.test.ts new file mode 100644 index 000000000..75f2fcd51 --- /dev/null +++ b/packages/bitcoin/src/__test__/fungible.test.ts @@ -0,0 +1,231 @@ +import { Config } from '@buildwithsygma/core'; +import * as bitcoin from 'bitcoinjs-lib'; +import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371'; +import * as tinysecp from 'tiny-secp256k1'; + +import { createBitcoinFungibleTransfer } from '../fungible.js'; +import type { BitcoinTransferParams } from '../types.js'; +import { TypeOfAddress } from '../types.js'; + +bitcoin.initEccLib(tinysecp); + +const P2TR_TRANSFER_PARAMS: BitcoinTransferParams = { + sourceAddress: 'tb1pxmrzd94rs6wtg6ewdjfmuu7s88n2kdqc20vzfmadanfaem3n9sdq0vagu0', + source: 'bip122:000000000933ea01ad0ee984209779ba', + destination: 1, + destinationAddress: '0x98729c03c4D5e820F5e8c45558ae07aE63F97461', + amount: BigInt(90000000), + resource: '0x0000000000000000000000000000000000000000000000000000000000000300', + utxoData: [ + { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + utxoAmount: BigInt(100000000), + utxoOutputIndex: 0, + }, + ], + publicKey: toXOnly( + Buffer.from('03feca449bd5b50085d23864a006f6ea4da80ff63816033f6437193c66bac7488c', 'hex'), + ), + typeOfAddress: TypeOfAddress.P2TR, + network: bitcoin.networks.testnet, + changeAddress: 'tb1pxmrzd94rs6wtg6ewdjfmuu7s88n2kdqc20vzfmadanfaem3n9sdq0vagu0', + feeRate: BigInt(103), + size: BigInt(400), +}; + +const P2PWKH_TRANSFER_PARAMS: BitcoinTransferParams = { + ...P2TR_TRANSFER_PARAMS, + typeOfAddress: TypeOfAddress.P2WPKH, + publicKey: Buffer.from( + '03feca449bd5b50085d23864a006f6ea4da80ff63816033f6437193c66bac7488c', + 'hex', + ), +}; + +const MOCKED_CONFIG = { + init: jest.fn(), + getDomainConfig: jest + .fn() + .mockReturnValue({ bridge: '', caipId: 'bip122:000000000933ea01ad0ee984209779ba' }), + getDomain: jest.fn().mockReturnValue({ + caipId: 'bip122:000000000933ea01ad0ee984209779ba', + feeAddress: 'tb1p0r2w3ugreaggd7nakw2wd04up6rl8k0cce8eetxwmhnrelgqx87s4zdkd7', + }), + getResources: jest.fn().mockReturnValue([ + { + resourceId: '0x0000000000000000000000000000000000000000000000000000000000000300', + type: 'fungible', + address: 'tb1pxmrzd94rs6wtg6ewdjfmuu7s88n2kdqc20vzfmadanfaem3n9sdq0vagu0', + decimals: 8, + tweak: 'd97ae87c238a8a674bff71db5eeb69519dbd1c57bec70a89f7b06fa2d0e97841', + feeAmount: '1000000', + }, + ]), + findDomainConfigBySygmaId: jest + .fn() + .mockReturnValue({ caipId: 'bip122:000000000933ea01ad0ee984209779ba' }), +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-return +jest.mock('@buildwithsygma/core', () => ({ + ...jest.requireActual('@buildwithsygma/core'), + Config: jest.fn(), +})); + +describe('Fungible - createBitcoinFungibleTransfer', () => { + beforeAll(() => { + (Config as jest.Mock).mockReturnValue(MOCKED_CONFIG); + }); + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + describe('Fungible - createBitcoinFungibleTransfer - P2TR', () => { + it('should create a bitcoin fungible transfer', async () => { + const transfer = await createBitcoinFungibleTransfer(P2TR_TRANSFER_PARAMS); + expect(transfer).toBeTruthy(); + }); + + it('should throw an error when resource is not found', async () => { + const transferParams = { ...P2TR_TRANSFER_PARAMS, resource: 'tb...' }; + const transfer = createBitcoinFungibleTransfer(transferParams); + await expect(transfer).rejects.toThrow('Resource not found.'); + }); + + it('should return PSBT instance', async () => { + const transfer = await createBitcoinFungibleTransfer(P2TR_TRANSFER_PARAMS); + const psbt = transfer.getTransferTransaction(); + expect(psbt).toBeTruthy(); + expect(psbt instanceof bitcoin.Psbt).toBeTruthy(); + }); + + it('should throw if the utxo amount is equal to the amount to transfer', async () => { + const transferParams = { ...P2TR_TRANSFER_PARAMS, amount: BigInt(100000000) }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + + it('should throw if the utxo amount is less than the amount to transfer', async () => { + const transferParams = { ...P2TR_TRANSFER_PARAMS, amount: BigInt(100000001) }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + + it('should throw if public key is incorrect', async () => { + const transferParams = { ...P2TR_TRANSFER_PARAMS, publicKey: Buffer.from('', 'hex') }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + + it('should throw if utxoData is partially defined', async () => { + const transferParams: BitcoinTransferParams = { + ...P2TR_TRANSFER_PARAMS, + utxoData: [ + { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + }, + ] as unknown as BitcoinTransferParams['utxoData'], + }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + it('should throw given an array of multiples utxo and amount thats bigger than the sum of the values', async () => { + const transferParams: BitcoinTransferParams = { + ...P2TR_TRANSFER_PARAMS, + utxoData: [ + { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + utxoAmount: BigInt(100000000), + utxoOutputIndex: 0, + }, + { + utxoTxId: '56796b5daa09cf5299784593a369669090d26cc70e96b6e5a1a510a417054b21', + utxoAmount: BigInt(100000000), + utxoOutputIndex: 0, + }, + ], + amount: BigInt(600000000), + }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + + it('should process multiple utxo inputs and create a valid PSBT', async () => { + const transferParams: BitcoinTransferParams = { + ...P2TR_TRANSFER_PARAMS, + utxoData: [ + { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + utxoAmount: BigInt(100000000), + utxoOutputIndex: 0, + }, + { + utxoTxId: '56796b5daa09cf5299784593a369669090d26cc70e96b6e5a1a510a417054b21', + utxoAmount: BigInt(100000000), + utxoOutputIndex: 0, + }, + ], + amount: BigInt(3000000), + }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + const psbt = transfer.getTransferTransaction(); + expect(psbt).toBeTruthy(); + }); + }); + + describe('Fungible - createBitcoinFungibleTransfer - P2WPKH', () => { + it('should create a bitcoin fungible transfer', async () => { + const transfer = await createBitcoinFungibleTransfer(P2PWKH_TRANSFER_PARAMS); + expect(transfer).toBeTruthy(); + }); + + it('should throw an error when resource is not found', async () => { + const transferParams = { ...P2PWKH_TRANSFER_PARAMS, resource: 'tb...' }; + const transfer = createBitcoinFungibleTransfer(transferParams); + await expect(transfer).rejects.toThrow('Resource not found.'); + }); + + it('should return PSBT instance', async () => { + const transfer = await createBitcoinFungibleTransfer(P2PWKH_TRANSFER_PARAMS); + const psbt = transfer.getTransferTransaction(); + expect(psbt).toBeTruthy(); + expect(psbt instanceof bitcoin.Psbt).toBeTruthy(); + }); + + it('should throw if the utxo amount is equal to the amount to transfer', async () => { + const transferParams = { ...P2PWKH_TRANSFER_PARAMS, amount: BigInt(100000000) }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + + it('should throw if the utxo amount is less than the amount to transfer', async () => { + const transferParams = { ...P2PWKH_TRANSFER_PARAMS, amount: BigInt(100000001) }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + expect(() => transfer.getTransferTransaction()).toThrow( + 'Not enough funds to spend from the UTXO', + ); + }); + + it('should throw if public key is incorrect', async () => { + const transferParams = { ...P2PWKH_TRANSFER_PARAMS, publicKey: Buffer.from('', 'hex') }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + + it('should throw if utxoData is partially defined', async () => { + const transferParams: BitcoinTransferParams = { + ...P2PWKH_TRANSFER_PARAMS, + utxoData: [ + { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + }, + ] as unknown as BitcoinTransferParams['utxoData'], + }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + }); +}); diff --git a/packages/bitcoin/src/environment.d.ts b/packages/bitcoin/src/environment.d.ts new file mode 100644 index 000000000..f7f28e351 --- /dev/null +++ b/packages/bitcoin/src/environment.d.ts @@ -0,0 +1,9 @@ +import type { Environment } from '@buildwithsygma/core'; + +declare global { + namespace NodeJS { + interface ProcessEnv { + SYGMA_ENV: Environment; + } + } +} diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts new file mode 100644 index 000000000..3fef457a8 --- /dev/null +++ b/packages/bitcoin/src/fungible.ts @@ -0,0 +1,71 @@ +import { BaseTransfer, Config, Environment } from '@buildwithsygma/core'; +import type { Config as TConfig, BitcoinResource } from '@buildwithsygma/core/types'; +import type { networks } from 'bitcoinjs-lib'; + +import type { + BitcoinTransferParams, + BitcoinTransaction, + TypeOfAddress, + UTXOData, +} from './types.js'; +import { getPsbt } from './utils/index.js'; + +export async function createBitcoinFungibleTransfer( + params: BitcoinTransferParams, +): Promise { + const config = new Config(); + await config.init(process.env.SYGMA_ENV || Environment.MAINNET); + return new BitcoinFungibleTransfer(params, config); +} + +class BitcoinFungibleTransfer extends BaseTransfer { + protected publicKey: Buffer; + protected typeOfAddress: TypeOfAddress; + protected network: networks.Network; + protected changeAddress?: string; + protected feeRate: bigint; + protected utxoData: UTXOData[]; + protected size: bigint; + protected destinationAddress: string; + protected amount: bigint; + protected feeAddress: string; + protected feeAmount: bigint; + + constructor(transfer: BitcoinTransferParams, config: TConfig) { + super(transfer, config); + this.destinationAddress = transfer.destinationAddress; + this.amount = transfer.amount; + this.publicKey = transfer.publicKey; + this.typeOfAddress = transfer.typeOfAddress; + this.network = transfer.network; + this.changeAddress = transfer.changeAddress; + this.feeRate = transfer.feeRate; + this.utxoData = transfer.utxoData; + this.size = transfer.size; + + this.feeAddress = this.sourceDomain.feeAddress as string; + this.feeAmount = BigInt((this.resource as BitcoinResource).feeAmount!); + } + + getTransferTransaction(): BitcoinTransaction { + return getPsbt( + { + source: this.sourceDomain.caipId, + destination: this.destinationDomain.id, + destinationAddress: this.destinationAddress, + amount: this.amount, + resource: this.resource.resourceId, + utxoData: this.utxoData, + publicKey: this.publicKey, + typeOfAddress: this.typeOfAddress, + network: this.network, + feeRate: this.feeRate, + changeAddress: this.changeAddress, + size: this.size, + }, + this.feeAddress, + (this.resource as BitcoinResource).address, + this.feeAmount, + ); + } +} diff --git a/packages/bitcoin/src/index.ts b/packages/bitcoin/src/index.ts new file mode 100644 index 000000000..d88873593 --- /dev/null +++ b/packages/bitcoin/src/index.ts @@ -0,0 +1,2 @@ +export * from './fungible.js'; +export * from './types.js'; diff --git a/packages/bitcoin/src/types.ts b/packages/bitcoin/src/types.ts new file mode 100644 index 000000000..a54db36e3 --- /dev/null +++ b/packages/bitcoin/src/types.ts @@ -0,0 +1,62 @@ +import type { BaseTransferParams } from '@buildwithsygma/core'; +import type { networks, Psbt } from 'bitcoinjs-lib'; + +export enum TypeOfAddress { + P2WPKH = 'P2WPKH', + P2TR = 'P2TR', +} + +export type UTXOData = { + utxoTxId: string; + utxoAmount: bigint; + utxoOutputIndex: number; +}; + +export type CreateInputData = { + utxoData: UTXOData; + publicKey: Buffer; + network: networks.Network; + typeOfAddress: TypeOfAddress; +}; + +export interface BitcoinTransferParams extends BaseTransferParams { + destinationAddress: string; + amount: bigint; + utxoData: UTXOData[]; + publicKey: Buffer; + typeOfAddress: TypeOfAddress; + network: networks.Network; + feeRate: bigint; + changeAddress?: string; + size: bigint; +} + +export type CreatePsbtParams = Pick< + BitcoinTransferParams, + | 'source' + | 'destination' + | 'destinationAddress' + | 'amount' + | 'resource' + | 'utxoData' + | 'publicKey' + | 'typeOfAddress' + | 'network' + | 'feeRate' + | 'changeAddress' + | 'size' +>; + +export type BitcoinTransaction = Psbt; + +export type PaymentReturnData = { + output: Buffer; + address?: string; +}; + +export type BitcoinTransferInputData = { + hash: string | Buffer; + index: number; + witnessUtxo: { value: number; script: Buffer }; + tapInternalKey?: Buffer; +}; diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts new file mode 100644 index 000000000..8373ac1ee --- /dev/null +++ b/packages/bitcoin/src/utils/helpers.ts @@ -0,0 +1,203 @@ +import type { networks, Payment } from 'bitcoinjs-lib'; +import { payments, Psbt } from 'bitcoinjs-lib'; + +import { TypeOfAddress } from '../types.js'; +import type { + BitcoinTransferInputData, + BitcoinTransaction, + PaymentReturnData, + UTXOData, + CreateInputData, + CreatePsbtParams, +} from '../types.js'; + +/** + * Get the scriptPubKey for the given public key and network + * + * @category Helpers + * @param typeOfAddress - type of address to use: currently p2wpkh or p2tr + * @param publicKey - public of the signer + * @param network - network to use + * @returns {scriptPubKey: Buffer} + */ +export function getScriptPubkey( + typeOfAddress: TypeOfAddress, + publicKey: Buffer, + network: networks.Network, +): { + scriptPubKey: Buffer; +} { + const { output } = + typeOfAddress === TypeOfAddress.P2WPKH + ? (payments.p2wpkh({ + pubkey: publicKey, + network, + }) as PaymentReturnData) + : (payments.p2tr({ + internalPubkey: publicKey, + network, + }) as PaymentReturnData); + + return { scriptPubKey: output }; +} + +/** + * Encode the deposit address and the domain id to pass into the OP_RETURN output + * + * @category Helpers + * @param depositAddress - address to deposit + * @param destinationDomainId - destination domain id + * @returns {Payment} + */ +function encodeDepositAddress(depositAddress: string, destinationDomainId: number): Payment { + return payments.embed({ + data: [Buffer.from(`${depositAddress}_${destinationDomainId}`)], + }); +} + +/** + * Create the input data for the PSBT + * + * @category Helpers + * @param utxoData - UTXO data + * @param publicKey - public key of the signer + * @param network - network to use + * @param typeOfAddress - type of address to use + * @returns {BitcoinTransferInputData} + */ +export function createInputData({ + utxoData: { utxoTxId, utxoOutputIndex, utxoAmount }, + publicKey, + network, + typeOfAddress, +}: CreateInputData): BitcoinTransferInputData { + if (typeOfAddress !== TypeOfAddress.P2TR) { + return { + hash: utxoTxId as unknown as Buffer, + index: utxoOutputIndex, + witnessUtxo: { + script: getScriptPubkey(typeOfAddress, publicKey, network).scriptPubKey, + value: Number(utxoAmount), + }, + }; + } + return { + hash: utxoTxId, + index: utxoOutputIndex, + witnessUtxo: { + script: getScriptPubkey(typeOfAddress, publicKey, network).scriptPubKey as unknown as Buffer, + value: Number(utxoAmount), + }, + tapInternalKey: publicKey, + }; +} + +/** + * Check if the amount to transfer is valid + * @category Helpers + * @param amount - amount to transfer + * @param utxoData - UTXO data + * @returns {boolean} + */ +const isValidAmount = (amount: bigint, utxoData: UTXOData[]): boolean => { + return utxoData.reduce((acc, curr) => acc + curr.utxoAmount, BigInt(0)) <= amount; +}; + +/** + * Create the PSBT for the transfer using the input data supplied and adding the ouputs to use for the transaction + * + * @category Helpers + * @param params - params to create the PSBT + * @param feeAddress - fee handler address on BTC + * @param depositAddress - bridge address on BTC + * @param feeAmount - fee amount to be paid + * @returns {BitcoinTransferRequest} + */ +export function getPsbt( + params: CreatePsbtParams, + feeAddress: string, + depositAddress: string, + feeAmount: bigint, +): BitcoinTransaction { + if (!['P2WPKH', 'P2TR'].includes(params.typeOfAddress.toString())) { + throw new Error('Unsuported address type'); + } + + if (params.utxoData.length === 0) { + throw new Error('UTXO data is required'); + } + + if (isValidAmount(params.amount, params.utxoData)) { + throw new Error('Not enough funds to spend from the UTXO'); + } + + const psbt = new Psbt({ network: params.network }); + + if (params.utxoData.length !== 1) { + params.utxoData.forEach(utxo => { + psbt.addInput( + createInputData({ + utxoData: utxo, + publicKey: params.publicKey, + network: params.network, + typeOfAddress: params.typeOfAddress, + }), + ); + }); + } else { + psbt.addInput( + createInputData({ + utxoData: params.utxoData[0], + publicKey: params.publicKey, + network: params.network, + typeOfAddress: params.typeOfAddress, + }), + ); + } + + // OP_RETURN output + psbt.addOutput({ + script: encodeDepositAddress(params.destinationAddress, Number(params.destination)) + .output as unknown as Buffer, + value: 0, + }); + + // Fee output + psbt.addOutput({ + address: feeAddress, + value: Number(feeAmount), + }); + + const minerFee = Math.floor(Number(params.feeRate) * Number(params.size)); + + let amountToSpent: number; + if (params.utxoData.length !== 1) { + amountToSpent = + params.utxoData.reduce((acc, curr) => acc + Number(curr.utxoAmount), 0) - + Number(feeAmount) - + minerFee; + } else { + amountToSpent = Number(params.utxoData[0].utxoAmount) - Number(feeAmount) - minerFee; + } + + if (amountToSpent < params.amount) { + throw new Error('Not enough funds'); + } + + // Amount to bridge + psbt.addOutput({ + address: depositAddress, + value: Number(params.amount), + }); + + if (params.changeAddress && amountToSpent > params.amount) { + const change = Number(amountToSpent) - Number(params.amount); + + psbt.addOutput({ + address: params.changeAddress, + value: change, + }); + } + + return psbt; +} diff --git a/packages/bitcoin/src/utils/index.ts b/packages/bitcoin/src/utils/index.ts new file mode 100644 index 000000000..2942c10a7 --- /dev/null +++ b/packages/bitcoin/src/utils/index.ts @@ -0,0 +1 @@ +export * from './helpers.js'; diff --git a/packages/bitcoin/test/setupJest.js b/packages/bitcoin/test/setupJest.js new file mode 100644 index 000000000..a048a41d0 --- /dev/null +++ b/packages/bitcoin/test/setupJest.js @@ -0,0 +1,2 @@ +// setupJest.js or similar file +require('jest-fetch-mock').enableMocks(); \ No newline at end of file diff --git a/packages/bitcoin/tsconfig.cjs.json b/packages/bitcoin/tsconfig.cjs.json new file mode 100644 index 000000000..000327dd4 --- /dev/null +++ b/packages/bitcoin/tsconfig.cjs.json @@ -0,0 +1,13 @@ +{ + "exclude": [ + "src/**/__test__/**", + "test/**", + ], + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "esModuleInterop": true, + "module": "commonjs", + "outDir": "./dist-cjs" + } +} diff --git a/packages/bitcoin/tsconfig.esm.json b/packages/bitcoin/tsconfig.esm.json new file mode 100644 index 000000000..d8c28947b --- /dev/null +++ b/packages/bitcoin/tsconfig.esm.json @@ -0,0 +1,12 @@ +{ + "exclude": [ + "src/**/__test__/**", + "test/**" + ], + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ES2020", + "esModuleInterop": true, + "outDir": "./dist-esm" + } +} diff --git a/packages/bitcoin/tsconfig.json b/packages/bitcoin/tsconfig.json new file mode 100644 index 000000000..cf1659b0d --- /dev/null +++ b/packages/bitcoin/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "exclude": ["node_modules/**"], + "include": ["./src/**/*.ts"] +} diff --git a/packages/bitcoin/tsconfig.types.json b/packages/bitcoin/tsconfig.types.json new file mode 100644 index 000000000..cc2946320 --- /dev/null +++ b/packages/bitcoin/tsconfig.types.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "emitDeclarationOnly": true, + "outDir": "types" + }, + "extends": "./tsconfig.json", + "exclude": ["test", "src/**/__test__/**"] +} diff --git a/packages/btc/src/base-transfer.ts b/packages/btc/src/base-transfer.ts deleted file mode 100644 index 6d770abaa..000000000 --- a/packages/btc/src/base-transfer.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - -import type { BitcoinResource } from "@buildwithsygma/core/src"; -import type { Config } from "@buildwithsygma/core/types"; - -type BaseTransferParams = { - destinationAddress: string; - amount: bigint; -}; - -type BitcoinTransferRequest = { - destinationAddress: string; - amount: bigint; - depositAddress: string; -}; - -export function createBitcoinTransferRequest( - params: BaseTransferParams, -): Promise { - throw new Error("Method not implemented"); -} - -export abstract class BaseTransfer { - constructor(transfer: BaseTransferParams, config: Config) {} - - private findResource( - resources: BitcoinResource[], - resourceIdentifier: string | BitcoinResource, - ): BitcoinResource | undefined { - throw new Error("Method not implemented"); - } - - /** - * Set resource to be transferred. - * @param {BitcoinResource} resource - */ - setResource(resource: BitcoinResource): void { - throw new Error("Method not implemented"); - } - - getUriEncodedUtxoRequest(btcTransferRequest: BaseTransferParams): string { - throw new Error("Method not implemented"); - } - - getBTCTransferRequest(): BitcoinTransferRequest { - throw new Error("Method not implemented"); - } -} diff --git a/packages/btc/src/index.ts b/packages/btc/src/index.ts deleted file mode 100644 index a94fd7c4c..000000000 --- a/packages/btc/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./base-transfer.js"; \ No newline at end of file diff --git a/packages/core/src/baseTransfer.ts b/packages/core/src/baseTransfer.ts index 8e451959d..088fbcf57 100644 --- a/packages/core/src/baseTransfer.ts +++ b/packages/core/src/baseTransfer.ts @@ -1,17 +1,23 @@ import type { Config } from './config/config.js'; -import type { Domain, Domainlike, EvmResource, SubstrateResource } from './types.js'; +import type { + Domainlike, + EvmResource, + Domain, + SubstrateResource, + BitcoinResource, +} from './types.js'; export interface BaseTransferParams { source: Domainlike; destination: Domainlike; - resource: string | EvmResource | SubstrateResource; + resource: string | EvmResource | SubstrateResource | BitcoinResource; sourceAddress: string; } export abstract class BaseTransfer { protected destinationDomain: Domain; protected sourceDomain: Domain; - protected transferResource: EvmResource | SubstrateResource; + protected transferResource: EvmResource | SubstrateResource | BitcoinResource; protected sygmaConfiguration: Config; protected sourceAddress: string; @@ -23,7 +29,7 @@ export abstract class BaseTransfer { return this.destinationDomain; } - public get resource(): EvmResource | SubstrateResource { + public get resource(): EvmResource | SubstrateResource | BitcoinResource { return this.transferResource; } @@ -32,8 +38,8 @@ export abstract class BaseTransfer { } private findResource( - resource: string | EvmResource | SubstrateResource, - ): EvmResource | SubstrateResource | undefined { + resource: string | EvmResource | SubstrateResource | BitcoinResource, + ): EvmResource | SubstrateResource | BitcoinResource | undefined { return this.sygmaConfiguration.getResources(this.source).find(_resource => { return typeof resource === 'string' ? resource === _resource.resourceId @@ -66,10 +72,10 @@ export abstract class BaseTransfer { } /** * Set resource to be transferred - * @param {EvmResource} resource + * @param {EvmResource | SubstrateResource | BitcoinResource} resource * @returns {BaseTransfer} */ - setResource(resource: EvmResource | SubstrateResource): void { + setResource(resource: EvmResource | SubstrateResource | BitcoinResource): void { this.transferResource = resource; } /** diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 46b299f92..a7e505a1b 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1,5 +1,6 @@ import { ConfigUrl } from '../index.js'; import type { + BitcoinConfig, Domain, Domainlike, EthereumConfig, @@ -7,6 +8,7 @@ import type { Resource, SubstrateConfig, SygmaConfig, + SygmaDomainConfig, } from '../types.js'; import { Environment } from '../types.js'; @@ -52,10 +54,10 @@ export class Config { } /** * Creates a domain object from config object - * @param {EthereumConfig | SubstrateConfig} config + * @param {SygmaDomainConfig} config * @returns {Domain} */ - private createDomain(config: EthereumConfig | SubstrateConfig): Domain { + private createDomain(config: SygmaDomainConfig): Domain { return { id: config.id, caipId: config.caipId, @@ -63,6 +65,8 @@ export class Config { name: config.name, type: config.type, parachainId: (config as SubstrateConfig).parachainId, + // used in bitcoin transfers + feeAddress: (config as BitcoinConfig).feeAddress, }; } /** @@ -72,7 +76,7 @@ export class Config { * @param {number} sygmaId * @returns {SubstrateConfig | EthereumConfig} */ - findDomainConfigBySygmaId(sygmaId: number): SubstrateConfig | EthereumConfig { + findDomainConfigBySygmaId(sygmaId: number): SygmaDomainConfig { const domainConfig = this.configuration.domains.find(domain => domain.id === sygmaId); if (!domainConfig) throw new Error(`Domain with sygmaId: ${sygmaId} not found.`); return domainConfig; @@ -81,9 +85,9 @@ export class Config { * Find configuration of the domain * existing in current sygma configuration * @param {Domainlike} domainLike - * @returns {{ config: SubstrateConfig | EthereumConfig | undefined; environment: Environment; }} + * @returns {{ config: SygmaDomainConfig | undefined; environment: Environment; }} */ - findDomainConfig(domainLike: Domainlike): SubstrateConfig | EthereumConfig { + findDomainConfig(domainLike: Domainlike): SygmaDomainConfig { const config = this.configuration.domains.find(domain => { switch (typeof domainLike) { case 'string': @@ -122,7 +126,7 @@ export class Config { * @param {Domainlike} domainLike chain id, caip id or sygma id * @returns {SubstrateConfig | EthereumConfig} */ - getDomainConfig(domainLike: Domainlike): SubstrateConfig | EthereumConfig { + getDomainConfig(domainLike: Domainlike): SubstrateConfig | EthereumConfig | BitcoinConfig { if (!this.initialized) throw new Error('SDK Uninitialized'); const domainConfig = this.findDomainConfig(domainLike); if (!domainConfig) throw new Error('Domain configuration not found.'); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 780aa903f..0f05deb04 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -17,7 +17,7 @@ export enum Environment { export enum Network { EVM = 'evm', SUBSTRATE = 'substrate', - BITCOIN = 'bitcoin', + BITCOIN = 'btc', } export enum SecurityModel { @@ -33,9 +33,10 @@ export type Domain = { iconUrl?: string; type: Network; parachainId?: ParachainId; + feeAddress?: string; }; -export type Resource = EvmResource | SubstrateResource; +export type Resource = EvmResource | SubstrateResource | BitcoinResource; export enum ResourceType { FUNGIBLE = 'fungible', @@ -78,8 +79,10 @@ export type XcmMultiAssetIdType = { }; export type BitcoinResource = BaseResource & { + address: string; script: string; tweak: string; + feeAmount?: number; }; export type SubstrateResource = BaseResource & { @@ -138,6 +141,12 @@ export interface SubstrateConfig extends BaseConfig { parachainId: ParachainId; } +export interface BitcoinConfig extends BaseConfig { + feeAddress: string; +} + +export type SygmaDomainConfig = EthereumConfig | SubstrateConfig | BitcoinConfig; + export type IndexerRoutesResponse = { routes: RouteIndexerType[] }; export type Handler = { @@ -146,7 +155,7 @@ export type Handler = { }; export interface SygmaConfig { - domains: Array; + domains: Array; } export type RouteIndexerType = { diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index d7dbefe66..88fe09e14 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -294,8 +294,12 @@ export function isValidEvmAddress(address: string): boolean { * @returns {boolean} */ export function isValidBitcoinAddress(address: string): boolean { - if (process.env.SYGMA_ENV === Environment.TESTNET || process.env.SYGMA_ENV === Environment.DEVNET) + if ( + process.env.SYGMA_ENV === Environment.TESTNET || + process.env.SYGMA_ENV === Environment.DEVNET + ) { return validate(address, BitcoinNetwork.testnet); + } return validate(address, BitcoinNetwork.mainnet); } diff --git a/packages/evm/src/utils/__test__/helpers.test.ts b/packages/evm/src/utils/__test__/helpers.test.ts index 1a00a534b..aa24a1460 100644 --- a/packages/evm/src/utils/__test__/helpers.test.ts +++ b/packages/evm/src/utils/__test__/helpers.test.ts @@ -6,6 +6,7 @@ import { createERCDepositData, toHex, constructSubstrateRecipient, + addressToHex, serializeGenericCallParameters, } from '../helpers.js'; @@ -31,6 +32,16 @@ describe('createERCDepositData', () => { expect(depositData).toEqual(expectedDepositData); }); + + it('should return the correct deposit data - bitcoin', () => { + const tokenAmount = BigInt(100); + const recipientAddress = 'tb1qsfyzl92pv7wkyaj0tfjdtwvcsj840p004jglvp'; + const expectedDepositData = + '0x0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000002a746231717366797a6c3932707637776b79616a3074666a6474777663736a383430703030346a676c7670'; + + const depositData = createERCDepositData(tokenAmount, recipientAddress); + expect(depositData).toEqual(expectedDepositData); + }); }); describe('constructSubstrateRecipient', () => { @@ -132,3 +143,23 @@ describe('toHex', () => { expect(result).toBe('0x00000000000000000000000000000000000000000000be951906eba2aa800000'); }); }); + +describe('addressToHex', () => { + test('should convert p2tr address to hex', () => { + const address = 'tb1pnyh5nayrmwux72guec3xy7qryjjww6tu9mev3d5347lqcwgus4jsd95d2r'; + const expectedHex = + '0x746231706e7968356e6179726d777578373267756563337879377172796a6a7777367475396d65763364353334376c716377677573346a73643935643272'; + + const result = addressToHex(address); + expect(result).toEqual(expectedHex); + }); + + test('should convert p2wpkh address to hex', () => { + const address = 'tb1qsfyzl92pv7wkyaj0tfjdtwvcsj840p004jglvp'; + const expectedHex = + '0x746231717366797a6c3932707637776b79616a3074666a6474777663736a383430703030346a676c7670'; + + const result = addressToHex(address); + expect(result).toEqual(expectedHex); + }); +}); diff --git a/packages/evm/src/utils/helpers.ts b/packages/evm/src/utils/helpers.ts index 0ad7f4b9c..5e0830552 100644 --- a/packages/evm/src/utils/helpers.ts +++ b/packages/evm/src/utils/helpers.ts @@ -25,9 +25,13 @@ export const createERCDepositData = ( let recipientAddressInBytes; if (utils.isAddress(recipientAddress)) { recipientAddressInBytes = getEVMRecipientAddressInBytes(recipientAddress); - } else { + } else if (parachainId) { recipientAddressInBytes = getSubstrateRecipientAddressInBytes(recipientAddress, parachainId); + } else { + const hexAddress = addressToHex(recipientAddress); + recipientAddressInBytes = utils.arrayify(`${hexAddress}`); } + const depositDataBytes = constructMainDepositData( BigNumber.from(tokenAmount), recipientAddressInBytes, @@ -189,6 +193,17 @@ export function serializeGenericCallParameters( return `0x${serialized}`; } +/** + * Return the address transformed to hex for bitcoin deposits + * + * @category Helpers + * @param address - bitcoin address + * @returns {string} + */ +export const addressToHex = (address: string): string => { + return utils.hexlify(utils.toUtf8Bytes(address)); +}; + /** * Creates the data for permissionless generic handler * diff --git a/packages/utils/src/bitcoin/blockstream.ts b/packages/utils/src/bitcoin/blockstream.ts new file mode 100644 index 000000000..3e940fdc8 --- /dev/null +++ b/packages/utils/src/bitcoin/blockstream.ts @@ -0,0 +1,102 @@ +import { Environment } from '@buildwithsygma/core'; + +const TESTNET_BLOCKSTREAM_URL = 'https://blockstream.info/testnet/api'; +const MAINNET_BLOCKSTREAM_URL = 'https://blockstream.info/api'; + +type FeeEstimates = Record; + +const blockStreamUrl = + process.env.SYGMA_ENV === Environment.MAINNET ? MAINNET_BLOCKSTREAM_URL : TESTNET_BLOCKSTREAM_URL; + +type Utxo = { + txid: string; + vout: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + value: number; +}; + +/** + * @category Bitcoin Helpers + * @description Get fee estimates from blockstream API + * @param blockConfirmations - number of confirmations + * @returns {Promise} - fee estimate + */ +export async function getFeeEstimates(blockConfirmations: string): Promise { + try { + const response = await fetch(`${blockStreamUrl}/fee-estimates`); + + const data = (await response.json()) as FeeEstimates; + + return data[blockConfirmations]; + } catch (error) { + throw new Error('Failed to get fee estimates'); + } +} + +/** + * @category Bitcoin Helpers + * @description Broadcast transaction to the network + * @param txHex - raw hex string of the signed transaction + * @returns {Promise} - transaction id + */ +export async function broadcastTransaction(txHex: string): Promise { + try { + const response = await fetch(`${blockStreamUrl}/tx`, { + method: 'POST', + body: txHex, + headers: { + 'Content-Type': 'text/plain', + }, + }); + + return await response.text(); + } catch (error) { + throw new Error('Failed to broadcast transaction'); + } +} + +/** + * @category Bitcoin Helpers + * @description Get UTXOs for a given address + * @param address - bitcoin address + * @returns {Promise} - array of UTXOs + */ +export const fetchUTXOS = async (address: string): Promise => { + try { + const response = await fetch(`${blockStreamUrl}/address/${address}/utxo`); + + const data = (await response.json()) as Utxo[]; + + return data; + } catch (error) { + throw new Error('Failed to get utxos'); + } +}; + +/** + * @category Bitcoin Helpers + * @description Process the UTXOs to get the required amount + * @param amount - amount to spend on the transaction + * @param utxo - array of UTXOs to process` + * @returns {Utxo[]} - array of UTXOs + */ +export const processUtxos = (utxo: Utxo[], amount: number): Utxo[] => { + const utoxCumSum = utxo.reduce>((acc, utxo, idx) => { + if (acc.length === 0) { + acc[idx] = { cumSum: utxo.value, index: idx }; + return acc; + } + acc[idx] = { cumSum: acc[idx - 1].cumSum + utxo.value, index: idx }; + return acc; + }, []); + + const utxoPosition = utoxCumSum.findIndex(utxo => utxo.cumSum > amount); + + const dataToReturn = utxo.slice(0, utxoPosition + 1); + return dataToReturn; +}; diff --git a/packages/utils/src/bitcoin/index.ts b/packages/utils/src/bitcoin/index.ts new file mode 100644 index 000000000..1cbc6a294 --- /dev/null +++ b/packages/utils/src/bitcoin/index.ts @@ -0,0 +1 @@ +export * from './blockstream.js'; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 53a0dceb9..c2a99f19b 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1 +1,2 @@ export * from './liquidity.js'; +export * from './bitcoin/index.js'; diff --git a/packages/utils/src/liquidity.ts b/packages/utils/src/liquidity.ts index 065fb563a..d2befea44 100644 --- a/packages/utils/src/liquidity.ts +++ b/packages/utils/src/liquidity.ts @@ -1,4 +1,10 @@ -import type { Eip1193Provider, EvmResource, SubstrateResource } from '@buildwithsygma/core'; +import type { + EvmResource, + SubstrateResource, + Eip1193Provider, + EthereumConfig, + SubstrateConfig, +} from '@buildwithsygma/core'; import { Network, ResourceType } from '@buildwithsygma/core'; import type { createFungibleAssetTransfer } from '@buildwithsygma/evm'; import { getEvmHandlerBalance } from '@buildwithsygma/evm'; @@ -21,7 +27,7 @@ export async function hasEnoughLiquidity( ): Promise { const { destination, resource, config } = transfer; const destinationDomainConfig = config.findDomainConfig(destination); - const handler = destinationDomainConfig.handlers.find( + const handler = (destinationDomainConfig as EthereumConfig | SubstrateConfig).handlers.find( handler => handler.type === ResourceType.FUNGIBLE, ); diff --git a/release-please/rp-bitcoin-config.json b/release-please/rp-bitcoin-config.json new file mode 100644 index 000000000..0c867ebaf --- /dev/null +++ b/release-please/rp-bitcoin-config.json @@ -0,0 +1,16 @@ +{ + "plugins": ["node-workspace"], + "separate-pull-requests": true, + "packages": { + "packages/bitcoin": { + "component": "bitcoin", + "releaseType": "node", + "draft": false, + "prerelease": false, + "bumpMinorPreMajor": false, + "bumpPatchForMinorPreMajor": false, + "changelogPath": "CHANGELOG.md", + "versioning": "default" + } + } +} diff --git a/release-please/rp-bitcoin-manifest.json b/release-please/rp-bitcoin-manifest.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/release-please/rp-bitcoin-manifest.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d934b80ba..920a4b824 100644 --- a/yarn.lock +++ b/yarn.lock @@ -415,6 +415,29 @@ __metadata: languageName: node linkType: hard +"@buildwithsygma/bitcoin@workspace:^, @buildwithsygma/bitcoin@workspace:packages/bitcoin": + version: 0.0.0-use.local + resolution: "@buildwithsygma/bitcoin@workspace:packages/bitcoin" + dependencies: + "@buildwithsygma/core": "workspace:^" + "@types/jest": "npm:^29.4.0" + bitcoinjs-lib: "npm:^6.1.6" + concurrently: "npm:7.0.0" + eslint: "npm:8" + hardhat: "npm:2.8.2" + jest: "npm:^29.4.1" + jest-environment-jsdom: "npm:^29.4.1" + jest-extended: "npm:1.2.0" + jest-fetch-mock: "npm:^3.0.3" + tiny-secp256k1: "npm:2.2.3" + ts-jest: "npm:^29.0.5" + ts-node: "npm:10.9.1" + typedoc: "npm:^0.24.1" + typedoc-plugin-markdown: "npm:^3.15.1" + typescript: "npm:5.0.4" + languageName: unknown + linkType: soft + "@buildwithsygma/core@workspace:*, @buildwithsygma/core@workspace:^, @buildwithsygma/core@workspace:packages/core": version: 0.0.0-use.local resolution: "@buildwithsygma/core@workspace:packages/core" @@ -598,6 +621,40 @@ __metadata: languageName: node linkType: hard +"@buildwithsygma/sygma-sdk-bitcoin-to-evm-fungible-transfer-example@workspace:examples/bitcoin-to-evm-fungible-transfer": + version: 0.0.0-use.local + resolution: "@buildwithsygma/sygma-sdk-bitcoin-to-evm-fungible-transfer-example@workspace:examples/bitcoin-to-evm-fungible-transfer" + dependencies: + "@buildwithsygma/bitcoin": "workspace:^" + "@buildwithsygma/core": "workspace:^" + "@buildwithsygma/utils": "workspace:^" + bip32: "npm:^4.0.0" + bip39: "npm:^3.1.0" + bitcoinjs-lib: "npm:^6.1.6" + dotenv: "npm:^16.3.1" + ecpair: "npm:^2.1.0" + eslint: "npm:8" + tiny-secp256k1: "npm:^2.2.3" + ts-node: "npm:10.9.1" + tsx: "npm:^4.15.4" + typescript: "npm:5.0.4" + languageName: unknown + linkType: soft + +"@buildwithsygma/sygma-sdk-evm-to-bitcoin-fungible-transfer@workspace:examples/evm-to-bitcoin-fungible-transfer": + version: 0.0.0-use.local + resolution: "@buildwithsygma/sygma-sdk-evm-to-bitcoin-fungible-transfer@workspace:examples/evm-to-bitcoin-fungible-transfer" + dependencies: + "@buildwithsygma/core": "workspace:^" + "@buildwithsygma/evm": "workspace:^" + dotenv: "npm:^16.3.1" + eslint: "npm:8" + ts-node: "npm:10.9.1" + tsx: "npm:^4.15.4" + typescript: "npm:5.0.4" + languageName: unknown + linkType: soft + "@buildwithsygma/sygma-sdk@workspace:.": version: 0.0.0-use.local resolution: "@buildwithsygma/sygma-sdk@workspace:." @@ -608,7 +665,7 @@ __metadata: languageName: unknown linkType: soft -"@buildwithsygma/utils@workspace:packages/utils": +"@buildwithsygma/utils@workspace:^, @buildwithsygma/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@buildwithsygma/utils@workspace:packages/utils" dependencies: @@ -2156,7 +2213,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.4.0": +"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" checksum: 8c3f005ee72e7b8f9cff756dfae1241485187254e3f743873e22073d63906863df5d4f13d441b7530ea614b7a093f0d889309f28b59850f33b66cb26a779a4a5 @@ -4066,6 +4123,13 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^4.0.0": + version: 4.0.0 + resolution: "base-x@npm:4.0.0" + checksum: 0cb47c94535144ab138f70bb5aa7e6e03049ead88615316b62457f110fc204f2c3baff5c64a1c1b33aeb068d79a68092c08a765c7ccfa133eee1e70e4c6eb903 + languageName: node + linkType: hard + "base58-js@npm:^1.0.0": version: 1.0.5 resolution: "base58-js@npm:1.0.5" @@ -4108,6 +4172,34 @@ __metadata: languageName: node linkType: hard +"bip174@npm:^2.1.1": + version: 2.1.1 + resolution: "bip174@npm:2.1.1" + checksum: d92e142fca85fa4f621dbc9131dafe1da7d69fa7cae03137fa4745d66ffa50561f85ff8c49ca41da8ed1ca65e642415b13dc046531412dfebe6ff03c275e71ae + languageName: node + linkType: hard + +"bip32@npm:^4.0.0": + version: 4.0.0 + resolution: "bip32@npm:4.0.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + "@scure/base": "npm:^1.1.1" + typeforce: "npm:^1.11.5" + wif: "npm:^2.0.6" + checksum: b74ffd3a96b42a783eca6455dff8f0decc8360025b69d486d5a047b52cca6e8436bc5a9812cbeae2638a50f35da7fcd85b55cfe58272f55a5b31d7ac40a25522 + languageName: node + linkType: hard + +"bip39@npm:^3.1.0": + version: 3.1.0 + resolution: "bip39@npm:3.1.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + checksum: 68f9673a0d6a851e9635f3af8a85f2a1ecef9066c76d77e6f0d58d274b5bf22a67f429da3997e07c0d2cf153a4d7321f9273e656cac0526f667575ddee28ef71 + languageName: node + linkType: hard + "bitcoin-address-validation@npm:^2.2.3": version: 2.2.3 resolution: "bitcoin-address-validation@npm:2.2.3" @@ -4119,6 +4211,20 @@ __metadata: languageName: node linkType: hard +"bitcoinjs-lib@npm:^6.1.6": + version: 6.1.6 + resolution: "bitcoinjs-lib@npm:6.1.6" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bech32: "npm:^2.0.0" + bip174: "npm:^2.1.1" + bs58check: "npm:^3.0.1" + typeforce: "npm:^1.11.3" + varuint-bitcoin: "npm:^1.1.2" + checksum: 27e77add09051fcbb32266c1a03c6c1eb691cb6b91706c4bfd41e1f4bea76f53529364f4ce22efff7879a3d08940d113df87de64b9422697463432c7b561e78c + languageName: node + linkType: hard + "blakejs@npm:^1.1.0": version: 1.2.1 resolution: "blakejs@npm:1.2.1" @@ -4235,7 +4341,16 @@ __metadata: languageName: node linkType: hard -"bs58check@npm:^2.1.2": +"bs58@npm:^5.0.0": + version: 5.0.0 + resolution: "bs58@npm:5.0.0" + dependencies: + base-x: "npm:^4.0.0" + checksum: 0d1b05630b11db48039421b5975cb2636ae0a42c62f770eec257b2e5c7d94cb5f015f440785f3ec50870a6e9b1132b35bd0a17c7223655b22229f24b2a3491d1 + languageName: node + linkType: hard + +"bs58check@npm:<3.0.0, bs58check@npm:^2.1.2": version: 2.1.2 resolution: "bs58check@npm:2.1.2" dependencies: @@ -4246,6 +4361,16 @@ __metadata: languageName: node linkType: hard +"bs58check@npm:^3.0.1": + version: 3.0.1 + resolution: "bs58check@npm:3.0.1" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bs58: "npm:^5.0.0" + checksum: a01f62351d17cea5f6607f75f6b4b79d3473d018c52f1dfa6f449751062bb079ebfd556ea81c453de657102ab8c5a6b78620161f21ae05f0e5a43543e0447700 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -5035,6 +5160,17 @@ __metadata: languageName: node linkType: hard +"ecpair@npm:^2.1.0": + version: 2.1.0 + resolution: "ecpair@npm:2.1.0" + dependencies: + randombytes: "npm:^2.1.0" + typeforce: "npm:^1.18.0" + wif: "npm:^2.0.6" + checksum: 206a3c9af725416e6e91515278259319d88c880cf067c4e4d275fee5e746064cc5bc6279bdb2f87a9218ef25dd82921422d05e0952d4b41c8e91c070c504aa70 + languageName: node + linkType: hard + "ejs@npm:^3.1.10": version: 3.1.10 resolution: "ejs@npm:3.1.10" @@ -10810,6 +10946,15 @@ __metadata: languageName: node linkType: hard +"tiny-secp256k1@npm:2.2.3, tiny-secp256k1@npm:^2.2.3": + version: 2.2.3 + resolution: "tiny-secp256k1@npm:2.2.3" + dependencies: + uint8array-tools: "npm:0.0.7" + checksum: 84ca5b88e90fc2a89b90814cec2394716393a9325f318ffede0cb99ff79aa4a63d609c76fc596727fd53192d9163ccaf690dc6817d3e571c625e3668d01177aa + languageName: node + linkType: hard + "tmp@npm:0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -11173,6 +11318,13 @@ __metadata: languageName: node linkType: hard +"typeforce@npm:^1.11.3, typeforce@npm:^1.11.5, typeforce@npm:^1.18.0": + version: 1.18.0 + resolution: "typeforce@npm:1.18.0" + checksum: 011f57effd9ae6d3dd8bb249e09b4ecadb2c2a3f803b27f977ac8b7782834855930bff971ba549bcd5a8cedc8136d8a977c0b7e050cc67deded948181b7ba3e8 + languageName: node + linkType: hard + "typescript@npm:5.0.4": version: 5.0.4 resolution: "typescript@npm:5.0.4" @@ -11229,6 +11381,13 @@ __metadata: languageName: node linkType: hard +"uint8array-tools@npm:0.0.7": + version: 0.0.7 + resolution: "uint8array-tools@npm:0.0.7" + checksum: 7d67aef80f3b6417c1dacc6505d5d82e3441bc6c1706b43698c5322e6fe7078a13a737efa8fcbd0884bd4b589511cc0a90cf4fe7cdd1b62b16007f1b0811ce36 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.0.2": version: 1.0.2 resolution: "unbox-primitive@npm:1.0.2" @@ -11361,6 +11520,15 @@ __metadata: languageName: node linkType: hard +"varuint-bitcoin@npm:^1.1.2": + version: 1.1.2 + resolution: "varuint-bitcoin@npm:1.1.2" + dependencies: + safe-buffer: "npm:^5.1.1" + checksum: 3d38f8de8192b7a4fc00abea01ed189f1e1e6aee1ebc4192040c5717d2483e0a6a73873fcf6b3c1910d947d338b671470505705fe40c765dc832255dfa2d4210 + languageName: node + linkType: hard + "vscode-oniguruma@npm:^1.7.0": version: 1.7.0 resolution: "vscode-oniguruma@npm:1.7.0" @@ -11605,6 +11773,15 @@ __metadata: languageName: node linkType: hard +"wif@npm:^2.0.6": + version: 2.0.6 + resolution: "wif@npm:2.0.6" + dependencies: + bs58check: "npm:<3.0.0" + checksum: 9ff55fdde73226bbae6a08b68298b6d14bbc22fa4cefac11edaacb2317c217700f715b95dc4432917f73511ec983f1bc032d22c467703b136f4e6ca7dfa9f10b + languageName: node + linkType: hard + "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5" From 27d18ba2e5b89cd2fb7703346fd22ccaabdac041 Mon Sep 17 00:00:00 2001 From: mace Date: Mon, 9 Sep 2024 11:56:09 +0200 Subject: [PATCH 5/8] chore(main): release core 1.2.0 (#517) :robot: I have created a release *beep* *boop* --- ## [1.2.0](https://github.com/sygmaprotocol/sygma-sdk/compare/core-v1.1.0...core-v1.2.0) (2024-09-09) ### Features * **bitcoin:** Added support for Bitcoin transfers ([#389](https://github.com/sygmaprotocol/sygma-sdk/issues/389)) ([1f428b1](https://github.com/sygmaprotocol/sygma-sdk/commit/1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/core/CHANGELOG.md | 7 +++++++ packages/core/package.json | 2 +- release-please/rp-core-manifest.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 67ffc1aa6..73d6334c9 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.2.0](https://github.com/sygmaprotocol/sygma-sdk/compare/core-v1.1.0...core-v1.2.0) (2024-09-09) + + +### Features + +* **bitcoin:** Added support for Bitcoin transfers ([#389](https://github.com/sygmaprotocol/sygma-sdk/issues/389)) ([1f428b1](https://github.com/sygmaprotocol/sygma-sdk/commit/1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15)) + ## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/core-v1.0.3...core-v1.1.0) (2024-09-05) diff --git a/packages/core/package.json b/packages/core/package.json index b8eadebd2..8e2ff2089 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@buildwithsygma/core", - "version": "1.1.0", + "version": "1.2.0", "description": "Core primitives for bridging and message passing", "main": "dist-esm/index.js", "types": "types/index.d.ts", diff --git a/release-please/rp-core-manifest.json b/release-please/rp-core-manifest.json index fe106deef..dd336be1d 100644 --- a/release-please/rp-core-manifest.json +++ b/release-please/rp-core-manifest.json @@ -1 +1 @@ -{"packages/core":"1.1.0"} +{"packages/core":"1.2.0"} From 4c83b1db0a1bee7ec1aae959864741d7a8c2fed9 Mon Sep 17 00:00:00 2001 From: mace Date: Mon, 9 Sep 2024 12:01:17 +0200 Subject: [PATCH 6/8] chore(main): release evm 1.2.0 (#516) :robot: I have created a release *beep* *boop* --- ## [1.2.0](https://github.com/sygmaprotocol/sygma-sdk/compare/evm-v1.1.0...evm-v1.2.0) (2024-09-09) ### Features * **bitcoin:** Added support for Bitcoin transfers ([#389](https://github.com/sygmaprotocol/sygma-sdk/issues/389)) ([1f428b1](https://github.com/sygmaprotocol/sygma-sdk/commit/1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/evm/CHANGELOG.md | 7 +++++++ packages/evm/package.json | 2 +- release-please/rp-evm-manifest.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/evm/CHANGELOG.md b/packages/evm/CHANGELOG.md index 307b68342..e336a48ee 100644 --- a/packages/evm/CHANGELOG.md +++ b/packages/evm/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.2.0](https://github.com/sygmaprotocol/sygma-sdk/compare/evm-v1.1.0...evm-v1.2.0) (2024-09-09) + + +### Features + +* **bitcoin:** Added support for Bitcoin transfers ([#389](https://github.com/sygmaprotocol/sygma-sdk/issues/389)) ([1f428b1](https://github.com/sygmaprotocol/sygma-sdk/commit/1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15)) + ## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/evm-v1.0.5...evm-v1.1.0) (2024-09-05) diff --git a/packages/evm/package.json b/packages/evm/package.json index 230834ff0..7c3b7a62a 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -1,6 +1,6 @@ { "name": "@buildwithsygma/evm", - "version": "1.1.0", + "version": "1.2.0", "description": "Core primitives for bridging and message passing", "main": "dist-esm/index.js", "types": "types/index.d.ts", diff --git a/release-please/rp-evm-manifest.json b/release-please/rp-evm-manifest.json index 2901e7420..2d21ee583 100644 --- a/release-please/rp-evm-manifest.json +++ b/release-please/rp-evm-manifest.json @@ -1 +1 @@ -{"packages/evm":"1.1.0"} +{"packages/evm":"1.2.0"} From 69deb4dc0336910eb5e3cce02b781334369297c3 Mon Sep 17 00:00:00 2001 From: mace Date: Mon, 9 Sep 2024 12:05:21 +0200 Subject: [PATCH 7/8] chore(main): release bitcoin 1.0.0 (#518) :robot: I have created a release *beep* *boop* --- ## 1.0.0 (2024-09-09) ### Features * **bitcoin:** Added support for Bitcoin transfers ([#389](https://github.com/sygmaprotocol/sygma-sdk/issues/389)) ([1f428b1](https://github.com/sygmaprotocol/sygma-sdk/commit/1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15)) ### Miscellaneous Chores * release 1.0.0 ([bb6a505](https://github.com/sygmaprotocol/sygma-sdk/commit/bb6a5053d843960f445f0dacebe101745f4d908f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/bitcoin/CHANGELOG.md | 13 +++++++++++++ release-please/rp-bitcoin-manifest.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 packages/bitcoin/CHANGELOG.md diff --git a/packages/bitcoin/CHANGELOG.md b/packages/bitcoin/CHANGELOG.md new file mode 100644 index 000000000..39dcd77ac --- /dev/null +++ b/packages/bitcoin/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## 1.0.0 (2024-09-09) + + +### Features + +* **bitcoin:** Added support for Bitcoin transfers ([#389](https://github.com/sygmaprotocol/sygma-sdk/issues/389)) ([1f428b1](https://github.com/sygmaprotocol/sygma-sdk/commit/1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15)) + + +### Miscellaneous Chores + +* release 1.0.0 ([bb6a505](https://github.com/sygmaprotocol/sygma-sdk/commit/bb6a5053d843960f445f0dacebe101745f4d908f)) diff --git a/release-please/rp-bitcoin-manifest.json b/release-please/rp-bitcoin-manifest.json index 9e26dfeeb..938ededa6 100644 --- a/release-please/rp-bitcoin-manifest.json +++ b/release-please/rp-bitcoin-manifest.json @@ -1 +1 @@ -{} \ No newline at end of file +{"packages/bitcoin":"1.0.0"} \ No newline at end of file From 002163da2e0259e7616d81086bfa45e1f8c55d75 Mon Sep 17 00:00:00 2001 From: mace Date: Mon, 9 Sep 2024 12:10:06 +0200 Subject: [PATCH 8/8] chore(main): release utils 1.2.0 (#515) :robot: I have created a release *beep* *boop* --- ## [1.2.0](https://github.com/sygmaprotocol/sygma-sdk/compare/utils-v1.1.0...utils-v1.2.0) (2024-09-09) ### Features * **bitcoin:** Added support for Bitcoin transfers ([#389](https://github.com/sygmaprotocol/sygma-sdk/issues/389)) ([1f428b1](https://github.com/sygmaprotocol/sygma-sdk/commit/1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/utils/CHANGELOG.md | 7 +++++++ packages/utils/package.json | 2 +- release-please/rp-utils-manifest.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index e4c8f8843..33b74f451 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.2.0](https://github.com/sygmaprotocol/sygma-sdk/compare/utils-v1.1.0...utils-v1.2.0) (2024-09-09) + + +### Features + +* **bitcoin:** Added support for Bitcoin transfers ([#389](https://github.com/sygmaprotocol/sygma-sdk/issues/389)) ([1f428b1](https://github.com/sygmaprotocol/sygma-sdk/commit/1f428b1b4d8dd0a6b04488f532f3e9d10c5d1b15)) + ## [1.1.0](https://github.com/sygmaprotocol/sygma-sdk/compare/utils-v1.0.2...utils-v1.1.0) (2024-09-05) diff --git a/packages/utils/package.json b/packages/utils/package.json index 625ef7edd..711b020a8 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@buildwithsygma/utils", - "version": "1.1.0", + "version": "1.2.0", "description": "Utilities to support bridging and message passing", "main": "dist-esm/index.js", "types": "types/index.d.ts", diff --git a/release-please/rp-utils-manifest.json b/release-please/rp-utils-manifest.json index 59c6229ae..59c6ab48d 100644 --- a/release-please/rp-utils-manifest.json +++ b/release-please/rp-utils-manifest.json @@ -1 +1 @@ -{"packages/utils":"1.1.0"} +{"packages/utils":"1.2.0"}