diff --git a/package-lock.json b/package-lock.json index ff7884104a1..63adee80c00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -156,8 +156,8 @@ "resolved": "packages/wasm-utxo", "link": true }, - "node_modules/@bitgo/wasm-utxo-ui": { - "resolved": "packages/wasm-utxo-ui", + "node_modules/@bitgo/wasm-web-ui": { + "resolved": "packages/webui", "link": true }, "node_modules/@brandonblack/musig": { @@ -2931,7 +2931,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -3310,7 +3309,6 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -3541,7 +3539,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4642,7 +4639,6 @@ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", @@ -5118,7 +5114,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5181,7 +5176,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5807,7 +5801,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001646", "electron-to-chromium": "^1.5.4", @@ -7950,7 +7943,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8793,8 +8785,7 @@ "node_modules/fp-ts": { "version": "2.16.9", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.9.tgz", - "integrity": "sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==", - "peer": true + "integrity": "sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==" }, "node_modules/fresh": { "version": "0.5.2", @@ -10235,7 +10226,6 @@ "version": "2.2.21", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.21.tgz", "integrity": "sha512-zz2Z69v9ZIC3mMLYWIeoUcwWD6f+O7yP92FMVVaXEOSZH1jnVBmET/urd/uoarD1WGBY4rCj8TAyMPzsGNzMFQ==", - "peer": true, "peerDependencies": { "fp-ts": "^2.5.0" } @@ -11734,7 +11724,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -12501,7 +12490,6 @@ "version": "2.3.13", "resolved": "https://registry.npmjs.org/monocle-ts/-/monocle-ts-2.3.13.tgz", "integrity": "sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ==", - "peer": true, "peerDependencies": { "fp-ts": "^2.5.0" } @@ -12805,7 +12793,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/newtype-ts/-/newtype-ts-0.3.5.tgz", "integrity": "sha512-v83UEQMlVR75yf1OUdoSFssjitxzjZlqBAjiGQ4WJaML8Jdc68LJ+BaSAXUmKY4bNzp7hygkKLYTsDi14PxI2g==", - "peer": true, "peerDependencies": { "fp-ts": "^2.0.0", "monocle-ts": "^2.0.0" @@ -15211,7 +15198,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15363,7 +15349,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -16670,7 +16655,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.1", @@ -17781,7 +17765,6 @@ "integrity": "sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -19894,7 +19877,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -20077,8 +20059,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/tsx": { "version": "4.20.6", @@ -20231,7 +20212,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20600,7 +20580,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -20647,7 +20626,6 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -20731,7 +20709,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -20844,7 +20821,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -21338,6 +21314,30 @@ "packages/wasm-utxo-ui": { "name": "@bitgo/wasm-utxo-ui", "version": "0.1.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@bitgo/wasm-utxo": "*", + "fp-ts": "^2.16.8", + "io-ts": "^2.2.21", + "io-ts-types": "^0.5.19", + "monocle-ts": "^2.3.13", + "newtype-ts": "^0.3.5" + }, + "devDependencies": { + "css-loader": "^7.1.2", + "gh-pages": "^6.1.1", + "html-webpack-plugin": "^5.6.0", + "style-loader": "^4.0.0", + "ts-loader": "^9.1.2", + "webpack": "^5.94.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.0.4" + } + }, + "packages/webui": { + "name": "@bitgo/wasm-web-ui", + "version": "0.1.0", "license": "MIT", "dependencies": { "@bitgo/wasm-utxo": "*", diff --git a/packages/wasm-utxo-ui/src/codec.ts b/packages/wasm-utxo-ui/src/codec.ts deleted file mode 100644 index b42c751e779..00000000000 --- a/packages/wasm-utxo-ui/src/codec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as t from "io-ts"; -import { isLeft } from "fp-ts/Either"; -import { PathReporter } from "io-ts/PathReporter"; - -export function decodeOrThrow(codec: t.Type, value: I): A { - const result = codec.decode(value); - if (isLeft(result)) { - throw new Error(PathReporter.report(result).join("\n")); - } - return result.right; -} - -export const ScriptContext = t.union([ - t.literal("tap"), - t.literal("segwitv0"), - t.literal("legacy"), -]); - -export type ScriptContext = t.TypeOf; diff --git a/packages/wasm-utxo-ui/src/descriptorFixtures.ts b/packages/wasm-utxo-ui/src/descriptorFixtures.ts deleted file mode 100644 index 2ac88200de3..00000000000 --- a/packages/wasm-utxo-ui/src/descriptorFixtures.ts +++ /dev/null @@ -1,670 +0,0 @@ -export const fixtures = { - valid: [ - { - descriptor: "pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", - script: "2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac", - checksumRequired: false, - }, - { - descriptor: "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", - script: "2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac", - checksumRequired: false, - }, - { - descriptor: "pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", - script: "76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac", - checksumRequired: false, - }, - { - descriptor: - "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", - script: "76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac", - checksumRequired: false, - }, - { - descriptor: "wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", - script: "00149a1c78a507689f6f54b847ad1cef1e614ee23f1e", - checksumRequired: false, - }, - { - descriptor: "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", - script: "00149a1c78a507689f6f54b847ad1cef1e614ee23f1e", - checksumRequired: false, - }, - { - descriptor: "sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", - script: "a91484ab21b1b2fd065d4504ff693d832434b6108d7b87", - checksumRequired: false, - }, - { - descriptor: "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", - script: "a91484ab21b1b2fd065d4504ff693d832434b6108d7b87", - checksumRequired: false, - }, - { - descriptor: "pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", - script: - "4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac", - checksumRequired: false, - }, - { - descriptor: - "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", - script: - "4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac", - checksumRequired: false, - }, - { - descriptor: "pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", - script: "76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac", - checksumRequired: false, - }, - { - descriptor: - "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", - script: "76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac", - checksumRequired: false, - }, - { - descriptor: "sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", - script: "a9141857af51a5e516552b3086430fd8ce55f7c1a52487", - checksumRequired: false, - }, - { - descriptor: "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", - script: "a9141857af51a5e516552b3086430fd8ce55f7c1a52487", - checksumRequired: false, - }, - { - descriptor: "sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", - script: "a9141a31ad23bf49c247dd531a623c2ef57da3c400c587", - checksumRequired: false, - }, - { - descriptor: "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", - script: "a9141a31ad23bf49c247dd531a623c2ef57da3c400c587", - checksumRequired: false, - }, - { - descriptor: "wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", - script: "00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa", - checksumRequired: false, - }, - { - descriptor: "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", - script: "00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa", - checksumRequired: false, - }, - { - descriptor: "wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", - script: "0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b", - checksumRequired: false, - }, - { - descriptor: "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", - script: "0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b", - checksumRequired: false, - }, - { - descriptor: "sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", - script: "a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787", - checksumRequired: false, - }, - { - descriptor: "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", - script: "a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787", - checksumRequired: false, - }, - { - descriptor: "sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", - script: "a914b61b92e2ca21bac1e72a3ab859a742982bea960a87", - checksumRequired: false, - }, - { - descriptor: - "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", - script: "a914b61b92e2ca21bac1e72a3ab859a742982bea960a87", - checksumRequired: false, - }, - { - descriptor: - "pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", - script: "210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac", - checksumRequired: false, - }, - { - descriptor: - "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", - script: "210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac", - checksumRequired: false, - }, - { - descriptor: - "pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", - script: "76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac", - checksumRequired: false, - }, - { - descriptor: - "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", - script: "0014326b2249e3a25d5dc60935f044ee835d090ba859", - index: 0, - checksumRequired: false, - }, - { - descriptor: - "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", - script: "0014326b2249e3a25d5dc60935f044ee835d090ba859", - index: 0, - checksumRequired: false, - }, - { - descriptor: - "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", - script: "0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7", - index: 1, - checksumRequired: false, - }, - { - descriptor: - "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", - script: "0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7", - index: 1, - checksumRequired: false, - }, - { - descriptor: - "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", - script: "00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27", - index: 2, - checksumRequired: false, - }, - { - descriptor: - "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", - script: "00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27", - index: 2, - checksumRequired: false, - }, - { - descriptor: - "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - script: "a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87", - index: 0, - checksumRequired: false, - }, - { - descriptor: - "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - script: "a914bed59fc0024fae941d6e20a3b44a109ae740129287", - index: 1, - checksumRequired: false, - }, - { - descriptor: - "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - script: "a9148483aa1116eb9c05c482a72bada4b1db24af654387", - index: 2, - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", - script: "0020cb155486048b23a6da976d4c6fe071a2dbc8a7b57aaf225b8955f2e2a27b5f00", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", - script: "0020cb155486048b23a6da976d4c6fe071a2dbc8a7b57aaf225b8955f2e2a27b5f00", - checksumRequired: false, - }, - { - descriptor: - "pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", - script: "76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", - script: "a91445a9a622a8b0a1269944be477640eedc447bbd8487", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", - script: "a91445a9a622a8b0a1269944be477640eedc447bbd8487", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - script: "0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f", - index: 0, - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - script: "002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203", - index: 1, - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - script: "0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c", - index: 2, - checksumRequired: false, - }, - { - descriptor: - "sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))", - script: "a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87", - checksumRequired: false, - }, - { - descriptor: - "sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", - script: "a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))", - script: "0020376bd8344b8b6ebe504ff85ef743eaa1aa9272178223bcb6887e9378efb341ac", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", - script: "0020376bd8344b8b6ebe504ff85ef743eaa1aa9272178223bcb6887e9378efb341ac", - checksumRequired: false, - }, - { - descriptor: - "sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv)))", - script: "a914c2c9c510e9d7f92fd6131e94803a8d34a8ef675e87", - checksumRequired: false, - }, - { - descriptor: - "sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", - script: "a914c2c9c510e9d7f92fd6131e94803a8d34a8ef675e87", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", - script: "a91445a9a622a8b0a1269944be477640eedc447bbd8487", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", - script: "a91445a9a622a8b0a1269944be477640eedc447bbd8487", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", - script: "a91445a9a622a8b0a1269944be477640eedc447bbd8487", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", - script: "a91445a9a622a8b0a1269944be477640eedc447bbd8487", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)))", - script: "0020acf425291b98a1d7e0d4690139442abc289175be32ef1f75945e339924246d73", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", - script: "0020acf425291b98a1d7e0d4690139442abc289175be32ef1f75945e339924246d73", - checksumRequired: false, - }, - { - descriptor: - "sh(wsh(thresh(1,pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),a:and_n(multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0),n:older(2)))))", - script: "a914767e9119ff3b3ac0cb6dcfe21de1842ccf85f1c487", - checksumRequired: false, - }, - { - descriptor: - "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", - script: "a914767e9119ff3b3ac0cb6dcfe21de1842ccf85f1c487", - checksumRequired: false, - }, - ], - invalid: [ - { - descriptor: "sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", - checksumRequired: false, - }, - { - descriptor: "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", - checksumRequired: false, - }, - { - descriptor: "pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", - checksumRequired: false, - }, - { - descriptor: - "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", - checksumRequired: false, - }, - { - descriptor: "pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", - checksumRequired: false, - }, - { - descriptor: - "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", - checksumRequired: false, - }, - { - descriptor: "wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", - checksumRequired: false, - }, - { - descriptor: - "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", - checksumRequired: false, - }, - { - descriptor: "wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", - checksumRequired: false, - }, - { - descriptor: - "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", - checksumRequired: false, - }, - { - descriptor: "sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", - checksumRequired: false, - }, - { - descriptor: - "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", - checksumRequired: false, - }, - { - descriptor: - "pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", - checksumRequired: false, - }, - { - descriptor: - "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", - checksumRequired: false, - }, - { - descriptor: - "pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", - checksumRequired: false, - }, - { - descriptor: - "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", - checksumRequired: false, - }, - { - descriptor: "sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", - checksumRequired: false, - }, - { - descriptor: "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", - checksumRequired: false, - }, - { - descriptor: "wsh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", - checksumRequired: false, - }, - { - descriptor: "wsh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", - checksumRequired: false, - }, - { - descriptor: "wsh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", - checksumRequired: false, - }, - { - descriptor: "wsh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", - checksumRequired: false, - }, - { - descriptor: "wsh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", - checksumRequired: false, - }, - { - descriptor: "wsh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", - checksumRequired: false, - }, - { - descriptor: "sh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", - checksumRequired: false, - }, - { - descriptor: "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", - checksumRequired: false, - }, - { - descriptor: "wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", - checksumRequired: false, - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5", - }, - { - descriptor: - "sh(multi(3,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", - }, - { - descriptor: - "sh(multi(3,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggssrxfy", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjq09x4t", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))##ggssrxfy", - }, - { - descriptor: - "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))##tjq09x4t", - }, - { - descriptor: "", - checksumRequired: false, - }, - { - descriptor: "addr(asdf)", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))#abcdef12", - }, - { - descriptor: - "wsh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))#abcdef12", - }, - { - descriptor: - "sh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", - checksumRequired: false, - }, - { - descriptor: - "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", - checksumRequired: false, - }, - { - descriptor: "", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_b(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_b(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_v(vc:andor(v:pk_k(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_v(vc:andor(v:pk_k(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", - checksumRequired: false, - }, - { - descriptor: "wsh(or_i(older(1),pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(or_i(older(1),pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(or_b(sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_b(or_b(pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),s:pk(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", - checksumRequired: false, - }, - { - descriptor: - "wsh(and_b(or_b(pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", - checksumRequired: false, - }, - ], -}; diff --git a/packages/wasm-utxo-ui/src/hex.ts b/packages/wasm-utxo-ui/src/hex.ts deleted file mode 100644 index 9f374e012c1..00000000000 --- a/packages/wasm-utxo-ui/src/hex.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function toHex(bytes: Uint8Array): string { - return Array.from(bytes) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join(""); -} - -export function fromHex(hex: string): Uint8Array { - // strip whitespace - hex = hex.replace(/\s/g, ""); - if (hex.length % 2 !== 0) { - throw new Error("hex string must have an even number of characters"); - } - const bytes = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) { - bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16); - } - return bytes; -} diff --git a/packages/wasm-utxo-ui/src/html.ts b/packages/wasm-utxo-ui/src/html.ts deleted file mode 100644 index 18bb24b0928..00000000000 --- a/packages/wasm-utxo-ui/src/html.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function getElement(id: string, type: { new (): T }): T { - const element = document.getElementById(id); - if (!element) { - throw new Error(`Element with id "${id}" not found`); - } - if (!(element instanceof type)) { - throw new Error(`Element with id "${id}" is not a ${type.name}`); - } - return element; -} diff --git a/packages/wasm-utxo-ui/src/htmlAST.ts b/packages/wasm-utxo-ui/src/htmlAST.ts deleted file mode 100644 index 8f66f3c1cdb..00000000000 --- a/packages/wasm-utxo-ui/src/htmlAST.ts +++ /dev/null @@ -1,53 +0,0 @@ -function nodeErr(message: string): HTMLElement { - const node = document.createElement("div"); - node.innerText = message; - return node; -} - -function nodeText(text: string): HTMLElement { - const node = document.createElement("span"); - node.innerText = text; - return node; -} - -function nodeArray(array: unknown[]): HTMLElement { - const node = document.createElement("ul"); - array.forEach((item) => { - const itemNode = document.createElement("li"); - itemNode.appendChild(getHtmlForAst(item)); - node.appendChild(itemNode); - }); - return node; -} - -function nodeObject(obj: Record): HTMLElement { - const node = document.createElement("ul"); - Object.entries(obj).forEach(([key, value]) => { - const keyNode = document.createElement("li"); - keyNode.innerText = key; - node.appendChild(keyNode); - node.appendChild(getHtmlForAst(value)); - }); - return node; -} - -export function getHtmlForAst(ast: unknown): HTMLElement { - if ( - ast === null || - ast === undefined || - typeof ast === "string" || - typeof ast === "boolean" || - typeof ast === "bigint" || - typeof ast === "symbol" || - typeof ast === "number" - ) { - return nodeText(String(ast)); - } - if (Array.isArray(ast)) { - return nodeArray(ast); - } - if (typeof ast === "object") { - return nodeObject(ast as Record); - } - throw new Error(`unknown ast type ${JSON.stringify(ast)}`); -} diff --git a/packages/wasm-utxo-ui/src/index.html b/packages/wasm-utxo-ui/src/index.html deleted file mode 100644 index 56a4f97a21c..00000000000 --- a/packages/wasm-utxo-ui/src/index.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - Wasm-Miniscript Playground - - - -
-
-

Descriptor

-
- - - - -
- -
-
-

Descriptor AST

-
AST
-
-
-

Miniscript

-
- - -
- -
-
-

Miniscript AST

-
AST
-
-
-

Bitcoin Address

- - -
-
-

Bitcoin Script (hex)

- -
-
-

Bitcoin Script (asm)

- -
-
-
Status
- - diff --git a/packages/wasm-utxo-ui/src/index.ts b/packages/wasm-utxo-ui/src/index.ts deleted file mode 100644 index 8dc17c8727a..00000000000 --- a/packages/wasm-utxo-ui/src/index.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { address, CoinName, Descriptor, Miniscript, ScriptContext } from "@bitgo/wasm-utxo"; - -import "./style.css"; - -import { getElement } from "./html"; -import { buildOptions, getOptions, Options } from "./options"; -import { getHtmlForAst } from "./htmlAST"; -import { fromHex, toHex } from "./hex"; -import { getShare, setShare, Share } from "./sharing"; - -function createMiniscriptFromBitcoinScriptDetectScriptContext( - script: Uint8Array, -): [Miniscript, ScriptContext] { - const formats = ["tap", "segwitv0", "legacy"] as const; - for (const format of formats) { - try { - return [Miniscript.fromBitcoinScript(script, format), format]; - } catch (e) { - // ignore - } - } - throw new Error(`could not create miniscript from bitcoin script: ${toHex(script)}`); -} - -const elEditDescriptor = getElement("edit-descriptor", HTMLTextAreaElement); -const elEditMiniscript = getElement("edit-miniscript", HTMLTextAreaElement); -const elEditBitcoinScriptHex = getElement("edit-bitcoin-script-hex", HTMLTextAreaElement); -const elEditBitcoinScriptAsm = getElement("edit-bitcoin-script-asm", HTMLTextAreaElement); -const elScriptPubkeyBytes = getElement("bitcoin-script-pubkey-hex", HTMLTextAreaElement); -const elAddress = getElement("bitcoin-script-pubkey-address", HTMLTextAreaElement); -const elDescriptorAst = getElement("output-descriptor-ast", HTMLDivElement); -const elMiniscriptAst = getElement("output-miniscript-ast", HTMLDivElement); -const elStatus = getElement("status", HTMLElement); - -function toAddress(scriptPubkeyBytes: Uint8Array, coin: CoinName) { - return address.fromOutputScriptWithCoin(scriptPubkeyBytes, coin); -} - -function setHtmlContent(el: HTMLElement, content: HTMLElement | undefined) { - el.innerHTML = ""; - if (content) { - el.appendChild(content); - } -} - -function applyUpdateWith( - changedEl: HTMLElement, - { - descriptor, - miniscript, - scriptBytes, - scriptPubkeyBytes, - scriptAsm, - }: { - descriptor: Descriptor | null | undefined; - miniscript: Miniscript | null | undefined; - scriptBytes: Uint8Array | null | undefined; - scriptPubkeyBytes?: Uint8Array | null | undefined; - scriptAsm: string | null | undefined; - }, - options: Options, -) { - if (descriptor) { - if (scriptBytes === undefined) { - scriptBytes = descriptor.encode(); - } - if (scriptAsm === undefined) { - scriptAsm = descriptor.toAsmString(); - } - setShare({ descriptor }); - } else if (descriptor === null) { - elEditDescriptor.value = ""; - setHtmlContent(elDescriptorAst, undefined); - } - - if (miniscript) { - if (scriptBytes === undefined) { - scriptBytes = miniscript.encode(); - } - if (scriptAsm === undefined) { - scriptAsm = miniscript.toAsmString(); - } - elEditMiniscript.value = miniscript.toString(); - setHtmlContent(elMiniscriptAst, getHtmlForAst(miniscript.node())); - if (!descriptor) { - setShare({ miniscript, scriptContext: options.scriptContext }); - } - } else if (miniscript === null) { - elEditMiniscript.value = ""; - setHtmlContent(elMiniscriptAst, undefined); - } else { - if (scriptBytes) { - try { - const [ms, scriptContext] = - createMiniscriptFromBitcoinScriptDetectScriptContext(scriptBytes); - getElement("input-script-context", HTMLSelectElement).value = scriptContext; - return applyUpdateWith( - changedEl, - { descriptor, miniscript: ms, scriptBytes, scriptAsm, scriptPubkeyBytes }, - options, - ); - } catch (e) { - applyUpdateWith( - changedEl, - { descriptor, miniscript: null, scriptBytes, scriptAsm: null, scriptPubkeyBytes }, - options, - ); - if (!descriptor) { - setShare({ scriptBytes }); - } - throw e; - } - } - - if (scriptBytes === undefined) { - applyUpdateWith( - changedEl, - { descriptor, miniscript, scriptBytes: null, scriptAsm: null, scriptPubkeyBytes }, - options, - ); - } - } - - if (scriptBytes) { - elEditBitcoinScriptHex.value = toHex(scriptBytes); - } else if (scriptBytes === null) { - elEditBitcoinScriptHex.value = ""; - } - - if (scriptAsm) { - elEditBitcoinScriptAsm.value = scriptAsm; - } else if (scriptAsm === null) { - elEditBitcoinScriptAsm.value = ""; - } - - if (scriptPubkeyBytes) { - elScriptPubkeyBytes.value = toHex(scriptPubkeyBytes); - try { - elAddress.value = toAddress(scriptPubkeyBytes, "btc"); - } catch (e: any) { - elAddress.value = `error: ${e.message}`; - } - } else if (scriptPubkeyBytes === null) { - elScriptPubkeyBytes.value = ""; - elAddress.value = ""; - } - - elStatus.innerText = "Status: OK"; -} - -function applyUpdate(changedEl: HTMLElement, options: Options) { - console.log(changedEl, options); - - if (changedEl === getElement("input-example", HTMLSelectElement)) { - elEditDescriptor.value = options.example; - return applyUpdate(elEditDescriptor, options); - } - - if ( - changedEl === elEditDescriptor || - changedEl === getElement("input-derivation-index", HTMLInputElement) - ) { - const descriptor = Descriptor.fromString(elEditDescriptor.value, "derivable"); - setHtmlContent(elDescriptorAst, getHtmlForAst(descriptor.node())); - const descriptorAtIndex = descriptor.atDerivationIndex(options.derivationIndex); - return applyUpdateWith( - changedEl, - { - descriptor: descriptorAtIndex, - miniscript: undefined, - scriptBytes: descriptorAtIndex.encode(), - scriptAsm: descriptorAtIndex.toAsmString(), - scriptPubkeyBytes: descriptorAtIndex.scriptPubkey(), - }, - options, - ); - } - - if ( - changedEl === elEditMiniscript || - changedEl === getElement("input-script-context", HTMLSelectElement) - ) { - try { - const script = Miniscript.fromString(elEditMiniscript.value, options.scriptContext); - return applyUpdateWith( - changedEl, - { descriptor: null, miniscript: script, scriptBytes: undefined, scriptAsm: undefined }, - options, - ); - } catch (e) { - applyUpdateWith( - changedEl, - { descriptor: null, miniscript: undefined, scriptBytes: null, scriptAsm: null }, - options, - ); - throw e; - } - } - - if (changedEl === elEditBitcoinScriptHex) { - return applyUpdateWith( - changedEl, - { - descriptor: undefined, - miniscript: undefined, - scriptBytes: fromHex(elEditBitcoinScriptHex.value), - scriptAsm: undefined, - }, - options, - ); - } - - throw new Error(`unexpected element ${changedEl.id}`); -} - -function update(changedEl: HTMLElement, options: Options) { - try { - applyUpdate(changedEl, options); - } catch (e: any) { - console.error(e); - elStatus.innerText = `Status: Error: ${e}`; - } -} - -function bindUpdate(el: HTMLElement, event: string) { - el.addEventListener(event, () => { - update(el, getOptions()); - }); -} - -buildOptions(); - -[...document.querySelectorAll("select"), ...document.querySelectorAll("input")].forEach((el) => { - bindUpdate(el, "change"); -}); - -bindUpdate(elEditDescriptor, "input"); -bindUpdate(elEditMiniscript, "input"); -bindUpdate(elEditBitcoinScriptHex, "input"); - -function updateFromShare(share: Share) { - if ("descriptor" in share) { - elEditDescriptor.value = share.descriptor.toString(); - return update(elEditDescriptor, getOptions()); - } - if ("miniscript" in share) { - elEditMiniscript.value = share.miniscript.toString(); - getElement("input-script-context", HTMLSelectElement).value = share.scriptContext; - return update(elEditMiniscript, getOptions()); - } - if ("scriptBytes" in share) { - elEditBitcoinScriptHex.value = toHex(share.scriptBytes); - return update(elEditBitcoinScriptHex, getOptions()); - } -} - -let share; -try { - share = getShare(); -} catch (e) { - console.error(e); -} - -if (share) { - updateFromShare(share); -} else { - update(getElement("input-example", HTMLSelectElement), getOptions()); -} - -window.addEventListener("error", (event) => { - console.error(event); - event.preventDefault(); -}); diff --git a/packages/wasm-utxo-ui/src/options.ts b/packages/wasm-utxo-ui/src/options.ts deleted file mode 100644 index 9438b882ae8..00000000000 --- a/packages/wasm-utxo-ui/src/options.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as t from "io-ts"; -import * as tt from "io-ts-types"; -import { fixtures } from "./descriptorFixtures"; -import { getElement } from "./html"; -import { decodeOrThrow, ScriptContext } from "./codec"; - -function getOption(id: string): unknown { - const el = getElement(id, HTMLElement); - if (el instanceof HTMLSelectElement) { - return el.value; - } - if (el instanceof HTMLInputElement) { - return el.value; - } - throw new Error(`element with id ${id} is not a select or input`); -} - -function showOutput(output: string) { - const outputElement = document.getElementById("output"); - if (!outputElement) { - throw new Error("output element not found"); - } - outputElement.innerText = output; -} - -type DropdownItem = { - label: string; - value: string; -}; - -export function getExampleOptions(index: number | undefined = undefined): DropdownItem[] { - let valid = fixtures.valid; - if (index !== undefined) { - valid = [valid[index]]; - } - return valid.map((fixture, i) => { - return { - label: `Example ${i}: ${fixture.descriptor.slice(0, 16)}...`, - value: fixture.descriptor, - }; - }); -} - -function buildSelect(elSelect: HTMLSelectElement, options: DropdownItem[]): void { - options.forEach((option) => { - const optionElement = document.createElement("option"); - optionElement.value = option.value; - optionElement.innerText = option.label; - elSelect.appendChild(optionElement); - }); -} - -function getOptionsFromUnion(u: t.UnionType): DropdownItem[] { - return u.types.map((e) => { - if (e instanceof t.LiteralType) { - return { label: e.value, value: e.value }; - } - throw new Error(`unexpected type ${t}`); - }); -} - -export function getOptionsFromType(c: t.Type): DropdownItem[] { - if (c instanceof t.UnionType) { - return getOptionsFromUnion(c); - } - throw new Error(`unexpected type ${c}`); -} - -export const Options = t.type({ - scriptContext: ScriptContext, - example: t.string, - derivationIndex: tt.NumberFromString, -}); - -export type Options = t.TypeOf; - -export function buildOptions(): void { - buildSelect( - getElement("input-script-context", HTMLSelectElement), - getOptionsFromType(ScriptContext), - ); - buildSelect(getElement("input-example", HTMLSelectElement), getExampleOptions()); -} - -export function getOptions(): Options { - return decodeOrThrow(Options, { - scriptContext: getOption("input-script-context"), - example: getOption("input-example"), - derivationIndex: getOption("input-derivation-index"), - }); -} diff --git a/packages/wasm-utxo-ui/src/sharing.ts b/packages/wasm-utxo-ui/src/sharing.ts deleted file mode 100644 index 6847d35769f..00000000000 --- a/packages/wasm-utxo-ui/src/sharing.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as t from "io-ts"; -import { Descriptor, Miniscript } from "@bitgo/wasm-utxo"; -import { fromHex, toHex } from "./hex"; -import { ScriptContext } from "./codec"; - -export type Share = - | { descriptor: Descriptor } - | { miniscript: Miniscript; scriptContext: ScriptContext } - | { scriptBytes: Uint8Array }; - -const ShareJson = t.union([ - t.type({ d: t.string }), - t.type({ ms: t.string, sc: ScriptContext }), - t.type({ sb: t.string }), -]); - -type ShareJson = t.TypeOf; - -export function setShare(share: Share): void { - const shareUrl = new URL(window.location.href); - let shareHash: Record = {}; - if ("descriptor" in share) { - // set fragment - shareHash = { d: share.descriptor.toString() }; - } - if ("miniscript" in share) { - shareHash = { ms: share.miniscript.toString(), sc: share.scriptContext }; - } - if ("scriptBytes" in share) { - shareHash = { sb: toHex(share.scriptBytes) }; - } - shareUrl.hash = JSON.stringify(shareHash); - window.history.replaceState({}, "", shareUrl.toString()); -} - -export function getShare( - v: ShareJson | Record | string | undefined = undefined, -): Share | undefined { - if (v === undefined) { - const shareUrl = new URL(window.location.href); - try { - return getShare(decodeURI(shareUrl.hash.slice(1))); - } catch (e) { - console.error("Error decoding share URL", shareUrl, e); - throw e; - } - } - - if (typeof v === "string") { - try { - return getShare(JSON.parse(v)); - } catch (e) { - console.error("Error parsing share JSON", v, e); - throw e; - } - } - - if (typeof v === "object") { - if (!ShareJson.is(v)) { - console.error("Invalid share JSON", v); - return undefined; - } - - if ("d" in v) { - return { descriptor: Descriptor.fromString(v.d, "derivable") }; - } - if ("ms" in v && "sc" in v) { - return { - miniscript: Miniscript.fromString(v.ms, v.sc), - scriptContext: v.sc, - }; - } - if ("sb" in v) { - return { scriptBytes: fromHex(v.sb) }; - } - } - - console.error("Invalid share", v); -} diff --git a/packages/wasm-utxo-ui/src/style.css b/packages/wasm-utxo-ui/src/style.css deleted file mode 100644 index 3e8150146f4..00000000000 --- a/packages/wasm-utxo-ui/src/style.css +++ /dev/null @@ -1,71 +0,0 @@ -body, -html { - margin: 0; - padding: 0; - height: 100%; - font-family: Arial, sans-serif; - font-size: 12pt; - background: #f1f1f1; - display: flex; - flex-direction: column; -} - -.container { - flex: 1; - height: calc(100% - 2em); - overflow-y: auto; -} - -#status { - padding: 1em; - width: 100%; - background: #f1f1f1; - display: flex; - box-sizing: border-box; -} - -.container > div { - padding: 0.5em; - margin: 0.5em; - background: #ffffff; -} - -.container > div h3 { - color: #3d6db7; - margin-top: 0.5em; - /*padding-top: 0;*/ -} - -.toolbar { - width: 100%; - height: 32px; -} - -textarea { - width: 100%; - height: calc(1.2em * 8); -} - -#bitcoin-script-pubkey-address, -#bitcoin-script-pubkey-hex { - width: 100%; - height: 1.8em; -} - -textarea { - font-family: monospace; - word-wrap: anywhere; - font-size: 12pt; - resize: none; -} - -#status, -.ast { - font-family: monospace; - word-wrap: break-word; - overflow: auto; -} - -#output-ast li { - word-wrap: break-word; -} diff --git a/packages/wasm-utxo-ui/.gitignore b/packages/webui/.gitignore similarity index 100% rename from packages/wasm-utxo-ui/.gitignore rename to packages/webui/.gitignore diff --git a/packages/webui/README.md b/packages/webui/README.md new file mode 100644 index 00000000000..c2684ef8935 --- /dev/null +++ b/packages/webui/README.md @@ -0,0 +1,10 @@ +# BitGoWASM WebUI + +This project contains demos for the different wasm packages in the BitGoWASM project. + +## Architecture + +- No frontend frameworks +- Demo vibe for UI: monospace font +- Demo of the `@bitgo/wasm-utxo` package lives in src/wasm-utxo +- Toplevel index.html contains navigation to the sub-package demo sites diff --git a/packages/wasm-utxo-ui/package.json b/packages/webui/package.json similarity index 93% rename from packages/wasm-utxo-ui/package.json rename to packages/webui/package.json index 3e7154a6bc1..f7676e22ef4 100644 --- a/packages/wasm-utxo-ui/package.json +++ b/packages/webui/package.json @@ -1,7 +1,7 @@ { - "name": "@bitgo/wasm-utxo-ui", + "name": "@bitgo/wasm-web-ui", "version": "0.1.0", - "description": "Web frontend for wasm-utxo", + "description": "Web frontend for BitGoWASM libraries", "repository": { "type": "git", "url": "git+https://github.com/BitGo/BitGoWASM.git" diff --git a/packages/webui/src/index.html b/packages/webui/src/index.html new file mode 100644 index 00000000000..e5dd1435c99 --- /dev/null +++ b/packages/webui/src/index.html @@ -0,0 +1,134 @@ + + + + + + + BitGoWASM Demos + + + + + + +
+ + diff --git a/packages/webui/src/index.ts b/packages/webui/src/index.ts new file mode 100644 index 00000000000..6dc8dbc52ee --- /dev/null +++ b/packages/webui/src/index.ts @@ -0,0 +1,167 @@ +/** + * BitGoWASM WebUI - Entry Point + * + * Initializes the router and registers all demo components. + */ + +import { BaseComponent, defineComponent, h, css, fragment } from "./lib/html"; +import { initRouter, type Route } from "./lib/router"; + +// Import demo components (registers them as custom elements) +import "./wasm-utxo/addresses"; + +// Common styles used across components +export const commonStyles = ` + * { + box-sizing: border-box; + } + + :host { + display: block; + font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; + color: var(--fg, #c9d1d9); + line-height: 1.5; + } + + a { + color: var(--accent, #58a6ff); + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + + h1, h2, h3 { + margin: 0 0 1rem; + font-weight: 500; + } + + h1 { + font-size: 1.5rem; + color: var(--fg, #c9d1d9); + } + + h2 { + font-size: 1.25rem; + } + + .breadcrumb { + font-size: 0.875rem; + margin-bottom: 1.5rem; + color: var(--muted, #8b949e); + } + + .breadcrumb a { + color: var(--accent, #58a6ff); + } + + .breadcrumb span { + color: var(--fg, #c9d1d9); + } +`; + +/** + * Home page component - navigation hub for all demos. + */ +class HomePage extends BaseComponent { + render() { + return fragment( + css(` + ${commonStyles} + + .home { + max-width: 800px; + } + + .demo-list { + list-style: none; + padding: 0; + margin: 2rem 0; + } + + .demo-list li { + margin-bottom: 1rem; + } + + .demo-link { + display: block; + padding: 1rem 1.25rem; + background: var(--surface, #161b22); + border: 1px solid var(--border, #30363d); + border-radius: 6px; + transition: border-color 0.15s, background 0.15s; + } + + .demo-link:hover { + border-color: var(--accent, #58a6ff); + background: var(--surface-hover, #1c2128); + text-decoration: none; + } + + .demo-title { + font-weight: 500; + margin-bottom: 0.25rem; + } + + .demo-desc { + font-size: 0.875rem; + color: var(--muted, #8b949e); + } + + .subtitle { + color: var(--muted, #8b949e); + margin-bottom: 2rem; + } + `), + h( + "div", + { class: "home" }, + h("h1", {}, "BitGoWASM Demos"), + h("p", { class: "subtitle" }, "Interactive demos for BitGo WASM libraries"), + h( + "ul", + { class: "demo-list" }, + h( + "li", + {}, + h( + "a", + { class: "demo-link", href: "#/wasm-utxo/addresses" }, + h("div", { class: "demo-title" }, "UTXO Address Converter"), + h( + "div", + { class: "demo-desc" }, + "Convert utxo addresses between different networks and formats", + ), + ), + ), + ), + ), + ); + } +} + +defineComponent("home-page", HomePage); + +// Route configuration +const routes: Route[] = [ + { path: "/", component: "home-page" }, + { path: "/wasm-utxo/addresses", component: "address-converter" }, +]; + +// Initialize router when DOM is ready +function init() { + const app = document.getElementById("app"); + if (!app) { + throw new Error("Could not find #app container"); + } + initRouter(app, routes); +} + +// Handle both cases: DOMContentLoaded already fired or not yet +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); +} else { + init(); +} diff --git a/packages/webui/src/lib/html.ts b/packages/webui/src/lib/html.ts new file mode 100644 index 00000000000..e3877b0ade4 --- /dev/null +++ b/packages/webui/src/lib/html.ts @@ -0,0 +1,210 @@ +/** + * Minimal hyperscript-style HTML helper library with Web Component base class. + * No dependencies, no virtual DOM - just real DOM nodes. + */ + +// Types for the h() function +export type Child = Node | string | number | null | undefined | false | Child[]; +export type EventHandler = (event: E) => void; + +export type Props = { + [key: string]: string | number | boolean | EventHandler | undefined; +}; + +/** + * Flatten nested arrays of children, filtering out nullish values. + */ +function flattenChildren(children: Child[]): (Node | string)[] { + const result: (Node | string)[] = []; + for (const child of children) { + if (child === null || child === undefined || child === false) { + continue; + } + if (Array.isArray(child)) { + result.push(...flattenChildren(child)); + } else if (child instanceof Node) { + result.push(child); + } else { + result.push(String(child)); + } + } + return result; +} + +/** + * Hyperscript-style element creation. + * + * @example + * h('div', { class: 'container', onclick: () => console.log('clicked') }, + * h('span', {}, 'Hello'), + * ' World' + * ) + */ +export function h( + tag: K, + props?: Props | null, + ...children: Child[] +): HTMLElementTagNameMap[K]; +export function h(tag: string, props?: Props | null, ...children: Child[]): HTMLElement; +export function h(tag: string, props?: Props | null, ...children: Child[]): HTMLElement { + const element = document.createElement(tag); + + if (props) { + for (const [key, value] of Object.entries(props)) { + if (value === undefined || value === false) { + continue; + } + + // Event handlers: onclick, oninput, etc. + if (key.startsWith("on") && typeof value === "function") { + const eventName = key.slice(2).toLowerCase(); + element.addEventListener(eventName, value as EventListener); + } + // Boolean attributes + else if (value === true) { + element.setAttribute(key, ""); + } + // Regular attributes + else { + element.setAttribute(key, String(value)); + } + } + } + + const flatChildren = flattenChildren(children); + for (const child of flatChildren) { + if (typeof child === "string") { + element.appendChild(document.createTextNode(child)); + } else { + element.appendChild(child); + } + } + + return element; +} + +/** + * Create a text node. + */ +export function text(content: string): Text { + return document.createTextNode(content); +} + +/** + * Base class for Web Components with common patterns. + */ +export abstract class BaseComponent extends HTMLElement { + protected shadow: ShadowRoot; + + constructor() { + super(); + this.shadow = this.attachShadow({ mode: "open" }); + } + + /** + * Called when the element is added to the DOM. + */ + connectedCallback(): void { + this.update(); + } + + /** + * Re-render the component by replacing shadow root contents. + */ + protected update(): void { + const content = this.render(); + this.shadow.replaceChildren(content); + } + + /** + * Override to define the component's content. + */ + abstract render(): HTMLElement | DocumentFragment; + + /** + * Called by the router when URL params change. + * Override to react to URL state changes. + */ + onParamsChange?(params: URLSearchParams): void; + + /** + * Query an element within the shadow root. + */ + protected $(selector: string): T | null { + return this.shadow.querySelector(selector); + } + + /** + * Query all elements within the shadow root. + */ + protected $$(selector: string): NodeListOf { + return this.shadow.querySelectorAll(selector); + } + + /** + * Set text content of an element by selector. + */ + protected setText(selector: string, content: string): void { + const el = this.$(selector); + if (el) { + el.textContent = content; + } + } + + /** + * Set innerHTML of an element by selector (use with caution). + */ + protected setHtml(selector: string, html: string): void { + const el = this.$(selector); + if (el) { + el.innerHTML = html; + } + } + + /** + * Replace children of an element by selector. + */ + protected setChildren(selector: string, ...children: Child[]): void { + const el = this.$(selector); + if (el) { + const flat = flattenChildren(children); + el.replaceChildren( + ...flat.map((c) => (typeof c === "string" ? document.createTextNode(c) : c)), + ); + } + } +} + +/** + * Define and register a custom element. + */ +export function defineComponent(name: string, component: CustomElementConstructor): void { + if (!customElements.get(name)) { + customElements.define(name, component); + } +} + +/** + * Create a style element for use in shadow DOM. + */ +export function css(styles: string): HTMLStyleElement { + const style = document.createElement("style"); + style.textContent = styles; + return style; +} + +/** + * Create a document fragment from multiple children. + */ +export function fragment(...children: Child[]): DocumentFragment { + const frag = document.createDocumentFragment(); + const flat = flattenChildren(children); + for (const child of flat) { + if (typeof child === "string") { + frag.appendChild(document.createTextNode(child)); + } else { + frag.appendChild(child); + } + } + return frag; +} diff --git a/packages/webui/src/lib/router.ts b/packages/webui/src/lib/router.ts new file mode 100644 index 00000000000..184fad4f130 --- /dev/null +++ b/packages/webui/src/lib/router.ts @@ -0,0 +1,194 @@ +/** + * Hash-based router for GitHub Pages compatibility. + * Supports URL state sharing via query parameters after the hash. + * + * URL format: #/path?param1=value1¶m2=value2 + */ + +import type { BaseComponent } from "./html"; + +export interface Route { + path: string; + component: string; // Custom element tag name +} + +interface ParsedHash { + path: string; + params: URLSearchParams; +} + +let routes: Route[] = []; +let container: HTMLElement | null = null; +let currentComponent: BaseComponent | null = null; +let currentPath: string | null = null; + +/** + * Parse the current hash into path and query params. + * + * Examples: + * - "#/foo/bar" -> { path: "/foo/bar", params: URLSearchParams{} } + * - "#/foo?a=1&b=2" -> { path: "/foo", params: URLSearchParams{a=1, b=2} } + * - "" or "#" or "#/" -> { path: "/", params: URLSearchParams{} } + */ +export function parseHash(hash: string = location.hash): ParsedHash { + // Remove leading # if present + let hashContent = hash.startsWith("#") ? hash.slice(1) : hash; + + // Default to root path + if (!hashContent || hashContent === "/") { + return { path: "/", params: new URLSearchParams() }; + } + + // Split path and query string + const questionIndex = hashContent.indexOf("?"); + if (questionIndex === -1) { + return { path: hashContent, params: new URLSearchParams() }; + } + + const path = hashContent.slice(0, questionIndex); + const queryString = hashContent.slice(questionIndex + 1); + return { path: path || "/", params: new URLSearchParams(queryString) }; +} + +/** + * Build a hash string from path and params. + */ +function buildHash(path: string, params?: Record): string { + const searchParams = new URLSearchParams(); + + if (params) { + for (const [key, value] of Object.entries(params)) { + if (value) { + searchParams.set(key, value); + } + } + } + + const queryString = searchParams.toString(); + return queryString ? `#${path}?${queryString}` : `#${path}`; +} + +/** + * Navigate to a new route, optionally with params. + */ +export function navigate(path: string, params?: Record): void { + location.hash = buildHash(path, params); +} + +/** + * Update just the params without changing the current route. + * Empty string values are removed from the URL. + */ +export function setParams(params: Record): void { + const { path, params: currentParams } = parseHash(); + + // Merge with current params + for (const [key, value] of Object.entries(params)) { + if (value) { + currentParams.set(key, value); + } else { + currentParams.delete(key); + } + } + + const queryString = currentParams.toString(); + location.hash = queryString ? `${path}?${queryString}` : path; +} + +/** + * Get current params without navigation. + */ +export function getParams(): URLSearchParams { + return parseHash().params; +} + +/** + * Get current path without navigation. + */ +export function getPath(): string { + return parseHash().path; +} + +/** + * Find the matching route for a path. + */ +function findRoute(path: string): Route | undefined { + return routes.find((r) => r.path === path); +} + +/** + * Handle route changes. + */ +function handleRouteChange(): void { + if (!container) return; + + const { path, params } = parseHash(); + const route = findRoute(path); + + if (!route) { + // Show 404 or redirect to home + console.warn(`No route found for path: ${path}`); + if (path !== "/") { + navigate("/"); + } + return; + } + + // If same path, just update params on existing component + if (currentPath === path && currentComponent) { + if (currentComponent.onParamsChange) { + currentComponent.onParamsChange(params); + } + return; + } + + // Different route - create new component + currentPath = path; + + // Create the component element + const element = document.createElement(route.component) as BaseComponent; + currentComponent = element; + + // Replace container contents + container.replaceChildren(element); + + // Notify component of initial params (after it's connected to DOM) + // Use microtask to ensure connectedCallback has run + queueMicrotask(() => { + if (element.onParamsChange) { + element.onParamsChange(params); + } + }); +} + +/** + * Register routes with the router. + */ +export function registerRoutes(newRoutes: Route[]): void { + routes = newRoutes; +} + +/** + * Initialize the router and start listening to hash changes. + */ +export function initRouter(containerElement: HTMLElement, routeConfig: Route[]): void { + container = containerElement; + routes = routeConfig; + + // Listen for hash changes + window.addEventListener("hashchange", handleRouteChange); + + // Handle initial route + handleRouteChange(); +} + +/** + * Clean up router listeners (useful for testing). + */ +export function destroyRouter(): void { + window.removeEventListener("hashchange", handleRouteChange); + container = null; + currentComponent = null; + currentPath = null; + routes = []; +} diff --git a/packages/webui/src/wasm-utxo/addresses/index.ts b/packages/webui/src/wasm-utxo/addresses/index.ts new file mode 100644 index 00000000000..dec3ac3d2d0 --- /dev/null +++ b/packages/webui/src/wasm-utxo/addresses/index.ts @@ -0,0 +1,593 @@ +/** + * Address Converter Demo + * + * Converts cryptocurrency addresses between different networks and formats. + * Supports base58, bech32, and cashaddr encodings. + */ + +import { BaseComponent, defineComponent, h, css, fragment, type Child } from "../../lib/html"; +import { setParams } from "../../lib/router"; +import { commonStyles } from "../../index"; +import { address, type CoinName, type AddressFormat } from "@bitgo/wasm-utxo"; + +const { toOutputScriptWithCoin, fromOutputScriptWithCoin } = address; + +// All supported coins +const ALL_COINS: CoinName[] = [ + "btc", + "tbtc", + "tbtc4", + "tbtcsig", + "tbtcbgsig", + "bch", + "tbch", + "bcha", + "tbcha", + "btg", + "tbtg", + "bsv", + "tbsv", + "dash", + "tdash", + "doge", + "tdoge", + "ltc", + "tltc", + "zec", + "tzec", +]; + +// Coins that support cashaddr format +const CASHADDR_COINS: CoinName[] = ["bch", "tbch", "bcha", "tbcha"]; + +// Coin display names +const COIN_NAMES: Record = { + btc: "Bitcoin", + tbtc: "Bitcoin Testnet", + tbtc4: "Bitcoin Testnet4", + tbtcsig: "Bitcoin Signet", + tbtcbgsig: "Bitcoin BitGo Signet", + bch: "Bitcoin Cash", + tbch: "Bitcoin Cash Testnet", + bcha: "eCash", + tbcha: "eCash Testnet", + btg: "Bitcoin Gold", + tbtg: "Bitcoin Gold Testnet", + bsv: "Bitcoin SV", + tbsv: "Bitcoin SV Testnet", + dash: "Dash", + tdash: "Dash Testnet", + doge: "Dogecoin", + tdoge: "Dogecoin Testnet", + ltc: "Litecoin", + tltc: "Litecoin Testnet", + zec: "Zcash", + tzec: "Zcash Testnet", +}; + +interface ConversionResult { + coin: CoinName; + address: string; + format: AddressFormat; +} + +interface ConversionError { + coin: CoinName; + error: string; + format: AddressFormat; +} + +type ConversionOutcome = ConversionResult | ConversionError; + +function isError(outcome: ConversionOutcome): outcome is ConversionError { + return "error" in outcome; +} + +/** + * Try to decode an address with any coin and return the output script. + */ +function decodeAddress(address: string): { script: Uint8Array; coin: CoinName } | null { + for (const coin of ALL_COINS) { + try { + const script = toOutputScriptWithCoin(address, coin); + return { script, coin }; + } catch { + // Try next coin + } + } + return null; +} + +/** + * Convert an output script to addresses for all networks and formats. + */ +function convertToAllNetworks(script: Uint8Array): ConversionOutcome[] { + const results: ConversionOutcome[] = []; + + for (const coin of ALL_COINS) { + // Try default format + try { + const address = fromOutputScriptWithCoin(script, coin); + results.push({ coin, address, format: "default" }); + } catch (e) { + results.push({ coin, error: String(e), format: "default" }); + } + + // Try cashaddr format for BCH/eCash + if (CASHADDR_COINS.includes(coin)) { + try { + const address = fromOutputScriptWithCoin(script, coin, "cashaddr"); + results.push({ coin, address, format: "cashaddr" }); + } catch (e) { + results.push({ coin, error: String(e), format: "cashaddr" }); + } + } + } + + return results; +} + +/** + * Convert bytes to hex string. + */ +function toHex(bytes: Uint8Array): string { + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +/** + * Copy text to clipboard and show feedback. + */ +async function copyToClipboard(text: string, button: HTMLElement): Promise { + try { + await navigator.clipboard.writeText(text); + const original = button.textContent; + button.textContent = "Copied!"; + setTimeout(() => { + button.textContent = original; + }, 1500); + } catch (e) { + console.error("Failed to copy:", e); + } +} + +/** + * Address Converter Web Component + */ +class AddressConverter extends BaseComponent { + private debounceTimer: number | null = null; + + render() { + return fragment( + css(` + ${commonStyles} + + .converter { + max-width: 1000px; + } + + .input-section { + margin-bottom: 2rem; + } + + .input-row { + display: flex; + gap: 0.5rem; + } + + textarea { + flex: 1; + font-family: inherit; + font-size: 0.9375rem; + padding: 0.75rem 1rem; + background: var(--surface, #161b22); + border: 1px solid var(--border, #30363d); + border-radius: 6px; + color: var(--fg, #c9d1d9); + resize: vertical; + min-height: 60px; + } + + textarea:focus { + outline: none; + border-color: var(--accent, #58a6ff); + } + + textarea::placeholder { + color: var(--muted, #8b949e); + } + + .btn { + padding: 0.75rem 1rem; + font-family: inherit; + font-size: 0.875rem; + background: var(--surface, #161b22); + border: 1px solid var(--border, #30363d); + border-radius: 6px; + color: var(--fg, #c9d1d9); + cursor: pointer; + transition: border-color 0.15s, background 0.15s; + white-space: nowrap; + } + + .btn:hover { + border-color: var(--accent, #58a6ff); + background: var(--surface-hover, #1c2128); + } + + .btn-primary { + background: var(--accent, #58a6ff); + border-color: var(--accent, #58a6ff); + color: var(--bg, #0d1117); + } + + .btn-primary:hover { + background: var(--accent-hover, #79b8ff); + border-color: var(--accent-hover, #79b8ff); + } + + .script-info { + margin-bottom: 2rem; + padding: 1rem; + background: var(--surface, #161b22); + border: 1px solid var(--border, #30363d); + border-radius: 6px; + } + + .script-info h3 { + font-size: 0.875rem; + color: var(--muted, #8b949e); + margin-bottom: 0.5rem; + } + + .script-hex { + font-size: 0.8125rem; + word-break: break-all; + color: var(--fg, #c9d1d9); + } + + .error-message { + padding: 1rem; + background: rgba(248, 81, 73, 0.1); + border: 1px solid rgba(248, 81, 73, 0.4); + border-radius: 6px; + color: #f85149; + margin-bottom: 2rem; + } + + .results-section h2 { + font-size: 1rem; + margin-bottom: 1rem; + color: var(--muted, #8b949e); + } + + .results-group { + margin-bottom: 2rem; + } + + .results-group-title { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--muted, #8b949e); + margin-bottom: 0.75rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--border, #30363d); + } + + .result-row { + display: grid; + grid-template-columns: 140px 80px 1fr auto; + gap: 0.75rem; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid var(--border-subtle, #21262d); + } + + .result-row:last-child { + border-bottom: none; + } + + .result-coin { + font-size: 0.8125rem; + color: var(--muted, #8b949e); + } + + .result-format { + font-size: 0.75rem; + color: var(--muted, #8b949e); + opacity: 0.7; + } + + .result-address { + font-size: 0.8125rem; + word-break: break-all; + color: var(--fg, #c9d1d9); + } + + .result-error { + font-size: 0.75rem; + color: var(--muted, #8b949e); + opacity: 0.5; + font-style: italic; + } + + .copy-btn { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + background: transparent; + border: 1px solid var(--border, #30363d); + border-radius: 4px; + color: var(--muted, #8b949e); + cursor: pointer; + transition: all 0.15s; + } + + .copy-btn:hover { + border-color: var(--accent, #58a6ff); + color: var(--accent, #58a6ff); + } + + .empty-state { + text-align: center; + padding: 3rem 1rem; + color: var(--muted, #8b949e); + } + + .empty-state p { + margin: 0; + } + + @media (max-width: 768px) { + .result-row { + grid-template-columns: 1fr auto; + gap: 0.25rem; + } + + .result-coin { + grid-column: 1 / -1; + } + + .result-format { + grid-column: 1; + } + + .result-address { + grid-column: 1 / -1; + font-size: 0.75rem; + } + } + `), + h( + "div", + { class: "converter" }, + h( + "nav", + { class: "breadcrumb" }, + h("a", { href: "#/" }, "Home"), + " / ", + h("span", {}, "UTXO Address Converter"), + ), + h("h1", {}, "UTXO Address Converter"), + h( + "section", + { class: "input-section" }, + h( + "div", + { class: "input-row" }, + h("textarea", { + id: "address-input", + placeholder: "Paste a utxo address...", + rows: "2", + oninput: (e: Event) => this.handleInput(e), + }), + h( + "button", + { + class: "btn", + onclick: () => this.share(), + }, + "Share", + ), + h( + "button", + { + class: "btn", + onclick: () => this.clear(), + }, + "Clear", + ), + ), + ), + h("div", { id: "script-info" }), + h("div", { id: "error-message" }), + h( + "div", + { id: "results" }, + h( + "div", + { class: "empty-state" }, + h("p", {}, "Enter an address above to convert it across networks"), + ), + ), + ), + ); + } + + onParamsChange(params: URLSearchParams): void { + const address = params.get("a"); + const input = this.$("#address-input"); + + if (input && address) { + input.value = address; + this.convert(address); + } else if (input && !address) { + input.value = ""; + this.showEmpty(); + } + } + + private handleInput(e: Event): void { + const value = (e.target as HTMLTextAreaElement).value.trim(); + + // Debounce URL updates + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + + this.debounceTimer = window.setTimeout(() => { + setParams({ a: value }); + }, 300); + + // Update results immediately + if (value) { + this.convert(value); + } else { + this.showEmpty(); + } + } + + private convert(address: string): void { + const scriptInfoEl = this.$("#script-info"); + const errorEl = this.$("#error-message"); + const resultsEl = this.$("#results"); + + if (!scriptInfoEl || !errorEl || !resultsEl) return; + + // Clear previous state + scriptInfoEl.innerHTML = ""; + errorEl.innerHTML = ""; + + // Try to decode the address + const decoded = decodeAddress(address); + + if (!decoded) { + errorEl.replaceChildren( + h( + "div", + { class: "error-message" }, + "Could not decode address. Please check it's a valid cryptocurrency address.", + ), + ); + resultsEl.replaceChildren( + h("div", { class: "empty-state" }, h("p", {}, "Enter a valid address to see conversions")), + ); + return; + } + + // Show script info + const hexString = toHex(decoded.script); + scriptInfoEl.replaceChildren( + h( + "div", + { class: "script-info" }, + h("h3", {}, `Output Script (decoded as ${decoded.coin})`), + h("code", { class: "script-hex" }, hexString), + h( + "button", + { + class: "copy-btn", + style: "margin-left: 0.5rem", + onclick: (e: Event) => copyToClipboard(hexString, e.target as HTMLElement), + }, + "Copy", + ), + ), + ); + + // Convert to all networks + const outcomes = convertToAllNetworks(decoded.script); + + // Group results by mainnet/testnet + const mainnetResults = outcomes.filter((r) => !r.coin.startsWith("t")); + const testnetResults = outcomes.filter((r) => r.coin.startsWith("t")); + + resultsEl.replaceChildren( + h( + "section", + { class: "results-section" }, + this.renderResultGroup("Mainnet", mainnetResults), + this.renderResultGroup("Testnet", testnetResults), + ), + ); + } + + private renderResultGroup(title: string, outcomes: ConversionOutcome[]): HTMLElement { + return h( + "div", + { class: "results-group" }, + h("div", { class: "results-group-title" }, title), + ...outcomes.map((outcome) => this.renderResult(outcome)), + ); + } + + private renderResult(outcome: ConversionOutcome): Child { + const coinName = COIN_NAMES[outcome.coin]; + const formatLabel = outcome.format === "cashaddr" ? "cashaddr" : "default"; + + if (isError(outcome)) { + // Don't show errors for cashaddr on non-cashaddr coins + if (outcome.format === "cashaddr") { + return null; + } + return h( + "div", + { class: "result-row" }, + h("span", { class: "result-coin" }, coinName), + h("span", { class: "result-format" }, formatLabel), + h("span", { class: "result-error" }, "Not supported"), + h("span", {}), + ); + } + + return h( + "div", + { class: "result-row" }, + h("span", { class: "result-coin" }, coinName), + h("span", { class: "result-format" }, formatLabel), + h("span", { class: "result-address" }, outcome.address), + h( + "button", + { + class: "copy-btn", + onclick: (e: Event) => copyToClipboard(outcome.address, e.target as HTMLElement), + }, + "Copy", + ), + ); + } + + private showEmpty(): void { + const scriptInfoEl = this.$("#script-info"); + const errorEl = this.$("#error-message"); + const resultsEl = this.$("#results"); + + if (scriptInfoEl) scriptInfoEl.innerHTML = ""; + if (errorEl) errorEl.innerHTML = ""; + if (resultsEl) { + resultsEl.replaceChildren( + h( + "div", + { class: "empty-state" }, + h("p", {}, "Enter an address above to convert it across networks"), + ), + ); + } + } + + private share(): void { + copyToClipboard(location.href, this.$(".btn")!); + } + + private clear(): void { + const input = this.$("#address-input"); + if (input) { + input.value = ""; + setParams({ a: "" }); + this.showEmpty(); + } + } +} + +defineComponent("address-converter", AddressConverter); diff --git a/packages/wasm-utxo-ui/tsconfig.json b/packages/webui/tsconfig.json similarity index 100% rename from packages/wasm-utxo-ui/tsconfig.json rename to packages/webui/tsconfig.json diff --git a/packages/wasm-utxo-ui/webpack.config.js b/packages/webui/webpack.config.js similarity index 100% rename from packages/wasm-utxo-ui/webpack.config.js rename to packages/webui/webpack.config.js