diff --git a/.changeset/brave-falcons-dance.md b/.changeset/brave-falcons-dance.md new file mode 100644 index 00000000..4f1723b0 --- /dev/null +++ b/.changeset/brave-falcons-dance.md @@ -0,0 +1,10 @@ +--- +"@offckb/cli": patch +--- + +Add daemon mode and structured JSON output for agent-friendly usage, plus a `node stop` command to terminate the daemon. + +- `offckb node --daemon` starts the CKB devnet as a detached background process and writes the PID and logs to the devnet data folder. +- `offckb --json ` emits structured JSON log output for programmatic consumption. +- `offckb node stop` reads the daemon PID file and gracefully shuts down the daemon, falling back to force-kill if necessary. It now verifies the target process identity, handles stale PID files, and cleans up on error paths. +- Hardened daemon lifecycle: duplicate daemon starts are rejected, CLI entry resolution supports packaged/npx environments via `OFFCKB_CLI_PATH`, and log/PID directory creation failures are handled gracefully. diff --git a/.changeset/fix-dependabot-alerts.md b/.changeset/fix-dependabot-alerts.md new file mode 100644 index 00000000..767e0772 --- /dev/null +++ b/.changeset/fix-dependabot-alerts.md @@ -0,0 +1,14 @@ +--- +"@offckb/cli": patch +--- + +Resolve Dependabot security alerts via pnpm overrides for transitive dependencies: + +- `qs` 6.15.0 → 6.15.2 +- `ip-address` 10.1.0 → 10.1.1 +- `js-yaml` 3.14.2 → 3.15.0 / 4.1.1 → 4.2.0 +- `@babel/core` 7.28.6 → 7.29.7 +- `@eslint/plugin-kit` 0.2.8 → 0.3.4 +- `brace-expansion` 5.0.5 → 5.0.6 + +`elliptic` remains unfixed because a patched version (>=6.6.2) is not yet published on npm. diff --git a/.changeset/integrate-ckb-tui.md b/.changeset/integrate-ckb-tui.md new file mode 100644 index 00000000..899a71d7 --- /dev/null +++ b/.changeset/integrate-ckb-tui.md @@ -0,0 +1,5 @@ +--- +"@offckb/cli": minor +--- + +Add `status` command to launch ckb-tui for monitoring CKB network from your node diff --git a/.changeset/tasty-walls-appear.md b/.changeset/tasty-walls-appear.md new file mode 100644 index 00000000..293777b4 --- /dev/null +++ b/.changeset/tasty-walls-appear.md @@ -0,0 +1,5 @@ +--- +"@offckb/cli": minor +--- + +Refactor UDT CLI support: reuse `balance` and `transfer` commands for CKB and UDT queries, and add `offckb udt issue` / `offckb udt destroy` subcommands. diff --git a/.changeset/wise-candies-stop.md b/.changeset/wise-candies-stop.md deleted file mode 100644 index 9fec1cb8..00000000 --- a/.changeset/wise-candies-stop.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@offckb/cli': minor ---- - -Replace ckb-transaction-dumper with ccc-based implementation - -- Rewrite transaction dumper to use ccc Client and molecule codecs -- Implement dep_group unpacking using ccc.mol -- Remove ckb-transaction-dumper npm dependency diff --git a/.github/workflows/changeset-check.yml b/.github/workflows/changeset-check.yml index 74e31d50..497059e8 100644 --- a/.github/workflows/changeset-check.yml +++ b/.github/workflows/changeset-check.yml @@ -44,7 +44,7 @@ jobs: fi - name: Comment on PR (success) - if: steps.check.outputs.has_changeset == 'true' + if: steps.check.outputs.has_changeset == 'true' && !github.event.pull_request.head.repo.fork uses: actions/github-script@v7 with: script: | @@ -68,7 +68,7 @@ jobs: } - name: Comment on PR (failure) - if: failure() + if: failure() && !github.event.pull_request.head.repo.fork uses: actions/github-script@v7 with: script: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 4130afc3..b5288319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # @offckb/cli +## 0.4.8 + +### Patch Changes + +- Override `form-data@>=4.0.0 <4.0.6` to `4.0.6` to fix CRLF injection (GHSA). +- Override `hono@<4.12.25` to `4.12.25` to fix CORS origin reflection with credentials. + +## 0.4.7 + +### Patch Changes + +- 1c17600: Bump @ckb-ccc/core to 1.14.0 to fix the ws vulnerability (ws >= 8.21.0). +- a687c6f: Bump default CKB version to 0.207.0 +- 3da9777: Replace ckb-transaction-dumper with ccc-based implementation + + - Rewrite transaction dumper to use ccc Client and molecule codecs + - Implement dep_group unpacking using ccc.mol + - Remove ckb-transaction-dumper npm dependency + ## 0.4.6 ### Patch Changes diff --git a/README.md b/README.md index d575e6db..82680adc 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Options: Commands: node [CKB-Version] Use the CKB to start devnet + node stop Stop the running CKB devnet daemon create [options] [project-name] Create a new CKB Smart Contract project in JavaScript. deploy [options] Deploy contracts to different networks, only supports devnet and testnet debug [options] Quickly debug transaction with tx-hash @@ -81,6 +82,7 @@ Commands: transfer-all [options] [toAddress] Transfer All CKB tokens to address, only devnet and testnet balance [options] [toAddress] Check account balance, only devnet and testnet debugger Port of the raw CKB Standalone Debugger + status [options] Show ckb-tui status interface config [item] [value] do a configuration action help [command] display help for command ``` @@ -118,6 +120,40 @@ offckb node --binary-path /path/to/your/ckb/binary When using `--binary-path`, it will ignore the specified version and network, and only work for devnet. +**Run in Daemon Mode** + +Start the devnet in the background so your terminal stays free: + +```sh +offckb node --daemon +``` + +The daemon writes its logs and PID to the devnet data folder, for example: + +- Logs: `~/Library/Application Support/offckb-nodejs/devnet/data/logs/daemon.log` +- PID file: `~/Library/Application Support/offckb-nodejs/devnet/data/logs/daemon.pid` + +Stop the daemon later with: + +```sh +offckb node stop +``` + +**Agent-Friendly JSON Output** + +For programmatic consumption or agent integration, add `--json` to any command to emit structured JSON logs: + +```sh +offckb node --json +offckb node --daemon --json +``` + +Each log line is a single JSON object: + +```json +{"level":"info","message":"Launching CKB devnet Node...","timestamp":"2026-07-07T07:10:00.000Z"} +``` + **RPC & Proxy RPC** When the Devnet starts: @@ -134,6 +170,10 @@ offckb node --network ``` Using a proxy RPC server for Testnet/Mainnet is especially helpful for debugging transactions, since failed transactions are dumped automatically. +**Watch Network with TUI** + +Once you start the CKB Node, you can use `offckb status --network devnet/testnet/mainnet` to start a CKB-TUI interface to monitor the CKB network from your node. + ### 2. Create a New Contract Project {#create-project} Generate a ready-to-use smart-contract project in JS/TS using templates: diff --git a/package.json b/package.json index 8bef32ec..d54f616e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@offckb/cli", - "version": "0.4.6", + "version": "0.4.8", "description": "ckb development network for your first try", "author": "CKB EcoFund", "license": "MIT", @@ -25,11 +25,6 @@ "publishConfig": { "access": "public" }, - "pnpm": { - "onlyBuiltDependencies": [ - "secp256k1" - ] - }, "scripts": { "build": "node scripts/build.js", "start": "ts-node-dev --transpile-only src/cli.ts", @@ -78,7 +73,7 @@ "typescript": "^5.3.3" }, "dependencies": { - "@ckb-ccc/core": "1.5.3", + "@ckb-ccc/core": "1.14.0", "@iarna/toml": "^2.2.5", "@inquirer/prompts": "^7.8.6", "@types/http-proxy": "^1.17.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6323bd81..3d1264d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,13 +4,24 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + form-data@>=4.0.0 <4.0.6: 4.0.6 + hono@<4.12.25: 4.12.25 + qs@>=6.11.1 <=6.15.1: 6.15.2 + ip-address@<=10.1.0: 10.1.1 + js-yaml@<3.15.0: 3.15.0 + js-yaml@>=4.0.0 <=4.1.1: 4.2.0 + '@babel/core@<=7.29.0': 7.29.7 + '@eslint/plugin-kit@<0.3.4': 0.3.4 + brace-expansion@>=5.0.0 <5.0.6: 5.0.6 + importers: .: dependencies: '@ckb-ccc/core': - specifier: 1.5.3 - version: 1.5.3(typescript@5.8.2)(zod@3.25.76) + specifier: 1.14.0 + version: 1.14.0(typescript@5.8.2)(zod@3.25.76) '@iarna/toml': specifier: ^2.2.5 version: 2.2.5 @@ -49,7 +60,7 @@ importers: version: 7.7.3 tar: specifier: ^7.5.3 - version: 7.5.11 + version: 7.5.16 winston: specifier: ^3.17.0 version: 3.17.0 @@ -104,7 +115,7 @@ importers: version: 3.5.3 ts-jest: specifier: ^29.4.6 - version: 29.4.6(@babel/core@7.28.6)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.6))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.17.24)(ts-node@10.9.2(@types/node@20.17.24)(typescript@5.8.2)))(typescript@5.8.2) + version: 29.4.6(@babel/core@7.29.7)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.7))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.17.24)(ts-node@10.9.2(@types/node@20.17.24)(typescript@5.8.2)))(typescript@5.8.2) ts-node-dev: specifier: ^2.0.0 version: 2.0.0(@types/node@20.17.24)(typescript@5.8.2) @@ -118,42 +129,50 @@ importers: packages: - '@adraffy/ens-normalize@1.10.1': - resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} '@babel/code-frame@7.28.6': resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.6': - resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.6': - resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} '@babel/generator@7.28.6': resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils@7.28.6': resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} @@ -163,16 +182,24 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} '@babel/parser@7.28.6': @@ -180,113 +207,122 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-syntax-async-generators@7.8.4': resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-bigint@7.8.3': resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-class-properties@7.12.13': resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-class-static-block@7.14.5': resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-import-attributes@7.28.6': resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-json-strings@7.8.3': resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-jsx@7.28.6': resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-logical-assignment-operators@7.10.4': resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-numeric-separator@7.10.4': resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-object-rest-spread@7.8.3': resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-optional-catch-binding@7.8.3': resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-optional-chaining@7.8.3': resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-private-property-in-object@7.14.5': resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-top-level-await@7.14.5': resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/plugin-syntax-typescript@7.28.6': resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.7 '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.6': - resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} '@babel/types@7.28.6': resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -345,8 +381,8 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@ckb-ccc/core@1.5.3': - resolution: {integrity: sha512-/W7SYbygBateN6odqkMhQlkoQFs+45pJ7hYZYEaEpRdF6DjU7sIOvVSkw3qXiUOK37b2qAWJj3I8CJQbesKpng==} + '@ckb-ccc/core@1.14.0': + resolution: {integrity: sha512-X0zicFdnHm7JJZBQml1waNDkZ+9L9xn7XglWExKPuG8DI8ZEuWpvHqdhmcn7ey4CRi5JdiRzfzgZqhoWBgd9+A==} '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} @@ -390,6 +426,10 @@ packages: resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.3': resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -402,15 +442,15 @@ packages: resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.8': - resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + '@eslint/plugin-kit@0.3.4': + resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@hono/node-server@1.19.13': resolution: {integrity: sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==} engines: {node: '>=18.14.1'} peerDependencies: - hono: ^4 + hono: 4.12.25 '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -663,11 +703,11 @@ packages: resolution: {integrity: sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@joyid/ckb@1.1.2': - resolution: {integrity: sha512-+e+ISF566zaKNhKNSSS5kBw8or4Kb5Xqxe/2jVkUXKkqVHSS02Trrqe0g4IjSyeN9bszzolr1XgStv2hz62tqA==} + '@joyid/ckb@1.1.4': + resolution: {integrity: sha512-8WqcfFd/kfttuaIf2/XsRcmQkEwYLUnZc9UZRcGSVX6GkwzpOLVY5S6Hq+fDXY82lQo9upJGjjudrtcPKYPx3g==} - '@joyid/common@0.2.1': - resolution: {integrity: sha512-DjA+Cy0koTCmPzhkhHkPc0icRLE78ktZY46rXHXfkSqxwQIJ/ED/whPoeF5tkTrN+teIC/hfzVRVkEE4zh/ASQ==} + '@joyid/common@0.2.2': + resolution: {integrity: sha512-Sh9cBlbL+WgcKJ7jngELg5oY0qAO1trHm4iq1Ao0drWwi4biF8p3cb5mAEoO36UxXOy3CYSDbECWLGIP9lWNgg==} '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -713,15 +753,16 @@ packages: '@nervosnetwork/ckb-types@0.109.5': resolution: {integrity: sha512-5jQNjFw76YCd+Ppl+0RvBWzxwvWaKfWC5wjVFFdNAieX7xksCHfZFIeow8je7AF8uVypwe56WlLBlblxw9NBBQ==} - '@noble/ciphers@0.5.3': - resolution: {integrity: sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==} + '@noble/ciphers@2.2.0': + resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} + engines: {node: '>= 20.19.0'} '@noble/curves@1.2.0': resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - '@noble/curves@1.8.1': - resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} - engines: {node: ^14.21.3 || >=16} + '@noble/curves@2.2.0': + resolution: {integrity: sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==} + engines: {node: '>= 20.19.0'} '@noble/hashes@1.3.2': resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} @@ -731,6 +772,10 @@ packages: resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + engines: {node: '>= 20.19.0'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -910,6 +955,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -1027,10 +1073,6 @@ packages: zod: optional: true - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1044,11 +1086,6 @@ packages: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -1134,15 +1171,11 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - babel-jest@30.2.0: resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: - '@babel/core': ^7.11.0 || ^8.0.0-0 + '@babel/core': 7.29.7 babel-plugin-istanbul@7.0.1: resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} @@ -1155,13 +1188,13 @@ packages: babel-preset-current-node-syntax@1.2.0: resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} peerDependencies: - '@babel/core': ^7.0.0 || ^8.0.0-0 + '@babel/core': 7.29.7 babel-preset-jest@30.2.0: resolution: {integrity: sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: - '@babel/core': ^7.11.0 || ^8.0.0-beta.1 + '@babel/core': 7.29.7 balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1170,9 +1203,6 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - base-x@3.0.11: - resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} - base-x@5.0.1: resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==} @@ -1183,9 +1213,6 @@ packages: resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} hasBin: true - bech32@1.1.4: - resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} - bech32@2.0.0: resolution: {integrity: sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==} @@ -1197,16 +1224,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - bip66@1.1.5: - resolution: {integrity: sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==} - - bitcoinjs-message@2.2.0: - resolution: {integrity: sha512-103Wy3xg8Y9o+pdhGP4M3/mtQQuUWs6sPuOp1mYphSUoSMHjHTlkj32K4zxU8qMH0Ckv23emfkGlFWtoWZ7YFA==} - engines: {node: '>=0.10'} - blessed@0.1.81: resolution: {integrity: sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==} engines: {node: '>= 0.8.0'} @@ -1222,8 +1239,8 @@ packages: brace-expansion@1.1.13: resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -1233,9 +1250,6 @@ packages: brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - browserify-aes@1.2.0: - resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} - browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1245,31 +1259,18 @@ packages: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} - bs58@4.0.1: - resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} - bs58@6.0.0: resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} - bs58check@2.1.2: - resolution: {integrity: sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==} - bs58check@4.0.0: resolution: {integrity: sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==} bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - buffer-equals@1.0.4: - resolution: {integrity: sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==} - engines: {node: '>=0.10.0'} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer-xor@1.0.3: - resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} - buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -1285,10 +1286,6 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -1342,10 +1339,6 @@ packages: resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} engines: {node: '>=8'} - cipher-base@1.0.6: - resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} - engines: {node: '>= 0.10'} - cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} @@ -1439,21 +1432,12 @@ packages: resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} engines: {node: '>=10.0.0'} - create-hash@1.2.0: - resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} - - create-hmac@1.1.7: - resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} - create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} cross-fetch@4.0.0: resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} - cross-fetch@4.1.0: - resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1482,10 +1466,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1510,10 +1490,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - drbg.js@1.0.1: - resolution: {integrity: sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==} - engines: {node: '>=0.10'} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1646,14 +1622,10 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - ethers@6.13.5: - resolution: {integrity: sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==} + ethers@6.17.0: + resolution: {integrity: sha512-BpyrpIPJ3ydEVow8zGaz1DuPS7YU8DcWxuBnY9a0UA/lvAPwrMr+EPXsfrul628SRaekPNeIM4UFh/91GWZang==} engines: {node: '>=14.0.0'} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -1668,9 +1640,6 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} - evp_bytestokey@1.0.3: - resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1713,8 +1682,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -1729,9 +1698,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1758,8 +1724,8 @@ packages: fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -1767,16 +1733,12 @@ packages: debug: optional: true - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.4: - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} forwarded@0.2.0: @@ -1882,9 +1844,6 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1893,10 +1852,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hash-base@3.1.0: - resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} - engines: {node: '>=4'} - hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} @@ -1904,11 +1859,15 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hono@4.12.12: - resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==} + hono@4.12.25: + resolution: {integrity: sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==} engines: {node: '>=16.9.0'} html-escaper@2.0.2: @@ -1978,8 +1937,8 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + ip-address@10.1.1: + resolution: {integrity: sha512-1FMu8/N15Ck1BL551Jf42NYIoin2unWjLQ2Fze/DXryJRl5twqtwNHlO39qERGbIOcKYWHdgRryhOC+NG4eaLw==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -1996,10 +1955,6 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -2047,17 +2002,10 @@ packages: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2223,12 +2171,12 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + js-yaml@3.15.0: + resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} hasBin: true - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true jsbi@3.1.3: @@ -2340,9 +2288,6 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - md5.js@1.3.5: - resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} - media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -2624,10 +2569,6 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2657,8 +2598,8 @@ packages: pure-rand@7.0.1: resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} - qs@6.15.0: - resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} engines: {node: '>=0.6'} quansync@0.2.11: @@ -2734,9 +2675,6 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - ripemd160@2.0.2: - resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} - router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -2754,10 +2692,6 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - secp256k1@3.8.1: - resolution: {integrity: sha512-tArjQw2P0RTdY7QmkNehgp6TVvQXq6ulIhxv8gaH6YubKG/wxxAoNKcbuXjDhybbc+b2Ihc7e0xxiGN744UIiQ==} - engines: {node: '>=4.0.0'} - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2775,18 +2709,9 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sha.js@2.4.12: - resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} - engines: {node: '>= 0.10'} - hasBin: true - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2931,8 +2856,8 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - tar@7.5.11: - resolution: {integrity: sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==} + tar@7.5.16: + resolution: {integrity: sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==} engines: {node: '>=18'} term-size@2.2.1: @@ -2949,10 +2874,6 @@ packages: tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - to-buffer@1.2.2: - resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} - engines: {node: '>= 0.4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2983,7 +2904,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' + '@babel/core': 7.29.7 '@jest/transform': ^29.0.0 || ^30.0.0 '@jest/types': ^29.0.0 || ^30.0.0 babel-jest: ^29.0.0 || ^30.0.0 @@ -3063,10 +2984,6 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} @@ -3113,9 +3030,6 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} - varuint-bitcoin@1.1.2: - resolution: {integrity: sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3129,10 +3043,6 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3176,20 +3086,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3250,7 +3148,7 @@ packages: snapshots: - '@adraffy/ens-normalize@1.10.1': {} + '@adraffy/ens-normalize@1.11.1': {} '@babel/code-frame@7.28.6': dependencies: @@ -3258,19 +3156,25 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.6': {} + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} - '@babel/core@7.28.6': + '@babel/core@7.29.7': dependencies: - '@babel/code-frame': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/template': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -3288,29 +3192,37 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.28.6': + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': dependencies: - '@babel/compat-data': 7.28.6 - '@babel/helper-validator-option': 7.27.1 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-globals@7.28.0': {} + '@babel/helper-globals@7.29.7': {} - '@babel/helper-module-imports@7.28.6': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -3318,120 +3230,128 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helpers@7.28.6': + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.28.6 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 '@babel/parser@7.28.6': dependencies: '@babel/types': 7.28.6 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.6)': + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.6)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.6)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.6)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.6)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.6)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.6)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.6)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.6)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.6)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.6)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.6)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.6)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.6)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/runtime@7.28.6': {} - '@babel/template@7.28.6': + '@babel/template@7.29.7': dependencies: - '@babel/code-frame': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 - '@babel/traverse@7.28.6': + '@babel/traverse@7.29.7': dependencies: - '@babel/code-frame': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.6 - '@babel/template': 7.28.6 - '@babel/types': 7.28.6 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -3441,6 +3361,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@bcoe/v8-coverage@0.2.3': {} '@changesets/apply-release-plan@7.0.14': @@ -3552,7 +3477,7 @@ snapshots: '@changesets/parse@0.4.2': dependencies: '@changesets/types': 6.1.0 - js-yaml: 4.1.1 + js-yaml: 4.2.0 '@changesets/pre@2.0.2': dependencies: @@ -3587,21 +3512,18 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 - '@ckb-ccc/core@1.5.3(typescript@5.8.2)(zod@3.25.76)': + '@ckb-ccc/core@1.14.0(typescript@5.8.2)(zod@3.25.76)': dependencies: - '@joyid/ckb': 1.1.2(typescript@5.8.2)(zod@3.25.76) - '@noble/ciphers': 0.5.3 - '@noble/curves': 1.8.1 - '@noble/hashes': 1.7.1 - abort-controller: 3.0.0 + '@joyid/ckb': 1.1.4(typescript@5.8.2)(zod@3.25.76) + '@noble/ciphers': 2.2.0 + '@noble/curves': 2.2.0 + '@noble/hashes': 2.2.0 bech32: 2.0.0 - bitcoinjs-message: 2.2.0 bs58check: 4.0.0 buffer: 6.0.3 - cross-fetch: 4.1.0 - ethers: 6.13.5 - isomorphic-ws: 5.0.0(ws@8.18.1) - ws: 8.18.1 + ethers: 6.17.0 + isomorphic-ws: 5.0.0(ws@8.21.0) + ws: 8.21.0 transitivePeerDependencies: - bufferutil - encoding @@ -3658,6 +3580,10 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.14.0 @@ -3666,7 +3592,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -3676,14 +3602,14 @@ snapshots: '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.2.8': + '@eslint/plugin-kit@0.3.4': dependencies: - '@eslint/core': 0.13.0 + '@eslint/core': 0.15.2 levn: 0.4.1 - '@hono/node-server@1.19.13(hono@4.12.12)': + '@hono/node-server@1.19.13(hono@4.12.25)': dependencies: - hono: 4.12.12 + hono: 4.12.25 '@humanfs/core@0.19.1': {} @@ -3841,7 +3767,7 @@ snapshots: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 3.14.2 + js-yaml: 3.15.0 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.3': {} @@ -3997,7 +3923,7 @@ snapshots: '@jest/transform@30.2.0': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 7.0.1 @@ -4025,9 +3951,9 @@ snapshots: '@types/yargs': 17.0.35 chalk: 4.1.2 - '@joyid/ckb@1.1.2(typescript@5.8.2)(zod@3.25.76)': + '@joyid/ckb@1.1.4(typescript@5.8.2)(zod@3.25.76)': dependencies: - '@joyid/common': 0.2.1(typescript@5.8.2)(zod@3.25.76) + '@joyid/common': 0.2.2(typescript@5.8.2)(zod@3.25.76) '@nervosnetwork/ckb-sdk-utils': 0.109.5 cross-fetch: 4.0.0 uncrypto: 0.1.3 @@ -4036,7 +3962,7 @@ snapshots: - typescript - zod - '@joyid/common@0.2.1(typescript@5.8.2)(zod@3.25.76)': + '@joyid/common@0.2.2(typescript@5.8.2)(zod@3.25.76)': dependencies: abitype: 0.8.7(typescript@5.8.2)(zod@3.25.76) type-fest: 4.6.0 @@ -4086,7 +4012,7 @@ snapshots: '@modelcontextprotocol/sdk@1.27.1(zod@3.25.76)': dependencies: - '@hono/node-server': 1.19.13(hono@4.12.12) + '@hono/node-server': 1.19.13(hono@4.12.25) ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 @@ -4096,7 +4022,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.3.1(express@5.2.1) - hono: 4.12.12 + hono: 4.12.25 jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -4123,20 +4049,22 @@ snapshots: '@nervosnetwork/ckb-types@0.109.5': {} - '@noble/ciphers@0.5.3': {} + '@noble/ciphers@2.2.0': {} '@noble/curves@1.2.0': dependencies: '@noble/hashes': 1.3.2 - '@noble/curves@1.8.1': + '@noble/curves@2.2.0': dependencies: - '@noble/hashes': 1.7.1 + '@noble/hashes': 2.2.0 '@noble/hashes@1.3.2': {} '@noble/hashes@1.7.1': {} + '@noble/hashes@2.2.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4232,7 +4160,7 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: '@types/node': 20.17.24 - form-data: 4.0.4 + form-data: 4.0.6 '@types/node@12.20.55': {} @@ -4415,10 +4343,6 @@ snapshots: optionalDependencies: zod: 3.25.76 - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -4432,8 +4356,6 @@ snapshots: dependencies: acorn: 8.15.0 - acorn@8.14.1: {} - acorn@8.15.0: {} adm-zip@0.5.16: {} @@ -4456,7 +4378,7 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -4501,17 +4423,13 @@ snapshots: asynckit@0.4.0: {} - available-typed-arrays@1.0.7: + babel-jest@30.2.0(@babel/core@7.29.7): dependencies: - possible-typed-array-names: 1.1.0 - - babel-jest@30.2.0(@babel/core@7.28.6): - dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@jest/transform': 30.2.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 7.0.1 - babel-preset-jest: 30.2.0(@babel/core@7.28.6) + babel-preset-jest: 30.2.0(@babel/core@7.29.7) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -4532,47 +4450,41 @@ snapshots: dependencies: '@types/babel__core': 7.20.5 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.6): - dependencies: - '@babel/core': 7.28.6 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.6) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.6) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.6) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.6) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.6) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.6) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.6) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.6) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.6) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.6) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.6) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.6) - - babel-preset-jest@30.2.0(@babel/core@7.28.6): - dependencies: - '@babel/core': 7.28.6 + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) + + babel-preset-jest@30.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 babel-plugin-jest-hoist: 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.6) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) balanced-match@1.0.2: {} balanced-match@4.0.4: {} - base-x@3.0.11: - dependencies: - safe-buffer: 5.2.1 - base-x@5.0.1: {} base64-js@1.5.1: {} baseline-browser-mapping@2.9.14: {} - bech32@1.1.4: {} - bech32@2.0.0: {} better-path-resolve@1.0.0: @@ -4581,23 +4493,6 @@ snapshots: binary-extensions@2.3.0: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - bip66@1.1.5: - dependencies: - safe-buffer: 5.2.1 - - bitcoinjs-message@2.2.0: - dependencies: - bech32: 1.1.4 - bs58check: 2.1.2 - buffer-equals: 1.0.4 - create-hash: 1.2.0 - secp256k1: 3.8.1 - varuint-bitcoin: 1.1.2 - blessed@0.1.81: {} bn.js@4.12.3: {} @@ -4610,7 +4505,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.0 + qs: 6.15.2 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: @@ -4621,7 +4516,7 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.5: + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -4631,15 +4526,6 @@ snapshots: brorand@1.1.0: {} - browserify-aes@1.2.0: - dependencies: - buffer-xor: 1.0.3 - cipher-base: 1.0.6 - create-hash: 1.2.0 - evp_bytestokey: 1.0.3 - inherits: 2.0.4 - safe-buffer: 5.2.1 - browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.14 @@ -4652,20 +4538,10 @@ snapshots: dependencies: fast-json-stable-stringify: 2.1.0 - bs58@4.0.1: - dependencies: - base-x: 3.0.11 - bs58@6.0.0: dependencies: base-x: 5.0.1 - bs58check@2.1.2: - dependencies: - bs58: 4.0.1 - create-hash: 1.2.0 - safe-buffer: 5.2.1 - bs58check@4.0.0: dependencies: '@noble/hashes': 1.7.1 @@ -4675,12 +4551,8 @@ snapshots: dependencies: node-int64: 0.4.0 - buffer-equals@1.0.4: {} - buffer-from@1.1.2: {} - buffer-xor@1.0.3: {} - buffer@6.0.3: dependencies: base64-js: 1.5.1 @@ -4696,13 +4568,6 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4747,11 +4612,6 @@ snapshots: ci-info@4.3.1: {} - cipher-base@1.0.6: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - cjs-module-lexer@2.2.0: {} cli-cursor@5.0.0: @@ -4835,23 +4695,6 @@ snapshots: nan: 2.22.2 optional: true - create-hash@1.2.0: - dependencies: - cipher-base: 1.0.6 - inherits: 2.0.4 - md5.js: 1.3.5 - ripemd160: 2.0.2 - sha.js: 2.4.12 - - create-hmac@1.1.7: - dependencies: - cipher-base: 1.0.6 - create-hash: 1.2.0 - inherits: 2.0.4 - ripemd160: 2.0.2 - safe-buffer: 5.2.1 - sha.js: 2.4.12 - create-require@1.1.1: {} cross-fetch@4.0.0: @@ -4860,12 +4703,6 @@ snapshots: transitivePeerDependencies: - encoding - cross-fetch@4.1.0: - dependencies: - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4882,12 +4719,6 @@ snapshots: deepmerge@4.3.1: {} - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - delayed-stream@1.0.0: {} depd@2.0.0: {} @@ -4902,12 +4733,6 @@ snapshots: dependencies: path-type: 4.0.0 - drbg.js@1.0.1: - dependencies: - browserify-aes: 1.2.0 - create-hash: 1.2.0 - create-hmac: 1.1.7 - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4970,7 +4795,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 escalade@3.2.0: {} @@ -4998,7 +4823,7 @@ snapshots: '@eslint/core': 0.13.0 '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.26.0 - '@eslint/plugin-kit': 0.2.8 + '@eslint/plugin-kit': 0.3.4 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 @@ -5054,21 +4879,19 @@ snapshots: etag@1.8.1: {} - ethers@6.13.5: + ethers@6.17.0: dependencies: - '@adraffy/ens-normalize': 1.10.1 + '@adraffy/ens-normalize': 1.11.1 '@noble/curves': 1.2.0 '@noble/hashes': 1.3.2 '@types/node': 22.7.5 aes-js: 4.0.0-beta.5 tslib: 2.7.0 - ws: 8.17.1 + ws: 8.21.0 transitivePeerDependencies: - bufferutil - utf-8-validate - event-target-shim@5.0.1: {} - eventemitter3@4.0.7: {} eventemitter3@5.0.1: {} @@ -5079,11 +4902,6 @@ snapshots: dependencies: eventsource-parser: 3.0.6 - evp_bytestokey@1.0.3: - dependencies: - md5.js: 1.3.5 - safe-buffer: 5.2.1 - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -5122,7 +4940,7 @@ snapshots: express-rate-limit@8.3.1(express@5.2.1): dependencies: express: 5.2.1 - ip-address: 10.1.0 + ip-address: 10.1.1 express@5.2.1: dependencies: @@ -5146,7 +4964,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.0 + qs: 6.15.2 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -5173,7 +4991,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.0: {} + fast-uri@3.1.2: {} fastq@1.19.1: dependencies: @@ -5189,8 +5007,6 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-uri-to-path@1.0.0: {} - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -5225,23 +5041,19 @@ snapshots: fn.name@1.1.0: {} - follow-redirects@1.15.9: {} - - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 + follow-redirects@1.16.0: {} foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.4: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.4 mime-types: 2.1.35 forwarded@0.2.0: {} @@ -5283,7 +5095,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-package-type@0.1.0: {} @@ -5351,22 +5163,12 @@ snapshots: has-flag@4.0.0: {} - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - has-symbols@1.1.0: {} has-tostringtag@1.0.2: dependencies: has-symbols: 1.1.0 - hash-base@3.1.0: - dependencies: - inherits: 2.0.4 - readable-stream: 3.6.2 - safe-buffer: 5.2.1 - hash.js@1.1.7: dependencies: inherits: 2.0.4 @@ -5376,13 +5178,17 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hmac-drbg@1.0.1: dependencies: hash.js: 1.1.7 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - hono@4.12.12: {} + hono@4.12.25: {} html-escaper@2.0.2: {} @@ -5397,7 +5203,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.9 + follow-redirects: 1.16.0 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -5448,7 +5254,7 @@ snapshots: inherits@2.0.4: {} - ip-address@10.1.0: {} + ip-address@10.1.1: {} ipaddr.js@1.9.1: {} @@ -5460,8 +5266,6 @@ snapshots: dependencies: binary-extensions: 2.3.0 - is-callable@1.2.7: {} - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -5494,25 +5298,19 @@ snapshots: dependencies: better-path-resolve: 1.0.0 - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.19 - is-windows@1.0.2: {} - isarray@2.0.5: {} - isexe@2.0.0: {} - isomorphic-ws@5.0.0(ws@8.18.1): + isomorphic-ws@5.0.0(ws@8.21.0): dependencies: - ws: 8.18.1 + ws: 8.21.0 istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/parser': 7.28.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -5598,12 +5396,12 @@ snapshots: jest-config@30.2.0(@types/node@20.17.24)(ts-node@10.9.2(@types/node@20.17.24)(typescript@5.8.2)): dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@jest/get-type': 30.1.0 '@jest/pattern': 30.0.1 '@jest/test-sequencer': 30.2.0 '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.6) + babel-jest: 30.2.0(@babel/core@7.29.7) chalk: 4.1.2 ci-info: 4.3.1 deepmerge: 4.3.1 @@ -5783,17 +5581,17 @@ snapshots: jest-snapshot@30.2.0: dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@babel/generator': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.7) '@babel/types': 7.28.6 '@jest/expect-utils': 30.2.0 '@jest/get-type': 30.1.0 '@jest/snapshot-utils': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.6) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) chalk: 4.1.2 expect: 30.2.0 graceful-fs: 4.2.11 @@ -5861,12 +5659,12 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.14.2: + js-yaml@3.15.0: dependencies: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.1: + js-yaml@4.2.0: dependencies: argparse: 2.0.1 @@ -5982,12 +5780,6 @@ snapshots: math-intrinsics@1.1.0: {} - md5.js@1.3.5: - dependencies: - hash-base: 3.1.0 - inherits: 2.0.4 - safe-buffer: 5.2.1 - media-typer@1.1.0: {} merge-descriptors@2.0.0: {} @@ -6029,7 +5821,7 @@ snapshots: minimatch@9.0.8: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.6 minimist@1.2.8: {} @@ -6049,7 +5841,8 @@ snapshots: mute-stream@2.0.0: {} - nan@2.22.2: {} + nan@2.22.2: + optional: true napi-postinstall@0.3.4: {} @@ -6196,8 +5989,6 @@ snapshots: dependencies: find-up: 4.1.0 - possible-typed-array-names@1.1.0: {} - prelude-ls@1.2.1: {} prettier@2.8.8: {} @@ -6219,7 +6010,7 @@ snapshots: pure-rand@7.0.1: {} - qs@6.15.0: + qs@6.15.2: dependencies: side-channel: 1.1.0 @@ -6241,7 +6032,7 @@ snapshots: read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 - js-yaml: 3.14.2 + js-yaml: 3.15.0 pify: 4.0.1 strip-bom: 3.0.0 @@ -6288,11 +6079,6 @@ snapshots: dependencies: glob: 7.2.3 - ripemd160@2.0.2: - dependencies: - hash-base: 3.1.0 - inherits: 2.0.4 - router@2.2.0: dependencies: debug: 4.4.3 @@ -6313,17 +6099,6 @@ snapshots: safer-buffer@2.1.2: {} - secp256k1@3.8.1: - dependencies: - bindings: 1.5.0 - bip66: 1.1.5 - bn.js: 4.12.3 - create-hash: 1.2.0 - drbg.js: 1.0.1 - elliptic: 6.6.1 - nan: 2.22.2 - safe-buffer: 5.2.1 - semver@6.3.1: {} semver@7.7.3: {} @@ -6353,23 +6128,8 @@ snapshots: transitivePeerDependencies: - supports-color - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - setprototypeof@1.2.0: {} - sha.js@2.4.12: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -6514,7 +6274,7 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - tar@7.5.11: + tar@7.5.16: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -6534,12 +6294,6 @@ snapshots: tmpl@1.0.5: {} - to-buffer@1.2.2: - dependencies: - isarray: 2.0.5 - safe-buffer: 5.2.1 - typed-array-buffer: 1.0.3 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -6556,7 +6310,7 @@ snapshots: dependencies: typescript: 5.8.2 - ts-jest@29.4.6(@babel/core@7.28.6)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.6))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.17.24)(ts-node@10.9.2(@types/node@20.17.24)(typescript@5.8.2)))(typescript@5.8.2): + ts-jest@29.4.6(@babel/core@7.29.7)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.7))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.17.24)(ts-node@10.9.2(@types/node@20.17.24)(typescript@5.8.2)))(typescript@5.8.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -6570,10 +6324,10 @@ snapshots: typescript: 5.8.2 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.6) + babel-jest: 30.2.0(@babel/core@7.29.7) jest-util: 30.2.0 ts-node-dev@2.0.0(@types/node@20.17.24)(typescript@5.8.2): @@ -6602,7 +6356,7 @@ snapshots: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 20.17.24 - acorn: 8.14.1 + acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 @@ -6641,12 +6395,6 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - typescript@5.8.2: {} uglify-js@3.19.3: @@ -6704,10 +6452,6 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - varuint-bitcoin@1.1.2: - dependencies: - safe-buffer: 5.2.1 - vary@1.1.2: {} walker@1.0.8: @@ -6721,16 +6465,6 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - which-typed-array@1.1.19: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -6790,9 +6524,7 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.17.1: {} - - ws@8.18.1: {} + ws@8.21.0: {} xtend@4.0.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..d7411c7a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,32 @@ +overrides: + # GHSA-7m2j-8qp9-m8jw: CRLF injection. Remove when no transitive dependency uses form-data >=4.0.0 <4.0.6. + "form-data@>=4.0.0 <4.0.6": "4.0.6" + # GHSA-88fw-hqm2-52qc: CORS middleware reflects any origin with credentials. Remove when hono >=4.12.25. + "hono@<4.12.25": "4.12.25" + # GHSA-q8mj-m7cp-5q26: qs.stringify DoS. Remove when direct dependency upgrades qs >=6.15.2. + "qs@>=6.11.1 <=6.15.1": "6.15.2" + # GHSA-v2v4-37r5-5v8g: XSS in Address6 HTML-emitting methods. Remove when direct dependency upgrades ip-address >=10.1.1. + "ip-address@<=10.1.0": "10.1.1" + # GHSA-h67p-54hq-rp68: quadratic-complexity DoS in merge key handling. Remove when js-yaml 3.x >=3.15.0. + "js-yaml@<3.15.0": "3.15.0" + # GHSA-h67p-54hq-rp68: quadratic-complexity DoS in merge key handling. Remove when js-yaml 4.x >=4.2.0. + "js-yaml@>=4.0.0 <=4.1.1": "4.2.0" + # GHSA-4x5r-pxfx-6jf8: arbitrary file read via sourceMappingURL. Remove when @babel/core >=7.29.6. + "@babel/core@<=7.29.0": "7.29.7" + # GHSA-xffm-g5w8-qvg7: ReDoS in ConfigCommentParser. Remove when @eslint/plugin-kit >=0.3.4. + "@eslint/plugin-kit@<0.3.4": "0.3.4" + # GHSA-jxxr-4gwj-5jf2: unbounded brace range expansion DoS. Remove when brace-expansion >=5.0.6. + "brace-expansion@>=5.0.0 <5.0.6": "5.0.6" + +onlyBuiltDependencies: + - secp256k1 + +minimumReleaseAgeExclude: + # Matches the overrides above; these versions are very recent but required for the security fixes. + - "@eslint/plugin-kit@0.3.4" + - "ip-address@10.1.1" + - "qs@6.15.2" + - "brace-expansion@5.0.6" + - "@babel/core@7.29.7" + - "js-yaml@3.15.0" + - "js-yaml@4.2.0" diff --git a/src/cfg/setting.ts b/src/cfg/setting.ts index 962ac1ab..113f17ab 100644 --- a/src/cfg/setting.ts +++ b/src/cfg/setting.ts @@ -52,9 +52,13 @@ export interface Settings { transactionsPath: string; }; tools: { + rootFolder: string; ckbDebugger: { minVersion: string; }; + ckbTui: { + version: string; + }; }; } @@ -62,7 +66,7 @@ export const defaultSettings: Settings = { proxy: undefined, bins: { rootFolder: path.resolve(dataPath, 'bins'), - defaultCKBVersion: '0.205.0', + defaultCKBVersion: '0.207.0', downloadPath: path.resolve(cachePath, 'download'), }, devnet: { @@ -88,9 +92,13 @@ export const defaultSettings: Settings = { transactionsPath: path.resolve(dataPath, 'mainnet/transactions'), }, tools: { + rootFolder: path.resolve(dataPath, 'tools'), ckbDebugger: { minVersion: '0.200.0', }, + ckbTui: { + version: 'v0.1.3', + }, }, }; @@ -98,7 +106,10 @@ export function readSettings(): Settings { try { if (fs.existsSync(configPath)) { const data = fs.readFileSync(configPath, 'utf8'); - return deepMerge(defaultSettings, JSON.parse(data)) as Settings; + const parsed = JSON.parse(data); + validateSettings(parsed); + // Deep-clone defaults before merging to prevent mutation of the shared default + return deepMerge(deepClone(defaultSettings), parsed) as Settings; } else { return defaultSettings; } @@ -129,10 +140,27 @@ export function getCKBBinaryPath(version: string) { return path.join(getCKBBinaryInstallPath(version), binaryName); } +function deepClone(obj: T): T { + if (obj === null || typeof obj !== 'object') { + return obj; + } + if (Array.isArray(obj)) { + return obj.map(deepClone) as unknown as T; + } + const clone: Record = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + clone[key] = deepClone((obj as Record)[key]); + } + } + return clone as T; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any function deepMerge(target: any, source: any): any { for (const key in source) { - if (source[key] && typeof source[key] === 'object') { - if (!target[key]) { + if (source[key] !== null && typeof source[key] === 'object' && !Array.isArray(source[key])) { + if (!target[key] || typeof target[key] !== 'object') { target[key] = {}; } deepMerge(target[key], source[key]); @@ -142,3 +170,30 @@ function deepMerge(target: any, source: any): any { } return target; } + +function validateSettings(raw: unknown): void { + if (!raw || typeof raw !== 'object') { + throw new Error('Settings must be a JSON object'); + } + + const obj = raw as Record; + + if (obj.tools && typeof obj.tools === 'object') { + const tools = obj.tools as Record; + if (tools.rootFolder !== undefined && typeof tools.rootFolder !== 'string') { + throw new Error('tools.rootFolder must be a string path'); + } + if (tools.ckbTui && typeof tools.ckbTui === 'object') { + const ckbTui = tools.ckbTui as Record; + if (ckbTui.version !== undefined && typeof ckbTui.version !== 'string') { + throw new Error('tools.ckbTui.version must be a string'); + } + } + } + + if (obj.proxy !== undefined && obj.proxy !== null) { + if (typeof obj.proxy !== 'object') { + throw new Error('proxy must be an object'); + } + } +} diff --git a/src/cli.ts b/src/cli.ts index c1ffa213..f5f0b25a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { Command } from 'commander'; -import { startNode } from './cmd/node'; +import { Command, Option } from 'commander'; +import { startNode, stopNode } from './cmd/node'; import { accounts } from './cmd/accounts'; import { clean } from './cmd/clean'; import { setUTF8EncodingForWindows } from './util/encoding'; @@ -8,6 +8,7 @@ import { DepositOptions, deposit } from './cmd/deposit'; import { DeployOptions, deploy } from './cmd/deploy'; import { TransferOptions, transfer } from './cmd/transfer'; import { BalanceOption, balanceOf } from './cmd/balance'; +import { udtIssue, udtDestroy, UdtIssueOption, UdtDestroyOption } from './cmd/udt'; import { createScriptProject, CreateScriptProjectOptions } from './cmd/create'; import { Config, ConfigItem } from './cmd/config'; import { devnetConfig } from './cmd/devnet-config'; @@ -18,6 +19,8 @@ import { genSystemScriptsJsonFile } from './scripts/gen'; import { CKBDebugger } from './tools/ckb-debugger'; import { logger } from './util/logger'; import { Network } from './type/base'; +import { status } from './cmd/status'; +import { validateNetworkOpt } from './util/validator'; const version = require('../package.json').version; const description = require('../package.json').description; @@ -28,7 +31,15 @@ setUTF8EncodingForWindows(); const program = new Command(); program.name('offckb').description(description).version(version).enablePositionalOptions(); -program +program.option('--json', 'Output logs in JSON format for agent/programmatic consumption'); +program.hook('preAction', (thisCommand) => { + const opts = thisCommand.opts(); + if (opts.json) { + logger.setJsonMode(true); + } +}); + +const nodeCommand = program .command('node [CKB-Version]') .description('Use the CKB to start devnet') .option('--network ', 'Specify the network to deploy to', 'devnet') @@ -36,10 +47,16 @@ program '-b, --binary-path ', 'Specify the CKB binary path to use, only for devnet, when set, will ignore version and network', ) - .action(async (version: string, options: { network: Network; binaryPath?: string }) => { - return startNode({ version, network: options.network, binaryPath: options.binaryPath }); + .option('--daemon', 'Run the node in the background as a daemon (devnet only)') + .action(async (version: string, options: { network: Network; binaryPath?: string; daemon?: boolean }) => { + return startNode({ version, network: options.network, binaryPath: options.binaryPath, daemon: options.daemon }); }); +nodeCommand + .command('stop') + .description('Stop the running CKB devnet daemon') + .action(async () => stopNode()); + program .command('create [project-name]') .description('Create a new CKB Smart Contract project in JavaScript.') @@ -123,13 +140,15 @@ program }); program - .command('transfer [toAddress] [amountInCKB]') - .description('Transfer CKB tokens to address, only devnet and testnet') + .command('transfer [toAddress] [amount]') + .description('Transfer CKB or UDT tokens to address, only devnet and testnet') .option('--network ', 'Specify the network to transfer to', 'devnet') - .option('--privkey ', 'Specify the private key to transfer CKB') + .option('--privkey ', 'Specify the private key to transfer') + .addOption(new Option('--udt-kind ', 'Specify the UDT kind').choices(['sudt', 'xudt']).default('sudt')) + .option('--udt-type-args ', 'Specify the UDT type script args to transfer UDT') .option('-r, --proxy-rpc', 'Use Proxy RPC to connect to blockchain') - .action(async (toAddress: string, amountInCKB: string, options: TransferOptions) => { - return transfer(toAddress, amountInCKB, options); + .action(async (toAddress: string, amount: string, options: TransferOptions) => { + return transfer(toAddress, amount, options); }); program @@ -144,12 +163,40 @@ program program .command('balance [toAddress]') - .description('Check account balance, only devnet and testnet') + .description('Check account balance (CKB + detected SUDT/xUDT), only devnet and testnet') .option('--network ', 'Specify the network to check', 'devnet') + .addOption(new Option('--udt-kind ', 'Filter by UDT kind').choices(['sudt', 'xudt'])) + .option('--udt-type-args ', 'Filter by UDT type script args') + .option('--no-udt', 'Skip UDT balance scan') .action(async (toAddress: string, options: BalanceOption) => { return balanceOf(toAddress, options); }); +const udtCommand = program.command('udt').description('UDT token commands'); + +udtCommand + .command('issue ') + .description('Issue new UDT tokens, only devnet and testnet') + .option('--network ', 'Specify the network', 'devnet') + .addOption(new Option('--udt-kind ', 'Specify the UDT kind').choices(['sudt', 'xudt']).default('sudt')) + .option('--type-args ', 'Specify the UDT type script args (xudt only; defaults to signer lock hash)') + .option('--to ', 'Specify the receiver address (defaults to signer)') + .option('--privkey ', 'Specify the private key to issue UDT') + .action(async (amount: string, options: UdtIssueOption) => { + return udtIssue(amount, options); + }); + +udtCommand + .command('destroy ') + .description('Destroy UDT tokens, only devnet and testnet') + .option('--network ', 'Specify the network', 'devnet') + .addOption(new Option('--udt-kind ', 'Specify the UDT kind').choices(['sudt', 'xudt']).default('sudt')) + .requiredOption('--type-args ', 'Specify the UDT type script args') + .option('--privkey ', 'Specify the private key to destroy UDT') + .action(async (amount: string, options: UdtDestroyOption) => { + return udtDestroy(amount, options); + }); + program .command('debugger') .description('Port of the raw CKB Standalone Debugger') @@ -160,6 +207,15 @@ program return CKBDebugger.runWithArgs(process.argv.slice(2)); }); +program + .command('status') + .description('Show ckb-tui status interface') + .option('--network ', 'Specify the network whose node status to monitor', 'devnet') + .action(async (option) => { + validateNetworkOpt(option.network); + return await status({ network: option.network }); + }); + program .command('config [item] [value]') .description('do a configuration action') diff --git a/src/cmd/balance.ts b/src/cmd/balance.ts index 5e11d3c4..a036a095 100644 --- a/src/cmd/balance.ts +++ b/src/cmd/balance.ts @@ -1,9 +1,13 @@ -import { CKB } from '../sdk/ckb'; +import { CKB, UdtBalanceInfo } from '../sdk/ckb'; import { validateNetworkOpt } from '../util/validator'; -import { NetworkOption, Network } from '../type/base'; +import { NetworkOption, Network, UdtKind } from '../type/base'; import { logger } from '../util/logger'; -export interface BalanceOption extends NetworkOption {} +export interface BalanceOption extends NetworkOption { + udtKind?: UdtKind; + udtTypeArgs?: string; + udt?: boolean; +} export async function balanceOf(address: string, opt: BalanceOption = { network: Network.devnet }) { const network = opt.network; @@ -11,7 +15,32 @@ export async function balanceOf(address: string, opt: BalanceOption = { network: const ckb = new CKB({ network }); - const balanceInCKB = await ckb.balance(address); - logger.info(`Balance: ${balanceInCKB} CKB`); + const [balanceInCKB, udtBalances] = await Promise.all([ + ckb.balance(address), + opt.udt !== false ? ckb.detectUdtBalances(address) : Promise.resolve([]), + ]); + logger.info(`CKB: ${balanceInCKB}`); + + const filtered = filterUdtBalances(udtBalances, opt); + + if (filtered.length > 0) { + logger.info('UDT:'); + for (const udt of filtered) { + logger.info(` ${udt.kind} (args=${udt.args}): ${udt.balance}`); + } + } + process.exit(0); } + +function filterUdtBalances(balances: UdtBalanceInfo[], opt: BalanceOption): UdtBalanceInfo[] { + if (!opt.udtKind && !opt.udtTypeArgs) { + return balances; + } + + return balances.filter((udt) => { + const kindMatch = opt.udtKind ? udt.kind === opt.udtKind : true; + const argsMatch = opt.udtTypeArgs ? udt.args === opt.udtTypeArgs : true; + return kindMatch && argsMatch; + }); +} diff --git a/src/cmd/node.ts b/src/cmd/node.ts index e0e67d13..0b5faf28 100644 --- a/src/cmd/node.ts +++ b/src/cmd/node.ts @@ -1,4 +1,6 @@ -import { exec } from 'child_process'; +import { exec, spawn } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; import { initChainIfNeeded } from '../node/init-chain'; import { installCKBBinary } from '../node/install'; import { getCKBBinaryPath, readSettings } from '../cfg/setting'; @@ -11,16 +13,31 @@ export interface NodeProp { version?: string; network?: Network; binaryPath?: string; + daemon?: boolean; } -export function startNode({ version, network = Network.devnet, binaryPath }: NodeProp) { +interface PidMetadata { + pid: number; + scriptPath: string; + startedAt: string; +} + +const DAEMON_LOG_DIR = 'logs'; +const DAEMON_LOG_FILE = 'daemon.log'; +const DAEMON_PID_FILE = 'daemon.pid'; +const DAEMON_CHILD_ENV = 'OFFCKB_DAEMON_CHILD'; + +export function startNode({ version, network = Network.devnet, binaryPath, daemon }: NodeProp) { if (binaryPath && network !== Network.devnet) { logger.warn('Custom binaryPath is only supported for devnet. The provided binaryPath will be ignored.'); } + if (daemon && network !== Network.devnet) { + logger.warn('Daemon mode is only supported for devnet. The daemon flag will be ignored.'); + } switch (network) { case Network.devnet: - return nodeDevnet({ version, binaryPath }); + return nodeDevnet({ version, binaryPath, daemon }); case Network.testnet: return nodeTestnet(); case Network.mainnet: @@ -30,7 +47,11 @@ export function startNode({ version, network = Network.devnet, binaryPath }: Nod } } -export async function nodeDevnet({ version, binaryPath }: NodeProp) { +export async function nodeDevnet({ version, binaryPath, daemon }: NodeProp) { + if (daemon) { + return startDaemon(); + } + const settings = readSettings(); const ckbVersion = version || settings.bins.defaultCKBVersion; let ckbBinPath = ''; @@ -86,6 +107,339 @@ export async function nodeDevnet({ version, binaryPath }: NodeProp) { } } +function resolveDaemonPaths() { + const settings = readSettings(); + const logDir = path.join(settings.devnet.dataPath, DAEMON_LOG_DIR); + const logFile = path.join(logDir, DAEMON_LOG_FILE); + const pidFile = path.join(logDir, DAEMON_PID_FILE); + return { logDir, logFile, pidFile }; +} + +function readPidFile(pidFile: string): PidMetadata | null { + let raw: string; + try { + raw = fs.readFileSync(pidFile, 'utf8').trim(); + } catch (error) { + // Treat a missing or unreadable PID file as "no daemon". + return null; + } + + if (!raw) { + return null; + } + + // Backward compatibility: plain integer PID written by older versions. + const plainPid = Number(raw); + if (Number.isInteger(plainPid) && plainPid > 0) { + return { pid: plainPid, scriptPath: resolveCliEntry() ?? '', startedAt: new Date(0).toISOString() }; + } + + try { + const parsed = JSON.parse(raw) as Partial; + const pid = Number(parsed.pid); + if (Number.isInteger(pid) && pid > 0 && typeof parsed.scriptPath === 'string') { + return { + pid, + scriptPath: parsed.scriptPath, + startedAt: parsed.startedAt ?? new Date(0).toISOString(), + }; + } + } catch { + // fall through to sentinel below + } + + // Content exists but is neither a valid plain PID nor valid metadata. + // Return a sentinel so stopNode can report an invalid PID and clean up. + return { pid: NaN, scriptPath: '', startedAt: new Date(0).toISOString() }; +} + +function writePidFile(pidFile: string, metadata: PidMetadata) { + fs.writeFileSync(pidFile, JSON.stringify(metadata, null, 2)); +} + +function resolveCliEntry(): string | null { + // In priority order. process.argv[1] is the most reliable for a Node CLI. + // OFFCKB_CLI_PATH is an escape hatch for packaged/npx/weird environments. + // require.main?.filename is a final fallback when argv is unavailable. + const candidates = [process.env.OFFCKB_CLI_PATH, process.argv[1], require.main?.filename].filter( + (c): c is string => typeof c === 'string' && c.length > 0, + ); + + for (const candidate of candidates) { + try { + const resolved = path.resolve(candidate); + const stats = fs.statSync(resolved); + if (stats.isFile()) { + return resolved; + } + } catch { + // Candidate is missing or not a file; try the next one. + } + } + + return null; +} + +function isProcessAlive(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +function cleanupPidFile(pidFile: string) { + try { + fs.unlinkSync(pidFile); + } catch (error) { + logger.warn(`Failed to remove PID file ${pidFile}:`, error); + } +} + +function waitForProcessExit(pid: number, timeoutMs: number): Promise { + const start = Date.now(); + return new Promise((resolve) => { + const check = () => { + if (!isProcessAlive(pid)) { + resolve(true); + return; + } + if (Date.now() - start >= timeoutMs) { + resolve(false); + return; + } + setTimeout(check, 100); + }; + check(); + }); +} + +function getProcessCommandLine(pid: number): Promise { + return new Promise((resolve) => { + if (process.platform === 'win32') { + exec(`wmic process where ProcessId=${pid} get CommandLine /format:list`, (error, stdout) => { + if (error) { + resolve(null); + return; + } + const match = stdout.match(/CommandLine=(.+)/); + resolve(match ? match[1].trim() : null); + }); + } else { + exec(`ps -p ${pid} -o args=`, (error, stdout) => { + if (error) { + resolve(null); + return; + } + resolve(stdout.trim()); + }); + } + }); +} + +async function verifyDaemonIdentity(pid: number, metadata: PidMetadata): Promise { + const cmdline = await getProcessCommandLine(pid); + if (!cmdline) { + return false; + } + + // The daemon child re-runs the same CLI entry point, so its command line + // should reference the same script and should be a Node process. + const scriptName = path.basename(metadata.scriptPath); + const scriptDir = path.dirname(metadata.scriptPath); + const looksLikeNode = cmdline.includes('node') || cmdline.includes('nodejs'); + const looksLikeOurScript = + cmdline.includes(metadata.scriptPath) || (scriptName !== '' && cmdline.includes(scriptName)); + const looksLikeOffckb = cmdline.includes('offckb') || scriptDir.includes('offckb'); + + return looksLikeNode && (looksLikeOurScript || looksLikeOffckb); +} + +function terminateProcess(pid: number, signal: 'SIGTERM' | 'SIGKILL'): Promise { + return new Promise((resolve, reject) => { + if (process.platform === 'win32') { + // Windows has no POSIX signals and process.kill(pid) only terminates the + // single process. Use taskkill to terminate the whole tree. + // /T kills the process and all child processes. + // /F forces termination when SIGKILL is requested. + const args = signal === 'SIGKILL' ? ['/T', '/F', '/PID', String(pid)] : ['/T', '/PID', String(pid)]; + const taskkill = spawn('taskkill', args, { stdio: 'ignore' }); + taskkill.on('error', reject); + taskkill.on('exit', () => { + // taskkill may return non-zero if the process is already gone, which + // is acceptable for our purposes. + resolve(); + }); + return; + } + + // On POSIX, detached: true makes the child a session/process group leader. + // A negative pid sends the signal to the entire process group, ensuring + // the CKB node, miner and RPC proxy all receive it. + try { + process.kill(-pid, signal); + resolve(); + } catch (error) { + reject(error); + } + }); +} + +function startDaemon() { + const { logDir, logFile, pidFile } = resolveDaemonPaths(); + + // Prevent duplicate daemon starts. If a daemon is already running, refuse + // to overwrite its PID file. + const existing = readPidFile(pidFile); + if (existing && isProcessAlive(existing.pid)) { + logger.error(`A CKB devnet daemon is already running (PID ${existing.pid}). Stop it first with: offckb node stop`); + return; + } + if (existing && !isProcessAlive(existing.pid)) { + // Stale PID file from a crashed daemon; clean it up before starting anew. + cleanupPidFile(pidFile); + } + + let out: number | undefined; + let err: number | undefined; + try { + fs.mkdirSync(logDir, { recursive: true }); + out = fs.openSync(logFile, 'a'); + err = fs.openSync(logFile, 'a'); + } catch (error) { + logger.error(`Failed to prepare daemon log directory or log file at ${logFile}:`, error); + return; + } + + const scriptPath = resolveCliEntry(); + if (!scriptPath) { + logger.error('Unable to determine the CLI entry point for daemon mode. Set OFFCKB_CLI_PATH to the offckb script.'); + closeFileDescriptors(out, err); + return; + } + + const childArgs = process.argv.slice(2).filter((arg) => arg !== '--daemon'); + const childEnv = { ...process.env, [DAEMON_CHILD_ENV]: '1' }; + + let child; + try { + child = spawn(process.execPath, [scriptPath, ...childArgs], { + detached: true, + stdio: ['ignore', out, err], + env: childEnv, + }); + } catch (error) { + logger.error('Failed to spawn daemon process:', error); + closeFileDescriptors(out, err); + return; + } + + if (!child.pid) { + logger.error('Failed to spawn daemon process: no PID returned.'); + closeFileDescriptors(out, err); + return; + } + + child.unref(); + + child.on('error', (error) => { + logger.error('Daemon child process failed to start:', error); + cleanupPidFile(pidFile); + }); + + const metadata: PidMetadata = { + pid: child.pid, + scriptPath, + startedAt: new Date().toISOString(), + }; + writePidFile(pidFile, metadata); + + // File descriptors are now owned by the spawned child; close our copies. + closeFileDescriptors(out, err); + + logger.success(`CKB devnet daemon started with PID ${child.pid}.`); + logger.info(`Logs: ${logFile}`); + logger.info(`PID file: ${pidFile}`); + logger.info('Stop the daemon with: offckb node stop'); +} + +function closeFileDescriptors(...fds: (number | undefined)[]) { + for (const fd of fds) { + if (fd === undefined) continue; + try { + fs.closeSync(fd); + } catch { + // ignore + } + } +} + +export async function stopNode() { + const { pidFile } = resolveDaemonPaths(); + + const metadata = readPidFile(pidFile); + if (!metadata) { + logger.warn(`No daemon PID file found at ${pidFile}. Is the devnet daemon running?`); + return; + } + + const pid = metadata.pid; + if (!Number.isInteger(pid) || pid <= 0) { + logger.error(`Invalid PID in ${pidFile}: ${pid}`); + cleanupPidFile(pidFile); + return; + } + + if (!isProcessAlive(pid)) { + logger.warn(`Daemon process ${pid} is not running.`); + cleanupPidFile(pidFile); + return; + } + + const identityOk = await verifyDaemonIdentity(pid, metadata); + if (!identityOk) { + logger.error( + `Process ${pid} does not appear to be the offckb daemon. Refusing to send signals to avoid killing an unrelated process. ` + + `If you are sure this is the daemon, stop it manually and remove ${pidFile}.`, + ); + return; + } + + logger.info(`Stopping CKB devnet daemon (PID ${pid})...`); + try { + await terminateProcess(pid, 'SIGTERM'); + } catch (error) { + const err = error as NodeJS.ErrnoException; + if (err.code === 'ESRCH') { + logger.warn(`Daemon process ${pid} is not running.`); + cleanupPidFile(pidFile); + return; + } + if (err.code === 'EPERM') { + logger.error(`Permission denied when sending SIGTERM to daemon process ${pid}.`); + } else { + logger.error(`Failed to send SIGTERM to daemon process ${pid}:`, error); + } + // Still try to clean up the PID file so the user can recover. + cleanupPidFile(pidFile); + return; + } + + const exited = await waitForProcessExit(pid, 5000); + if (!exited) { + logger.warn(`Daemon process ${pid} did not exit gracefully, sending SIGKILL...`); + try { + await terminateProcess(pid, 'SIGKILL'); + } catch (error) { + logger.error(`Failed to send SIGKILL to daemon process ${pid}:`, error); + } + } + + cleanupPidFile(pidFile); + logger.success('CKB devnet daemon stopped.'); +} + export async function nodeTestnet() { // todo: maybe we can actually start a node for testnet later // by default we start a proxy server for testnet diff --git a/src/cmd/status.ts b/src/cmd/status.ts new file mode 100644 index 00000000..881effea --- /dev/null +++ b/src/cmd/status.ts @@ -0,0 +1,80 @@ +import { readSettings } from '../cfg/setting'; +import { CKBTui } from '../tools/ckb-tui'; +import { Network } from '../type/base'; +import { logger } from '../util/logger'; +import * as net from 'net'; + +export interface StatusOptions { + network: Network; +} + +type NetworkSettingsKey = 'devnet' | 'testnet' | 'mainnet'; + +const NETWORK_SETTINGS_KEY: Record = { + [Network.devnet]: 'devnet', + [Network.testnet]: 'testnet', + [Network.mainnet]: 'mainnet', +}; + +export async function status({ network }: StatusOptions) { + // ckb-tui is an interactive terminal UI. Running it without a TTY + // (pipe, redirect, CI) would hang or produce garbage output. + if (!process.stdout.isTTY || !process.stdin.isTTY) { + logger.error( + 'The status command requires an interactive terminal (TTY). ' + + 'It cannot be used in pipes, redirects, or non-interactive environments like CI.', + ); + process.exit(1); + } + + const settings = readSettings(); + const networkKey = NETWORK_SETTINGS_KEY[network]; + const port = settings[networkKey].rpcProxyPort; + const url = `http://127.0.0.1:${port}`; + const isListening = await isRPCPortListening(port); + if (!isListening) { + logger.error( + `RPC port ${port} is not listening. Please make sure the ${network} node is running and Proxy RPC is enabled.`, + ); + return; + } + const result = CKBTui.run(['-r', url]); + // Propagate ckb-tui exit code so scripts can detect TUI failure + if (result.status !== 0) { + process.exitCode = result.status ?? 1; + } +} + +async function isRPCPortListening(port: number): Promise { + if (!Number.isInteger(port) || port < 1 || port > 65535) { + return false; + } + const client = new net.Socket(); + return new Promise((resolve) => { + let settled = false; + const TIMEOUT_MS = 2000; + const timeout = setTimeout(() => { + if (!settled) { + settled = true; + client.destroy(); + resolve(false); + } + }, TIMEOUT_MS); + client.once('error', () => { + if (!settled) { + settled = true; + clearTimeout(timeout); + resolve(false); + } + }); + client.once('connect', () => { + if (!settled) { + settled = true; + clearTimeout(timeout); + client.end(); + resolve(true); + } + }); + client.connect(port, '127.0.0.1'); + }); +} diff --git a/src/cmd/transfer.ts b/src/cmd/transfer.ts index bea46883..1518eb3e 100644 --- a/src/cmd/transfer.ts +++ b/src/cmd/transfer.ts @@ -1,18 +1,15 @@ import { CKB } from '../sdk/ckb'; -import { NetworkOption, Network } from '../type/base'; -import { buildTestnetTxLink } from '../util/link'; -import { validateNetworkOpt } from '../util/validator'; -import { logger } from '../util/logger'; +import { NetworkOption, Network, UdtKind } from '../type/base'; +import { logTxSuccess } from '../util/link'; +import { validateNetworkOpt, validateUdtKind, validateUdtTypeArgs } from '../util/validator'; export interface TransferOptions extends NetworkOption { privkey?: string | null; + udtKind?: UdtKind; + udtTypeArgs?: string; } -export async function transfer( - toAddress: string, - amountInCKB: string, - opt: TransferOptions = { network: Network.devnet }, -) { +export async function transfer(toAddress: string, amount: string, opt: TransferOptions = { network: Network.devnet }) { const network = opt.network; validateNetworkOpt(network); @@ -23,15 +20,27 @@ export async function transfer( const privateKey = opt.privkey; const ckb = new CKB({ network }); + if (opt.udtTypeArgs) { + const kind = opt.udtKind ?? 'sudt'; + validateUdtKind(kind); + const udtTypeArgs = validateUdtTypeArgs(kind, opt.udtTypeArgs); + const udtType = await ckb.buildUdtTypeScript(kind, udtTypeArgs); + const txHash = await ckb.udtTransfer({ + toAddress, + amount, + privateKey, + udtType, + kind, + }); + + logTxSuccess(network, txHash, 'transfer UDT'); + return; + } + const txHash = await ckb.transfer({ toAddress, - amountInCKB, + amountInCKB: amount, privateKey, }); - if (network === 'testnet') { - logger.info(`Successfully transfer, check ${buildTestnetTxLink(txHash)} for details.`); - return; - } - - logger.info('Successfully transfer, txHash:', txHash); + logTxSuccess(network, txHash, 'transfer'); } diff --git a/src/cmd/udt.ts b/src/cmd/udt.ts new file mode 100644 index 00000000..4d7ea238 --- /dev/null +++ b/src/cmd/udt.ts @@ -0,0 +1,64 @@ +import { CKB } from '../sdk/ckb'; +import { NetworkOption, Network, UdtKind } from '../type/base'; +import { logTxSuccess } from '../util/link'; +import { validateNetworkOpt, validateUdtKind, validateUdtTypeArgs } from '../util/validator'; + +export interface UdtIssueOption extends NetworkOption { + udtKind: UdtKind; + typeArgs?: string; + to?: string; + privkey: string; +} + +export interface UdtDestroyOption extends NetworkOption { + udtKind: UdtKind; + typeArgs: string; + privkey: string; +} + +export async function udtIssue( + amount: string, + opt: UdtIssueOption = { network: Network.devnet, udtKind: 'sudt', privkey: '' }, +) { + const network = opt.network; + validateNetworkOpt(network); + validateUdtKind(opt.udtKind); + + if (!opt.privkey) { + throw new Error('--privkey is required!'); + } + + const ckb = new CKB({ network }); + const txHash = await ckb.udtIssue({ + privateKey: opt.privkey, + kind: opt.udtKind, + amount, + typeArgs: opt.typeArgs ? validateUdtTypeArgs(opt.udtKind, opt.typeArgs) : undefined, + toAddress: opt.to, + }); + + logTxSuccess(network, txHash, 'issued UDT'); +} + +export async function udtDestroy( + amount: string, + opt: UdtDestroyOption = { network: Network.devnet, udtKind: 'sudt', typeArgs: '', privkey: '' }, +) { + const network = opt.network; + validateNetworkOpt(network); + validateUdtKind(opt.udtKind); + + if (!opt.privkey) { + throw new Error('--privkey is required!'); + } + + const ckb = new CKB({ network }); + const txHash = await ckb.udtDestroy({ + privateKey: opt.privkey, + kind: opt.udtKind, + amount, + typeArgs: validateUdtTypeArgs(opt.udtKind, opt.typeArgs), + }); + + logTxSuccess(network, txHash, 'destroyed UDT'); +} diff --git a/src/scripts/private.ts b/src/scripts/private.ts index 73b8c02d..e9afc33d 100644 --- a/src/scripts/private.ts +++ b/src/scripts/private.ts @@ -73,6 +73,10 @@ export function toCCCKnownScripts(scripts: SystemScriptsRecord) { hashType: 'type', cellDeps: [], }, + // ccc >= 1.14.0 calls getKnownScript(NervosDao) during completeFeeBy + // for all inputs. Devnet deploys the DAO system cell, so map it to the + // actual devnet script derived from list-hashes. + [KnownScript.NervosDao]: scripts.dao!.script, }; return DEVNET_SCRIPTS; } diff --git a/src/sdk/ckb.ts b/src/sdk/ckb.ts index 1c07cdc4..328a1853 100644 --- a/src/sdk/ckb.ts +++ b/src/sdk/ckb.ts @@ -2,13 +2,26 @@ // to replace lumos with ccc import { ccc, ClientPublicMainnet, ClientPublicTestnet, OutPointLike, Script } from '@ckb-ccc/core'; -import { isValidNetworkString, normalizePrivKey } from '../util/validator'; +import { isValidNetworkString, normalizePrivKey, validateUdtAmount, validateUdtTypeArgs } from '../util/validator'; import { networks } from './network'; -import { buildCCCDevnetKnownScripts } from '../scripts/private'; +import { buildCCCDevnetKnownScripts, getDevnetSystemScriptsFromListHashes } from '../scripts/private'; +import { MAINNET_SYSTEM_SCRIPTS, TESTNET_SYSTEM_SCRIPTS } from '../scripts/public'; +import { SystemScriptsRecord } from '../scripts/type'; import { Migration } from '../deploy/migration'; -import { Network, HexNumber, HexString } from '../type/base'; +import { Network, HexNumber, HexString, UdtKind } from '../type/base'; + +export { UdtKind } from '../type/base'; import { logger } from '../util/logger'; +const DEFAULT_UDT_SCAN_MAX_CELLS = 1000; +const DEFAULT_UDT_DESTROY_MAX_INPUT_CELLS = 100; + +interface UdtScriptInfo { + codeHash: HexString; + hashType: string; + cellDeps: ccc.CellDepInfoLike[]; +} + export class CKBProps { network?: Network; feeRate?: number; @@ -31,6 +44,45 @@ export interface TransferOption { export type TransferAllOption = Pick; +export interface UdtTransferOption { + privateKey: HexString; + toAddress: string; + amount: HexNumber; + udtType: ccc.Script; + kind: UdtKind; +} + +export interface UdtIssueOption { + privateKey: HexString; + kind: UdtKind; + amount: HexNumber; + typeArgs?: HexString; + toAddress?: string; +} + +export interface UdtDestroyOption { + privateKey: HexString; + kind: UdtKind; + typeArgs: HexString; + amount: HexNumber; +} + +export interface UdtBalanceInfo { + kind: UdtKind; + codeHash: HexString; + hashType: string; + args: HexString; + balance: string; +} + +function readUdtBalance(outputData: string | ccc.HexLike): bigint | null { + try { + return BigInt(ccc.udtBalanceFrom(outputData)); + } catch { + return null; + } +} + export class CKB { public network: Network; public feeRate: number; @@ -165,6 +217,296 @@ export class CKB { return txHash; } + private getSystemScripts(): SystemScriptsRecord { + if (this.network === Network.mainnet) { + return MAINNET_SYSTEM_SCRIPTS; + } + if (this.network === Network.testnet) { + return TESTNET_SYSTEM_SCRIPTS; + } + const scripts = getDevnetSystemScriptsFromListHashes(); + if (!scripts) { + throw new Error(`Failed to load devnet system scripts`); + } + return scripts; + } + + async buildUdtTypeScript(kind: UdtKind, args: HexString): Promise { + if (kind === 'xudt') { + return ccc.Script.fromKnownScript(this.client, ccc.KnownScript.XUdt, args); + } + + const scriptInfo = await this.getUdtScriptInfo(kind); + return ccc.Script.from({ + codeHash: scriptInfo.codeHash, + hashType: scriptInfo.hashType, + args, + }); + } + + private async getUdtScriptInfo(kind: UdtKind): Promise { + if (kind === 'xudt') { + return this.client.getKnownScript(ccc.KnownScript.XUdt); + } + + const systemScripts = this.getSystemScripts(); + const sudtScript = systemScripts.sudt?.script; + if (!sudtScript) { + throw new Error(`SUDT script not found on ${this.network}`); + } + return sudtScript; + } + + async detectUdtBalances( + address: string, + { maxCells = DEFAULT_UDT_SCAN_MAX_CELLS }: { maxCells?: number } = {}, + ): Promise { + const lock = (await ccc.Address.fromString(address, this.client)).script; + + const sudtScriptInfo = await this.getUdtScriptInfo('sudt').catch(() => null); + const xudtScriptInfo = await this.getUdtScriptInfo('xudt').catch(() => null); + + const balances = new Map< + string, + { kind: UdtKind; codeHash: HexString; hashType: string; args: HexString; balance: bigint } + >(); + + let scanned = 0; + + const scan = async (scriptInfo: UdtScriptInfo, kind: UdtKind) => { + for await (const cell of this.client.findCells( + { + script: { + codeHash: scriptInfo.codeHash, + hashType: scriptInfo.hashType, + args: '0x', + }, + scriptType: 'type', + scriptSearchMode: 'prefix', + filter: { script: lock }, + withData: true, + }, + 'asc', + )) { + if (scanned >= maxCells) { + logger.warn(`UDT balance scan stopped after ${maxCells} cells; balances may be incomplete`); + break; + } + scanned++; + + const type = cell.cellOutput.type; + if (!type) { + continue; + } + + const cellBalance = readUdtBalance(cell.outputData); + if (cellBalance == null) { + logger.debug(`Skipping corrupted UDT cell ${cell.outPoint?.txHash}:${cell.outPoint?.index}`); + continue; + } + + const key = `${kind}:${type.codeHash}:${type.hashType}:${type.args}`; + const entry = balances.get(key); + if (entry) { + entry.balance += cellBalance; + } else { + balances.set(key, { + kind, + codeHash: type.codeHash as HexString, + hashType: String(type.hashType), + args: type.args as HexString, + balance: cellBalance, + }); + } + } + }; + + if (sudtScriptInfo) { + await scan(sudtScriptInfo, 'sudt'); + } + if (xudtScriptInfo) { + await scan(xudtScriptInfo, 'xudt'); + } + + return Array.from(balances.values()).map((item) => ({ + ...item, + balance: item.balance.toString(), + })); + } + + async udtBalance( + address: string, + udtType: ccc.Script, + { maxCells = DEFAULT_UDT_SCAN_MAX_CELLS }: { maxCells?: number } = {}, + ): Promise { + const lock = (await ccc.Address.fromString(address, this.client)).script; + let balance = BigInt(0); + let scanned = 0; + for await (const cell of this.client.findCellsByLock(lock, udtType, true)) { + scanned++; + if (scanned > maxCells) { + logger.warn(`UDT balance scan stopped after ${maxCells} cells; balances may be incomplete`); + break; + } + const cellBalance = readUdtBalance(cell.outputData); + if (cellBalance != null) { + balance += cellBalance; + } + } + return balance.toString(); + } + + async udtTransfer({ privateKey, toAddress, amount, udtType, kind }: UdtTransferOption): Promise { + const signer = this.buildSigner(privateKey); + const to = await ccc.Address.fromString(toAddress, this.client); + const amountBigInt = validateUdtAmount(amount); + + const outputsData = [ccc.hexFrom(ccc.numToBytes(amountBigInt, 16))]; + const tx = ccc.Transaction.from({ + outputs: [ + { + lock: to.script, + type: udtType, + capacity: ccc.fixedPointFrom(0), + }, + ], + outputsData, + }); + + const scriptInfo = await this.getUdtScriptInfo(kind); + tx.addCellDeps(scriptInfo.cellDeps.map((dep) => dep.cellDep)); + + await tx.completeInputsByUdt(signer, udtType); + + const inputsUdtBalance = await tx.getInputsUdtBalance(this.client, udtType); + const outputsUdtBalance = tx.getOutputsUdtBalance(udtType); + const changeAmount = inputsUdtBalance - outputsUdtBalance; + if (changeAmount > BigInt(0)) { + const from = await signer.getAddressObjSecp256k1(); + tx.outputs.push( + ccc.CellOutput.from( + { + lock: from.script, + type: udtType, + capacity: ccc.fixedPointFrom(0), + }, + ccc.hexFrom(ccc.numToBytes(changeAmount, 16)), + ), + ); + tx.outputsData.push(ccc.hexFrom(ccc.numToBytes(changeAmount, 16))); + } + + await tx.completeFeeBy(signer, this.feeRate); + const txHash = await signer.sendTransaction(tx); + return txHash; + } + + async udtIssue({ privateKey, kind, amount, typeArgs, toAddress }: UdtIssueOption): Promise { + const signer = this.buildSigner(privateKey); + const signerAddress = await signer.getAddressObjSecp256k1(); + const to = toAddress ? await ccc.Address.fromString(toAddress, this.client) : signerAddress; + const amountBigInt = validateUdtAmount(amount); + + let resolvedTypeArgs: HexString; + if (kind === 'sudt') { + if (typeArgs) { + logger.warn('SUDT type args are derived from the issuer lock hash; --type-args is ignored'); + } + const issuerLockHash = signerAddress.script.hash(); + resolvedTypeArgs = ('0x' + issuerLockHash.slice(2, 42)) as HexString; + } else { + if (typeArgs) { + resolvedTypeArgs = validateUdtTypeArgs(kind, typeArgs); + } else { + const issuerLockHash = signerAddress.script.hash(); + resolvedTypeArgs = issuerLockHash as HexString; + } + } + + const udtType = await this.buildUdtTypeScript(kind, resolvedTypeArgs); + + const outputsData = [ccc.hexFrom(ccc.numToBytes(amountBigInt, 16))]; + const tx = ccc.Transaction.from({ + outputs: [ + { + lock: to.script, + type: udtType, + capacity: ccc.fixedPointFrom(0), + }, + ], + outputsData, + }); + + const scriptInfo = await this.getUdtScriptInfo(kind); + tx.addCellDeps(scriptInfo.cellDeps.map((dep) => dep.cellDep)); + + await tx.completeInputsByCapacity(signer); + await tx.completeFeeBy(signer, this.feeRate); + const txHash = await signer.sendTransaction(tx); + return txHash; + } + + async udtDestroy( + { privateKey, kind, typeArgs, amount }: UdtDestroyOption, + { maxInputCells = DEFAULT_UDT_DESTROY_MAX_INPUT_CELLS }: { maxInputCells?: number } = {}, + ): Promise { + const signer = this.buildSigner(privateKey); + const from = await signer.getAddressObjSecp256k1(); + const validatedTypeArgs = validateUdtTypeArgs(kind, typeArgs); + const udtType = await this.buildUdtTypeScript(kind, validatedTypeArgs); + const destroyAmount = validateUdtAmount(amount); + + const cells: ccc.Cell[] = []; + let totalBalance = BigInt(0); + for await (const cell of this.client.findCellsByLock(from.script, udtType, true)) { + if (cells.length >= maxInputCells) { + throw new Error(`Too many UDT cells to destroy (limit: ${maxInputCells}); split into smaller operations`); + } + const cellBalance = readUdtBalance(cell.outputData); + if (cellBalance == null) { + continue; + } + cells.push(cell); + totalBalance += cellBalance; + } + + if (totalBalance < destroyAmount) { + throw new Error(`Insufficient UDT balance: ${totalBalance} < ${destroyAmount}`); + } + + if (destroyAmount === totalBalance) { + throw new Error( + 'Destroying the entire UDT balance may be rejected by the UDT script. Leave at least 1 token or use a smaller amount.', + ); + } + + const tx = ccc.Transaction.from({}); + for (const cell of cells) { + tx.addInput({ previousOutput: cell.outPoint }); + } + + const remaining = totalBalance - destroyAmount; + tx.addOutput( + ccc.CellOutput.from( + { + lock: from.script, + type: udtType, + capacity: ccc.fixedPointFrom(0), + }, + ccc.hexFrom(ccc.numToBytes(remaining, 16)), + ), + ccc.hexFrom(ccc.numToBytes(remaining, 16)), + ); + + const scriptInfo = await this.getUdtScriptInfo(kind); + tx.addCellDeps(scriptInfo.cellDeps.map((dep) => dep.cellDep)); + + await tx.completeInputsByCapacity(signer); + await tx.completeFeeBy(signer, this.feeRate); + const txHash = await signer.sendTransaction(tx); + return txHash; + } + async deployScript(scriptBinBytes: Uint8Array, privateKey: string): Promise { const signer = this.buildSigner(privateKey); const signerSecp256k1Address = await signer.getAddressObjSecp256k1(); diff --git a/src/tools/ckb-tui.ts b/src/tools/ckb-tui.ts new file mode 100644 index 00000000..8b3ba785 --- /dev/null +++ b/src/tools/ckb-tui.ts @@ -0,0 +1,302 @@ +import { spawnSync } from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as crypto from 'crypto'; +import AdmZip from 'adm-zip'; +import { readSettings, dataPath } from '../cfg/setting'; +import { logger } from '../util/logger'; +import { findFileInFolder } from '../util/fs'; + +const DOWNLOAD_TIMEOUT_MS = 120_000; +const EXTRACT_TIMEOUT_MS = 60_000; + +// Strict semver regex: v.. (no leading zeros on digits) +const STRICT_VERSION_REGEX = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/; + +export class CKBTui { + private static binaryPath: string | null = null; + + /** + * Pure lookup — returns the expected binary path without triggering any + * download or installation side effects. May return null if not yet computed. + */ + static getBinaryPath(): string | null { + if (!this.binaryPath) { + const settings = readSettings(); + const binDir = this.resolveAndValidateBinDir(settings.tools.rootFolder); + const binaryName = process.platform === 'win32' ? 'ckb-tui.exe' : 'ckb-tui'; + this.binaryPath = path.join(binDir, binaryName); + } + return this.binaryPath; + } + + /** + * Returns the binary path, downloading and installing if the binary + * does not already exist. + */ + static ensureInstalled(): string { + const binaryPath = this.getBinaryPath(); + if (binaryPath && fs.existsSync(binaryPath)) { + return binaryPath; + } + + // Reset and re-install + this.binaryPath = null; + this.installSync(); + return this.binaryPath!; + } + + static isInstalled(): boolean { + try { + const binPath = this.getBinaryPath(); + return binPath !== null && fs.existsSync(binPath); + } catch { + return false; + } + } + + static run(args: string[] = []) { + const binaryPath = this.ensureInstalled(); + return spawnSync(binaryPath, args, { stdio: 'inherit' }); + } + + // --- private helpers --- + + /** + * Resolve and validate that the configured rootFolder is under the + * OffCKB data directory. Rejects paths that resolve outside. + */ + private static resolveAndValidateBinDir(configuredRoot: string): string { + const resolved = path.resolve(configuredRoot); + const resolvedData = path.resolve(dataPath); + + // Require the resolved path to be within the resolved data directory + const relative = path.relative(resolvedData, resolved); + if (relative.startsWith('..') || path.isAbsolute(relative)) { + throw new Error( + `tools.rootFolder ("${configuredRoot}") resolves outside the OffCKB data directory ` + + `("${resolvedData}"). For security, tool binaries must be stored under the data path.`, + ); + } + + return resolved; + } + + private static validateVersion(version: string): void { + if (!STRICT_VERSION_REGEX.test(version)) { + throw new Error(`Invalid version format: "${version}". Expected format: vX.Y.Z (e.g., v0.1.3)`); + } + } + + private static getAssetName(): string { + const platform = process.platform; + const arch = process.arch; + + if (platform === 'darwin') { + if (arch !== 'arm64') { + throw new Error(`Unsupported architecture for macOS: ${arch}. Only Apple Silicon (arm64) is supported.`); + } + return 'ckb-tui-with-node-macos-aarch64.tar.gz'; + } else if (platform === 'linux') { + if (arch !== 'x64') { + throw new Error(`Unsupported architecture for Linux: ${arch}. Only x86_64 is supported.`); + } + return 'ckb-tui-with-node-linux-amd64.tar.gz'; + } else if (platform === 'win32') { + if (arch !== 'x64') { + throw new Error(`Unsupported architecture for Windows: ${arch}. Only x86_64 is supported.`); + } + return 'ckb-tui-with-node-windows-amd64.zip'; + } + + throw new Error(`Unsupported platform: ${platform}`); + } + + /** + * Synchronously download and install the ckb-tui binary. + * Uses spawnSync with array arguments (no shell interpolation) for security. + */ + private static installSync(): void { + const settings = readSettings(); + const version = settings.tools.ckbTui.version; + + this.validateVersion(version); + + const assetName = this.getAssetName(); + const binDir = this.resolveAndValidateBinDir(settings.tools.rootFolder); + const binaryName = process.platform === 'win32' ? 'ckb-tui.exe' : 'ckb-tui'; + this.binaryPath = path.join(binDir, binaryName); + + const downloadUrl = `https://github.com/Officeyutong/ckb-tui/releases/download/${version}/${assetName}`; + + // Ensure the target directory exists + fs.mkdirSync(binDir, { recursive: true }); + + // Use a temp directory for atomic download & extraction + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'offckb-ckb-tui-')); + const archivePath = path.join(tempDir, assetName); + + try { + // 1. Download + logger.info(`Downloading ckb-tui from ${downloadUrl}...`); + const curlResult = spawnSync('curl', ['-fsSL', '--max-time', '300', '-o', archivePath, downloadUrl], { + stdio: 'inherit', + timeout: DOWNLOAD_TIMEOUT_MS, + }); + + if (curlResult.error) { + throw new Error(`Failed to download ckb-tui: ${curlResult.error.message}`); + } + if (curlResult.status !== 0) { + throw new Error(`curl exited with code ${curlResult.status}`); + } + + // 2. Verify checksum (best-effort: warns if checksum file is unavailable) + this.verifyChecksum(version, assetName, archivePath); + + // 3. Extract to temp directory + logger.info('Extracting...'); + const extractDir = path.join(tempDir, 'extracted'); + fs.mkdirSync(extractDir, { recursive: true }); + + this.extractArchive(archivePath, extractDir); + + // 4. Locate the extracted binary + const extractedBinary = findFileInFolder(extractDir, binaryName); + if (!extractedBinary) { + throw new Error(`ckb-tui binary ("${binaryName}") was not found after extraction.`); + } + + // 5. Atomically move to the final location + fs.renameSync(extractedBinary, this.binaryPath); + + // 6. Make executable on Unix + if (process.platform !== 'win32') { + fs.chmodSync(this.binaryPath, 0o755); + } + + logger.info('ckb-tui installed successfully.'); + } catch (error) { + // Reset cached path on failure so a subsequent call retries + this.binaryPath = null; + + const message = error instanceof Error ? error.message : String(error); + logger.error( + 'Failed to download/install ckb-tui:', + message, + '\nPlease check your network connectivity, verify that the specified version exists in the releases, ' + + 'and ensure you have sufficient file system permissions.', + ); + throw error; + } finally { + // Clean up the temp directory + try { + fs.rmSync(tempDir, { recursive: true, force: true }); + } catch { + // Best-effort cleanup — temp dir will be cleaned by the OS eventually + } + } + } + + /** + * Best-effort SHA-256 checksum verification. + * Downloads the checksum file published alongside the release asset and + * verifies the downloaded archive. Logs a warning (but does not fail) if + * the checksum file is unavailable — this maintains compatibility while + * the upstream project adopts checksum publishing. + */ + private static verifyChecksum(version: string, assetName: string, archivePath: string): void { + const checksumUrl = `https://github.com/Officeyutong/ckb-tui/releases/download/${version}/checksums-sha256.txt`; + + const checksumPath = path.join(path.dirname(archivePath), 'checksums-sha256.txt'); + + const fetchResult = spawnSync('curl', ['-fsSL', '--max-time', '30', '-o', checksumPath, checksumUrl], { + stdio: 'pipe', + timeout: 30_000, + }); + + if (fetchResult.status !== 0) { + logger.warn( + `SHA-256 checksum file not available for version ${version}. ` + + 'Skipping integrity verification. Consider asking the upstream maintainer to publish checksum files.', + ); + return; + } + + try { + const checksumContent = fs.readFileSync(checksumPath, 'utf8'); + const expectedHash = this.parseChecksumFile(checksumContent, assetName); + + if (!expectedHash) { + logger.warn(`Checksum entry for "${assetName}" not found in checksums file. Skipping verification.`); + return; + } + + const actualHash = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex'); + + if (actualHash !== expectedHash) { + throw new Error( + `SHA-256 checksum mismatch for ${assetName}.\n` + + `Expected: ${expectedHash}\nActual: ${actualHash}\n` + + 'The downloaded file may be corrupted or tampered with.', + ); + } + + logger.info('SHA-256 checksum verified successfully.'); + } finally { + try { + fs.unlinkSync(checksumPath); + } catch { + // Best effort + } + } + } + + /** + * Parse a standard SHA-256 checksum file (format: " " per line) + * and return the hex hash for the given asset name, or null if not found. + */ + private static parseChecksumFile(content: string, assetName: string): string | null { + for (const line of content.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + + const match = trimmed.match(/^([0-9a-fA-F]{64})\s+[*]?(.+)$/); + if (match && match[2] === assetName) { + return match[1].toLowerCase(); + } + } + return null; + } + + /** + * Extract a downloaded archive to the given directory. + * Uses AdmZip for .zip files (Node-native, no shell) and spawnSync with array + * arguments for .tar.gz (no shell interpolation). + */ + private static extractArchive(archivePath: string, extractDir: string): void { + if (archivePath.endsWith('.tar.gz')) { + const result = spawnSync('tar', ['-xzf', archivePath, '-C', extractDir], { + stdio: 'inherit', + timeout: EXTRACT_TIMEOUT_MS, + }); + if (result.error) { + throw new Error(`tar extraction failed: ${result.error.message}`); + } + if (result.status !== 0) { + throw new Error(`tar exited with code ${result.status}`); + } + } else if (archivePath.endsWith('.zip')) { + try { + const zip = new AdmZip(archivePath); + zip.extractAllTo(extractDir, true); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`ZIP extraction failed: ${message}`); + } + } else { + throw new Error(`Unsupported archive format: ${path.extname(archivePath)}`); + } + } +} diff --git a/src/type/base.ts b/src/type/base.ts index e3fd0aca..2eb7d40c 100644 --- a/src/type/base.ts +++ b/src/type/base.ts @@ -13,3 +13,5 @@ export type H256 = string; export type HexString = string; export type HexNumber = string; + +export type UdtKind = 'sudt' | 'xudt'; diff --git a/src/util/link.ts b/src/util/link.ts index 5de75d44..5f21ca6c 100644 --- a/src/util/link.ts +++ b/src/util/link.ts @@ -1,3 +1,14 @@ +import { logger } from './logger'; +import { Network } from '../type/base'; + export function buildTestnetTxLink(txHash: string) { return `https://pudge.explorer.nervos.org/transaction/${txHash}`; } + +export function logTxSuccess(network: Network, txHash: string, action: string) { + if (network === 'testnet') { + logger.info(`Successfully ${action}, check ${buildTestnetTxLink(txHash)} for details.`); + } else { + logger.info(`Successfully ${action}, txHash:`, txHash); + } +} diff --git a/src/util/logger.ts b/src/util/logger.ts index 1358486d..05ec4b36 100644 --- a/src/util/logger.ts +++ b/src/util/logger.ts @@ -17,16 +17,20 @@ interface LoggerOptions { level?: LogLevel; enableColors?: boolean; showLevel?: boolean; + jsonMode?: boolean; + transports?: winston.transport[]; } class UnifiedLogger { private logger: winston.Logger; private enableColors: boolean; private showLevel: boolean; + private jsonMode: boolean; constructor(options: LoggerOptions = {}) { this.enableColors = options.enableColors !== false; this.showLevel = options.showLevel !== false; + this.jsonMode = options.jsonMode === true; // Create Winston logger with custom format and levels this.logger = winston.createLogger({ @@ -45,7 +49,7 @@ class UnifiedLogger { return this.formatMessage(level as LogLevel, message as string, timestamp as string); }), ), - transports: [ + transports: options.transports || [ new winston.transports.Console({ stderrLevels: ['error', 'warn'], }), @@ -53,10 +57,28 @@ class UnifiedLogger { }); } + /** + * Toggle JSON output mode. When enabled, every log line is emitted as a + * structured JSON object, which is easier for agents and scripts to parse. + */ + setJsonMode(enabled: boolean) { + this.jsonMode = enabled; + } + /** * Format the message with appropriate colors and structure */ - private formatMessage(level: LogLevel, message: string, _timestamp?: string): string { + private formatMessage(level: LogLevel, message: string, timestamp?: string): string { + // Agent-friendly JSON output: one JSON object per log line + if (this.jsonMode) { + const normalizedMessage = Array.isArray(message) ? message.join('\n') : String(message); + return JSON.stringify({ + level, + message: normalizedMessage, + timestamp, + }); + } + // If showLevel is false, return just the message if (!this.showLevel) { if (Array.isArray(message)) { @@ -148,6 +170,13 @@ class UnifiedLogger { * Log a message with the specified level */ private log(level: LogLevel, message: string | string[]) { + // In JSON mode, emit multi-line messages as a single structured log entry + // so agents can parse one complete JSON object per line. + if (this.jsonMode && Array.isArray(message)) { + this.logger.log(level, message.join('\n')); + return; + } + if (Array.isArray(message)) { message.forEach((line) => this.logger.log(level, line)); } else { diff --git a/src/util/validator.ts b/src/util/validator.ts index 53e9fe71..fa0021e8 100644 --- a/src/util/validator.ts +++ b/src/util/validator.ts @@ -1,6 +1,6 @@ import path from 'path'; import fs from 'fs'; -import { Network } from '../type/base'; +import { Network, HexString, UdtKind } from '../type/base'; import { logger } from './logger'; export function validateTypescriptWorkspace() { @@ -113,3 +113,50 @@ export function normalizePrivKey(privKey: string): string { // Return the formally strictly padded ckb format `0x` string return '0x' + key; } + +export function isValidUdtKind(kind: string): kind is UdtKind { + return kind === 'sudt' || kind === 'xudt'; +} + +export function validateUdtKind(kind?: string): asserts kind is UdtKind { + if (!kind) { + throw new Error('UDT kind is required'); + } + if (!isValidUdtKind(kind)) { + throw new Error(`invalid UDT kind "${kind}", must be "sudt" or "xudt"`); + } +} + +const U128_MAX = (BigInt(1) << BigInt(128)) - BigInt(1); + +export function validateUdtAmount(amount: string): bigint { + if (!/^\d+$/.test(amount)) { + throw new Error(`invalid UDT amount "${amount}", must be a non-negative decimal integer`); + } + const value = BigInt(amount); + if (value > U128_MAX) { + throw new Error(`UDT amount exceeds 128-bit max: ${amount}`); + } + return value; +} + +const HEX_REGEX = /^0x[0-9a-fA-F]*$/; + +export function validateHexString(value: string, name: string): HexString { + if (!value || !HEX_REGEX.test(value)) { + throw new Error(`invalid ${name} "${value}", must be a hex string starting with 0x`); + } + return value as HexString; +} + +export function validateUdtTypeArgs(kind: UdtKind, typeArgs: string): HexString { + const hex = validateHexString(typeArgs, 'type args'); + const byteLength = (hex.length - 2) / 2; + if (kind === 'sudt' && byteLength !== 20) { + throw new Error(`invalid SUDT type args length: expected 20 bytes, got ${byteLength}`); + } + if (kind === 'xudt' && byteLength !== 32) { + throw new Error(`invalid xUDT type args length: expected 32 bytes, got ${byteLength}`); + } + return hex; +} diff --git a/tests/logger.test.ts b/tests/logger.test.ts new file mode 100644 index 00000000..a8e55280 --- /dev/null +++ b/tests/logger.test.ts @@ -0,0 +1,56 @@ +import winston from 'winston'; +import { UnifiedLogger } from '../src/util/logger'; + +interface WinstonInfo { + level: string; + message: unknown; + [Symbol.for('message')]?: string; +} + +class CapturingTransport extends winston.transports.Console { + logs: string[] = []; + + log(info: WinstonInfo, next: () => void) { + this.logs.push(info[Symbol.for('message')] ?? String(info.message)); + next(); + } +} + +function createCapturingTransport(): { transport: CapturingTransport; logs: string[] } { + const transport = new CapturingTransport(); + return { transport, logs: transport.logs }; +} + +describe('UnifiedLogger JSON mode', () => { + it('outputs plain text by default', () => { + const { transport, logs } = createCapturingTransport(); + const log = UnifiedLogger.create({ transports: [transport], showLevel: false }); + log.info('hello world'); + + expect(logs).toHaveLength(1); + expect(logs[0]).toBe('hello world'); + }); + + it('outputs structured JSON when jsonMode is enabled', () => { + const { transport, logs } = createCapturingTransport(); + const log = UnifiedLogger.create({ transports: [transport] }); + log.setJsonMode(true); + log.info('agent friendly'); + + expect(logs).toHaveLength(1); + const parsed = JSON.parse(logs[0]); + expect(parsed.level).toBe('info'); + expect(parsed.message).toBe('agent friendly'); + expect(parsed.timestamp).toBeDefined(); + }); + + it('joins array messages into a single string in JSON mode', () => { + const { transport, logs } = createCapturingTransport(); + const log = UnifiedLogger.create({ transports: [transport] }); + log.setJsonMode(true); + log.info(['line one', 'line two']); + + const parsed = JSON.parse(logs[0]); + expect(parsed.message).toBe('line one\nline two'); + }); +}); diff --git a/tests/node-command.test.ts b/tests/node-command.test.ts new file mode 100644 index 00000000..b5c99aad --- /dev/null +++ b/tests/node-command.test.ts @@ -0,0 +1,375 @@ +import { startNode, stopNode } from '../src/cmd/node'; +import { Network } from '../src/type/base'; +import * as path from 'path'; + +const mockSpawn = jest.fn(); +const mockExec = jest.fn(); +const mockOpenSync = jest.fn(); +const mockWriteFileSync = jest.fn(); +const mockMkdirSync = jest.fn(); +const mockReadFileSync = jest.fn(); +const mockExistsSync = jest.fn(); +const mockUnlinkSync = jest.fn(); +const mockStatSync = jest.fn(); +const mockCloseSync = jest.fn(); + +jest.mock('child_process', () => ({ + ...jest.requireActual('child_process'), + spawn: (...args: unknown[]) => mockSpawn(...args), + exec: (...args: unknown[]) => mockExec(...args), +})); + +jest.mock('fs', () => ({ + ...jest.requireActual('fs'), + openSync: (...args: unknown[]) => mockOpenSync(...args), + writeFileSync: (...args: unknown[]) => mockWriteFileSync(...args), + mkdirSync: (...args: unknown[]) => mockMkdirSync(...args), + readFileSync: (...args: unknown[]) => mockReadFileSync(...args), + existsSync: (...args: unknown[]) => mockExistsSync(...args), + unlinkSync: (...args: unknown[]) => mockUnlinkSync(...args), + statSync: (...args: unknown[]) => mockStatSync(...args), + closeSync: (...args: unknown[]) => mockCloseSync(...args), +})); + +jest.mock('../src/tools/rpc-proxy', () => ({ + createRPCProxy: jest.fn(() => ({ + start: jest.fn(), + stop: jest.fn(), + })), +})); + +jest.mock('../src/cfg/setting', () => ({ + readSettings: () => ({ + devnet: { + configPath: '/tmp/offckb-devnet-config', + dataPath: '/tmp/offckb-devnet-data', + rpcUrl: 'http://127.0.0.1:8114', + rpcProxyPort: 28114, + }, + testnet: { + rpcUrl: 'https://testnet.ckb.dev', + rpcProxyPort: 38114, + }, + mainnet: { + rpcUrl: 'https://mainnet.ckb.dev', + rpcProxyPort: 48114, + }, + }), +})); + +jest.mock('../src/util/logger', () => ({ + logger: { + success: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + setJsonMode: jest.fn(), + }, +})); + +import { logger } from '../src/util/logger'; + +const dataPath = '/tmp/offckb-devnet-data'; +const logDir = path.join(dataPath, 'logs'); +const pidFile = path.join(logDir, 'daemon.pid'); +const logFile = path.join(logDir, 'daemon.log'); + +function mockDaemonCommandLine(scriptPath: string) { + mockExec.mockImplementation((cmd: string, callback: (err: Error | null, stdout?: string) => void) => { + if (cmd.startsWith('ps ')) { + callback(null, `/usr/bin/node ${scriptPath} node`); + return undefined as unknown as ReturnType; + } + if (cmd.startsWith('wmic ')) { + // WMIC returns key/value pairs, e.g. "CommandLine=..." + callback(null, `CommandLine=/usr/bin/node ${scriptPath} node`); + return undefined as unknown as ReturnType; + } + callback(null, ''); + return undefined as unknown as ReturnType; + }); +} + +describe('node command daemon mode', () => { + const originalArgv = process.argv; + let killSpy: jest.SpyInstance; + + beforeEach(() => { + jest.clearAllMocks(); + mockReadFileSync.mockReset(); + process.argv = ['node', '/path/to/offckb', 'node', '--daemon']; + mockOpenSync.mockReturnValue(3); + mockStatSync.mockReturnValue({ isFile: () => true }); + mockSpawn.mockReturnValue({ + pid: 12345, + unref: jest.fn(), + on: jest.fn(), + }); + killSpy = jest.spyOn(process, 'kill').mockImplementation((pid: number, signal?: NodeJS.Signals | number) => { + if (signal === 0) { + // By default pretend the PID from a PID file is alive; individual tests override this. + return true; + } + return true; + }); + }); + + afterEach(() => { + process.argv = originalArgv; + killSpy.mockRestore(); + }); + + it('spawns a detached child process without the --daemon flag', () => { + startNode({ network: Network.devnet, daemon: true }); + + expect(mockMkdirSync).toHaveBeenCalledWith(logDir, { recursive: true }); + const resolvedScriptPath = path.resolve('/path/to/offckb'); + expect(mockSpawn).toHaveBeenCalledWith( + process.execPath, + [resolvedScriptPath, 'node'], + expect.objectContaining({ + detached: true, + stdio: ['ignore', 3, 3], + env: expect.objectContaining({ OFFCKB_DAEMON_CHILD: '1' }), + }), + ); + + const writtenMetadata = JSON.parse(mockWriteFileSync.mock.calls[0][1]); + expect(writtenMetadata.pid).toBe(12345); + expect(writtenMetadata.scriptPath).toBe(resolvedScriptPath); + expect(writtenMetadata.startedAt).toBeDefined(); + + expect(logger.success).toHaveBeenCalledWith('CKB devnet daemon started with PID 12345.'); + }); + + it('warns and ignores daemon flag for non-devnet networks', () => { + startNode({ network: Network.testnet, daemon: true }); + + expect(logger.warn).toHaveBeenCalledWith( + 'Daemon mode is only supported for devnet. The daemon flag will be ignored.', + ); + expect(mockSpawn).not.toHaveBeenCalled(); + }); + + it('refuses to start when a daemon is already running', () => { + mockReadFileSync.mockReturnValue( + JSON.stringify({ pid: 9999, scriptPath: '/path/to/offckb', startedAt: new Date().toISOString() }), + ); + + startNode({ network: Network.devnet, daemon: true }); + + expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('already running')); + expect(mockSpawn).not.toHaveBeenCalled(); + }); + + it('cleans up a stale PID file and starts a new daemon', () => { + mockReadFileSync.mockReturnValue( + JSON.stringify({ pid: 9999, scriptPath: '/path/to/offckb', startedAt: new Date().toISOString() }), + ); + killSpy.mockImplementation((pid: number, signal?: NodeJS.Signals | number) => { + if (signal === 0) { + const err = new Error('ESRCH') as NodeJS.ErrnoException; + err.code = 'ESRCH'; + throw err; + } + return true; + }); + + startNode({ network: Network.devnet, daemon: true }); + + expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + expect(mockSpawn).toHaveBeenCalled(); + }); + + it('errors and cleans up when spawn fails synchronously', () => { + mockSpawn.mockImplementation(() => { + throw new Error('spawn error'); + }); + + startNode({ network: Network.devnet, daemon: true }); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('Failed to spawn daemon process'), + expect.any(Error), + ); + expect(mockCloseSync).toHaveBeenCalledWith(3); + expect(mockWriteFileSync).not.toHaveBeenCalled(); + }); + + it('errors when the spawned child has no PID', () => { + mockSpawn.mockReturnValue({ + pid: undefined, + unref: jest.fn(), + on: jest.fn(), + }); + + startNode({ network: Network.devnet, daemon: true }); + + expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('no PID returned')); + expect(mockWriteFileSync).not.toHaveBeenCalled(); + }); + + it('handles backward-compatible plain PID files', () => { + mockReadFileSync.mockReturnValue('9999'); + killSpy.mockImplementation((pid: number, signal?: NodeJS.Signals | number) => { + if (signal === 0) { + const err = new Error('ESRCH') as NodeJS.ErrnoException; + err.code = 'ESRCH'; + throw err; + } + return true; + }); + + startNode({ network: Network.devnet, daemon: true }); + + expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + expect(mockSpawn).toHaveBeenCalled(); + }); +}); + +describe('node command stop', () => { + let killSpy: jest.SpyInstance; + let processAlive = true; + const scriptPath = '/path/to/offckb'; + const originalPlatform = process.platform; + + function setPlatform(value: string) { + Object.defineProperty(process, 'platform', { value }); + } + + beforeEach(() => { + jest.useFakeTimers(); + jest.clearAllMocks(); + processAlive = true; + mockExec.mockReset(); + mockStatSync.mockReturnValue({ isFile: () => true }); + mockReadFileSync.mockReturnValue(JSON.stringify({ pid: 12345, scriptPath, startedAt: new Date().toISOString() })); + mockDaemonCommandLine(scriptPath); + + // Normalize to POSIX for deterministic signal-based assertions. The + // implementation has a separate Windows path (taskkill) that is exercised + // by the daemon-mode spawn tests above and by integration tests. + setPlatform('linux'); + + killSpy = jest.spyOn(process, 'kill').mockImplementation((pid: number, signal?: NodeJS.Signals | number) => { + if (signal === 0) { + if (!processAlive) { + const err = new Error('ESRCH') as NodeJS.ErrnoException; + err.code = 'ESRCH'; + throw err; + } + return true; + } + if (signal === 'SIGTERM' || signal === 'SIGKILL') { + processAlive = false; + return true; + } + return true; + }); + }); + + afterEach(() => { + killSpy.mockRestore(); + setPlatform(originalPlatform); + jest.useRealTimers(); + }); + + it('warns when no PID file exists', async () => { + mockReadFileSync.mockImplementation(() => { + const err = new Error('ENOENT') as NodeJS.ErrnoException; + err.code = 'ENOENT'; + throw err; + }); + + await stopNode(); + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('No daemon PID file found')); + expect(killSpy).not.toHaveBeenCalled(); + }); + + it('errors when the PID file contains an invalid PID', async () => { + mockReadFileSync.mockReturnValue('not-a-number'); + await stopNode(); + expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Invalid PID')); + expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + }); + + it('removes the PID file when the daemon is not running', async () => { + processAlive = false; + await stopNode(); + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('is not running')); + expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + }); + + it('stops the daemon gracefully with SIGTERM', async () => { + await stopNode(); + expect(killSpy).toHaveBeenCalledWith(-12345, 'SIGTERM'); + expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + expect(logger.success).toHaveBeenCalledWith('CKB devnet daemon stopped.'); + }); + + it('falls back to SIGKILL when the daemon does not exit gracefully', async () => { + // Simulate a process that ignores SIGTERM but dies on SIGKILL. + killSpy.mockImplementation((pid: number, signal?: NodeJS.Signals | number) => { + if (signal === 0) { + return true; + } + if (signal === 'SIGKILL') { + processAlive = false; + } + return true; + }); + + const stopPromise = stopNode(); + await jest.advanceTimersByTimeAsync(5000); + await stopPromise; + + expect(killSpy).toHaveBeenCalledWith(-12345, 'SIGKILL'); + expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + }); + + it('refuses to kill a process that does not look like the daemon', async () => { + mockExec.mockImplementation((cmd: string, callback: (err: Error | null, stdout?: string) => void) => { + callback(null, '/usr/bin/some-other-process'); + return undefined as unknown as ReturnType; + }); + + await stopNode(); + + expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('does not appear to be the offckb daemon')); + expect(killSpy).not.toHaveBeenCalledWith(expect.any(Number), 'SIGTERM'); + expect(mockUnlinkSync).not.toHaveBeenCalled(); + }); + + it('cleans up the PID file when SIGTERM fails with an unknown error', async () => { + killSpy.mockImplementation((pid: number, signal?: NodeJS.Signals | number) => { + if (signal === 0) { + return true; + } + const err = new Error('EPERM') as NodeJS.ErrnoException; + err.code = 'EPERM'; + throw err; + }); + + await stopNode(); + + expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Permission denied')); + expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + }); + + it('cleans up the PID file when the process disappears between alive-check and SIGTERM', async () => { + killSpy.mockImplementation((pid: number, signal?: NodeJS.Signals | number) => { + if (signal === 0) { + return true; + } + const err = new Error('ESRCH') as NodeJS.ErrnoException; + err.code = 'ESRCH'; + throw err; + }); + + await stopNode(); + + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('is not running')); + expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + }); +}); diff --git a/tests/sdk/ckb.udt.test.ts b/tests/sdk/ckb.udt.test.ts new file mode 100644 index 00000000..f36f24c1 --- /dev/null +++ b/tests/sdk/ckb.udt.test.ts @@ -0,0 +1,194 @@ +import { CKB, UdtKind } from '../../src/sdk/ckb'; +import { Network } from '../../src/type/base'; +import { logger } from '../../src/util/logger'; + +jest.mock('../../src/sdk/network', () => ({ + networks: { + devnet: { rpc_url: 'http://localhost:8114', proxy_rpc_url: 'http://localhost:8114' }, + testnet: { rpc_url: 'http://testnet', proxy_rpc_url: 'http://testnet' }, + mainnet: { rpc_url: 'http://mainnet', proxy_rpc_url: 'http://mainnet' }, + }, +})); + +jest.mock('../../src/scripts/private', () => ({ + buildCCCDevnetKnownScripts: jest.fn(() => ({})), + getDevnetSystemScriptsFromListHashes: jest.fn(() => ({ + sudt: { + script: { + codeHash: '0x' + 'c3'.repeat(32), + hashType: 'type', + cellDeps: [{ cellDep: { outPoint: { txHash: '0x' + 'aa'.repeat(32), index: 0 }, depType: 'depGroup' } }], + }, + }, + })), +})); + +const mockKnownScript = jest.fn(); +const mockFindCellsByLock = jest.fn(); +const mockFindCells = jest.fn(); +const mockClient = { + getKnownScript: mockKnownScript, + findCellsByLock: mockFindCellsByLock, + findCells: mockFindCells, +}; + +jest.mock('@ckb-ccc/core', () => { + return { + ccc: { + ClientPublicTestnet: jest.fn(() => mockClient), + ClientPublicMainnet: jest.fn(() => mockClient), + Address: { + fromString: jest.fn(async (address: string) => ({ + script: { + codeHash: '0x' + '00'.repeat(32), + hashType: 'type', + args: address.startsWith('ckt1') ? '0x' + '11'.repeat(20) : '0x' + '22'.repeat(20), + }, + })), + }, + Script: { + fromKnownScript: jest.fn(async (_client: unknown, script: unknown, args: string) => ({ + codeHash: script === 'XUdt' ? '0x' + 'dd'.repeat(32) : '0x' + 'cc'.repeat(32), + hashType: 'type', + args, + })), + from: jest.fn((script: unknown) => script), + }, + KnownScript: { XUdt: 'XUdt', TypeId: 'TypeId' }, + udtBalanceFrom: jest.fn((data: string) => { + if (!data || data === '0x') return 0n; + if (data === '0xbad') throw new Error('corrupted'); + return BigInt(data); + }), + fixedPointFrom: jest.fn((value: string | number) => BigInt(value) * BigInt(10 ** 8)), + numToBytes: jest.fn((value: bigint, bytes: number) => value.toString(16).padStart(bytes * 2, '0')), + hexFrom: jest.fn((value: string) => '0x' + value), + Transaction: { + from: jest.fn(() => ({ + outputs: [], + outputsData: [], + inputs: [], + cellDeps: [], + addCellDeps: jest.fn(), + addInput: jest.fn(), + addOutput: jest.fn(), + completeInputsByUdt: jest.fn(), + completeInputsByCapacity: jest.fn(), + completeFeeBy: jest.fn(), + getInputsUdtBalance: jest.fn().mockResolvedValue(0n), + getOutputsUdtBalance: jest.fn().mockReturnValue(0n), + })), + }, + CellOutput: { + from: jest.fn((output: unknown) => output), + }, + SignerCkbPrivateKey: jest.fn(() => ({ + getAddressObjSecp256k1: jest.fn().mockResolvedValue({ + script: { hash: () => '0x' + '00'.repeat(32) }, + }), + sendTransaction: jest.fn().mockResolvedValue('0xtxhash'), + })), + }, + }; +}); + +function createCKB(network: Network = Network.devnet) { + return new CKB({ network, isEnableProxyRpc: false }); +} + +describe('CKB SDK UDT helpers', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockKnownScript.mockResolvedValue({ + codeHash: '0x' + 'dd'.repeat(32), + hashType: 'type', + cellDeps: [{ cellDep: { outPoint: { txHash: '0x' + 'aa'.repeat(32), index: 0 }, depType: 'depGroup' } }], + }); + }); + + describe('buildUdtTypeScript', () => { + it('should build xUDT type script from known script', async () => { + const ckb = createCKB(); + const type = await ckb.buildUdtTypeScript('xudt', '0x' + '12'.repeat(32)); + expect(type.codeHash).toBe('0x' + 'dd'.repeat(32)); + expect(type.args).toBe('0x' + '12'.repeat(32)); + }); + + it('should build SUDT type script from system scripts', async () => { + const ckb = createCKB(); + const type = await ckb.buildUdtTypeScript('sudt', '0x' + '12'.repeat(20)); + expect(type.codeHash).toBe('0x' + 'c3'.repeat(32)); + expect(type.args).toBe('0x' + '12'.repeat(20)); + }); + }); + + describe('detectUdtBalances', () => { + beforeEach(() => { + mockFindCells.mockImplementation(async function* (searchKey: { script: { codeHash: string } }) { + const isSudt = searchKey.script.codeHash === '0x' + 'c3'.repeat(32); + if (isSudt) { + yield makeCell('sudt', '0xabcd', '100'); + yield makeCell('sudt', '0xabcd', '200'); + } else { + yield makeCell('xudt', '0xbeef', '50'); + } + }); + }); + + it('should aggregate UDT balances by kind and args', async () => { + const ckb = createCKB(); + + const balances = await ckb.detectUdtBalances('ckt1q9gry5zgmceslalm9x6s5xgnqe9cjn6y0q3c9'); + + expect(balances).toHaveLength(2); + expect(balances.find((b) => b.kind === 'sudt' && b.args === '0xabcd')?.balance).toBe('300'); + expect(balances.find((b) => b.kind === 'xudt' && b.args === '0xbeef')?.balance).toBe('50'); + }); + + it('should skip corrupted UDT cells instead of failing', async () => { + mockFindCells.mockImplementation(async function* (searchKey: { script: { codeHash: string } }) { + const isSudt = searchKey.script.codeHash === '0x' + 'c3'.repeat(32); + if (!isSudt) { + return; + } + yield makeCell('sudt', '0xabcd', '100'); + yield makeCell('sudt', '0xabcd', '0xbad'); + yield makeCell('sudt', '0xabcd', '200'); + }); + + const ckb = createCKB(); + const balances = await ckb.detectUdtBalances('ckt1q9gry5zgmceslalm9x6s5xgnqe9cjn6y0q3c9'); + + expect(balances).toHaveLength(1); + expect(balances[0].balance).toBe('300'); + }); + }); + + describe('udtIssue type args handling', () => { + it('should warn when SUDT issue receives a user type args', async () => { + const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {}); + const ckb = createCKB(); + + await ckb.udtIssue({ + privateKey: '0x' + '11'.repeat(32), + kind: 'sudt', + amount: '100', + typeArgs: '0x' + '12'.repeat(20), + }); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('--type-args is ignored')); + warnSpy.mockRestore(); + }); + }); +}); + +function makeCell(kind: UdtKind, args: string, balance: string) { + const codeHash = kind === 'sudt' ? '0x' + 'c3'.repeat(32) : '0x' + 'dd'.repeat(32); + return { + outPoint: { txHash: '0x' + '00'.repeat(32), index: 0 }, + cellOutput: { + type: { codeHash, hashType: 'type', args }, + }, + outputData: balance, + }; +} diff --git a/tests/udt.test.ts b/tests/udt.test.ts new file mode 100644 index 00000000..00050122 --- /dev/null +++ b/tests/udt.test.ts @@ -0,0 +1,193 @@ +import { Network } from '../src/type/base'; +import { balanceOf } from '../src/cmd/balance'; +import { transfer } from '../src/cmd/transfer'; +import { udtIssue, udtDestroy } from '../src/cmd/udt'; +import { CKB } from '../src/sdk/ckb'; +import { logger } from '../src/util/logger'; + +const mockTypeArgs = '0x' + 'ab'.repeat(20); +const mockUdtType = { codeHash: '0x1234', hashType: 'type', args: mockTypeArgs }; + +jest.mock('../src/sdk/ckb', () => { + return { + CKB: jest.fn().mockImplementation(() => ({ + balance: jest.fn().mockResolvedValue('1234.5678'), + transfer: jest.fn().mockResolvedValue('0xtxhash'), + buildUdtTypeScript: jest.fn().mockResolvedValue(mockUdtType), + detectUdtBalances: jest.fn().mockResolvedValue([ + { + kind: 'sudt', + codeHash: '0x1234', + hashType: 'type', + args: mockTypeArgs, + balance: '1000', + }, + ]), + udtTransfer: jest.fn().mockResolvedValue('0xtxhash'), + udtIssue: jest.fn().mockResolvedValue('0xissuehash'), + udtDestroy: jest.fn().mockResolvedValue('0xdestroyhash'), + })), + }; +}); + +jest.mock('../src/util/logger', () => ({ + logger: { + info: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + success: jest.fn(), + debug: jest.fn(), + }, +})); + +function mockProcessExit() { + return jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); +} + +describe('balance command', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should print CKB and detected UDT balances by default', async () => { + const exitSpy = mockProcessExit(); + await balanceOf('ckt1q9gry5zgmceslalm9x6s5xgnqe9cjn6y0q3c9', { + network: Network.devnet, + }); + + const ckbInstance = (CKB as jest.Mock).mock.results[0].value; + expect(ckbInstance.balance).toHaveBeenCalled(); + expect(ckbInstance.detectUdtBalances).toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith('CKB: 1234.5678'); + expect(logger.info).toHaveBeenCalledWith('UDT:'); + expect(logger.info).toHaveBeenCalledWith(` sudt (args=${mockTypeArgs}): 1000`); + expect(exitSpy).toHaveBeenCalledWith(0); + exitSpy.mockRestore(); + }); + + it('should filter UDT balances by kind and type args', async () => { + const exitSpy = mockProcessExit(); + await balanceOf('ckt1q9gry5zgmceslalm9x6s5xgnqe9cjn6y0q3c9', { + network: Network.devnet, + udtKind: 'sudt', + udtTypeArgs: mockTypeArgs, + }); + + const ckbInstance = (CKB as jest.Mock).mock.results[0].value; + expect(ckbInstance.detectUdtBalances).toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith(` sudt (args=${mockTypeArgs}): 1000`); + expect(exitSpy).toHaveBeenCalledWith(0); + exitSpy.mockRestore(); + }); + + it('should skip UDT scan with --no-udt', async () => { + const exitSpy = mockProcessExit(); + await balanceOf('ckt1q9gry5zgmceslalm9x6s5xgnqe9cjn6y0q3c9', { + network: Network.devnet, + udt: false, + }); + + const ckbInstance = (CKB as jest.Mock).mock.results[0].value; + expect(ckbInstance.balance).toHaveBeenCalled(); + expect(ckbInstance.detectUdtBalances).not.toHaveBeenCalled(); + expect(exitSpy).toHaveBeenCalledWith(0); + exitSpy.mockRestore(); + }); +}); + +describe('transfer command', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should transfer CKB by default', async () => { + await transfer('ckt1q9gry5zgmceslalm9x6s5xgnqe9cjn6y0q3c9', '100', { + network: Network.devnet, + privkey: '0x1234567812345678123456781234567812345678123456781234567812345678', + }); + + const ckbInstance = (CKB as jest.Mock).mock.results[0].value; + expect(ckbInstance.transfer).toHaveBeenCalled(); + expect(ckbInstance.udtTransfer).not.toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith('Successfully transfer, txHash:', '0xtxhash'); + }); + + it('should transfer UDT when --udt-type-args is provided', async () => { + await transfer('ckt1q9gry5zgmceslalm9x6s5xgnqe9cjn6y0q3c9', '100', { + network: Network.devnet, + privkey: '0x1234567812345678123456781234567812345678123456781234567812345678', + udtTypeArgs: mockTypeArgs, + udtKind: 'sudt', + }); + + const ckbInstance = (CKB as jest.Mock).mock.results[0].value; + expect(ckbInstance.buildUdtTypeScript).toHaveBeenCalledWith('sudt', mockTypeArgs); + expect(ckbInstance.udtTransfer).toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith('Successfully transfer UDT, txHash:', '0xtxhash'); + }); + + it('should throw when privkey is missing for UDT transfer', async () => { + await expect( + transfer('ckt1q9gry5zgmceslalm9x6s5xgnqe9cjn6y0q3c9', '100', { + network: Network.devnet, + udtTypeArgs: '0xabcd', + }), + ).rejects.toThrow('--privkey is required!'); + }); +}); + +describe('udt command', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('udtIssue', () => { + it('should throw when privkey is missing', async () => { + await expect( + udtIssue('100', { + network: Network.devnet, + udtKind: 'sudt', + privkey: '', + }), + ).rejects.toThrow('--privkey is required!'); + }); + + it('should issue UDT with privkey', async () => { + await udtIssue('100', { + network: Network.devnet, + udtKind: 'sudt', + privkey: '0x1234567812345678123456781234567812345678123456781234567812345678', + }); + + const ckbInstance = (CKB as jest.Mock).mock.results[0].value; + expect(ckbInstance.udtIssue).toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith('Successfully issued UDT, txHash:', '0xissuehash'); + }); + }); + + describe('udtDestroy', () => { + it('should throw when privkey is missing', async () => { + await expect( + udtDestroy('100', { + network: Network.devnet, + udtKind: 'sudt', + typeArgs: mockTypeArgs, + privkey: '', + }), + ).rejects.toThrow('--privkey is required!'); + }); + + it('should destroy UDT with privkey', async () => { + await udtDestroy('100', { + network: Network.devnet, + udtKind: 'sudt', + typeArgs: mockTypeArgs, + privkey: '0x1234567812345678123456781234567812345678123456781234567812345678', + }); + + const ckbInstance = (CKB as jest.Mock).mock.results[0].value; + expect(ckbInstance.udtDestroy).toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith('Successfully destroyed UDT, txHash:', '0xdestroyhash'); + }); + }); +}); diff --git a/tests/validator.test.ts b/tests/validator.test.ts index 9b57b262..f216139f 100644 --- a/tests/validator.test.ts +++ b/tests/validator.test.ts @@ -1,4 +1,10 @@ -import { normalizePrivKey } from '../src/util/validator'; +import { + normalizePrivKey, + isValidUdtKind, + validateUdtKind, + validateUdtAmount, + validateUdtTypeArgs, +} from '../src/util/validator'; describe('normalizePrivKey', () => { const validHex64 = '1234567812345678123456781234567812345678123456781234567812345678'; @@ -44,3 +50,76 @@ describe('normalizePrivKey', () => { expect(() => normalizePrivKey(longKey)).toThrow('Invalid private key length'); }); }); + +describe('UDT validation helpers', () => { + describe('isValidUdtKind', () => { + it('should accept sudt and xudt', () => { + expect(isValidUdtKind('sudt')).toBe(true); + expect(isValidUdtKind('xudt')).toBe(true); + }); + + it('should reject other strings', () => { + expect(isValidUdtKind('')).toBe(false); + expect(isValidUdtKind('SUDT')).toBe(false); + expect(isValidUdtKind('unknown')).toBe(false); + }); + }); + + describe('validateUdtKind', () => { + it('should accept sudt and xudt', () => { + expect(() => validateUdtKind('sudt')).not.toThrow(); + expect(() => validateUdtKind('xudt')).not.toThrow(); + }); + + it('should reject invalid kinds', () => { + expect(() => validateUdtKind('')).toThrow('UDT kind is required'); + expect(() => validateUdtKind('SUDT')).toThrow('invalid UDT kind'); + }); + }); + + describe('validateUdtAmount', () => { + it('should accept non-negative decimal integers', () => { + expect(validateUdtAmount('0')).toBe(0n); + expect(validateUdtAmount('1')).toBe(1n); + expect(validateUdtAmount('123456789012345678901234567890')).toBe(123456789012345678901234567890n); + }); + + it('should reject negative, decimal, hex, scientific and empty values', () => { + expect(() => validateUdtAmount('-1')).toThrow('invalid UDT amount'); + expect(() => validateUdtAmount('1.5')).toThrow('invalid UDT amount'); + expect(() => validateUdtAmount('0x10')).toThrow('invalid UDT amount'); + expect(() => validateUdtAmount('1e10')).toThrow('invalid UDT amount'); + expect(() => validateUdtAmount('')).toThrow('invalid UDT amount'); + expect(() => validateUdtAmount('abc')).toThrow('invalid UDT amount'); + }); + + it('should reject amounts exceeding u128 max', () => { + const u128Max = (BigInt(1) << BigInt(128)) - BigInt(1); + expect(validateUdtAmount(u128Max.toString())).toBe(u128Max); + expect(() => validateUdtAmount((u128Max + BigInt(1)).toString())).toThrow('exceeds 128-bit max'); + }); + }); + + describe('validateUdtTypeArgs', () => { + it('should accept valid SUDT type args', () => { + const args = '0x' + '12'.repeat(20); + expect(validateUdtTypeArgs('sudt', args)).toBe(args); + }); + + it('should accept valid xUDT type args', () => { + const args = '0x' + '12'.repeat(32); + expect(validateUdtTypeArgs('xudt', args)).toBe(args); + }); + + it('should reject invalid hex', () => { + expect(() => validateUdtTypeArgs('sudt', 'not-hex')).toThrow('invalid type args'); + expect(() => validateUdtTypeArgs('sudt', '')).toThrow('invalid type args'); + }); + + it('should reject wrong lengths', () => { + expect(() => validateUdtTypeArgs('sudt', '0x' + '12'.repeat(19))).toThrow('invalid SUDT type args length'); + expect(() => validateUdtTypeArgs('sudt', '0x' + '12'.repeat(32))).toThrow('invalid SUDT type args length'); + expect(() => validateUdtTypeArgs('xudt', '0x' + '12'.repeat(31))).toThrow('invalid xUDT type args length'); + }); + }); +});