diff --git a/.changeset/new-seas-vanish.md b/.changeset/new-seas-vanish.md new file mode 100644 index 00000000..378a76db --- /dev/null +++ b/.changeset/new-seas-vanish.md @@ -0,0 +1,5 @@ +--- +"@caravan/descriptors": patch +--- + +Add new package for encoding and decoding descriptors using bdk with wasm bindings. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc52b428..ea3b7076 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: node-version: ["20.x"] - # To use Turborepo Remote Caching + # To use Turborepo Remote Caching, set the following environment variables for the job. env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} @@ -22,8 +22,16 @@ jobs: with: node-version: 20 + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.4.0 + with: + # Optional version of wasm-pack to install(eg. 'v0.9.1', 'latest') + version: 'latest' + - name: Install Dependencies run: npm install + - name: Run tests - run: npm run ci + run: | + npx turbo run ci diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8da8f287..efe6bcfa 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,6 +26,12 @@ jobs: with: node-version: 20 + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.4.0 + with: + # Optional version of wasm-pack to install(eg. 'v0.9.1', 'latest') + version: 'latest' + - name: Install dependencies run: npm install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e8aa1cd..042889b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,6 +38,12 @@ jobs: env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.4.0 + with: + # Optional version of wasm-pack to install(eg. 'v0.9.1', 'latest') + version: 'latest' + - name: Install Dependencies run: npm install diff --git a/.gitignore b/.gitignore index 79016c75..db779605 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,8 @@ yarn-error.log* .DS_Store *.pem +**/target +.rustup +.cargo + .vscode diff --git a/apps/coordinator/src/components/Wallet/index.jsx b/apps/coordinator/src/components/Wallet/index.jsx index 612ce7ab..3e45dd5f 100644 --- a/apps/coordinator/src/components/Wallet/index.jsx +++ b/apps/coordinator/src/components/Wallet/index.jsx @@ -356,12 +356,7 @@ class CreateWallet extends React.Component { type="file" /> - @@ -542,7 +537,9 @@ class CreateWallet extends React.Component { - {this.renderWalletImporter()} + + {this.renderWalletImporter()} + {this.renderExtendedPublicKeyImporters()} diff --git a/package-lock.json b/package-lock.json index a24bffb4..73a849b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2684,6 +2684,10 @@ "resolved": "apps/coordinator", "link": true }, + "node_modules/@caravan/descriptors": { + "resolved": "packages/caravan-descriptors", + "link": true + }, "node_modules/@caravan/eslint-config": { "resolved": "packages/eslint-config", "link": true @@ -14021,6 +14025,15 @@ "node": ">= 0.4" } }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -20744,6 +20757,18 @@ "node": ">=8.10.0" } }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -21521,6 +21546,39 @@ "node": ">=8" } }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -26304,6 +26362,19 @@ "webidl-conversions": "^4.0.2" } }, + "packages/caravan-descriptors": { + "name": "@caravan/descriptors", + "version": "1.0.0", + "dependencies": { + "@caravan/bitcoin": "*" + }, + "devDependencies": { + "shx": "^0.3.4" + }, + "engines": { + "node": ">=20" + } + }, "packages/caravan-psbt": { "name": "@caravan/psbt", "version": "0.0.0-beta", diff --git a/packages/caravan-bitcoin/package.json b/packages/caravan-bitcoin/package.json index 98e65483..77bf2f66 100644 --- a/packages/caravan-bitcoin/package.json +++ b/packages/caravan-bitcoin/package.json @@ -61,6 +61,8 @@ "scripts": { "build": "tsup src/index.ts --format cjs,esm --dts", "dev": "npm run build -- --watch", + "lint": "eslint src/ --ext .ts", + "ci": "npm run test", "test": "jest src", "test:watch": "jest --watch src" }, diff --git a/packages/caravan-clients/package.json b/packages/caravan-clients/package.json index 3ccaa977..9dd33283 100644 --- a/packages/caravan-clients/package.json +++ b/packages/caravan-clients/package.json @@ -13,6 +13,8 @@ "scripts": { "build": "tsup src/index.ts --format cjs,esm --dts", "dev": "npm run build -- --watch", + "lint": "eslint src", + "ci": "npm run lint && npm run test", "test": "jest src", "test:watch": "jest --watch src" }, diff --git a/packages/caravan-clients/src/client.test.ts b/packages/caravan-clients/src/client.test.ts index 4753aad8..fd6ae338 100644 --- a/packages/caravan-clients/src/client.test.ts +++ b/packages/caravan-clients/src/client.test.ts @@ -1,5 +1,5 @@ import { Network, satoshisToBitcoins } from "@caravan/bitcoin"; -import { BlockchainClient, ClientType, ClientBase } from "./client"; +import { BlockchainClient, ClientType, ClientBase, UTXO } from "./client"; import * as bitcoind from "./bitcoind"; import BigNumber from "bignumber.js"; @@ -481,9 +481,9 @@ describe("BlockchainClient", () => { .mockResolvedValue(mockTransactionResult); // Mock the response from the Get method - const mockUtxos = [ - { txid: "txid1", vout: 0, value: 100, status: "confirmed" }, - { txid: "txid2", vout: 1, value: 200, status: "confirmed" }, + const mockUtxos: UTXO[] = [ + { txid: "txid1", vout: 0, value: 100, status: {confirmed: true, block_time: 21} }, + { txid: "txid2", vout: 1, value: 200, status: {confirmed: true, block_time: 42} }, ]; const mockGet = jest.fn().mockResolvedValue(mockUtxos); @@ -504,8 +504,8 @@ describe("BlockchainClient", () => { // Verify the returned result expect(result.utxos).toEqual( await Promise.all( - mockUtxos.map((utxo: any) => blockchainClient.formatUtxo(utxo)), - ), + mockUtxos.map((utxo: UTXO) => blockchainClient.formatUtxo(utxo)) + ) ); expect(result.balanceSats).toEqual(new BigNumber(300)); expect(result.addressKnown).toBe(true); diff --git a/packages/caravan-clients/src/client.ts b/packages/caravan-clients/src/client.ts index c3eeb5c6..ada5db83 100644 --- a/packages/caravan-clients/src/client.ts +++ b/packages/caravan-clients/src/client.ts @@ -15,7 +15,7 @@ import { } from "./bitcoind"; import BigNumber from "bignumber.js"; -interface UTXO { +export interface UTXO { txid: string; vout: number; value: number; diff --git a/packages/caravan-descriptors/.eslintrc.js b/packages/caravan-descriptors/.eslintrc.js new file mode 100644 index 00000000..6c828954 --- /dev/null +++ b/packages/caravan-descriptors/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + root: true, + extends: [ + "@caravan/eslint-config/library.js" + ], + parser: "@typescript-eslint/parser", + parserOptions: { + project: true, + }, +}; diff --git a/packages/caravan-descriptors/.prettierrc b/packages/caravan-descriptors/.prettierrc new file mode 100644 index 00000000..222861c3 --- /dev/null +++ b/packages/caravan-descriptors/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/packages/caravan-descriptors/README.md b/packages/caravan-descriptors/README.md new file mode 100644 index 00000000..8930c837 --- /dev/null +++ b/packages/caravan-descriptors/README.md @@ -0,0 +1,58 @@ +# Caravan Descriptors + +## Installation + +1. clone +2. install [rust toolchain](https://www.rust-lang.org/tools/install) and `cargo install wasm-pack` +3. npm install in the main directory +4. cd to the caravan-rs directory and `wasm-pack build -t nodejs` to build the `pkg/` directory (not clear yet if nodejs is the right target or how to make it more flexible) + 1. You might need to install llvm/clang + 2. Will also need to setup paths to build libsecp + +``` +export PATH="/opt/homebrew/opt/llvm/bin:$PATH" +# for older homebrew installs +# export PATH="/usr/local/opt/llvm/bin:$PATH" +export CC=/opt/homebrew/opt/llvm/bin/clang +export AR=/opt/homebrew/opt/llvm/bin/llvm-ar +``` + +## Usage +You can use npm scripts from the main directory to do all building + +```shell +$ npm run build +``` +This will cd into the rust directory, build packages for web and node +targets, and then build the artifacts for the js library to be packaged +and used. + +```shell +$ npm run test +``` +This will run the TypeScript tests only. + + +### Web +You'll need to make sure that the web environment this is used in +supports wasm. For example, if you're using in a vite.js project +you'll need the `vite-plugin-wasm` plugin. + +Also note that all functions exported are async and need to be awaited +since they will load up the wasm modules to be used (this way consumers +of the library don't have to worry about loading up the modules themselves) + + +## API +NOTE: This is subject to change as this is still very much alpha + +The two main functions available for import are: + +### encodeDescriptors +Takes a config for a multisig wallet and encodes it into +the two corresponding descriptors + +### decodeDescriptors +Take two descriptors and convert them into a multisig wallet +config object. This will make it possible to determine and parse the wallet type +(e.g. P2SH) and the key origins. diff --git a/packages/caravan-descriptors/caravan-rs/Cargo.lock b/packages/caravan-descriptors/caravan-rs/Cargo.lock new file mode 100644 index 00000000..70b076c8 --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/Cargo.lock @@ -0,0 +1,1367 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "bdk" +version = "1.0.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c748f5f3167395cdad782428e9ccb496deca0ba2405f2aff49bda50ee3065fc0" +dependencies = [ + "bdk_chain", + "bitcoin", + "getrandom", + "js-sys", + "log", + "miniscript", + "rand", + "serde", + "serde_json", +] + +[[package]] +name = "bdk_chain" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaba1449489201f5e6d4d3905b7e02aad6270b9215665c649c4a4dcee5bde4e0" +dependencies = [ + "bitcoin", + "miniscript", + "serde", +] + +[[package]] +name = "bdk_esplora" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ee3e68680372e8a44544426fa9fad7a817ce158ce06fb6127400c0c39b426b" +dependencies = [ + "async-trait", + "bdk_chain", + "esplora-client", + "futures", +] + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bitcoin" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" +dependencies = [ + "base64 0.13.1", + "bech32", + "bitcoin_hashes", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "caravan-rs" +version = "0.1.0" +dependencies = [ + "assert-json-diff", + "bdk", + "bdk_chain", + "bdk_esplora", + "console_error_panic_hook", + "getrandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "esplora-client" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e11244e7fd8b0beee0a3c62137c4bd9f756fe2c492ccf93171f81467b59200" +dependencies = [ + "bitcoin", + "log", + "reqwest", + "serde", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + +[[package]] +name = "log" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniscript" +version = "9.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5b106477a0709e2da253e5559ba4ab20a272f8577f1eefff72f3a905b5d35f5" +dependencies = [ + "bitcoin", + "serde", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl" +version = "0.10.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "reqwest" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +dependencies = [ + "base64 0.21.4", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tokio-socks", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "secp256k1" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +dependencies = [ + "bitcoin_hashes", + "rand", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2 0.5.4", + "windows-sys", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys", +] diff --git a/packages/caravan-descriptors/caravan-rs/Cargo.toml b/packages/caravan-descriptors/caravan-rs/Cargo.toml new file mode 100644 index 00000000..d58a77fb --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "caravan-rs" +version = "0.1.0" +authors = ["Steve Myers "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +wasm-bindgen = "0.2.84" +serde = { version = "^1.0", features = ["derive"] } +serde_json = { version = "^1.0" } + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } + +# Bitcoin Dev Kit +bdk = { version = "1.0.0-alpha.1", default-features = false } +getrandom = { version = "0.2", features = ["js"] } +bdk_chain = "0.5.0" +bdk_esplora = { version = "0.3.0", default-features = false, features = ["async-https"] } + +[dev-dependencies] +wasm-bindgen-test = "0.3.34" +assert-json-diff = "2.0" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" diff --git a/packages/caravan-descriptors/caravan-rs/LICENSE_APACHE b/packages/caravan-descriptors/caravan-rs/LICENSE_APACHE new file mode 100644 index 00000000..11069edd --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/LICENSE_APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/caravan-descriptors/caravan-rs/LICENSE_MIT b/packages/caravan-descriptors/caravan-rs/LICENSE_MIT new file mode 100644 index 00000000..bd83e368 --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/LICENSE_MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 Steve Myers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/packages/caravan-descriptors/caravan-rs/README.md b/packages/caravan-descriptors/caravan-rs/README.md new file mode 100644 index 00000000..51713be1 --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/README.md @@ -0,0 +1,86 @@ +dependency to build wasm module to use in javascript + +
+ +

