From 9b40663d30cf370d47dccd4817174c2828fbb18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 17 Jun 2024 10:05:40 -0400 Subject: [PATCH 01/87] feat: btc interfaces --- packages/btc/src/base-transfer.ts | 22 ++++++++++++++++++++++ packages/btc/src/index.ts | 1 + packages/core/src/types.ts | 1 + 3 files changed, 24 insertions(+) create mode 100644 packages/btc/src/base-transfer.ts create mode 100644 packages/btc/src/index.ts diff --git a/packages/btc/src/base-transfer.ts b/packages/btc/src/base-transfer.ts new file mode 100644 index 000000000..231497c1f --- /dev/null +++ b/packages/btc/src/base-transfer.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +type BitcoinTransferRequest = { + destinationEvmAddress: string; + btcAmount: number; +}; + +export function createUriEncodedUtxoRequest(): BaseTransfer { + throw new Error("Method not implemented"); +} + +export abstract class BaseTransfer { + constructor(transfer: BitcoinTransferRequest) {} + + getUriEncodedUtxoRequest(btcTransferRequest: BitcoinTransferRequest): string { + throw new Error("Method not implemented"); + } + + getBTCTransferRequest(): BitcoinTransferRequest { + throw new Error("Method not implemented"); + } +} \ No newline at end of file diff --git a/packages/btc/src/index.ts b/packages/btc/src/index.ts new file mode 100644 index 000000000..a94fd7c4c --- /dev/null +++ b/packages/btc/src/index.ts @@ -0,0 +1 @@ +export * from "./base-transfer.js"; \ No newline at end of file diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 17c21f007..ce054ad8a 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -20,6 +20,7 @@ export enum FeeHandlerType { export enum Network { EVM = 'evm', SUBSTRATE = 'substrate', + BITCOIN = 'bitcoin', } export enum SecurityModel { From dcbf0335a5cb2621920d4af19f024616dc91389e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 17 Jun 2024 15:53:52 -0400 Subject: [PATCH 02/87] chore: addressing comments --- packages/btc/src/base-transfer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/btc/src/base-transfer.ts b/packages/btc/src/base-transfer.ts index 231497c1f..956f3aca2 100644 --- a/packages/btc/src/base-transfer.ts +++ b/packages/btc/src/base-transfer.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ type BitcoinTransferRequest = { - destinationEvmAddress: string; - btcAmount: number; + destinationAddress: string; + amount: bigint; }; export function createUriEncodedUtxoRequest(): BaseTransfer { From 51990f31321b2f58a1e0df8cac45c09cf48e0894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 19 Jun 2024 11:36:29 -0400 Subject: [PATCH 03/87] feat: base implementation for btc deposits --- package.json | 7 +++- packages/btc/.eslintrc.cjs | 10 +++++ packages/btc/.gitignore | 45 +++++++++++++++++++++ packages/btc/.npmignore | 26 +++++++++++++ packages/btc/.prettierrc.json | 7 ++++ packages/btc/README.md | 1 + packages/btc/jest.config.cjs | 19 +++++++++ packages/btc/package.json | 63 ++++++++++++++++++++++++++++++ packages/btc/src/base-transfer.ts | 65 ++++++++++++++++--------------- packages/btc/src/environment.d.ts | 9 +++++ packages/btc/src/fungible.ts | 38 ++++++++++++++++++ packages/btc/src/index.ts | 3 +- packages/btc/src/types.ts | 14 +++++++ packages/btc/test/setupTest.js | 2 + packages/btc/tsconfig.cjs.json | 13 +++++++ packages/btc/tsconfig.esm.json | 12 ++++++ packages/btc/tsconfig.json | 34 ++++++++++++++++ packages/btc/tsconfig.types.json | 12 ++++++ packages/core/src/types.ts | 3 +- yarn.lock | 35 +++++++++++++++++ 20 files changed, 384 insertions(+), 34 deletions(-) create mode 100644 packages/btc/.eslintrc.cjs create mode 100644 packages/btc/.gitignore create mode 100644 packages/btc/.npmignore create mode 100644 packages/btc/.prettierrc.json create mode 100644 packages/btc/README.md create mode 100644 packages/btc/jest.config.cjs create mode 100644 packages/btc/package.json create mode 100644 packages/btc/src/environment.d.ts create mode 100644 packages/btc/src/fungible.ts create mode 100644 packages/btc/src/types.ts create mode 100644 packages/btc/test/setupTest.js create mode 100644 packages/btc/tsconfig.cjs.json create mode 100644 packages/btc/tsconfig.esm.json create mode 100644 packages/btc/tsconfig.json create mode 100644 packages/btc/tsconfig.types.json diff --git a/package.json b/package.json index aeae13151..3a90462f1 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,12 @@ "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", - "build:all": "yarn workspace @buildwithsygma/core build:all; yarn workspace @buildwithsygma/evm build:all", + "btc:build": "yarn workspace @buildwithsygma/btc build:all", + "btc:cleanDist": "yarn workspace @buildwithsygma/btc clean", + "btc:test": "yarn workspace @buildwithsygma/btc test", + "btc:lint": "yarn workspace @buildwithsygma/btc lint", + "btc:lint:fix": "yarn workspace @buildwithsygma/btc lint:fix", + "build:all": "yarn workspace @buildwithsygma/core build:all; yarn workspace @buildwithsygma/evm build:all; yarn workspace @buildwithsygma/btc build:all", "clean:all": "yarn workspace @buildwithsygma/core clean; yarn workspace @buildwithsygma/evm clean", "docs": "typedoc" }, diff --git a/packages/btc/.eslintrc.cjs b/packages/btc/.eslintrc.cjs new file mode 100644 index 000000000..29c27c015 --- /dev/null +++ b/packages/btc/.eslintrc.cjs @@ -0,0 +1,10 @@ +module.exports = { + extends: '../../.eslintrc.cjs', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + sourceType: 'module', + tsconfigRootDir: __dirname, + }, +}; diff --git a/packages/btc/.gitignore b/packages/btc/.gitignore new file mode 100644 index 000000000..15b2f08aa --- /dev/null +++ b/packages/btc/.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/btc/.npmignore b/packages/btc/.npmignore new file mode 100644 index 000000000..f98c1c55b --- /dev/null +++ b/packages/btc/.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/btc/.prettierrc.json b/packages/btc/.prettierrc.json new file mode 100644 index 000000000..c50ce2de0 --- /dev/null +++ b/packages/btc/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "singleQuote": true, + "trailingComma": "all", + "useTabs": false, + "arrowParens": "avoid" +} diff --git a/packages/btc/README.md b/packages/btc/README.md new file mode 100644 index 000000000..642773809 --- /dev/null +++ b/packages/btc/README.md @@ -0,0 +1 @@ +# btc diff --git a/packages/btc/jest.config.cjs b/packages/btc/jest.config.cjs new file mode 100644 index 000000000..856e48580 --- /dev/null +++ b/packages/btc/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: 'jsdom', + testTimeout: 15000, + transform: { + '^.+\\.(ts|tsx)?$': ['ts-jest', { useESM: true }], + }, + testPathIgnorePatterns: ['./dist'], + automock: false, + setupFiles: [ + "./test/setupJest.js" + ] + }; diff --git a/packages/btc/package.json b/packages/btc/package.json new file mode 100644 index 000000000..da958704f --- /dev/null +++ b/packages/btc/package.json @@ -0,0 +1,63 @@ +{ + "name": "@buildwithsygma/btc", + "version": "0.0.1", + "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 --silent", + "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" + }, + "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", + "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:^" + } +} diff --git a/packages/btc/src/base-transfer.ts b/packages/btc/src/base-transfer.ts index 4417243b7..75cf2ea91 100644 --- a/packages/btc/src/base-transfer.ts +++ b/packages/btc/src/base-transfer.ts @@ -1,31 +1,42 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - -import { BitcoinResource } from "@buildwithsygma/core/src"; -import { Config } from "@buildwithsygma/core/types"; - -type BaseTransferParams = { - destinationAddress: string; - amount: bigint; -}; - -type BitcoinTransferRequest = { - destinationAddress: string; - amount: bigint; - depositAddress: string; -}; - -export async function createBitcoinTransferRequest(params: BaseTransferParams): Promise { - throw new Error("Method not implemented"); -} +import type { BitcoinResource } from '@buildwithsygma/core/src'; +import type { Config, Domain } from '@buildwithsygma/core/types'; +import type { BaseTransferParams } from 'types'; export abstract class BaseTransfer { - constructor(transfer: BaseTransferParams, config: Config) {} + protected destinationAddress: string; + protected amount: bigint; + protected config: Config; + protected resource: BitcoinResource; + protected sourceDomain: Domain; + + constructor(transfer: BaseTransferParams, config: Config) { + this.destinationAddress = transfer.destinationAddress; + this.amount = transfer.amount; + this.sourceDomain = config.getDomain(transfer.sourceDomain); + + const resources = config.getResources(this.sourceDomain) as BitcoinResource[]; + const resource = this.findResource(resources, transfer.resource); + + if (resource) { + this.resource = resource; + } else { + throw new Error('Resource not found.'); + } + + this.config = config; + } private findResource( resources: BitcoinResource[], resourceIdentifier: string | BitcoinResource, ): BitcoinResource | undefined { - throw new Error("Method not implemented"); + return resources.find(resource => { + if (typeof resourceIdentifier === 'string') { + return resource.resourceId === resourceIdentifier; + } + + return resource === resourceIdentifier; + }); } /** @@ -33,14 +44,6 @@ export abstract class BaseTransfer { * @param {BitcoinResource} resource */ setResource(resource: BitcoinResource): void { - throw new Error("Method not implemented"); - } - - getUriEncodedUtxoRequest(btcTransferRequest: BaseTransferParams): string { - throw new Error("Method not implemented"); + this.resource = resource; } - - getBTCTransferRequest(): BitcoinTransferRequest { - throw new Error("Method not implemented"); - } -} \ No newline at end of file +} diff --git a/packages/btc/src/environment.d.ts b/packages/btc/src/environment.d.ts new file mode 100644 index 000000000..f7f28e351 --- /dev/null +++ b/packages/btc/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/btc/src/fungible.ts b/packages/btc/src/fungible.ts new file mode 100644 index 000000000..39fd1d6cd --- /dev/null +++ b/packages/btc/src/fungible.ts @@ -0,0 +1,38 @@ +import { Config } from '@buildwithsygma/core'; +import type { BaseTransferParams, BitcoinTransferRequest } from 'types'; + +import { BaseTransfer } from './base-transfer.js'; + +export async function createBitcoinFungibleTransfer( + params: BaseTransferParams, +): Promise { + const config = new Config(); + await config.init(process.env.SYGMA_ENV); + const transfer = new BitcoinTransfer(params, config); + + return transfer; +} + +class BitcoinTransfer extends BaseTransfer { + protected destinationAddress: string; + protected amount: bigint; + + constructor(transfer: BaseTransferParams, config: Config) { + super(transfer, config); + this.destinationAddress = transfer.destinationAddress; + this.amount = transfer.amount; + } + + getUriEncodedUtxoRequest(): string { + const { address } = this.resource; + return `bitcoin:${address}?amount=${this.amount}&destinationAddress=${this.destinationAddress}`; + } + + getBTCTransferRequest(): BitcoinTransferRequest { + return { + destinationAddress: this.destinationAddress, + amount: this.amount, + depositAddress: this.resource.address, + }; + } +} diff --git a/packages/btc/src/index.ts b/packages/btc/src/index.ts index a94fd7c4c..c5aa1fb21 100644 --- a/packages/btc/src/index.ts +++ b/packages/btc/src/index.ts @@ -1 +1,2 @@ -export * from "./base-transfer.js"; \ No newline at end of file +export * from './base-transfer.js'; +export * from './fungible.js'; diff --git a/packages/btc/src/types.ts b/packages/btc/src/types.ts new file mode 100644 index 000000000..5e71a8be0 --- /dev/null +++ b/packages/btc/src/types.ts @@ -0,0 +1,14 @@ +import type { BitcoinResource, Domainlike } from '@buildwithsygma/core'; + +export type BaseTransferParams = { + sourceDomain: Domainlike; + destinationAddress: string; + amount: bigint; + resource: BitcoinResource | string; +}; + +export type BitcoinTransferRequest = { + destinationAddress: string; + amount: bigint; + depositAddress: string; +}; diff --git a/packages/btc/test/setupTest.js b/packages/btc/test/setupTest.js new file mode 100644 index 000000000..a048a41d0 --- /dev/null +++ b/packages/btc/test/setupTest.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/btc/tsconfig.cjs.json b/packages/btc/tsconfig.cjs.json new file mode 100644 index 000000000..000327dd4 --- /dev/null +++ b/packages/btc/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/btc/tsconfig.esm.json b/packages/btc/tsconfig.esm.json new file mode 100644 index 000000000..d8c28947b --- /dev/null +++ b/packages/btc/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/btc/tsconfig.json b/packages/btc/tsconfig.json new file mode 100644 index 000000000..1b9a286c6 --- /dev/null +++ b/packages/btc/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "module": "ES2022", + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "moduleResolution": "node", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "strict": true, + "sourceMap": true, + "target": "es2020", + "skipLibCheck": true, + "baseUrl": "./src", + "resolveJsonModule": true + }, + "exclude": ["node_modules/**"], + "include": ["./src/**/*.ts", "./test/**/*.ts", "substrate-asset-transfer.ts"], + "ts-node": { + "esm": true, + "experimentalSpecifierResolution": "node" + } + } + \ No newline at end of file diff --git a/packages/btc/tsconfig.types.json b/packages/btc/tsconfig.types.json new file mode 100644 index 000000000..6ee41752a --- /dev/null +++ b/packages/btc/tsconfig.types.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "types" + }, + "extends": "./tsconfig.json", + "exclude": [ + "test", + "src/**/__test__/**", + ] +} \ No newline at end of file diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 6f0177674..7e9dd00f6 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -38,7 +38,7 @@ export type Domain = { parachainId?: ParachainId; }; -export type Resource = EvmResource | SubstrateResource; +export type Resource = EvmResource | SubstrateResource | BitcoinResource; export enum ResourceType { FUNGIBLE = 'fungible', @@ -75,6 +75,7 @@ export type XcmMultiAssetIdType = { }; export type BitcoinResource = BaseResource & { + address: string; script: string; tweak: string; }; diff --git a/yarn.lock b/yarn.lock index 614849ef2..824c41ed7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -418,6 +418,27 @@ __metadata: languageName: node linkType: hard +"@buildwithsygma/btc@workspace:^, @buildwithsygma/btc@workspace:packages/btc": + version: 0.0.0-use.local + resolution: "@buildwithsygma/btc@workspace:packages/btc" + dependencies: + "@buildwithsygma/core": "workspace:^" + "@types/jest": "npm:^29.4.0" + 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" + 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:packages/core": version: 0.0.0-use.local resolution: "@buildwithsygma/core@workspace:packages/core" @@ -501,6 +522,20 @@ __metadata: languageName: node linkType: hard +"@buildwithsygma/sygma-sdk-btc-to-evm-fungible-transfer-example@workspace:examples/btc-to-evm-fungible-transfer": + version: 0.0.0-use.local + resolution: "@buildwithsygma/sygma-sdk-btc-to-evm-fungible-transfer-example@workspace:examples/btc-to-evm-fungible-transfer" + dependencies: + "@buildwithsygma/btc": "workspace:^" + "@buildwithsygma/core": "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-evm-to-evm-fungible-transfer-example@workspace:examples/evm-to-evm-fungible-transfer": version: 0.0.0-use.local resolution: "@buildwithsygma/sygma-sdk-evm-to-evm-fungible-transfer-example@workspace:examples/evm-to-evm-fungible-transfer" From f18513947519fa7b9b27002a2f25d77479c648d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 19 Jun 2024 11:45:25 -0400 Subject: [PATCH 04/87] chore: small change to findResource method on base transfer file --- packages/btc/src/base-transfer.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/btc/src/base-transfer.ts b/packages/btc/src/base-transfer.ts index 75cf2ea91..479267561 100644 --- a/packages/btc/src/base-transfer.ts +++ b/packages/btc/src/base-transfer.ts @@ -30,12 +30,10 @@ export abstract class BaseTransfer { resources: BitcoinResource[], resourceIdentifier: string | BitcoinResource, ): BitcoinResource | undefined { - return resources.find(resource => { - if (typeof resourceIdentifier === 'string') { - return resource.resourceId === resourceIdentifier; - } - - return resource === resourceIdentifier; + return resources.find(res => { + return typeof resourceIdentifier === 'string' + ? res.resourceId === resourceIdentifier + : res.resourceId === resourceIdentifier.resourceId; }); } From cb782eac9b736a3c527b5314663aa577b687f95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 19 Jun 2024 11:48:56 -0400 Subject: [PATCH 05/87] chore: base files for btc to evm example --- .../btc-to-evm-fungible-transfer/.env.sample | 0 .../btc-to-evm-fungible-transfer/README.md | 1 + .../btc-to-evm-fungible-transfer/package.json | 36 +++++++++++++++++++ .../tsconfig.json | 21 +++++++++++ 4 files changed, 58 insertions(+) create mode 100644 examples/btc-to-evm-fungible-transfer/.env.sample create mode 100644 examples/btc-to-evm-fungible-transfer/README.md create mode 100644 examples/btc-to-evm-fungible-transfer/package.json create mode 100644 examples/btc-to-evm-fungible-transfer/tsconfig.json diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample new file mode 100644 index 000000000..e69de29bb diff --git a/examples/btc-to-evm-fungible-transfer/README.md b/examples/btc-to-evm-fungible-transfer/README.md new file mode 100644 index 000000000..36b54abcd --- /dev/null +++ b/examples/btc-to-evm-fungible-transfer/README.md @@ -0,0 +1 @@ +# btc-to-evm-fungible-transfer diff --git a/examples/btc-to-evm-fungible-transfer/package.json b/examples/btc-to-evm-fungible-transfer/package.json new file mode 100644 index 000000000..b4a2692ad --- /dev/null +++ b/examples/btc-to-evm-fungible-transfer/package.json @@ -0,0 +1,36 @@ +{ + "name": "@buildwithsygma/sygma-sdk-btc-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": "tsx src/transfer.ts", + "watch": "tsx watch src/transfer.ts" + }, + "author": "Sygmaprotocol Product Team", + "license": "LGPL-3.0-or-later", + "devDependencies": { + "dotenv": "^16.3.1", + "eslint": "8", + "ts-node": "10.9.1", + "typescript": "5.0.4" + }, + "dependencies": { + "@buildwithsygma/btc": "workspace:^", + "@buildwithsygma/core": "workspace:^", + "tsx": "^4.15.4" + } +} diff --git a/examples/btc-to-evm-fungible-transfer/tsconfig.json b/examples/btc-to-evm-fungible-transfer/tsconfig.json new file mode 100644 index 000000000..4078b3ff7 --- /dev/null +++ b/examples/btc-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 From 13b51d7ca6709c762166b5df3ff10314449f803b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 19 Jun 2024 23:04:05 -0400 Subject: [PATCH 06/87] feat: base implementation for btc example --- .../btc-to-evm-fungible-transfer/package.json | 6 +- .../src/transfer.ts | 98 +++++++++++++++ .../btc-to-evm-fungible-transfer/src/utils.ts | 86 +++++++++++++ yarn.lock | 113 +++++++++++++++++- 4 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 examples/btc-to-evm-fungible-transfer/src/transfer.ts create mode 100644 examples/btc-to-evm-fungible-transfer/src/utils.ts diff --git a/examples/btc-to-evm-fungible-transfer/package.json b/examples/btc-to-evm-fungible-transfer/package.json index b4a2692ad..c5868b3c7 100644 --- a/examples/btc-to-evm-fungible-transfer/package.json +++ b/examples/btc-to-evm-fungible-transfer/package.json @@ -17,8 +17,7 @@ "bitcoin" ], "scripts": { - "transfer": "tsx src/transfer.ts", - "watch": "tsx watch src/transfer.ts" + "transfer": "tsx src/transfer.ts" }, "author": "Sygmaprotocol Product Team", "license": "LGPL-3.0-or-later", @@ -31,6 +30,9 @@ "dependencies": { "@buildwithsygma/btc": "workspace:^", "@buildwithsygma/core": "workspace:^", + "bitcoinjs-lib": "^6.1.6", + "ecpair": "^2.1.0", + "tiny-secp256k1": "^2.2.3", "tsx": "^4.15.4" } } diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts new file mode 100644 index 000000000..471272a1e --- /dev/null +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -0,0 +1,98 @@ +import dotenv from 'dotenv'; +import { createBitcoinFungibleTransfer } from '@buildwithsygma/btc'; +import { ECPairFactory, ECPairAPI, TinySecp256k1Interface } from 'ecpair'; +import { + initEccLib, + networks, + payments, + Psbt +} from "bitcoinjs-lib"; +import * as tinysecp from 'tiny-secp256k1'; +import { Utxo, broadcastTransaction, getLastUtxo, getTweakedSigner, toXOnly } from './utils'; + +dotenv.config(); + +const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; +const DOMAIN_ID = process.env.DOMAIN_ID; +const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; +const PRIVATE_KEY = process.env.PRIVATE_KEY; + +if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DOMAIN_ID || !BLOCKSTREAM_URL) { + throw new Error('Please provide DESTINATION_ADDRESS or PRIVATE_KEY or DOMAIN_ID or BLOCKSTREAM_URL in .env file'); +} + +async function btcToEvmTransfer(): Promise { + // pre setup + const testnet = networks.testnet; + initEccLib(tinysecp as any); + const ECPair: ECPairAPI = ECPairFactory(tinysecp); + + // Transfer BTC to EVM + console.log('Transfer BTC to EVM'); + + // tweaking signer + const { tweakedSigner, publicKey } = getTweakedSigner(ECPair, tinysecp, testnet, PRIVATE_KEY!); + + // Generate an address from the tweaked public key + const p2pktr = payments.p2tr({ + pubkey: toXOnly(tweakedSigner.publicKey), + network: testnet, + }); + + + const p2pktrAdddress = p2pktr.address as string; + // address here should match the one that you generated for your private key + console.log('taproot address', p2pktrAdddress) + + // Get for UTXO + const utxoData: Utxo = await getLastUtxo(BLOCKSTREAM_URL!, p2pktrAdddress); + + const params = { + sourceDomain: 3, // current Domain Id for the BTC domain on devnet + destinationAddress: DESTINATION_ADDRESS, + amount: utxoData.value, + resource: '0x0000000000000000000000000000000000000000000000000000000000000300' // resource id for BTC + } + const transfer = await createBitcoinFungibleTransfer(params); + + const transferRequestData = transfer.getBTCTransferRequest(); + console.log('Transfer Request Data:', transferRequestData); + + const psbt = new Psbt({ network: testnet }); + + psbt.addInput({ + hash: utxoData.txid, + index: utxoData.vout, + witnessUtxo: { value: utxoData.value, script: p2pktr.output! }, + tapInternalKey: toXOnly(publicKey) + }); + + const data = Buffer.from( + `${DESTINATION_ADDRESS}_${DOMAIN_ID}`, // EMV ADDRESS + DESTINATION DOMAIN ID HERE + "utf8", + ); + + const embed = payments.embed({ data: [data] }); + + psbt.addOutput({ + script: embed.output!, + value: 0, + }); + + psbt.addOutput({ + address: transferRequestData.depositAddress, + value: transferRequestData.amount - (161830) + }); + + psbt.signInput(0, tweakedSigner); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(true); + + console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); + const txid = await broadcastTransaction(BLOCKSTREAM_URL!, tx.toHex()); + + console.log(`Transaction ID: ${txid}`); +} + +btcToEvmTransfer().finally(() => {}) \ No newline at end of file diff --git a/examples/btc-to-evm-fungible-transfer/src/utils.ts b/examples/btc-to-evm-fungible-transfer/src/utils.ts new file mode 100644 index 000000000..f4182d2fc --- /dev/null +++ b/examples/btc-to-evm-fungible-transfer/src/utils.ts @@ -0,0 +1,86 @@ +import { Signer, networks, crypto } from "bitcoinjs-lib"; +import { ECPairAPI, TinySecp256k1Interface } from "ecpair"; + +// shortens the public key to 32 bytes +export function toXOnly(pubkey: Buffer): Buffer { + return pubkey.subarray(1, 33) +} + +function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { + return crypto.taggedHash( + 'TapTweak', + Buffer.concat(h ? [pubKey, h] : [pubKey]), + ); +} + +// Get the tweaked signer and returns also public key +export function getTweakedSigner(ECPair: ECPairAPI, tinysecp: TinySecp256k1Interface, network: networks.Network, privateKey: string): { tweakedSigner: Signer, publicKey: Buffer } { + const keypair = ECPair.fromWIF(privateKey!, networks.testnet) as Signer; + // @ts-ignore + let privKey: Uint8Array = keypair.privateKey!; + + if (keypair.publicKey[0] === 3) { + privKey = tinysecp.privateNegate(privKey); + } + + const tweakedPrivateKey = tinysecp.privateAdd( + privKey, + tapTweakHash(toXOnly(keypair.publicKey), undefined), + ); + + if (!tweakedPrivateKey) { + throw new Error('Invalid tweaked private key!'); + } + + return { + tweakedSigner: ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { + network: networks.testnet, + }), publicKey: keypair.publicKey + }; +} + +export type Utxo = { + txid: string; + vout: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + value: number; +} + +export async function getLastUtxo(blockstreamUrl: string, bitcoinAddress: string): Promise { + try { + const response = await fetch(`${blockstreamUrl}/address/${bitcoinAddress}/utxo`); + + const data = await response.json(); + + if(data.length === 0) { + throw new Error('No UTXO found'); + } + + return data[0] as Utxo; + } catch (error) { + throw error; + } +} + +export async function broadcastTransaction(blockstreamUrl: string, txHex: string): Promise { + try { + const response = await fetch(`${blockstreamUrl}/tx`, { + method: 'POST', + body: txHex, + headers: { + 'Content-Type': 'application/json' + } + }); + + const data = await response.json(); + + return data; + } catch (error) { + throw new Error('Failed to broadcast transaction'); + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 824c41ed7..34868876d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -528,8 +528,11 @@ __metadata: dependencies: "@buildwithsygma/btc": "workspace:^" "@buildwithsygma/core": "workspace:^" + 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" @@ -2561,7 +2564,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:^1.4.0": +"@noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" checksum: 8c3f005ee72e7b8f9cff756dfae1241485187254e3f743873e22073d63906863df5d4f13d441b7530ea614b7a093f0d889309f28b59850f33b66cb26a779a4a5 @@ -4467,6 +4470,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 + "base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -4488,6 +4498,13 @@ __metadata: languageName: node linkType: hard +"bech32@npm:^2.0.0": + version: 2.0.0 + resolution: "bech32@npm:2.0.0" + checksum: 45e7cc62758c9b26c05161b4483f40ea534437cf68ef785abadc5b62a2611319b878fef4f86ddc14854f183b645917a19addebc9573ab890e19194bc8f521942 + languageName: node + linkType: hard + "better-path-resolve@npm:1.0.0": version: 1.0.0 resolution: "better-path-resolve@npm:1.0.0" @@ -4504,6 +4521,27 @@ __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 + +"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" @@ -4636,7 +4674,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: @@ -4647,6 +4694,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" @@ -5593,6 +5650,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 + "ed2curve@npm:^0.3.0": version: 0.3.0 resolution: "ed2curve@npm:0.3.0" @@ -12325,6 +12393,15 @@ __metadata: languageName: node linkType: hard +"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, tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -12811,6 +12888,13 @@ __metadata: languageName: node linkType: hard +"typeforce@npm:^1.11.3, 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.4.5 resolution: "typescript@npm:5.4.5" @@ -12840,6 +12924,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" @@ -13003,6 +13094,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" @@ -13339,6 +13439,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 addaf8c52f36c9707f534d0afabfbc387b303daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 19 Jun 2024 23:05:10 -0400 Subject: [PATCH 07/87] chore: update env sample file --- examples/btc-to-evm-fungible-transfer/.env.sample | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index e69de29bb..3024eb8c7 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -0,0 +1,5 @@ +SYGMA_ENV= +BLOCKSTREAM_URL= +DESTINATION_ADDRESS= +PRIVATE_KEY= +DOMAIN_ID= \ No newline at end of file From 20f916bc10a915000cf9f935c6a3dfd52fca758a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 25 Jun 2024 23:23:10 -0400 Subject: [PATCH 08/87] feat: evm to btc example --- .../evm-to-btc-fungible-transfer/.env.sample | 4 ++ .../evm-to-btc-fungible-transfer/README.md | 1 + .../evm-to-btc-fungible-transfer/package.json | 35 ++++++++++++ .../src/transfer.ts | 56 +++++++++++++++++++ .../tsconfig.json | 21 +++++++ packages/evm/src/fungible.ts | 4 +- packages/evm/src/utils/helpers.ts | 10 +++- yarn.lock | 14 +++++ 8 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 examples/evm-to-btc-fungible-transfer/.env.sample create mode 100644 examples/evm-to-btc-fungible-transfer/README.md create mode 100644 examples/evm-to-btc-fungible-transfer/package.json create mode 100644 examples/evm-to-btc-fungible-transfer/src/transfer.ts create mode 100644 examples/evm-to-btc-fungible-transfer/tsconfig.json diff --git a/examples/evm-to-btc-fungible-transfer/.env.sample b/examples/evm-to-btc-fungible-transfer/.env.sample new file mode 100644 index 000000000..0fbadef91 --- /dev/null +++ b/examples/evm-to-btc-fungible-transfer/.env.sample @@ -0,0 +1,4 @@ +SEPOLIA_RPC_URL= +BTC_DESTINATION_ADDRESS= +PRIVATE_KEY= +SYGMA_ENV=devnet \ No newline at end of file diff --git a/examples/evm-to-btc-fungible-transfer/README.md b/examples/evm-to-btc-fungible-transfer/README.md new file mode 100644 index 000000000..838ebc898 --- /dev/null +++ b/examples/evm-to-btc-fungible-transfer/README.md @@ -0,0 +1 @@ +# evm-to-btc-fungible-transfer diff --git a/examples/evm-to-btc-fungible-transfer/package.json b/examples/evm-to-btc-fungible-transfer/package.json new file mode 100644 index 000000000..aef7d4fd6 --- /dev/null +++ b/examples/evm-to-btc-fungible-transfer/package.json @@ -0,0 +1,35 @@ +{ + "name": "@buildwithsygma/sygma-sdk-evm-to-btc-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-btc-fungible-transfer/src/transfer.ts b/examples/evm-to-btc-fungible-transfer/src/transfer.ts new file mode 100644 index 000000000..7a0661c3e --- /dev/null +++ b/examples/evm-to-btc-fungible-transfer/src/transfer.ts @@ -0,0 +1,56 @@ +import { Environment, } from "@buildwithsygma/core"; +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_ID = 3; +const RESOURCE_ID = "0x0000000000000000000000000000000000000000000000000000000000000300"; +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_ID, + sourceNetworkProvider: web3Provider, + resource: RESOURCE_ID, + amount: BigInt(1) * BigInt(1e8), + destinationAddress: BTC_DESTINATION_ADDRESS, + environment: Environment.DEVNET, + 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(`Depositted, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`); +} + +erc20Transfer().finally(() => {}); \ No newline at end of file diff --git a/examples/evm-to-btc-fungible-transfer/tsconfig.json b/examples/evm-to-btc-fungible-transfer/tsconfig.json new file mode 100644 index 000000000..4078b3ff7 --- /dev/null +++ b/examples/evm-to-btc-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/packages/evm/src/fungible.ts b/packages/evm/src/fungible.ts index c2954b804..3c76988e4 100644 --- a/packages/evm/src/fungible.ts +++ b/packages/evm/src/fungible.ts @@ -2,7 +2,7 @@ import type { Domainlike, EvmResource } from '@buildwithsygma/core'; import { SecurityModel, Config, FeeHandlerType } from '@buildwithsygma/core'; import { Bridge__factory, ERC20__factory } from '@buildwithsygma/sygma-contracts'; import { Web3Provider } from '@ethersproject/providers'; -import { BigNumber, constants, providers, utils, type PopulatedTransaction } from 'ethers'; +import { BigNumber, providers, type PopulatedTransaction, constants, utils } from 'ethers'; import type { Eip1193Provider, EvmFee, TransactionRequest } from 'types.js'; import { BaseTransfer } from './base-transfer.js'; @@ -89,7 +89,7 @@ class EvmFungibleAssetTransfer extends BaseTransfer { protected _amount: bigint; get amount(): bigint { - return this.amount; + return this._amount; } constructor(transfer: EvmFungibleTransferRequest, config: Config) { diff --git a/packages/evm/src/utils/helpers.ts b/packages/evm/src/utils/helpers.ts index 75fa16711..3b6bc98b8 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(`0x${hexAddress}`); } + const depositDataBytes = constructMainDepositData( BigNumber.from(tokenAmount), recipientAddressInBytes, @@ -136,3 +140,7 @@ export const toHex = (covertThis: string | number | BigNumber, padding: number): const amount = covertThis instanceof BigNumber ? covertThis : BigNumber.from(covertThis); return utils.hexZeroPad(utils.hexlify(amount), padding); }; + +export const addressToHex = (address: string): string => { + return address.split('').map((_, idx) => address.charCodeAt(idx).toString(16)).join(''); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 34868876d..fe4e867b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -539,6 +539,20 @@ __metadata: languageName: unknown linkType: soft +"@buildwithsygma/sygma-sdk-evm-to-btc-fungible-transfer@workspace:examples/evm-to-btc-fungible-transfer": + version: 0.0.0-use.local + resolution: "@buildwithsygma/sygma-sdk-evm-to-btc-fungible-transfer@workspace:examples/evm-to-btc-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-evm-to-evm-fungible-transfer-example@workspace:examples/evm-to-evm-fungible-transfer": version: 0.0.0-use.local resolution: "@buildwithsygma/sygma-sdk-evm-to-evm-fungible-transfer-example@workspace:examples/evm-to-evm-fungible-transfer" From 5cd9e4afd4e04650a58cc31e55fc4bbde061c3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 25 Jun 2024 23:32:38 -0400 Subject: [PATCH 09/87] feat: adding some updates to btc to evm example --- .../src/transfer.ts | 64 +++++++++++------ .../btc-to-evm-fungible-transfer/src/utils.ts | 70 ++++++++++++++----- 2 files changed, 96 insertions(+), 38 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index 471272a1e..8528f507b 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -1,6 +1,6 @@ import dotenv from 'dotenv'; import { createBitcoinFungibleTransfer } from '@buildwithsygma/btc'; -import { ECPairFactory, ECPairAPI, TinySecp256k1Interface } from 'ecpair'; +import { ECPairFactory, ECPairAPI } from 'ecpair'; import { initEccLib, networks, @@ -8,17 +8,19 @@ import { Psbt } from "bitcoinjs-lib"; import * as tinysecp from 'tiny-secp256k1'; -import { Utxo, broadcastTransaction, getLastUtxo, getTweakedSigner, toXOnly } from './utils'; +import { Utxo, broadcastTransaction, calculateFee, getFeeEstimates, getLastConfirmedUTXO, getTweakedSigner, toXOnly } from './utils'; dotenv.config(); const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; -const DOMAIN_ID = process.env.DOMAIN_ID; +const DESTINATION_DOMAIN_ID = Number(process.env.DESTINATION_DOMAIN_ID); const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; +const RESOURCE_ID = process.env.RESOURCE_ID; +const SOURCE_DOMAIN_ID = Number(process.env.SOURCE_DOMAIN_ID); -if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DOMAIN_ID || !BLOCKSTREAM_URL) { - throw new Error('Please provide DESTINATION_ADDRESS or PRIVATE_KEY or DOMAIN_ID or BLOCKSTREAM_URL in .env file'); +if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DESTINATION_DOMAIN_ID || !BLOCKSTREAM_URL || !RESOURCE_ID || !SOURCE_DOMAIN_ID) { + throw new Error('Please provide DESTINATION_ADDRESS or PRIVATE_KEY or DOMAIN_ID or BLOCKSTREAM_URL or RESOURCE_ID in .env file'); } async function btcToEvmTransfer(): Promise { @@ -45,13 +47,14 @@ async function btcToEvmTransfer(): Promise { console.log('taproot address', p2pktrAdddress) // Get for UTXO - const utxoData: Utxo = await getLastUtxo(BLOCKSTREAM_URL!, p2pktrAdddress); + const utxoData: Utxo = await getLastConfirmedUTXO(BLOCKSTREAM_URL!, p2pktrAdddress); + const feeEstimatesPerBlockConfirmation = await getFeeEstimates(BLOCKSTREAM_URL!); const params = { - sourceDomain: 3, // current Domain Id for the BTC domain on devnet + sourceDomain: SOURCE_DOMAIN_ID, destinationAddress: DESTINATION_ADDRESS, amount: utxoData.value, - resource: '0x0000000000000000000000000000000000000000000000000000000000000300' // resource id for BTC + resource: RESOURCE_ID } const transfer = await createBitcoinFungibleTransfer(params); @@ -60,34 +63,51 @@ async function btcToEvmTransfer(): Promise { const psbt = new Psbt({ network: testnet }); - psbt.addInput({ + const data = Buffer.from( + `${DESTINATION_ADDRESS}_${DESTINATION_DOMAIN_ID}`, // EMV ADDRESS + DESTINATION DOMAIN ID HERE + "utf8", + ); + + const embed = payments.embed({ data: [data] }); + + const feeValue = calculateFee(psbt, feeEstimatesPerBlockConfirmation, { hash: utxoData.txid, index: utxoData.vout, witnessUtxo: { value: utxoData.value, script: p2pktr.output! }, tapInternalKey: toXOnly(publicKey) - }); + }, { + script: embed.output!, + value: 0, + }, { + address: transferRequestData.depositAddress, + value: utxoData.value - transferRequestData.amount + }, tweakedSigner); - const data = Buffer.from( - `${DESTINATION_ADDRESS}_${DOMAIN_ID}`, // EMV ADDRESS + DESTINATION DOMAIN ID HERE - "utf8", - ); + console.log('feeValue', feeValue, transferRequestData.amount, utxoData.value /2, transferRequestData.amount - feeValue) - const embed = payments.embed({ data: [data] }); + const psbtWithFee = new Psbt({ network: testnet }); + + psbtWithFee.addInput({ + hash: utxoData.txid, + index: utxoData.vout, + witnessUtxo: { value: utxoData.value, script: p2pktr.output! }, + tapInternalKey: toXOnly(publicKey) + }); - psbt.addOutput({ + psbtWithFee.addOutput({ script: embed.output!, value: 0, }); - psbt.addOutput({ + psbtWithFee.addOutput({ address: transferRequestData.depositAddress, - value: transferRequestData.amount - (161830) + value: transferRequestData.amount - feeValue }); - psbt.signInput(0, tweakedSigner); - psbt.finalizeAllInputs(); + psbtWithFee.signInput(0, tweakedSigner); + psbtWithFee.finalizeAllInputs(); - const tx = psbt.extractTransaction(true); + const tx = psbtWithFee.extractTransaction(true); console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); const txid = await broadcastTransaction(BLOCKSTREAM_URL!, tx.toHex()); @@ -95,4 +115,4 @@ async function btcToEvmTransfer(): Promise { console.log(`Transaction ID: ${txid}`); } -btcToEvmTransfer().finally(() => {}) \ No newline at end of file +btcToEvmTransfer().finally(() => { }) \ No newline at end of file diff --git a/examples/btc-to-evm-fungible-transfer/src/utils.ts b/examples/btc-to-evm-fungible-transfer/src/utils.ts index f4182d2fc..483076f5e 100644 --- a/examples/btc-to-evm-fungible-transfer/src/utils.ts +++ b/examples/btc-to-evm-fungible-transfer/src/utils.ts @@ -1,6 +1,23 @@ -import { Signer, networks, crypto } from "bitcoinjs-lib"; +import { Signer, networks, crypto, Psbt } from "bitcoinjs-lib"; import { ECPairAPI, TinySecp256k1Interface } from "ecpair"; +export type Utxo = { + txid: string; + vout: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + value: number; +}; + +type InputData = { hash: string, index: number, witnessUtxo: { value: number, script: Buffer }, tapInternalKey: Buffer } + +type OutputData = { value: number, script: Buffer, address: string } + + // shortens the public key to 32 bytes export function toXOnly(pubkey: Buffer): Buffer { return pubkey.subarray(1, 33) @@ -39,34 +56,40 @@ export function getTweakedSigner(ECPair: ECPairAPI, tinysecp: TinySecp256k1Inter }; } -export type Utxo = { - txid: string; - vout: number; - status: { - confirmed: boolean; - block_height: number; - block_hash: string; - block_time: number; - }; - value: number; -} - -export async function getLastUtxo(blockstreamUrl: string, bitcoinAddress: string): Promise { +export async function getLastConfirmedUTXO(blockstreamUrl: string, bitcoinAddress: string): Promise { try { const response = await fetch(`${blockstreamUrl}/address/${bitcoinAddress}/utxo`); const data = await response.json(); - if(data.length === 0) { + if (data.length === 0) { throw new Error('No UTXO found'); } - return data[0] as Utxo; + const confirmedUtxos = data.filter((utxo: Utxo) => utxo.status.confirmed); + + if (confirmedUtxos.length === 0) { + throw new Error('No confirmed UTXO found'); + } + + return confirmedUtxos[0]; } catch (error) { throw error; } } +export async function getFeeEstimates(blockstreamUrl: string): Promise { + try { + const response = await fetch(`${blockstreamUrl}/fee-estimates`); + + const data = await response.json(); + + return data['5']; // fee for 5 blocks confirmation + } catch (error) { + throw new Error('Failed to get fee estimates'); + } +} + export async function broadcastTransaction(blockstreamUrl: string, txHex: string): Promise { try { const response = await fetch(`${blockstreamUrl}/tx`, { @@ -83,4 +106,19 @@ export async function broadcastTransaction(blockstreamUrl: string, txHex: string } catch (error) { throw new Error('Failed to broadcast transaction'); } +} + +export function calculateFee(psbt: Psbt, feeRate: number, inputData: InputData, bridgeOutputData: Pick, valueOutputData: Pick, tweakedSigner: Signer): number { + psbt.addInput(inputData); + psbt.addOutput(bridgeOutputData); + psbt.addOutput(valueOutputData); + + psbt.signInput(0, tweakedSigner); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(true); + + const virtualSize = tx.virtualSize(); + + return Math.round(virtualSize * feeRate); } \ No newline at end of file From c40b08f53fb1d7b88da8968f1a9c3626d7f2cec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 25 Jun 2024 23:33:49 -0400 Subject: [PATCH 10/87] chore: update env sample file --- examples/btc-to-evm-fungible-transfer/.env.sample | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index 3024eb8c7..be7e54f89 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -2,4 +2,6 @@ SYGMA_ENV= BLOCKSTREAM_URL= DESTINATION_ADDRESS= PRIVATE_KEY= -DOMAIN_ID= \ No newline at end of file +DESTINATION_DOMAIN_ID= +RESOURCE_ID= +SOURCE_DOMAIN_ID= \ No newline at end of file From ab25db581ca8cebec9498f5081c485f89147f282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 25 Jun 2024 23:38:14 -0400 Subject: [PATCH 11/87] chore: update to btc to evm example --- .../src/transfer.ts | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index 8528f507b..f409a8c60 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -70,39 +70,34 @@ async function btcToEvmTransfer(): Promise { const embed = payments.embed({ data: [data] }); - const feeValue = calculateFee(psbt, feeEstimatesPerBlockConfirmation, { + const inputData = { hash: utxoData.txid, index: utxoData.vout, witnessUtxo: { value: utxoData.value, script: p2pktr.output! }, tapInternalKey: toXOnly(publicKey) - }, { + }; + + const outputEncodedData = { script: embed.output!, value: 0, - }, { + }; + + const outputData = { address: transferRequestData.depositAddress, value: utxoData.value - transferRequestData.amount - }, tweakedSigner); + }; + + const feeValue = calculateFee(psbt, feeEstimatesPerBlockConfirmation, inputData, outputEncodedData, outputData, tweakedSigner); console.log('feeValue', feeValue, transferRequestData.amount, utxoData.value /2, transferRequestData.amount - feeValue) const psbtWithFee = new Psbt({ network: testnet }); - psbtWithFee.addInput({ - hash: utxoData.txid, - index: utxoData.vout, - witnessUtxo: { value: utxoData.value, script: p2pktr.output! }, - tapInternalKey: toXOnly(publicKey) - }); + psbtWithFee.addInput(inputData); - psbtWithFee.addOutput({ - script: embed.output!, - value: 0, - }); + psbtWithFee.addOutput(outputEncodedData); - psbtWithFee.addOutput({ - address: transferRequestData.depositAddress, - value: transferRequestData.amount - feeValue - }); + psbtWithFee.addOutput(outputData); psbtWithFee.signInput(0, tweakedSigner); psbtWithFee.finalizeAllInputs(); From affdcb8a00ef88ee30f651685c87e47ba86d4657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 25 Jun 2024 23:40:10 -0400 Subject: [PATCH 12/87] chore: update to btc to evm example --- examples/btc-to-evm-fungible-transfer/src/transfer.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index f409a8c60..e2f497bc7 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -20,7 +20,7 @@ const RESOURCE_ID = process.env.RESOURCE_ID; const SOURCE_DOMAIN_ID = Number(process.env.SOURCE_DOMAIN_ID); if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DESTINATION_DOMAIN_ID || !BLOCKSTREAM_URL || !RESOURCE_ID || !SOURCE_DOMAIN_ID) { - throw new Error('Please provide DESTINATION_ADDRESS or PRIVATE_KEY or DOMAIN_ID or BLOCKSTREAM_URL or RESOURCE_ID in .env file'); + throw new Error('Please provided needed env variavles in .env file'); } async function btcToEvmTransfer(): Promise { @@ -41,10 +41,9 @@ async function btcToEvmTransfer(): Promise { network: testnet, }); - const p2pktrAdddress = p2pktr.address as string; // address here should match the one that you generated for your private key - console.log('taproot address', p2pktrAdddress) + console.log('Taproot address to use', p2pktrAdddress) // Get for UTXO const utxoData: Utxo = await getLastConfirmedUTXO(BLOCKSTREAM_URL!, p2pktrAdddress); @@ -59,7 +58,6 @@ async function btcToEvmTransfer(): Promise { const transfer = await createBitcoinFungibleTransfer(params); const transferRequestData = transfer.getBTCTransferRequest(); - console.log('Transfer Request Data:', transferRequestData); const psbt = new Psbt({ network: testnet }); @@ -89,8 +87,6 @@ async function btcToEvmTransfer(): Promise { const feeValue = calculateFee(psbt, feeEstimatesPerBlockConfirmation, inputData, outputEncodedData, outputData, tweakedSigner); - console.log('feeValue', feeValue, transferRequestData.amount, utxoData.value /2, transferRequestData.amount - feeValue) - const psbtWithFee = new Psbt({ network: testnet }); psbtWithFee.addInput(inputData); From 6647e6dfb4d8337fe93e57509b732c6c6701c056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 25 Jun 2024 23:44:51 -0400 Subject: [PATCH 13/87] chore: using fee value --- examples/btc-to-evm-fungible-transfer/src/transfer.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index e2f497bc7..f51fc73fd 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -93,7 +93,10 @@ async function btcToEvmTransfer(): Promise { psbtWithFee.addOutput(outputEncodedData); - psbtWithFee.addOutput(outputData); + psbtWithFee.addOutput({ + ...outputData, + value: outputData.value - feeValue + }); psbtWithFee.signInput(0, tweakedSigner); psbtWithFee.finalizeAllInputs(); From f1da6f1127b04112340ce244219a3461ca06e05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 2 Jul 2024 00:13:14 -0400 Subject: [PATCH 14/87] feat: adding readme, removing utility function and updating env sample template --- .../btc-to-evm-fungible-transfer/.env.sample | 7 +- .../btc-to-evm-fungible-transfer/README.md | 78 ++++++++++++++++++- .../src/transfer.ts | 63 ++++++++++----- .../btc-to-evm-fungible-transfer/src/utils.ts | 45 ++--------- .../src/transfer.ts | 2 +- 5 files changed, 133 insertions(+), 62 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index be7e54f89..7959f5115 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -1,7 +1,8 @@ -SYGMA_ENV= -BLOCKSTREAM_URL= +SYGMA_ENV=devnet +BLOCKSTREAM_URL=https://blockstream.info/testnet/api DESTINATION_ADDRESS= PRIVATE_KEY= DESTINATION_DOMAIN_ID= RESOURCE_ID= -SOURCE_DOMAIN_ID= \ No newline at end of file +SOURCE_DOMAIN_ID= +EXPLORER_URL=https://blockstream.info/testnet/tx \ No newline at end of file diff --git a/examples/btc-to-evm-fungible-transfer/README.md b/examples/btc-to-evm-fungible-transfer/README.md index 36b54abcd..891f75daa 100644 --- a/examples/btc-to-evm-fungible-transfer/README.md +++ b/examples/btc-to-evm-fungible-transfer/README.md @@ -1 +1,77 @@ -# btc-to-evm-fungible-transfer +# Sygma SDK BTC to EVM example + +## Sygma SDK ERC20 Example + +This is an example script that demonstrates the functionality of the SDK using the Sygma ecosystem. The script showcases a asset transfer between a BTC testnet account and a EVM Sepolia account. + +## 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 between the quotation marks your taproot address private key: + +`PRIVATE_KEY="YOUR_PRIVATE_KEY_HERE"` + +To send Testnet BTC to your EVM account on Sepolia run: + +```bash +yarn run transfer +``` + +Replace the placeholder values in the `.env` file with your own Testnet BTC Taproot private key as well as the other env variables needed such as DESTINATION_ADDRESS, DESTINATION_DOMAIN_ID, RESOURCE_ID and SOURCE_DOMAIN_ID. + +## 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/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index f51fc73fd..5423cdbb5 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -8,7 +8,7 @@ import { Psbt } from "bitcoinjs-lib"; import * as tinysecp from 'tiny-secp256k1'; -import { Utxo, broadcastTransaction, calculateFee, getFeeEstimates, getLastConfirmedUTXO, getTweakedSigner, toXOnly } from './utils'; +import { broadcastTransaction, calculateFee, getFeeEstimates, getTweakedSigner, toXOnly } from './utils'; dotenv.config(); @@ -18,18 +18,27 @@ const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; const RESOURCE_ID = process.env.RESOURCE_ID; const SOURCE_DOMAIN_ID = Number(process.env.SOURCE_DOMAIN_ID); +const FEE_ADDRESS = 'tb1p0r2w3ugreaggd7nakw2wd04up6rl8k0cce8eetxwmhnrelgqx87s4zdkd7' +const FEE_AMOUNT = 1000000; +const EXPLORER_URL = process.env.EXPLORER_URL; if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DESTINATION_DOMAIN_ID || !BLOCKSTREAM_URL || !RESOURCE_ID || !SOURCE_DOMAIN_ID) { throw new Error('Please provided needed env variavles in .env file'); } +type InputData = { + hash: string; + index: number; + witnessUtxo: { value: number; script: Buffer }; + tapInternalKey: Buffer; +}; + async function btcToEvmTransfer(): Promise { // pre setup const testnet = networks.testnet; initEccLib(tinysecp as any); const ECPair: ECPairAPI = ECPairFactory(tinysecp); - // Transfer BTC to EVM console.log('Transfer BTC to EVM'); // tweaking signer @@ -45,22 +54,35 @@ async function btcToEvmTransfer(): Promise { // address here should match the one that you generated for your private key console.log('Taproot address to use', p2pktrAdddress) - // Get for UTXO - const utxoData: Utxo = await getLastConfirmedUTXO(BLOCKSTREAM_URL!, p2pktrAdddress); const feeEstimatesPerBlockConfirmation = await getFeeEstimates(BLOCKSTREAM_URL!); + /** + * Get UTXO to use for the transfer + * You can get UTXO from any source, for this example we are using Blockstream API + * Add the txid and vout of the UTXO to the inputData. Also add the value of the UTXO to the witnessUtxo value field + */ + + const inputData: InputData = { + hash: '', // utxo tx id + index: 0, // utxo index + witnessUtxo: { value: 0, script: p2pktr.output! }, // utxo value + tapInternalKey: toXOnly(publicKey) + }; + const params = { sourceDomain: SOURCE_DOMAIN_ID, destinationAddress: DESTINATION_ADDRESS, - amount: utxoData.value, + amount: inputData.witnessUtxo.value, resource: RESOURCE_ID } + const transfer = await createBitcoinFungibleTransfer(params); const transferRequestData = transfer.getBTCTransferRequest(); const psbt = new Psbt({ network: testnet }); + // encoded data const data = Buffer.from( `${DESTINATION_ADDRESS}_${DESTINATION_DOMAIN_ID}`, // EMV ADDRESS + DESTINATION DOMAIN ID HERE "utf8", @@ -68,34 +90,39 @@ async function btcToEvmTransfer(): Promise { const embed = payments.embed({ data: [data] }); - const inputData = { - hash: utxoData.txid, - index: utxoData.vout, - witnessUtxo: { value: utxoData.value, script: p2pktr.output! }, - tapInternalKey: toXOnly(publicKey) - }; + const amount = transferRequestData.amount - (16183 + 1000) + const amountMinusBridgeFee = amount - FEE_AMOUNT; const outputEncodedData = { script: embed.output!, value: 0, }; - + const outputData = { address: transferRequestData.depositAddress, - value: utxoData.value - transferRequestData.amount + value: amountMinusBridgeFee }; - - const feeValue = calculateFee(psbt, feeEstimatesPerBlockConfirmation, inputData, outputEncodedData, outputData, tweakedSigner); + + const outputDataFee = { + address: FEE_ADDRESS, + value: FEE_AMOUNT + }; + + const feeValue = calculateFee(psbt, feeEstimatesPerBlockConfirmation, inputData, outputEncodedData, outputData, outputDataFee, tweakedSigner); const psbtWithFee = new Psbt({ network: testnet }); + const amountWitFeeApplied = amount - feeValue - FEE_AMOUNT; + psbtWithFee.addInput(inputData); psbtWithFee.addOutput(outputEncodedData); + psbtWithFee.addOutput(outputDataFee); + psbtWithFee.addOutput({ - ...outputData, - value: outputData.value - feeValue + address: transferRequestData.depositAddress, + value: amountWitFeeApplied }); psbtWithFee.signInput(0, tweakedSigner); @@ -106,7 +133,7 @@ async function btcToEvmTransfer(): Promise { console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); const txid = await broadcastTransaction(BLOCKSTREAM_URL!, tx.toHex()); - console.log(`Transaction ID: ${txid}`); + console.log(`Transaction ID: ${EXPLORER_URL}/${txid}`); } btcToEvmTransfer().finally(() => { }) \ No newline at end of file diff --git a/examples/btc-to-evm-fungible-transfer/src/utils.ts b/examples/btc-to-evm-fungible-transfer/src/utils.ts index 483076f5e..d498c4f34 100644 --- a/examples/btc-to-evm-fungible-transfer/src/utils.ts +++ b/examples/btc-to-evm-fungible-transfer/src/utils.ts @@ -1,19 +1,8 @@ import { Signer, networks, crypto, Psbt } from "bitcoinjs-lib"; import { ECPairAPI, TinySecp256k1Interface } from "ecpair"; -export type Utxo = { - txid: string; - vout: number; - status: { - confirmed: boolean; - block_height: number; - block_hash: string; - block_time: number; - }; - value: number; -}; -type InputData = { hash: string, index: number, witnessUtxo: { value: number, script: Buffer }, tapInternalKey: Buffer } +type InputData = { hash: string, index: number, witnessUtxo: { value: number, script: Buffer }, tapInternalKey: Buffer } type OutputData = { value: number, script: Buffer, address: string } @@ -56,28 +45,6 @@ export function getTweakedSigner(ECPair: ECPairAPI, tinysecp: TinySecp256k1Inter }; } -export async function getLastConfirmedUTXO(blockstreamUrl: string, bitcoinAddress: string): Promise { - try { - const response = await fetch(`${blockstreamUrl}/address/${bitcoinAddress}/utxo`); - - const data = await response.json(); - - if (data.length === 0) { - throw new Error('No UTXO found'); - } - - const confirmedUtxos = data.filter((utxo: Utxo) => utxo.status.confirmed); - - if (confirmedUtxos.length === 0) { - throw new Error('No confirmed UTXO found'); - } - - return confirmedUtxos[0]; - } catch (error) { - throw error; - } -} - export async function getFeeEstimates(blockstreamUrl: string): Promise { try { const response = await fetch(`${blockstreamUrl}/fee-estimates`); @@ -96,22 +63,22 @@ export async function broadcastTransaction(blockstreamUrl: string, txHex: string method: 'POST', body: txHex, headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'text/plain' } }); - const data = await response.json(); - - return data; + return await response.text(); } catch (error) { + console.log('error', error) throw new Error('Failed to broadcast transaction'); } } -export function calculateFee(psbt: Psbt, feeRate: number, inputData: InputData, bridgeOutputData: Pick, valueOutputData: Pick, tweakedSigner: Signer): number { +export function calculateFee(psbt: Psbt, feeRate: number, inputData: InputData, bridgeOutputData: Pick, valueOutputData: Pick, outputFeeData: Pick, tweakedSigner: Signer): number { psbt.addInput(inputData); psbt.addOutput(bridgeOutputData); psbt.addOutput(valueOutputData); + psbt.addOutput(outputFeeData); psbt.signInput(0, tweakedSigner); psbt.finalizeAllInputs(); diff --git a/examples/evm-to-btc-fungible-transfer/src/transfer.ts b/examples/evm-to-btc-fungible-transfer/src/transfer.ts index 7a0661c3e..10aaad96c 100644 --- a/examples/evm-to-btc-fungible-transfer/src/transfer.ts +++ b/examples/evm-to-btc-fungible-transfer/src/transfer.ts @@ -31,7 +31,7 @@ export async function erc20Transfer(): Promise { destination: BITCOIN_DOMAIN_ID, sourceNetworkProvider: web3Provider, resource: RESOURCE_ID, - amount: BigInt(1) * BigInt(1e8), + amount: BigInt(1) * BigInt(1e8), // or any amount to send destinationAddress: BTC_DESTINATION_ADDRESS, environment: Environment.DEVNET, sourceAddress: await wallet.getAddress(), From 9691dad6afe350203dd00967a776568478c2100d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 2 Jul 2024 00:59:06 -0400 Subject: [PATCH 15/87] chore: small adjustment to calculate fee --- examples/btc-to-evm-fungible-transfer/src/transfer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index 5423cdbb5..39412b34f 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -90,7 +90,7 @@ async function btcToEvmTransfer(): Promise { const embed = payments.embed({ data: [data] }); - const amount = transferRequestData.amount - (16183 + 1000) + const amount = transferRequestData.amount - 1e4; const amountMinusBridgeFee = amount - FEE_AMOUNT; const outputEncodedData = { From 1585793f1e6d2d54868a95a984d4adbb8ae42ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 2 Jul 2024 09:48:38 -0400 Subject: [PATCH 16/87] chore: update transfer script to use caipId and also update to the sample env file --- examples/btc-to-evm-fungible-transfer/.env.sample | 2 +- examples/btc-to-evm-fungible-transfer/src/transfer.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index 7959f5115..e97a5c1de 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -4,5 +4,5 @@ DESTINATION_ADDRESS= PRIVATE_KEY= DESTINATION_DOMAIN_ID= RESOURCE_ID= -SOURCE_DOMAIN_ID= +SOURCE_DOMAIN_CAIPID= EXPLORER_URL=https://blockstream.info/testnet/tx \ No newline at end of file diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index 39412b34f..c106c7b77 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -17,12 +17,12 @@ const DESTINATION_DOMAIN_ID = Number(process.env.DESTINATION_DOMAIN_ID); const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; const RESOURCE_ID = process.env.RESOURCE_ID; -const SOURCE_DOMAIN_ID = Number(process.env.SOURCE_DOMAIN_ID); +const SOURCE_DOMAIN_CAIPID = process.env.SOURCE_DOMAIN_CAIPID const FEE_ADDRESS = 'tb1p0r2w3ugreaggd7nakw2wd04up6rl8k0cce8eetxwmhnrelgqx87s4zdkd7' const FEE_AMOUNT = 1000000; const EXPLORER_URL = process.env.EXPLORER_URL; -if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DESTINATION_DOMAIN_ID || !BLOCKSTREAM_URL || !RESOURCE_ID || !SOURCE_DOMAIN_ID) { +if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DESTINATION_DOMAIN_ID || !BLOCKSTREAM_URL || !RESOURCE_ID || !SOURCE_DOMAIN_CAIPID) { throw new Error('Please provided needed env variavles in .env file'); } @@ -70,7 +70,7 @@ async function btcToEvmTransfer(): Promise { }; const params = { - sourceDomain: SOURCE_DOMAIN_ID, + sourceDomain: SOURCE_DOMAIN_CAIPID, destinationAddress: DESTINATION_ADDRESS, amount: inputData.witnessUtxo.value, resource: RESOURCE_ID From d21682b58866dabca53ecbd0dbaa5bbd4884b048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 2 Jul 2024 13:15:00 -0400 Subject: [PATCH 17/87] chore: update evm to btc example script --- examples/evm-to-btc-fungible-transfer/src/transfer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/evm-to-btc-fungible-transfer/src/transfer.ts b/examples/evm-to-btc-fungible-transfer/src/transfer.ts index 10aaad96c..67b0fcba1 100644 --- a/examples/evm-to-btc-fungible-transfer/src/transfer.ts +++ b/examples/evm-to-btc-fungible-transfer/src/transfer.ts @@ -13,7 +13,7 @@ if (!privateKey) { } const SEPOLIA_CHAIN_ID = 11155111; -const BITCOIN_DOMAIN_ID = 3; +const BITCOIN_DOMAIN_CAIPID = "bip122:000000000933ea01ad0ee984209779ba"; const RESOURCE_ID = "0x0000000000000000000000000000000000000000000000000000000000000300"; const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "https://eth-sepolia-public.unifra.io" const BTC_DESTINATION_ADDRESS = process.env.BTC_DESTINATION_ADDRESS; @@ -28,7 +28,7 @@ export async function erc20Transfer(): Promise { const params = { source: SEPOLIA_CHAIN_ID, - destination: BITCOIN_DOMAIN_ID, + destination: BITCOIN_DOMAIN_CAIPID, sourceNetworkProvider: web3Provider, resource: RESOURCE_ID, amount: BigInt(1) * BigInt(1e8), // or any amount to send @@ -53,4 +53,4 @@ export async function erc20Transfer(): Promise { console.log(`Depositted, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`); } -erc20Transfer().finally(() => {}); \ No newline at end of file +erc20Transfer().finally(() => { }); \ No newline at end of file From 2de20e6a43085e59bae4382bfdd893a30a02c3fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 2 Jul 2024 16:29:12 -0400 Subject: [PATCH 18/87] chore: readme for evm to btc example --- .../evm-to-btc-fungible-transfer/README.md | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/examples/evm-to-btc-fungible-transfer/README.md b/examples/evm-to-btc-fungible-transfer/README.md index 838ebc898..c8f7a621c 100644 --- a/examples/evm-to-btc-fungible-transfer/README.md +++ b/examples/evm-to-btc-fungible-transfer/README.md @@ -1 +1,81 @@ -# evm-to-btc-fungible-transfer +# Sygma SDK EVM to BTC example + +This is an example script that demonstrates the functionality of the SDK using the Sygma ecosystem. The script showcases a token transfer between the provided account and a BTC testnet account. + +## 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. From aefdbf20ffdc819deb3d64917703ec32daf220ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 3 Jul 2024 09:44:00 -0400 Subject: [PATCH 19/87] chore: pr comments --- examples/btc-to-evm-fungible-transfer/.env.sample | 3 ++- examples/btc-to-evm-fungible-transfer/src/transfer.ts | 5 +++-- examples/btc-to-evm-fungible-transfer/src/utils.ts | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index e97a5c1de..91435f6fc 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -5,4 +5,5 @@ PRIVATE_KEY= DESTINATION_DOMAIN_ID= RESOURCE_ID= SOURCE_DOMAIN_CAIPID= -EXPLORER_URL=https://blockstream.info/testnet/tx \ No newline at end of file +EXPLORER_URL=https://blockstream.info/testnet/tx +FIXED_TRANSACTION_FEE=1e4 \ No newline at end of file diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index c106c7b77..d058051f8 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -21,6 +21,7 @@ const SOURCE_DOMAIN_CAIPID = process.env.SOURCE_DOMAIN_CAIPID const FEE_ADDRESS = 'tb1p0r2w3ugreaggd7nakw2wd04up6rl8k0cce8eetxwmhnrelgqx87s4zdkd7' const FEE_AMOUNT = 1000000; const EXPLORER_URL = process.env.EXPLORER_URL; +const FIXED_TRANSACTION_FEE = Number(process.env.FIXED_TRANSACTION_FEE); if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DESTINATION_DOMAIN_ID || !BLOCKSTREAM_URL || !RESOURCE_ID || !SOURCE_DOMAIN_CAIPID) { throw new Error('Please provided needed env variavles in .env file'); @@ -36,7 +37,7 @@ type InputData = { async function btcToEvmTransfer(): Promise { // pre setup const testnet = networks.testnet; - initEccLib(tinysecp as any); + initEccLib(tinysecp); const ECPair: ECPairAPI = ECPairFactory(tinysecp); console.log('Transfer BTC to EVM'); @@ -90,7 +91,7 @@ async function btcToEvmTransfer(): Promise { const embed = payments.embed({ data: [data] }); - const amount = transferRequestData.amount - 1e4; + const amount = transferRequestData.amount - FIXED_TRANSACTION_FEE; const amountMinusBridgeFee = amount - FEE_AMOUNT; const outputEncodedData = { diff --git a/examples/btc-to-evm-fungible-transfer/src/utils.ts b/examples/btc-to-evm-fungible-transfer/src/utils.ts index d498c4f34..4c1ccd538 100644 --- a/examples/btc-to-evm-fungible-transfer/src/utils.ts +++ b/examples/btc-to-evm-fungible-transfer/src/utils.ts @@ -46,6 +46,7 @@ export function getTweakedSigner(ECPair: ECPairAPI, tinysecp: TinySecp256k1Inter } export async function getFeeEstimates(blockstreamUrl: string): Promise { + if(!blockstreamUrl) throw new Error('Blockstream url is required'); try { const response = await fetch(`${blockstreamUrl}/fee-estimates`); From 02496137e96b76580096174423c830bb8df089a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 3 Jul 2024 23:07:37 -0400 Subject: [PATCH 20/87] chore: update lock file --- yarn.lock | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index fde3a9742..a1298dbf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -421,6 +421,27 @@ __metadata: languageName: node linkType: hard +"@buildwithsygma/btc@workspace:^, @buildwithsygma/btc@workspace:packages/btc": + version: 0.0.0-use.local + resolution: "@buildwithsygma/btc@workspace:packages/btc" + dependencies: + "@buildwithsygma/core": "workspace:^" + "@types/jest": "npm:^29.4.0" + 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" + 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" @@ -555,6 +576,37 @@ __metadata: languageName: node linkType: hard +"@buildwithsygma/sygma-sdk-btc-to-evm-fungible-transfer-example@workspace:examples/btc-to-evm-fungible-transfer": + version: 0.0.0-use.local + resolution: "@buildwithsygma/sygma-sdk-btc-to-evm-fungible-transfer-example@workspace:examples/btc-to-evm-fungible-transfer" + dependencies: + "@buildwithsygma/btc": "workspace:^" + "@buildwithsygma/core": "workspace:^" + 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-btc-fungible-transfer@workspace:examples/evm-to-btc-fungible-transfer": + version: 0.0.0-use.local + resolution: "@buildwithsygma/sygma-sdk-evm-to-btc-fungible-transfer@workspace:examples/evm-to-btc-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:." @@ -2078,7 +2130,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 @@ -3866,6 +3918,13 @@ __metadata: languageName: node linkType: hard +"bech32@npm:^2.0.0": + version: 2.0.0 + resolution: "bech32@npm:2.0.0" + checksum: 45e7cc62758c9b26c05161b4483f40ea534437cf68ef785abadc5b62a2611319b878fef4f86ddc14854f183b645917a19addebc9573ab890e19194bc8f521942 + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.3.0 resolution: "binary-extensions@npm:2.3.0" @@ -4810,6 +4869,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 + "electron-to-chromium@npm:^1.4.796": version: 1.4.803 resolution: "electron-to-chromium@npm:1.4.803" @@ -10427,6 +10497,15 @@ __metadata: languageName: node linkType: hard +"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" @@ -10772,6 +10851,13 @@ __metadata: languageName: node linkType: hard +"typeforce@npm:^1.11.3, 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" @@ -10960,6 +11046,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" From 7cdbf7a6f4b4e8c762ba436cac38e1d7e6f6f4de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 3 Jul 2024 23:19:21 -0400 Subject: [PATCH 21/87] chore: fixing lint issues --- packages/evm/src/utils/helpers.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/evm/src/utils/helpers.ts b/packages/evm/src/utils/helpers.ts index 3b6bc98b8..d48c1a63c 100644 --- a/packages/evm/src/utils/helpers.ts +++ b/packages/evm/src/utils/helpers.ts @@ -142,5 +142,8 @@ export const toHex = (covertThis: string | number | BigNumber, padding: number): }; export const addressToHex = (address: string): string => { - return address.split('').map((_, idx) => address.charCodeAt(idx).toString(16)).join(''); -} \ No newline at end of file + return address + .split('') + .map((_, idx) => address.charCodeAt(idx).toString(16)) + .join(''); +}; From f1533effbbd6568eb48266c402eaf1045fa4a4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Sun, 14 Jul 2024 23:43:05 -0400 Subject: [PATCH 22/87] feat: returning psbt to sign outside, changes on example and env vars needed to be provided. Helpers still on wip to use the example on testnet --- .../btc-to-evm-fungible-transfer/.env.sample | 17 +- .../src/transfer.ts | 206 ++++++++---------- .../btc-to-evm-fungible-transfer/src/utils.ts | 84 +++---- packages/btc/package.json | 3 +- packages/btc/src/base-transfer.ts | 13 +- packages/btc/src/fungible.ts | 57 +++-- packages/btc/src/index.ts | 1 + packages/btc/src/types.ts | 30 ++- packages/btc/src/utils/helpers.ts | 113 ++++++++++ packages/btc/src/utils/index.ts | 1 + packages/core/src/config/config.ts | 12 +- packages/core/src/types.ts | 8 +- 12 files changed, 341 insertions(+), 204 deletions(-) create mode 100644 packages/btc/src/utils/helpers.ts create mode 100644 packages/btc/src/utils/index.ts diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index 91435f6fc..2044309cd 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -1,9 +1,14 @@ -SYGMA_ENV=devnet -BLOCKSTREAM_URL=https://blockstream.info/testnet/api +SYGMA_ENV=testnet +BLOCKSTREAM_URL= DESTINATION_ADDRESS= -PRIVATE_KEY= -DESTINATION_DOMAIN_ID= +DESTINATION_DOMAIN_CHAIN_ID= RESOURCE_ID= SOURCE_DOMAIN_CAIPID= -EXPLORER_URL=https://blockstream.info/testnet/tx -FIXED_TRANSACTION_FEE=1e4 \ No newline at end of file +EXPLORER_URL= +MINER_FEE= +MNEMONIC= +DERIVATION_PATH= +UTXO_TX_ID= +UTXO_AMOUNT= +UTXO_OUTPUT_INDEX= +CHANGE_ADDRESS= \ No newline at end of file diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.ts index d058051f8..2b0e53f2d 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.ts @@ -1,140 +1,118 @@ -import dotenv from 'dotenv'; -import { createBitcoinFungibleTransfer } from '@buildwithsygma/btc'; -import { ECPairFactory, ECPairAPI } from 'ecpair'; +import type { BaseTransferParams } from "@buildwithsygma/btc"; import { - initEccLib, - networks, - payments, - Psbt -} from "bitcoinjs-lib"; -import * as tinysecp from 'tiny-secp256k1'; -import { broadcastTransaction, calculateFee, getFeeEstimates, getTweakedSigner, toXOnly } from './utils'; + createBitcoinFungibleTransfer, + TypeOfAddress, +} from "@buildwithsygma/btc"; +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 } from "./utils"; dotenv.config(); +const SOURCE_DOMAIN_CAIPID = process.env.SOURCE_DOMAIN_CAIPID; const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; -const DESTINATION_DOMAIN_ID = Number(process.env.DESTINATION_DOMAIN_ID); -const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; -const PRIVATE_KEY = process.env.PRIVATE_KEY; +const DESTINATION_DOMAIN_CHAIN_ID = Number( + process.env.DESTINATION_DOMAIN_CHAIN_ID, +); const RESOURCE_ID = process.env.RESOURCE_ID; -const SOURCE_DOMAIN_CAIPID = process.env.SOURCE_DOMAIN_CAIPID -const FEE_ADDRESS = 'tb1p0r2w3ugreaggd7nakw2wd04up6rl8k0cce8eetxwmhnrelgqx87s4zdkd7' -const FEE_AMOUNT = 1000000; +const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const EXPLORER_URL = process.env.EXPLORER_URL; -const FIXED_TRANSACTION_FEE = Number(process.env.FIXED_TRANSACTION_FEE); - -if (!DESTINATION_ADDRESS || !PRIVATE_KEY || !DESTINATION_DOMAIN_ID || !BLOCKSTREAM_URL || !RESOURCE_ID || !SOURCE_DOMAIN_CAIPID) { - throw new Error('Please provided needed env variavles in .env file'); +const MNEMONIC = process.env.MNEMONIC; +const MINER_FEE = Number(process.env.MINER_FEE); +const UTXO_TX_ID = process.env.UTXO_TX_ID; +const UTXO_AMOUNT = Number(process.env.UTXO_AMOUNT); +const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); +const DERIVATION_PATH = process.env.DERIVATION_PATH; +const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; + +// TODO: remove this log +console.table({ + SOURCE_DOMAIN_CAIPID, + DESTINATION_ADDRESS, + DESTINATION_DOMAIN_CHAIN_ID, + RESOURCE_ID, + MNEMONIC, + MINER_FEE, + UTXO_TX_ID, + UTXO_AMOUNT, + UTXO_OUTPUT_INDEX, + DERIVATION_PATH, + CHANGE_ADDRESS, + BLOCKSTREAM_URL, +}); + +if ( + !SOURCE_DOMAIN_CAIPID || + !DESTINATION_ADDRESS || + !DESTINATION_DOMAIN_CHAIN_ID || + !RESOURCE_ID || + !MNEMONIC || + !MINER_FEE || + !UTXO_TX_ID || + !UTXO_AMOUNT || + !UTXO_OUTPUT_INDEX || + !DERIVATION_PATH || + !CHANGE_ADDRESS || + !BLOCKSTREAM_URL +) { + throw new Error( + "Please provided needed env variables needed into the .env file", + ); } -type InputData = { - hash: string; - index: number; - witnessUtxo: { value: number; script: Buffer }; - tapInternalKey: Buffer; -}; +initEccLib(tinysecp); +const bip32 = BIP32Factory(tinysecp); async function btcToEvmTransfer(): Promise { // pre setup const testnet = networks.testnet; - initEccLib(tinysecp); - const ECPair: ECPairAPI = ECPairFactory(tinysecp); - - console.log('Transfer BTC to EVM'); - - // tweaking signer - const { tweakedSigner, publicKey } = getTweakedSigner(ECPair, tinysecp, testnet, PRIVATE_KEY!); - - // Generate an address from the tweaked public key - const p2pktr = payments.p2tr({ - pubkey: toXOnly(tweakedSigner.publicKey), - network: testnet, - }); - - const p2pktrAdddress = p2pktr.address as string; - // address here should match the one that you generated for your private key - console.log('Taproot address to use', p2pktrAdddress) - - const feeEstimatesPerBlockConfirmation = await getFeeEstimates(BLOCKSTREAM_URL!); - - /** - * Get UTXO to use for the transfer - * You can get UTXO from any source, for this example we are using Blockstream API - * Add the txid and vout of the UTXO to the inputData. Also add the value of the UTXO to the witnessUtxo value field - */ - - const inputData: InputData = { - hash: '', // utxo tx id - index: 0, // utxo index - witnessUtxo: { value: 0, script: p2pktr.output! }, // utxo value - tapInternalKey: toXOnly(publicKey) - }; + console.log("Transfer BTC to EVM"); + const seed = await mnemonicToSeed(MNEMONIC); + const rootKey = bip32.fromSeed(seed, testnet); + const derivedNode = rootKey.derivePath(DERIVATION_PATH); - const params = { - sourceDomain: SOURCE_DOMAIN_CAIPID, - destinationAddress: DESTINATION_ADDRESS, - amount: inputData.witnessUtxo.value, - resource: RESOURCE_ID - } - - const transfer = await createBitcoinFungibleTransfer(params); - - const transferRequestData = transfer.getBTCTransferRequest(); - - const psbt = new Psbt({ network: testnet }); + // Note: default example is going to run P2TR transfer + const publicKeyDropedDERHeader = toXOnly(derivedNode.publicKey); - // encoded data - const data = Buffer.from( - `${DESTINATION_ADDRESS}_${DESTINATION_DOMAIN_ID}`, // EMV ADDRESS + DESTINATION DOMAIN ID HERE - "utf8", + const tweakedSigner = derivedNode.tweak( + crypto.taggedHash("TapTweak", publicKeyDropedDERHeader), ); - const embed = payments.embed({ data: [data] }); - - const amount = transferRequestData.amount - FIXED_TRANSACTION_FEE; - const amountMinusBridgeFee = amount - FEE_AMOUNT; - - const outputEncodedData = { - script: embed.output!, - value: 0, - }; - - const outputData = { - address: transferRequestData.depositAddress, - value: amountMinusBridgeFee - }; - - const outputDataFee = { - address: FEE_ADDRESS, - value: FEE_AMOUNT + const transferParams: BaseTransferParams = { + source: SOURCE_DOMAIN_CAIPID, + destination: DESTINATION_DOMAIN_CHAIN_ID, + destinationAddress: DESTINATION_ADDRESS, + amount: 0, + resource: RESOURCE_ID, + utxoTxId: UTXO_TX_ID, + utxoOutputIndex: UTXO_OUTPUT_INDEX, + utxoAmount: UTXO_AMOUNT, + publicKey: tweakedSigner.publicKey, + typeOfAddress: TypeOfAddress.P2TR, + minerFee: MINER_FEE, + network: testnet, + changeAddress: CHANGE_ADDRESS, }; - - const feeValue = calculateFee(psbt, feeEstimatesPerBlockConfirmation, inputData, outputEncodedData, outputData, outputDataFee, tweakedSigner); - - const psbtWithFee = new Psbt({ network: testnet }); - - const amountWitFeeApplied = amount - feeValue - FEE_AMOUNT; - - psbtWithFee.addInput(inputData); - - psbtWithFee.addOutput(outputEncodedData); - psbtWithFee.addOutput(outputDataFee); + const transfer = await createBitcoinFungibleTransfer(transferParams); - psbtWithFee.addOutput({ - address: transferRequestData.depositAddress, - value: amountWitFeeApplied - }); + const psbt = transfer.getTransferTransaction(); - psbtWithFee.signInput(0, tweakedSigner); - psbtWithFee.finalizeAllInputs(); + console.log("Signing the transaction"); - const tx = psbtWithFee.extractTransaction(true); + psbt.signInput(0, tweakedSigner); + psbt.finalizeAllInputs(); - console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); - const txid = await broadcastTransaction(BLOCKSTREAM_URL!, tx.toHex()); + console.log("Extracting the transaction"); + const tx = psbt.extractTransaction(true); + console.log("Transaction hex", tx.toHex()); - console.log(`Transaction ID: ${EXPLORER_URL}/${txid}`); + const txId = await broadcastTransaction(BLOCKSTREAM_URL, tx.toHex()); + console.log("Transaction broadcasted", `${EXPLORER_URL}/tx/${txId}`); } -btcToEvmTransfer().finally(() => { }) \ No newline at end of file +btcToEvmTransfer().finally(() => {}); diff --git a/examples/btc-to-evm-fungible-transfer/src/utils.ts b/examples/btc-to-evm-fungible-transfer/src/utils.ts index 4c1ccd538..2d00583fe 100644 --- a/examples/btc-to-evm-fungible-transfer/src/utils.ts +++ b/examples/btc-to-evm-fungible-transfer/src/utils.ts @@ -1,81 +1,57 @@ -import { Signer, networks, crypto, Psbt } from "bitcoinjs-lib"; -import { ECPairAPI, TinySecp256k1Interface } from "ecpair"; +import type { Signer, Psbt } from "bitcoinjs-lib"; +type InputData = { + hash: string; + index: number; + witnessUtxo: { value: number; script: Buffer }; + tapInternalKey: Buffer; +}; -type InputData = { hash: string, index: number, witnessUtxo: { value: number, script: Buffer }, tapInternalKey: Buffer } +type OutputData = { value: number; script: Buffer; address: string }; -type OutputData = { value: number, script: Buffer, address: string } - - -// shortens the public key to 32 bytes -export function toXOnly(pubkey: Buffer): Buffer { - return pubkey.subarray(1, 33) -} - -function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { - return crypto.taggedHash( - 'TapTweak', - Buffer.concat(h ? [pubKey, h] : [pubKey]), - ); -} - -// Get the tweaked signer and returns also public key -export function getTweakedSigner(ECPair: ECPairAPI, tinysecp: TinySecp256k1Interface, network: networks.Network, privateKey: string): { tweakedSigner: Signer, publicKey: Buffer } { - const keypair = ECPair.fromWIF(privateKey!, networks.testnet) as Signer; - // @ts-ignore - let privKey: Uint8Array = keypair.privateKey!; - - if (keypair.publicKey[0] === 3) { - privKey = tinysecp.privateNegate(privKey); - } - - const tweakedPrivateKey = tinysecp.privateAdd( - privKey, - tapTweakHash(toXOnly(keypair.publicKey), undefined), - ); - - if (!tweakedPrivateKey) { - throw new Error('Invalid tweaked private key!'); - } - - return { - tweakedSigner: ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { - network: networks.testnet, - }), publicKey: keypair.publicKey - }; -} export async function getFeeEstimates(blockstreamUrl: string): Promise { - if(!blockstreamUrl) throw new Error('Blockstream url is required'); + if (!blockstreamUrl) throw new Error("Blockstream url is required"); try { const response = await fetch(`${blockstreamUrl}/fee-estimates`); const data = await response.json(); - return data['5']; // fee for 5 blocks confirmation + return data["5"]; // fee for 5 blocks confirmation } catch (error) { - throw new Error('Failed to get fee estimates'); + throw new Error("Failed to get fee estimates"); } } -export async function broadcastTransaction(blockstreamUrl: string, txHex: string): Promise { +export async function broadcastTransaction( + blockstreamUrl: string, + txHex: string, +): Promise { try { const response = await fetch(`${blockstreamUrl}/tx`, { - method: 'POST', + method: "POST", body: txHex, headers: { - 'Content-Type': 'text/plain' - } + "Content-Type": "text/plain", + }, }); return await response.text(); } catch (error) { - console.log('error', error) - throw new Error('Failed to broadcast transaction'); + console.log("error", error); + throw new Error("Failed to broadcast transaction"); } } -export function calculateFee(psbt: Psbt, feeRate: number, inputData: InputData, bridgeOutputData: Pick, valueOutputData: Pick, outputFeeData: Pick, tweakedSigner: Signer): number { +export function calculateFee( + psbt: Psbt, + feeRate: number, + inputData: InputData, + bridgeOutputData: Pick, + valueOutputData: Pick, + outputFeeData: Pick, + tweakedSigner: Signer, +): number { psbt.addInput(inputData); psbt.addOutput(bridgeOutputData); psbt.addOutput(valueOutputData); @@ -89,4 +65,4 @@ export function calculateFee(psbt: Psbt, feeRate: number, inputData: InputData, const virtualSize = tx.virtualSize(); return Math.round(virtualSize * feeRate); -} \ No newline at end of file +} diff --git a/packages/btc/package.json b/packages/btc/package.json index da958704f..57517b28e 100644 --- a/packages/btc/package.json +++ b/packages/btc/package.json @@ -58,6 +58,7 @@ "typescript": "5.0.4" }, "dependencies": { - "@buildwithsygma/core": "workspace:^" + "@buildwithsygma/core": "workspace:^", + "bitcoinjs-lib": "^6.1.6" } } diff --git a/packages/btc/src/base-transfer.ts b/packages/btc/src/base-transfer.ts index 479267561..1491eb00c 100644 --- a/packages/btc/src/base-transfer.ts +++ b/packages/btc/src/base-transfer.ts @@ -1,24 +1,31 @@ import type { BitcoinResource } from '@buildwithsygma/core/src'; -import type { Config, Domain } from '@buildwithsygma/core/types'; +import type { BitcoinConfig, Config, Domain } from '@buildwithsygma/core/types'; import type { BaseTransferParams } from 'types'; export abstract class BaseTransfer { protected destinationAddress: string; - protected amount: bigint; + protected amount: number; protected config: Config; protected resource: BitcoinResource; protected sourceDomain: Domain; + protected destinationDomain: Domain; + protected feeAmount: number; + protected feeAddress: string; constructor(transfer: BaseTransferParams, config: Config) { this.destinationAddress = transfer.destinationAddress; this.amount = transfer.amount; - this.sourceDomain = config.getDomain(transfer.sourceDomain); + this.sourceDomain = config.getDomain(transfer.source); + this.destinationDomain = config.getDomain(transfer.destination); + + this.feeAddress = (this.sourceDomain as BitcoinConfig).feeAddress; const resources = config.getResources(this.sourceDomain) as BitcoinResource[]; const resource = this.findResource(resources, transfer.resource); if (resource) { this.resource = resource; + this.feeAmount = resource.feeAmount as number; } else { throw new Error('Resource not found.'); } diff --git a/packages/btc/src/fungible.ts b/packages/btc/src/fungible.ts index 39fd1d6cd..fc518e6de 100644 --- a/packages/btc/src/fungible.ts +++ b/packages/btc/src/fungible.ts @@ -1,38 +1,69 @@ import { Config } from '@buildwithsygma/core'; -import type { BaseTransferParams, BitcoinTransferRequest } from 'types'; +import type { networks } from 'bitcoinjs-lib'; +import type { BaseTransferParams, BitcoinTransferRequest, TypeOfAddress } from 'types'; import { BaseTransfer } from './base-transfer.js'; +import { getPsbt } from './utils/index.js'; export async function createBitcoinFungibleTransfer( params: BaseTransferParams, ): Promise { const config = new Config(); - await config.init(process.env.SYGMA_ENV); - const transfer = new BitcoinTransfer(params, config); - - return transfer; + await config.init(process.env.SYGMA_ENV || params.environment); + return new BitcoinTransfer(params, config); } class BitcoinTransfer extends BaseTransfer { protected destinationAddress: string; - protected amount: bigint; + protected amount: number; + protected utxoTxId: string; + protected utxoOutputIndex: number; + protected utxoAmount: number; + protected publicKey: Buffer; + protected typeOfAddress: TypeOfAddress; + protected minerFee: number; + protected network: networks.Network; + protected changeAddress?: string; constructor(transfer: BaseTransferParams, config: Config) { super(transfer, config); this.destinationAddress = transfer.destinationAddress; this.amount = transfer.amount; + this.utxoTxId = transfer.utxoTxId; + this.utxoOutputIndex = transfer.utxoOutputIndex; + this.utxoAmount = transfer.utxoAmount; + this.publicKey = transfer.publicKey; + this.typeOfAddress = transfer.typeOfAddress; + this.minerFee = transfer.minerFee; + this.network = transfer.network; + this.changeAddress = transfer.changeAddress; } getUriEncodedUtxoRequest(): string { const { address } = this.resource; - return `bitcoin:${address}?amount=${this.amount}&destinationAddress=${this.destinationAddress}`; + return `bitcoin:${address}?amount=${this.amount}&message=${this.destinationAddress}`; } - getBTCTransferRequest(): BitcoinTransferRequest { - return { - destinationAddress: this.destinationAddress, - amount: this.amount, - depositAddress: this.resource.address, - }; + getTransferTransaction(): BitcoinTransferRequest { + return getPsbt( + { + source: this.sourceDomain.caipId, + destination: this.destinationDomain.id, + destinationAddress: this.destinationAddress, + amount: this.amount, + resource: this.resource.resourceId, + utxoTxId: this.utxoTxId, + utxoAmount: this.utxoAmount, + utxoOutputIndex: this.utxoOutputIndex, + publicKey: this.publicKey, + typeOfAddress: this.typeOfAddress, + minerFee: this.minerFee, + network: this.network, + changeAddress: this.changeAddress, + }, + this.feeAddress, + this.resource.address, + this.feeAmount, + ); } } diff --git a/packages/btc/src/index.ts b/packages/btc/src/index.ts index c5aa1fb21..ad5e3fe7c 100644 --- a/packages/btc/src/index.ts +++ b/packages/btc/src/index.ts @@ -1,2 +1,3 @@ export * from './base-transfer.js'; export * from './fungible.js'; +export * from './types.js'; diff --git a/packages/btc/src/types.ts b/packages/btc/src/types.ts index 5e71a8be0..80fa1d682 100644 --- a/packages/btc/src/types.ts +++ b/packages/btc/src/types.ts @@ -1,14 +1,30 @@ -import type { BitcoinResource, Domainlike } from '@buildwithsygma/core'; +import type { BitcoinResource, Domainlike, Environment } from '@buildwithsygma/core'; +import type { networks, Psbt } from 'bitcoinjs-lib'; + +export enum TypeOfAddress { + P2WPKH = 'P2WPKH', + P2TR = 'P2TR', +} export type BaseTransferParams = { - sourceDomain: Domainlike; + environment?: Environment; + source: Domainlike; + destination: Domainlike; destinationAddress: string; - amount: bigint; + amount: number; resource: BitcoinResource | string; + utxoTxId: string; + utxoAmount: number; + utxoOutputIndex: number; + publicKey: Buffer; + typeOfAddress: TypeOfAddress; + minerFee: number; + network: networks.Network; + changeAddress?: string; }; -export type BitcoinTransferRequest = { - destinationAddress: string; - amount: bigint; - depositAddress: string; +export type BitcoinTransferRequest = Psbt; + +export type PaymentReturnData = { + output: Buffer; }; diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts new file mode 100644 index 000000000..dc30285da --- /dev/null +++ b/packages/btc/src/utils/helpers.ts @@ -0,0 +1,113 @@ +import type { networks, Payment } from 'bitcoinjs-lib'; +import { payments, Psbt } from 'bitcoinjs-lib'; + +import { TypeOfAddress } from '../types.js'; +import type { BaseTransferParams, BitcoinTransferRequest, PaymentReturnData } from '../types.js'; + +export function getScriptPubkey( + typeOfAddress: TypeOfAddress, + publicKey: Buffer, + network: networks.Network, +): { + scriptPubKey: Buffer; +} { + if (typeOfAddress === TypeOfAddress.P2WPKH) { + const { output } = payments.p2wpkh({ + pubkey: publicKey, + network, + }) as PaymentReturnData; + + return { scriptPubKey: output }; + } else { + const { output } = payments.p2tr({ + internalPubkey: publicKey, + network, + }) as PaymentReturnData; + + return { scriptPubKey: output }; + } +} + +function encodeDepositAddress(depositAddress: string, destinationDomainId: number): Payment { + return payments.embed({ + data: [Buffer.from(`${depositAddress}_${destinationDomainId}`)], + }); +} + +export function getPsbt( + params: BaseTransferParams, + feeAddress: string, + depositAddress: string, + feeAmount: number, +): BitcoinTransferRequest { + console.log("params", params); + if (!['P2WPKH', 'P2TR'].includes(params.typeOfAddress)) { + throw new Error('Unsuported address type'); + } + + // TODO: to remove the address parameter being returned + const { scriptPubKey } = getScriptPubkey(params.typeOfAddress, params.publicKey, params.network); + + const psbt = new Psbt({ network: params.network }); + + let amountToSpent; // TODO: this condition is temporary since there is no fee on testnet + if (feeAmount) { + amountToSpent = params.utxoAmount - feeAmount - params.minerFee; + } else { + amountToSpent = params.utxoAmount - params.minerFee; + } + + if (params.typeOfAddress !== TypeOfAddress.P2TR) { + psbt.addInput({ + hash: params.utxoTxId, + index: params.utxoOutputIndex, + witnessUtxo: { + script: scriptPubKey, + value: params.utxoAmount, + }, + }); + } else { + psbt.addInput({ + hash: params.utxoTxId, + index: params.utxoOutputIndex, + witnessUtxo: { + script: scriptPubKey as unknown as Buffer, + value: params.utxoAmount, + }, + tapInternalKey: params.publicKey, + }); + } + + // OP_RETURN output + psbt.addOutput({ + script: encodeDepositAddress(params.destinationAddress, Number(params.destination)) + .output as unknown as Buffer, + value: 0, + }); + + + // just because there is no fee on testnet + if (feeAmount) { + psbt.addOutput({ + address: feeAddress, + value: Number(feeAmount), + }); + } + + // Amount to bridge + psbt.addOutput({ + address: depositAddress, + value: params.amount, + }); + + if (params.changeAddress && amountToSpent > params.amount) { + const change = amountToSpent - params.amount; + + psbt.addOutput({ + address: params.changeAddress, + value: change + }); + } + + return psbt; +} diff --git a/packages/btc/src/utils/index.ts b/packages/btc/src/utils/index.ts new file mode 100644 index 000000000..2942c10a7 --- /dev/null +++ b/packages/btc/src/utils/index.ts @@ -0,0 +1 @@ +export * from './helpers.js'; diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 27443c7d6..b3f078582 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, @@ -52,10 +53,10 @@ export class Config { } /** * Creates a domain object from config object - * @param {EthereumConfig | SubstrateConfig} config + * @param {EthereumConfig | SubstrateConfig | BitcoinConfig} config * @returns {Domain} */ - private createDomain(config: EthereumConfig | SubstrateConfig): Domain { + private createDomain(config: EthereumConfig | SubstrateConfig | BitcoinConfig): Domain { return { id: config.id, caipId: config.caipId, @@ -63,6 +64,7 @@ export class Config { name: config.name, type: config.type, parachainId: (config as SubstrateConfig).parachainId, + feeAddress: (config as BitcoinConfig).feeAddress, }; } /** @@ -72,7 +74,7 @@ export class Config { * @param {number} sygmaId * @returns {SubstrateConfig | EthereumConfig} */ - findDomainConfigBySygmaId(sygmaId: number): SubstrateConfig | EthereumConfig { + findDomainConfigBySygmaId(sygmaId: number): SubstrateConfig | EthereumConfig | BitcoinConfig { const domainConfig = this.configuration.domains.find(domain => domain.id === sygmaId); if (!domainConfig) throw new Error(`Domain with sygmaId: ${sygmaId} not found.`); return domainConfig; @@ -83,7 +85,7 @@ export class Config { * @param {Domainlike} domainLike * @returns {{ config: SubstrateConfig | EthereumConfig | undefined; environment: Environment; }} */ - findDomainConfig(domainLike: Domainlike): SubstrateConfig | EthereumConfig { + findDomainConfig(domainLike: Domainlike): SubstrateConfig | EthereumConfig | BitcoinConfig { const config = this.configuration.domains.find(domain => { switch (typeof domainLike) { case 'string': @@ -124,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 e53f84905..419d081ba 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -31,6 +31,7 @@ export type Domain = { iconUrl?: string; type: Network; parachainId?: ParachainId; + feeAddress?: string; }; export type Resource = EvmResource | SubstrateResource | BitcoinResource; @@ -78,6 +79,7 @@ export type BitcoinResource = BaseResource & { address: string; script: string; tweak: string; + feeAmount?: number; }; export type SubstrateResource = BaseResource & { @@ -136,6 +138,10 @@ export interface SubstrateConfig extends BaseConfig { parachainId: ParachainId; } +export interface BitcoinConfig extends BaseConfig { + feeAddress: string; +} + export type IndexerRoutesResponse = { routes: RouteIndexerType[] }; export type Handler = { @@ -144,7 +150,7 @@ export type Handler = { }; export interface SygmaConfig { - domains: Array; + domains: Array; } export type RouteIndexerType = { From c487cab904b5e80358571d3b69b3500ebce5f6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Sun, 14 Jul 2024 23:47:28 -0400 Subject: [PATCH 23/87] chore: remove log --- packages/btc/src/utils/helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index dc30285da..e638d91da 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -40,7 +40,6 @@ export function getPsbt( depositAddress: string, feeAmount: number, ): BitcoinTransferRequest { - console.log("params", params); if (!['P2WPKH', 'P2TR'].includes(params.typeOfAddress)) { throw new Error('Unsuported address type'); } From e229338f30411666a2def96bc7360dcf44a18a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 15 Jul 2024 10:27:44 -0400 Subject: [PATCH 24/87] chore: p2tr and p2wpkh examples separated --- .../btc-to-evm-fungible-transfer/package.json | 5 +- .../src/{transfer.ts => transfer.p2tr.ts} | 0 .../src/transfer.p2wpkh.ts | 110 ++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) rename examples/btc-to-evm-fungible-transfer/src/{transfer.ts => transfer.p2tr.ts} (100%) create mode 100644 examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts diff --git a/examples/btc-to-evm-fungible-transfer/package.json b/examples/btc-to-evm-fungible-transfer/package.json index c5868b3c7..830ccfc5d 100644 --- a/examples/btc-to-evm-fungible-transfer/package.json +++ b/examples/btc-to-evm-fungible-transfer/package.json @@ -17,7 +17,8 @@ "bitcoin" ], "scripts": { - "transfer": "tsx src/transfer.ts" + "transfer:p2tr": "tsx src/transfer.p2tr.ts", + "transfer:p2wpkh": "tsx src/transfer.p2wpkh.ts" }, "author": "Sygmaprotocol Product Team", "license": "LGPL-3.0-or-later", @@ -30,6 +31,8 @@ "dependencies": { "@buildwithsygma/btc": "workspace:^", "@buildwithsygma/core": "workspace:^", + "bip32": "^4.0.0", + "bip39": "^3.1.0", "bitcoinjs-lib": "^6.1.6", "ecpair": "^2.1.0", "tiny-secp256k1": "^2.2.3", diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts similarity index 100% rename from examples/btc-to-evm-fungible-transfer/src/transfer.ts rename to examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts new file mode 100644 index 000000000..676b6283f --- /dev/null +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -0,0 +1,110 @@ +import type { BaseTransferParams } from "@buildwithsygma/btc"; +import { + createBitcoinFungibleTransfer, + TypeOfAddress, +} from "@buildwithsygma/btc"; +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 { broadcastTransaction } from "./utils"; + +dotenv.config(); + +const SOURCE_DOMAIN_CAIPID = process.env.SOURCE_DOMAIN_CAIPID; +const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; +const DESTINATION_DOMAIN_CHAIN_ID = Number( + process.env.DESTINATION_DOMAIN_CHAIN_ID, +); +const RESOURCE_ID = process.env.RESOURCE_ID; +const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; +const EXPLORER_URL = process.env.EXPLORER_URL; +const MNEMONIC = process.env.MNEMONIC; +const MINER_FEE = Number(process.env.MINER_FEE); +const UTXO_TX_ID = process.env.UTXO_TX_ID; +const UTXO_AMOUNT = Number(process.env.UTXO_AMOUNT); +const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); +const DERIVATION_PATH = process.env.DERIVATION_PATH; +const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; + +// TODO: remove this log +console.table({ + SOURCE_DOMAIN_CAIPID, + DESTINATION_ADDRESS, + DESTINATION_DOMAIN_CHAIN_ID, + RESOURCE_ID, + MNEMONIC, + MINER_FEE, + UTXO_TX_ID, + UTXO_AMOUNT, + UTXO_OUTPUT_INDEX, + DERIVATION_PATH, + CHANGE_ADDRESS, + BLOCKSTREAM_URL, +}); + +if ( + !SOURCE_DOMAIN_CAIPID || + !DESTINATION_ADDRESS || + !DESTINATION_DOMAIN_CHAIN_ID || + !RESOURCE_ID || + !MNEMONIC || + !MINER_FEE || + !UTXO_TX_ID || + !UTXO_AMOUNT || + !UTXO_OUTPUT_INDEX || + !DERIVATION_PATH || + !CHANGE_ADDRESS || + !BLOCKSTREAM_URL +) { + throw new Error( + "Please provided needed env variables needed into the .env file", + ); +} + +initEccLib(tinysecp); +const bip32 = BIP32Factory(tinysecp); + +async function btcToEvmTransfer(): Promise { + // pre setup + const testnet = networks.testnet; + console.log("Transfer BTC to EVM"); + const seed = await mnemonicToSeed(MNEMONIC); + const rootKey = bip32.fromSeed(seed, testnet); + const derivedNode = rootKey.derivePath(DERIVATION_PATH); + + const transferParams: BaseTransferParams = { + source: SOURCE_DOMAIN_CAIPID, + destination: DESTINATION_DOMAIN_CHAIN_ID, + destinationAddress: DESTINATION_ADDRESS, + amount: 0, + resource: RESOURCE_ID, + utxoTxId: UTXO_TX_ID, + utxoOutputIndex: UTXO_OUTPUT_INDEX, + utxoAmount: UTXO_AMOUNT, + publicKey: derivedNode.publicKey, + typeOfAddress: TypeOfAddress.P2WPKH, + minerFee: MINER_FEE, + network: testnet, + changeAddress: CHANGE_ADDRESS, + }; + + const transfer = await createBitcoinFungibleTransfer(transferParams); + + const psbt = transfer.getTransferTransaction(); + + console.log("Signing the transaction"); + + psbt.signInput(0, derivedNode); + psbt.finalizeAllInputs(); + + console.log("Extracting the transaction"); + const tx = psbt.extractTransaction(true); + console.log("Transaction hex", tx.toHex()); + + const txId = await broadcastTransaction(BLOCKSTREAM_URL, tx.toHex()); + console.log("Transaction broadcasted", `${EXPLORER_URL}/tx/${txId}`); +} + +btcToEvmTransfer().finally(() => {}); From acba2257d1a04d4688141bfc604323528d8bb803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 17 Jul 2024 10:22:56 -0400 Subject: [PATCH 25/87] chore: remove bip21 method --- packages/btc/src/fungible.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/btc/src/fungible.ts b/packages/btc/src/fungible.ts index fc518e6de..4ff557dec 100644 --- a/packages/btc/src/fungible.ts +++ b/packages/btc/src/fungible.ts @@ -39,11 +39,6 @@ class BitcoinTransfer extends BaseTransfer { this.changeAddress = transfer.changeAddress; } - getUriEncodedUtxoRequest(): string { - const { address } = this.resource; - return `bitcoin:${address}?amount=${this.amount}&message=${this.destinationAddress}`; - } - getTransferTransaction(): BitcoinTransferRequest { return getPsbt( { From 9cb72f814156228f962a951fe342d1071c582fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Fri, 19 Jul 2024 10:18:04 -0400 Subject: [PATCH 26/87] fix: passing correct public key --- examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index 2b0e53f2d..7cc433c37 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -91,7 +91,7 @@ async function btcToEvmTransfer(): Promise { utxoTxId: UTXO_TX_ID, utxoOutputIndex: UTXO_OUTPUT_INDEX, utxoAmount: UTXO_AMOUNT, - publicKey: tweakedSigner.publicKey, + publicKey: publicKeyDropedDERHeader, typeOfAddress: TypeOfAddress.P2TR, minerFee: MINER_FEE, network: testnet, From 213dc3143ed57ed60a8bdc01b10e3b4573b9a3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 24 Jul 2024 10:22:43 -0400 Subject: [PATCH 27/87] chore: test for p2tr transaction, fixing linter issues, adding some checks on data being passed to return the psbt --- packages/btc/jest.config.cjs | 4 +- packages/btc/package.json | 1 + packages/btc/src/__test__/fungible.test.ts | 122 +++++++++++++++ packages/btc/src/fungible.ts | 17 +-- packages/btc/src/types.ts | 19 ++- packages/btc/src/utils/helpers.ts | 141 +++++++++++++----- .../btc/test/{setupTest.js => setupJest.js} | 0 7 files changed, 253 insertions(+), 51 deletions(-) create mode 100644 packages/btc/src/__test__/fungible.test.ts rename packages/btc/test/{setupTest.js => setupJest.js} (100%) diff --git a/packages/btc/jest.config.cjs b/packages/btc/jest.config.cjs index 856e48580..c85c0e8fd 100644 --- a/packages/btc/jest.config.cjs +++ b/packages/btc/jest.config.cjs @@ -6,7 +6,7 @@ module.exports = { moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, - testEnvironment: 'jsdom', + testEnvironment: 'node', testTimeout: 15000, transform: { '^.+\\.(ts|tsx)?$': ['ts-jest', { useESM: true }], @@ -14,6 +14,6 @@ module.exports = { testPathIgnorePatterns: ['./dist'], automock: false, setupFiles: [ - "./test/setupJest.js" + "/test/setupJest.js" ] }; diff --git a/packages/btc/package.json b/packages/btc/package.json index 57517b28e..bfb4ac9ee 100644 --- a/packages/btc/package.json +++ b/packages/btc/package.json @@ -51,6 +51,7 @@ "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", diff --git a/packages/btc/src/__test__/fungible.test.ts b/packages/btc/src/__test__/fungible.test.ts new file mode 100644 index 000000000..d1c8dece2 --- /dev/null +++ b/packages/btc/src/__test__/fungible.test.ts @@ -0,0 +1,122 @@ +import { Config, Environment } 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 { BaseTransferParams } from '../types.js'; +import { TypeOfAddress } from '../types.js'; + +bitcoin.initEccLib(tinysecp); + +const P2TR_TRANSFER_PARAMS: BaseTransferParams = { + source: 'bip122:000000000933ea01ad0ee984209779ba', + destination: 1, + destinationAddress: '0x98729c03c4D5e820F5e8c45558ae07aE63F97461', + amount: 90000000, + resource: '0x0000000000000000000000000000000000000000000000000000000000000300', + utxoData: { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + utxoAmount: 100000000, + utxoOutputIndex: 0, + }, + publicKey: toXOnly( + Buffer.from('03feca449bd5b50085d23864a006f6ea4da80ff63816033f6437193c66bac7488c', 'hex'), + ), + typeOfAddress: TypeOfAddress.P2TR, + minerFee: 1000, + network: bitcoin.networks.testnet, + changeAddress: 'tb1pxmrzd94rs6wtg6ewdjfmuu7s88n2kdqc20vzfmadanfaem3n9sdq0vagu0', + environment: Environment.DEVNET, + feeRate: 103, +}; + +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 - P2TR', () => { + beforeAll(() => { + (Config as jest.Mock).mockReturnValue(MOCKED_CONFIG); + }); + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + 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: 100000000 }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + await expect(() => transfer.getTransferTransaction()).rejects.toThrow('Not enough funds'); + }); + + it('should throw if the utxo amount is less than the amount to transfer', async () => { + const transferParams = { ...P2TR_TRANSFER_PARAMS, amount: 100000001 }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + await expect(() => transfer.getTransferTransaction()).rejects.toThrow( + 'Not enough funds to spend from the UTXO', + ); + }); + + it('should throw if public key is incorrect', async () => { + const transferParams = { ...P2TR_TRANSFER_PARAMS, publicKey: Buffer.from('', 'hex') }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + await expect(() => transfer.getTransferTransaction()).rejects.toThrow(); + }); + + it('should throw if utxoData is partially defined', async () => { + const transferParams: BaseTransferParams = { + ...P2TR_TRANSFER_PARAMS, + utxoData: { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + } as unknown as BaseTransferParams['utxoData'], + }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + + await expect(() => transfer.getTransferTransaction()).rejects.toThrow('UTXO data is required'); + }); +}); diff --git a/packages/btc/src/fungible.ts b/packages/btc/src/fungible.ts index 4ff557dec..d5633a5f6 100644 --- a/packages/btc/src/fungible.ts +++ b/packages/btc/src/fungible.ts @@ -16,27 +16,25 @@ export async function createBitcoinFungibleTransfer( class BitcoinTransfer extends BaseTransfer { protected destinationAddress: string; protected amount: number; - protected utxoTxId: string; - protected utxoOutputIndex: number; - protected utxoAmount: number; protected publicKey: Buffer; protected typeOfAddress: TypeOfAddress; protected minerFee: number; protected network: networks.Network; protected changeAddress?: string; + protected feeRate: number; + protected utxoData: { utxoTxId: string; utxoAmount: number; utxoOutputIndex: number }; constructor(transfer: BaseTransferParams, config: Config) { super(transfer, config); this.destinationAddress = transfer.destinationAddress; this.amount = transfer.amount; - this.utxoTxId = transfer.utxoTxId; - this.utxoOutputIndex = transfer.utxoOutputIndex; - this.utxoAmount = transfer.utxoAmount; this.publicKey = transfer.publicKey; this.typeOfAddress = transfer.typeOfAddress; this.minerFee = transfer.minerFee; this.network = transfer.network; this.changeAddress = transfer.changeAddress; + this.feeRate = transfer.feeRate; + this.utxoData = transfer.utxoData; } getTransferTransaction(): BitcoinTransferRequest { @@ -47,14 +45,13 @@ class BitcoinTransfer extends BaseTransfer { destinationAddress: this.destinationAddress, amount: this.amount, resource: this.resource.resourceId, - utxoTxId: this.utxoTxId, - utxoAmount: this.utxoAmount, - utxoOutputIndex: this.utxoOutputIndex, + utxoData: this.utxoData, publicKey: this.publicKey, typeOfAddress: this.typeOfAddress, - minerFee: this.minerFee, network: this.network, + feeRate: this.feeRate, changeAddress: this.changeAddress, + minerFee: this.minerFee, }, this.feeAddress, this.resource.address, diff --git a/packages/btc/src/types.ts b/packages/btc/src/types.ts index 80fa1d682..aeaf75ebb 100644 --- a/packages/btc/src/types.ts +++ b/packages/btc/src/types.ts @@ -13,13 +13,16 @@ export type BaseTransferParams = { destinationAddress: string; amount: number; resource: BitcoinResource | string; - utxoTxId: string; - utxoAmount: number; - utxoOutputIndex: number; + utxoData: { + utxoTxId: string; + utxoAmount: number; + utxoOutputIndex: number; + }; publicKey: Buffer; typeOfAddress: TypeOfAddress; - minerFee: number; network: networks.Network; + feeRate: number; + minerFee: number; changeAddress?: string; }; @@ -27,4 +30,12 @@ export type BitcoinTransferRequest = 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/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index e638d91da..38e611448 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -1,8 +1,22 @@ -import type { networks, Payment } from 'bitcoinjs-lib'; +import type { networks, Payment, Signer } from 'bitcoinjs-lib'; import { payments, Psbt } from 'bitcoinjs-lib'; import { TypeOfAddress } from '../types.js'; -import type { BaseTransferParams, BitcoinTransferRequest, PaymentReturnData } from '../types.js'; +import type { + BaseTransferParams, + BitcoinTransferInputData, + BitcoinTransferRequest, + PaymentReturnData, +} from '../types.js'; + +type InputData = { + hash: string; + index: number; + witnessUtxo: { value: number; script: Buffer }; + tapInternalKey: Buffer; +}; + +type OutputData = { value: number; script: Buffer; address: string }; export function getScriptPubkey( typeOfAddress: TypeOfAddress, @@ -19,11 +33,13 @@ export function getScriptPubkey( return { scriptPubKey: output }; } else { - const { output } = payments.p2tr({ + const { output, address } = payments.p2tr({ internalPubkey: publicKey, network, }) as PaymentReturnData; + console.log('address', address, output.toString('hex')); + return { scriptPubKey: output }; } } @@ -34,48 +50,89 @@ function encodeDepositAddress(depositAddress: string, destinationDomainId: numbe }); } +// TODO: this needs to be used first +export function calculateFee({ + psbt, + feeRate, + inputData, + bridgeOutputData, + valueOutputData, + outputFeeData, + signer, +}: { + feeRate: number; + psbt: Psbt; + inputData: InputData; + bridgeOutputData: Pick; + valueOutputData: Pick; + outputFeeData: Pick; + signer: Signer; +}): number { + psbt.addInput(inputData); + psbt.addOutput(bridgeOutputData); + psbt.addOutput(valueOutputData); + psbt.addOutput(outputFeeData); + psbt.signInput(0, signer); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(true); + + const virtualSize = tx.virtualSize(); + + return Math.round(virtualSize * feeRate); +} + +export function createInputData({ + utxoData: { utxoTxId, utxoOutputIndex, utxoAmount }, + publicKey, + network, + typeOfAddress, +}: Pick< + BaseTransferParams, + 'utxoData' | 'publicKey' | 'network' | 'typeOfAddress' +>): BitcoinTransferInputData { + if (typeOfAddress !== TypeOfAddress.P2TR) { + return { + hash: utxoTxId as unknown as Buffer, + index: utxoOutputIndex, + witnessUtxo: { + script: getScriptPubkey(typeOfAddress, publicKey, network).scriptPubKey, + value: utxoAmount, + }, + }; + } + return { + hash: utxoTxId, + index: utxoOutputIndex, + witnessUtxo: { + script: getScriptPubkey(typeOfAddress, publicKey, network).scriptPubKey as unknown as Buffer, + value: utxoAmount, + }, + tapInternalKey: publicKey, + }; +} + export function getPsbt( params: BaseTransferParams, feeAddress: string, depositAddress: string, feeAmount: number, ): BitcoinTransferRequest { - if (!['P2WPKH', 'P2TR'].includes(params.typeOfAddress)) { + if (!['P2WPKH', 'P2TR'].includes(params.typeOfAddress.toString())) { throw new Error('Unsuported address type'); } + if (Object.keys(params.utxoData).length !== 3) { + throw new Error('UTXO data is required'); + } - // TODO: to remove the address parameter being returned - const { scriptPubKey } = getScriptPubkey(params.typeOfAddress, params.publicKey, params.network); + if (params.amount > params.utxoData.utxoAmount) { + throw new Error('Not enough funds to spend from the UTXO'); + } + // TODO: to remove the address parameter being returned const psbt = new Psbt({ network: params.network }); - let amountToSpent; // TODO: this condition is temporary since there is no fee on testnet - if (feeAmount) { - amountToSpent = params.utxoAmount - feeAmount - params.minerFee; - } else { - amountToSpent = params.utxoAmount - params.minerFee; - } - - if (params.typeOfAddress !== TypeOfAddress.P2TR) { - psbt.addInput({ - hash: params.utxoTxId, - index: params.utxoOutputIndex, - witnessUtxo: { - script: scriptPubKey, - value: params.utxoAmount, - }, - }); - } else { - psbt.addInput({ - hash: params.utxoTxId, - index: params.utxoOutputIndex, - witnessUtxo: { - script: scriptPubKey as unknown as Buffer, - value: params.utxoAmount, - }, - tapInternalKey: params.publicKey, - }); - } + psbt.addInput(createInputData(params)); // OP_RETURN output psbt.addOutput({ @@ -84,7 +141,6 @@ export function getPsbt( value: 0, }); - // just because there is no fee on testnet if (feeAmount) { psbt.addOutput({ @@ -93,6 +149,21 @@ export function getPsbt( }); } + const size = 303; // this is the size on testnet with fee handler + const minerFee = Math.floor(params.feeRate * size); + console.log('minerFee', minerFee); + + let amountToSpent; // TODO: this condition is temporary since there is no fee on testnet + if (feeAmount) { + amountToSpent = params.utxoData.utxoAmount - Number(feeAmount) - minerFee; + } else { + amountToSpent = params.utxoData.utxoAmount - minerFee; + } + + if (amountToSpent < params.amount) { + throw new Error('Not enough funds'); + } + // Amount to bridge psbt.addOutput({ address: depositAddress, @@ -104,7 +175,7 @@ export function getPsbt( psbt.addOutput({ address: params.changeAddress, - value: change + value: change, }); } diff --git a/packages/btc/test/setupTest.js b/packages/btc/test/setupJest.js similarity index 100% rename from packages/btc/test/setupTest.js rename to packages/btc/test/setupJest.js From 9a5a2845bace5816a6b26f5e09aa83cf193e408b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 24 Jul 2024 10:23:10 -0400 Subject: [PATCH 28/87] chore: update lock file --- yarn.lock | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index a1298dbf9..01fb0b105 100644 --- a/yarn.lock +++ b/yarn.lock @@ -427,6 +427,7 @@ __metadata: 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" @@ -434,6 +435,7 @@ __metadata: 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" @@ -582,6 +584,8 @@ __metadata: dependencies: "@buildwithsygma/btc": "workspace:^" "@buildwithsygma/core": "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" @@ -3939,6 +3943,27 @@ __metadata: 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 + "bitcoinjs-lib@npm:^6.1.6": version: 6.1.6 resolution: "bitcoinjs-lib@npm:6.1.6" @@ -10497,7 +10522,7 @@ __metadata: languageName: node linkType: hard -"tiny-secp256k1@npm:^2.2.3": +"tiny-secp256k1@npm:2.2.3, tiny-secp256k1@npm:^2.2.3": version: 2.2.3 resolution: "tiny-secp256k1@npm:2.2.3" dependencies: @@ -10851,7 +10876,7 @@ __metadata: languageName: node linkType: hard -"typeforce@npm:^1.11.3, typeforce@npm:^1.18.0": +"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 From cc09767876cdd7658e620a46b07c341f0f502e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 24 Jul 2024 17:12:51 -0400 Subject: [PATCH 29/87] chore: p2pwkh test --- packages/btc/src/__test__/fungible.test.ts | 141 +++++++++++++++------ 1 file changed, 100 insertions(+), 41 deletions(-) diff --git a/packages/btc/src/__test__/fungible.test.ts b/packages/btc/src/__test__/fungible.test.ts index d1c8dece2..2bb59b7f9 100644 --- a/packages/btc/src/__test__/fungible.test.ts +++ b/packages/btc/src/__test__/fungible.test.ts @@ -31,6 +31,15 @@ const P2TR_TRANSFER_PARAMS: BaseTransferParams = { feeRate: 103, }; +const P2PWKH_TRANSFER_PARAMS: BaseTransferParams = { + ...P2TR_TRANSFER_PARAMS, + typeOfAddress: TypeOfAddress.P2WPKH, + publicKey: Buffer.from( + '03feca449bd5b50085d23864a006f6ea4da80ff63816033f6437193c66bac7488c', + 'hex', + ), +}; + const MOCKED_CONFIG = { init: jest.fn(), getDomainConfig: jest @@ -61,7 +70,7 @@ jest.mock('@buildwithsygma/core', () => ({ Config: jest.fn(), })); -describe('Fungible - createBitcoinFungibleTransfer - P2TR', () => { +describe('Fungible - createBitcoinFungibleTransfer', () => { beforeAll(() => { (Config as jest.Mock).mockReturnValue(MOCKED_CONFIG); }); @@ -70,53 +79,103 @@ describe('Fungible - createBitcoinFungibleTransfer - P2TR', () => { jest.restoreAllMocks(); }); - it('should create a bitcoin fungible transfer', async () => { - const transfer = await createBitcoinFungibleTransfer(P2TR_TRANSFER_PARAMS); - expect(transfer).toBeTruthy(); - }); + 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 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 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: 100000000 }; - const transfer = await createBitcoinFungibleTransfer(transferParams); - await expect(() => transfer.getTransferTransaction()).rejects.toThrow('Not enough funds'); - }); + it('should throw if the utxo amount is equal to the amount to transfer', async () => { + const transferParams = { ...P2TR_TRANSFER_PARAMS, amount: 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: 100000001 }; - const transfer = await createBitcoinFungibleTransfer(transferParams); - await expect(() => transfer.getTransferTransaction()).rejects.toThrow( - 'Not enough funds to spend from the UTXO', - ); - }); + it('should throw if the utxo amount is less than the amount to transfer', async () => { + const transferParams = { ...P2TR_TRANSFER_PARAMS, amount: 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); - await expect(() => transfer.getTransferTransaction()).rejects.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: BaseTransferParams = { + ...P2TR_TRANSFER_PARAMS, + utxoData: { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + } as unknown as BaseTransferParams['utxoData'], + }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + + expect(() => transfer.getTransferTransaction()).toThrow(); + }); }); - it('should throw if utxoData is partially defined', async () => { - const transferParams: BaseTransferParams = { - ...P2TR_TRANSFER_PARAMS, - utxoData: { - utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', - } as unknown as BaseTransferParams['utxoData'], - }; - const transfer = await createBitcoinFungibleTransfer(transferParams); + 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: 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: 100000001 }; + const transfer = await createBitcoinFungibleTransfer(transferParams); + expect(() => transfer.getTransferTransaction()).toThrow(); + }); + + 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: BaseTransferParams = { + ...P2PWKH_TRANSFER_PARAMS, + utxoData: { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + } as unknown as BaseTransferParams['utxoData'], + }; + const transfer = await createBitcoinFungibleTransfer(transferParams); - await expect(() => transfer.getTransferTransaction()).rejects.toThrow('UTXO data is required'); + expect(() => transfer.getTransferTransaction()).toThrow(); + }); }); }); From 0069fc4d6136db17bce4862c7863af5b68901b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 15:20:24 -0400 Subject: [PATCH 30/87] chore: update address to hext method, adding test cases and test cases for constructing btc deposit data --- .../evm/src/utils/__test__/helpers.test.ts | 31 +++++++++++++++++++ packages/evm/src/utils/helpers.ts | 23 ++++++++++---- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/evm/src/utils/__test__/helpers.test.ts b/packages/evm/src/utils/__test__/helpers.test.ts index da46f8a36..54a5b689d 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, } from '../helpers.js'; describe('createERCDepositData', () => { @@ -30,6 +31,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', () => { @@ -112,3 +123,23 @@ describe('toHex', () => { expect(result).toBe('0x00000000000000000000000000000000000000000000be951906eba2aa800000'); }); }); + +describe('addressToHex', () => { + test('should convert p2tr address to hex', () => { + const address = 'tb1pnyh5nayrmwux72guec3xy7qryjjww6tu9mev3d5347lqcwgus4jsd95d2r'; + const expectedHex = + '746231706e7968356e6179726d777578373267756563337879377172796a6a7777367475396d65763364353334376c716377677573346a73643935643272'; + + const result = addressToHex(address, address.length); + expect(result).toEqual(expectedHex); + }); + + test('should convert p2wpkh address to hex', () => { + const address = 'tb1qsfyzl92pv7wkyaj0tfjdtwvcsj840p004jglvp'; + const expectedHex = + '746231717366797a6c3932707637776b79616a3074666a6474777663736a383430703030346a676c7670'; + + const result = addressToHex(address, address.length); + expect(result).toEqual(expectedHex); + }); +}); diff --git a/packages/evm/src/utils/helpers.ts b/packages/evm/src/utils/helpers.ts index d7bda1108..aa5e84e81 100644 --- a/packages/evm/src/utils/helpers.ts +++ b/packages/evm/src/utils/helpers.ts @@ -28,7 +28,7 @@ export const createERCDepositData = ( } else if (parachainId) { recipientAddressInBytes = getSubstrateRecipientAddressInBytes(recipientAddress, parachainId); } else { - const hexAddress = addressToHex(recipientAddress); + const hexAddress = addressToHex(recipientAddress, recipientAddress.length); recipientAddressInBytes = utils.arrayify(`0x${hexAddress}`); } @@ -141,11 +141,22 @@ export const toHex = (covertThis: string | number | BigNumber, padding: number): return utils.hexZeroPad(utils.hexlify(amount), padding); }; -export const addressToHex = (address: string): string => { - return address - .split('') - .map((_, idx) => address.charCodeAt(idx).toString(16)) - .join(''); +/** + * Return the address transformed to hex for bitcoin deposits + * + * @category Helpers + * @param address - bitcoin address + * @param addressLength - length of the address + * @returns {string} + */ +export const addressToHex = (address: string, addressLength: number): string => { + const hexData = new Array(addressLength); + for (let i = 0; i < hexData.length; i++) { + const codePoint = address.charCodeAt(i); + hexData[i] = codePoint.toString(16); + } + + return hexData.join(''); }; /** From 00e2ac025da8921bd0660276017654a74d95d15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 16:04:40 -0400 Subject: [PATCH 31/87] chore: docs for function helpers --- packages/btc/src/utils/helpers.ts | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index 38e611448..542ca2dcf 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -18,6 +18,15 @@ type InputData = { type OutputData = { value: number; script: Buffer; address: string }; +/** + * 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, @@ -44,6 +53,14 @@ export function getScriptPubkey( } } +/** + * 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}`)], @@ -82,6 +99,16 @@ export function calculateFee({ return Math.round(virtualSize * feeRate); } +/** + * 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, @@ -112,6 +139,16 @@ export function createInputData({ }; } +/** + * 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: BaseTransferParams, feeAddress: string, @@ -121,6 +158,7 @@ export function getPsbt( if (!['P2WPKH', 'P2TR'].includes(params.typeOfAddress.toString())) { throw new Error('Unsuported address type'); } + if (Object.keys(params.utxoData).length !== 3) { throw new Error('UTXO data is required'); } From bfa82ee721b1e4478c33bbca0abcc76291e08d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 16:17:25 -0400 Subject: [PATCH 32/87] chore: removing branch condition for fee output --- packages/btc/src/utils/helpers.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index 542ca2dcf..436e067db 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -179,13 +179,11 @@ export function getPsbt( value: 0, }); - // just because there is no fee on testnet - if (feeAmount) { - psbt.addOutput({ - address: feeAddress, - value: Number(feeAmount), - }); - } + // Fee output + psbt.addOutput({ + address: feeAddress, + value: Number(feeAmount), + }); const size = 303; // this is the size on testnet with fee handler const minerFee = Math.floor(params.feeRate * size); From 01f95dede835efb072da4fe6f2df8d09aa58fddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 23:17:24 -0400 Subject: [PATCH 33/87] chore: removing table log --- .../src/transfer.p2tr.ts | 34 +++++++------------ .../src/transfer.p2wpkh.ts | 31 ++++++----------- 2 files changed, 22 insertions(+), 43 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index 7cc433c37..5b63fea6e 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -9,7 +9,8 @@ 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 } from "./utils"; + +import { broadcastTransaction, getFeeEstimates } from "./utils.js"; dotenv.config(); @@ -29,22 +30,6 @@ const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); const DERIVATION_PATH = process.env.DERIVATION_PATH; const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; -// TODO: remove this log -console.table({ - SOURCE_DOMAIN_CAIPID, - DESTINATION_ADDRESS, - DESTINATION_DOMAIN_CHAIN_ID, - RESOURCE_ID, - MNEMONIC, - MINER_FEE, - UTXO_TX_ID, - UTXO_AMOUNT, - UTXO_OUTPUT_INDEX, - DERIVATION_PATH, - CHANGE_ADDRESS, - BLOCKSTREAM_URL, -}); - if ( !SOURCE_DOMAIN_CAIPID || !DESTINATION_ADDRESS || @@ -69,8 +54,8 @@ const bip32 = BIP32Factory(tinysecp); async function btcToEvmTransfer(): Promise { // pre setup - const testnet = networks.testnet; console.log("Transfer BTC to EVM"); + const testnet = networks.testnet; const seed = await mnemonicToSeed(MNEMONIC); const rootKey = bip32.fromSeed(seed, testnet); const derivedNode = rootKey.derivePath(DERIVATION_PATH); @@ -82,15 +67,20 @@ async function btcToEvmTransfer(): Promise { crypto.taggedHash("TapTweak", publicKeyDropedDERHeader), ); + const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); + const transferParams: BaseTransferParams = { source: SOURCE_DOMAIN_CAIPID, destination: DESTINATION_DOMAIN_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, - amount: 0, + amount: 100000000, resource: RESOURCE_ID, - utxoTxId: UTXO_TX_ID, - utxoOutputIndex: UTXO_OUTPUT_INDEX, - utxoAmount: UTXO_AMOUNT, + utxoData: { + utxoTxId: UTXO_TX_ID, + utxoOutputIndex: UTXO_OUTPUT_INDEX, + utxoAmount: UTXO_AMOUNT, + }, + feeRate, publicKey: publicKeyDropedDERHeader, typeOfAddress: TypeOfAddress.P2TR, minerFee: MINER_FEE, diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 676b6283f..923c85d0a 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -8,7 +8,7 @@ import { mnemonicToSeed } from "bip39"; import { initEccLib, networks } from "bitcoinjs-lib"; import dotenv from "dotenv"; import * as tinysecp from "tiny-secp256k1"; -import { broadcastTransaction } from "./utils"; +import { broadcastTransaction, getFeeEstimates } from "./utils"; dotenv.config(); @@ -28,22 +28,6 @@ const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); const DERIVATION_PATH = process.env.DERIVATION_PATH; const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; -// TODO: remove this log -console.table({ - SOURCE_DOMAIN_CAIPID, - DESTINATION_ADDRESS, - DESTINATION_DOMAIN_CHAIN_ID, - RESOURCE_ID, - MNEMONIC, - MINER_FEE, - UTXO_TX_ID, - UTXO_AMOUNT, - UTXO_OUTPUT_INDEX, - DERIVATION_PATH, - CHANGE_ADDRESS, - BLOCKSTREAM_URL, -}); - if ( !SOURCE_DOMAIN_CAIPID || !DESTINATION_ADDRESS || @@ -74,20 +58,25 @@ async function btcToEvmTransfer(): Promise { const rootKey = bip32.fromSeed(seed, testnet); const derivedNode = rootKey.derivePath(DERIVATION_PATH); + const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); + const transferParams: BaseTransferParams = { source: SOURCE_DOMAIN_CAIPID, destination: DESTINATION_DOMAIN_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, - amount: 0, + amount: 150000000, resource: RESOURCE_ID, - utxoTxId: UTXO_TX_ID, - utxoOutputIndex: UTXO_OUTPUT_INDEX, - utxoAmount: UTXO_AMOUNT, + utxoData: { + utxoTxId: UTXO_TX_ID, + utxoOutputIndex: UTXO_OUTPUT_INDEX, + utxoAmount: UTXO_AMOUNT, + }, publicKey: derivedNode.publicKey, typeOfAddress: TypeOfAddress.P2WPKH, minerFee: MINER_FEE, network: testnet, changeAddress: CHANGE_ADDRESS, + feeRate, }; const transfer = await createBitcoinFungibleTransfer(transferParams); From ebbdd9e3e7b97b3b42d797bdded4018bdc62850f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 23:21:06 -0400 Subject: [PATCH 34/87] chore: removing unused method and types --- packages/btc/src/utils/helpers.ts | 41 ------------------------------- 1 file changed, 41 deletions(-) diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index 436e067db..f2e9a0ea0 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -9,15 +9,6 @@ import type { PaymentReturnData, } from '../types.js'; -type InputData = { - hash: string; - index: number; - witnessUtxo: { value: number; script: Buffer }; - tapInternalKey: Buffer; -}; - -type OutputData = { value: number; script: Buffer; address: string }; - /** * Get the scriptPubKey for the given public key and network * @@ -67,38 +58,6 @@ function encodeDepositAddress(depositAddress: string, destinationDomainId: numbe }); } -// TODO: this needs to be used first -export function calculateFee({ - psbt, - feeRate, - inputData, - bridgeOutputData, - valueOutputData, - outputFeeData, - signer, -}: { - feeRate: number; - psbt: Psbt; - inputData: InputData; - bridgeOutputData: Pick; - valueOutputData: Pick; - outputFeeData: Pick; - signer: Signer; -}): number { - psbt.addInput(inputData); - psbt.addOutput(bridgeOutputData); - psbt.addOutput(valueOutputData); - psbt.addOutput(outputFeeData); - psbt.signInput(0, signer); - psbt.finalizeAllInputs(); - - const tx = psbt.extractTransaction(true); - - const virtualSize = tx.virtualSize(); - - return Math.round(virtualSize * feeRate); -} - /** * Create the input data for the PSBT * From 19a826951f6efe8cd53a746325e491d9cc032443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 23:24:11 -0400 Subject: [PATCH 35/87] chore: better env.sample description --- .../btc-to-evm-fungible-transfer/.env.sample | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index 2044309cd..eb11d1dc4 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -1,14 +1,14 @@ SYGMA_ENV=testnet -BLOCKSTREAM_URL= -DESTINATION_ADDRESS= -DESTINATION_DOMAIN_CHAIN_ID= -RESOURCE_ID= -SOURCE_DOMAIN_CAIPID= -EXPLORER_URL= -MINER_FEE= -MNEMONIC= -DERIVATION_PATH= -UTXO_TX_ID= -UTXO_AMOUNT= -UTXO_OUTPUT_INDEX= -CHANGE_ADDRESS= \ No newline at end of file +BLOCKSTREAM_URL="your blockstream url" +DESTINATION_ADDRESS="your evm destination address" +DESTINATION_DOMAIN_CHAIN_ID="destination domain chain id" +RESOURCE_ID="resource id" +SOURCE_DOMAIN_CAIPID="source domain caip id" +EXPLORER_URL="your bitcoin explorer url" +MINER_FEE="miner fee" +MNEMONIC="your 12 or 24 mnemonic" +DERIVATION_PATH="your derivation path" +UTXO_TX_ID="your utxo tx id" +UTXO_AMOUNT="your utxo amount" +UTXO_OUTPUT_INDEX="your utxo output index" +CHANGE_ADDRESS="your change address" \ No newline at end of file From 24aa725f127137468a339b15c71df2b12b8d0b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 23:26:28 -0400 Subject: [PATCH 36/87] chore: small change --- examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 923c85d0a..22eeb6a98 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -52,8 +52,8 @@ const bip32 = BIP32Factory(tinysecp); async function btcToEvmTransfer(): Promise { // pre setup - const testnet = networks.testnet; console.log("Transfer BTC to EVM"); + const testnet = networks.testnet; const seed = await mnemonicToSeed(MNEMONIC); const rootKey = bip32.fromSeed(seed, testnet); const derivedNode = rootKey.derivePath(DERIVATION_PATH); From fd5267996680e2e2d4818c537e5f37b96ee68b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 23:28:11 -0400 Subject: [PATCH 37/87] chore: remove unused utils method from example --- .../btc-to-evm-fungible-transfer/src/utils.ts | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/utils.ts b/examples/btc-to-evm-fungible-transfer/src/utils.ts index 2d00583fe..be2928c93 100644 --- a/examples/btc-to-evm-fungible-transfer/src/utils.ts +++ b/examples/btc-to-evm-fungible-transfer/src/utils.ts @@ -1,15 +1,3 @@ -import type { Signer, Psbt } from "bitcoinjs-lib"; - -type InputData = { - hash: string; - index: number; - witnessUtxo: { value: number; script: Buffer }; - tapInternalKey: Buffer; -}; - -type OutputData = { value: number; script: Buffer; address: string }; - - export async function getFeeEstimates(blockstreamUrl: string): Promise { if (!blockstreamUrl) throw new Error("Blockstream url is required"); try { @@ -42,27 +30,3 @@ export async function broadcastTransaction( throw new Error("Failed to broadcast transaction"); } } - -export function calculateFee( - psbt: Psbt, - feeRate: number, - inputData: InputData, - bridgeOutputData: Pick, - valueOutputData: Pick, - outputFeeData: Pick, - tweakedSigner: Signer, -): number { - psbt.addInput(inputData); - psbt.addOutput(bridgeOutputData); - psbt.addOutput(valueOutputData); - psbt.addOutput(outputFeeData); - - psbt.signInput(0, tweakedSigner); - psbt.finalizeAllInputs(); - - const tx = psbt.extractTransaction(true); - - const virtualSize = tx.virtualSize(); - - return Math.round(virtualSize * feeRate); -} From 24b1ca571627968af4e5a5a7e2d2053ba9f3996f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 23:30:45 -0400 Subject: [PATCH 38/87] chore: typo --- examples/evm-to-btc-fungible-transfer/src/transfer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/evm-to-btc-fungible-transfer/src/transfer.ts b/examples/evm-to-btc-fungible-transfer/src/transfer.ts index 67b0fcba1..1e01f094b 100644 --- a/examples/evm-to-btc-fungible-transfer/src/transfer.ts +++ b/examples/evm-to-btc-fungible-transfer/src/transfer.ts @@ -50,7 +50,7 @@ export async function erc20Transfer(): Promise { const transferTx = await transfer.getTransferTransaction(); const response = await wallet.sendTransaction(transferTx); await response.wait(); - console.log(`Depositted, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`); + console.log(`Deposited, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`); } erc20Transfer().finally(() => { }); \ No newline at end of file From f51eae9ea6d7aa7feb644a97d73d2c50f5b99a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 23:32:58 -0400 Subject: [PATCH 39/87] chore: remove log --- packages/btc/src/utils/helpers.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index f2e9a0ea0..d531de4e0 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -1,4 +1,4 @@ -import type { networks, Payment, Signer } from 'bitcoinjs-lib'; +import type { networks, Payment } from 'bitcoinjs-lib'; import { payments, Psbt } from 'bitcoinjs-lib'; import { TypeOfAddress } from '../types.js'; @@ -33,13 +33,11 @@ export function getScriptPubkey( return { scriptPubKey: output }; } else { - const { output, address } = payments.p2tr({ + const { output } = payments.p2tr({ internalPubkey: publicKey, network, }) as PaymentReturnData; - console.log('address', address, output.toString('hex')); - return { scriptPubKey: output }; } } From df4360e9171469e7d6a57ff57c723bd6a846b071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 29 Jul 2024 23:36:31 -0400 Subject: [PATCH 40/87] chore: removing comment --- packages/btc/src/utils/helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index d531de4e0..bcbea812d 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -124,7 +124,6 @@ export function getPsbt( throw new Error('Not enough funds to spend from the UTXO'); } - // TODO: to remove the address parameter being returned const psbt = new Psbt({ network: params.network }); psbt.addInput(createInputData(params)); From 8a292c98a3a3b6712e9c24e5274add8c58413fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 30 Jul 2024 00:05:11 -0400 Subject: [PATCH 41/87] chore: removing miner fee --- examples/btc-to-evm-fungible-transfer/.env.sample | 1 - examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts | 5 +---- examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts | 5 +---- packages/btc/src/fungible.ts | 3 --- packages/btc/src/types.ts | 1 - packages/btc/src/utils/helpers.ts | 1 - 6 files changed, 2 insertions(+), 14 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index eb11d1dc4..5b92d3ccb 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -5,7 +5,6 @@ DESTINATION_DOMAIN_CHAIN_ID="destination domain chain id" RESOURCE_ID="resource id" SOURCE_DOMAIN_CAIPID="source domain caip id" EXPLORER_URL="your bitcoin explorer url" -MINER_FEE="miner fee" MNEMONIC="your 12 or 24 mnemonic" DERIVATION_PATH="your derivation path" UTXO_TX_ID="your utxo tx id" diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index 5b63fea6e..5664c66e4 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -23,7 +23,6 @@ const RESOURCE_ID = process.env.RESOURCE_ID; const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const EXPLORER_URL = process.env.EXPLORER_URL; const MNEMONIC = process.env.MNEMONIC; -const MINER_FEE = Number(process.env.MINER_FEE); const UTXO_TX_ID = process.env.UTXO_TX_ID; const UTXO_AMOUNT = Number(process.env.UTXO_AMOUNT); const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); @@ -36,7 +35,6 @@ if ( !DESTINATION_DOMAIN_CHAIN_ID || !RESOURCE_ID || !MNEMONIC || - !MINER_FEE || !UTXO_TX_ID || !UTXO_AMOUNT || !UTXO_OUTPUT_INDEX || @@ -73,7 +71,7 @@ async function btcToEvmTransfer(): Promise { source: SOURCE_DOMAIN_CAIPID, destination: DESTINATION_DOMAIN_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, - amount: 100000000, + amount: 0, resource: RESOURCE_ID, utxoData: { utxoTxId: UTXO_TX_ID, @@ -83,7 +81,6 @@ async function btcToEvmTransfer(): Promise { feeRate, publicKey: publicKeyDropedDERHeader, typeOfAddress: TypeOfAddress.P2TR, - minerFee: MINER_FEE, network: testnet, changeAddress: CHANGE_ADDRESS, }; diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 22eeb6a98..d5abd4d12 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -21,7 +21,6 @@ const RESOURCE_ID = process.env.RESOURCE_ID; const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const EXPLORER_URL = process.env.EXPLORER_URL; const MNEMONIC = process.env.MNEMONIC; -const MINER_FEE = Number(process.env.MINER_FEE); const UTXO_TX_ID = process.env.UTXO_TX_ID; const UTXO_AMOUNT = Number(process.env.UTXO_AMOUNT); const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); @@ -34,7 +33,6 @@ if ( !DESTINATION_DOMAIN_CHAIN_ID || !RESOURCE_ID || !MNEMONIC || - !MINER_FEE || !UTXO_TX_ID || !UTXO_AMOUNT || !UTXO_OUTPUT_INDEX || @@ -64,7 +62,7 @@ async function btcToEvmTransfer(): Promise { source: SOURCE_DOMAIN_CAIPID, destination: DESTINATION_DOMAIN_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, - amount: 150000000, + amount: 0, resource: RESOURCE_ID, utxoData: { utxoTxId: UTXO_TX_ID, @@ -73,7 +71,6 @@ async function btcToEvmTransfer(): Promise { }, publicKey: derivedNode.publicKey, typeOfAddress: TypeOfAddress.P2WPKH, - minerFee: MINER_FEE, network: testnet, changeAddress: CHANGE_ADDRESS, feeRate, diff --git a/packages/btc/src/fungible.ts b/packages/btc/src/fungible.ts index d5633a5f6..c79ef077d 100644 --- a/packages/btc/src/fungible.ts +++ b/packages/btc/src/fungible.ts @@ -18,7 +18,6 @@ class BitcoinTransfer extends BaseTransfer { protected amount: number; protected publicKey: Buffer; protected typeOfAddress: TypeOfAddress; - protected minerFee: number; protected network: networks.Network; protected changeAddress?: string; protected feeRate: number; @@ -30,7 +29,6 @@ class BitcoinTransfer extends BaseTransfer { this.amount = transfer.amount; this.publicKey = transfer.publicKey; this.typeOfAddress = transfer.typeOfAddress; - this.minerFee = transfer.minerFee; this.network = transfer.network; this.changeAddress = transfer.changeAddress; this.feeRate = transfer.feeRate; @@ -51,7 +49,6 @@ class BitcoinTransfer extends BaseTransfer { network: this.network, feeRate: this.feeRate, changeAddress: this.changeAddress, - minerFee: this.minerFee, }, this.feeAddress, this.resource.address, diff --git a/packages/btc/src/types.ts b/packages/btc/src/types.ts index aeaf75ebb..049f664ab 100644 --- a/packages/btc/src/types.ts +++ b/packages/btc/src/types.ts @@ -22,7 +22,6 @@ export type BaseTransferParams = { typeOfAddress: TypeOfAddress; network: networks.Network; feeRate: number; - minerFee: number; changeAddress?: string; }; diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index bcbea812d..967f4d84a 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -143,7 +143,6 @@ export function getPsbt( const size = 303; // this is the size on testnet with fee handler const minerFee = Math.floor(params.feeRate * size); - console.log('minerFee', minerFee); let amountToSpent; // TODO: this condition is temporary since there is no fee on testnet if (feeAmount) { From f0956d88644a0c8e96324c58747287c5e1fb79fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 30 Jul 2024 00:07:43 -0400 Subject: [PATCH 42/87] chore: miner free removed from test --- packages/btc/src/__test__/fungible.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/btc/src/__test__/fungible.test.ts b/packages/btc/src/__test__/fungible.test.ts index 2bb59b7f9..05884298c 100644 --- a/packages/btc/src/__test__/fungible.test.ts +++ b/packages/btc/src/__test__/fungible.test.ts @@ -24,7 +24,6 @@ const P2TR_TRANSFER_PARAMS: BaseTransferParams = { Buffer.from('03feca449bd5b50085d23864a006f6ea4da80ff63816033f6437193c66bac7488c', 'hex'), ), typeOfAddress: TypeOfAddress.P2TR, - minerFee: 1000, network: bitcoin.networks.testnet, changeAddress: 'tb1pxmrzd94rs6wtg6ewdjfmuu7s88n2kdqc20vzfmadanfaem3n9sdq0vagu0', environment: Environment.DEVNET, From 20226a1059e7b4a25de87f245697fb4ec9f307da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 30 Jul 2024 00:11:20 -0400 Subject: [PATCH 43/87] chore: removing comment and if branch --- packages/btc/src/utils/helpers.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/btc/src/utils/helpers.ts b/packages/btc/src/utils/helpers.ts index 967f4d84a..e5bf0c6f4 100644 --- a/packages/btc/src/utils/helpers.ts +++ b/packages/btc/src/utils/helpers.ts @@ -141,15 +141,10 @@ export function getPsbt( value: Number(feeAmount), }); - const size = 303; // this is the size on testnet with fee handler + const size = 303; const minerFee = Math.floor(params.feeRate * size); - let amountToSpent; // TODO: this condition is temporary since there is no fee on testnet - if (feeAmount) { - amountToSpent = params.utxoData.utxoAmount - Number(feeAmount) - minerFee; - } else { - amountToSpent = params.utxoData.utxoAmount - minerFee; - } + const amountToSpent = params.utxoData.utxoAmount - Number(feeAmount) - minerFee; if (amountToSpent < params.amount) { throw new Error('Not enough funds'); From 1af4531bdf95bfb3d77f6ddfcd6817bae4db6e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 30 Jul 2024 23:14:49 -0400 Subject: [PATCH 44/87] chore: pr comments --- .../btc-to-evm-fungible-transfer/.env.sample | 4 ++-- .../btc-to-evm-fungible-transfer/package.json | 2 +- .../src/transfer.p2tr.ts | 19 ++++++++----------- .../src/transfer.p2wpkh.ts | 19 +++++++++---------- package.json | 12 ++++++------ packages/{btc => bitcoin}/.eslintrc.cjs | 0 packages/{btc => bitcoin}/.gitignore | 0 packages/{btc => bitcoin}/.npmignore | 0 packages/{btc => bitcoin}/.prettierrc.json | 0 packages/{btc => bitcoin}/README.md | 0 packages/{btc => bitcoin}/jest.config.cjs | 0 packages/{btc => bitcoin}/package.json | 7 ++++--- .../src/__test__/fungible.test.ts | 0 .../{btc => bitcoin}/src/base-transfer.ts | 0 .../{btc => bitcoin}/src/environment.d.ts | 0 packages/{btc => bitcoin}/src/fungible.ts | 0 packages/{btc => bitcoin}/src/index.ts | 0 packages/{btc => bitcoin}/src/types.ts | 0 .../{btc => bitcoin}/src/utils/helpers.ts | 0 packages/{btc => bitcoin}/src/utils/index.ts | 0 packages/{btc => bitcoin}/test/setupJest.js | 0 packages/{btc => bitcoin}/tsconfig.cjs.json | 0 packages/{btc => bitcoin}/tsconfig.esm.json | 0 packages/{btc => bitcoin}/tsconfig.json | 0 packages/{btc => bitcoin}/tsconfig.types.json | 0 packages/core/src/config/config.ts | 11 ++++++----- packages/core/src/types.ts | 2 ++ 27 files changed, 38 insertions(+), 38 deletions(-) rename packages/{btc => bitcoin}/.eslintrc.cjs (100%) rename packages/{btc => bitcoin}/.gitignore (100%) rename packages/{btc => bitcoin}/.npmignore (100%) rename packages/{btc => bitcoin}/.prettierrc.json (100%) rename packages/{btc => bitcoin}/README.md (100%) rename packages/{btc => bitcoin}/jest.config.cjs (100%) rename packages/{btc => bitcoin}/package.json (88%) rename packages/{btc => bitcoin}/src/__test__/fungible.test.ts (100%) rename packages/{btc => bitcoin}/src/base-transfer.ts (100%) rename packages/{btc => bitcoin}/src/environment.d.ts (100%) rename packages/{btc => bitcoin}/src/fungible.ts (100%) rename packages/{btc => bitcoin}/src/index.ts (100%) rename packages/{btc => bitcoin}/src/types.ts (100%) rename packages/{btc => bitcoin}/src/utils/helpers.ts (100%) rename packages/{btc => bitcoin}/src/utils/index.ts (100%) rename packages/{btc => bitcoin}/test/setupJest.js (100%) rename packages/{btc => bitcoin}/tsconfig.cjs.json (100%) rename packages/{btc => bitcoin}/tsconfig.esm.json (100%) rename packages/{btc => bitcoin}/tsconfig.json (100%) rename packages/{btc => bitcoin}/tsconfig.types.json (100%) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index 5b92d3ccb..85d2939bd 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -1,9 +1,9 @@ SYGMA_ENV=testnet BLOCKSTREAM_URL="your blockstream url" DESTINATION_ADDRESS="your evm destination address" -DESTINATION_DOMAIN_CHAIN_ID="destination domain chain id" +DESTINATION_CHAIN_ID="destination domain chain id" RESOURCE_ID="resource id" -SOURCE_DOMAIN_CAIPID="source domain caip id" +SOURCE_CAIPID="source domain caip id" EXPLORER_URL="your bitcoin explorer url" MNEMONIC="your 12 or 24 mnemonic" DERIVATION_PATH="your derivation path" diff --git a/examples/btc-to-evm-fungible-transfer/package.json b/examples/btc-to-evm-fungible-transfer/package.json index 830ccfc5d..c873a2ce0 100644 --- a/examples/btc-to-evm-fungible-transfer/package.json +++ b/examples/btc-to-evm-fungible-transfer/package.json @@ -29,7 +29,7 @@ "typescript": "5.0.4" }, "dependencies": { - "@buildwithsygma/btc": "workspace:^", + "@buildwithsygma/bitcoin": "workspace:^", "@buildwithsygma/core": "workspace:^", "bip32": "^4.0.0", "bip39": "^3.1.0", diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index 5664c66e4..5f6e06aad 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -14,11 +14,9 @@ import { broadcastTransaction, getFeeEstimates } from "./utils.js"; dotenv.config(); -const SOURCE_DOMAIN_CAIPID = process.env.SOURCE_DOMAIN_CAIPID; +const SOURCE_CAIPID = process.env.SOURCE_CAIPID; const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; -const DESTINATION_DOMAIN_CHAIN_ID = Number( - process.env.DESTINATION_DOMAIN_CHAIN_ID, -); +const DESTINATION_CHAIN_ID = Number(process.env.DESTINATION_CHAIN_ID); const RESOURCE_ID = process.env.RESOURCE_ID; const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const EXPLORER_URL = process.env.EXPLORER_URL; @@ -30,9 +28,9 @@ const DERIVATION_PATH = process.env.DERIVATION_PATH; const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; if ( - !SOURCE_DOMAIN_CAIPID || + !SOURCE_CAIPID || !DESTINATION_ADDRESS || - !DESTINATION_DOMAIN_CHAIN_ID || + !DESTINATION_CHAIN_ID || !RESOURCE_ID || !MNEMONIC || !UTXO_TX_ID || @@ -47,15 +45,14 @@ if ( ); } -initEccLib(tinysecp); -const bip32 = BIP32Factory(tinysecp); async function btcToEvmTransfer(): Promise { // pre setup + initEccLib(tinysecp); + const bip32 = BIP32Factory(tinysecp); console.log("Transfer BTC to EVM"); - const testnet = networks.testnet; const seed = await mnemonicToSeed(MNEMONIC); - const rootKey = bip32.fromSeed(seed, testnet); + const rootKey = bip32.fromSeed(seed, networks.testnet); const derivedNode = rootKey.derivePath(DERIVATION_PATH); // Note: default example is going to run P2TR transfer @@ -81,7 +78,7 @@ async function btcToEvmTransfer(): Promise { feeRate, publicKey: publicKeyDropedDERHeader, typeOfAddress: TypeOfAddress.P2TR, - network: testnet, + network: networks.testnet, changeAddress: CHANGE_ADDRESS, }; diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index d5abd4d12..3ca4c1c36 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -12,10 +12,10 @@ import { broadcastTransaction, getFeeEstimates } from "./utils"; dotenv.config(); -const SOURCE_DOMAIN_CAIPID = process.env.SOURCE_DOMAIN_CAIPID; +const SOURCE_CAIPID = process.env.SOURCE_CAIPID; const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; -const DESTINATION_DOMAIN_CHAIN_ID = Number( - process.env.DESTINATION_DOMAIN_CHAIN_ID, +const DESTINATION_CHAIN_ID = Number( + process.env.DESTINATION_CHAIN_ID, ); const RESOURCE_ID = process.env.RESOURCE_ID; const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; @@ -28,9 +28,9 @@ const DERIVATION_PATH = process.env.DERIVATION_PATH; const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; if ( - !SOURCE_DOMAIN_CAIPID || + !SOURCE_CAIPID || !DESTINATION_ADDRESS || - !DESTINATION_DOMAIN_CHAIN_ID || + !DESTINATION_CHAIN_ID || !RESOURCE_ID || !MNEMONIC || !UTXO_TX_ID || @@ -45,15 +45,14 @@ if ( ); } -initEccLib(tinysecp); -const bip32 = BIP32Factory(tinysecp); async function btcToEvmTransfer(): Promise { // pre setup + initEccLib(tinysecp); + const bip32 = BIP32Factory(tinysecp); console.log("Transfer BTC to EVM"); - const testnet = networks.testnet; const seed = await mnemonicToSeed(MNEMONIC); - const rootKey = bip32.fromSeed(seed, testnet); + const rootKey = bip32.fromSeed(seed, networks.testnet); const derivedNode = rootKey.derivePath(DERIVATION_PATH); const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); @@ -71,7 +70,7 @@ async function btcToEvmTransfer(): Promise { }, publicKey: derivedNode.publicKey, typeOfAddress: TypeOfAddress.P2WPKH, - network: testnet, + network: networks.testnet, changeAddress: CHANGE_ADDRESS, feeRate, }; diff --git a/package.json b/package.json index 6feff9708..928a531a7 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,12 @@ "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", - "btc:build": "yarn workspace @buildwithsygma/btc build:all", - "btc:cleanDist": "yarn workspace @buildwithsygma/btc clean", - "btc:test": "yarn workspace @buildwithsygma/btc test", - "btc:lint": "yarn workspace @buildwithsygma/btc lint", - "btc:lint:fix": "yarn workspace @buildwithsygma/btc lint:fix", - "build:all": "yarn workspace @buildwithsygma/core build:all; yarn workspace @buildwithsygma/evm build:all; yarn workspace @buildwithsygma/btc build:all", + "btc:build": "yarn workspace @buildwithsygma/bitcoin build:all", + "btc:cleanDist": "yarn workspace @buildwithsygma/bitcoin clean", + "btc:test": "yarn workspace @buildwithsygma/bitcoin test", + "btc:lint": "yarn workspace @buildwithsygma/bitcoin lint", + "btc:lint:fix": "yarn workspace @buildwithsygma/bitcoin lint:fix", + "build:all": "yarn workspace @buildwithsygma/core build:all; yarn workspace @buildwithsygma/evm build:all; yarn workspace @buildwithsygma/bitcoin build:all", "clean:all": "yarn workspace @buildwithsygma/core clean; yarn workspace @buildwithsygma/evm clean", "substrate:build": "yarn workspace @buildwithsygma/substrate build:all", "substrate:cleanDist": "yarn workspace @buildwithsygma/substrate clean", diff --git a/packages/btc/.eslintrc.cjs b/packages/bitcoin/.eslintrc.cjs similarity index 100% rename from packages/btc/.eslintrc.cjs rename to packages/bitcoin/.eslintrc.cjs diff --git a/packages/btc/.gitignore b/packages/bitcoin/.gitignore similarity index 100% rename from packages/btc/.gitignore rename to packages/bitcoin/.gitignore diff --git a/packages/btc/.npmignore b/packages/bitcoin/.npmignore similarity index 100% rename from packages/btc/.npmignore rename to packages/bitcoin/.npmignore diff --git a/packages/btc/.prettierrc.json b/packages/bitcoin/.prettierrc.json similarity index 100% rename from packages/btc/.prettierrc.json rename to packages/bitcoin/.prettierrc.json diff --git a/packages/btc/README.md b/packages/bitcoin/README.md similarity index 100% rename from packages/btc/README.md rename to packages/bitcoin/README.md diff --git a/packages/btc/jest.config.cjs b/packages/bitcoin/jest.config.cjs similarity index 100% rename from packages/btc/jest.config.cjs rename to packages/bitcoin/jest.config.cjs diff --git a/packages/btc/package.json b/packages/bitcoin/package.json similarity index 88% rename from packages/btc/package.json rename to packages/bitcoin/package.json index bfb4ac9ee..6a328ed1a 100644 --- a/packages/btc/package.json +++ b/packages/bitcoin/package.json @@ -1,5 +1,5 @@ { - "name": "@buildwithsygma/btc", + "name": "@buildwithsygma/bitcoin", "version": "0.0.1", "description": "Core primitives for bridging and message passing", "main": "dist-esm/index.js", @@ -17,7 +17,7 @@ "url": "https://github.com/sygmaprotocol/sygma-sdk" }, "scripts": { - "test": "jest --watchAll --detectOpenHandles --silent", + "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", @@ -30,7 +30,8 @@ "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" + "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", diff --git a/packages/btc/src/__test__/fungible.test.ts b/packages/bitcoin/src/__test__/fungible.test.ts similarity index 100% rename from packages/btc/src/__test__/fungible.test.ts rename to packages/bitcoin/src/__test__/fungible.test.ts diff --git a/packages/btc/src/base-transfer.ts b/packages/bitcoin/src/base-transfer.ts similarity index 100% rename from packages/btc/src/base-transfer.ts rename to packages/bitcoin/src/base-transfer.ts diff --git a/packages/btc/src/environment.d.ts b/packages/bitcoin/src/environment.d.ts similarity index 100% rename from packages/btc/src/environment.d.ts rename to packages/bitcoin/src/environment.d.ts diff --git a/packages/btc/src/fungible.ts b/packages/bitcoin/src/fungible.ts similarity index 100% rename from packages/btc/src/fungible.ts rename to packages/bitcoin/src/fungible.ts diff --git a/packages/btc/src/index.ts b/packages/bitcoin/src/index.ts similarity index 100% rename from packages/btc/src/index.ts rename to packages/bitcoin/src/index.ts diff --git a/packages/btc/src/types.ts b/packages/bitcoin/src/types.ts similarity index 100% rename from packages/btc/src/types.ts rename to packages/bitcoin/src/types.ts diff --git a/packages/btc/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts similarity index 100% rename from packages/btc/src/utils/helpers.ts rename to packages/bitcoin/src/utils/helpers.ts diff --git a/packages/btc/src/utils/index.ts b/packages/bitcoin/src/utils/index.ts similarity index 100% rename from packages/btc/src/utils/index.ts rename to packages/bitcoin/src/utils/index.ts diff --git a/packages/btc/test/setupJest.js b/packages/bitcoin/test/setupJest.js similarity index 100% rename from packages/btc/test/setupJest.js rename to packages/bitcoin/test/setupJest.js diff --git a/packages/btc/tsconfig.cjs.json b/packages/bitcoin/tsconfig.cjs.json similarity index 100% rename from packages/btc/tsconfig.cjs.json rename to packages/bitcoin/tsconfig.cjs.json diff --git a/packages/btc/tsconfig.esm.json b/packages/bitcoin/tsconfig.esm.json similarity index 100% rename from packages/btc/tsconfig.esm.json rename to packages/bitcoin/tsconfig.esm.json diff --git a/packages/btc/tsconfig.json b/packages/bitcoin/tsconfig.json similarity index 100% rename from packages/btc/tsconfig.json rename to packages/bitcoin/tsconfig.json diff --git a/packages/btc/tsconfig.types.json b/packages/bitcoin/tsconfig.types.json similarity index 100% rename from packages/btc/tsconfig.types.json rename to packages/bitcoin/tsconfig.types.json diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index b3f078582..e073dcfbc 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -8,6 +8,7 @@ import type { Resource, SubstrateConfig, SygmaConfig, + SygmaDomainConfig, } from '../types.js'; import { Environment } from '../types.js'; @@ -53,10 +54,10 @@ export class Config { } /** * Creates a domain object from config object - * @param {EthereumConfig | SubstrateConfig | BitcoinConfig} config + * @param {SygmaDomainConfig} config * @returns {Domain} */ - private createDomain(config: EthereumConfig | SubstrateConfig | BitcoinConfig): Domain { + private createDomain(config: SygmaDomainConfig): Domain { return { id: config.id, caipId: config.caipId, @@ -74,7 +75,7 @@ export class Config { * @param {number} sygmaId * @returns {SubstrateConfig | EthereumConfig} */ - findDomainConfigBySygmaId(sygmaId: number): SubstrateConfig | EthereumConfig | BitcoinConfig { + 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; @@ -83,9 +84,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 | BitcoinConfig { + findDomainConfig(domainLike: Domainlike): SygmaDomainConfig { const config = this.configuration.domains.find(domain => { switch (typeof domainLike) { case 'string': diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9e6cd9573..495de999e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -143,6 +143,8 @@ export interface BitcoinConfig extends BaseConfig { feeAddress: string; } +export type SygmaDomainConfig = EthereumConfig | SubstrateConfig | BitcoinConfig; + export type IndexerRoutesResponse = { routes: RouteIndexerType[] }; export type Handler = { From d44812347a17927410bc03f51755d1f2bb4e58d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 31 Jul 2024 08:25:46 -0400 Subject: [PATCH 45/87] chore: amount and removing duplicate properties from fungible class --- examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts | 6 ++++-- .../btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts | 6 ++++-- packages/bitcoin/src/fungible.ts | 2 -- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index 5f6e06aad..2ca32ff13 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -26,6 +26,7 @@ const UTXO_AMOUNT = Number(process.env.UTXO_AMOUNT); const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); const DERIVATION_PATH = process.env.DERIVATION_PATH; const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; +const AMOUNT = Number(process.env.AMOUNT); if ( !SOURCE_CAIPID || @@ -38,7 +39,8 @@ if ( !UTXO_OUTPUT_INDEX || !DERIVATION_PATH || !CHANGE_ADDRESS || - !BLOCKSTREAM_URL + !BLOCKSTREAM_URL || + !AMOUNT ) { throw new Error( "Please provided needed env variables needed into the .env file", @@ -68,7 +70,7 @@ async function btcToEvmTransfer(): Promise { source: SOURCE_DOMAIN_CAIPID, destination: DESTINATION_DOMAIN_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, - amount: 0, + amount: AMOUNT, resource: RESOURCE_ID, utxoData: { utxoTxId: UTXO_TX_ID, diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 3ca4c1c36..de138eb80 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -26,6 +26,7 @@ const UTXO_AMOUNT = Number(process.env.UTXO_AMOUNT); const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); const DERIVATION_PATH = process.env.DERIVATION_PATH; const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; +const AMOUNT = Number(process.env.AMOUNT); if ( !SOURCE_CAIPID || @@ -38,7 +39,8 @@ if ( !UTXO_OUTPUT_INDEX || !DERIVATION_PATH || !CHANGE_ADDRESS || - !BLOCKSTREAM_URL + !BLOCKSTREAM_URL || + !AMOUNT ) { throw new Error( "Please provided needed env variables needed into the .env file", @@ -61,7 +63,7 @@ async function btcToEvmTransfer(): Promise { source: SOURCE_DOMAIN_CAIPID, destination: DESTINATION_DOMAIN_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, - amount: 0, + amount: AMOUNT, resource: RESOURCE_ID, utxoData: { utxoTxId: UTXO_TX_ID, diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index c79ef077d..a7a441f55 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -14,8 +14,6 @@ export async function createBitcoinFungibleTransfer( } class BitcoinTransfer extends BaseTransfer { - protected destinationAddress: string; - protected amount: number; protected publicKey: Buffer; protected typeOfAddress: TypeOfAddress; protected network: networks.Network; From 88e3e9ffec7d1b17ae1c5d7b0653e09ba6b9c7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 31 Jul 2024 08:29:32 -0400 Subject: [PATCH 46/87] chore: removed utils file and updated imports --- .../btc-to-evm-fungible-transfer/.env.sample | 3 +- .../src/blockstream-api.ts | 31 +++++++++++++++++++ .../src/transfer.p2tr.ts | 3 +- .../src/transfer.p2wpkh.ts | 2 +- 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index 85d2939bd..c1a056d52 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -10,4 +10,5 @@ DERIVATION_PATH="your derivation path" UTXO_TX_ID="your utxo tx id" UTXO_AMOUNT="your utxo amount" UTXO_OUTPUT_INDEX="your utxo output index" -CHANGE_ADDRESS="your change address" \ No newline at end of file +CHANGE_ADDRESS="your change address" +AMOUNT="your amount to transfer" \ No newline at end of file diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts b/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts new file mode 100644 index 000000000..0bf0cbb12 --- /dev/null +++ b/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts @@ -0,0 +1,31 @@ +export async function getFeeEstimates(blockstreamUrl: string): Promise { + if (!blockstreamUrl) throw new Error("Blockstream url is required"); + try { + const response = await fetch(`${blockstreamUrl}/fee-estimates`); + + const data = await response.json(); + + return data["5"]; // fee for 5 blocks confirmation + } catch (error) { + throw new Error("Failed to get fee estimates"); + } +} + +export async function broadcastTransaction( + blockstreamUrl: string, + txHex: string, +): Promise { + 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"); + } +} diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index 2ca32ff13..a1c63701e 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -9,8 +9,7 @@ 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, getFeeEstimates } from "./utils.js"; +import { broadcastTransaction, getFeeEstimates } from "./blockstream-api"; dotenv.config(); diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index de138eb80..67822a858 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -8,7 +8,7 @@ import { mnemonicToSeed } from "bip39"; import { initEccLib, networks } from "bitcoinjs-lib"; import dotenv from "dotenv"; import * as tinysecp from "tiny-secp256k1"; -import { broadcastTransaction, getFeeEstimates } from "./utils"; +import { broadcastTransaction, getFeeEstimates } from "./blockstream-api"; dotenv.config(); From bb4ca1fb8926f246e873ab672c04c9a9912ed06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 31 Jul 2024 08:30:32 -0400 Subject: [PATCH 47/87] chore: removing utils --- .../btc-to-evm-fungible-transfer/src/utils.ts | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 examples/btc-to-evm-fungible-transfer/src/utils.ts diff --git a/examples/btc-to-evm-fungible-transfer/src/utils.ts b/examples/btc-to-evm-fungible-transfer/src/utils.ts deleted file mode 100644 index be2928c93..000000000 --- a/examples/btc-to-evm-fungible-transfer/src/utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -export async function getFeeEstimates(blockstreamUrl: string): Promise { - if (!blockstreamUrl) throw new Error("Blockstream url is required"); - try { - const response = await fetch(`${blockstreamUrl}/fee-estimates`); - - const data = await response.json(); - - return data["5"]; // fee for 5 blocks confirmation - } catch (error) { - throw new Error("Failed to get fee estimates"); - } -} - -export async function broadcastTransaction( - blockstreamUrl: string, - txHex: string, -): Promise { - try { - const response = await fetch(`${blockstreamUrl}/tx`, { - method: "POST", - body: txHex, - headers: { - "Content-Type": "text/plain", - }, - }); - - return await response.text(); - } catch (error) { - console.log("error", error); - throw new Error("Failed to broadcast transaction"); - } -} From 5dc0062884b88ad5e9a746697608e1bb42d1142e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 31 Jul 2024 08:36:44 -0400 Subject: [PATCH 48/87] chore: changing name of the type --- packages/bitcoin/src/base-transfer.ts | 4 ++-- packages/bitcoin/src/fungible.ts | 6 +++--- packages/bitcoin/src/types.ts | 2 +- packages/bitcoin/src/utils/helpers.ts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/bitcoin/src/base-transfer.ts b/packages/bitcoin/src/base-transfer.ts index 1491eb00c..06790389e 100644 --- a/packages/bitcoin/src/base-transfer.ts +++ b/packages/bitcoin/src/base-transfer.ts @@ -1,6 +1,6 @@ import type { BitcoinResource } from '@buildwithsygma/core/src'; import type { BitcoinConfig, Config, Domain } from '@buildwithsygma/core/types'; -import type { BaseTransferParams } from 'types'; +import type { BitcoinTransferParams } from 'types'; export abstract class BaseTransfer { protected destinationAddress: string; @@ -12,7 +12,7 @@ export abstract class BaseTransfer { protected feeAmount: number; protected feeAddress: string; - constructor(transfer: BaseTransferParams, config: Config) { + constructor(transfer: BitcoinTransferParams, config: Config) { this.destinationAddress = transfer.destinationAddress; this.amount = transfer.amount; this.sourceDomain = config.getDomain(transfer.source); diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index a7a441f55..89b56b715 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -1,12 +1,12 @@ import { Config } from '@buildwithsygma/core'; import type { networks } from 'bitcoinjs-lib'; -import type { BaseTransferParams, BitcoinTransferRequest, TypeOfAddress } from 'types'; +import type { BitcoinTransferParams, BitcoinTransferRequest, TypeOfAddress } from 'types'; import { BaseTransfer } from './base-transfer.js'; import { getPsbt } from './utils/index.js'; export async function createBitcoinFungibleTransfer( - params: BaseTransferParams, + params: BitcoinTransferParams, ): Promise { const config = new Config(); await config.init(process.env.SYGMA_ENV || params.environment); @@ -21,7 +21,7 @@ class BitcoinTransfer extends BaseTransfer { protected feeRate: number; protected utxoData: { utxoTxId: string; utxoAmount: number; utxoOutputIndex: number }; - constructor(transfer: BaseTransferParams, config: Config) { + constructor(transfer: BitcoinTransferParams, config: Config) { super(transfer, config); this.destinationAddress = transfer.destinationAddress; this.amount = transfer.amount; diff --git a/packages/bitcoin/src/types.ts b/packages/bitcoin/src/types.ts index 049f664ab..4de16298f 100644 --- a/packages/bitcoin/src/types.ts +++ b/packages/bitcoin/src/types.ts @@ -6,7 +6,7 @@ export enum TypeOfAddress { P2TR = 'P2TR', } -export type BaseTransferParams = { +export type BitcoinTransferParams = { environment?: Environment; source: Domainlike; destination: Domainlike; diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts index e5bf0c6f4..eea7cc13f 100644 --- a/packages/bitcoin/src/utils/helpers.ts +++ b/packages/bitcoin/src/utils/helpers.ts @@ -3,7 +3,7 @@ import { payments, Psbt } from 'bitcoinjs-lib'; import { TypeOfAddress } from '../types.js'; import type { - BaseTransferParams, + BitcoinTransferParams, BitcoinTransferInputData, BitcoinTransferRequest, PaymentReturnData, @@ -72,7 +72,7 @@ export function createInputData({ network, typeOfAddress, }: Pick< - BaseTransferParams, + BitcoinTransferParams, 'utxoData' | 'publicKey' | 'network' | 'typeOfAddress' >): BitcoinTransferInputData { if (typeOfAddress !== TypeOfAddress.P2TR) { @@ -107,7 +107,7 @@ export function createInputData({ * @returns {BitcoinTransferRequest} */ export function getPsbt( - params: BaseTransferParams, + params: BitcoinTransferParams, feeAddress: string, depositAddress: string, feeAmount: number, From 3107ecd64c47998e78e5135fdf43fa5d6629f624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 31 Jul 2024 08:39:16 -0400 Subject: [PATCH 49/87] chore: removing build:all command --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 928a531a7..bf85c8b75 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "btc:test": "yarn workspace @buildwithsygma/bitcoin test", "btc:lint": "yarn workspace @buildwithsygma/bitcoin lint", "btc:lint:fix": "yarn workspace @buildwithsygma/bitcoin lint:fix", - "build:all": "yarn workspace @buildwithsygma/core build:all; yarn workspace @buildwithsygma/evm build:all; yarn workspace @buildwithsygma/bitcoin build:all", "clean:all": "yarn workspace @buildwithsygma/core clean; yarn workspace @buildwithsygma/evm clean", "substrate:build": "yarn workspace @buildwithsygma/substrate build:all", "substrate:cleanDist": "yarn workspace @buildwithsygma/substrate clean", From 88090bd264fb1c9b0d57089aef8c0dd56837e7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 31 Jul 2024 09:43:32 -0400 Subject: [PATCH 50/87] chore: update readme, explaining env vars, minor changes on examples scripts --- .../btc-to-evm-fungible-transfer/.env.sample | 1 - .../btc-to-evm-fungible-transfer/README.md | 59 +++++++++++++++++-- .../src/transfer.p2tr.ts | 7 +-- .../src/transfer.p2wpkh.ts | 9 +-- 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index c1a056d52..1ccacc6a4 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -1,7 +1,6 @@ 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" diff --git a/examples/btc-to-evm-fungible-transfer/README.md b/examples/btc-to-evm-fungible-transfer/README.md index 891f75daa..5db1fd012 100644 --- a/examples/btc-to-evm-fungible-transfer/README.md +++ b/examples/btc-to-evm-fungible-transfer/README.md @@ -54,14 +54,65 @@ cd examples/btc-to-evm-fungible-transfer touch .env ``` -Replace between the quotation marks your taproot address private key: +Replace the values that are defined in the `.env.sample` file: -`PRIVATE_KEY="YOUR_PRIVATE_KEY_HERE"` +```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" +UTXO_TX_ID="your utxo tx id" +UTXO_AMOUNT="your utxo amount" +UTXO_OUTPUT_INDEX="your utxo output index" +CHANGE_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 +* `UTXO_TX_ID`: transaction id of your available utxos for your address +* `UTXO_AMOUNT`: the amount available at your utxo +* `UTXO_OUTPUT_INDEX`: the vout index of your utxo +* `CHANGE_ADDRESS`: the address where you want to receive back your unspent funds +* `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 run: +To send Testnet BTC to your EVM account on Sepolia using P2WPKH address run: ```bash -yarn run transfer +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 and SOURCE_DOMAIN_ID. diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index a1c63701e..ac99d3995 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -15,7 +15,7 @@ dotenv.config(); const SOURCE_CAIPID = process.env.SOURCE_CAIPID; const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; -const DESTINATION_CHAIN_ID = Number(process.env.DESTINATION_CHAIN_ID); +const DESTINATION_CHAIN_ID = 11155111; const RESOURCE_ID = process.env.RESOURCE_ID; const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const EXPLORER_URL = process.env.EXPLORER_URL; @@ -30,7 +30,6 @@ const AMOUNT = Number(process.env.AMOUNT); if ( !SOURCE_CAIPID || !DESTINATION_ADDRESS || - !DESTINATION_CHAIN_ID || !RESOURCE_ID || !MNEMONIC || !UTXO_TX_ID || @@ -66,8 +65,8 @@ async function btcToEvmTransfer(): Promise { const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); const transferParams: BaseTransferParams = { - source: SOURCE_DOMAIN_CAIPID, - destination: DESTINATION_DOMAIN_CHAIN_ID, + source: SOURCE_CAIPID, + destination: DESTINATION_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, amount: AMOUNT, resource: RESOURCE_ID, diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 67822a858..9e9bacc3e 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -14,9 +14,7 @@ dotenv.config(); const SOURCE_CAIPID = process.env.SOURCE_CAIPID; const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; -const DESTINATION_CHAIN_ID = Number( - process.env.DESTINATION_CHAIN_ID, -); +const DESTINATION_CHAIN_ID = 11155111; const RESOURCE_ID = process.env.RESOURCE_ID; const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const EXPLORER_URL = process.env.EXPLORER_URL; @@ -31,7 +29,6 @@ const AMOUNT = Number(process.env.AMOUNT); if ( !SOURCE_CAIPID || !DESTINATION_ADDRESS || - !DESTINATION_CHAIN_ID || !RESOURCE_ID || !MNEMONIC || !UTXO_TX_ID || @@ -60,8 +57,8 @@ async function btcToEvmTransfer(): Promise { const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); const transferParams: BaseTransferParams = { - source: SOURCE_DOMAIN_CAIPID, - destination: DESTINATION_DOMAIN_CHAIN_ID, + source: SOURCE_CAIPID, + destination: DESTINATION_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, amount: AMOUNT, resource: RESOURCE_ID, From c728966fbcebaacda7766da7ee4d9284bc9ff724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 31 Jul 2024 10:30:57 -0400 Subject: [PATCH 51/87] chore: changing type from number to bigint --- packages/bitcoin/src/base-transfer.ts | 6 +++--- packages/bitcoin/src/fungible.ts | 6 +++--- packages/bitcoin/src/types.ts | 16 +++++++++------- packages/bitcoin/src/utils/helpers.ts | 14 +++++++------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/bitcoin/src/base-transfer.ts b/packages/bitcoin/src/base-transfer.ts index 06790389e..afdaa52a1 100644 --- a/packages/bitcoin/src/base-transfer.ts +++ b/packages/bitcoin/src/base-transfer.ts @@ -4,12 +4,12 @@ import type { BitcoinTransferParams } from 'types'; export abstract class BaseTransfer { protected destinationAddress: string; - protected amount: number; + protected amount: bigint; protected config: Config; protected resource: BitcoinResource; protected sourceDomain: Domain; protected destinationDomain: Domain; - protected feeAmount: number; + protected feeAmount: bigint; protected feeAddress: string; constructor(transfer: BitcoinTransferParams, config: Config) { @@ -25,7 +25,7 @@ export abstract class BaseTransfer { if (resource) { this.resource = resource; - this.feeAmount = resource.feeAmount as number; + this.feeAmount = BigInt(resource.feeAmount as number); } else { throw new Error('Resource not found.'); } diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index 89b56b715..827f71595 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -1,6 +1,6 @@ import { Config } from '@buildwithsygma/core'; import type { networks } from 'bitcoinjs-lib'; -import type { BitcoinTransferParams, BitcoinTransferRequest, TypeOfAddress } from 'types'; +import type { BitcoinTransferParams, BitcoinTransferRequest, TypeOfAddress, UTXOData } from 'types'; import { BaseTransfer } from './base-transfer.js'; import { getPsbt } from './utils/index.js'; @@ -18,8 +18,8 @@ class BitcoinTransfer extends BaseTransfer { protected typeOfAddress: TypeOfAddress; protected network: networks.Network; protected changeAddress?: string; - protected feeRate: number; - protected utxoData: { utxoTxId: string; utxoAmount: number; utxoOutputIndex: number }; + protected feeRate: bigint; + protected utxoData: UTXOData; constructor(transfer: BitcoinTransferParams, config: Config) { super(transfer, config); diff --git a/packages/bitcoin/src/types.ts b/packages/bitcoin/src/types.ts index 4de16298f..cc03b31da 100644 --- a/packages/bitcoin/src/types.ts +++ b/packages/bitcoin/src/types.ts @@ -6,22 +6,24 @@ export enum TypeOfAddress { P2TR = 'P2TR', } +export type UTXOData = { + utxoTxId: string; + utxoAmount: bigint; + utxoOutputIndex: number; +}; + export type BitcoinTransferParams = { environment?: Environment; source: Domainlike; destination: Domainlike; destinationAddress: string; - amount: number; + amount: bigint; resource: BitcoinResource | string; - utxoData: { - utxoTxId: string; - utxoAmount: number; - utxoOutputIndex: number; - }; + utxoData: UTXOData; publicKey: Buffer; typeOfAddress: TypeOfAddress; network: networks.Network; - feeRate: number; + feeRate: bigint; changeAddress?: string; }; diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts index eea7cc13f..884c9c510 100644 --- a/packages/bitcoin/src/utils/helpers.ts +++ b/packages/bitcoin/src/utils/helpers.ts @@ -81,7 +81,7 @@ export function createInputData({ index: utxoOutputIndex, witnessUtxo: { script: getScriptPubkey(typeOfAddress, publicKey, network).scriptPubKey, - value: utxoAmount, + value: Number(utxoAmount), }, }; } @@ -90,7 +90,7 @@ export function createInputData({ index: utxoOutputIndex, witnessUtxo: { script: getScriptPubkey(typeOfAddress, publicKey, network).scriptPubKey as unknown as Buffer, - value: utxoAmount, + value: Number(utxoAmount), }, tapInternalKey: publicKey, }; @@ -110,7 +110,7 @@ export function getPsbt( params: BitcoinTransferParams, feeAddress: string, depositAddress: string, - feeAmount: number, + feeAmount: bigint, ): BitcoinTransferRequest { if (!['P2WPKH', 'P2TR'].includes(params.typeOfAddress.toString())) { throw new Error('Unsuported address type'); @@ -142,9 +142,9 @@ export function getPsbt( }); const size = 303; - const minerFee = Math.floor(params.feeRate * size); + const minerFee = Math.floor(Number(params.feeRate) * size); - const amountToSpent = params.utxoData.utxoAmount - Number(feeAmount) - minerFee; + const amountToSpent = Number(params.utxoData.utxoAmount) - Number(feeAmount) - minerFee; if (amountToSpent < params.amount) { throw new Error('Not enough funds'); @@ -153,11 +153,11 @@ export function getPsbt( // Amount to bridge psbt.addOutput({ address: depositAddress, - value: params.amount, + value: Number(params.amount), }); if (params.changeAddress && amountToSpent > params.amount) { - const change = amountToSpent - params.amount; + const change = Number(amountToSpent) - Number(params.amount); psbt.addOutput({ address: params.changeAddress, From eb55fbc07110a3f71afee41e9eb05a81a8cc263f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 6 Aug 2024 00:39:10 -0400 Subject: [PATCH 52/87] chore: addressing comments + processing either one or multiple inputs + checks. Test are updated --- .../bitcoin/src/__test__/fungible.test.ts | 93 ++++++++++++++----- packages/bitcoin/src/fungible.ts | 5 +- packages/bitcoin/src/types.ts | 10 +- packages/bitcoin/src/utils/helpers.ts | 61 ++++++++++-- 4 files changed, 135 insertions(+), 34 deletions(-) diff --git a/packages/bitcoin/src/__test__/fungible.test.ts b/packages/bitcoin/src/__test__/fungible.test.ts index 05884298c..30c30ca5d 100644 --- a/packages/bitcoin/src/__test__/fungible.test.ts +++ b/packages/bitcoin/src/__test__/fungible.test.ts @@ -4,22 +4,24 @@ import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371'; import * as tinysecp from 'tiny-secp256k1'; import { createBitcoinFungibleTransfer } from '../fungible.js'; -import type { BaseTransferParams } from '../types.js'; +import type { BitcoinTransferParams } from '../types.js'; import { TypeOfAddress } from '../types.js'; bitcoin.initEccLib(tinysecp); -const P2TR_TRANSFER_PARAMS: BaseTransferParams = { +const P2TR_TRANSFER_PARAMS: BitcoinTransferParams = { source: 'bip122:000000000933ea01ad0ee984209779ba', destination: 1, destinationAddress: '0x98729c03c4D5e820F5e8c45558ae07aE63F97461', - amount: 90000000, + amount: BigInt(90000000), resource: '0x0000000000000000000000000000000000000000000000000000000000000300', - utxoData: { - utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', - utxoAmount: 100000000, - utxoOutputIndex: 0, - }, + utxoData: [ + { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + utxoAmount: BigInt(100000000), + utxoOutputIndex: 0, + }, + ], publicKey: toXOnly( Buffer.from('03feca449bd5b50085d23864a006f6ea4da80ff63816033f6437193c66bac7488c', 'hex'), ), @@ -27,10 +29,11 @@ const P2TR_TRANSFER_PARAMS: BaseTransferParams = { network: bitcoin.networks.testnet, changeAddress: 'tb1pxmrzd94rs6wtg6ewdjfmuu7s88n2kdqc20vzfmadanfaem3n9sdq0vagu0', environment: Environment.DEVNET, - feeRate: 103, + feeRate: BigInt(103), + size: BigInt(400), }; -const P2PWKH_TRANSFER_PARAMS: BaseTransferParams = { +const P2PWKH_TRANSFER_PARAMS: BitcoinTransferParams = { ...P2TR_TRANSFER_PARAMS, typeOfAddress: TypeOfAddress.P2WPKH, publicKey: Buffer.from( @@ -98,13 +101,13 @@ describe('Fungible - createBitcoinFungibleTransfer', () => { }); it('should throw if the utxo amount is equal to the amount to transfer', async () => { - const transferParams = { ...P2TR_TRANSFER_PARAMS, amount: 100000000 }; + 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: 100000001 }; + const transferParams = { ...P2TR_TRANSFER_PARAMS, amount: BigInt(100000001) }; const transfer = await createBitcoinFungibleTransfer(transferParams); expect(() => transfer.getTransferTransaction()).toThrow(); }); @@ -116,16 +119,60 @@ describe('Fungible - createBitcoinFungibleTransfer', () => { }); it('should throw if utxoData is partially defined', async () => { - const transferParams: BaseTransferParams = { + const transferParams: BitcoinTransferParams = { ...P2TR_TRANSFER_PARAMS, - utxoData: { - utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', - } as unknown as BaseTransferParams['utxoData'], + 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', () => { @@ -148,13 +195,13 @@ describe('Fungible - createBitcoinFungibleTransfer', () => { }); it('should throw if the utxo amount is equal to the amount to transfer', async () => { - const transferParams = { ...P2PWKH_TRANSFER_PARAMS, amount: 100000000 }; + 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: 100000001 }; + const transferParams = { ...P2PWKH_TRANSFER_PARAMS, amount: BigInt(100000001) }; const transfer = await createBitcoinFungibleTransfer(transferParams); expect(() => transfer.getTransferTransaction()).toThrow(); }); @@ -166,11 +213,13 @@ describe('Fungible - createBitcoinFungibleTransfer', () => { }); it('should throw if utxoData is partially defined', async () => { - const transferParams: BaseTransferParams = { + const transferParams: BitcoinTransferParams = { ...P2PWKH_TRANSFER_PARAMS, - utxoData: { - utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', - } as unknown as BaseTransferParams['utxoData'], + utxoData: [ + { + utxoTxId: 'dbcd2f7e54392fbfeca85d15ce405dfecddc65c42e6f72a1b84c79dcd2eb7e7c', + }, + ] as unknown as BitcoinTransferParams['utxoData'], }; const transfer = await createBitcoinFungibleTransfer(transferParams); diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index 827f71595..49eab4cea 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -19,7 +19,8 @@ class BitcoinTransfer extends BaseTransfer { protected network: networks.Network; protected changeAddress?: string; protected feeRate: bigint; - protected utxoData: UTXOData; + protected utxoData: UTXOData[]; + protected size: bigint; constructor(transfer: BitcoinTransferParams, config: Config) { super(transfer, config); @@ -31,6 +32,7 @@ class BitcoinTransfer extends BaseTransfer { this.changeAddress = transfer.changeAddress; this.feeRate = transfer.feeRate; this.utxoData = transfer.utxoData; + this.size = transfer.size; } getTransferTransaction(): BitcoinTransferRequest { @@ -47,6 +49,7 @@ class BitcoinTransfer extends BaseTransfer { network: this.network, feeRate: this.feeRate, changeAddress: this.changeAddress, + size: this.size, }, this.feeAddress, this.resource.address, diff --git a/packages/bitcoin/src/types.ts b/packages/bitcoin/src/types.ts index cc03b31da..4439063c0 100644 --- a/packages/bitcoin/src/types.ts +++ b/packages/bitcoin/src/types.ts @@ -12,6 +12,13 @@ export type UTXOData = { utxoOutputIndex: number; }; +export type CreateInputData = { + utxoData: UTXOData; + publicKey: Buffer; + network: networks.Network; + typeOfAddress: TypeOfAddress; +}; + export type BitcoinTransferParams = { environment?: Environment; source: Domainlike; @@ -19,12 +26,13 @@ export type BitcoinTransferParams = { destinationAddress: string; amount: bigint; resource: BitcoinResource | string; - utxoData: UTXOData; + utxoData: UTXOData[]; publicKey: Buffer; typeOfAddress: TypeOfAddress; network: networks.Network; feeRate: bigint; changeAddress?: string; + size: bigint; }; export type BitcoinTransferRequest = Psbt; diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts index 884c9c510..cc8e16e3a 100644 --- a/packages/bitcoin/src/utils/helpers.ts +++ b/packages/bitcoin/src/utils/helpers.ts @@ -7,6 +7,8 @@ import type { BitcoinTransferInputData, BitcoinTransferRequest, PaymentReturnData, + UTXOData, + CreateInputData, } from '../types.js'; /** @@ -71,10 +73,7 @@ export function createInputData({ publicKey, network, typeOfAddress, -}: Pick< - BitcoinTransferParams, - 'utxoData' | 'publicKey' | 'network' | 'typeOfAddress' ->): BitcoinTransferInputData { +}: CreateInputData): BitcoinTransferInputData { if (typeOfAddress !== TypeOfAddress.P2TR) { return { hash: utxoTxId as unknown as Buffer, @@ -96,6 +95,21 @@ export function createInputData({ }; } +/** + * Check if the amount to transfer is valid + * @category Helpers + * @param amount - amount to transfer + * @param utxoData - UTXO data + * @returns {boolean} + */ +const invalidAmount = (amount: bigint, utxoData: UTXOData[]): boolean => { + if (utxoData.length === 1) { + return amount > Number(utxoData[0].utxoAmount); + } + + 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 * @@ -116,17 +130,37 @@ export function getPsbt( throw new Error('Unsuported address type'); } - if (Object.keys(params.utxoData).length !== 3) { + if (params.utxoData.length === 0) { throw new Error('UTXO data is required'); } - if (params.amount > params.utxoData.utxoAmount) { + if (invalidAmount(params.amount, params.utxoData)) { throw new Error('Not enough funds to spend from the UTXO'); } const psbt = new Psbt({ network: params.network }); - psbt.addInput(createInputData(params)); + 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({ @@ -141,10 +175,17 @@ export function getPsbt( value: Number(feeAmount), }); - const size = 303; - const minerFee = Math.floor(Number(params.feeRate) * size); + const minerFee = Math.floor(Number(params.feeRate) * Number(params.size)); - const amountToSpent = Number(params.utxoData.utxoAmount) - Number(feeAmount) - minerFee; + 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'); From 2e7b5018dd967dd5b8ebe5a975fb223269f9dcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 7 Aug 2024 12:08:25 -0400 Subject: [PATCH 53/87] feat: calculating size on example to have reference to pass to the package for miner fee --- .../src/blockstream-api.ts | 138 +++++++++++++++++- .../src/transfer.p2tr.ts | 64 +++++--- .../src/transfer.p2wpkh.ts | 64 +++++--- 3 files changed, 223 insertions(+), 43 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts b/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts index 0bf0cbb12..ce123ec61 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts @@ -1,9 +1,28 @@ +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 Utxo = { + txid: string; + vout: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + value: number; +}; + +type FeeEstimates = Record; + export async function getFeeEstimates(blockstreamUrl: string): Promise { if (!blockstreamUrl) throw new Error("Blockstream url is required"); try { const response = await fetch(`${blockstreamUrl}/fee-estimates`); - const data = await response.json(); + const data = (await response.json()) as FeeEstimates; return data["5"]; // fee for 5 blocks confirmation } catch (error) { @@ -29,3 +48,120 @@ export async function broadcastTransaction( throw new Error("Failed to broadcast transaction"); } } + +export const fetchUTXOS = async ( + address: string, + blockstreamUrl: 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"); + } +}; + +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; +}; + +// we calculate the size of the transaction by using a tx with zero fee => input amount == output amount +export const calculateSize = ( + utxoData: BitcoinTransferParams["utxoData"][], + network: Network, + publicKey: Buffer, + depositAddress: string, + domainId: number, + amount: bigint, + feeValue: bigint, + changeAddress: string, + signer: Signer, + typeOfAddress: TypeOfAddress, +): number => { + const pstb = new Psbt({ network: network }); + + let scriptPubKey: Buffer; + if (typeOfAddress !== TypeOfAddress.P2TR) { + const { output } = payments.p2wpkh({ + pubkey: publicKey, + network: network, + }) as { output: Buffer }; + scriptPubKey = output; + } else { + const { output } = payments.p2tr({ + internalPubkey: publicKey, + network: network, + }) as { output: Buffer }; + scriptPubKey = output; + } + + if (typeOfAddress === TypeOfAddress.P2TR) { + utxoData.forEach((utxo) => { + pstb.addInput({ + hash: utxo.utxoTxId, + index: utxo.utxoOutputIndex, + witnessUtxo: { + value: Number(utxo.utxoAmount), + script: scriptPubKey, + }, + tapInternalKey: publicKey, + }); + }); + } else { + utxoData.forEach((utxo) => { + pstb.addInput({ + hash: utxo.utxoTxId, + index: utxo.utxoOutputIndex, + witnessUtxo: { + value: Number(utxo.utxoAmount), + script: scriptPubKey, + }, + }); + }); + } + + pstb.addOutput({ + script: payments.embed({ + data: [Buffer.from(`${depositAddress}_${domainId}`)], + }).output as unknown as Buffer, + value: 0, + }); + + pstb.addOutput({ + address: changeAddress, + value: Number(amount), + }); + + pstb.addOutput({ + address: changeAddress, + value: Number(feeValue), + }); + + pstb.addOutput({ + address: changeAddress, + value: 0, + }); + + pstb.signAllInputs(signer); + pstb.finalizeAllInputs(); + const vsize = pstb.extractTransaction(true).virtualSize(); + return vsize; +}; diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index ac99d3995..2fd7ba9da 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -1,15 +1,22 @@ -import type { BaseTransferParams } from "@buildwithsygma/btc"; import { createBitcoinFungibleTransfer, TypeOfAddress, -} from "@buildwithsygma/btc"; +} 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, getFeeEstimates } from "./blockstream-api"; + +import { + broadcastTransaction, + calculateSize, + fetchUTXOS, + getFeeEstimates, + processUtxos, +} from "./blockstream-api.js"; dotenv.config(); @@ -20,11 +27,8 @@ const RESOURCE_ID = process.env.RESOURCE_ID; const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const EXPLORER_URL = process.env.EXPLORER_URL; const MNEMONIC = process.env.MNEMONIC; -const UTXO_TX_ID = process.env.UTXO_TX_ID; -const UTXO_AMOUNT = Number(process.env.UTXO_AMOUNT); -const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); const DERIVATION_PATH = process.env.DERIVATION_PATH; -const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; +const ADDRESS = process.env.ADDRESS; const AMOUNT = Number(process.env.AMOUNT); if ( @@ -32,11 +36,8 @@ if ( !DESTINATION_ADDRESS || !RESOURCE_ID || !MNEMONIC || - !UTXO_TX_ID || - !UTXO_AMOUNT || - !UTXO_OUTPUT_INDEX || !DERIVATION_PATH || - !CHANGE_ADDRESS || + !ADDRESS || !BLOCKSTREAM_URL || !AMOUNT ) { @@ -45,7 +46,6 @@ if ( ); } - async function btcToEvmTransfer(): Promise { // pre setup initEccLib(tinysecp); @@ -63,23 +63,45 @@ async function btcToEvmTransfer(): Promise { ); const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); + const utxos = await fetchUTXOS( + ADDRESS as unknown as string, + BLOCKSTREAM_URL 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( + mapedUtxos, + networks.testnet, + publicKeyDropedDERHeader, + ADDRESS as unknown as string, + DESTINATION_CHAIN_ID, + BigInt(AMOUNT), + BigInt(0), + ADDRESS as unknown as string, + tweakedSigner, + TypeOfAddress.P2TR, + ); - const transferParams: BaseTransferParams = { + const transferParams: BitcoinTransferParams = { source: SOURCE_CAIPID, destination: DESTINATION_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, amount: AMOUNT, resource: RESOURCE_ID, - utxoData: { - utxoTxId: UTXO_TX_ID, - utxoOutputIndex: UTXO_OUTPUT_INDEX, - utxoAmount: UTXO_AMOUNT, - }, - feeRate, + utxoData: mapedUtxos, + feeRate: BigInt(Math.ceil(feeRate)), publicKey: publicKeyDropedDERHeader, typeOfAddress: TypeOfAddress.P2TR, network: networks.testnet, - changeAddress: CHANGE_ADDRESS, + changeAddress: ADDRESS, + size: BigInt(size), }; const transfer = await createBitcoinFungibleTransfer(transferParams); @@ -88,7 +110,7 @@ async function btcToEvmTransfer(): Promise { console.log("Signing the transaction"); - psbt.signInput(0, tweakedSigner); + psbt.signAllInputs(tweakedSigner); psbt.finalizeAllInputs(); console.log("Extracting the transaction"); diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 9e9bacc3e..1868de5de 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -1,14 +1,21 @@ -import type { BaseTransferParams } from "@buildwithsygma/btc"; +import type { BitcoinTransferParams, UTXOData } from "@buildwithsygma/bitcoin"; import { createBitcoinFungibleTransfer, TypeOfAddress, -} from "@buildwithsygma/btc"; +} 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 { broadcastTransaction, getFeeEstimates } from "./blockstream-api"; + +import { + broadcastTransaction, + calculateSize, + fetchUTXOS, + getFeeEstimates, + processUtxos, +} from "./blockstream-api.js"; dotenv.config(); @@ -19,11 +26,8 @@ const RESOURCE_ID = process.env.RESOURCE_ID; const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; const EXPLORER_URL = process.env.EXPLORER_URL; const MNEMONIC = process.env.MNEMONIC; -const UTXO_TX_ID = process.env.UTXO_TX_ID; -const UTXO_AMOUNT = Number(process.env.UTXO_AMOUNT); -const UTXO_OUTPUT_INDEX = Number(process.env.UTXO_OUTPUT_INDEX); const DERIVATION_PATH = process.env.DERIVATION_PATH; -const CHANGE_ADDRESS = process.env.CHANGE_ADDRESS; +const ADDRESS = process.env.ADDRESS; const AMOUNT = Number(process.env.AMOUNT); if ( @@ -31,11 +35,8 @@ if ( !DESTINATION_ADDRESS || !RESOURCE_ID || !MNEMONIC || - !UTXO_TX_ID || - !UTXO_AMOUNT || - !UTXO_OUTPUT_INDEX || !DERIVATION_PATH || - !CHANGE_ADDRESS || + !ADDRESS || !BLOCKSTREAM_URL || !AMOUNT ) { @@ -44,7 +45,6 @@ if ( ); } - async function btcToEvmTransfer(): Promise { // pre setup initEccLib(tinysecp); @@ -55,23 +55,45 @@ async function btcToEvmTransfer(): Promise { const derivedNode = rootKey.derivePath(DERIVATION_PATH); const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); + const utxos = await fetchUTXOS( + ADDRESS as unknown as string, + BLOCKSTREAM_URL 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( + mapedUtxos, + networks.testnet, + derivedNode.publicKey, + ADDRESS as unknown as string, + DESTINATION_CHAIN_ID, + BigInt(AMOUNT), + BigInt(0), + ADDRESS as unknown as string, + derivedNode, + TypeOfAddress.P2WPKH, + ); // aprox estimation of the size of the tx - const transferParams: BaseTransferParams = { + const transferParams: BitcoinTransferParams = { source: SOURCE_CAIPID, destination: DESTINATION_CHAIN_ID, destinationAddress: DESTINATION_ADDRESS, amount: AMOUNT, resource: RESOURCE_ID, - utxoData: { - utxoTxId: UTXO_TX_ID, - utxoOutputIndex: UTXO_OUTPUT_INDEX, - utxoAmount: UTXO_AMOUNT, - }, + utxoData: mapedUtxos, publicKey: derivedNode.publicKey, typeOfAddress: TypeOfAddress.P2WPKH, network: networks.testnet, - changeAddress: CHANGE_ADDRESS, - feeRate, + changeAddress: ADDRESS, + feeRate: BigInt(Math.ceil(feeRate)), + size: BigInt(size), }; const transfer = await createBitcoinFungibleTransfer(transferParams); @@ -80,7 +102,7 @@ async function btcToEvmTransfer(): Promise { console.log("Signing the transaction"); - psbt.signInput(0, derivedNode); + psbt.signAllInputs(derivedNode); psbt.finalizeAllInputs(); console.log("Extracting the transaction"); From 4fc94bab5f68da071e7a0de298e27ab302f0e1e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 7 Aug 2024 12:09:45 -0400 Subject: [PATCH 54/87] chore: update env sample file --- examples/btc-to-evm-fungible-transfer/.env.sample | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/btc-to-evm-fungible-transfer/.env.sample index 1ccacc6a4..a251cf896 100644 --- a/examples/btc-to-evm-fungible-transfer/.env.sample +++ b/examples/btc-to-evm-fungible-transfer/.env.sample @@ -6,8 +6,5 @@ SOURCE_CAIPID="source domain caip id" EXPLORER_URL="your bitcoin explorer url" MNEMONIC="your 12 or 24 mnemonic" DERIVATION_PATH="your derivation path" -UTXO_TX_ID="your utxo tx id" -UTXO_AMOUNT="your utxo amount" -UTXO_OUTPUT_INDEX="your utxo output index" -CHANGE_ADDRESS="your change address" +ADDRESS="your bitcoin address to use and send change" AMOUNT="your amount to transfer" \ No newline at end of file From 6e3ad03967ed4ab18adbdd40e7a1c56ec7e54df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 7 Aug 2024 12:14:08 -0400 Subject: [PATCH 55/87] chore: addressing last comment from Saad --- examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts b/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts index ce123ec61..cc488254b 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts @@ -17,6 +17,8 @@ type Utxo = { type FeeEstimates = Record; +const BLOCK_CONFIRMATION_INDEX = "5"; + export async function getFeeEstimates(blockstreamUrl: string): Promise { if (!blockstreamUrl) throw new Error("Blockstream url is required"); try { @@ -24,7 +26,7 @@ export async function getFeeEstimates(blockstreamUrl: string): Promise { const data = (await response.json()) as FeeEstimates; - return data["5"]; // fee for 5 blocks confirmation + return data[BLOCK_CONFIRMATION_INDEX]; // fee for 5 blocks confirmation } catch (error) { throw new Error("Failed to get fee estimates"); } From b57a8fb7cf356f4fda09d87df09a7fbd14943b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 7 Aug 2024 12:26:54 -0400 Subject: [PATCH 56/87] chore: update on comment + update on readme --- examples/btc-to-evm-fungible-transfer/README.md | 10 ++-------- .../src/blockstream-api.ts | 5 ++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/README.md b/examples/btc-to-evm-fungible-transfer/README.md index 5db1fd012..e0e049a16 100644 --- a/examples/btc-to-evm-fungible-transfer/README.md +++ b/examples/btc-to-evm-fungible-transfer/README.md @@ -66,10 +66,7 @@ SOURCE_CAIPID="source domain caip id" EXPLORER_URL="your bitcoin explorer url" MNEMONIC="your 12 or 24 mnemonic" DERIVATION_PATH="your derivation path" -UTXO_TX_ID="your utxo tx id" -UTXO_AMOUNT="your utxo amount" -UTXO_OUTPUT_INDEX="your utxo output index" -CHANGE_ADDRESS="your change address" +ADDRESS="your change address" AMOUNT="your amount to transfer" ``` @@ -79,10 +76,7 @@ AMOUNT="your amount to transfer" * `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 -* `UTXO_TX_ID`: transaction id of your available utxos for your address -* `UTXO_AMOUNT`: the amount available at your utxo -* `UTXO_OUTPUT_INDEX`: the vout index of your utxo -* `CHANGE_ADDRESS`: the address where you want to receive back your unspent funds +* `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: diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts b/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts index cc488254b..7e6533990 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts @@ -85,7 +85,10 @@ export const processUtxos = (utxo: Utxo[], amount: number): Utxo[] => { return dataToReturn; }; -// we calculate the size of the transaction by using a tx with zero fee => input amount == output amount +/** + * 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: BitcoinTransferParams["utxoData"][], network: Network, From 083e148fef95d5d5663c7636ae8572ff52e26895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Thu, 8 Aug 2024 09:04:36 -0400 Subject: [PATCH 57/87] chore: update lock file --- yarn.lock | 80 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 35330c1cb..86190ce72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -382,9 +382,9 @@ __metadata: languageName: node linkType: hard -"@buildwithsygma/btc@workspace:^, @buildwithsygma/btc@workspace:packages/btc": +"@buildwithsygma/bitcoin@workspace:^, @buildwithsygma/bitcoin@workspace:packages/bitcoin": version: 0.0.0-use.local - resolution: "@buildwithsygma/btc@workspace:packages/btc" + resolution: "@buildwithsygma/bitcoin@workspace:packages/bitcoin" dependencies: "@buildwithsygma/core": "workspace:^" "@types/jest": "npm:^29.4.0" @@ -576,7 +576,7 @@ __metadata: version: 0.0.0-use.local resolution: "@buildwithsygma/sygma-sdk-btc-to-evm-fungible-transfer-example@workspace:examples/btc-to-evm-fungible-transfer" dependencies: - "@buildwithsygma/btc": "workspace:^" + "@buildwithsygma/bitcoin": "workspace:^" "@buildwithsygma/core": "workspace:^" bip32: "npm:^4.0.0" bip39: "npm:^3.1.0" @@ -4070,13 +4070,6 @@ __metadata: languageName: node linkType: hard -"base58-js@npm:^1.0.0": - version: 1.0.5 - resolution: "base58-js@npm:1.0.5" - checksum: 2f8aacd9b10a82175efff6c81bf9a0d0a95e5b802ea403e855664f4bd848112a5d7b7a4e659761c8cd3bb1ffa0cb748a6f97490af52635593b9cdf7d8511ee0f - languageName: node - linkType: hard - "base-x@npm:^4.0.0": version: 4.0.0 resolution: "base-x@npm:4.0.0" @@ -4084,6 +4077,13 @@ __metadata: languageName: node linkType: hard +"base58-js@npm:^1.0.0": + version: 1.0.5 + resolution: "base58-js@npm:1.0.5" + checksum: 2f8aacd9b10a82175efff6c81bf9a0d0a95e5b802ea403e855664f4bd848112a5d7b7a4e659761c8cd3bb1ffa0cb748a6f97490af52635593b9cdf7d8511ee0f + languageName: node + linkType: hard + "base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -4119,6 +4119,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" @@ -4130,6 +4158,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" @@ -5065,6 +5107,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" @@ -11212,6 +11265,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" From 7da382e0dccb7e0f50252be812080ab9e941f059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Thu, 8 Aug 2024 23:22:19 -0400 Subject: [PATCH 58/87] chore: fix linter and using base transfer class from core + some modifications to that class --- packages/bitcoin/src/base-transfer.ts | 54 ------------------------- packages/bitcoin/src/bitcoinTransfer.ts | 21 ++++++++++ packages/bitcoin/src/fungible.ts | 12 +++--- packages/bitcoin/src/index.ts | 1 - packages/bitcoin/src/types.ts | 25 +++++++++--- packages/bitcoin/src/utils/helpers.ts | 4 +- packages/core/src/baseTransfer.ts | 22 ++++++---- packages/utils/src/liquidity.ts | 10 ++++- 8 files changed, 71 insertions(+), 78 deletions(-) delete mode 100644 packages/bitcoin/src/base-transfer.ts create mode 100644 packages/bitcoin/src/bitcoinTransfer.ts diff --git a/packages/bitcoin/src/base-transfer.ts b/packages/bitcoin/src/base-transfer.ts deleted file mode 100644 index afdaa52a1..000000000 --- a/packages/bitcoin/src/base-transfer.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { BitcoinResource } from '@buildwithsygma/core/src'; -import type { BitcoinConfig, Config, Domain } from '@buildwithsygma/core/types'; -import type { BitcoinTransferParams } from 'types'; - -export abstract class BaseTransfer { - protected destinationAddress: string; - protected amount: bigint; - protected config: Config; - protected resource: BitcoinResource; - protected sourceDomain: Domain; - protected destinationDomain: Domain; - protected feeAmount: bigint; - protected feeAddress: string; - - constructor(transfer: BitcoinTransferParams, config: Config) { - this.destinationAddress = transfer.destinationAddress; - this.amount = transfer.amount; - this.sourceDomain = config.getDomain(transfer.source); - this.destinationDomain = config.getDomain(transfer.destination); - - this.feeAddress = (this.sourceDomain as BitcoinConfig).feeAddress; - - const resources = config.getResources(this.sourceDomain) as BitcoinResource[]; - const resource = this.findResource(resources, transfer.resource); - - if (resource) { - this.resource = resource; - this.feeAmount = BigInt(resource.feeAmount as number); - } else { - throw new Error('Resource not found.'); - } - - this.config = config; - } - - private findResource( - resources: BitcoinResource[], - resourceIdentifier: string | BitcoinResource, - ): BitcoinResource | undefined { - return resources.find(res => { - return typeof resourceIdentifier === 'string' - ? res.resourceId === resourceIdentifier - : res.resourceId === resourceIdentifier.resourceId; - }); - } - - /** - * Set resource to be transferred. - * @param {BitcoinResource} resource - */ - setResource(resource: BitcoinResource): void { - this.resource = resource; - } -} diff --git a/packages/bitcoin/src/bitcoinTransfer.ts b/packages/bitcoin/src/bitcoinTransfer.ts new file mode 100644 index 000000000..d48245e3d --- /dev/null +++ b/packages/bitcoin/src/bitcoinTransfer.ts @@ -0,0 +1,21 @@ +import { BaseTransfer, type BitcoinResource } from '@buildwithsygma/core'; +import type { BitcoinConfig, Config } from '@buildwithsygma/core/types'; +import type { BitcoinTransferParams } from 'types'; + +export class BitcoinTransfer extends BaseTransfer { + protected amount: bigint; + protected feeAmount: bigint; + protected feeAddress: string; + protected destinationAddress: string; + + constructor(params: BitcoinTransferParams, config: Config) { + super(params, config); + this.destinationAddress = params.destinationAddress; + this.amount = params.amount; + this.sourceDomain = config.getDomain(params.source); + this.destinationDomain = config.getDomain(params.destination); + + this.feeAddress = (this.sourceDomain as BitcoinConfig).feeAddress; + this.feeAmount = BigInt((this.resource as BitcoinResource).feeAmount!); + } +} diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index 49eab4cea..c6ac02429 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -1,19 +1,20 @@ +import type { BitcoinResource } from '@buildwithsygma/core'; import { Config } from '@buildwithsygma/core'; +import { BitcoinTransfer } from 'bitcoinTransfer.js'; import type { networks } from 'bitcoinjs-lib'; import type { BitcoinTransferParams, BitcoinTransferRequest, TypeOfAddress, UTXOData } from 'types'; -import { BaseTransfer } from './base-transfer.js'; import { getPsbt } from './utils/index.js'; export async function createBitcoinFungibleTransfer( params: BitcoinTransferParams, -): Promise { +): Promise { const config = new Config(); await config.init(process.env.SYGMA_ENV || params.environment); - return new BitcoinTransfer(params, config); + return new BitcoinFungibleTransfer(params, config); } -class BitcoinTransfer extends BaseTransfer { +class BitcoinFungibleTransfer extends BitcoinTransfer { protected publicKey: Buffer; protected typeOfAddress: TypeOfAddress; protected network: networks.Network; @@ -21,6 +22,7 @@ class BitcoinTransfer extends BaseTransfer { protected feeRate: bigint; protected utxoData: UTXOData[]; protected size: bigint; + protected destinationAddress: string; constructor(transfer: BitcoinTransferParams, config: Config) { super(transfer, config); @@ -52,7 +54,7 @@ class BitcoinTransfer extends BaseTransfer { size: this.size, }, this.feeAddress, - this.resource.address, + (this.resource as BitcoinResource).address, this.feeAmount, ); } diff --git a/packages/bitcoin/src/index.ts b/packages/bitcoin/src/index.ts index ad5e3fe7c..d88873593 100644 --- a/packages/bitcoin/src/index.ts +++ b/packages/bitcoin/src/index.ts @@ -1,3 +1,2 @@ -export * from './base-transfer.js'; export * from './fungible.js'; export * from './types.js'; diff --git a/packages/bitcoin/src/types.ts b/packages/bitcoin/src/types.ts index 4439063c0..425f0750b 100644 --- a/packages/bitcoin/src/types.ts +++ b/packages/bitcoin/src/types.ts @@ -1,4 +1,4 @@ -import type { BitcoinResource, Domainlike, Environment } from '@buildwithsygma/core'; +import type { BaseTransferParams, Environment } from '@buildwithsygma/core'; import type { networks, Psbt } from 'bitcoinjs-lib'; export enum TypeOfAddress { @@ -19,13 +19,10 @@ export type CreateInputData = { typeOfAddress: TypeOfAddress; }; -export type BitcoinTransferParams = { +export interface BitcoinTransferParams extends BaseTransferParams { environment?: Environment; - source: Domainlike; - destination: Domainlike; destinationAddress: string; amount: bigint; - resource: BitcoinResource | string; utxoData: UTXOData[]; publicKey: Buffer; typeOfAddress: TypeOfAddress; @@ -33,7 +30,23 @@ export type BitcoinTransferParams = { 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 BitcoinTransferRequest = Psbt; diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts index cc8e16e3a..f726a204f 100644 --- a/packages/bitcoin/src/utils/helpers.ts +++ b/packages/bitcoin/src/utils/helpers.ts @@ -3,12 +3,12 @@ import { payments, Psbt } from 'bitcoinjs-lib'; import { TypeOfAddress } from '../types.js'; import type { - BitcoinTransferParams, BitcoinTransferInputData, BitcoinTransferRequest, PaymentReturnData, UTXOData, CreateInputData, + CreatePsbtParams, } from '../types.js'; /** @@ -121,7 +121,7 @@ const invalidAmount = (amount: bigint, utxoData: UTXOData[]): boolean => { * @returns {BitcoinTransferRequest} */ export function getPsbt( - params: BitcoinTransferParams, + params: CreatePsbtParams, feeAddress: string, depositAddress: string, feeAmount: bigint, diff --git a/packages/core/src/baseTransfer.ts b/packages/core/src/baseTransfer.ts index 122161cd3..7816c64fe 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 { Domainlike, EvmResource, Domain, 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; @@ -24,7 +30,7 @@ export abstract class BaseTransfer { return this.destinationDomain; } - public get resource(): EvmResource | SubstrateResource { + public get resource(): EvmResource | SubstrateResource | BitcoinResource { return this.transferResource; } @@ -33,8 +39,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 @@ -67,10 +73,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/utils/src/liquidity.ts b/packages/utils/src/liquidity.ts index 350b14036..a1fca4e36 100644 --- a/packages/utils/src/liquidity.ts +++ b/packages/utils/src/liquidity.ts @@ -1,4 +1,10 @@ -import type { EvmResource, SubstrateResource, Eip1193Provider } from '@buildwithsygma/core'; +import type { + EvmResource, + SubstrateResource, + Eip1193Provider, + EthereumConfig, + SubstrateConfig, +} from '@buildwithsygma/core'; import { Network, ResourceType } from '@buildwithsygma/core'; import type { createEvmFungibleAssetTransfer } from '@buildwithsygma/evm'; import { getEvmHandlerBalance } from '@buildwithsygma/evm'; @@ -15,7 +21,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, ); From d0cecb42778bd673bbbc2b1188800fb162a2088f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Fri, 9 Aug 2024 10:58:46 -0400 Subject: [PATCH 59/87] chore: update package.json to build with topological flag --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 624632b18..65ac6fed8 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "scripts": { "lint": "yarn workspaces foreach -A run lint", "clean": "yarn workspaces foreach -A run clean", - "build": "yarn run clean && yarn workspaces foreach -A run build:all", + "build": "yarn run clean && yarn workspaces foreach -A -t run build:all", "test": "yarn workspaces foreach -A run test:unit --ci --silent --coverage", "core:build": "yarn workspace @buildwithsygma/core build:all", "core:cleanDist": "yarn workspace @buildwithsygma/core clean", From 2a18d3d4e79822c850a26e9fe386b21b16db41c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Fri, 9 Aug 2024 11:02:28 -0400 Subject: [PATCH 60/87] chore: update lock file --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index 86190ce72..4dd92d45b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -619,6 +619,7 @@ __metadata: version: 0.0.0-use.local resolution: "@buildwithsygma/utils@workspace:packages/utils" dependencies: + "@buildwithsygma/bitcoin": "workspace:^" "@buildwithsygma/core": "workspace:^" "@buildwithsygma/evm": "workspace:^" "@buildwithsygma/substrate": "workspace:^" From 6a5b6371ccc17728127e4102f8dbeee57b4160f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Fri, 9 Aug 2024 11:29:13 -0400 Subject: [PATCH 61/87] chore: update test --- packages/bitcoin/src/__test__/fungible.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bitcoin/src/__test__/fungible.test.ts b/packages/bitcoin/src/__test__/fungible.test.ts index 30c30ca5d..6d090c564 100644 --- a/packages/bitcoin/src/__test__/fungible.test.ts +++ b/packages/bitcoin/src/__test__/fungible.test.ts @@ -10,6 +10,7 @@ import { TypeOfAddress } from '../types.js'; bitcoin.initEccLib(tinysecp); const P2TR_TRANSFER_PARAMS: BitcoinTransferParams = { + sourceAddress: 'tb1pxmrzd94rs6wtg6ewdjfmuu7s88n2kdqc20vzfmadanfaem3n9sdq0vagu0', source: 'bip122:000000000933ea01ad0ee984209779ba', destination: 1, destinationAddress: '0x98729c03c4D5e820F5e8c45558ae07aE63F97461', From e6f6700c960dace2a957f12c7e1680eb4d4b179e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Fri, 9 Aug 2024 11:39:46 -0400 Subject: [PATCH 62/87] chore: update lock file --- yarn.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 4dd92d45b..86190ce72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -619,7 +619,6 @@ __metadata: version: 0.0.0-use.local resolution: "@buildwithsygma/utils@workspace:packages/utils" dependencies: - "@buildwithsygma/bitcoin": "workspace:^" "@buildwithsygma/core": "workspace:^" "@buildwithsygma/evm": "workspace:^" "@buildwithsygma/substrate": "workspace:^" From dda238b1e2942fc7d3291fa2bb38a02219e3a5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Fri, 9 Aug 2024 11:48:06 -0400 Subject: [PATCH 63/87] chore: fixing test --- packages/bitcoin/src/fungible.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index c6ac02429..758b954b4 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -1,9 +1,9 @@ import type { BitcoinResource } from '@buildwithsygma/core'; import { Config } from '@buildwithsygma/core'; -import { BitcoinTransfer } from 'bitcoinTransfer.js'; import type { networks } from 'bitcoinjs-lib'; import type { BitcoinTransferParams, BitcoinTransferRequest, TypeOfAddress, UTXOData } from 'types'; +import { BitcoinTransfer } from './bitcoinTransfer.js'; import { getPsbt } from './utils/index.js'; export async function createBitcoinFungibleTransfer( From ccf0c7b06e0b121f66aaa74c202a2fa93a2b20a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 12 Aug 2024 16:04:22 -0400 Subject: [PATCH 64/87] chore: fixing import to validate btc address --- packages/core/src/types.ts | 2 +- packages/core/src/utils.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 495de999e..de3342c5a 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -15,7 +15,7 @@ export enum Environment { export enum Network { EVM = 'evm', SUBSTRATE = 'substrate', - BITCOIN = 'bitcoin', + BITCOIN = 'btc', } export enum SecurityModel { diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 98273e1f2..c82d6b70f 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -2,7 +2,7 @@ import * as process from 'node:process'; import { decodeAddress, encodeAddress } from '@polkadot/keyring'; import { hexToU8a, isHex } from '@polkadot/util'; -import validate, { Network as BitcoinNetwork } from 'bitcoin-address-validation'; +import { Network as BitcoinNetwork, validate } from 'bitcoin-address-validation'; import { ethers } from 'ethers'; import { ExplorerUrl, IndexerUrl } from './constants.js'; @@ -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); } From fd672f884e99e9e5ed5acaf20a21351eb7fc568d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 14 Aug 2024 09:46:04 -0400 Subject: [PATCH 65/87] chore: update on package.json file --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 65ac6fed8..6e0176a10 100644 --- a/package.json +++ b/package.json @@ -29,11 +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", - "btc:build": "yarn workspace @buildwithsygma/bitcoin build:all", - "btc:cleanDist": "yarn workspace @buildwithsygma/bitcoin clean", - "btc:test": "yarn workspace @buildwithsygma/bitcoin test", - "btc:lint": "yarn workspace @buildwithsygma/bitcoin lint", - "btc:lint:fix": "yarn workspace @buildwithsygma/bitcoin lint:fix", + "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", "clean:all": "yarn workspace @buildwithsygma/core clean; yarn workspace @buildwithsygma/evm clean", "substrate:build": "yarn workspace @buildwithsygma/substrate build:all", "substrate:cleanDist": "yarn workspace @buildwithsygma/substrate clean", From 17e69aae34b30b207c1ca1f0c13d0b2da349a1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 14 Aug 2024 09:47:40 -0400 Subject: [PATCH 66/87] chore: adding comment to config class for btc parameter --- packages/core/src/config/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index c6943b4f9..a7e505a1b 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -65,6 +65,7 @@ export class Config { name: config.name, type: config.type, parachainId: (config as SubstrateConfig).parachainId, + // used in bitcoin transfers feeAddress: (config as BitcoinConfig).feeAddress, }; } From 7f32d734510f559a9734bc2fcc45773403099b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 14 Aug 2024 09:49:39 -0400 Subject: [PATCH 67/87] chore: change on blockstream utils for example --- .../src/{blockstream-api.ts => blockstreamApi.ts} | 3 +-- examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts | 2 +- examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) rename examples/btc-to-evm-fungible-transfer/src/{blockstream-api.ts => blockstreamApi.ts} (99%) diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts similarity index 99% rename from examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts rename to examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts index 7e6533990..441abadbe 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstream-api.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts @@ -17,12 +17,11 @@ type Utxo = { type FeeEstimates = Record; -const BLOCK_CONFIRMATION_INDEX = "5"; - export async function getFeeEstimates(blockstreamUrl: string): Promise { if (!blockstreamUrl) throw new Error("Blockstream url is required"); try { const response = await fetch(`${blockstreamUrl}/fee-estimates`); + const BLOCK_CONFIRMATION_INDEX = "5"; const data = (await response.json()) as FeeEstimates; diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index 2fd7ba9da..28b124ebb 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -16,7 +16,7 @@ import { fetchUTXOS, getFeeEstimates, processUtxos, -} from "./blockstream-api.js"; +} from "./blockstreamApi.js"; dotenv.config(); diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 1868de5de..ba39448cf 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -15,7 +15,7 @@ import { fetchUTXOS, getFeeEstimates, processUtxos, -} from "./blockstream-api.js"; +} from "./blockstreamApi.js"; dotenv.config(); From 35339d52dbae8b020ea8954345092c824b4fe8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 14 Aug 2024 11:26:11 -0400 Subject: [PATCH 68/87] chore: removing unncessary class + other minor changes --- packages/bitcoin/src/bitcoinTransfer.ts | 21 --------------------- packages/bitcoin/src/fungible.ts | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 26 deletions(-) delete mode 100644 packages/bitcoin/src/bitcoinTransfer.ts diff --git a/packages/bitcoin/src/bitcoinTransfer.ts b/packages/bitcoin/src/bitcoinTransfer.ts deleted file mode 100644 index d48245e3d..000000000 --- a/packages/bitcoin/src/bitcoinTransfer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { BaseTransfer, type BitcoinResource } from '@buildwithsygma/core'; -import type { BitcoinConfig, Config } from '@buildwithsygma/core/types'; -import type { BitcoinTransferParams } from 'types'; - -export class BitcoinTransfer extends BaseTransfer { - protected amount: bigint; - protected feeAmount: bigint; - protected feeAddress: string; - protected destinationAddress: string; - - constructor(params: BitcoinTransferParams, config: Config) { - super(params, config); - this.destinationAddress = params.destinationAddress; - this.amount = params.amount; - this.sourceDomain = config.getDomain(params.source); - this.destinationDomain = config.getDomain(params.destination); - - this.feeAddress = (this.sourceDomain as BitcoinConfig).feeAddress; - this.feeAmount = BigInt((this.resource as BitcoinResource).feeAmount!); - } -} diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index 758b954b4..27080c950 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -1,9 +1,8 @@ -import type { BitcoinResource } from '@buildwithsygma/core'; -import { Config } from '@buildwithsygma/core'; +import { BaseTransfer, Config } from '@buildwithsygma/core'; +import type { Config as TConfig, BitcoinResource, Domain } from '@buildwithsygma/core/types'; import type { networks } from 'bitcoinjs-lib'; import type { BitcoinTransferParams, BitcoinTransferRequest, TypeOfAddress, UTXOData } from 'types'; -import { BitcoinTransfer } from './bitcoinTransfer.js'; import { getPsbt } from './utils/index.js'; export async function createBitcoinFungibleTransfer( @@ -14,7 +13,7 @@ export async function createBitcoinFungibleTransfer( return new BitcoinFungibleTransfer(params, config); } -class BitcoinFungibleTransfer extends BitcoinTransfer { +class BitcoinFungibleTransfer extends BaseTransfer { protected publicKey: Buffer; protected typeOfAddress: TypeOfAddress; protected network: networks.Network; @@ -23,8 +22,13 @@ class BitcoinFungibleTransfer extends BitcoinTransfer { protected utxoData: UTXOData[]; protected size: bigint; protected destinationAddress: string; + protected amount: bigint; + protected sourceDomain: Domain; + protected destinationDomain: Domain; + protected feeAddress: string; + protected feeAmount: bigint; - constructor(transfer: BitcoinTransferParams, config: Config) { + constructor(transfer: BitcoinTransferParams, config: TConfig) { super(transfer, config); this.destinationAddress = transfer.destinationAddress; this.amount = transfer.amount; @@ -35,6 +39,11 @@ class BitcoinFungibleTransfer extends BitcoinTransfer { this.feeRate = transfer.feeRate; this.utxoData = transfer.utxoData; this.size = transfer.size; + this.sourceDomain = config.getDomain(transfer.source); + this.destinationDomain = config.getDomain(transfer.destination); + + this.feeAddress = this.sourceDomain.feeAddress as string; + this.feeAmount = BigInt((this.resource as BitcoinResource).feeAmount!); } getTransferTransaction(): BitcoinTransferRequest { From 931f4e921e1e55b7c8cb126aacf032cb0706a8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 20 Aug 2024 10:49:27 -0400 Subject: [PATCH 69/87] chore: pr comments, removing clean all packages script --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 6e0176a10..20826a41f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "bitcoin:test": "yarn workspace @buildwithsygma/bitcoin test", "bitcoin:lint": "yarn workspace @buildwithsygma/bitcoin lint", "bitcoin:lint:fix": "yarn workspace @buildwithsygma/bitcoin lint:fix", - "clean:all": "yarn workspace @buildwithsygma/core clean; yarn workspace @buildwithsygma/evm clean", "substrate:build": "yarn workspace @buildwithsygma/substrate build:all", "substrate:cleanDist": "yarn workspace @buildwithsygma/substrate clean", "substrate:test": "yarn workspace @buildwithsygma/substrate test", From ea1076244c013af2f70c16ebd88ab10537d1a39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 20 Aug 2024 13:04:19 -0400 Subject: [PATCH 70/87] chore: interface for calculate size function + addressing some comments on the PR --- .../src/blockstreamApi.ts | 130 +++++++++--------- 1 file changed, 63 insertions(+), 67 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts index 441abadbe..d0fd470f5 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts @@ -3,6 +3,19 @@ import type { BitcoinTransferParams } from "@buildwithsygma/bitcoin"; import type { Network, Signer } from "bitcoinjs-lib"; import { payments, Psbt } from "bitcoinjs-lib"; +type CalculateSizeParams = { + utxoData: BitcoinTransferParams["utxoData"][]; + network: Network; + publicKey: Buffer; + depositAddress: string; + domainId: number; + amount: bigint; + feeValue: bigint; + changeAddress: string; + signer: Signer; + typeOfAddress: TypeOfAddress; +}; + type Utxo = { txid: string; vout: number; @@ -88,81 +101,64 @@ export const processUtxos = (utxo: Utxo[], amount: number): Utxo[] => { * 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: BitcoinTransferParams["utxoData"][], - network: Network, - publicKey: Buffer, - depositAddress: string, - domainId: number, - amount: bigint, - feeValue: bigint, - changeAddress: string, - signer: Signer, - typeOfAddress: TypeOfAddress, -): number => { +export const calculateSize = ({ + utxoData, + network, + publicKey, + depositAddress, + domainId, + amount, + feeValue, + changeAddress, + signer, + typeOfAddress, +}: CalculateSizeParams): number => { const pstb = new Psbt({ network: network }); - let scriptPubKey: Buffer; - if (typeOfAddress !== TypeOfAddress.P2TR) { - const { output } = payments.p2wpkh({ - pubkey: publicKey, - network: network, - }) as { output: Buffer }; - scriptPubKey = output; - } else { - const { output } = payments.p2tr({ - internalPubkey: publicKey, - network: network, - }) as { output: Buffer }; - scriptPubKey = output; - } + 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) { - utxoData.forEach((utxo) => { - pstb.addInput({ - hash: utxo.utxoTxId, - index: utxo.utxoOutputIndex, - witnessUtxo: { - value: Number(utxo.utxoAmount), - script: scriptPubKey, - }, - tapInternalKey: publicKey, - }); - }); - } else { - utxoData.forEach((utxo) => { - pstb.addInput({ - hash: utxo.utxoTxId, - index: utxo.utxoOutputIndex, - witnessUtxo: { - value: Number(utxo.utxoAmount), - script: scriptPubKey, - }, - }); - }); - } + if (typeOfAddress === TypeOfAddress.P2TR) { + (input as any).tapInternalKey = publicKey; + } - pstb.addOutput({ - script: payments.embed({ - data: [Buffer.from(`${depositAddress}_${domainId}`)], - }).output as unknown as Buffer, - value: 0, + pstb.addInput(input); }); - pstb.addOutput({ - address: changeAddress, - value: Number(amount), - }); - pstb.addOutput({ - address: changeAddress, - value: Number(feeValue), - }); + 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, + } + ]; - pstb.addOutput({ - address: changeAddress, - value: 0, - }); + outputs.forEach(output => pstb.addOutput(output)); pstb.signAllInputs(signer); pstb.finalizeAllInputs(); From a5ee7d6bc56c15a67b94b428bb5059d38fa4a8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 20 Aug 2024 13:18:27 -0400 Subject: [PATCH 71/87] chore: inline return + small changes to sample env file --- examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts | 3 +-- examples/evm-to-btc-fungible-transfer/.env.sample | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts index d0fd470f5..76fa49a89 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts @@ -162,6 +162,5 @@ export const calculateSize = ({ pstb.signAllInputs(signer); pstb.finalizeAllInputs(); - const vsize = pstb.extractTransaction(true).virtualSize(); - return vsize; + return pstb.extractTransaction(true).virtualSize(); }; diff --git a/examples/evm-to-btc-fungible-transfer/.env.sample b/examples/evm-to-btc-fungible-transfer/.env.sample index 0fbadef91..4bb371177 100644 --- a/examples/evm-to-btc-fungible-transfer/.env.sample +++ b/examples/evm-to-btc-fungible-transfer/.env.sample @@ -1,4 +1,4 @@ -SEPOLIA_RPC_URL= -BTC_DESTINATION_ADDRESS= -PRIVATE_KEY= +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 From 3a7263c7e7f16378ca804e656f9f41e8096d03e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 20 Aug 2024 13:38:14 -0400 Subject: [PATCH 72/87] chore: passing object as parameter into the calculate size function --- .../src/blockstreamApi.ts | 2 +- .../src/transfer.p2tr.ts | 24 ++++++++--------- .../src/transfer.p2wpkh.ts | 26 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts index 76fa49a89..95f3ef9cd 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts @@ -4,7 +4,7 @@ import type { Network, Signer } from "bitcoinjs-lib"; import { payments, Psbt } from "bitcoinjs-lib"; type CalculateSizeParams = { - utxoData: BitcoinTransferParams["utxoData"][]; + utxoData: BitcoinTransferParams["utxoData"]; network: Network; publicKey: Buffer; depositAddress: string; diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index 28b124ebb..eb8fd5f0f 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -76,18 +76,18 @@ async function btcToEvmTransfer(): Promise { utxoAmount: BigInt(utxo.value), })) as unknown as UTXOData[]; - const size = calculateSize( - mapedUtxos, - networks.testnet, - publicKeyDropedDERHeader, - ADDRESS as unknown as string, - DESTINATION_CHAIN_ID, - BigInt(AMOUNT), - BigInt(0), - ADDRESS as unknown as string, - tweakedSigner, - TypeOfAddress.P2TR, - ); + const size = calculateSize({ + utxoData: mapedUtxos, + network: networks.testnet, + publicKey: publicKeyDropedDERHeader, + depositAddress: ADDRESS as unknown as string, + domainId: DESTINATION_CHAIN_ID, + amount: BigInt(AMOUNT), + feeValue: BigInt(0), + changeAddress: ADDRESS as unknown as string, + signer: tweakedSigner, + typeOfAddress: TypeOfAddress.P2TR, + }); const transferParams: BitcoinTransferParams = { source: SOURCE_CAIPID, diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index ba39448cf..0ef094914 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -68,18 +68,18 @@ async function btcToEvmTransfer(): Promise { utxoAmount: BigInt(utxo.value), })) as unknown as UTXOData[]; - const size = calculateSize( - mapedUtxos, - networks.testnet, - derivedNode.publicKey, - ADDRESS as unknown as string, - DESTINATION_CHAIN_ID, - BigInt(AMOUNT), - BigInt(0), - ADDRESS as unknown as string, - derivedNode, - TypeOfAddress.P2WPKH, - ); // aprox estimation of the size of the tx + const size = calculateSize({ + utxoData: mapedUtxos, + network: networks.testnet, + publicKey: derivedNode.publicKey, + depositAddress: ADDRESS as unknown as string, + domainId: DESTINATION_CHAIN_ID, + amount: BigInt(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, @@ -113,4 +113,4 @@ async function btcToEvmTransfer(): Promise { console.log("Transaction broadcasted", `${EXPLORER_URL}/tx/${txId}`); } -btcToEvmTransfer().finally(() => {}); +btcToEvmTransfer().finally(() => { }); From 6e869cc9729c6190ebdda9ea4588f7fa4a5c2e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 21 Aug 2024 09:43:14 -0400 Subject: [PATCH 73/87] chore: pr comments and test update --- packages/bitcoin/src/__test__/fungible.test.ts | 4 +++- packages/bitcoin/src/utils/helpers.ts | 4 ---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/bitcoin/src/__test__/fungible.test.ts b/packages/bitcoin/src/__test__/fungible.test.ts index 6d090c564..de78ba01c 100644 --- a/packages/bitcoin/src/__test__/fungible.test.ts +++ b/packages/bitcoin/src/__test__/fungible.test.ts @@ -204,7 +204,9 @@ describe('Fungible - createBitcoinFungibleTransfer', () => { 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(); + expect(() => transfer.getTransferTransaction()).toThrow( + 'Not enough funds to spend from the UTXO', + ); }); it('should throw if public key is incorrect', async () => { diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts index f726a204f..af6793d23 100644 --- a/packages/bitcoin/src/utils/helpers.ts +++ b/packages/bitcoin/src/utils/helpers.ts @@ -103,10 +103,6 @@ export function createInputData({ * @returns {boolean} */ const invalidAmount = (amount: bigint, utxoData: UTXOData[]): boolean => { - if (utxoData.length === 1) { - return amount > Number(utxoData[0].utxoAmount); - } - return utxoData.reduce((acc, curr) => acc + curr.utxoAmount, BigInt(0)) < amount; }; From f2a173617fdb668793a6fb23bda10a5eb8ecac10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 21 Aug 2024 10:03:39 -0400 Subject: [PATCH 74/87] chore: ternary operator --- packages/bitcoin/src/utils/helpers.ts | 31 ++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts index af6793d23..b96b8c7a2 100644 --- a/packages/bitcoin/src/utils/helpers.ts +++ b/packages/bitcoin/src/utils/helpers.ts @@ -27,21 +27,18 @@ export function getScriptPubkey( ): { scriptPubKey: Buffer; } { - if (typeOfAddress === TypeOfAddress.P2WPKH) { - const { output } = payments.p2wpkh({ - pubkey: publicKey, - network, - }) as PaymentReturnData; - - return { scriptPubKey: output }; - } else { - const { output } = payments.p2tr({ - internalPubkey: publicKey, - network, - }) as PaymentReturnData; - - return { scriptPubKey: output }; - } + const { output } = + typeOfAddress === TypeOfAddress.P2WPKH + ? (payments.p2wpkh({ + pubkey: publicKey, + network, + }) as PaymentReturnData) + : (payments.p2tr({ + internalPubkey: publicKey, + network, + }) as PaymentReturnData); + + return { scriptPubKey: output }; } /** @@ -102,7 +99,7 @@ export function createInputData({ * @param utxoData - UTXO data * @returns {boolean} */ -const invalidAmount = (amount: bigint, utxoData: UTXOData[]): boolean => { +const isValidAmount = (amount: bigint, utxoData: UTXOData[]): boolean => { return utxoData.reduce((acc, curr) => acc + curr.utxoAmount, BigInt(0)) < amount; }; @@ -130,7 +127,7 @@ export function getPsbt( throw new Error('UTXO data is required'); } - if (invalidAmount(params.amount, params.utxoData)) { + if (isValidAmount(params.amount, params.utxoData)) { throw new Error('Not enough funds to spend from the UTXO'); } From fb71f2b993e5c0e914f2252d8ff5e7c8386b93f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 21 Aug 2024 10:05:10 -0400 Subject: [PATCH 75/87] chore: moving var again to the top --- examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts index 95f3ef9cd..5c87fae34 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts @@ -30,11 +30,12 @@ type Utxo = { type FeeEstimates = Record; +const BLOCK_CONFIRMATION_INDEX = "5"; + export async function getFeeEstimates(blockstreamUrl: string): Promise { if (!blockstreamUrl) throw new Error("Blockstream url is required"); try { const response = await fetch(`${blockstreamUrl}/fee-estimates`); - const BLOCK_CONFIRMATION_INDEX = "5"; const data = (await response.json()) as FeeEstimates; From 2497a1a518d1e19fc59296e7be3e5f7a88d96433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 21 Aug 2024 16:18:26 -0400 Subject: [PATCH 76/87] chore: homologating tsconfig with the other ones in the package and other changes --- packages/bitcoin/src/fungible.ts | 7 +++++- packages/bitcoin/tsconfig.json | 37 +++------------------------- packages/bitcoin/tsconfig.types.json | 18 ++++++-------- 3 files changed, 17 insertions(+), 45 deletions(-) diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index 27080c950..8daa84840 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -1,8 +1,13 @@ import { BaseTransfer, Config } from '@buildwithsygma/core'; import type { Config as TConfig, BitcoinResource, Domain } from '@buildwithsygma/core/types'; import type { networks } from 'bitcoinjs-lib'; -import type { BitcoinTransferParams, BitcoinTransferRequest, TypeOfAddress, UTXOData } from 'types'; +import type { + BitcoinTransferParams, + BitcoinTransferRequest, + TypeOfAddress, + UTXOData, +} from './types.js'; import { getPsbt } from './utils/index.js'; export async function createBitcoinFungibleTransfer( diff --git a/packages/bitcoin/tsconfig.json b/packages/bitcoin/tsconfig.json index 1b9a286c6..cf1659b0d 100644 --- a/packages/bitcoin/tsconfig.json +++ b/packages/bitcoin/tsconfig.json @@ -1,34 +1,5 @@ { - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "module": "ES2022", - "declaration": true, - "declarationMap": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "importHelpers": true, - "moduleResolution": "node", - "noEmitOnError": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "strict": true, - "sourceMap": true, - "target": "es2020", - "skipLibCheck": true, - "baseUrl": "./src", - "resolveJsonModule": true - }, - "exclude": ["node_modules/**"], - "include": ["./src/**/*.ts", "./test/**/*.ts", "substrate-asset-transfer.ts"], - "ts-node": { - "esm": true, - "experimentalSpecifierResolution": "node" - } - } - \ No newline at end of file + "extends": "../../tsconfig.base.json", + "exclude": ["node_modules/**"], + "include": ["./src/**/*.ts"] +} diff --git a/packages/bitcoin/tsconfig.types.json b/packages/bitcoin/tsconfig.types.json index 6ee41752a..cc2946320 100644 --- a/packages/bitcoin/tsconfig.types.json +++ b/packages/bitcoin/tsconfig.types.json @@ -1,12 +1,8 @@ { - "compilerOptions": { - "declaration": true, - "emitDeclarationOnly": true, - "outDir": "types" - }, - "extends": "./tsconfig.json", - "exclude": [ - "test", - "src/**/__test__/**", - ] -} \ No newline at end of file + "compilerOptions": { + "emitDeclarationOnly": true, + "outDir": "types" + }, + "extends": "./tsconfig.json", + "exclude": ["test", "src/**/__test__/**"] +} From 659b43c0ac1409b90ca348d47122f31f42b83fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 21 Aug 2024 23:53:40 -0400 Subject: [PATCH 77/87] feat: moving some functions to the utils package and make use of them in the btc to evm example --- .../btc-to-evm-fungible-transfer/package.json | 1 + .../src/blockstreamApi.ts | 82 -------------- .../src/transfer.p2tr.ts | 15 +-- .../src/transfer.p2wpkh.ts | 14 +-- packages/utils/src/bitcoin/blockstream.ts | 102 ++++++++++++++++++ packages/utils/src/index.ts | 1 + packages/utils/src/liquidity.ts | 2 +- yarn.lock | 3 +- 8 files changed, 116 insertions(+), 104 deletions(-) create mode 100644 packages/utils/src/bitcoin/blockstream.ts diff --git a/examples/btc-to-evm-fungible-transfer/package.json b/examples/btc-to-evm-fungible-transfer/package.json index c873a2ce0..ea880f212 100644 --- a/examples/btc-to-evm-fungible-transfer/package.json +++ b/examples/btc-to-evm-fungible-transfer/package.json @@ -31,6 +31,7 @@ "dependencies": { "@buildwithsygma/bitcoin": "workspace:^", "@buildwithsygma/core": "workspace:^", + "@buildwithsygma/utils": "workspace:^", "bip32": "^4.0.0", "bip39": "^3.1.0", "bitcoinjs-lib": "^6.1.6", diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts index 5c87fae34..6fc26d1e7 100644 --- a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts +++ b/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts @@ -16,88 +16,6 @@ type CalculateSizeParams = { typeOfAddress: TypeOfAddress; }; -type Utxo = { - txid: string; - vout: number; - status: { - confirmed: boolean; - block_height: number; - block_hash: string; - block_time: number; - }; - value: number; -}; - -type FeeEstimates = Record; - -const BLOCK_CONFIRMATION_INDEX = "5"; - -export async function getFeeEstimates(blockstreamUrl: string): Promise { - if (!blockstreamUrl) throw new Error("Blockstream url is required"); - try { - const response = await fetch(`${blockstreamUrl}/fee-estimates`); - - const data = (await response.json()) as FeeEstimates; - - return data[BLOCK_CONFIRMATION_INDEX]; // fee for 5 blocks confirmation - } catch (error) { - throw new Error("Failed to get fee estimates"); - } -} - -export async function broadcastTransaction( - blockstreamUrl: string, - 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"); - } -} - -export const fetchUTXOS = async ( - address: string, - blockstreamUrl: 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"); - } -}; - -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; -}; - /** * 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 diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts index eb8fd5f0f..9f6e93c98 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -9,13 +9,10 @@ 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 { - broadcastTransaction, calculateSize, - fetchUTXOS, - getFeeEstimates, - processUtxos, } from "./blockstreamApi.js"; dotenv.config(); @@ -62,11 +59,9 @@ async function btcToEvmTransfer(): Promise { crypto.taggedHash("TapTweak", publicKeyDropedDERHeader), ); - const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); + const feeRate = await getFeeEstimates('5'); const utxos = await fetchUTXOS( - ADDRESS as unknown as string, - BLOCKSTREAM_URL as unknown as string, - ); + ADDRESS as unknown as string); const processedUtxos = processUtxos(utxos, AMOUNT); @@ -117,8 +112,8 @@ async function btcToEvmTransfer(): Promise { const tx = psbt.extractTransaction(true); console.log("Transaction hex", tx.toHex()); - const txId = await broadcastTransaction(BLOCKSTREAM_URL, tx.toHex()); + const txId = await broadcastTransaction(tx.toHex()); console.log("Transaction broadcasted", `${EXPLORER_URL}/tx/${txId}`); } -btcToEvmTransfer().finally(() => {}); +btcToEvmTransfer().finally(() => { }); diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 0ef094914..ebb3e7aa5 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -10,12 +10,9 @@ import dotenv from "dotenv"; import * as tinysecp from "tiny-secp256k1"; import { - broadcastTransaction, calculateSize, - fetchUTXOS, - getFeeEstimates, - processUtxos, } from "./blockstreamApi.js"; +import { broadcastTransaction, fetchUTXOS, getFeeEstimates, processUtxos } from "@buildwithsygma/utils"; dotenv.config(); @@ -54,11 +51,8 @@ async function btcToEvmTransfer(): Promise { const rootKey = bip32.fromSeed(seed, networks.testnet); const derivedNode = rootKey.derivePath(DERIVATION_PATH); - const feeRate = await getFeeEstimates(BLOCKSTREAM_URL); - const utxos = await fetchUTXOS( - ADDRESS as unknown as string, - BLOCKSTREAM_URL as unknown as string, - ); + const feeRate = await getFeeEstimates('5'); + const utxos = await fetchUTXOS(ADDRESS as unknown as string); const processedUtxos = processUtxos(utxos, AMOUNT); @@ -109,7 +103,7 @@ async function btcToEvmTransfer(): Promise { const tx = psbt.extractTransaction(true); console.log("Transaction hex", tx.toHex()); - const txId = await broadcastTransaction(BLOCKSTREAM_URL, tx.toHex()); + const txId = await broadcastTransaction(tx.toHex()); console.log("Transaction broadcasted", `${EXPLORER_URL}/tx/${txId}`); } 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/index.ts b/packages/utils/src/index.ts index 53a0dceb9..60647b248 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1 +1,2 @@ export * from './liquidity.js'; +export * from './bitcoin/blockstream.js'; diff --git a/packages/utils/src/liquidity.ts b/packages/utils/src/liquidity.ts index 77063df09..1d74fa121 100644 --- a/packages/utils/src/liquidity.ts +++ b/packages/utils/src/liquidity.ts @@ -9,7 +9,7 @@ import { Network, ResourceType } from '@buildwithsygma/core'; import type { createEvmFungibleAssetTransfer } from '@buildwithsygma/evm'; import { getEvmHandlerBalance } from '@buildwithsygma/evm'; import type { createSubstrateFungibleAssetTransfer } from '@buildwithsygma/substrate/src'; -import { HttpProvider } from 'web3-providers-http'; +import HttpProvider from 'web3-providers-http'; import { getSubstrateHandlerBalance } from './substrate/balances.js'; diff --git a/yarn.lock b/yarn.lock index e705a42dd..a72e9f60d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -611,6 +611,7 @@ __metadata: 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" @@ -648,7 +649,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: From 6b4ce47f4d928d49f4e5bc6a56680efbaf1344f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 21 Aug 2024 23:59:12 -0400 Subject: [PATCH 78/87] chore: release workflow file + manifest config files --- .github/workflows/release-bitcoin.yml | 62 +++++++++++++++++++++++++ release-please/rp-bitcoin-config.json | 16 +++++++ release-please/rp-bitcoin-manifest.json | 1 + 3 files changed, 79 insertions(+) create mode 100644 .github/workflows/release-bitcoin.yml 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/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 From b5793e57e325ee6a0a291fffdf6589482ccb9de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 26 Aug 2024 23:22:36 -0400 Subject: [PATCH 79/87] chore: renamed examples and addressing some comments from Saad --- .../.env.sample | 0 .../README.md | 6 ++--- .../package.json | 2 +- .../src/blockstreamApi.ts | 0 .../src/transfer.p2tr.ts | 23 +++++++++++-------- .../src/transfer.p2wpkh.ts | 22 ++++++++++-------- .../tsconfig.json | 0 .../.env.sample | 0 .../README.md | 2 +- .../package.json | 2 +- .../src/transfer.ts | 0 .../tsconfig.json | 0 packages/bitcoin/package.json | 2 +- packages/bitcoin/src/fungible.ts | 8 +++---- packages/bitcoin/src/types.ts | 5 ++-- packages/bitcoin/src/utils/helpers.ts | 4 ++-- packages/utils/src/bitcoin/index.ts | 1 + packages/utils/src/index.ts | 2 +- 18 files changed, 42 insertions(+), 37 deletions(-) rename examples/{btc-to-evm-fungible-transfer => bitcoin-to-evm-fungible-transfer}/.env.sample (100%) rename examples/{btc-to-evm-fungible-transfer => bitcoin-to-evm-fungible-transfer}/README.md (93%) rename examples/{btc-to-evm-fungible-transfer => bitcoin-to-evm-fungible-transfer}/package.json (92%) rename examples/{btc-to-evm-fungible-transfer => bitcoin-to-evm-fungible-transfer}/src/blockstreamApi.ts (100%) rename examples/{btc-to-evm-fungible-transfer => bitcoin-to-evm-fungible-transfer}/src/transfer.p2tr.ts (86%) rename examples/{btc-to-evm-fungible-transfer => bitcoin-to-evm-fungible-transfer}/src/transfer.p2wpkh.ts (85%) rename examples/{btc-to-evm-fungible-transfer => bitcoin-to-evm-fungible-transfer}/tsconfig.json (100%) rename examples/{evm-to-btc-fungible-transfer => evm-to-bitcoin-fungible-transfer}/.env.sample (100%) rename examples/{evm-to-btc-fungible-transfer => evm-to-bitcoin-fungible-transfer}/README.md (92%) rename examples/{evm-to-btc-fungible-transfer => evm-to-bitcoin-fungible-transfer}/package.json (90%) rename examples/{evm-to-btc-fungible-transfer => evm-to-bitcoin-fungible-transfer}/src/transfer.ts (100%) rename examples/{evm-to-btc-fungible-transfer => evm-to-bitcoin-fungible-transfer}/tsconfig.json (100%) create mode 100644 packages/utils/src/bitcoin/index.ts diff --git a/examples/btc-to-evm-fungible-transfer/.env.sample b/examples/bitcoin-to-evm-fungible-transfer/.env.sample similarity index 100% rename from examples/btc-to-evm-fungible-transfer/.env.sample rename to examples/bitcoin-to-evm-fungible-transfer/.env.sample diff --git a/examples/btc-to-evm-fungible-transfer/README.md b/examples/bitcoin-to-evm-fungible-transfer/README.md similarity index 93% rename from examples/btc-to-evm-fungible-transfer/README.md rename to examples/bitcoin-to-evm-fungible-transfer/README.md index e0e049a16..99908b10e 100644 --- a/examples/btc-to-evm-fungible-transfer/README.md +++ b/examples/bitcoin-to-evm-fungible-transfer/README.md @@ -1,8 +1,8 @@ -# Sygma SDK BTC to EVM example +# Sygma SDK Bitcoin to EVM example ## Sygma SDK ERC20 Example -This is an example script that demonstrates the functionality of the SDK using the Sygma ecosystem. The script showcases a asset transfer between a BTC testnet account and a EVM Sepolia account. +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 @@ -109,7 +109,7 @@ To send Testnet BTC to your EVM account on Sepolia using P2WPKH address run: 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 and SOURCE_DOMAIN_ID. +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 diff --git a/examples/btc-to-evm-fungible-transfer/package.json b/examples/bitcoin-to-evm-fungible-transfer/package.json similarity index 92% rename from examples/btc-to-evm-fungible-transfer/package.json rename to examples/bitcoin-to-evm-fungible-transfer/package.json index ea880f212..70cec83b2 100644 --- a/examples/btc-to-evm-fungible-transfer/package.json +++ b/examples/bitcoin-to-evm-fungible-transfer/package.json @@ -1,5 +1,5 @@ { - "name": "@buildwithsygma/sygma-sdk-btc-to-evm-fungible-transfer-example", + "name": "@buildwithsygma/sygma-sdk-bitcoin-to-evm-fungible-transfer-example", "version": "0.1.0", "type": "module", "description": "Sygma sdk examples", diff --git a/examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts b/examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts similarity index 100% rename from examples/btc-to-evm-fungible-transfer/src/blockstreamApi.ts rename to examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts similarity index 86% rename from examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts rename to examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts index 9f6e93c98..3ca92d02b 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -17,16 +17,19 @@ import { dotenv.config(); -const SOURCE_CAIPID = process.env.SOURCE_CAIPID; -const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; const DESTINATION_CHAIN_ID = 11155111; -const RESOURCE_ID = process.env.RESOURCE_ID; -const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; -const EXPLORER_URL = process.env.EXPLORER_URL; -const MNEMONIC = process.env.MNEMONIC; -const DERIVATION_PATH = process.env.DERIVATION_PATH; -const ADDRESS = process.env.ADDRESS; -const AMOUNT = Number(process.env.AMOUNT); +const { + SOURCE_CAIPID, + DESTINATION_ADDRESS, + RESOURCE_ID, + BLOCKSTREAM_URL, + EXPLORER_URL, + MNEMONIC, + DERIVATION_PATH, + ADDRESS, + AMOUNT, +} = process.env; + if ( !SOURCE_CAIPID || @@ -39,7 +42,7 @@ if ( !AMOUNT ) { throw new Error( - "Please provided needed env variables needed into the .env file", + "Missing required environment variables, please make sure .env file exists." ); } diff --git a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts similarity index 85% rename from examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts rename to examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index ebb3e7aa5..4f5f46f09 100644 --- a/examples/btc-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -16,16 +16,18 @@ import { broadcastTransaction, fetchUTXOS, getFeeEstimates, processUtxos } from dotenv.config(); -const SOURCE_CAIPID = process.env.SOURCE_CAIPID; -const DESTINATION_ADDRESS = process.env.DESTINATION_ADDRESS; const DESTINATION_CHAIN_ID = 11155111; -const RESOURCE_ID = process.env.RESOURCE_ID; -const BLOCKSTREAM_URL = process.env.BLOCKSTREAM_URL; -const EXPLORER_URL = process.env.EXPLORER_URL; -const MNEMONIC = process.env.MNEMONIC; -const DERIVATION_PATH = process.env.DERIVATION_PATH; -const ADDRESS = process.env.ADDRESS; -const AMOUNT = Number(process.env.AMOUNT); +const { + SOURCE_CAIPID, + DESTINATION_ADDRESS, + RESOURCE_ID, + BLOCKSTREAM_URL, + EXPLORER_URL, + MNEMONIC, + DERIVATION_PATH, + ADDRESS, + AMOUNT, +} = process.env; if ( !SOURCE_CAIPID || @@ -38,7 +40,7 @@ if ( !AMOUNT ) { throw new Error( - "Please provided needed env variables needed into the .env file", + "Missing required environment variables, please make sure .env file exists." ); } diff --git a/examples/btc-to-evm-fungible-transfer/tsconfig.json b/examples/bitcoin-to-evm-fungible-transfer/tsconfig.json similarity index 100% rename from examples/btc-to-evm-fungible-transfer/tsconfig.json rename to examples/bitcoin-to-evm-fungible-transfer/tsconfig.json diff --git a/examples/evm-to-btc-fungible-transfer/.env.sample b/examples/evm-to-bitcoin-fungible-transfer/.env.sample similarity index 100% rename from examples/evm-to-btc-fungible-transfer/.env.sample rename to examples/evm-to-bitcoin-fungible-transfer/.env.sample diff --git a/examples/evm-to-btc-fungible-transfer/README.md b/examples/evm-to-bitcoin-fungible-transfer/README.md similarity index 92% rename from examples/evm-to-btc-fungible-transfer/README.md rename to examples/evm-to-bitcoin-fungible-transfer/README.md index c8f7a621c..bc19ff05b 100644 --- a/examples/evm-to-btc-fungible-transfer/README.md +++ b/examples/evm-to-bitcoin-fungible-transfer/README.md @@ -1,6 +1,6 @@ # Sygma SDK EVM to BTC example -This is an example script that demonstrates the functionality of the SDK using the Sygma ecosystem. The script showcases a token transfer between the provided account and a BTC testnet account. +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 diff --git a/examples/evm-to-btc-fungible-transfer/package.json b/examples/evm-to-bitcoin-fungible-transfer/package.json similarity index 90% rename from examples/evm-to-btc-fungible-transfer/package.json rename to examples/evm-to-bitcoin-fungible-transfer/package.json index aef7d4fd6..e52dbefec 100644 --- a/examples/evm-to-btc-fungible-transfer/package.json +++ b/examples/evm-to-bitcoin-fungible-transfer/package.json @@ -1,5 +1,5 @@ { - "name": "@buildwithsygma/sygma-sdk-evm-to-btc-fungible-transfer", + "name": "@buildwithsygma/sygma-sdk-evm-to-bitcoin-fungible-transfer", "version": "0.1.0", "type": "module", "description": "Sygma sdk examples", diff --git a/examples/evm-to-btc-fungible-transfer/src/transfer.ts b/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts similarity index 100% rename from examples/evm-to-btc-fungible-transfer/src/transfer.ts rename to examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts diff --git a/examples/evm-to-btc-fungible-transfer/tsconfig.json b/examples/evm-to-bitcoin-fungible-transfer/tsconfig.json similarity index 100% rename from examples/evm-to-btc-fungible-transfer/tsconfig.json rename to examples/evm-to-bitcoin-fungible-transfer/tsconfig.json diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json index 6a328ed1a..a27faccbc 100644 --- a/packages/bitcoin/package.json +++ b/packages/bitcoin/package.json @@ -1,6 +1,6 @@ { "name": "@buildwithsygma/bitcoin", - "version": "0.0.1", + "version": "1.0.0", "description": "Core primitives for bridging and message passing", "main": "dist-esm/index.js", "types": "types/index.d.ts", diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index 8daa84840..8ac55975c 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -1,10 +1,10 @@ -import { BaseTransfer, Config } from '@buildwithsygma/core'; +import { BaseTransfer, Config, Environment } from '@buildwithsygma/core'; import type { Config as TConfig, BitcoinResource, Domain } from '@buildwithsygma/core/types'; import type { networks } from 'bitcoinjs-lib'; import type { BitcoinTransferParams, - BitcoinTransferRequest, + BitcoinTransaction, TypeOfAddress, UTXOData, } from './types.js'; @@ -14,7 +14,7 @@ export async function createBitcoinFungibleTransfer( params: BitcoinTransferParams, ): Promise { const config = new Config(); - await config.init(process.env.SYGMA_ENV || params.environment); + await config.init(process.env.SYGMA_ENV || Environment.MAINNET); return new BitcoinFungibleTransfer(params, config); } @@ -51,7 +51,7 @@ class BitcoinFungibleTransfer extends BaseTransfer { this.feeAmount = BigInt((this.resource as BitcoinResource).feeAmount!); } - getTransferTransaction(): BitcoinTransferRequest { + getTransferTransaction(): BitcoinTransaction { return getPsbt( { source: this.sourceDomain.caipId, diff --git a/packages/bitcoin/src/types.ts b/packages/bitcoin/src/types.ts index 425f0750b..a54db36e3 100644 --- a/packages/bitcoin/src/types.ts +++ b/packages/bitcoin/src/types.ts @@ -1,4 +1,4 @@ -import type { BaseTransferParams, Environment } from '@buildwithsygma/core'; +import type { BaseTransferParams } from '@buildwithsygma/core'; import type { networks, Psbt } from 'bitcoinjs-lib'; export enum TypeOfAddress { @@ -20,7 +20,6 @@ export type CreateInputData = { }; export interface BitcoinTransferParams extends BaseTransferParams { - environment?: Environment; destinationAddress: string; amount: bigint; utxoData: UTXOData[]; @@ -48,7 +47,7 @@ export type CreatePsbtParams = Pick< | 'size' >; -export type BitcoinTransferRequest = Psbt; +export type BitcoinTransaction = Psbt; export type PaymentReturnData = { output: Buffer; diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts index b96b8c7a2..24d17fdeb 100644 --- a/packages/bitcoin/src/utils/helpers.ts +++ b/packages/bitcoin/src/utils/helpers.ts @@ -4,7 +4,7 @@ import { payments, Psbt } from 'bitcoinjs-lib'; import { TypeOfAddress } from '../types.js'; import type { BitcoinTransferInputData, - BitcoinTransferRequest, + BitcoinTransaction, PaymentReturnData, UTXOData, CreateInputData, @@ -118,7 +118,7 @@ export function getPsbt( feeAddress: string, depositAddress: string, feeAmount: bigint, -): BitcoinTransferRequest { +): BitcoinTransaction { if (!['P2WPKH', 'P2TR'].includes(params.typeOfAddress.toString())) { throw new Error('Unsuported address type'); } 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 60647b248..c2a99f19b 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,2 +1,2 @@ export * from './liquidity.js'; -export * from './bitcoin/blockstream.js'; +export * from './bitcoin/index.js'; From ccb94919653a7e1c45a781ed80d57a9c7fbe0ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Mon, 26 Aug 2024 23:41:51 -0400 Subject: [PATCH 80/87] chore: update lock file --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index a72e9f60d..607b3df8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -605,9 +605,9 @@ __metadata: languageName: node linkType: hard -"@buildwithsygma/sygma-sdk-btc-to-evm-fungible-transfer-example@workspace:examples/btc-to-evm-fungible-transfer": +"@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-btc-to-evm-fungible-transfer-example@workspace:examples/btc-to-evm-fungible-transfer" + resolution: "@buildwithsygma/sygma-sdk-bitcoin-to-evm-fungible-transfer-example@workspace:examples/bitcoin-to-evm-fungible-transfer" dependencies: "@buildwithsygma/bitcoin": "workspace:^" "@buildwithsygma/core": "workspace:^" @@ -625,9 +625,9 @@ __metadata: languageName: unknown linkType: soft -"@buildwithsygma/sygma-sdk-evm-to-btc-fungible-transfer@workspace:examples/evm-to-btc-fungible-transfer": +"@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-btc-fungible-transfer@workspace:examples/evm-to-btc-fungible-transfer" + resolution: "@buildwithsygma/sygma-sdk-evm-to-bitcoin-fungible-transfer@workspace:examples/evm-to-bitcoin-fungible-transfer" dependencies: "@buildwithsygma/core": "workspace:^" "@buildwithsygma/evm": "workspace:^" From 2b2ef9e806e6b6953b76f67580bcb28dbdca8883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 27 Aug 2024 09:56:19 -0400 Subject: [PATCH 81/87] chore: more pr comments + readme + fixing test --- .../src/blockstreamApi.ts | 4 +- .../src/transfer.p2tr.ts | 1 - packages/bitcoin/README.md | 139 +++++++++++++++++- .../bitcoin/src/__test__/fungible.test.ts | 3 +- 4 files changed, 141 insertions(+), 6 deletions(-) diff --git a/examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts b/examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts index 6fc26d1e7..a9f3f1628 100644 --- a/examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts +++ b/examples/bitcoin-to-evm-fungible-transfer/src/blockstreamApi.ts @@ -3,7 +3,7 @@ import type { BitcoinTransferParams } from "@buildwithsygma/bitcoin"; import type { Network, Signer } from "bitcoinjs-lib"; import { payments, Psbt } from "bitcoinjs-lib"; -type CalculateSizeParams = { +type SizeCalculationParams = { utxoData: BitcoinTransferParams["utxoData"]; network: Network; publicKey: Buffer; @@ -31,7 +31,7 @@ export const calculateSize = ({ changeAddress, signer, typeOfAddress, -}: CalculateSizeParams): number => { +}: SizeCalculationParams): number => { const pstb = new Psbt({ network: network }); const scriptPubKey: Buffer = (typeOfAddress !== TypeOfAddress.P2TR) diff --git a/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts index 3ca92d02b..004895da5 100644 --- a/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -55,7 +55,6 @@ async function btcToEvmTransfer(): Promise { const rootKey = bip32.fromSeed(seed, networks.testnet); const derivedNode = rootKey.derivePath(DERIVATION_PATH); - // Note: default example is going to run P2TR transfer const publicKeyDropedDERHeader = toXOnly(derivedNode.publicKey); const tweakedSigner = derivedNode.tweak( diff --git a/packages/bitcoin/README.md b/packages/bitcoin/README.md index 642773809..2cd20cd9a 100644 --- a/packages/bitcoin/README.md +++ b/packages/bitcoin/README.md @@ -1 +1,138 @@ -# btc +## 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/src/__test__/fungible.test.ts b/packages/bitcoin/src/__test__/fungible.test.ts index de78ba01c..75f2fcd51 100644 --- a/packages/bitcoin/src/__test__/fungible.test.ts +++ b/packages/bitcoin/src/__test__/fungible.test.ts @@ -1,4 +1,4 @@ -import { Config, Environment } from '@buildwithsygma/core'; +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'; @@ -29,7 +29,6 @@ const P2TR_TRANSFER_PARAMS: BitcoinTransferParams = { typeOfAddress: TypeOfAddress.P2TR, network: bitcoin.networks.testnet, changeAddress: 'tb1pxmrzd94rs6wtg6ewdjfmuu7s88n2kdqc20vzfmadanfaem3n9sdq0vagu0', - environment: Environment.DEVNET, feeRate: BigInt(103), size: BigInt(400), }; From db0e63f1c8337f8e322599f62e3f725b6ceb253b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 27 Aug 2024 10:31:05 -0400 Subject: [PATCH 82/87] chore: small adjustments to the example --- .../bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts | 2 +- .../bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts | 2 +- packages/bitcoin/src/fungible.ts | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts index 004895da5..e18ee0b6e 100644 --- a/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts +++ b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2tr.ts @@ -79,7 +79,7 @@ async function btcToEvmTransfer(): Promise { publicKey: publicKeyDropedDERHeader, depositAddress: ADDRESS as unknown as string, domainId: DESTINATION_CHAIN_ID, - amount: BigInt(AMOUNT), + amount: AMOUNT, feeValue: BigInt(0), changeAddress: ADDRESS as unknown as string, signer: tweakedSigner, diff --git a/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts index 4f5f46f09..c0e29d7fd 100644 --- a/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts +++ b/examples/bitcoin-to-evm-fungible-transfer/src/transfer.p2wpkh.ts @@ -70,7 +70,7 @@ async function btcToEvmTransfer(): Promise { publicKey: derivedNode.publicKey, depositAddress: ADDRESS as unknown as string, domainId: DESTINATION_CHAIN_ID, - amount: BigInt(AMOUNT), + amount: AMOUNT, feeValue: BigInt(0), changeAddress: ADDRESS as unknown as string, signer: derivedNode, diff --git a/packages/bitcoin/src/fungible.ts b/packages/bitcoin/src/fungible.ts index 8ac55975c..3fef457a8 100644 --- a/packages/bitcoin/src/fungible.ts +++ b/packages/bitcoin/src/fungible.ts @@ -1,5 +1,5 @@ import { BaseTransfer, Config, Environment } from '@buildwithsygma/core'; -import type { Config as TConfig, BitcoinResource, Domain } from '@buildwithsygma/core/types'; +import type { Config as TConfig, BitcoinResource } from '@buildwithsygma/core/types'; import type { networks } from 'bitcoinjs-lib'; import type { @@ -28,8 +28,6 @@ class BitcoinFungibleTransfer extends BaseTransfer { protected size: bigint; protected destinationAddress: string; protected amount: bigint; - protected sourceDomain: Domain; - protected destinationDomain: Domain; protected feeAddress: string; protected feeAmount: bigint; @@ -44,8 +42,6 @@ class BitcoinFungibleTransfer extends BaseTransfer { this.feeRate = transfer.feeRate; this.utxoData = transfer.utxoData; this.size = transfer.size; - this.sourceDomain = config.getDomain(transfer.source); - this.destinationDomain = config.getDomain(transfer.destination); this.feeAddress = this.sourceDomain.feeAddress as string; this.feeAmount = BigInt((this.resource as BitcoinResource).feeAmount!); From b7ab0f0e904baf91e103c387b47e366e0e00be07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 27 Aug 2024 10:35:39 -0400 Subject: [PATCH 83/87] chore: addressing last pr comment --- packages/bitcoin/src/utils/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bitcoin/src/utils/helpers.ts b/packages/bitcoin/src/utils/helpers.ts index 24d17fdeb..8373ac1ee 100644 --- a/packages/bitcoin/src/utils/helpers.ts +++ b/packages/bitcoin/src/utils/helpers.ts @@ -100,7 +100,7 @@ export function createInputData({ * @returns {boolean} */ const isValidAmount = (amount: bigint, utxoData: UTXOData[]): boolean => { - return utxoData.reduce((acc, curr) => acc + curr.utxoAmount, BigInt(0)) < amount; + return utxoData.reduce((acc, curr) => acc + curr.utxoAmount, BigInt(0)) <= amount; }; /** From 9c213349e8e2d1f8f902fc766428bc523ba59940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Tue, 27 Aug 2024 10:52:41 -0400 Subject: [PATCH 84/87] chore: fixing issue with test --- packages/utils/src/liquidity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/liquidity.ts b/packages/utils/src/liquidity.ts index 1d74fa121..77063df09 100644 --- a/packages/utils/src/liquidity.ts +++ b/packages/utils/src/liquidity.ts @@ -9,7 +9,7 @@ import { Network, ResourceType } from '@buildwithsygma/core'; import type { createEvmFungibleAssetTransfer } from '@buildwithsygma/evm'; import { getEvmHandlerBalance } from '@buildwithsygma/evm'; import type { createSubstrateFungibleAssetTransfer } from '@buildwithsygma/substrate/src'; -import HttpProvider from 'web3-providers-http'; +import { HttpProvider } from 'web3-providers-http'; import { getSubstrateHandlerBalance } from './substrate/balances.js'; From 040fc3c71e935ef721a1755b644608718e569a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 4 Sep 2024 08:33:08 -0400 Subject: [PATCH 85/87] chore: last pr comments --- .../src/transfer.ts | 4 +--- packages/evm/src/utils/helpers.ts | 15 ++++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts b/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts index 1e01f094b..68ed66976 100644 --- a/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts +++ b/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts @@ -1,4 +1,3 @@ -import { Environment, } from "@buildwithsygma/core"; import { createEvmFungibleAssetTransfer } from "@buildwithsygma/evm"; import dotenv from "dotenv"; import { Wallet, providers } from "ethers"; @@ -14,7 +13,7 @@ if (!privateKey) { const SEPOLIA_CHAIN_ID = 11155111; const BITCOIN_DOMAIN_CAIPID = "bip122:000000000933ea01ad0ee984209779ba"; -const RESOURCE_ID = "0x0000000000000000000000000000000000000000000000000000000000000300"; +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; @@ -33,7 +32,6 @@ export async function erc20Transfer(): Promise { resource: RESOURCE_ID, amount: BigInt(1) * BigInt(1e8), // or any amount to send destinationAddress: BTC_DESTINATION_ADDRESS, - environment: Environment.DEVNET, sourceAddress: await wallet.getAddress(), }; diff --git a/packages/evm/src/utils/helpers.ts b/packages/evm/src/utils/helpers.ts index 8a655e83d..5e0830552 100644 --- a/packages/evm/src/utils/helpers.ts +++ b/packages/evm/src/utils/helpers.ts @@ -28,8 +28,8 @@ export const createERCDepositData = ( } else if (parachainId) { recipientAddressInBytes = getSubstrateRecipientAddressInBytes(recipientAddress, parachainId); } else { - const hexAddress = addressToHex(recipientAddress, recipientAddress.length); - recipientAddressInBytes = utils.arrayify(`0x${hexAddress}`); + const hexAddress = addressToHex(recipientAddress); + recipientAddressInBytes = utils.arrayify(`${hexAddress}`); } const depositDataBytes = constructMainDepositData( @@ -198,17 +198,10 @@ export function serializeGenericCallParameters( * * @category Helpers * @param address - bitcoin address - * @param addressLength - length of the address * @returns {string} */ -export const addressToHex = (address: string, addressLength: number): string => { - const hexData = new Array(addressLength); - for (let i = 0; i < hexData.length; i++) { - const codePoint = address.charCodeAt(i); - hexData[i] = codePoint.toString(16); - } - - return hexData.join(''); +export const addressToHex = (address: string): string => { + return utils.hexlify(utils.toUtf8Bytes(address)); }; /** From f87fdde9e86f8e118db1f859648cccfc4d1bcc94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Wed, 4 Sep 2024 10:33:54 -0400 Subject: [PATCH 86/87] chore; fix tests --- packages/evm/src/utils/__test__/helpers.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/evm/src/utils/__test__/helpers.test.ts b/packages/evm/src/utils/__test__/helpers.test.ts index efdc15771..aa24a1460 100644 --- a/packages/evm/src/utils/__test__/helpers.test.ts +++ b/packages/evm/src/utils/__test__/helpers.test.ts @@ -148,18 +148,18 @@ describe('addressToHex', () => { test('should convert p2tr address to hex', () => { const address = 'tb1pnyh5nayrmwux72guec3xy7qryjjww6tu9mev3d5347lqcwgus4jsd95d2r'; const expectedHex = - '746231706e7968356e6179726d777578373267756563337879377172796a6a7777367475396d65763364353334376c716377677573346a73643935643272'; + '0x746231706e7968356e6179726d777578373267756563337879377172796a6a7777367475396d65763364353334376c716377677573346a73643935643272'; - const result = addressToHex(address, address.length); + const result = addressToHex(address); expect(result).toEqual(expectedHex); }); test('should convert p2wpkh address to hex', () => { const address = 'tb1qsfyzl92pv7wkyaj0tfjdtwvcsj840p004jglvp'; const expectedHex = - '746231717366797a6c3932707637776b79616a3074666a6474777663736a383430703030346a676c7670'; + '0x746231717366797a6c3932707637776b79616a3074666a6474777663736a383430703030346a676c7670'; - const result = addressToHex(address, address.length); + const result = addressToHex(address); expect(result).toEqual(expectedHex); }); }); From dfbbbf0bcf62e71f1387820a04bba06670d2e5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Riquelme=20Guzm=C3=A1n?= Date: Thu, 5 Sep 2024 10:58:17 -0400 Subject: [PATCH 87/87] chore: last comments --- examples/bitcoin-to-evm-fungible-transfer/README.md | 2 +- examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/bitcoin-to-evm-fungible-transfer/README.md b/examples/bitcoin-to-evm-fungible-transfer/README.md index 99908b10e..2dc96dabd 100644 --- a/examples/bitcoin-to-evm-fungible-transfer/README.md +++ b/examples/bitcoin-to-evm-fungible-transfer/README.md @@ -91,7 +91,7 @@ Take into consideration that a typical response when query the utxos of your add "block_hash": "", "block_time": 1721666904 }, - "value": 936396` + "value": 936396 } ``` diff --git a/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts b/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts index 68ed66976..d525b620d 100644 --- a/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts +++ b/examples/evm-to-bitcoin-fungible-transfer/src/transfer.ts @@ -23,7 +23,7 @@ const getTxExplorerUrl = (params: { txHash: string; chainId: number }): string = 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 wallet = new Wallet(privateKey!, ethersWeb3Provider); const params = { source: SEPOLIA_CHAIN_ID,