From 61be47c72261d6ad67b767ed2270224f428a3a4f Mon Sep 17 00:00:00 2001 From: Victor Oliva Date: Fri, 26 Jun 2026 22:07:36 +0200 Subject: [PATCH] feat: validate and dry-run transactions (#162) * refactor: break down SubmitTx into separate files * add validate transaction, start dry run * add dry run * fix worker getting stuck after forklift is disposed * handle rejections in promiseWithTeardown --- .papi/descriptors/package.json | 2 +- .papi/whitelist.ts | 2 +- package.json | 1 + pnpm-lock.yaml | 463 +++++++++++++++++- src/pages/Extrinsics/SubmitTx/DryRun.tsx | 224 +++++++++ src/pages/Extrinsics/SubmitTx/Estimates.tsx | 142 ++++++ .../Extrinsics/SubmitTx/SubmitExtrinsic.tsx | 180 +------ src/pages/Extrinsics/SubmitTx/submit.state.ts | 72 +++ src/pages/Extrinsics/SubmitTx/validate.ts | 320 ++++++++++++ src/pages/RuntimeCalls/runtimeCalls.state.ts | 4 +- src/pages/Storage/storage.state.ts | 4 +- src/pages/ViewFns/viewFns.state.ts | 4 +- src/state/chains/chain.state.ts | 16 +- 13 files changed, 1253 insertions(+), 181 deletions(-) create mode 100644 src/pages/Extrinsics/SubmitTx/DryRun.tsx create mode 100644 src/pages/Extrinsics/SubmitTx/Estimates.tsx create mode 100644 src/pages/Extrinsics/SubmitTx/submit.state.ts create mode 100644 src/pages/Extrinsics/SubmitTx/validate.ts diff --git a/.papi/descriptors/package.json b/.papi/descriptors/package.json index a5fdd7c..49f5bee 100644 --- a/.papi/descriptors/package.json +++ b/.papi/descriptors/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.0-autogenerated.8494276640227355333", + "version": "0.1.0-autogenerated.3203825568190824154", "name": "@polkadot-api/descriptors", "files": [ "dist" diff --git a/.papi/whitelist.ts b/.papi/whitelist.ts index 6be1580..ed798a6 100644 --- a/.papi/whitelist.ts +++ b/.papi/whitelist.ts @@ -14,5 +14,5 @@ export const whitelist: WhitelistEntriesByChain = { "query.Session.CurrentIndex", "api.TransactionPaymentApi.query_info", ], - dotAh: ["query.*", "tx.*", "const.*"], + dotAh: ["query.*", "tx.*", "const.*", "api.*"], } diff --git a/package.json b/package.json index 6d3b189..bf51b3f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@monaco-editor/react": "^4.7.0", "@paraspell/sdk": "^13.7.2", "@polkadot-api/descriptors": "file:.papi/descriptors", + "@polkadot-api/forklift": "^0.3.0", "@polkadot-api/json-rpc-provider-proxy": "^0.4.0", "@polkadot-api/metadata-builders": "^0.14.3", "@polkadot-api/observable-client": "^0.18.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7249ff0..c0736d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,9 @@ importers: '@polkadot-api/descriptors': specifier: file:.papi/descriptors version: file:.papi/descriptors(polkadot-api@2.1.6(bufferutil@4.1.0)(esbuild@0.25.12)(rxjs@7.8.2)) + '@polkadot-api/forklift': + specifier: ^0.3.0 + version: 0.3.0(@react-rxjs/core@0.10.8(react@19.2.7)(rxjs@7.8.2))(bufferutil@4.1.0)(esbuild@0.25.12)(react@19.2.7)(typescript@6.0.3) '@polkadot-api/json-rpc-provider-proxy': specifier: ^0.4.0 version: 0.4.0 @@ -157,7 +160,7 @@ importers: version: 10.0.1(eslint@10.5.0(jiti@2.7.0)) '@tailwindcss/vite': specifier: ^4.3.1 - version: 4.3.1(vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.6.1)) + version: 4.3.1(vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.9.0)) '@types/node': specifier: ^25.9.3 version: 25.9.3 @@ -172,7 +175,7 @@ importers: version: 4.0.7 '@vitejs/plugin-react': specifier: ^6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.6.1)) + version: 6.0.2(vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.9.0)) eslint: specifier: ^10.5.0 version: 10.5.0(jiti@2.7.0) @@ -199,7 +202,7 @@ importers: version: 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) vite: specifier: ^8.0.16 - version: 8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.6.1) + version: 8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.9.0) packages: @@ -686,6 +689,10 @@ packages: resolution: {integrity: sha512-VcTfNrzIf6acMSbBlmnyTWsR4KIMGc/QmIzGzIUne+Qj145H7BkYJ7Rvr7dG92tK+eT7Keack+CJME6pjPXcLQ==} hasBin: true + '@polkadot-api/cli@0.21.6': + resolution: {integrity: sha512-1OQ2LvRSHkUcT4WRvLQFJ5PhLLN1Z1o6X1c7dguWW3eZ0bv7I9PWrbE5AyyXcy/jkceKCQ6HyISsfbBv8tIQmw==} + hasBin: true + '@polkadot-api/codegen@0.22.5': resolution: {integrity: sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==} @@ -694,6 +701,12 @@ packages: peerDependencies: polkadot-api: '>=2.0.0' + '@polkadot-api/forklift@0.3.0': + resolution: {integrity: sha512-cMi/K5yR0RI55SaMS9l+xgshlhDyxg4cftl+Hgnmgdjxx9NKHTcXRt34HxAPTk2IMqAJ+0W2FPW1JSLt5tFLgA==} + hasBin: true + peerDependencies: + typescript: ^6.0.3 + '@polkadot-api/ink-contracts@0.6.3': resolution: {integrity: sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==} @@ -712,6 +725,9 @@ packages: '@polkadot-api/known-chains@0.11.5': resolution: {integrity: sha512-KOVQRyZj43lr1W5w1lZpzzLUszjPPGV32USsGVxFdguWG7L/gH+nHyBPkyhw3GhN+8NMQD7ZCyuMuNKXbRYlDA==} + '@polkadot-api/known-chains@0.11.6': + resolution: {integrity: sha512-EEffAbSELaYgUjZN1oiPQuJLshI0kuepr+QcT3Z28CQFRfCfSvhLTI0YE4keJ43b8TnqK5VvE8g4fW/rZqTxoA==} + '@polkadot-api/ledger-signer@0.3.3': resolution: {integrity: sha512-kb1AvGu0m0d0IDXvcXzPPwJ8+Lmx7wPQ/VzuBKPZyZWyKk6RM8Zdxnp+v15eHpdmrUvmz//BtPbId01wJmLD2Q==} peerDependencies: @@ -1616,139 +1632,277 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.62.0': resolution: {integrity: sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.62.0': resolution: {integrity: sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.62.0': resolution: {integrity: sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + '@rollup/rollup-freebsd-arm64@4.62.0': resolution: {integrity: sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==} cpu: [arm64] os: [freebsd] + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + '@rollup/rollup-freebsd-x64@4.62.0': resolution: {integrity: sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==} cpu: [x64] os: [freebsd] + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.62.0': resolution: {integrity: sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==} cpu: [arm] os: [linux] libc: [glibc] + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-arm-musleabihf@4.62.0': resolution: {integrity: sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==} cpu: [arm] os: [linux] libc: [musl] + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-arm64-gnu@4.62.0': resolution: {integrity: sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==} cpu: [arm64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-arm64-musl@4.62.0': resolution: {integrity: sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==} cpu: [arm64] os: [linux] libc: [musl] + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-loong64-gnu@4.62.0': resolution: {integrity: sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==} cpu: [loong64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-loong64-musl@4.62.0': resolution: {integrity: sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==} cpu: [loong64] os: [linux] libc: [musl] + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-ppc64-gnu@4.62.0': resolution: {integrity: sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==} cpu: [ppc64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-ppc64-musl@4.62.0': resolution: {integrity: sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==} cpu: [ppc64] os: [linux] libc: [musl] + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-riscv64-gnu@4.62.0': resolution: {integrity: sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==} cpu: [riscv64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-riscv64-musl@4.62.0': resolution: {integrity: sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==} cpu: [riscv64] os: [linux] libc: [musl] + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@rollup/rollup-linux-s390x-gnu@4.62.0': resolution: {integrity: sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==} cpu: [s390x] os: [linux] libc: [glibc] + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-x64-gnu@4.62.0': resolution: {integrity: sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==} cpu: [x64] os: [linux] libc: [glibc] + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rollup/rollup-linux-x64-musl@4.62.0': resolution: {integrity: sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==} cpu: [x64] os: [linux] libc: [musl] + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + libc: [musl] + '@rollup/rollup-openbsd-x64@4.62.0': resolution: {integrity: sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==} cpu: [x64] os: [openbsd] + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + '@rollup/rollup-openharmony-arm64@4.62.0': resolution: {integrity: sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==} cpu: [arm64] os: [openharmony] + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.62.0': resolution: {integrity: sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.62.0': resolution: {integrity: sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + '@rollup/rollup-win32-x64-gnu@4.62.0': resolution: {integrity: sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.62.0': resolution: {integrity: sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + '@rx-state/core@0.1.4': resolution: {integrity: sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ==} peerDependencies: @@ -2289,6 +2443,9 @@ packages: '@types/node@25.9.3': resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + '@types/node@25.9.4': + resolution: {integrity: sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2309,6 +2466,9 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.61.0': resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3421,6 +3581,10 @@ packages: resolution: {integrity: sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==} engines: {node: '>=20'} + ora@9.4.1: + resolution: {integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==} + engines: {node: '>=20'} + ox@0.14.29: resolution: {integrity: sha512-M5j87Ec4V99MQdRct/g09eWXW60g6zhHTUs1lr4deUtrPDnezBdCJTgKd7pxqTpSZBFveV0ALi9jMMuT1qKyNg==} peerDependencies: @@ -3516,6 +3680,10 @@ packages: resolution: {integrity: sha512-eI9pKwWEix40kfvSzqEP6ldqOoBIN7dwD/o91TY5z8vQI12sAffpR/pOqAD1IVVwIVHDpHjkq0joBPdJD0rafA==} hasBin: true + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + pino@9.14.0: resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} hasBin: true @@ -3530,6 +3698,12 @@ packages: peerDependencies: rxjs: '>=7.8.0' + polkadot-api@2.1.7: + resolution: {integrity: sha512-hSXzpbxtm4DkgBxC1m3ahqCiGTnOI75NmkWOJHlqgR3jmFYzPCMjuURpe8V1gKxzcDWMcffEmgwsM/4nm1Ox5Q==} + hasBin: true + peerDependencies: + rxjs: '>=7.8.0' + polkahub@0.7.0: resolution: {integrity: sha512-qFSMsARaJmzjWZCF4L9EHNhKkZr995dUDw/tAYPKgwsayo8mOA+xCcYH7eJzoZOZsY2LAM4KLgM4acY1Ptt5bA==} peerDependencies: @@ -3689,6 +3863,9 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -3720,6 +3897,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -3865,6 +4047,10 @@ packages: thread-stream@3.2.0: resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==} + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -4200,9 +4386,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.6.1: - resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} - engines: {node: '>= 14'} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} hasBin: true yargs-parser@18.1.3: @@ -4806,6 +4992,41 @@ snapshots: - supports-color - utf-8-validate + '@polkadot-api/cli@0.21.6(bufferutil@4.1.0)(esbuild@0.25.12)': + dependencies: + '@commander-js/extra-typings': 15.0.0(commander@15.0.0) + '@polkadot-api/codegen': 0.22.5 + '@polkadot-api/ink-contracts': 0.6.3 + '@polkadot-api/json-rpc-provider': 0.2.0 + '@polkadot-api/known-chains': 0.11.6 + '@polkadot-api/metadata-compatibility': 0.6.3 + '@polkadot-api/observable-client': 0.18.7(rxjs@7.8.2) + '@polkadot-api/sm-provider': 0.3.6(@polkadot-api/smoldot@0.4.5(bufferutil@4.1.0)) + '@polkadot-api/smoldot': 0.4.5(bufferutil@4.1.0) + '@polkadot-api/substrate-bindings': 0.20.3 + '@polkadot-api/substrate-client': 0.7.0 + '@polkadot-api/utils': 0.4.0 + '@polkadot-api/wasm-executor': 0.2.3 + '@polkadot-api/ws-middleware': 0.3.5(rxjs@7.8.2) + '@polkadot-api/ws-provider': 0.9.0(rxjs@7.8.2) + '@types/node': 25.9.4 + commander: 15.0.0 + execa: 9.6.1 + fs.promises.exists: 1.1.4 + ora: 9.4.1 + read-pkg: 10.1.0 + rollup: 4.62.2 + rollup-plugin-esbuild: 6.2.1(esbuild@0.25.12)(rollup@4.62.2) + rxjs: 7.8.2 + tsc-prog: 2.3.0(typescript@6.0.3) + typescript: 6.0.3 + write-package: 7.2.0 + transitivePeerDependencies: + - bufferutil + - esbuild + - supports-color + - utf-8-validate + '@polkadot-api/codegen@0.22.5': dependencies: '@polkadot-api/ink-contracts': 0.6.3 @@ -4818,6 +5039,35 @@ snapshots: dependencies: polkadot-api: 2.1.6(bufferutil@4.1.0)(esbuild@0.25.12)(rxjs@7.8.2) + '@polkadot-api/forklift@0.3.0(@react-rxjs/core@0.10.8(react@19.2.7)(rxjs@7.8.2))(bufferutil@4.1.0)(esbuild@0.25.12)(react@19.2.7)(typescript@6.0.3)': + dependencies: + '@acala-network/chopsticks-executor': 1.4.2 + '@polkadot-api/metadata-builders': 0.14.3 + '@polkadot-api/observable-client': 0.18.7(rxjs@7.8.2) + '@polkadot-api/substrate-bindings': 0.20.3 + '@polkadot-api/substrate-client': 0.7.0 + '@polkadot-api/tx-utils': 0.3.3 + '@polkadot-api/ws-middleware': 0.3.5(rxjs@7.8.2) + '@polkadot-api/ws-provider': 0.9.0(rxjs@7.8.2) + '@react-rxjs/utils': 0.9.7(@react-rxjs/core@0.10.8(react@19.2.7)(rxjs@7.8.2))(react@19.2.7)(rxjs@7.8.2) + '@types/ws': 8.18.1 + comlink: 4.4.2 + commander: 15.0.0 + pino: 10.3.1 + pino-pretty: 13.1.3 + polkadot-api: 2.1.7(bufferutil@4.1.0)(esbuild@0.25.12)(rxjs@7.8.2) + rxjs: 7.8.2 + typescript: 6.0.3 + ws: 8.21.0(bufferutil@4.1.0) + yaml: 2.9.0 + transitivePeerDependencies: + - '@react-rxjs/core' + - bufferutil + - esbuild + - react + - supports-color + - utf-8-validate + '@polkadot-api/ink-contracts@0.6.3': dependencies: '@polkadot-api/metadata-builders': 0.14.3 @@ -4836,6 +5086,8 @@ snapshots: '@polkadot-api/known-chains@0.11.5': {} + '@polkadot-api/known-chains@0.11.6': {} + '@polkadot-api/ledger-signer@0.3.3(@ledgerhq/hw-transport@6.35.4)': dependencies: '@ledgerhq/hw-transport': 6.35.4 @@ -6384,78 +6636,153 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.62.0': optional: true + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + '@rollup/rollup-android-arm64@4.62.0': optional: true + '@rollup/rollup-android-arm64@4.62.2': + optional: true + '@rollup/rollup-darwin-arm64@4.62.0': optional: true + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + '@rollup/rollup-darwin-x64@4.62.0': optional: true + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + '@rollup/rollup-freebsd-arm64@4.62.0': optional: true + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + '@rollup/rollup-freebsd-x64@4.62.0': optional: true + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.62.0': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.62.0': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.62.0': optional: true + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-arm64-musl@4.62.0': optional: true + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-loong64-gnu@4.62.0': optional: true + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-loong64-musl@4.62.0': optional: true + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-ppc64-gnu@4.62.0': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-ppc64-musl@4.62.0': optional: true + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.62.0': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-riscv64-musl@4.62.0': optional: true + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.62.0': optional: true + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-x64-gnu@4.62.0': optional: true + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-x64-musl@4.62.0': optional: true + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + '@rollup/rollup-openbsd-x64@4.62.0': optional: true + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + '@rollup/rollup-openharmony-arm64@4.62.0': optional: true + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.62.0': optional: true + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.62.0': optional: true + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + '@rollup/rollup-win32-x64-gnu@4.62.0': optional: true + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + '@rollup/rollup-win32-x64-msvc@4.62.0': optional: true + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + '@rx-state/core@0.1.4(rxjs@7.8.2)': dependencies: rxjs: 7.8.2 @@ -7080,12 +7407,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1 '@tailwindcss/oxide-win32-x64-msvc': 4.3.1 - '@tailwindcss/vite@4.3.1(vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.6.1))': + '@tailwindcss/vite@4.3.1(vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.3.1 '@tailwindcss/oxide': 4.3.1 tailwindcss: 4.3.1 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.6.1) + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.9.0) '@tanem/svg-injector@11.3.2': dependencies: @@ -7112,6 +7439,10 @@ snapshots: dependencies: undici-types: 7.24.6 + '@types/node@25.9.4': + dependencies: + undici-types: 7.24.6 + '@types/normalize-package-data@2.4.4': {} '@types/prop-types@15.7.15': {} @@ -7130,6 +7461,10 @@ snapshots: '@types/trusted-types@2.0.7': {} + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.9.3 + '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -7221,10 +7556,10 @@ snapshots: '@typescript-eslint/types': 8.61.0 eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.6.1))': + '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.6.1) + vite: 8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.9.0) '@wallet-standard/base@1.1.1': {} @@ -8520,6 +8855,17 @@ snapshots: stdin-discarder: 0.3.2 string-width: 8.2.1 + ora@9.4.1: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.2.1 + ox@0.14.29(typescript@6.0.3)(zod@3.22.4): dependencies: '@adraffy/ens-normalize': 1.11.1 @@ -8660,6 +9006,20 @@ snapshots: sonic-boom: 4.2.1 thread-stream: 3.2.0 + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + pino@9.14.0: dependencies: '@pinojs/redact': 0.4.0 @@ -8704,6 +9064,34 @@ snapshots: - supports-color - utf-8-validate + polkadot-api@2.1.7(bufferutil@4.1.0)(esbuild@0.25.12)(rxjs@7.8.2): + dependencies: + '@polkadot-api/cli': 0.21.6(bufferutil@4.1.0)(esbuild@0.25.12) + '@polkadot-api/ink-contracts': 0.6.3 + '@polkadot-api/json-rpc-provider': 0.2.0 + '@polkadot-api/known-chains': 0.11.6 + '@polkadot-api/logs-provider': 0.2.0 + '@polkadot-api/metadata-builders': 0.14.3 + '@polkadot-api/metadata-compatibility': 0.6.3 + '@polkadot-api/observable-client': 0.18.7(rxjs@7.8.2) + '@polkadot-api/pjs-signer': 0.7.3 + '@polkadot-api/polkadot-signer': 0.1.6 + '@polkadot-api/signer': 0.3.3 + '@polkadot-api/sm-provider': 0.3.6(@polkadot-api/smoldot@0.4.5(bufferutil@4.1.0)) + '@polkadot-api/smoldot': 0.4.5(bufferutil@4.1.0) + '@polkadot-api/substrate-bindings': 0.20.3 + '@polkadot-api/substrate-client': 0.7.0 + '@polkadot-api/utils': 0.4.0 + '@polkadot-api/ws-middleware': 0.3.5(rxjs@7.8.2) + '@polkadot-api/ws-provider': 0.9.0(rxjs@7.8.2) + '@rx-state/core': 0.1.4(rxjs@7.8.2) + rxjs: 7.8.2 + transitivePeerDependencies: + - bufferutil + - esbuild + - supports-color + - utf-8-validate + polkahub@0.7.0(@ledgerhq/hw-transport@6.35.4)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(bufferutil@4.1.0)(esbuild@0.25.12)(fastestsmallesttextencoderdecoder@1.0.22)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rxjs@7.8.2)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.7))(zod@3.25.76): dependencies: '@polkahub/context': 0.6.0(@types/react@19.2.17)(bufferutil@4.1.0)(esbuild@0.25.12)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -8906,6 +9294,8 @@ snapshots: real-require@0.2.0: {} + real-require@1.0.0: {} + require-directory@2.1.1: {} require-main-filename@2.0.0: {} @@ -8949,6 +9339,17 @@ snapshots: transitivePeerDependencies: - supports-color + rollup-plugin-esbuild@6.2.1(esbuild@0.25.12)(rollup@4.62.2): + dependencies: + debug: 4.4.3 + es-module-lexer: 1.7.0 + esbuild: 0.25.12 + get-tsconfig: 4.14.0 + rollup: 4.62.2 + unplugin-utils: 0.2.5 + transitivePeerDependencies: + - supports-color + rollup@4.62.0: dependencies: '@types/estree': 1.0.9 @@ -8980,6 +9381,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.62.0 fsevents: 2.3.3 + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -9102,6 +9534,10 @@ snapshots: dependencies: real-require: 0.2.0 + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -9262,7 +9698,7 @@ snapshots: - utf-8-validate - zod - vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.6.1): + vite@8.0.16(@types/node@25.9.3)(esbuild@0.25.12)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -9274,7 +9710,7 @@ snapshots: esbuild: 0.25.12 fsevents: 2.3.3 jiti: 2.7.0 - yaml: 2.6.1 + yaml: 2.9.0 web-streams-polyfill@3.3.3: {} @@ -9337,8 +9773,7 @@ snapshots: yallist@3.1.1: {} - yaml@2.6.1: - optional: true + yaml@2.9.0: {} yargs-parser@18.1.3: dependencies: diff --git a/src/pages/Extrinsics/SubmitTx/DryRun.tsx b/src/pages/Extrinsics/SubmitTx/DryRun.tsx new file mode 100644 index 0000000..505ad9e --- /dev/null +++ b/src/pages/Extrinsics/SubmitTx/DryRun.tsx @@ -0,0 +1,224 @@ +import { ActionButton } from "@/components/ActionButton" +import { JsonDisplay } from "@/components/JsonDisplay" +import { + Dialog, + DialogBody, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { SystemEvent } from "@polkadot-api/observable-client" +import { state, useStateObservable } from "@react-rxjs/core" +import { createSignal } from "@react-rxjs/utils" +import { CheckCircle, CircleAlert, CircleX, Loader2, Play } from "lucide-react" +import { FC, ReactNode, useEffect, useState } from "react" +import { combineLatest, startWith, switchMap, takeUntil } from "rxjs" +import { txValidity$ } from "./Estimates" +import { transaction$, txOptions$ } from "./submit.state" +import { dryRun$ } from "./validate" + +const [triggerDryRun$, dryRun] = createSignal() +const [cancelDryRun$, cancelDryRun] = createSignal() +const dryRunResult$ = state( + triggerDryRun$.pipe( + switchMap(() => + combineLatest([transaction$, txOptions$]).pipe( + switchMap(([tx, txOptions], idx) => { + // any late change interrupts and cancels the dry run + if (idx > 0 || !tx) return [null] + + return dryRun$(tx, txOptions).pipe( + startWith({ + type: "running" as const, + value: undefined, + }), + ) + }), + takeUntil(cancelDryRun$), + ), + ), + ), + null, +) + +export const DryRun = () => { + const validity = useStateObservable(txValidity$) + const [open, setOpen] = useState(false) + + useEffect(() => { + const sub = dryRunResult$.subscribe() + return () => sub.unsubscribe() + }, []) + + const disabled = validity?.type !== "valid" + const button = ( + + + Dry Run + + ) + + return disabled ? ( + + +
{button}
+
+ + Can only dry run transactions that have passed validation + +
+ ) : ( + { + setOpen(open) + if (open) { + dryRun() + } else { + cancelDryRun() + } + }} + > + {button} + + + Dry Run + + + + + + + ) +} + +const DryRunModalContent: FC = () => { + const result = useStateObservable(dryRunResult$) + if (!result || result.type === "no-signer") { + // Should be unreachable + return null + } + + if (result.type === "running") { + return ( +
+ + Simulating +
+ ) + } + + if (result.type === "invalid" || result.type === "error") { + return ( +
+
+ +
+
+ Rejected by transaction validation +
+
+ +
+
+
+
+ ) + } + + const { ok, dispatchError, events } = result.value + const passed = ok && !dispatchError + + return ( +
+
+
+ {passed ? ( + + ) : ( + + )} +
+
+ {passed ? "Transaction succeeded" : "Transaction failed"} +
+
+
+
+ + {dispatchError ? ( + + + + ) : null} + + +
    + {events.map((event, index) => ( + + ))} +
+
+
+ ) +} + +const DryRunSection: FC<{ title: string; children: ReactNode }> = ({ + title, + children, +}) => ( +
+

+ {title} +

+ {children} +
+) + +type FlattenedEvent = SystemEvent & SystemEvent["event"] +const DryRunEvent: FC<{ event: FlattenedEvent }> = ({ event }) => { + const [expanded, setExpanded] = useState(false) + const eventName = getEventName(event) + + return ( +
  • + + {expanded ? ( +
    + +
    + ) : null} +
  • + ) +} + +const getEventName = (event: FlattenedEvent) => { + const pallet = event.type + const name = event.value.type + return `${pallet}.${name}` +} diff --git a/src/pages/Extrinsics/SubmitTx/Estimates.tsx b/src/pages/Extrinsics/SubmitTx/Estimates.tsx new file mode 100644 index 0000000..770170a --- /dev/null +++ b/src/pages/Extrinsics/SubmitTx/Estimates.tsx @@ -0,0 +1,142 @@ +import { TokenAmount } from "@/components/TokenAmount" +import { client$ } from "@/state/chains/chain.state" +import { + getAccountGenericAddress, + getAccountPublicKey, + selectedAccount$, +} from "@/state/polkahub" +import { polkadot_people } from "@polkadot-api/descriptors" +import { + liftSuspense, + state, + SUSPENSE, + useStateObservable, +} from "@react-rxjs/core" +import { switchMapSuspended } from "@react-rxjs/utils" +import { CheckCircle, CircleX, Loader2 } from "lucide-react" +import { FC, ReactNode } from "react" +import { catchError, combineLatest, map, of, switchMap, timer } from "rxjs" +import { transaction$, txOptions$ } from "./submit.state" +import { validate$ } from "./validate" + +const accountBalance$ = state( + combineLatest([selectedAccount$, client$]).pipe( + switchMapSuspended(([account, client]) => + account + ? client + .getTypedApi(polkadot_people) + .query.System.Account.getValue(getAccountGenericAddress(account)) + : [], + ), + liftSuspense(), + map((v) => (v === SUSPENSE ? null : v)), + map((v) => { + if (!v) return null + const { reserved, free, frozen } = v.data + const total = reserved + free + + // TODO ED + const untouchable = total == 0n ? 0n : maxBigInt(frozen - reserved, 0n) + + return free - untouchable + }), + ), + null, +) + +export const paymentInfo$ = state( + combineLatest([transaction$, selectedAccount$, txOptions$]).pipe( + switchMapSuspended(([tx, account, txOptions]) => { + if (!tx || !account) return [null] + + // Adding a small delay for debouncing quick input changes + return timer(200).pipe( + switchMap(() => + tx.getPaymentInfo(getAccountPublicKey(account), txOptions), + ), + catchError((ex) => { + console.error(ex) + return of(null) + }), + ) + }), + liftSuspense(), + map((v) => (v === SUSPENSE ? null : v)), + ), + null, +) + +export const txValidity$ = state( + combineLatest({ + tx: transaction$, + txOptions: txOptions$, + }).pipe( + switchMapSuspended(({ tx, txOptions }) => + tx ? timer(500).pipe(switchMap(() => validate$(tx, txOptions))) : [null], + ), + liftSuspense(), + map((v) => (v === SUSPENSE ? null : v)), + ), + null, +) + +export const Estimates: FC = () => { + const paymentInfo = useStateObservable(paymentInfo$) + const balance = useStateObservable(accountBalance$) + const validity = useStateObservable(txValidity$) + + const loader = + const renderValidity = () => { + if (!validity) return loader + if (validity.type === "valid") { + return ( +
    + Valid + +
    + ) + } + if (validity.type === "invalid") { + return ( +
    + {`Invalid: ${validity.value}`} + +
    + ) + } + return "N/A" + } + + return ( +
    +

    Submission

    + {paymentInfo.partial_fee} + ) : ( + loader + ) + } + /> + {balance}} + /> + +
    + ) +} + +const EstimateRow: FC<{ label: string; value: ReactNode }> = ({ + label, + value, +}) => ( +
    + {label} + {value} +
    +) + +const maxBigInt = (a: bigint, b: bigint) => (a > b ? a : b) diff --git a/src/pages/Extrinsics/SubmitTx/SubmitExtrinsic.tsx b/src/pages/Extrinsics/SubmitTx/SubmitExtrinsic.tsx index 6265bd3..ab809be 100644 --- a/src/pages/Extrinsics/SubmitTx/SubmitExtrinsic.tsx +++ b/src/pages/Extrinsics/SubmitTx/SubmitExtrinsic.tsx @@ -1,6 +1,5 @@ import { ActionButton } from "@/components/ActionButton" import { Spinner } from "@/components/Icons" -import { TokenAmount } from "@/components/TokenAmount" import { Dialog, DialogTrigger } from "@/components/ui/dialog" import { Select, @@ -9,15 +8,9 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" -import { createState } from "@/lib/externalState" import { PolkahubModalBasedManagers } from "@/pages/Accounts/Providers" -import { client$, unsafeApi$ } from "@/state/chains/chain.state" -import { - getAccountGenericAddress, - getAccountPublicKey, - selectedAccount$, -} from "@/state/polkahub" -import { polkadot_people } from "@polkadot-api/descriptors" +import { unsafeApi$ } from "@/state/chains/chain.state" +import { getAccountGenericAddress, selectedAccount$ } from "@/state/polkahub" import { Button, DialogBody, @@ -33,13 +26,8 @@ import { useStateObservable, withDefault, } from "@react-rxjs/core" -import { - createSignal, - mergeWithKey, - switchMapSuspended, -} from "@react-rxjs/utils" +import { switchMapSuspended } from "@react-rxjs/utils" import { ChevronLeft, Send, Settings, WalletCards } from "lucide-react" -import { TxOptions } from "polkadot-api" import { ModalContext, PjsWalletButtons, @@ -47,19 +35,23 @@ import { useSelectedAccount, } from "polkahub" import { FC, forwardRef, ReactNode, useState } from "react" -import { - catchError, - combineLatest, - distinctUntilChanged, - map, - of, - scan, - switchMap, - timer, -} from "rxjs" -import { callData$ } from "../componentValue.state" +import { catchError, map, switchMap, timer } from "rxjs" import { CustomSignedExt, customSignedExtensions$ } from "../CustomSignedExt" import { trackTx } from "../ExtrinsicsWorkspaceEntry" +import { Estimates } from "./Estimates" +import { DryRun } from "./DryRun" +import { + blurNonce, + DEFAULT_MORTAL, + mortality$, + nonce$, + setMortality, + setNonce, + setTip, + tip$, + transaction$, + txOptions$, +} from "./submit.state" import { SelectAccount } from "./SubmitTxForm" const customExtensionsCount$ = state( @@ -76,8 +68,9 @@ const customExtensionsCount$ = state( null, ) -const [nonceChanged$, setNonce] = createSignal() -const [nonceBlurred$, blurNonce] = createSignal() +// powers of 2 from 4 to 16 (incl) +const periodOptions = new Array(16 - 4 + 1).fill(0).map((_, i) => 1 << (i + 4)) + const chainNonce$ = unsafeApi$.pipeState( switchMapSuspended((api) => selectedAccount$.pipe( @@ -100,107 +93,6 @@ const chainNonce$ = unsafeApi$.pipeState( map((v) => (v === SUSPENSE ? null : (v as number))), withDefault(null), ) -const isIntegerStr = (str: string) => /^\d+$/.test(str) -const nonce$ = state( - mergeWithKey({ - nonceChanged$, - nonceBlurred$, - }).pipe( - scan( - (acc, v) => - v.type === "nonceChanged$" ? v.payload : isIntegerStr(acc) ? acc : "", - "", - ), - distinctUntilChanged(), - ), - "", -) - -type Mortality = NonNullable["mortality"]> -const DEFAULT_MORTAL = { - mortal: true, - period: 64, -} -// powers of 2 from 4 to 16 (incl) -const periodOptions = new Array(16 - 4 + 1).fill(0).map((_, i) => 1 << (i + 4)) -const [mortality$, setMortality] = createState(DEFAULT_MORTAL) -const [tip$, setTip] = createState("0") - -const transaction$ = state( - combineLatest([unsafeApi$, callData$]).pipe( - switchMapSuspended(([unsafeApi, callData]) => - callData ? unsafeApi.txFromCallData(callData) : [null], - ), - liftSuspense(), - map((v) => (v === SUSPENSE ? null : v)), - ), - null, -) - -const txOptions$ = state( - combineLatest([ - nonce$.pipe(map((v) => (isIntegerStr(v) ? Number(v) : null))), - mortality$, - tip$.pipe(map((v) => (isIntegerStr(v) ? BigInt(v) : null))), - customSignedExtensions$, - ]).pipe( - map(([nonce, mortality, tip, signedExt]): TxOptions => { - return { - mortality, - nonce: nonce ?? undefined, - tip: tip ?? undefined, - customSignedExtensions: signedExt, - } - }), - ), - {} satisfies TxOptions, -) - -export const paymentInfo$ = state( - combineLatest([transaction$, selectedAccount$, txOptions$]).pipe( - switchMapSuspended(([tx, account, txOptions]) => { - if (!tx || !account) return [null] - - // Adding a small delay for debouncing quick input changes - return timer(200).pipe( - switchMap(() => - tx.getPaymentInfo(getAccountPublicKey(account), txOptions), - ), - catchError((ex) => { - console.error(ex) - return of(null) - }), - ) - }), - liftSuspense(), - map((v) => (v === SUSPENSE ? null : v)), - ), - null, -) -const accountBalance$ = state( - combineLatest([selectedAccount$, client$]).pipe( - switchMapSuspended(([account, client]) => - account - ? client - .getTypedApi(polkadot_people) - .query.System.Account.getValue(getAccountGenericAddress(account)) - : [], - ), - liftSuspense(), - map((v) => (v === SUSPENSE ? null : v)), - map((v) => { - if (!v) return null - const { reserved, free, frozen } = v.data - const total = reserved + free - - // TODO ED - const untouchable = total == 0n ? 0n : maxBigInt(frozen - reserved, 0n) - - return free - untouchable - }), - ), - null, -) export const SubmitExtrinsic = forwardRef((_, ref) => { const [account] = useSelectedAccount() @@ -208,8 +100,6 @@ export const SubmitExtrinsic = forwardRef((_, ref) => { const nonce = useStateObservable(nonce$) const mortality = useStateObservable(mortality$) const tip = useStateObservable(tip$) - const paymentInfo = useStateObservable(paymentInfo$) - const balance = useStateObservable(accountBalance$) const tx = useStateObservable(transaction$) const txOptions = useStateObservable(txOptions$) const [isSigning, setIsSigning] = useState(false) @@ -326,25 +216,11 @@ export const SubmitExtrinsic = forwardRef((_, ref) => { -
    -

    Fees

    - {paymentInfo.partial_fee} - ) : ( - "…" - ) - } - /> - {balance}} - /> -
    +
    + + ) - -const FeeRow: FC<{ label: string; value: ReactNode }> = ({ label, value }) => ( -
    - {label} - {value} -
    -) -const maxBigInt = (a: bigint, b: bigint) => (a > b ? a : b) diff --git a/src/pages/Extrinsics/SubmitTx/submit.state.ts b/src/pages/Extrinsics/SubmitTx/submit.state.ts new file mode 100644 index 0000000..745a165 --- /dev/null +++ b/src/pages/Extrinsics/SubmitTx/submit.state.ts @@ -0,0 +1,72 @@ +import { createState } from "@/lib/externalState" +import { unsafeApi$ } from "@/state/chains/chain.state" +import { liftSuspense, state, SUSPENSE } from "@react-rxjs/core" +import { + createSignal, + mergeWithKey, + switchMapSuspended, +} from "@react-rxjs/utils" +import { TxOptions } from "polkadot-api" +import { combineLatest, distinctUntilChanged, map, scan } from "rxjs" +import { callData$ } from "../componentValue.state" +import { customSignedExtensions$ } from "../CustomSignedExt" + +export const [nonceChanged$, setNonce] = createSignal() +export const [nonceBlurred$, blurNonce] = createSignal() + +const isIntegerStr = (str: string) => /^\d+$/.test(str) +export const nonce$ = state( + mergeWithKey({ + nonceChanged$, + nonceBlurred$, + }).pipe( + scan( + (acc, v) => + v.type === "nonceChanged$" ? v.payload : isIntegerStr(acc) ? acc : "", + "", + ), + ), + "", +) + +type Mortality = NonNullable["mortality"]> +export const DEFAULT_MORTAL = { + mortal: true, + period: 64, +} + +export const [mortality$, setMortality] = createState(DEFAULT_MORTAL) +export const [tip$, setTip] = createState("0") + +export const transaction$ = state( + combineLatest([unsafeApi$, callData$]).pipe( + switchMapSuspended(([unsafeApi, callData]) => + callData ? unsafeApi.txFromCallData(callData) : [null], + ), + liftSuspense(), + map((v) => (v === SUSPENSE ? null : v)), + ), + null, +) + +export const txOptions$ = state( + combineLatest([ + nonce$.pipe( + distinctUntilChanged(), + map((v) => (isIntegerStr(v) ? Number(v) : null)), + ), + mortality$, + tip$.pipe(map((v) => (isIntegerStr(v) ? BigInt(v) : null))), + customSignedExtensions$, + ]).pipe( + map(([nonce, mortality, tip, signedExt]): TxOptions => { + return { + mortality, + nonce: nonce ?? undefined, + tip: tip ?? undefined, + customSignedExtensions: signedExt, + } + }), + ), + {} satisfies TxOptions, +) diff --git a/src/pages/Extrinsics/SubmitTx/validate.ts b/src/pages/Extrinsics/SubmitTx/validate.ts new file mode 100644 index 0000000..fc0a505 --- /dev/null +++ b/src/pages/Extrinsics/SubmitTx/validate.ts @@ -0,0 +1,320 @@ +import { chainClient$ } from "@/state/chains/chain.state" +import { selectedAccount$ } from "@/state/polkahub" +import { + DotAh, + TransactionValidityTransactionSource, +} from "@polkadot-api/descriptors" +import { forklift, fromWorker } from "@polkadot-api/forklift" +import Worker from "@polkadot-api/forklift/worker?worker" +import { ChainHead$ } from "@polkadot-api/observable-client" +import { shareLatest } from "@react-rxjs/core" +import { + AccountId, + Binary, + createClient, + Enum, + HexString, + InvalidTxError, + PolkadotClient, + Transaction, +} from "polkadot-api" +import { getPolkadotSigner } from "polkadot-api/signer" +import { AccountAddress } from "polkahub" +import { + catchError, + combineLatest, + filter, + firstValueFrom, + lastValueFrom, + map, + Observable, + switchMap, + take, + TeardownLogic, + toArray, +} from "rxjs" + +const promiseWithTeardown = ( + deferFn: () => Promise<{ + value: T + teardown: TeardownLogic + }>, +) => + new Observable((obs) => { + deferFn().then( + ({ value, teardown }) => { + obs.add(teardown) + obs.next(value) + }, + (ex) => obs.error(ex), + ) + }) + +const forkLift$ = chainClient$.pipe( + switchMap(({ client, chainHead }) => + promiseWithTeardown(async () => { + const [forklift, worker] = await createTmpForklift(client, chainHead) + const forkClient = createClient( + // withLogsRecorder(console.log, forklift.serve), + forklift.serve, + ) + + return { + value: { forklift, forkClient }, + teardown: () => { + forkClient.destroy() + forklift.destroy() + worker.terminate() + }, + } + }), + ), + shareLatest(), +) + +const fakeSign$ = (tx: Transaction, txOptions: any) => + combineLatest({ + signer: selectedAccount$.pipe( + map((account) => (account ? createFakeSigner(account.address) : null)), + ), + mockChain: forkLift$.pipe( + switchMap(async ({ forkClient }) => { + const unsafeApi = forkClient.getUnsafeApi() + return { + client: forkClient, + unsafeApi, + tx: await unsafeApi.txFromCallData(await tx.getEncodedData()), + } + }), + ), + }).pipe( + switchMap(async ({ signer, mockChain: { tx, unsafeApi, client } }) => { + if (!signer) return null + + const extrinsic = await tx.sign(signer, txOptions) + + return { extrinsic, tx, unsafeApi, client } + }), + ) + +export const validate$ = (tx: Transaction, txOptions: any) => + fakeSign$(tx, txOptions).pipe( + switchMap(async (evt) => + evt + ? evt.unsafeApi.apis.TaggedTransactionQueue.validate_transaction( + TransactionValidityTransactionSource.External(), + evt.extrinsic.slice(2), + (await evt.client.getFinalizedBlock()).hash, + ) + : null, + ), + map((evt) => + !evt + ? { + type: "no-signer" as const, + value: undefined, + } + : evt.success + ? { + type: "valid" as const, + value: undefined, + } + : { + type: "invalid" as const, + value: evt.value.value.type, + }, + ), + catchError((ex) => { + console.error(ex) + return [ + { + type: "error" as const, + value: ex.error, + }, + ] + }), + take(1), + ) + +export const dryRun$ = (tx: Transaction, txOptions: any) => + fakeSign$(tx, txOptions).pipe( + switchMap((evt) => + evt ? evt.client.submitAndWatch(evt.extrinsic) : [null], + ), + map((evt) => { + if (!evt) { + return { + type: "no-signer" as const, + value: undefined, + } + } + if (evt.type === "txBestBlocksState" && evt.found) { + return { + type: "valid" as const, + value: { + ok: evt.ok, + dispatchError: evt.dispatchError, + events: evt.events, + }, + } + } + return null + }), + catchError((ex) => { + if (ex instanceof InvalidTxError) { + return [ + { + type: "invalid" as const, + value: ex.error, + }, + ] + } + console.error(ex) + return [ + { + type: "error" as const, + value: ex.error, + }, + ] + }), + filter((v) => v !== null), + take(1), + ) + +const codeCache = new WeakMap>() +const CODE_KEY = "0x3a636f6465" +const PARAMETERS_KEY = + "0xc63bdd4a39095ccf55623a6f2872bf8ac63bdd4a39095ccf55623a6f2872bf8a" + +const createTmpForklift = async ( + client: PolkadotClient, + chainHead: ChainHead$, +) => { + const [block] = await client.getBestBlocks() + const unhodl = client.hodlBlock(block.hash) + const metadata = await client.getMetadata(block.hash) + + // forklift needs the code, which is a big-ass query (~4MB). + // PAPI doesn't have it, but we should prevent this query to be repeated by every block when the runtime is the same + if (!codeCache.has(metadata)) { + const codeP = firstValueFrom( + chainHead + .storage$(block.hash, "value", () => CODE_KEY) + .pipe( + map((v) => { + if (!v.value) { + throw new Error("Code not available") + } + return Binary.fromHex(v.value) + }), + ), + ) + codeCache.set(metadata, codeP) + } + + // We can also optimise by pre-quering "Parameters.Parameters", which is often serialised and is mostly empty + const parametersP = firstValueFrom( + chainHead + .storage$(block.hash, "descendantsValues", () => PARAMETERS_KEY) + .pipe( + map((v) => + Object.fromEntries( + v.value.map(({ key, value }) => [key, Binary.fromHex(value)]), + ), + ), + ), + ) + const [code, parameters] = await Promise.all([ + codeCache.get(metadata)!, + parametersP, + ]) + + const getBlock = async () => ({ + blockHash: block.hash, + header: await client.getBlockHeader(block.hash), + body: await client.getBlockBody(block.hash), + }) + const getStorage = async (key: HexString) => { + if (key === CODE_KEY) return code + if (key.startsWith(PARAMETERS_KEY)) return parameters[key] ?? null + + return firstValueFrom( + chainHead + .storage$(block.hash, "value", () => key) + .pipe(map((v) => (v.value ? Binary.fromHex(v.value) : null))), + ) + } + const getStorageBatch = async (keys: HexString[]) => { + if (!keys.length) return [] + + const result = new Array(keys.length) + const keyToIdx = Object.fromEntries(keys.map((key, i) => [key, i])) + + const results = await lastValueFrom( + chainHead + .storageQueries$( + block.hash, + keys.map((key) => ({ + type: "value", + key, + })), + ) + .pipe(toArray()), + ) + results.forEach( + (r) => + (result[keyToIdx[r.key]] = r.value ? Binary.fromHex(r.value) : null), + ) + + return result + } + const getStorageDescendants = (prefix: HexString) => { + return firstValueFrom( + chainHead + .storage$(block.hash, "descendantsValues", () => prefix) + .pipe( + map((v) => + Object.fromEntries( + v.value.map(({ key, value }) => [key, Binary.fromHex(value)]), + ), + ), + ), + ) + } + const getChainSpecData = client.getChainSpecData + + const forkliftWorker = new Worker() + return [ + forklift( + { + block: getBlock(), + getStorage, + getStorageBatch, + getStorageDescendants, + getChainSpecData, + destroy() { + unhodl() + }, + }, + { + mockSignatureHost: true, + disableOnIdle: true, + buildBlockMode: Enum("timer", 0), + executor: fromWorker(forkliftWorker), + }, + ), + forkliftWorker, + ] as const +} + +const decodeAddress = (address: AccountAddress) => + address.startsWith("0x") ? Binary.fromHex(address) : AccountId().enc(address)! + +export const createFakeSigner = (address: AccountAddress) => + getPolkadotSigner(decodeAddress(address), "Sr25519", () => { + // From https://wiki.acala.network/build/sdks/homa + const signature = new Uint8Array(64) + signature.fill(0xcd) + signature.set([0xde, 0xad, 0xbe, 0xef]) + return signature + }) diff --git a/src/pages/RuntimeCalls/runtimeCalls.state.ts b/src/pages/RuntimeCalls/runtimeCalls.state.ts index aa20036..a85b0f5 100644 --- a/src/pages/RuntimeCalls/runtimeCalls.state.ts +++ b/src/pages/RuntimeCalls/runtimeCalls.state.ts @@ -1,7 +1,7 @@ import { createMetadataEntryState } from "@/components/MetadataEntryInput" import { pushWorkspaceEntry, WorkspaceEntryData } from "@/components/Workspace" import { getHashParams } from "@/hashParams" -import { runtimeCtxAt$, unsafeApi$ } from "@/state/chains/chain.state" +import { genericUnsafeApi$, runtimeCtxAt$ } from "@/state/chains/chain.state" import { RuntimeContext } from "@polkadot-api/observable-client" import { state } from "@react-rxjs/core" import { ServerCog } from "lucide-react" @@ -108,7 +108,7 @@ export const runtimeCallToWorkspaceEntry = async ( query: RuntimeCallQuery, ): Promise> => { const [unsafeApi, ctx] = await firstValueFrom( - combineLatest([unsafeApi$, runtimeCtxAt$(query.blockHash)]), + combineLatest([genericUnsafeApi$, runtimeCtxAt$(query.blockHash)]), ) const query$ = from( diff --git a/src/pages/Storage/storage.state.ts b/src/pages/Storage/storage.state.ts index 3b2b464..3107152 100644 --- a/src/pages/Storage/storage.state.ts +++ b/src/pages/Storage/storage.state.ts @@ -5,10 +5,10 @@ import { getHashParams } from "@/hashParams" import { chainClient$, client$, + genericUnsafeApi$, runtimeCtx$, runtimeCtxAt$, selectedChainChanged$, - unsafeApi$, } from "@/state/chains/chain.state" import { BlockInfo, @@ -180,7 +180,7 @@ export const storageSubscriptionToWorkspaceEntry = async ({ const [ctx, unsafeApi] = await firstValueFrom( combineLatest([ blockHash ? runtimeCtxAt$(blockHash) : runtimeCtx$, - unsafeApi$, + genericUnsafeApi$, ]), ) const entry = getEntry(ctx, getStorageItem(ctx, pallet, item)!.item.type) diff --git a/src/pages/ViewFns/viewFns.state.ts b/src/pages/ViewFns/viewFns.state.ts index a15f75a..0f81c68 100644 --- a/src/pages/ViewFns/viewFns.state.ts +++ b/src/pages/ViewFns/viewFns.state.ts @@ -1,7 +1,7 @@ import { createMetadataEntryState } from "@/components/MetadataEntryInput" import { pushWorkspaceEntry, WorkspaceEntryData } from "@/components/Workspace" import { getHashParams } from "@/hashParams" -import { runtimeCtxAt$, unsafeApi$ } from "@/state/chains/chain.state" +import { genericUnsafeApi$, runtimeCtxAt$ } from "@/state/chains/chain.state" import { RuntimeContext } from "@polkadot-api/observable-client" import { shareLatest, state } from "@react-rxjs/core" import { SquareFunction } from "lucide-react" @@ -110,7 +110,7 @@ export const viewFnCallToWorkspaceEntry = async ( call: ViewFnCall, ): Promise> => { const [unsafeApi, ctx] = await firstValueFrom( - combineLatest([unsafeApi$, runtimeCtxAt$(call.blockHash)]), + combineLatest([genericUnsafeApi$, runtimeCtxAt$(call.blockHash)]), ) const query$ = from( diff --git a/src/state/chains/chain.state.ts b/src/state/chains/chain.state.ts index c64ea15..4f887e0 100644 --- a/src/state/chains/chain.state.ts +++ b/src/state/chains/chain.state.ts @@ -1,5 +1,6 @@ import { createChopsticksProvider } from "@/chopsticks/chopsticks" import { getHashParams, setHashParams } from "@/hashParams" +import { DotAh } from "@polkadot-api/descriptors" import { getDynamicBuilder, getLookupFn } from "@polkadot-api/metadata-builders" import type { ChainHead$, @@ -10,10 +11,16 @@ import { HexString, unifyMetadata, } from "@polkadot-api/substrate-bindings" -import { liftSuspense, sinkSuspense, state, SUSPENSE } from "@react-rxjs/core" +import { + liftSuspense, + sinkSuspense, + state, + StateObservable, + SUSPENSE, +} from "@react-rxjs/core" import { createSignal } from "@react-rxjs/utils" import { get, update } from "idb-keyval" -import { createClient } from "polkadot-api" +import { ChainDefinition, createClient, TypedApi } from "polkadot-api" import { withLogsRecorder } from "polkadot-api/logs-provider" import { fromHex, toHex } from "polkadot-api/utils" import { @@ -249,8 +256,11 @@ export const canSetStorage$ = state( ) export const unsafeApi$ = chainClient$.pipeState( - map(({ client }) => client.getUnsafeApi()), + map(({ client }) => client.getUnsafeApi()), ) +export const genericUnsafeApi$ = unsafeApi$ as StateObservable< + TypedApi +> const uncachedRuntimeCtx$ = chainClient$.pipeState( switchMap(({ chainHead }) => chainHead.runtime$),