wasm-pack-template

+ + A template for kick starting a Rust and WebAssembly project using wasm-pack. + +

+ Build Status +

+ +

+ Tutorial + | + Chat +

+ + Built with 🦀🕸 by The Rust and WebAssembly Working Group +
+ +## About + +[**📚 Read this template tutorial! 📚**][template-docs] + +This template is designed for compiling Rust libraries into WebAssembly and +publishing the resulting package to NPM. + +Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other +templates and usages of `wasm-pack`. + +[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html +[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html + +## 🚴 Usage + +### 🐑 Use `cargo generate` to Clone this Template + +[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) + +``` +cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project +cd my-project +``` + +### 🛠️ Build with `wasm-pack build` + +``` +wasm-pack build +``` + +### 🔬 Test in Headless Browsers with `wasm-pack test` + +``` +wasm-pack test --headless --firefox +``` + +### 🎁 Publish to NPM with `wasm-pack publish` + +``` +wasm-pack publish +``` + +## 🔋 Batteries Included + +* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating + between WebAssembly and JavaScript. +* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) + for logging panic messages to the developer console. +* `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/packages/caravan-descriptors/caravan-rs/src/config.rs b/packages/caravan-descriptors/caravan-rs/src/config.rs new file mode 100644 index 00000000..53cef482 --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/src/config.rs @@ -0,0 +1,1176 @@ +//! Caravan config +//! +//! This modules implements the config format used by Unchained Capital's +//! [Caravan](https://github.com/unchained-capital/caravan) bitcoin multisig coordinator software. +//! +//! ## Examples +//! +//! ### Export descriptors from a caravan config +//! +//! ```rust +//! # use std::str::FromStr; +//! # use bdk::bitcoin::Network; +//! # use bdk::Error::Descriptor; +//! # use bdk::KeychainKind; +//! # use crate::caravan_rs::config::{CaravanConfig, Error}; +//! +//! let config_json = r#"{ +//! "name": "P2WSH-T", +//! "addressType": "P2WSH", +//! "network": "testnet", +//! "client": { +//! "type": "public" +//! }, +//! "quorum": { +//! "requiredSigners": 2, +//! "totalSigners": 2 +//! }, +//! "extendedPublicKeys": [ +//! { +//! "name": "osw", +//! "bip32Path": "m/48'/1'/100'/2'", +//! "xpub": "tpubDFc9Mm4tw6EkgR4YTC1GrU6CGEd9yw7KSBnSssL4LXAXh89D4uMZigRyv3csdXbeU3BhLQc4vWKTLewboA1Pt8Fu6fbHKu81MZ6VGdc32eM", +//! "xfp" : "f57ec65d" +//! }, +//! { +//! "name": "d", +//! "bip32Path": "m/48'/1'/100'/2'", +//! "xpub": "tpubDErWN5qfdLwYE94mh12oWr4uURDDNKCjKVhCEcAgZ7jKnnAwq5tcTF2iEk3VuznkJuk2G8SCHft9gS6aKbBd18ptYWPqKLRSTRQY7e2rrDj", +//! "xfp" : "efa5d916" +//! } +//! ], +//! "startingAddressIndex": 0 +//! }"#; +//! +//! let config = CaravanConfig::from_str(config_json)?; +//! +//! let external_descriptor = config.descriptor(KeychainKind::External)?; +//! let internal_descriptor = config.descriptor(KeychainKind::Internal)?; +//! let network = config.network(); +//! +//! # Ok::<_, Error>(()) +//! ``` +//! +//! ### Import external and internal descriptors into a caravan config +//! ```rust +//! # use std::str::FromStr; +//! # use bdk::bitcoin::Network; +//! # use bdk::descriptor::ExtendedDescriptor; +//! # use caravan_rs::config::{CaravanConfig, Error}; +//! +//! let external_descriptor = "wsh(sortedmulti(2,[f57ec65d/48'/1'/100'/2']tpubDFc9Mm4tw6EkgR4YTC1GrU6CGEd9yw7KSBnSssL4LXAXh89D4uMZigRyv3csdXbeU3BhLQc4vWKTLewboA1Pt8Fu6fbHKu81MZ6VGdc32eM/0/*,[efa5d916/48'/1'/100'/2']tpubDErWN5qfdLwYE94mh12oWr4uURDDNKCjKVhCEcAgZ7jKnnAwq5tcTF2iEk3VuznkJuk2G8SCHft9gS6aKbBd18ptYWPqKLRSTRQY7e2rrDj/0/*))#nv5k65uf"; +//! let external_descriptor = ExtendedDescriptor::from_str(external_descriptor).expect("external descriptor"); +//! +//! let internal_descriptor = "wsh(sortedmulti(2,[f57ec65d/48'/1'/100'/2']tpubDFc9Mm4tw6EkgR4YTC1GrU6CGEd9yw7KSBnSssL4LXAXh89D4uMZigRyv3csdXbeU3BhLQc4vWKTLewboA1Pt8Fu6fbHKu81MZ6VGdc32eM/1/*,[efa5d916/48'/1'/100'/2']tpubDErWN5qfdLwYE94mh12oWr4uURDDNKCjKVhCEcAgZ7jKnnAwq5tcTF2iEk3VuznkJuk2G8SCHft9gS6aKbBd18ptYWPqKLRSTRQY7e2rrDj/1/*))"; +//! let internal_descriptor = ExtendedDescriptor::from_str(internal_descriptor).expect("internal descriptor"); +//! +//! let name = "P2WSH-T".to_string(); +//! let client = "public".to_string(); +//! let network = Network::Testnet; +//! +//! +//! let import = CaravanConfig::new(network, external_descriptor, internal_descriptor, name, client)?; +//! +//! println!("Imported: {}", import.to_string()); +//! # Ok::<_, Error>(()) +//! ``` + +use core::fmt; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +use bdk::bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint}; +use bdk::bitcoin::Network; +use bdk::miniscript::ScriptContext; + +use bdk::descriptor::{DescriptorError, DescriptorPublicKey, ExtendedDescriptor, Legacy, Segwitv0}; +use bdk::keys::{DerivableKey, DescriptorKey, SortedMultiVec}; +use bdk::miniscript::descriptor::{ShInner, WshInner}; +use bdk::miniscript::MiniscriptKey; +use bdk::{descriptor, KeychainKind}; + +/// Structure that contains a Caravan wallet configuration. +/// +/// For a usage example see [this module](crate::config)'s documentation. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CaravanConfig { + /// Vault name + pub name: String, + /// Caravan address type, + pub address_type: CaravanAddressType, + /// Caravan network + network: CaravanNetwork, + /// Caravan client + pub client: CaravanClient, + /// Signing quorum + pub quorum: Quorum, + /// Extended public keys + pub extended_public_keys: Vec, + /// Starting address index, always 0 when exporting + pub starting_address_index: u32, +} + +#[derive(Debug)] +pub enum Error { + Descriptor(DescriptorError), + InvalidNetwork { requested: Network, found: Network }, + Generic(String), + Json(serde_json::Error), +} + +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Descriptor(e) => write!(f, "Descriptor error. {}", e), + Error::InvalidNetwork { requested, found } => write!( + f, + "Invalid network error. requested: {}, found: {}", + requested, found + ), + Error::Generic(e) => write!(f, "Generic error. {}", e), + Error::Json(e) => write!(f, "Json error. {}", e), + } + } +} + +impl From for Error { + fn from(e: DescriptorError) -> Self { + Error::Descriptor(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Json(e) + } +} + +impl CaravanConfig { + /// Get the bitcoin network value + pub fn network(&self) -> Network { + match self.network { + CaravanNetwork::Mainnet => Network::Bitcoin, + CaravanNetwork::Testnet => Network::Testnet, + } + } + /// Get the descriptor value + pub fn descriptor(&self, keychain: KeychainKind) -> Result { + let required = self.quorum.required_signers; + let network: Network = self.network(); + + let result = match self.address_type { + CaravanAddressType::P2sh => { + let keys: Vec> = self.descriptor_keys(keychain)?; + descriptor! { sh ( sortedmulti_vec(required, keys) ) } + } + CaravanAddressType::P2shP2wsh => { + let keys: Vec> = self.descriptor_keys(keychain)?; + descriptor! { sh ( wsh ( sortedmulti_vec(required, keys) ) ) } + } + CaravanAddressType::P2wsh => { + let keys: Vec> = self.descriptor_keys(keychain)?; + descriptor! { wsh ( sortedmulti_vec(required, keys) ) } + } + } + .map_err(Error::Descriptor); + + match result { + Ok((d, _, n)) => { + if n.contains(&network) { + Ok(d) + } else { + Err(Error::InvalidNetwork { + requested: network, + found: *n.iter().last().expect("network"), + }) + } + } + Err(e) => Err(e), + } + } + + fn descriptor_keys( + &self, + keychain: KeychainKind, + ) -> Result>, DescriptorError> { + let result = self + .extended_public_keys + .iter() + .map(|k| { + let fingerprint = k.xfp; + let key_path = k.bip32_path.clone(); + let key_source = fingerprint.zip(key_path); + let keychain_index = keychain as u32; + let derivation_path = DerivationPath::master().child(ChildNumber::Normal { + index: keychain_index, + }); + k.xpub + .into_descriptor_key(key_source, derivation_path) + .map_err(DescriptorError::Key) + }) + .collect(); + result + } + + /// Create a Caravan config from network, descriptors, name and client type. + pub fn new( + network: Network, + external_descriptor: ExtendedDescriptor, + internal_descriptor: ExtendedDescriptor, + name: String, + client_type: String, + ) -> Result { + let (external_address_type, external_quorum, external_public_keys) = + parse_descriptor(&external_descriptor)?; + let (internal_address_type, internal_quorum, internal_public_keys) = + parse_descriptor(&internal_descriptor)?; + + // verify external and internal address types match + if external_address_type != internal_address_type { + return Err(Error::Generic( + "External and internal descriptor address type configs don't match.".to_string(), + )); + } + + // verify external and internal descriptor configs match + if external_quorum != internal_quorum { + return Err(Error::Generic( + "External and internal descriptor quorum configs don't match.".to_string(), + )); + } + + // verify internal and external descriptor keys match except ends with m/(0|1)/* + for (external_key, internal_key) in + external_public_keys.iter().zip(internal_public_keys.iter()) + { + let ex_caravan_key = parse_key(external_key)?; + let in_caravan_key = parse_key(internal_key)?; + if ex_caravan_key.bip32_path != in_caravan_key.bip32_path { + return Err(Error::Generic( + "External and internal keys have different bip32_path".to_string(), + )); + } + if ex_caravan_key.xfp != in_caravan_key.xfp { + return Err(Error::Generic( + "External and internal keys have different xfp".to_string(), + )); + } + if ex_caravan_key.xpub_last_index != 0 { + return Err(Error::Generic( + "External keys last normal index must be 0".to_string(), + )); + } + if in_caravan_key.xpub_last_index != 1 { + return Err(Error::Generic( + "Internal keys last normal index must be 0".to_string(), + )); + } + } + + let network = match network { + Network::Bitcoin => CaravanNetwork::Mainnet, + _ => CaravanNetwork::Testnet, + }; + + let client = CaravanClient { value: client_type }; + let extended_public_keys = external_public_keys.iter().flat_map(parse_key).collect(); + + Ok(Self { + name, + address_type: external_address_type, + network, + client, + quorum: external_quorum, + extended_public_keys, + starting_address_index: 0, + }) + } + + pub fn to_string_pretty(&self) -> String { + serde_json::to_string_pretty(self).unwrap() + } +} + +fn parse_sorted_multi( + sorted_multi: &SortedMultiVec, +) -> (Quorum, &[Pk]) { + let quorum = Quorum { + required_signers: sorted_multi.k, + total_signers: sorted_multi.pks.len(), + }; + let extended_public_keys = sorted_multi.pks.as_slice(); + (quorum, extended_public_keys) +} + +fn parse_descriptor( + descriptor: &ExtendedDescriptor, +) -> Result<(CaravanAddressType, Quorum, &[DescriptorPublicKey]), Error> { + match descriptor { + ExtendedDescriptor::Sh(sh) => match sh.as_inner() { + ShInner::SortedMulti(sorted_multi) => { + let (quorum, extended_public_keys) = parse_sorted_multi(sorted_multi); + Ok((CaravanAddressType::P2sh, quorum, extended_public_keys)) + } + ShInner::Wsh(wsh) => match wsh.as_inner() { + WshInner::SortedMulti(sorted_multi) => { + let (quorum, extended_public_keys) = parse_sorted_multi(sorted_multi); + Ok((CaravanAddressType::P2shP2wsh, quorum, extended_public_keys)) + } + _ => Err(Error::Generic( + "Unsupported sh(wsh()) inner descriptor.".to_string(), + )), + }, + _ => Err(Error::Generic( + "Unsupported sh() inner descriptor.".to_string(), + )), + }, + ExtendedDescriptor::Wsh(sh) => match sh.as_inner() { + WshInner::SortedMulti(smv) => { + let (quorum, extended_public_keys) = parse_sorted_multi(smv); + Ok((CaravanAddressType::P2wsh, quorum, extended_public_keys)) + } + _ => Err(Error::Generic( + "Unsupported wsh() inner descriptor.".to_string(), + )), + }, + _ => Err(Error::Generic( + "Unsupported top level descriptor.".to_string(), + )), + } +} + +fn parse_key(pubkey: &DescriptorPublicKey) -> Result { + match pubkey { + DescriptorPublicKey::Single(_) => { + Err(Error::Generic("Unsupported single pub key.".to_string())) + } + DescriptorPublicKey::XPub(xpub) => { + let mut xfp = None; + let mut bip32_path = None; + if let Some((s_xfp, s_bip32_path)) = xpub.origin.clone() { + xfp = Some(s_xfp); + bip32_path = Some(s_bip32_path); + } + let xpub_index = xpub.derivation_path.len() - 1; + let xpub_last_child = xpub.derivation_path[xpub_index]; + let xpub_last_index = match xpub_last_child { + ChildNumber::Normal { index } => index, + ChildNumber::Hardened { .. } => { + return Err(Error::Generic("Last key index must be normal.".to_string())) + } + }; + Ok(CaravanExtendedPublicKey { + name: xpub.xkey.fingerprint().to_string(), + bip32_path, + xpub: xpub.xkey, + xfp, + xpub_last_index, + }) + } + } +} + +/// The address types supported by Caravan +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] +pub enum CaravanAddressType { + /// P2SH + #[serde(rename = "P2SH")] + P2sh, + /// P2SH-P2WSH + #[serde(rename = "P2SH-P2WSH")] + P2shP2wsh, + /// P2WSH + #[serde(rename = "P2WSH")] + P2wsh, +} + +impl Display for CaravanAddressType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CaravanAddressType::P2sh => write!(f, "P2SH"), + CaravanAddressType::P2shP2wsh => write!(f, "P2SH-P2WSH"), + CaravanAddressType::P2wsh => write!(f, "P2WSH"), + } + } +} + +/// The networks supported by Caravan +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +enum CaravanNetwork { + Mainnet, + Testnet, +} + +impl From for Network { + fn from(n: CaravanNetwork) -> Self { + match n { + CaravanNetwork::Mainnet => Network::Bitcoin, + CaravanNetwork::Testnet => Network::Testnet, + } + } +} + +/// A caravan client +#[derive(Debug, Serialize, Deserialize)] +pub struct CaravanClient { + /// The client type value + #[serde(rename = "type")] + value: String, +} + +/// The quorum of signers required and total signers +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct Quorum { + #[serde(rename = "requiredSigners")] + required_signers: usize, + #[serde(rename = "totalSigners")] + total_signers: usize, +} + +/// The Caravan extended public key information +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CaravanExtendedPublicKey { + name: String, + #[serde(rename = "bip32Path")] + bip32_path: Option, + xpub: ExtendedPubKey, + xfp: Option, + #[serde(skip, default)] + xpub_last_index: u32, +} + +impl ToString for CaravanConfig { + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } +} + +impl FromStr for CaravanConfig { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} + +/// Begin MultisigWalletConfig + +/// Structure that contains a deserialized config for a multisig wallet. +/// +/// For a usage example see [this module](crate::config)'s documentation. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MultisigWalletConfig { + /// Address type, + pub address_type: CaravanAddressType, + /// required number of signers + #[serde(rename = "requiredSigners")] + pub required_signers: usize, + /// Extended public keys + pub key_origins: Vec, + /// Caravan network + network: CaravanNetwork, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyOrigin { + #[serde(rename = "bip32Path")] + bip32_path: Option, + xpub: ExtendedPubKey, + xfp: Option, +} + +impl ToString for MultisigWalletConfig { + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } +} + +impl FromStr for MultisigWalletConfig { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} + +impl MultisigWalletConfig { + /// Get the bitcoin network value + pub fn network(&self) -> Network { + match self.network { + CaravanNetwork::Mainnet => Network::Bitcoin, + CaravanNetwork::Testnet => Network::Testnet, + } + } + + /// Get the descriptor value + pub fn descriptor(&self, keychain: KeychainKind) -> Result { + let required = self.required_signers; + let network: Network = self.network(); + + let result = match self.address_type { + CaravanAddressType::P2sh => { + let keys: Vec> = self.descriptor_keys(keychain)?; + descriptor! { sh ( sortedmulti_vec(required, keys) ) } + } + CaravanAddressType::P2shP2wsh => { + let keys: Vec> = self.descriptor_keys(keychain)?; + descriptor! { sh ( wsh ( sortedmulti_vec(required, keys) ) ) } + } + CaravanAddressType::P2wsh => { + let keys: Vec> = self.descriptor_keys(keychain)?; + descriptor! { wsh ( sortedmulti_vec(required, keys) ) } + } + } + .map_err(Error::Descriptor); + + match result { + Ok((d, _, n)) => { + if n.contains(&network) { + Ok(d) + } else { + Err(Error::InvalidNetwork { + requested: network, + found: *n.iter().last().expect("network"), + }) + } + } + Err(e) => Err(e), + } + } + + fn descriptor_keys( + &self, + keychain: KeychainKind, + ) -> Result>, DescriptorError> { + let result = self + .key_origins + .iter() + .map(|k| { + let fingerprint = k.xfp; + let key_path = k.bip32_path.clone(); + let key_source = fingerprint.zip(key_path); + let keychain_index = keychain as u32; + let derivation_path = DerivationPath::master().child(ChildNumber::Normal { + index: keychain_index, + }); + k.xpub + .into_descriptor_key(key_source, derivation_path) + .map_err(DescriptorError::Key) + }) + .collect(); + result + } + + pub fn to_string_pretty(&self) -> String { + serde_json::to_string_pretty(self).unwrap() + } +} + +/// END MultisigWalletConfig + +#[cfg(test)] +mod test { + use std::convert::TryFrom; + use std::str::FromStr; + + use bdk::bitcoin::Address; + + use super::*; + use assert_json_diff::assert_json_include; + use serde_json::Value; + + fn test_export(config_json: &str, expected_addresses: Vec<&str>) { + let config = CaravanConfig::from_str(config_json).expect("import"); + let external_descriptor = config + .descriptor(KeychainKind::External) + .expect("external descriptor"); + println!("Exported external descriptor: {}", external_descriptor); + let internal_descriptor = config + .descriptor(KeychainKind::Internal) + .expect("internal descriptor"); + println!("Exported internal descriptor: {}", internal_descriptor); + + for (index, expected_address) in expected_addresses.iter().enumerate() { + let expected_address = Address::from_str(expected_address).expect("expected address"); + let derived_address = external_descriptor + .at_derivation_index(u32::try_from(index).expect("u32 index")) + .address(config.network.into()) + .expect("derived address"); + assert_eq!(derived_address, expected_address); + } + } + + fn test_import( + network: &str, + external_descriptor: &str, + internal_descriptor: &str, + name: &str, + expected_config_json: &str, + ) { + let network = Network::from_str(network).expect("network"); + let external_descriptor = + ExtendedDescriptor::from_str(external_descriptor).expect("external descriptor"); + let internal_descriptor = + ExtendedDescriptor::from_str(internal_descriptor).expect("internal descriptor"); + + let config = CaravanConfig::new( + network, + external_descriptor, + internal_descriptor, + name.to_string(), + "public".to_string(), + ) + .expect("config"); + + println!("Imported config: {}", config.to_string()); + + // NOTE: .extendedPublicKeys[].name fields are set to key hash and are not expected + let expected_export: Value = + serde_json::from_str(expected_config_json).expect("expected export"); + assert_json_include!(actual: config, expected: expected_export); + } + + fn test_wallet_config(config_json: &str, expected_descriptors: Vec<&str>) { + let config = MultisigWalletConfig::from_str(config_json).expect("import from json failed"); + let external_descriptor = config + .descriptor(KeychainKind::External) + .expect("external descriptor"); + println!("Exported external descriptor: {}", external_descriptor); + let internal_descriptor = config + .descriptor(KeychainKind::Internal) + .expect("internal descriptor"); + println!("Exported internal descriptor: {}", internal_descriptor); + let expected_external = ExtendedDescriptor::from_str(expected_descriptors[0]).expect("external descriptor"); + assert_eq!(external_descriptor, expected_external); + let expected_internal = ExtendedDescriptor::from_str(expected_descriptors[1]).expect("internal descriptor"); + assert_eq!(internal_descriptor, expected_internal); + } + + #[test] + fn test_wallet_config_p2sh_m() { + let config_json = r#"{ + "addressType": "P2SH", + "network": "mainnet", + "requiredSigners": 2, + "keyOrigins": [ + { + "bip32Path": "m/45'/0'/100'", + "xpub": "xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH", + "xfp" : "f57ec65d" + }, + { + "bip32Path": "m/45'/0'/100'", + "xpub": "xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub", + "xfp" : "efa5d916" + } + ] + }"#; + + test_wallet_config( + config_json, + vec![ + "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/0/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/0/*))#uxj9xxul", + "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/1/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/1/*))#3hxf9z66", + ], + ); + } + + #[test] + fn test_export_p2sh_m() { + let config_json = r#"{ + "name": "P2SH-M", + "addressType": "P2SH", + "network": "mainnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "name": "osw", + "bip32Path": "m/45'/0'/100'", + "xpub": "xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH", + "xfp" : "f57ec65d" + }, + { + "name": "d", + "bip32Path": "m/45'/0'/100'", + "xpub": "xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub", + "xfp" : "efa5d916" + } + ], + "startingAddressIndex": 0 + }"#; + + test_export( + config_json, + vec![ + "3PiCF26aq57Wo5DJEbFNTVwD1bLCUEpAYZ", + "3EvHiVyDVoLjeZNMt3v1QTQfs2P4ohVwmg", + "3PSAx42y6hzWvx2QxQon7CymauWs2SZXuA", + ], + ); + } + + #[test] + fn test_import_p2sh_m() { + let external_descriptor = "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/0/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/0/*))#uxj9xxul"; + let internal_descriptor = "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/1/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/1/*))#3hxf9z66"; + + let name = "P2SH-M"; + + // NOTE: .extendedPublicKeys[].name fields are set to key hash and are not expected + let expected_config_json = r#"{ + "name": "P2SH-M", + "addressType": "P2SH", + "network": "mainnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "bip32Path": "m/45'/0'/100'", + "xpub": "xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH", + "xfp" : "f57ec65d" + }, + { + "bip32Path": "m/45'/0'/100'", + "xpub": "xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub", + "xfp" : "efa5d916" + } + ], + "startingAddressIndex": 0 + }"#; + + test_import( + "bitcoin", + external_descriptor, + internal_descriptor, + name, + expected_config_json, + ); + } + + #[test] + fn test_export_p2sh_t() { + let config_json = r#"{ + "name": "P2SH-T", + "addressType": "P2SH", + "network": "testnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "name": "dev", + "bip32Path": "m/45'/1'/100'", + "xpub": "tpubDDinbKDXyddTUKcX6mv936Ux5utCJteq5S6EEKhfpM8CqN2rMAcccv6GecsB3cPt8eGL4e4K2eaZ9Jis9TGf7mbwBsRTN7ngnFR7yJZxBKC", + "xfp" : "efa5d916" + }, + { + "name": "osw", + "bip32Path": "m/45'/1'/100'", + "xpub": "tpubDDQubdBx9cbwQtdcRTisKF7wVCwHgHewhU7wh77VzCi62Q9q81qyQeLoZjKWZ62FnQbWU8k7CuKo2A21pAWaFtPGDHP9WuhtAx4smcCxqn1", + "xfp" : "f57ec65d" + } + ], + "startingAddressIndex": 0 + }"#; + + test_export( + config_json, + vec![ + "2N5KgAnFFpmk5TRMiCicRZDQS8FFNCKqKf1", + "2N5hHeNeqk72xkQiHWTHvmpVTpyuKynGrcH", + "2NC1zVgtFLBfc3UZvnhhjNAF15NmksNCZXe", + ], + ); + } + + #[test] + fn test_import_p2sh_t() { + let external_descriptor = "sh(sortedmulti(2,[efa5d916/45'/1'/100']tpubDDinbKDXyddTUKcX6mv936Ux5utCJteq5S6EEKhfpM8CqN2rMAcccv6GecsB3cPt8eGL4e4K2eaZ9Jis9TGf7mbwBsRTN7ngnFR7yJZxBKC/0/*,[f57ec65d/45'/1'/100']tpubDDQubdBx9cbwQtdcRTisKF7wVCwHgHewhU7wh77VzCi62Q9q81qyQeLoZjKWZ62FnQbWU8k7CuKo2A21pAWaFtPGDHP9WuhtAx4smcCxqn1/0/*))#e4qrgzdy"; + let internal_descriptor = "sh(sortedmulti(2,[efa5d916/45'/1'/100']tpubDDinbKDXyddTUKcX6mv936Ux5utCJteq5S6EEKhfpM8CqN2rMAcccv6GecsB3cPt8eGL4e4K2eaZ9Jis9TGf7mbwBsRTN7ngnFR7yJZxBKC/1/*,[f57ec65d/45'/1'/100']tpubDDQubdBx9cbwQtdcRTisKF7wVCwHgHewhU7wh77VzCi62Q9q81qyQeLoZjKWZ62FnQbWU8k7CuKo2A21pAWaFtPGDHP9WuhtAx4smcCxqn1/1/*))#5y50txtp"; + let name = "P2SH-T"; + + // NOTE: .extendedPublicKeys[].name fields are set to key hash and are not expected + let expected_config_json = r#"{ + "name": "P2SH-T", + "addressType": "P2SH", + "network": "testnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "bip32Path": "m/45'/1'/100'", + "xpub": "tpubDDinbKDXyddTUKcX6mv936Ux5utCJteq5S6EEKhfpM8CqN2rMAcccv6GecsB3cPt8eGL4e4K2eaZ9Jis9TGf7mbwBsRTN7ngnFR7yJZxBKC", + "xfp" : "efa5d916" + }, + { + "bip32Path": "m/45'/1'/100'", + "xpub": "tpubDDQubdBx9cbwQtdcRTisKF7wVCwHgHewhU7wh77VzCi62Q9q81qyQeLoZjKWZ62FnQbWU8k7CuKo2A21pAWaFtPGDHP9WuhtAx4smcCxqn1", + "xfp" : "f57ec65d" + } + ], + "startingAddressIndex": 0 + }"#; + + test_import( + "testnet", + external_descriptor, + internal_descriptor, + name, + expected_config_json, + ); + } + + #[test] + fn test_export_p2sh_p2wsh_m() { + let config_json = r#"{ + "name": "P2SH-P2WSH-M", + "addressType": "P2SH-P2WSH", + "network": "mainnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "name": "d", + "bip32Path": "m/48'/0'/100'/1'", + "xpub": "xpub6EwJjKaiocGvo9f7XSGXGwzo1GLB1URxSZ5Ccp1wqdxNkhrSoqNQkC2CeMsU675urdmFJLHSX62xz56HGcnn6u21wRy6uipovmzaE65PfBp", + "xfp" : "efa5d916" + }, + { + "name": "osw", + "bip32Path": "m/48'/0'/100'/1'", + "xpub": "xpub6DcqYQxnbefzEBJF6osEuT5yXoHVZu1YCCsS5YkATvqD2h7tdMBgdBrUXk26FrJwawDGX6fHKPvhhZxKc5b8dPAPb8uANDhsjAPMJqTFDjH", + "xfp" : "f57ec65d" + } + ], + "startingAddressIndex": 0 + }"#; + + test_export( + config_json, + vec![ + "348PsXezZAHcW7RjmCoMJ8PHWx1QBTXJvm", + "3GFHyS5GGzTLJaJz6qeSjMrtQGLsbFG4Z8", + "34Gam7P9rrWwZTeF74WceJ2PGH9XCZTEi6", + ], + ); + } + + #[test] + fn test_import_p2sh_p2wsh_m() { + let external_descriptor = "sh(wsh(sortedmulti(2,[efa5d916/48'/0'/100'/1']xpub6EwJjKaiocGvo9f7XSGXGwzo1GLB1URxSZ5Ccp1wqdxNkhrSoqNQkC2CeMsU675urdmFJLHSX62xz56HGcnn6u21wRy6uipovmzaE65PfBp/0/*,[f57ec65d/48'/0'/100'/1']xpub6DcqYQxnbefzEBJF6osEuT5yXoHVZu1YCCsS5YkATvqD2h7tdMBgdBrUXk26FrJwawDGX6fHKPvhhZxKc5b8dPAPb8uANDhsjAPMJqTFDjH/0/*)))#jeqfd8lr"; + let internal_descriptor = "sh(wsh(sortedmulti(2,[efa5d916/48'/0'/100'/1']xpub6EwJjKaiocGvo9f7XSGXGwzo1GLB1URxSZ5Ccp1wqdxNkhrSoqNQkC2CeMsU675urdmFJLHSX62xz56HGcnn6u21wRy6uipovmzaE65PfBp/1/*,[f57ec65d/48'/0'/100'/1']xpub6DcqYQxnbefzEBJF6osEuT5yXoHVZu1YCCsS5YkATvqD2h7tdMBgdBrUXk26FrJwawDGX6fHKPvhhZxKc5b8dPAPb8uANDhsjAPMJqTFDjH/1/*)))#j58fg4ec"; + let name = "P2SH-P2WSH-M"; + + // NOTE: .extendedPublicKeys[].name fields are set to key hash and are not expected + let expected_config_json = r#"{ + "name": "P2SH-P2WSH-M", + "addressType": "P2SH-P2WSH", + "network": "mainnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "bip32Path": "m/48'/0'/100'/1'", + "xpub": "xpub6EwJjKaiocGvo9f7XSGXGwzo1GLB1URxSZ5Ccp1wqdxNkhrSoqNQkC2CeMsU675urdmFJLHSX62xz56HGcnn6u21wRy6uipovmzaE65PfBp", + "xfp" : "efa5d916" + }, + { + "bip32Path": "m/48'/0'/100'/1'", + "xpub": "xpub6DcqYQxnbefzEBJF6osEuT5yXoHVZu1YCCsS5YkATvqD2h7tdMBgdBrUXk26FrJwawDGX6fHKPvhhZxKc5b8dPAPb8uANDhsjAPMJqTFDjH", + "xfp" : "f57ec65d" + } + ], + "startingAddressIndex": 0 + }"#; + + test_import( + "bitcoin", + external_descriptor, + internal_descriptor, + name, + expected_config_json, + ); + } + + #[test] + fn test_export_p2sh_p2wsh_t() { + let config_json = r#"{ + "name": "P2SH-P2WSH-T", + "addressType": "P2SH-P2WSH", + "network": "testnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "name": "osw", + "bip32Path": "m/48'/1'/100'/1'", + "xpub": "tpubDFc9Mm4tw6EkdXuk24MnQYRrDsdKEFh498vFffqa2KJmxytpcHbWrcFYwTKAdLxkSWpadzb5M5VVZ7PDAUjDjymvUmQ7pBbRecz2FM952Am", + "xfp" : "f57ec65d" + }, + { + "name": "d", + "bip32Path": "m/48'/1'/100'/1'", + "xpub": "tpubDErWN5qfdLwY9ZJo9HWpxjcuEFuEBVHSbQbPqF35LQr3etWNGirKcgAa93DZ4DmtHm36p2gTf4aj6KybLqHaS3UePM5LtPqtb3d3dYVDs2F", + "xfp" : "efa5d916" + } + ], + "startingAddressIndex": 0 + }"#; + + test_export( + config_json, + vec![ + "2NDBsV6VBe4d2Ukp2XB644dg2xZ2SuWGkyG", + "2N2HfmoavC1zjYKxU71Lp1YwCECHXPVKb2Y", + "2N9g9FZRJ1KUbEvdQ6Mpm5cMGxR3fpM8h5h", + ], + ); + } + + #[test] + fn test_import_p2sh_p2wsh_t() { + let external_descriptor = "sh(wsh(sortedmulti(2,[f57ec65d/48'/1'/100'/1']tpubDFc9Mm4tw6EkdXuk24MnQYRrDsdKEFh498vFffqa2KJmxytpcHbWrcFYwTKAdLxkSWpadzb5M5VVZ7PDAUjDjymvUmQ7pBbRecz2FM952Am/0/*,[efa5d916/48'/1'/100'/1']tpubDErWN5qfdLwY9ZJo9HWpxjcuEFuEBVHSbQbPqF35LQr3etWNGirKcgAa93DZ4DmtHm36p2gTf4aj6KybLqHaS3UePM5LtPqtb3d3dYVDs2F/0/*)))#j7jzgtur"; + let internal_descriptor = "sh(wsh(sortedmulti(2,[f57ec65d/48'/1'/100'/1']tpubDFc9Mm4tw6EkdXuk24MnQYRrDsdKEFh498vFffqa2KJmxytpcHbWrcFYwTKAdLxkSWpadzb5M5VVZ7PDAUjDjymvUmQ7pBbRecz2FM952Am/1/*,[efa5d916/48'/1'/100'/1']tpubDErWN5qfdLwY9ZJo9HWpxjcuEFuEBVHSbQbPqF35LQr3etWNGirKcgAa93DZ4DmtHm36p2gTf4aj6KybLqHaS3UePM5LtPqtb3d3dYVDs2F/1/*)))#jn4zde6c"; + let name = "P2SH-P2WSH-T"; + + // NOTE: .extendedPublicKeys[].name fields are set to key hash and are not expected + let expected_config_json = r#"{ + "name": "P2SH-P2WSH-T", + "addressType": "P2SH-P2WSH", + "network": "testnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "bip32Path": "m/48'/1'/100'/1'", + "xpub": "tpubDFc9Mm4tw6EkdXuk24MnQYRrDsdKEFh498vFffqa2KJmxytpcHbWrcFYwTKAdLxkSWpadzb5M5VVZ7PDAUjDjymvUmQ7pBbRecz2FM952Am", + "xfp" : "f57ec65d" + }, + { + "bip32Path": "m/48'/1'/100'/1'", + "xpub": "tpubDErWN5qfdLwY9ZJo9HWpxjcuEFuEBVHSbQbPqF35LQr3etWNGirKcgAa93DZ4DmtHm36p2gTf4aj6KybLqHaS3UePM5LtPqtb3d3dYVDs2F", + "xfp" : "efa5d916" + } + ], + "startingAddressIndex": 0 + }"#; + + test_import( + "testnet", + external_descriptor, + internal_descriptor, + name, + expected_config_json, + ); + } + + #[test] + fn test_export_p2wsh_m() { + let config_json = r#"{ + "name": "P2WSH-M", + "addressType": "P2WSH", + "network": "mainnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "name": "d", + "bip32Path": "m/48'/0'/100'/2'", + "xpub": "xpub6EwJjKaiocGvqSuM2jRZSuQ9HEddiFUFu9RdjE47zG7kXVNDQpJ3GyvskwYiLmvU4SBTNZyv8UH53QcmFEE23YwozE61V3dwzZJEFQr6H2b", + "xfp" : "efa5d916" + }, + { + "name": "osw", + "bip32Path": "m/48'/0'/100'/2'", + "xpub": "xpub6DcqYQxnbefzFkaRBK63FSE2GzNuNnNhFGw1xV9RioVG7av6r3JDf1aELqBSq5gt5487CtNxvVtaiJjQU2HQWzgG5NzLyTPbYav6otW8qEc", + "xfp" : "f57ec65d" + } + ], + "startingAddressIndex": 0 + }"#; + + test_export( + config_json, + vec![ + "bc1qf9asympax4r6xrndsqrw8p0qxe40tm9zkk69tkrc8p6eg8ju075sjeekkt", + "bc1q2dexslsgvj4w2adf2lltthglkchmh3d2qvyrtdrece6lfr5tl4cq382unz", + "bc1q3kwd3zfaa90r20nvm2u3zxtw9c8cf5x4a4ecgw2y7pf59pnpmxns9keq9w", + ], + ); + } + + #[test] + fn test_import_p2wsh_m() { + let external_descriptor = "wsh(sortedmulti(2,[efa5d916/48'/0'/100'/2']xpub6EwJjKaiocGvqSuM2jRZSuQ9HEddiFUFu9RdjE47zG7kXVNDQpJ3GyvskwYiLmvU4SBTNZyv8UH53QcmFEE23YwozE61V3dwzZJEFQr6H2b/0/*,[f57ec65d/48'/0'/100'/2']xpub6DcqYQxnbefzFkaRBK63FSE2GzNuNnNhFGw1xV9RioVG7av6r3JDf1aELqBSq5gt5487CtNxvVtaiJjQU2HQWzgG5NzLyTPbYav6otW8qEc/0/*))#decr929e"; + let internal_descriptor = "wsh(sortedmulti(2,[efa5d916/48'/0'/100'/2']xpub6EwJjKaiocGvqSuM2jRZSuQ9HEddiFUFu9RdjE47zG7kXVNDQpJ3GyvskwYiLmvU4SBTNZyv8UH53QcmFEE23YwozE61V3dwzZJEFQr6H2b/1/*,[f57ec65d/48'/0'/100'/2']xpub6DcqYQxnbefzFkaRBK63FSE2GzNuNnNhFGw1xV9RioVG7av6r3JDf1aELqBSq5gt5487CtNxvVtaiJjQU2HQWzgG5NzLyTPbYav6otW8qEc/1/*))#wj94h3at"; + let name = "P2WSH-M"; + + // NOTE: .extendedPublicKeys[].name fields are set to key hash and are not expected + let expected_config_json = r#"{ + "name": "P2WSH-M", + "addressType": "P2WSH", + "network": "mainnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "bip32Path": "m/48'/0'/100'/2'", + "xpub": "xpub6EwJjKaiocGvqSuM2jRZSuQ9HEddiFUFu9RdjE47zG7kXVNDQpJ3GyvskwYiLmvU4SBTNZyv8UH53QcmFEE23YwozE61V3dwzZJEFQr6H2b", + "xfp" : "efa5d916" + }, + { + "bip32Path": "m/48'/0'/100'/2'", + "xpub": "xpub6DcqYQxnbefzFkaRBK63FSE2GzNuNnNhFGw1xV9RioVG7av6r3JDf1aELqBSq5gt5487CtNxvVtaiJjQU2HQWzgG5NzLyTPbYav6otW8qEc", + "xfp" : "f57ec65d" + } + ], + "startingAddressIndex": 0 + }"#; + + test_import( + "bitcoin", + external_descriptor, + internal_descriptor, + name, + expected_config_json, + ); + } + + #[test] + fn test_export_p2wsh_t() { + let config_json = r#"{ + "name": "P2WSH-T", + "addressType": "P2WSH", + "network": "testnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "name": "osw", + "bip32Path": "m/48'/1'/100'/2'", + "xpub": "tpubDFc9Mm4tw6EkgR4YTC1GrU6CGEd9yw7KSBnSssL4LXAXh89D4uMZigRyv3csdXbeU3BhLQc4vWKTLewboA1Pt8Fu6fbHKu81MZ6VGdc32eM", + "xfp" : "f57ec65d" + }, + { + "name": "d", + "bip32Path": "m/48'/1'/100'/2'", + "xpub": "tpubDErWN5qfdLwYE94mh12oWr4uURDDNKCjKVhCEcAgZ7jKnnAwq5tcTF2iEk3VuznkJuk2G8SCHft9gS6aKbBd18ptYWPqKLRSTRQY7e2rrDj", + "xfp" : "efa5d916" + } + ], + "startingAddressIndex": 0 + }"#; + + test_export( + config_json, + vec![ + "tb1qhgj3fnwn50pq966rjnj4pg8uz9ktsd8nge32qxd73ffvvg636p5q54g7m0", + "tb1q4ka64s7fcdv8ms7xs6j2w35dz8t7n0zd450lgsny73jvg8lpyqfqr9n037", + "tb1q8fglyvwtlr5t427cqn898jc9vrqxkc43522tpxjaupmn8ewu9sushz86gf", + ], + ); + } + + #[test] + fn test_import_p2wsh_t() { + let external_descriptor = "wsh(sortedmulti(2,[f57ec65d/48'/1'/100'/2']tpubDFc9Mm4tw6EkgR4YTC1GrU6CGEd9yw7KSBnSssL4LXAXh89D4uMZigRyv3csdXbeU3BhLQc4vWKTLewboA1Pt8Fu6fbHKu81MZ6VGdc32eM/0/*,[efa5d916/48'/1'/100'/2']tpubDErWN5qfdLwYE94mh12oWr4uURDDNKCjKVhCEcAgZ7jKnnAwq5tcTF2iEk3VuznkJuk2G8SCHft9gS6aKbBd18ptYWPqKLRSTRQY7e2rrDj/0/*))#nv5k65uf"; + let internal_descriptor = "wsh(sortedmulti(2,[f57ec65d/48'/1'/100'/2']tpubDFc9Mm4tw6EkgR4YTC1GrU6CGEd9yw7KSBnSssL4LXAXh89D4uMZigRyv3csdXbeU3BhLQc4vWKTLewboA1Pt8Fu6fbHKu81MZ6VGdc32eM/1/*,[efa5d916/48'/1'/100'/2']tpubDErWN5qfdLwYE94mh12oWr4uURDDNKCjKVhCEcAgZ7jKnnAwq5tcTF2iEk3VuznkJuk2G8SCHft9gS6aKbBd18ptYWPqKLRSTRQY7e2rrDj/1/*))#s8fqg0ym"; + let name = "P2WSH-T"; + + // NOTE: .extendedPublicKeys[].name fields are set to key hash and are not expected + let expected_config_json = r#"{ + "name": "P2WSH-T", + "addressType": "P2WSH", + "network": "testnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "bip32Path": "m/48'/1'/100'/2'", + "xpub": "tpubDFc9Mm4tw6EkgR4YTC1GrU6CGEd9yw7KSBnSssL4LXAXh89D4uMZigRyv3csdXbeU3BhLQc4vWKTLewboA1Pt8Fu6fbHKu81MZ6VGdc32eM", + "xfp" : "f57ec65d" + }, + { + "bip32Path": "m/48'/1'/100'/2'", + "xpub": "tpubDErWN5qfdLwYE94mh12oWr4uURDDNKCjKVhCEcAgZ7jKnnAwq5tcTF2iEk3VuznkJuk2G8SCHft9gS6aKbBd18ptYWPqKLRSTRQY7e2rrDj", + "xfp" : "efa5d916" + } + ], + "startingAddressIndex": 0 + }"#; + + test_import( + "testnet", + external_descriptor, + internal_descriptor, + name, + expected_config_json, + ); + } +} diff --git a/packages/caravan-descriptors/caravan-rs/src/lib.rs b/packages/caravan-descriptors/caravan-rs/src/lib.rs new file mode 100644 index 00000000..4ef11ecf --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/src/lib.rs @@ -0,0 +1,173 @@ +pub mod config; +pub mod utils; + +use bdk::bitcoin::Network as BdkNetwork; +use bdk::descriptor::ExtendedDescriptor as BdkExtendedDescriptor; +use bdk::KeychainKind::{External, Internal}; +use std::str::FromStr; +use wasm_bindgen::prelude::*; + +#[derive(Debug)] +#[wasm_bindgen] +pub struct Network(BdkNetwork); + +#[wasm_bindgen] +impl Network { + pub fn from_str(network: &str) -> Result { + let network = BdkNetwork::from_str(network)?; + Ok(Network(network)) + } + + pub fn to_string(&self) -> String { + self.0.to_string() + } +} + +#[wasm_bindgen(start)] +pub fn init() { + // initialization code +} + +#[derive(Debug)] +#[wasm_bindgen] +pub struct ExtendedDescriptor(BdkExtendedDescriptor); + +#[wasm_bindgen] +impl ExtendedDescriptor { + pub fn from_str(descriptor: &str) -> Result { + let descriptor = BdkExtendedDescriptor::from_str(descriptor)?; + Ok(ExtendedDescriptor(descriptor)) + } + + pub fn to_string(&self) -> String { + self.0.to_string() + } + + pub fn get_address(&self, index: u32, network: Network) -> Result { + Ok(self + .0 + .at_derivation_index(index) + .address(network.0)? + .to_string()) + } +} + +#[derive(Debug)] +#[wasm_bindgen] +pub struct MultisigWalletConfig(config::MultisigWalletConfig); + +#[wasm_bindgen] +impl MultisigWalletConfig { + pub fn from_str(config: &str) -> Result { + let config = config::MultisigWalletConfig::from_str(config)?; + Ok(MultisigWalletConfig(config)) + } + + pub fn to_string(&self) -> String { + self.0.to_string() + } + + pub fn to_string_pretty(&self) -> String { + self.0.to_string_pretty() + } + + pub fn external_descriptor(&self) -> Result { + let descriptor = self.0.descriptor(External)?; + Ok(ExtendedDescriptor(descriptor)) + } + + pub fn external_address(&self, index: u32) -> Result { + let network = self.network(); + let external_descriptor = self.external_descriptor()?; + Ok(external_descriptor.get_address(index, network)?) + } + + pub fn internal_descriptor(&self) -> Result { + let descriptor = self.0.descriptor(Internal)?; + Ok(ExtendedDescriptor(descriptor)) + } + + pub fn internal_address(&self, index: u32) -> Result { + let network = self.network(); + let internal_descriptor = self.internal_descriptor()?; + Ok(internal_descriptor.get_address(index, network)?) + } + + pub fn network(&self) -> Network { + Network(self.0.network()) + } +} + +#[derive(Debug)] +#[wasm_bindgen] +pub struct CaravanConfig(config::CaravanConfig); + +#[wasm_bindgen] +impl CaravanConfig { + pub fn from_str(config: &str) -> Result { + let config = config::CaravanConfig::from_str(config)?; + Ok(CaravanConfig(config)) + } + + pub fn new( + network: Network, + external_descriptor: ExtendedDescriptor, + internal_descriptor: ExtendedDescriptor, + name: String, + client_type: String, + ) -> Result { + let network = network.0; + let external_descriptor = external_descriptor.0; + let internal_descriptor = internal_descriptor.0; + let config = config::CaravanConfig::new( + network, + external_descriptor, + internal_descriptor, + name, + client_type, + )?; + Ok(CaravanConfig(config)) + } + + pub fn to_string(&self) -> String { + self.0.to_string() + } + + pub fn to_string_pretty(&self) -> String { + self.0.to_string_pretty() + } + + pub fn name(&self) -> String { + self.0.name.clone() + } + + pub fn address_type(&self) -> String { + self.0.address_type.to_string() + } + + pub fn external_descriptor(&self) -> Result { + let descriptor = self.0.descriptor(External)?; + Ok(ExtendedDescriptor(descriptor)) + } + + pub fn external_address(&self, index: u32) -> Result { + let network = self.network(); + let external_descriptor = self.external_descriptor()?; + Ok(external_descriptor.get_address(index, network)?) + } + + pub fn internal_descriptor(&self) -> Result { + let descriptor = self.0.descriptor(Internal)?; + Ok(ExtendedDescriptor(descriptor)) + } + + pub fn internal_address(&self, index: u32) -> Result { + let network = self.network(); + let internal_descriptor = self.internal_descriptor()?; + Ok(internal_descriptor.get_address(index, network)?) + } + + pub fn network(&self) -> Network { + Network(self.0.network()) + } +} diff --git a/packages/caravan-descriptors/caravan-rs/src/utils.rs b/packages/caravan-descriptors/caravan-rs/src/utils.rs new file mode 100644 index 00000000..b1d7929d --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/packages/caravan-descriptors/caravan-rs/tests/web.rs b/packages/caravan-descriptors/caravan-rs/tests/web.rs new file mode 100644 index 00000000..bf2b7de6 --- /dev/null +++ b/packages/caravan-descriptors/caravan-rs/tests/web.rs @@ -0,0 +1,94 @@ +//! Test suite for the Web and headless browsers. + +#![cfg(target_arch = "wasm32")] + +extern crate wasm_bindgen_test; + +use caravan_rs::utils::set_panic_hook; +use caravan_rs::{CaravanConfig, ExtendedDescriptor, Network}; +use wasm_bindgen::__rt::IntoJsResult; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn test_export() { + set_panic_hook(); + let config_json = r#"{ + "name": "P2SH-M", + "addressType": "P2SH", + "network": "mainnet", + "client": { + "type": "public" + }, + "quorum": { + "requiredSigners": 2, + "totalSigners": 2 + }, + "extendedPublicKeys": [ + { + "name": "osw", + "bip32Path": "m/45'/0'/100'", + "xpub": "xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH", + "xfp" : "f57ec65d" + }, + { + "name": "d", + "bip32Path": "m/45'/0'/100'", + "xpub": "xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub", + "xfp" : "efa5d916" + } + ], + "startingAddressIndex": 0 + }"#; + let config = CaravanConfig::from_str(config_json) + .map_err(|e| e.into_js_result()) + .expect("config"); + let external_descriptor = config + .external_descriptor() + .map_err(|e| e.into_js_result()) + .expect("external descriptor"); + assert_eq!(external_descriptor.to_string(), "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/0/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/0/*))#uxj9xxul"); + let internal_descriptor = config + .internal_descriptor() + .map_err(|e| e.into_js_result()) + .expect("internal descriptor"); + assert_eq!(internal_descriptor.to_string(), "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/1/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/1/*))#3hxf9z66"); + let network = config.network(); + assert_eq!("bitcoin", network.to_string()); +} + +#[wasm_bindgen_test] +fn test_import_p2sh_m() { + set_panic_hook(); + let external_descriptor = "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/0/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/0/*))#uxj9xxul"; + let external_descriptor = ExtendedDescriptor::from_str(external_descriptor) + .map_err(|e| e.into_js_result()) + .expect("external descriptor"); + let internal_descriptor = "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/1/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/1/*))#3hxf9z66"; + let internal_descriptor = ExtendedDescriptor::from_str(internal_descriptor) + .map_err(|e| e.into_js_result()) + .expect("internal descriptor"); + + let name = "P2SH-M".to_string(); + let client_type = "public".to_string(); + let network = Network::from_str("bitcoin") + .map_err(|e| e.into_js_result()) + .expect("network"); + + let config = CaravanConfig::new( + network, + external_descriptor, + internal_descriptor, + name, + client_type, + ) + .map_err(|e| e.into_js_result()) + .expect("external descriptor"); + + let address_0 = config + .external_address(1) + .map_err(|e| e.into_js_result()) + .expect("external address 0"); + assert_eq!(address_0, "3EvHiVyDVoLjeZNMt3v1QTQfs2P4ohVwmg"); +} diff --git a/packages/caravan-descriptors/jest.config.js b/packages/caravan-descriptors/jest.config.js new file mode 100644 index 00000000..6c44c6c8 --- /dev/null +++ b/packages/caravan-descriptors/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + modulePathIgnorePatterns: ["/dist/"], +}; diff --git a/packages/caravan-descriptors/package.json b/packages/caravan-descriptors/package.json new file mode 100644 index 00000000..3b2c7e4a --- /dev/null +++ b/packages/caravan-descriptors/package.json @@ -0,0 +1,30 @@ +{ + "name": "@caravan/descriptors", + "version": "0.0.0", + "author": "Unchained Capital", + "description": "A library for parsing and encoding descriptor strings using bdk", + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "engines": { + "node": ">=20" + }, + "scripts": { + "build:rs": "cd caravan-rs && wasm-pack build -t web --out-dir pkg-web && wasm-pack build --target nodejs --out-dir pkg-nodejs", + "build:ts": "tsc", + "build": "npm run build:rs && npm run build:ts && npm run copy-wasm", + "clean": "rm -rf dist/", + "copy-wasm": "shx mkdir -p dist/caravan-rs/pkg-web && shx mkdir -p dist/caravan-rs/pkg-nodejs && shx cp -R caravan-rs/pkg-web/* dist/caravan-rs/pkg-web/ && shx cp -R caravan-rs/pkg-nodejs/* dist/caravan-rs/pkg-nodejs/", + "dev": "npm run build -- --watch", + "lint": "eslint src", + "ci": "npm run build && npm run lint && npm run test", + "test": "jest src", + "test:watch": "jest --watch src", + "test:debug": "node --inspect-brk ../../node_modules/.bin/jest --runInBand" + }, + "dependencies": { + "@caravan/bitcoin": "*" + }, + "devDependencies": { + "shx": "^0.3.4" + } +} diff --git a/packages/caravan-descriptors/src/descriptors.test.ts b/packages/caravan-descriptors/src/descriptors.test.ts new file mode 100644 index 00000000..9dba723d --- /dev/null +++ b/packages/caravan-descriptors/src/descriptors.test.ts @@ -0,0 +1,142 @@ +import { Network } from '@caravan/bitcoin'; +import { + KeyOrigin, + MultisigWalletConfig, + decodeDescriptors, + encodeDescriptors, + getChecksum, + getWalletFromDescriptor, +} from "./descriptors"; + +const external = + "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/0/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/0/*))#uxj9xxul"; +const internal = + "sh(sortedmulti(2,[f57ec65d/45'/0'/100']xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH/1/*,[efa5d916/45'/0'/100']xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub/1/*))#3hxf9z66"; + +const expectedKeys = [ + { + xfp: "f57ec65d", + bip32Path: "m/45'/0'/100'", + xpub: "xpub6CCHViYn5VzPfSR7baop9FtGcbm3UnqHwa54Z2eNvJnRFCJCdo9HtCYoLJKZCoATMLUowDDA1BMGfQGauY3fDYU3HyMzX4NDkoLYCSkLpbH", + }, + { + xfp: "efa5d916", + bip32Path: "m/45'/0'/100'", + xpub: "xpub6Ca5CwTgRASgkXbXE5TeddTP9mPCbYHreCpmGt9dhz9y6femstHGCoFESHHKKRcm414xMKnuLjP9LDS7TwaJC9n5gxua6XB1rwPcC6hqDub", + }, +]; + +const testConfig = (config: MultisigWalletConfig) => { + expect(config.addressType).toEqual("P2SH"); + expect(config.requiredSigners).toEqual(2); + const derivation1: KeyOrigin = config.keyOrigins[0]; + const derivation2: KeyOrigin = config.keyOrigins[1]; + expect(derivation1).toStrictEqual(expectedKeys[0]); + expect(derivation2).toStrictEqual(expectedKeys[1]); +}; + +describe("decodeDescriptors", () => { + it("works", async () => { + const config = await decodeDescriptors(internal, external); + testConfig(config); + }); + + it("should throw if called with inconsistent network", async () => { + const testInternal = + "sh(sortedmulti(2,[611d202e/45'/1'/11'/3]tpubDEcXYgwH59QbtaS1q7CNskaL23oXnePHiU5zQuVDTDbSfM2xx5WYKaqgpfKnjAzgrHymmA7rZYmgtLKpugFq4dWJEC6HPpeUrMjFprLx8fW/1/*,[3e191e15/0/0/0/0]tpubDEeGXbhQg9q8ch8RvufnqvK4FPTRxidayvdb4Z24eyGUBSHsEBhQ8jaGZ4acKUzfP3FgVChNEPB47KzMHJbaL2WzvQqijrFTbSUqoHvXuoE/1/*,[96cf6667/45'/1'/12'/2]tpubDEX9s9A6av9oHR89T9VArgrt4zg3zBGndMm6Q2LEaBiEF153K2yF2yewHWmfNicEUdBXzmaP7VBZvT5D3GG1m5cYy36qfsA9RQS1uYw3MGi/1/*))#j8hgkfxv"; + const testExternal = + "sh(sortedmulti(2,[611d202e/45'/1'/11'/3]tpubDEcXYgwH59QbtaS1q7CNskaL23oXnePHiU5zQuVDTDbSfM2xx5WYKaqgpfKnjAzgrHymmA7rZYmgtLKpugFq4dWJEC6HPpeUrMjFprLx8fW/0/*,[3e191e15/0/0/0/0]tpubDEeGXbhQg9q8ch8RvufnqvK4FPTRxidayvdb4Z24eyGUBSHsEBhQ8jaGZ4acKUzfP3FgVChNEPB47KzMHJbaL2WzvQqijrFTbSUqoHvXuoE/0/*,[96cf6667/45'/1'/12'/2]tpubDEX9s9A6av9oHR89T9VArgrt4zg3zBGndMm6Q2LEaBiEF153K2yF2yewHWmfNicEUdBXzmaP7VBZvT5D3GG1m5cYy36qfsA9RQS1uYw3MGi/0/*))#medls6ae"; + + // Jest's "expect...toThrowError" doesn't work for some reason here + let passed = false; + try { + await decodeDescriptors(testInternal, testExternal, Network.MAINNET); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + if (e instanceof Error) { + passed = true; + expect(e.message).toMatch("xpubs do not match expected network"); + } + } + expect(passed).toBeTruthy(); + passed = false; + try { + await decodeDescriptors(internal, external, Network.TESTNET); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + if (e instanceof Error) { + passed = true; + expect(e.message).toMatch("xpubs do not match expected network"); + } + } + expect(passed).toBeTruthy(); + }); +}); + +describe("encodeDescriptors", () => { + it("should convert a config to descriptors", async () => { + const config = { + addressType: "P2SH", + keyOrigins: expectedKeys, + requiredSigners: 2, + network: "mainnet", + } as MultisigWalletConfig; + const actual = await encodeDescriptors(config); + expect(actual.receive).toEqual(external); + expect(actual.change).toEqual(internal); + }); +}); + +describe("getWalletFromDescriptor", () => { + it("should convert a receive descriptor to a wallet", async () => { + const config = await getWalletFromDescriptor(external); + testConfig(config); + }); + it("should convert a change descriptor to a wallet", async () => { + const config = await getWalletFromDescriptor(internal); + testConfig(config); + }); + it("should fail if passed with inconsistent network", async () => { + let passed = false; + try { + await getWalletFromDescriptor(internal, Network.TESTNET); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + if (e instanceof Error) { + passed = true; + expect(e.message).toMatch("xpubs do not match expected network"); + } + } + expect(passed).toBeTruthy(); + }); +}); + +describe("getChecksum", () => { + it("should return correct checksum", async () => { + const internalChecksum = await getChecksum(internal); + expect(internalChecksum).toEqual("3hxf9z66"); + const externalChecksum = await getChecksum(external); + expect(externalChecksum).toEqual("uxj9xxul"); + }); + + it("should throw if invalid or missing checksum", async () => { + let passed = false; + const invalids = [ + internal.split("#")[0], + internal.concat("asdf"), + internal.split("#")[0].concat("#123"), + internal.split("#")[0].concat("#1234abcd"), + ]; + for (const test of invalids) { + try { + await getChecksum(test); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + if (e instanceof Error) { + passed = true; + } + } + expect(passed).toBeTruthy(); + } + }); +}); diff --git a/packages/caravan-descriptors/src/descriptors.ts b/packages/caravan-descriptors/src/descriptors.ts new file mode 100644 index 00000000..d620059c --- /dev/null +++ b/packages/caravan-descriptors/src/descriptors.ts @@ -0,0 +1,126 @@ +import { getRustAPI } from "./wasmLoader"; +import { + BitcoinNetwork, + MultisigAddressType, + validateExtendedPublicKeyForNetwork, +} from "@caravan/bitcoin"; + +// TODO: should come from unchained-wallets +export interface KeyOrigin { + xfp: string; + bip32Path: string; + xpub: string; +} + +// should be a 32 byte hex string +export type PolicyHmac = string; +// should be an 8 byte hex string +export type RootFingerprint = string; + +export interface MultisigWalletConfig { + requiredSigners: number; + addressType: MultisigAddressType; + keyOrigins: KeyOrigin[]; + network: BitcoinNetwork | "bitcoin"; +} + +export const decodeDescriptors = async ( + internal: string, + external: string, + network?: BitcoinNetwork, +): Promise => { + const { ExtendedDescriptor, CaravanConfig, Network } = await getRustAPI(); + const external_descriptor = ExtendedDescriptor.from_str(external); + const internal_descriptor = ExtendedDescriptor.from_str(internal); + let _network: BitcoinNetwork | "bitcoin"; + if (network === "mainnet" || !network) { + _network = "bitcoin"; + } else { + _network = network; + } + const config = CaravanConfig.new( + Network.from_str(_network), + external_descriptor, + internal_descriptor, + "test1", + "public", + ); + const configObj = JSON.parse(config.to_string_pretty()); + const requiredSigners = configObj.quorum.requiredSigners; + const keyOrigins = configObj.extendedPublicKeys.map( + ({ bip32Path, xpub, xfp }: KeyOrigin): KeyOrigin => { + if (network) { + const error = validateExtendedPublicKeyForNetwork(xpub, network); + if (error) { + throw new Error( + `xpubs do not match expected network ${network}: ${error}`, + ); + } + } + return { + bip32Path, + xpub, + xfp, + }; + }, + ); + + return { + addressType: config.address_type() as MultisigAddressType, + requiredSigners, + keyOrigins, + network: _network, + }; +}; + +export const encodeDescriptors = async ( + config: MultisigWalletConfig, +): Promise<{ receive: string; change: string }> => { + const bdk = await getRustAPI(); + const { MultisigWalletConfig: RsWalletConfig } = bdk; + const wallet = RsWalletConfig.from_str(JSON.stringify(config)); + + return { + receive: wallet.external_descriptor().to_string(), + change: wallet.internal_descriptor().to_string(), + }; +}; + +const checksumRegex = /#[0-9a-zA-Z]{8}/g; + +export const getChecksum = async (descriptor: string) => { + // let's just check that the descriptor is valid + try { + await getWalletFromDescriptor(descriptor); + } catch (e) { + if (e instanceof Error) { + throw new Error(`Invalid descriptor: ${e.message}`); + } else { + throw e; + } + } + const checksum = descriptor.match(checksumRegex); + const pieces = descriptor.split("#"); + if (!checksum || pieces.length !== 2) { + throw new Error("Could not find valid checksum"); + } + return pieces[1]; +}; + +export const getWalletFromDescriptor = async ( + descriptor: string, + network?: BitcoinNetwork, +): Promise => { + let internal: string = "", + external: string = ""; + if (descriptor.includes("0/*")) { + external = descriptor; + internal = descriptor.replace(/0\/\*/g, "1/*").replace(checksumRegex, ""); + } else if (descriptor.includes("1/*")) { + internal = descriptor; + external = descriptor.replace(/1\/\*/g, "0/*").replace(checksumRegex, ""); + } + return await decodeDescriptors(internal, external, network); +}; + +export default { encodeDescriptors, decodeDescriptors }; diff --git a/packages/caravan-descriptors/src/index.ts b/packages/caravan-descriptors/src/index.ts new file mode 100644 index 00000000..28ad7a36 --- /dev/null +++ b/packages/caravan-descriptors/src/index.ts @@ -0,0 +1 @@ +export * from "./descriptors"; diff --git a/packages/caravan-descriptors/src/wasmLoader.ts b/packages/caravan-descriptors/src/wasmLoader.ts new file mode 100644 index 00000000..cf6044b3 --- /dev/null +++ b/packages/caravan-descriptors/src/wasmLoader.ts @@ -0,0 +1,46 @@ +type WasmWebModule = typeof import("../caravan-rs/pkg-web/caravan_rs"); +type WasmNodeModule = typeof import("../caravan-rs/pkg-nodejs/caravan_rs"); + +let init: WasmWebModule["default"]; +let ExtendedDescriptor: + | WasmWebModule["ExtendedDescriptor"] + | WasmNodeModule["ExtendedDescriptor"]; +let CaravanConfig: + | WasmWebModule["CaravanConfig"] + | WasmNodeModule["CaravanConfig"]; +let Network: WasmWebModule["Network"] | WasmNodeModule["Network"]; +let MultisigWalletConfig: + | WasmWebModule["MultisigWalletConfig"] + | WasmNodeModule["MultisigWalletConfig"]; + +async function initWasm() { + if (typeof window !== "undefined") { + // Browser environment + const module = await import("../caravan-rs/pkg-web/caravan_rs"); + ({ + default: init, + ExtendedDescriptor, + CaravanConfig, + Network, + MultisigWalletConfig, + } = module); + // need to tell the js where the wasm module is to init with + // this ends up getting called from built js code from wasm-pack build + // so it's relative to that file + await init(); + } else { + // Node.js environment + const module = await import("../caravan-rs/pkg-nodejs/caravan_rs"); + ({ ExtendedDescriptor, CaravanConfig, Network, MultisigWalletConfig } = + module); + } +} + +// export default bdkWasm; +export async function getRustAPI() { + if (!init) { + await initWasm(); + } + + return { ExtendedDescriptor, CaravanConfig, Network, MultisigWalletConfig }; +} diff --git a/packages/caravan-descriptors/tsconfig.json b/packages/caravan-descriptors/tsconfig.json new file mode 100644 index 00000000..283eaf80 --- /dev/null +++ b/packages/caravan-descriptors/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "@caravan/typescript-config/base.json", + "compilerOptions": { + "declaration": true, + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "module": "es2020", + "moduleResolution": "node", + "rootDir": ".", /* Specify the root folder within your source files. */ + "baseUrl": ".", /* Specify the base directory to resolve non-relative module names. */ + "outDir": "dist/", /* Specify an output folder for all emitted files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "src/**/*.ts", + ] +} diff --git a/packages/caravan-psbt/.prettierrc b/packages/caravan-psbt/.prettierrc new file mode 100644 index 00000000..222861c3 --- /dev/null +++ b/packages/caravan-psbt/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/packages/caravan-psbt/package.json b/packages/caravan-psbt/package.json index a26ea2b8..7b69ef70 100644 --- a/packages/caravan-psbt/package.json +++ b/packages/caravan-psbt/package.json @@ -13,6 +13,8 @@ "build": "tsup src/index.ts --format cjs,esm --dts", "dev": "npm run build -- --watch", "test": "jest src", + "lint": "eslint src", + "ci": "npm run test", "test:watch": "jest --watch src", "test:debug": "node --inspect-brk ../../node_modules/.bin/jest --runInBand" }, diff --git a/packages/caravan-wallets/package.json b/packages/caravan-wallets/package.json index 89170dec..a70d143a 100644 --- a/packages/caravan-wallets/package.json +++ b/packages/caravan-wallets/package.json @@ -66,6 +66,7 @@ "scripts": { "compile-images": "node images/compile.js", "build": "tsup src/index.ts --format cjs,esm --dts", + "ci": "npm run lint && npm run test", "dev": "npm run build -- --watch", "test": "jest src", "test:watch": "jest --watch src", diff --git a/turbo.json b/turbo.json index c21948f2..b1593073 100644 --- a/turbo.json +++ b/turbo.json @@ -30,6 +30,12 @@ , "deploy": { "dependsOn": ["build", "test", "lint"] + }, + "preview": { + "dependsOn": ["build"] + }, + "ci": { + "dependsOn": ["^build"] } } } diff --git a/turbo/generators/config.js b/turbo/generators/config.js index 82e47125..464a756a 100644 --- a/turbo/generators/config.js +++ b/turbo/generators/config.js @@ -43,6 +43,12 @@ module.exports = function(plop) { message: "Do you want a .eslintrc.js?", default: true, }, + { + type: "confirm", + name: "prettier", + message: "Do you want a .prettierrc?", + default: true, + }, ], actions: function(data) { const actions = [ diff --git a/turbo/generators/templates/.prettierrc.hbs b/turbo/generators/templates/.prettierrc.hbs new file mode 100644 index 00000000..222861c3 --- /dev/null +++ b/turbo/generators/templates/.prettierrc.hbs @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/turbo/generators/templates/package.json.hbs b/turbo/generators/templates/package.json.hbs index 87093116..402c3560 100644 --- a/turbo/generators/templates/package.json.hbs +++ b/turbo/generators/templates/package.json.hbs @@ -6,8 +6,10 @@ "scripts": { "build": "tsup src/index.ts --format cjs,esm --dts", "dev": "npm run build -- --watch", + "lint": "eslint --ext .js,.jsx,.ts,.tsx src/", + "ci": "npm run lint && npm run test", "test": "jest src", "test:watch": "jest --watch src", - "test:debug": "node --inspect-brk ../../node_modules/.bin/jest --runInBand" + "test:debug": "node --inspect-brk ../../node_modules/.bin/jest --runInBand", } }