diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d05493..336c3b6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,31 @@ ## 0.8.7 (2026-06-06) +### ๐Ÿš€ Features + +- **statement-store:** sign-and-submit primitives are now exported from the package index: `createExpiryAllocator` (a strictly-increasing expiry/priority source for one signing account, with a floor that adopts chain-reported minimums), `submitStatementOnce` / `signAndSubmitStatement` (allocate an expiry, prove and submit a statement, resyncing the allocator on a priority rejection), and `submitWithRetry` / `isPriorityTooLow` (a retry policy with separate budgets for transient failures and priority rejections). The session consumes the same primitives internally, and `createSession` accepts an optional shared `allocator` so several writers signing with the same account cannot tie on same-second priorities. + ### ๐Ÿฉน Fixes +- **statement-store:** reworked the session to match the iOS/Android implementations. Concurrent incoming requests are now tracked independently (an older request stays answerable after a newer one arrives), transient submit/query failures are retried with a short backoff, and message batches are sized against the full encoded request payload instead of the raw bytes. Statement expiry is pinned to a non-expiring max with a wall-clock priority so channel supersession is deterministic, and a request id is best-effort recovered from a corrupt payload so the sender can be NACKed. Adds an in-memory adapter for tests. +- **statement-store:** the statement priority (the expiry's low word) is now counted from the spec's priority epoch (2025-11-15, exported as `PRIORITY_EPOCH_OFFSET`), matching iOS and Android. Previously it was the raw Unix timestamp, which made every TS-written statement outrank mobile-written ones in cross-client priority comparisons. +- **statement-store:** priority rejections (`ExpiryTooLowError` and `AccountFullError`) no longer surface to session callers: while a submission is live the session resyncs its expiry above the chain-reported minimum and keeps retrying beyond the transient-failure cap; once the submission is superseded on its channel, the rejection is absorbed as success โ€” it merely lost the race to a newer statement, and re-answering would clobber it. +- **statement-store:** a disposed session now rejects `submitRequestMessage` / `submitResponseMessage` immediately instead of hanging, and no longer re-activates (or submits queued work) when `dispose()` lands while initialization is still in flight. +- **statement-store / host-papp:** the SSO allowance service now builds its statement-store prover from the mobile slot-account secret (`privateKey || nonce`) via the new `createSlotAccountProver`, instead of treating it as a raw sr25519 secret โ€” proofs now sign and verify against the correct slot-account public key. - **host-papp:** the authorising device's encryption public key (`deviceEncPubKey`) from the V2 handshake response is now persisted on `StoredUserSession` and exposed on the session, so the host can ECDH-address the paired device (e.g. for device-sync). It was previously decoded but dropped on persistence, leaving consumers to mis-read the 32-byte SSO shared secret in `remoteAccount.publicKey` as a public key. +- **host-papp:** the SSO request size limit was raised from 254 KiB to 500 KiB, tracking the mobile statement allowance. +- bumped `polkadot-api` to 2.1.6, which fixes a double-notification bug. -### โš ๏ธ Breaking Changes +### ๐Ÿก Chore + +- every published package now ships a `LICENSE` file, and a repo-wide `THIRD_PARTY_NOTICES.md` records dependency licenses. +- added a `SECURITY.md` policy and hardened `.env` handling in `.gitignore`. -- **host-papp:** `StoredUserSession` gained a required `deviceEncPubKey` field (65-byte uncompressed P-256). Sessions persisted before this change do not decode and are not migrated โ€” a fresh SSO handshake (re-pairing / reset app data) is required after upgrading. +### โค๏ธ Thank You + +- Ilya Kalinin +- PG Herveou @pgherveou +- Sergey Zhuravlev @johnthecat +- Yanaty ## 0.8.6 (2026-06-05) diff --git a/package-lock.json b/package-lock.json index d992f6d3..443d64f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,8 @@ "@nx/js": "22.7.5", "@nx/storybook": "22.7.5", "@nx/vite": "22.7.5", - "@storybook/react-vite": "10.4.1", - "@types/node": "25.9.1", + "@storybook/react-vite": "10.4.4", + "@types/node": "25.9.3", "@types/ws": "8.18.1", "@vitejs/plugin-react": "5.2.0", "babel-plugin-react-compiler": "1.0.0", @@ -30,15 +30,15 @@ "globals": "17.6.0", "husky": "9.1.7", "jiti": "2.7.0", - "knip": "6.14.2", + "knip": "6.16.1", "nanoevents": "9.1.0", "nx": "22.7.5", - "prettier": "3.8.3", - "storybook": "10.4.1", + "prettier": "3.8.4", + "storybook": "10.4.4", "ts-node": "10.9.2", "tslib": "2.8.1", "typescript": "6.0.3", - "typescript-eslint": "8.60.0", + "typescript-eslint": "8.61.0", "vite": "7.3.2", "vite-plugin-dts": "5.0.2", "vite-plugin-externalize-deps": "0.10.0", @@ -2820,9 +2820,9 @@ "link": true }, "node_modules/@novasamatech/tr-ui": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@novasamatech/tr-ui/-/tr-ui-0.2.9.tgz", - "integrity": "sha512-HwExQ1Y6lh5fIdbxtjm8XspfiUaISBEHYsw8X+bVM7z2CZOfDJy+AlMw5QODU6/qpvpleYydFs/K2cV78pwCaA==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@novasamatech/tr-ui/-/tr-ui-0.2.11.tgz", + "integrity": "sha512-qiC2P5486WfK5l01/JbwVeiyRAzgkrWUJnR+qyKQpXuBR90Ijs5tOxrt2UgpClvUPQrpo6B4rn1i7+ibjz9CDg==", "license": "Apache-2.0", "dependencies": { "@fontsource/inter": "^5", @@ -3262,9 +3262,9 @@ } }, "node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.130.0.tgz", - "integrity": "sha512-h/xYU8/7ADWzVSf5I+YalLpj33LOy9CI/zgbJNIZ5eunRBG+Czqa3lZsvuPHHf3rOt6z1c5+UzoxjbAzAvhwVw==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.133.0.tgz", + "integrity": "sha512-l/44caGse+VpnY9gx0yvvc5QnnG3yG1FO3KZgYvNL1GZrfK86zIwAOgGEVlxDyRymzrU/KHiblPFpevKOmJmUA==", "cpu": [ "arm" ], @@ -3279,9 +3279,9 @@ } }, "node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.130.0.tgz", - "integrity": "sha512-oFWFJrsGv9siFM4HjMqKNB7IuIZD/SMmZdCXl8xyx7lDplGvPKyewpOo272rSWgMXe2Wx7bWI0Yj+gkHv4qbeg==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.133.0.tgz", + "integrity": "sha512-KUHmPMziLBp4u+zbrLdB7iWS7KshuZe+RAp7ELnY9SI9nNXBZ+dp8fiBqWOxhXqn+FQg3a4UcQhwmsJOKV8Jjg==", "cpu": [ "arm64" ], @@ -3296,9 +3296,9 @@ } }, "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.130.0.tgz", - "integrity": "sha512-sGUzupdTplK9jQg7eJZ878HfEgQjJNBc6dAYVWJ9W5aU+J8rLfRJhTVsKThiu1pNwm6Y1qKCcbC6WhNWSXR3Ig==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.133.0.tgz", + "integrity": "sha512-q8dWmnU/8ea2tga9w2f1PinQ5rcMPDUGkF64T189b65YMjUomET4oy5oRldOr4AwOQkneOG/Zttnz1Dvrc62wg==", "cpu": [ "arm64" ], @@ -3313,9 +3313,9 @@ } }, "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.130.0.tgz", - "integrity": "sha512-PsB4cdCISbC00Uy8eiD8bc2AkGWjZqrSrJnkBFuG2ptrrf6mZ2F5gLFSjOAVMMgZPg8B1D7OydJwLWSfyI2Plg==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.133.0.tgz", + "integrity": "sha512-cOKeIELIB2bJnCKwqx4Rdj+1Lss/U6uCbLxRySZrhyOOQa1flKhwZFjEHRHxk8fU1NKmhK5OnTdPQ4CpjuFuVw==", "cpu": [ "x64" ], @@ -3330,9 +3330,9 @@ } }, "node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.130.0.tgz", - "integrity": "sha512-DgABp3l38hS77JbXCV4qk1+n6DPym5u8zzwuweokezm2tX194nDSJDENbDRECxVsiNbprKATLbk+Z5wlHT0OHw==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.133.0.tgz", + "integrity": "sha512-OpaSv4pW3KgFrMYQxTaS0aOE4T1DQF3qZE/4B6uqqv1KgPWWd4UQhJALi8PJPX1RRV5K7ThKXRfF7qGg2+3l1A==", "cpu": [ "x64" ], @@ -3347,9 +3347,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.130.0.tgz", - "integrity": "sha512-4Kn3CTEmwFrzhTSC/JuUW16qovmaMdX7jeSKbL8w0pLtLww7To1a2XJi9Z5uD8QWUkfUHhqfV+VD6dVzBnWzoA==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.133.0.tgz", + "integrity": "sha512-JGK1wlGrGwxBIlVSF7KWTX1/ru6BEtf28fRROztDRkLfiW+Kxa4onnriezMIiogfn9hVw2KzYcKiLjkLR2ns8A==", "cpu": [ "arm" ], @@ -3364,9 +3364,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.130.0.tgz", - "integrity": "sha512-D35KZM3F4rRu1uAFKyBlg3Gaf/ybCjyaPR1hfgvk5ex8NtcTmRgc0JgSighEyNg96TPrFhemFba68SZuxaha8w==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.133.0.tgz", + "integrity": "sha512-yuZO533Ftonxn/iyoqQzURzLQHMspvsIyfiCSNi1t/ER4eIQaR0SsmUOUm5b/lmSig7IWIUa5/BrbEkAPwcilQ==", "cpu": [ "arm" ], @@ -3381,13 +3381,16 @@ } }, "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.130.0.tgz", - "integrity": "sha512-Q9o7oVlo955KHwS8l1u0bCzIx+JsZUA3XToLXC+MsMhye/9LeBQbt84nh120cl2XLy+TEzvugYDiHShg5yaX6Q==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.133.0.tgz", + "integrity": "sha512-hvpbqT5pN2rR+3+xtWeizwfR/aZ0vGceg6TqYMl+ToxMpk9/tmnX7kSvQnfEUkoua8mhogzvIKsAkn0wxgblBA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3398,13 +3401,16 @@ } }, "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.130.0.tgz", - "integrity": "sha512-EiJ/gC0ljbcwVpycC8YWw6ggMbtsPX8XMOt0mPx0aqWeMsNR+L9m05Flbvd5T+GlivG+GkSWQL7tM9SRFpM/dw==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.133.0.tgz", + "integrity": "sha512-wJQGamIosQBoJHW9+S5XxrtKRo3eyJxsnS1XCPrqN0LHi8uw1pTqqTfn3t/NVuvbBg7Pumn4ez9Eidgcn0xbEg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3415,13 +3421,16 @@ } }, "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.130.0.tgz", - "integrity": "sha512-b+h/lsLLurp756dMGizNs5uPaJfyEdWrTcV5t8M609jWm1DEHB1StpRXCkyvwtkJx3m+qL5BNQ0dEKan/4yGFA==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.133.0.tgz", + "integrity": "sha512-Koaz32/O5+abIfrNGdyndgRvdOZ9jEf5/z3Ep9h3h2QWpdDiUQpVwgH0OcMXCs+l9aXxPLtkupqyVig9W6FDKw==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3432,13 +3441,16 @@ } }, "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.130.0.tgz", - "integrity": "sha512-O19Cil83XAyjEFfo8WhkMwY58ALqZ7ckjGL+25mjMIuF84urWBeANH0FC8B8BsSSygWU3/1aY3ADdDbp+wlBnw==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.133.0.tgz", + "integrity": "sha512-R4vOjWzxhnNWHnVLeiB6jNuIifdy9vcMXZGPc7StXcxBovI+U2zg1QhZ9o8OjV80oGivs1lX5NfPLzk4IPqlRA==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3449,13 +3461,16 @@ } }, "node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.130.0.tgz", - "integrity": "sha512-BgXRVC0+83n3YzCscLQjj6nbyeBIVeZYPTI4fFMAE4WNm2+4RXhWp03IVizL7esIz36kgmT48aebk1iM+cs8sw==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.133.0.tgz", + "integrity": "sha512-iwgBNUTHiMdxARLYuM0SBlnYeb19iw1Ea5M+4ERZupCsBMLArti6FyZ6UfFjJxIiTDr2oW2DGQFxlQVQ/dW9rA==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3466,13 +3481,16 @@ } }, "node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.130.0.tgz", - "integrity": "sha512-6tJz0xvnGhsokE7N1WlUSBXibpYmT9xSJFS1Ce41Km/+8gQvdlW8MLhRv8PD0L7ix8vRG0FDDepp3jdOFzdVdw==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.133.0.tgz", + "integrity": "sha512-ZwZNo8FZmB/gVfboQl+wXilBigGl+6nQQs+nITOeAP/HcAOjiHl6XZJL9F/KXNEspODQcbjAiyjUbeCJd9a0fA==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3483,13 +3501,16 @@ } }, "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.130.0.tgz", - "integrity": "sha512-9aCWj83dp3heTQGmGnZGdIWgxjZrr/7VQ0TGFHH5PKByxJKF2Hcr4qvaSUHhhGEa3MSsDjTL1YDP8RAgdL5/Cg==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.133.0.tgz", + "integrity": "sha512-govCvWx1dBlED3uu4qXctxpRcouu9I8Kn+DBktGCl760JtlGJzc9l/OmPJKlYWSbrRqKkMZehNeZ/4Wfma7uSA==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3500,13 +3521,16 @@ } }, "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.130.0.tgz", - "integrity": "sha512-afXt87aZBqrUVli8TB/I8H1G50RDWcwirjWtXGXYqJ2ZqWEiErH7V72j3LUSDZaivmtu2OLX0KQ/mbhP81mr7A==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.133.0.tgz", + "integrity": "sha512-ssTlpXD5Mq9uCssDJPzlRWqBt4Y7Zzd9i+XZhWmK/9Y6KUIuAxVYTYiI8lxcGWi0+3/Cz4A8q9UrD4NK9Y2j7g==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3517,9 +3541,9 @@ } }, "node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.130.0.tgz", - "integrity": "sha512-I0NCrZV/YZuCGWgqwNN/GO/iXlLF2z+Wgc7u+Aa9N4P51oYeIa0XT+zVBUne4csO9GqxskXgI4g8JzzWGRpfOw==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.133.0.tgz", + "integrity": "sha512-51aByfXhPtLEdWG4a2Ihdw6cPWV1ei1AarALpFdDP8MLWDLE2NuUMgbo3DERR2Kt8fT/ok1GUvBiLxVGke9uUQ==", "cpu": [ "arm64" ], @@ -3534,9 +3558,9 @@ } }, "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.130.0.tgz", - "integrity": "sha512-sJgQkGaBX0WJvPUDfwciex6IcTk5O5NLQ1bhEb6f3nBruh1GshKMRSMt2bxZlYrgBzjyBbJzsnO+InPG0bg+fA==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.133.0.tgz", + "integrity": "sha512-2e16tkKp+wDO2GTAmXfxbBcCmGEaFPIJEIRBBmVKNVXSc8/fJsSIaBGyFTPHM9ST5GNWgJcYIt94rDTks+PLwA==", "cpu": [ "wasm32" ], @@ -3553,14 +3577,14 @@ } }, "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.2" }, "funding": { "type": "github", @@ -3583,9 +3607,9 @@ } }, "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.130.0.tgz", - "integrity": "sha512-bjcma99sQrNh6RY4mPO9yTkfxql6TDFoN3HWdK31RCKXwNhcDgJXW/l8PUtzKNiQ+9vpKJfJtQq+LklBuxSOBA==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.133.0.tgz", + "integrity": "sha512-KPTNDKbxH1cglrqTyVeXHb4Pk4oksz8EcE1/v8zqU7N4UXbiHfA/IwtXZ2U77fnRAWBbgVkl/lZbL7o3hRdejg==", "cpu": [ "arm64" ], @@ -3600,9 +3624,9 @@ } }, "node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.130.0.tgz", - "integrity": "sha512-hRYbv6HhpSTzT4xTiIkadLI7upLQxuOdLPR/9nL1fTjwhgutBTPXrwaAPb/jTFVx6/8C7Jb5HcUKhmNwloTbFA==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.133.0.tgz", + "integrity": "sha512-Una1bNYv9zCavQrfnDR9wuZVB3itLjCEH4Oz7i6CwAJN/Xq9b+zbbcxmvdkKvvJt4Ngc/MBmIYlbLo3zS4TQ0A==", "cpu": [ "ia32" ], @@ -3617,9 +3641,9 @@ } }, "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.130.0.tgz", - "integrity": "sha512-RBpA9TsRucJq6HNVNCFF1iKg+QeTkLdZf7hi4xaOGCPvMZWvDHjQgSOEZMUpuW4JNciHbxNhLEYmz5CVygjVGQ==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.133.0.tgz", + "integrity": "sha512-kjBhCiOGSYTwDJQuuZa7a94JbP8htWu7J0X1KwH74kV2K5eYf6eyJRYmkpCDvr0XEL8tMxYI4WU1VekblFCLgg==", "cpu": [ "x64" ], @@ -3634,9 +3658,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", - "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -3644,9 +3668,9 @@ } }, "node_modules/@oxc-resolver/binding-android-arm-eabi": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.19.1.tgz", - "integrity": "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.20.0.tgz", + "integrity": "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg==", "cpu": [ "arm" ], @@ -3658,9 +3682,9 @@ ] }, "node_modules/@oxc-resolver/binding-android-arm64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.19.1.tgz", - "integrity": "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.20.0.tgz", + "integrity": "sha512-QqslZAuFQG8Q9xm7JuIn8JUbvywhSBMVhuQHtYW+auirZJloS41oxUUaBXk7uUhZJgp44c5zQLeVvmFaDQB+2Q==", "cpu": [ "arm64" ], @@ -3672,9 +3696,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.19.1.tgz", - "integrity": "sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.20.0.tgz", + "integrity": "sha512-MUcavykj2ewlR+kc5arpg4tC2RvzJkUxWtNv74pf7lcNk00GpIpN43vXMj+j6r4eMmfZhlb8hueKoIb8e9kAGQ==", "cpu": [ "arm64" ], @@ -3686,9 +3710,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.19.1.tgz", - "integrity": "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.20.0.tgz", + "integrity": "sha512-BGB16nRUK5Etiv//ihPyzj8Lj1px0mhh4YIfe0FDf045ywknfSm0GEbiRESpr6Q4K82AvnyaRIhhluHByvS4bg==", "cpu": [ "x64" ], @@ -3700,9 +3724,9 @@ ] }, "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.19.1.tgz", - "integrity": "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.20.0.tgz", + "integrity": "sha512-JZgtePaqj3qmD5XFHJaSLWzHRxQu0LaPkdoM1KJXYADvAaa83ijXHclV3ej3CueeW0wxfIAbGCZVP45J0CA7uQ==", "cpu": [ "x64" ], @@ -3714,9 +3738,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.19.1.tgz", - "integrity": "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.20.0.tgz", + "integrity": "sha512-hOQ/p3ry3v3SchUBXicrrnszaI/UmYzM4wtS4RGfwgVUX7a+HbyQSzJ5aOzu+o6XZkFkS3ZXN4PZAzhOb77OSg==", "cpu": [ "arm" ], @@ -3728,9 +3752,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.19.1.tgz", - "integrity": "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.20.0.tgz", + "integrity": "sha512-2ArPksaw0AqeuGBfoS715VF+JvJQAhD2niWgjE5hVO+L+nAfikVQopvngCMX9x4BD8itWoQ3dnikrQyl5Ho5Jg==", "cpu": [ "arm" ], @@ -3742,13 +3766,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.19.1.tgz", - "integrity": "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.20.0.tgz", + "integrity": "sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3756,13 +3783,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.19.1.tgz", - "integrity": "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.20.0.tgz", + "integrity": "sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3770,13 +3800,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.19.1.tgz", - "integrity": "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.20.0.tgz", + "integrity": "sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3784,13 +3817,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.19.1.tgz", - "integrity": "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.20.0.tgz", + "integrity": "sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3798,13 +3834,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.19.1.tgz", - "integrity": "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.20.0.tgz", + "integrity": "sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3812,13 +3851,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.19.1.tgz", - "integrity": "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.20.0.tgz", + "integrity": "sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3826,13 +3868,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.19.1.tgz", - "integrity": "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.20.0.tgz", + "integrity": "sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3840,13 +3885,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.19.1.tgz", - "integrity": "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.20.0.tgz", + "integrity": "sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3854,9 +3902,9 @@ ] }, "node_modules/@oxc-resolver/binding-openharmony-arm64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.19.1.tgz", - "integrity": "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.20.0.tgz", + "integrity": "sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ==", "cpu": [ "arm64" ], @@ -3868,9 +3916,9 @@ ] }, "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.19.1.tgz", - "integrity": "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.20.0.tgz", + "integrity": "sha512-Tn0y1XOFYHNfK1wp1Z5QK8Rcld/bsOwRISQXfqAZ5IBpv8Gz1IvV39fUWNprqNdRizgcvFhOzWwFun2zkJsyBg==", "cpu": [ "wasm32" ], @@ -3878,21 +3926,23 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.2" }, "funding": { "type": "github", @@ -3904,9 +3954,9 @@ } }, "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -3915,9 +3965,9 @@ } }, "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.19.1.tgz", - "integrity": "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.20.0.tgz", + "integrity": "sha512-qPi25YNPe4YenS8MgsQU2+bIFHxxpLx1LVna2444cEHqNPhNjvWf9zqj4aWE43H9LpAsTmkkAlA3eL5ElBU3mA==", "cpu": [ "arm64" ], @@ -3928,24 +3978,10 @@ "win32" ] }, - "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.19.1.tgz", - "integrity": "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.19.1.tgz", - "integrity": "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.20.0.tgz", + "integrity": "sha512-Wb14jWEW8huH6It9F6sXd9vrYmIS7pMrgkU6sxpLxkP+9z+wRgs71hUEhRpcn8FOXAFa27FVWfY2tRpbfTzfLw==", "cpu": [ "x64" ], @@ -5029,9 +5065,9 @@ } }, "node_modules/@polkadot-labs/schnorrkel-wasm": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@polkadot-labs/schnorrkel-wasm/-/schnorrkel-wasm-0.0.8.tgz", - "integrity": "sha512-N5XuBPsabi2XLJiazwnMH7J3DrK2Zkv7P4T6SEebTsOrvONyDX8Ort2MutxPtJgTviRHZMs2xc+FmqDqDRCECA==", + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@polkadot-labs/schnorrkel-wasm/-/schnorrkel-wasm-0.0.9.tgz", + "integrity": "sha512-MWO28z29OgKihdXjybvbuECASAIu52KZfBMFNZtK7jeyoFvA8ZrpJzYxPgFaBIsk1hHM488q7EXHckaMnZ1xng==", "license": "MIT" }, "node_modules/@polkadot/api": { @@ -7527,13 +7563,13 @@ "license": "MIT" }, "node_modules/@storybook/builder-vite": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.4.1.tgz", - "integrity": "sha512-/oyQrXoNOqN8SW5hNnYP+I1uvgFxKxWXj/EP6NXYzc5SQwImofgru+D2+6gDhL0+Q//+Hx05DJoQO2omvUJ8bQ==", + "version": "10.4.4", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.4.4.tgz", + "integrity": "sha512-VyuZ4mEvhhVXjJa1qXMWKH8ohnas0rgEuJDf6u4aJ54XeENFebPUEAHde1Qo2PflJ4rUdVdXieOZzKbYwP5RAQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.4.1", + "@storybook/csf-plugin": "10.4.4", "ts-dedent": "^2.0.0" }, "funding": { @@ -7541,14 +7577,14 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.4.1", + "storybook": "^10.4.4", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@storybook/csf-plugin": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.4.1.tgz", - "integrity": "sha512-WdPepGBxDGOUDjYd8KxMtcf+us/2PAcnBczl77XtrnxxHNs0jWesxKkiJ9yiuGrge4BPhDeAj6rxjbBoaHxLBA==", + "version": "10.4.4", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.4.4.tgz", + "integrity": "sha512-1mzZyAwVUmAcw4WEUsJDVdSupkJf+Kf/f5uNAs4RzlBXA75P8YRkDKAb2EoMwsB5URiXFi9XoeAN/vWke0G6+w==", "dev": true, "license": "MIT", "dependencies": { @@ -7561,7 +7597,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.4.1", + "storybook": "^10.4.4", "vite": "*", "webpack": "*" }, @@ -7599,14 +7635,14 @@ } }, "node_modules/@storybook/react": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.4.1.tgz", - "integrity": "sha512-WuYz4NaUk4gmFAMliSpCbV8w6jP5OY9juBfw1huwzu2S/k5FhnVXwmrUaL0fmf3Bq/7NgkzmBBbZr6I6LuHayQ==", + "version": "10.4.4", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.4.4.tgz", + "integrity": "sha512-6K5/uHrvjswrueyVpUt6IWGuSgYCMtMOYyVs86XJZYqKBV3Pv7nGsGNH7YSMLAVQBZW4CQqm2etd5Op0GHY9Kg==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.4.1", + "@storybook/react-dom-shim": "10.4.4", "react-docgen": "^8.0.2", "react-docgen-typescript": "^2.2.2" }, @@ -7619,7 +7655,7 @@ "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.4.1", + "storybook": "^10.4.4", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -7635,9 +7671,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.4.1.tgz", - "integrity": "sha512-6QFqfDNH4DMrt7yHKRfpqRopsVUc/Az+sXIdJ39IetYnHUxL3nW4NVaPc6uy/8Qi8urzUyEXL/nn7cpSIP2aPQ==", + "version": "10.4.4", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.4.4.tgz", + "integrity": "sha512-y6SObmoW78AydE6VfKQSUmCkuqiaMPy9LgMpMdMEyWfJ/pSxBDMIKycr9dlRMJP1cvNgByaJgrusWtA46ndSQw==", "dev": true, "license": "MIT", "funding": { @@ -7649,7 +7685,7 @@ "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.4.1" + "storybook": "^10.4.4" }, "peerDependenciesMeta": { "@types/react": { @@ -7661,16 +7697,16 @@ } }, "node_modules/@storybook/react-vite": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.4.1.tgz", - "integrity": "sha512-zY6OzaXvXqBIUyc5ySE55/LAPQiF+o9ZyhQI978WMu4mY/fL7FpQ+ZVHRUCCgz/wTXtqE9jJwd/N10HI1kD0/Q==", + "version": "10.4.4", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.4.4.tgz", + "integrity": "sha512-hXw1c9Jq2eFzwmJ3u9phmszbHoPjwPLYjcR1Grd6Xbe2g3bReGH35urm/fTZ0HNdjXAgQlUaXp2bWw6vz0BHQw==", "dev": true, "license": "MIT", "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.7.0", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.4.1", - "@storybook/react": "10.4.1", + "@storybook/builder-vite": "10.4.4", + "@storybook/react": "10.4.4", "empathic": "^2.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", @@ -7684,7 +7720,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.4.1", + "storybook": "^10.4.4", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, @@ -8145,9 +8181,9 @@ } }, "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -8197,9 +8233,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.15", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", - "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -8265,17 +8301,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", - "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz", + "integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/type-utils": "8.60.0", - "@typescript-eslint/utils": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/type-utils": "8.61.0", + "@typescript-eslint/utils": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -8288,7 +8324,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.60.0", + "@typescript-eslint/parser": "^8.61.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -8304,16 +8340,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", - "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz", + "integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3" }, "engines": { @@ -8329,14 +8365,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", - "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz", + "integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.60.0", - "@typescript-eslint/types": "^8.60.0", + "@typescript-eslint/tsconfig-utils": "^8.61.0", + "@typescript-eslint/types": "^8.61.0", "debug": "^4.4.3" }, "engines": { @@ -8351,14 +8387,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", - "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz", + "integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0" + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8369,9 +8405,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", - "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz", + "integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==", "dev": true, "license": "MIT", "engines": { @@ -8386,15 +8422,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", - "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz", + "integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -8411,9 +8447,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", - "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz", + "integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==", "dev": true, "license": "MIT", "engines": { @@ -8425,16 +8461,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", - "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz", + "integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.60.0", - "@typescript-eslint/tsconfig-utils": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/project-service": "8.61.0", + "@typescript-eslint/tsconfig-utils": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -8453,16 +8489,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", - "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz", + "integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0" + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8477,13 +8513,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", - "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz", + "integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/types": "8.61.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -12346,9 +12382,9 @@ } }, "node_modules/knip": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/knip/-/knip-6.14.2.tgz", - "integrity": "sha512-Vg3JhIINjZew1I7qAFI4UHemW1mc4azP/BxJvsq9eGDfxpGO7oVCuD/bsWkog9TO/ZwwJeAeOMFZ1kd9jnY9+Q==", + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.16.1.tgz", + "integrity": "sha512-TKMn1rxgH6h9vXR9Y0B+Cq7AdPTr9EI02IwoT65NzqYUkvoDQAaJ/aPybiFpAhZ1px6cNYYwXf86iHkBgzCo9w==", "dev": true, "funding": [ { @@ -12366,9 +12402,8 @@ "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.7.0", - "minimist": "^1.2.8", - "oxc-parser": "^0.130.0", - "oxc-resolver": "^11.19.1", + "oxc-parser": "^0.133.0", + "oxc-resolver": "^11.20.0", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", @@ -13348,13 +13383,13 @@ } }, "node_modules/oxc-parser": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.130.0.tgz", - "integrity": "sha512-X0PJ+NmOok8qP3vK9uaW431ngkdM9UPEK7KG466urtIL2+EYTEgbZK2yqe2MWKJKBjRlFweP/pJPx0x9muMEVw==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.133.0.tgz", + "integrity": "sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.130.0" + "@oxc-project/types": "^0.133.0" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -13363,58 +13398,57 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.130.0", - "@oxc-parser/binding-android-arm64": "0.130.0", - "@oxc-parser/binding-darwin-arm64": "0.130.0", - "@oxc-parser/binding-darwin-x64": "0.130.0", - "@oxc-parser/binding-freebsd-x64": "0.130.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.130.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.130.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.130.0", - "@oxc-parser/binding-linux-arm64-musl": "0.130.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.130.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.130.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.130.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.130.0", - "@oxc-parser/binding-linux-x64-gnu": "0.130.0", - "@oxc-parser/binding-linux-x64-musl": "0.130.0", - "@oxc-parser/binding-openharmony-arm64": "0.130.0", - "@oxc-parser/binding-wasm32-wasi": "0.130.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.130.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.130.0", - "@oxc-parser/binding-win32-x64-msvc": "0.130.0" + "@oxc-parser/binding-android-arm-eabi": "0.133.0", + "@oxc-parser/binding-android-arm64": "0.133.0", + "@oxc-parser/binding-darwin-arm64": "0.133.0", + "@oxc-parser/binding-darwin-x64": "0.133.0", + "@oxc-parser/binding-freebsd-x64": "0.133.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.133.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.133.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.133.0", + "@oxc-parser/binding-linux-arm64-musl": "0.133.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.133.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.133.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.133.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.133.0", + "@oxc-parser/binding-linux-x64-gnu": "0.133.0", + "@oxc-parser/binding-linux-x64-musl": "0.133.0", + "@oxc-parser/binding-openharmony-arm64": "0.133.0", + "@oxc-parser/binding-wasm32-wasi": "0.133.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.133.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.133.0", + "@oxc-parser/binding-win32-x64-msvc": "0.133.0" } }, "node_modules/oxc-resolver": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.19.1.tgz", - "integrity": "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.20.0.tgz", + "integrity": "sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-resolver/binding-android-arm-eabi": "11.19.1", - "@oxc-resolver/binding-android-arm64": "11.19.1", - "@oxc-resolver/binding-darwin-arm64": "11.19.1", - "@oxc-resolver/binding-darwin-x64": "11.19.1", - "@oxc-resolver/binding-freebsd-x64": "11.19.1", - "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", - "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", - "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", - "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", - "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", - "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", - "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", - "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", - "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", - "@oxc-resolver/binding-linux-x64-musl": "11.19.1", - "@oxc-resolver/binding-openharmony-arm64": "11.19.1", - "@oxc-resolver/binding-wasm32-wasi": "11.19.1", - "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", - "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", - "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" + "@oxc-resolver/binding-android-arm-eabi": "11.20.0", + "@oxc-resolver/binding-android-arm64": "11.20.0", + "@oxc-resolver/binding-darwin-arm64": "11.20.0", + "@oxc-resolver/binding-darwin-x64": "11.20.0", + "@oxc-resolver/binding-freebsd-x64": "11.20.0", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.20.0", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.20.0", + "@oxc-resolver/binding-linux-arm64-gnu": "11.20.0", + "@oxc-resolver/binding-linux-arm64-musl": "11.20.0", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.20.0", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.20.0", + "@oxc-resolver/binding-linux-riscv64-musl": "11.20.0", + "@oxc-resolver/binding-linux-s390x-gnu": "11.20.0", + "@oxc-resolver/binding-linux-x64-gnu": "11.20.0", + "@oxc-resolver/binding-linux-x64-musl": "11.20.0", + "@oxc-resolver/binding-openharmony-arm64": "11.20.0", + "@oxc-resolver/binding-wasm32-wasi": "11.20.0", + "@oxc-resolver/binding-win32-arm64-msvc": "11.20.0", + "@oxc-resolver/binding-win32-x64-msvc": "11.20.0" } }, "node_modules/p-cancelable": { @@ -13881,9 +13915,9 @@ } }, "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", "dev": true, "license": "MIT", "bin": { @@ -14201,9 +14235,9 @@ } }, "node_modules/react": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", - "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14265,15 +14299,15 @@ } }, "node_modules/react-dom": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", - "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.6" + "react": "^19.2.7" } }, "node_modules/react-is": { @@ -15106,9 +15140,9 @@ } }, "node_modules/storybook": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.4.1.tgz", - "integrity": "sha512-V1Zd2e+gBFufqAQVZ1JR8KLqALsEZ3JYSBnWwQbKa6zCfWWanR6AFMyuOkLt2gZOgGp3h2Riuz88pGNVTQSG0A==", + "version": "10.4.4", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.4.4.tgz", + "integrity": "sha512-Nn0qFRxU5fyABa6dGRftfL3lz0Y+HkKOaAkfytF8S4Q2K6Szwwq7TwPAEs3Wsj8hBQbYhsobrKADcPsyXQpJaA==", "dev": true, "license": "MIT", "dependencies": { @@ -16056,9 +16090,9 @@ } }, "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", "dev": true, "license": "MIT", "engines": { @@ -16184,16 +16218,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", - "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.0.tgz", + "integrity": "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.60.0", - "@typescript-eslint/parser": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0" + "@typescript-eslint/eslint-plugin": "8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -17295,7 +17329,7 @@ }, "packages/handoff-service": { "name": "@novasamatech/handoff-service", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { "@noble/ciphers": "2.2.0", @@ -17308,10 +17342,10 @@ }, "packages/host-api": { "name": "@novasamatech/host-api", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { - "@novasamatech/scale": "0.8.7-0", + "@novasamatech/scale": "0.8.7", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", @@ -17320,10 +17354,10 @@ }, "packages/host-api-wrapper": { "name": "@novasamatech/host-api-wrapper", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { - "@novasamatech/host-api": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", "@polkadot-api/json-rpc-provider-proxy": "^0.4.0", "@polkadot-api/substrate-bindings": "^0.20.3", "@polkadot/extension-inject": "^0.63.1", @@ -17363,12 +17397,12 @@ }, "packages/host-chat": { "name": "@novasamatech/host-chat", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { - "@novasamatech/scale": "0.8.7-0", - "@novasamatech/statement-store": "0.8.7-0", - "@novasamatech/storage-adapter": "0.8.7-0", + "@novasamatech/scale": "0.8.7", + "@novasamatech/statement-store": "0.8.7", + "@novasamatech/storage-adapter": "0.8.7", "nanoid": "5.1.11", "neverthrow": "^8.2.0" } @@ -17393,11 +17427,11 @@ }, "packages/host-container": { "name": "@novasamatech/host-container", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { "@noble/hashes": "2.2.0", - "@novasamatech/host-api": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", "@polkadot-api/substrate-client": "^0.7.0", "nanoevents": "9.1.0", "nanoid": "5.1.11", @@ -17428,16 +17462,16 @@ }, "packages/host-papp": { "name": "@novasamatech/host-papp", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { "@noble/ciphers": "2.2.0", "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", - "@novasamatech/host-api": "0.8.7-0", - "@novasamatech/scale": "0.8.7-0", - "@novasamatech/statement-store": "0.8.7-0", - "@novasamatech/storage-adapter": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", + "@novasamatech/scale": "0.8.7", + "@novasamatech/statement-store": "0.8.7", + "@novasamatech/storage-adapter": "0.8.7", "@polkadot-api/utils": "^0.4.0", "@polkadot-labs/hdkd-helpers": "^0.0.30", "nanoevents": "9.1.0", @@ -17450,24 +17484,24 @@ }, "packages/host-papp-react-ui": { "name": "@novasamatech/host-papp-react-ui", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { - "@novasamatech/host-papp": "0.8.7-0", - "@novasamatech/statement-store": "0.8.7-0", - "@novasamatech/tr-ui": "0.2.9", + "@novasamatech/host-papp": "0.8.7", + "@novasamatech/statement-store": "0.8.7", + "@novasamatech/tr-ui": "^0.2.11", "@polkadot-api/utils": "^0.4.0", - "@radix-ui/react-dialog": "1.1.15", - "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-dialog": "1.1.16", + "@radix-ui/react-popover": "1.1.16", "qrcode": "^1.5.4" }, "devDependencies": { "@polkadot-api/substrate-bindings": "^0.20.3", "@types/qrcode": "^1.5.6", - "@types/react": "19.2.15", + "@types/react": "19.2.17", "@types/react-dom": "19.2.3", - "react": "19.2.6", - "react-dom": "19.2.6", + "react": "19.2.7", + "react-dom": "19.2.7", "typescript-plugin-css-modules": "5.2.0", "vite-plugin-wasm": "3.6.0" }, @@ -17489,6 +17523,470 @@ "scale-ts": "^1.6.1" } }, + "packages/host-papp-react-ui/node_modules/@radix-ui/primitive": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", + "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", + "license": "MIT" + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-arrow": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.9.tgz", + "integrity": "sha512-yqHW5WQ/cTpU/un7dqqIKNy2iRU8BC0JB78PEzTfCCYvZu1U6W9KwObAniMk9nhSfyotKPQTYaUD/HB0f5muig==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-context": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", + "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-dialog": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.16.tgz", + "integrity": "sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.12", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.9", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-portal": "1.1.11", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-slot": "1.2.5", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.12.tgz", + "integrity": "sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-escape-keydown": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", + "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.9.tgz", + "integrity": "sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-popover": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.16.tgz", + "integrity": "sha512-8brVpAU5Uq7Bh0c8EFc4ZTf2JJTYn0o+1L+CUJB3UYIOkTjKGMgoHvduylrahdmNlr3DfH0rFq2DrbNZXgaspw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.12", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.9", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.0", + "@radix-ui/react-portal": "1.1.11", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-slot": "1.2.5", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-popper": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.0.tgz", + "integrity": "sha512-9PB589e1aWZbrlFUHdz6WiPCL+xLZHQFX7oibqG/6Q0SwOkxDyQX9W/cyPa+sAPPKuC8cpLCpRczE5a/1DiwVQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.9", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-rect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-portal": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.11.tgz", + "integrity": "sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", + "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-primitive": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.5.tgz", + "integrity": "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-slot": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.5.tgz", + "integrity": "sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.2.tgz", + "integrity": "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", + "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/react-use-size": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", + "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "packages/host-papp-react-ui/node_modules/@radix-ui/rect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", + "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", + "license": "MIT" + }, "packages/host-papp/node_modules/nanoid": { "version": "5.1.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz", @@ -17509,10 +18007,10 @@ }, "packages/host-substrate-chain-connection": { "name": "@novasamatech/host-substrate-chain-connection", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { - "@novasamatech/storage-adapter": "0.8.7-0", + "@novasamatech/storage-adapter": "0.8.7", "@polkadot-api/json-rpc-provider": "^0.2.0", "@polkadot-api/json-rpc-provider-proxy": "^0.4.0", "@polkadot-api/ws-provider": "^0.9.0", @@ -17522,31 +18020,31 @@ }, "packages/host-worker-sandbox": { "name": "@novasamatech/host-worker-sandbox", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { - "@novasamatech/host-api": "0.8.7-0", - "@novasamatech/host-container": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", + "@novasamatech/host-container": "0.8.7", "quickjs-emscripten": "0.32.0" } }, "packages/product-bulletin": { "name": "@novasamatech/product-bulletin", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { - "@novasamatech/host-api-wrapper": "0.8.7-0", + "@novasamatech/host-api-wrapper": "0.8.7", "@parity/bulletin-sdk": "^0.3.0", "polkadot-api": ">=2" } }, "packages/product-react-renderer": { "name": "@novasamatech/product-react-renderer", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { - "@novasamatech/host-api": "0.8.7-0", - "@novasamatech/host-api-wrapper": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", + "@novasamatech/host-api-wrapper": "0.8.7", "react-reconciler": "0.33.0", "scale-ts": "1.6.1" }, @@ -17573,7 +18071,7 @@ }, "packages/scale": { "name": "@novasamatech/scale", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { "@polkadot-api/utils": "^0.4.0", @@ -17582,18 +18080,18 @@ }, "packages/statement-store": { "name": "@novasamatech/statement-store", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { "@noble/ciphers": "2.2.0", "@noble/hashes": "2.2.0", - "@novasamatech/scale": "0.8.7-0", + "@novasamatech/scale": "0.8.7", "@novasamatech/sdk-statement": "^0.6.0", - "@novasamatech/substrate-slot-sr25519-wasm": "0.8.7-0", + "@novasamatech/substrate-slot-sr25519-wasm": "0.8.7", "@polkadot-api/substrate-bindings": "^0.20.3", "@polkadot-api/substrate-client": "^0.7.0", "@polkadot-labs/hdkd-helpers": "^0.0.30", - "@polkadot-labs/schnorrkel-wasm": "0.0.8", + "@polkadot-labs/schnorrkel-wasm": "0.0.9", "@scure/sr25519": "2.2.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", @@ -17633,7 +18131,7 @@ }, "packages/storage-adapter": { "name": "@novasamatech/storage-adapter", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0", "dependencies": { "nanoevents": "^9.1.0", @@ -17642,7 +18140,7 @@ }, "packages/substrate-slot-sr25519-wasm": { "name": "@novasamatech/substrate-slot-sr25519-wasm", - "version": "0.8.7-0", + "version": "0.8.7", "license": "Apache-2.0" } } diff --git a/package.json b/package.json index 252d1de0..90e246d5 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "@nx/js": "22.7.5", "@nx/storybook": "22.7.5", "@nx/vite": "22.7.5", - "@storybook/react-vite": "10.4.1", - "@types/node": "25.9.1", + "@storybook/react-vite": "10.4.4", + "@types/node": "25.9.3", "@types/ws": "8.18.1", "@vitejs/plugin-react": "5.2.0", "babel-plugin-react-compiler": "1.0.0", @@ -39,15 +39,15 @@ "globals": "17.6.0", "husky": "9.1.7", "jiti": "2.7.0", - "knip": "6.14.2", + "knip": "6.16.1", "nanoevents": "9.1.0", "nx": "22.7.5", - "prettier": "3.8.3", - "storybook": "10.4.1", + "prettier": "3.8.4", + "storybook": "10.4.4", "ts-node": "10.9.2", "tslib": "2.8.1", "typescript": "6.0.3", - "typescript-eslint": "8.60.0", + "typescript-eslint": "8.61.0", "vite": "7.3.2", "vite-plugin-dts": "5.0.2", "vite-plugin-externalize-deps": "0.10.0", diff --git a/packages/handoff-service/package.json b/packages/handoff-service/package.json index 45d8238f..76c851c8 100644 --- a/packages/handoff-service/package.json +++ b/packages/handoff-service/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/handoff-service", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "HOP (Handoff Pool) file transfer service for P2P chat", "license": "Apache-2.0", "repository": { diff --git a/packages/host-api-wrapper/package.json b/packages/host-api-wrapper/package.json index ebd65921..55e1843c 100644 --- a/packages/host-api-wrapper/package.json +++ b/packages/host-api-wrapper/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/host-api-wrapper", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Host API wrapper: integrate and run your product inside Polkadot browser.", "license": "Apache-2.0", "repository": { @@ -28,7 +28,7 @@ "@polkadot/extension-inject": "^0.63.1", "@polkadot-api/json-rpc-provider-proxy": "^0.4.0", "@polkadot-api/substrate-bindings": "^0.20.3", - "@novasamatech/host-api": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", "polkadot-api": ">=2", "neverthrow": "^8.2.0" }, diff --git a/packages/host-api/package.json b/packages/host-api/package.json index 39ca4bdf..59b83611 100644 --- a/packages/host-api/package.json +++ b/packages/host-api/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/host-api", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Host API: transport implementation for host - product integration.", "license": "Apache-2.0", "repository": { @@ -22,7 +22,7 @@ "README.md" ], "dependencies": { - "@novasamatech/scale": "0.8.7-0", + "@novasamatech/scale": "0.8.7", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", diff --git a/packages/host-chat/package.json b/packages/host-chat/package.json index 0fa16b68..4b7fa4f9 100644 --- a/packages/host-chat/package.json +++ b/packages/host-chat/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/host-chat", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Host statement store chat integration", "license": "Apache-2.0", "repository": { @@ -40,9 +40,9 @@ "README.md" ], "dependencies": { - "@novasamatech/scale": "0.8.7-0", - "@novasamatech/statement-store": "0.8.7-0", - "@novasamatech/storage-adapter": "0.8.7-0", + "@novasamatech/scale": "0.8.7", + "@novasamatech/statement-store": "0.8.7", + "@novasamatech/storage-adapter": "0.8.7", "nanoid": "5.1.11", "neverthrow": "^8.2.0" }, diff --git a/packages/host-container/package.json b/packages/host-container/package.json index 21a9879a..980bf340 100644 --- a/packages/host-container/package.json +++ b/packages/host-container/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/host-container", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Host container for hosting and managing products within the Polkadot ecosystem.", "license": "Apache-2.0", "repository": { @@ -28,7 +28,7 @@ "@noble/hashes": "2.2.0", "polkadot-api": ">=2", "@polkadot-api/substrate-client": "^0.7.0", - "@novasamatech/host-api": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0" diff --git a/packages/host-papp-react-ui/package.json b/packages/host-papp-react-ui/package.json index 71648576..4372db1b 100644 --- a/packages/host-papp-react-ui/package.json +++ b/packages/host-papp-react-ui/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/host-papp-react-ui", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Polkadot app UI Flow", "license": "Apache-2.0", "repository": { @@ -33,21 +33,21 @@ "react-dom": ">=18" }, "dependencies": { - "@novasamatech/host-papp": "0.8.7-0", - "@novasamatech/statement-store": "0.8.7-0", + "@novasamatech/host-papp": "0.8.7", + "@novasamatech/statement-store": "0.8.7", "@polkadot-api/utils": "^0.4.0", - "@radix-ui/react-dialog": "1.1.15", - "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-dialog": "1.1.16", + "@radix-ui/react-popover": "1.1.16", "qrcode": "^1.5.4", - "@novasamatech/tr-ui": "0.2.9" + "@novasamatech/tr-ui": "^0.2.11" }, "devDependencies": { "@types/qrcode": "^1.5.6", - "@types/react": "19.2.15", + "@types/react": "19.2.17", "@types/react-dom": "19.2.3", "@polkadot-api/substrate-bindings": "^0.20.3", - "react": "19.2.6", - "react-dom": "19.2.6", + "react": "19.2.7", + "react-dom": "19.2.7", "typescript-plugin-css-modules": "5.2.0", "vite-plugin-wasm": "3.6.0" }, diff --git a/packages/host-papp/package.json b/packages/host-papp/package.json index 538addee..d9cc319f 100644 --- a/packages/host-papp/package.json +++ b/packages/host-papp/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/host-papp", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Polkadot app integration", "license": "Apache-2.0", "repository": { @@ -34,10 +34,10 @@ "@noble/ciphers": "2.2.0", "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", - "@novasamatech/host-api": "0.8.7-0", - "@novasamatech/scale": "0.8.7-0", - "@novasamatech/statement-store": "0.8.7-0", - "@novasamatech/storage-adapter": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", + "@novasamatech/scale": "0.8.7", + "@novasamatech/statement-store": "0.8.7", + "@novasamatech/storage-adapter": "0.8.7", "@polkadot-api/utils": "^0.4.0", "@polkadot-labs/hdkd-helpers": "^0.0.30", "nanoevents": "9.1.0", diff --git a/packages/host-papp/src/sso/sessionManager/userSession.ts b/packages/host-papp/src/sso/sessionManager/userSession.ts index 7ec3cf9c..b67df5c3 100644 --- a/packages/host-papp/src/sso/sessionManager/userSession.ts +++ b/packages/host-papp/src/sso/sessionManager/userSession.ts @@ -26,8 +26,8 @@ import type { SigningPayloadRequest, SigningPayloadResponseData, SigningRawReque // payload is for an SDK version the mobile app doesn't support yet. After // this timeout the queue task fails, freeing the pool for the next request. const QUEUE_TASK_TIMEOUT_MS = 180_000; -// Mobile SSO statements allow 256 KiB total; keep headroom for statement/session overhead. -const MAX_SSO_REQUEST_SIZE = 254 * 1024; +// Mobile SSO statements allow 500 KiB total; keep headroom for statement/session overhead. +const MAX_SSO_REQUEST_SIZE = 498 * 1024; function withQueueTimeout(resultAsync: ResultAsync, label: string): ResultAsync { const timeoutPromise = new Promise>(resolve => diff --git a/packages/host-substrate-chain-connection/package.json b/packages/host-substrate-chain-connection/package.json index f0b18316..351f65f0 100644 --- a/packages/host-substrate-chain-connection/package.json +++ b/packages/host-substrate-chain-connection/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/host-substrate-chain-connection", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Chain connection pool with ref counting and provider branching for Polkadot API", "license": "Apache-2.0", "repository": { @@ -25,7 +25,7 @@ "README.md" ], "dependencies": { - "@novasamatech/storage-adapter": "0.8.7-0", + "@novasamatech/storage-adapter": "0.8.7", "@polkadot-api/ws-provider": "^0.9.0", "@polkadot-api/json-rpc-provider": "^0.2.0", "@polkadot-api/json-rpc-provider-proxy": "^0.4.0", diff --git a/packages/host-worker-sandbox/package.json b/packages/host-worker-sandbox/package.json index 4cf97580..874026a4 100644 --- a/packages/host-worker-sandbox/package.json +++ b/packages/host-worker-sandbox/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/host-worker-sandbox", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "QuickJS-based sandbox for running product worker code with Triangle Host API.", "license": "Apache-2.0", "repository": { @@ -25,8 +25,8 @@ "README.md" ], "dependencies": { - "@novasamatech/host-api": "0.8.7-0", - "@novasamatech/host-container": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", + "@novasamatech/host-container": "0.8.7", "quickjs-emscripten": "0.32.0" }, "publishConfig": { diff --git a/packages/product-bulletin/package.json b/packages/product-bulletin/package.json index c5e00ea1..bdd78f3b 100644 --- a/packages/product-bulletin/package.json +++ b/packages/product-bulletin/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/product-bulletin", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Bulletin Chain client adapter for Polkadot product applications", "license": "Apache-2.0", "repository": { @@ -27,7 +27,7 @@ "README.md" ], "dependencies": { - "@novasamatech/host-api-wrapper": "0.8.7-0", + "@novasamatech/host-api-wrapper": "0.8.7", "@parity/bulletin-sdk": "^0.3.0", "polkadot-api": ">=2" }, diff --git a/packages/product-react-renderer/package.json b/packages/product-react-renderer/package.json index 06ac8fe4..ba6e99ba 100644 --- a/packages/product-react-renderer/package.json +++ b/packages/product-react-renderer/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/product-react-renderer", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "React wrapper for custom renderer format from host-api-wrapper", "license": "Apache-2.0", "repository": { @@ -24,8 +24,8 @@ "README.md" ], "dependencies": { - "@novasamatech/host-api": "0.8.7-0", - "@novasamatech/host-api-wrapper": "0.8.7-0", + "@novasamatech/host-api": "0.8.7", + "@novasamatech/host-api-wrapper": "0.8.7", "scale-ts": "1.6.1", "react-reconciler": "0.33.0" }, diff --git a/packages/scale/package.json b/packages/scale/package.json index 6ea1a3e2..6e16ee09 100644 --- a/packages/scale/package.json +++ b/packages/scale/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/scale", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "additional scale-ts bindings", "license": "Apache-2.0", "repository": { diff --git a/packages/statement-store/package.json b/packages/statement-store/package.json index 84b270fa..9fc69f7e 100644 --- a/packages/statement-store/package.json +++ b/packages/statement-store/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/statement-store", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Statement store integration", "license": "Apache-2.0", "repository": { @@ -25,13 +25,13 @@ "README.md" ], "dependencies": { - "@novasamatech/scale": "0.8.7-0", + "@novasamatech/scale": "0.8.7", "@novasamatech/sdk-statement": "^0.6.0", - "@novasamatech/substrate-slot-sr25519-wasm": "0.8.7-0", + "@novasamatech/substrate-slot-sr25519-wasm": "0.8.7", "@polkadot-api/substrate-bindings": "^0.20.3", "@polkadot-api/substrate-client": "^0.7.0", "@polkadot-labs/hdkd-helpers": "^0.0.30", - "@polkadot-labs/schnorrkel-wasm": "0.0.8", + "@polkadot-labs/schnorrkel-wasm": "0.0.9", "@noble/hashes": "2.2.0", "@noble/ciphers": "2.2.0", "@scure/sr25519": "2.2.0", diff --git a/packages/statement-store/src/adapter/rpc.ts b/packages/statement-store/src/adapter/rpc.ts index 82becfaf..4317fac2 100644 --- a/packages/statement-store/src/adapter/rpc.ts +++ b/packages/statement-store/src/adapter/rpc.ts @@ -131,9 +131,9 @@ export function createPapiStatementStoreAdapter(lazyClient: LazyClient): Stateme case 'dataTooLarge': return errAsync(new DataTooLargeError(result.submitted_size, result.available_size)); case 'channelPriorityTooLow': - return errAsync(new ExpiryTooLowError(result.submitted_expiry, result.min_expiry)); + return errAsync(new ExpiryTooLowError(BigInt(result.submitted_expiry), BigInt(result.min_expiry))); case 'accountFull': - return errAsync(new AccountFullError(result.submitted_expiry, result.min_expiry)); + return errAsync(new AccountFullError(BigInt(result.submitted_expiry), BigInt(result.min_expiry))); case 'storeFull': return errAsync(new StorageFullError()); case 'noAllowance': diff --git a/packages/statement-store/src/index.ts b/packages/statement-store/src/index.ts index 3ac8c404..9e0ba455 100644 --- a/packages/statement-store/src/index.ts +++ b/packages/statement-store/src/index.ts @@ -46,6 +46,13 @@ export { } from './adapter/types.js'; export { createPapiStatementStoreAdapter } from './adapter/rpc.js'; +export type { ExpiryAllocator } from './submit/allocator.js'; +export { PRIORITY_EPOCH_OFFSET, createExpiryAllocator } from './submit/allocator.js'; +export type { SubmitRetryOptions } from './submit/retry.js'; +export { isPriorityTooLow, submitWithRetry } from './submit/retry.js'; +export type { SubmitStatementParams } from './submit/submitStatement.js'; +export { signAndSubmitStatement, submitStatementOnce } from './submit/submitStatement.js'; + export { createSr25519Derivation, createSr25519Secret, diff --git a/packages/statement-store/src/session/session.spec.ts b/packages/statement-store/src/session/session.spec.ts index 01e72a86..3c57eaa8 100644 --- a/packages/statement-store/src/session/session.spec.ts +++ b/packages/statement-store/src/session/session.spec.ts @@ -1,20 +1,20 @@ import type { SignedStatement, Statement } from '@novasamatech/sdk-statement'; import { createExpiryFromDuration } from '@novasamatech/sdk-statement'; import type { Result } from 'neverthrow'; -import { ResultAsync, errAsync, ok, okAsync } from 'neverthrow'; +import { ResultAsync, err, errAsync, ok, okAsync } from 'neverthrow'; import type { CodecType } from 'scale-ts'; import { Bytes, Struct, str } from 'scale-ts'; import { describe, expect, it, vi } from 'vitest'; import { createInMemoryStatementStore } from '../adapter/inMemory.js'; import type { StatementStoreAdapter, StatementsPage } from '../adapter/types.js'; -import { ExpiryTooLowError } from '../adapter/types.js'; +import { AccountFullError, ExpiryTooLowError } from '../adapter/types.js'; import { createAccountId, createLocalSessionAccount, createRemoteSessionAccount } from '../model/sessionAccount.js'; import type { Encryption } from './encyption.js'; import { DecodingError, UnknownError } from './error.js'; import { StatementData } from './scale/statementData.js'; -import { STATEMENT_OVERHEAD, createSession, nextExpiry } from './session.js'; +import { STATEMENT_OVERHEAD, createSession } from './session.js'; import type { StatementProver } from './statementProver.js'; // Real signature work belongs in statementProver tests; this stub stamps a @@ -124,6 +124,24 @@ function lastSubmittedRequestId(adapter: ReturnType): string return decoded.tag === 'request' ? decoded.value.requestId : ''; } +// A submitStatement mock that defers every submission instead of resolving: each call records a +// `{ requestId, settle }` entry in `pendings`, letting a test land or reject submissions in a chosen +// order (used to drive shared-channel supersession races). Works for request and response payloads. +function deferredSubmit() { + const pendings: Array<{ requestId: string; settle: (r: Result) => void }> = []; + const submitStatement = vi.fn((stmt: Statement) => { + const decoded = StatementData.dec(stmt.data!); + const requestId = decoded.tag === 'request' || decoded.tag === 'response' ? decoded.value.requestId : ''; + return ResultAsync.fromPromise( + new Promise((resolve, reject) => { + pendings.push({ requestId, settle: r => (r.isOk() ? resolve() : reject(r.error)) }); + }), + e => e as Error, + ); + }); + return { submitStatement, pendings }; +} + // Encoded size of the request payload (the statement `data` field) for these messages โ€” // what the session sizes batches against. Includes the requestId (a fixed-length nanoid) // and the SCALE vector framing, so it is larger than the raw message bytes alone. @@ -178,34 +196,6 @@ function makeMobile(adapter: StatementStoreAdapter) { describe('session', () => { const rawCodec = Bytes(); - // Statement expiry/priority: u64 = (expiration_epoch << 32) | priority. The high word is pinned - // to 0xFFFFFFFF (non-expiring) and the low word is a wall-clock-floored monotonic priority that - // drives channel supersession. (Spec ยง1; matches iOS/Android.) - describe('expiry priority', () => { - it('encodes a non-expiring statement (high word pinned to 0xFFFFFFFF)', () => { - expect(nextExpiry(0n) >> 32n).toBe(0xffff_ffffn); - }); - - it('carries a wall-clock priority in the low word', () => { - const result = nextExpiry(0n); - expect(result & 0xffff_ffffn).toBeGreaterThan(0n); - }); - - it('increments by one when the current value already exceeds the wall-clock priority', () => { - const high = (0xffff_ffffn << 32n) | 0xffff_ffffn; // max u64 - expect(nextExpiry(high)).toBe(high + 1n); - }); - - it('is strictly monotonic across repeated calls', () => { - let expiry = 0n; - for (let i = 0; i < 5; i++) { - const next = nextExpiry(expiry); - expect(next).toBeGreaterThan(expiry); - expiry = next; - } - }); - }); - // On creation a session queries both of its topics, derives the starting expiry, and buffers // anything it finds until it goes active. (Spec ยง5 initialization.) describe('initialization', () => { @@ -960,6 +950,53 @@ describe('session', () => { expect(second.isOk()).toBe(true); expect(adapter.submitStatement.mock.calls.length).toBeGreaterThan(submitsBefore); }, 3000); + + it.each([ + ['ExpiryTooLow', ExpiryTooLowError], + ['AccountFull', AccountFullError], + ])( + 'absorbs a superseded response rejected as %s and keeps the request answered', + async (_name, PriorityError) => { + // Two incoming requests are answered on the SHARED response channel. The response to A is in + // flight when the response to B takes over the channel; A then lands at a now-lower expiry and + // the store rejects it with a priority error. That supersession is expected: B's response owns + // the channel, so A's rejection must be absorbed (not surfaced) and A must stay marked + // answered โ€” re-answering would only clobber B. (Returning ok here is also what stops + // respondToRequests from logging it as a failed response.) + const reqA = makeStatement({ tag: 'request', value: { requestId: 'A', data: [] } }); + const { subscribeStatements, callbacks } = capturingSubscribe(); + const { submitStatement, pendings } = deferredSubmit(); + const { session, adapter } = makeSession({ peer: [reqA], subscribeStatements, submitStatement }); + await delay(); + session.subscribe(rawCodec, vi.fn()); // activate the store subscription + const reqB = makeStatement({ tag: 'request', value: { requestId: 'B', data: [] } }); + callbacks[0]!({ statements: [reqB], isComplete: true }); + await delay(); + + const resAPromise = session.submitResponseMessage('A', 'success'); // in flight on the shared channel + const resBPromise = session.submitResponseMessage('B', 'success'); // supersedes A + await delay(); // both reach submitStatement + + expect(pendings).toHaveLength(2); + pendings.find(p => p.requestId === 'B')!.settle(ok(undefined)); // B lands, owns the channel + pendings.find(p => p.requestId === 'A')!.settle(err(new PriorityError(0n, 0n))); // A lands late, rejected + + const resA = await resAPromise; + const resB = await resBPromise; + expect(resB.isOk()).toBe(true); + expect(resA.isOk()).toBe(true); // superseded rejection absorbed, not surfaced as an error + + // A stays answered: re-answering it must NOT submit again (which would clobber B's response). + const submitsBefore = adapter.submitStatement.mock.calls.length; + const reAnswer = await session.submitResponseMessage('A', 'success'); + await delay(); + expect(reAnswer.isOk()).toBe(true); + expect(adapter.submitStatement.mock.calls.length).toBe(submitsBefore); // deduped โ†’ no resubmit + + session.dispose(); + }, + 3000, + ); }); // clearOutgoingStatement aborts the in-flight request: it drops local state, rejects waiters, and @@ -976,6 +1013,27 @@ describe('session', () => { expect(adapter.submitStatement.mock.calls.length).toBe(before); }); + it('absorbs an ExpiryTooLow on the superseding empty batch as success', async () => { + // clearOutgoingStatement runs a single direct submit (no submitWithRetry). If the empty batch + // is rejected ExpiryTooLow, the channel already advanced past us โ€” the request is already gone + // โ€” so the clear has effectively happened. The caller must see success, not the sync artifact. + let calls = 0; + const submitStatement = vi.fn((stmt: Statement) => + ++calls === 1 + ? okAsync(undefined) // the request itself lands + : errAsync(new ExpiryTooLowError(stmt.expiry ?? 0n, (0xffff_ffffn << 32n) | 9_000_000_000n)), + ); + const { session } = makeSession({ submitStatement }); + await delay(); + + void session.submitRequestMessage(rawCodec, new Uint8Array([1])); + await delay(); + + const cleared = await session.clearOutgoingStatement(); + expect(cleared.isOk()).toBe(true); // ExpiryTooLow suppressed + session.dispose(); + }, 3000); + it('submits an empty batch on the same channel at a higher expiry and clears local state', async () => { const { session, adapter } = makeSession(); await delay(); @@ -1147,26 +1205,71 @@ describe('session', () => { session.dispose(); }, 3000); - it('resyncs its expiry above the chain minimum after an ExpiryTooLow rejection', async () => { - // The in-memory expiry counter has drifted behind the channel's real priority (prior run / - // other writer / propagation lag). The chain reports the minimum; the retry must clear it. - const CHAIN_MIN = (0xffff_ffffn << 32n) | 4_000_000_000n; // well above the wall-clock priority - let calls = 0; - const submitStatement = vi.fn((stmt: Statement) => { - calls++; - return calls === 1 ? errAsync(new ExpiryTooLowError(stmt.expiry ?? 0n, CHAIN_MIN)) : okAsync(undefined); - }); - const { session, adapter } = makeSession({ submitStatement }); - await delay(); - - void session.submitRequestMessage(rawCodec, new Uint8Array([1])); - await new Promise(resolve => setTimeout(resolve, 100)); // allow the retry (25ms backoff) - - expect(adapter.submitStatement.mock.calls.length).toBeGreaterThanOrEqual(2); - const retried = adapter.submitStatement.mock.calls.at(-1)?.[0] as Statement; - expect(retried.expiry ?? 0n).toBeGreaterThan(CHAIN_MIN); // healed past the chain minimum - session.dispose(); - }, 3000); + it.each([ + ['ExpiryTooLow', ExpiryTooLowError], + ['AccountFull', AccountFullError], + ])( + 'resyncs its expiry above the chain minimum after an %s rejection', + async (_name, PriorityError) => { + // The in-memory expiry counter has drifted behind the chain's real priority floor (prior run / + // other writer / propagation lag / account full of higher-priority statements). The chain + // reports the minimum; the retry must clear it. + const CHAIN_MIN = (0xffff_ffffn << 32n) | 4_000_000_000n; // well above the wall-clock priority + let calls = 0; + const submitStatement = vi.fn((stmt: Statement) => { + calls++; + return calls === 1 ? errAsync(new PriorityError(stmt.expiry ?? 0n, CHAIN_MIN)) : okAsync(undefined); + }); + const { session, adapter } = makeSession({ submitStatement }); + await delay(); + + void session.submitRequestMessage(rawCodec, new Uint8Array([1])); + await new Promise(resolve => setTimeout(resolve, 100)); // allow the retry (25ms backoff) + + expect(adapter.submitStatement.mock.calls.length).toBeGreaterThanOrEqual(2); + const retried = adapter.submitStatement.mock.calls.at(-1)?.[0] as Statement; + expect(retried.expiry ?? 0n).toBeGreaterThan(CHAIN_MIN); // healed past the chain minimum + session.dispose(); + }, + 3000, + ); + + it.each([ + ['ExpiryTooLow', ExpiryTooLowError], + ['AccountFull', AccountFullError], + ])( + 'keeps retrying a live %s past the transient-retry cap until it lands', + async (_name, PriorityError) => { + // Priority errors are sync artifacts, not chain failures: while the submission is still live + // the session keeps retrying (resyncing each time) BEYOND MAX_SUBMIT_RETRIES until it lands, + // and never surfaces the error to the caller. (A non-priority error gives up at the cap โ€” + // see the test below.) + const CHAIN_MIN = (0xffff_ffffn << 32n) | 4_000_000_000n; + let calls = 0; + const submitStatement = vi.fn((stmt: Statement) => + ++calls <= 6 ? errAsync(new PriorityError(stmt.expiry ?? 0n, CHAIN_MIN)) : okAsync(undefined), + ); + const { session } = makeSession({ submitStatement }); + await delay(); + + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined); + try { + const submit = await session.submitRequestMessage(rawCodec, new Uint8Array([1])); + let waiterRejected = false; + void session.waitForResponseMessage(submit._unsafeUnwrap().requestId).mapErr(() => (waiterRejected = true)); + + await new Promise(resolve => setTimeout(resolve, 300)); // 6 retries ร— 25ms backoff + slack + + expect(calls).toBeGreaterThanOrEqual(7); // retried well past the 3-attempt cap, then landed + expect(errorSpy).not.toHaveBeenCalledWith('submitRequest failed:', expect.anything()); + expect(waiterRejected).toBe(false); // the priority error never surfaced to the caller + } finally { + errorSpy.mockRestore(); + session.dispose(); + } + }, + 3000, + ); it('rejects the pending waiter once request-submission retries are exhausted', async () => { const { session } = makeSession({ @@ -1180,6 +1283,45 @@ describe('session', () => { const waited = await session.waitForResponseMessage(requestId); expect(waited.isErr()).toBe(true); }, 2000); + + it('absorbs a superseded older submission rejected as ExpiryTooLow without surfacing an error', async () => { + // Two messages batch onto one outgoing request: the first submission (requestId A) is in + // flight when the second (requestId B, higher expiry, SAME tokens) is sent. B lands first and + // sets the channel priority; A then lands at a now-lower expiry and the store rejects it with + // ExpiryTooLow. A is superseded, so its rejection is expected protocol behaviour โ€” it must not + // be logged as an error and must not reject the shared waiters (B carries them). + const { submitStatement, pendings } = deferredSubmit(); + const { session, adapter } = makeSession({ submitStatement }); + await delay(); + + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined); + try { + const first = (await session.submitRequestMessage(rawCodec, new Uint8Array([1])))._unsafeUnwrap(); + void session.submitRequestMessage(rawCodec, new Uint8Array([2])); // batches onto the same outgoing request + await delay(); // let both submissions reach submitStatement + + expect(pendings).toHaveLength(2); + const liveRequestId = lastSubmittedRequestId(adapter); // the newer (B) submission + const live = pendings.find(p => p.requestId === liveRequestId)!; + const superseded = pendings.find(p => p.requestId !== liveRequestId)!; + + let firstWaiterRejected = false; + void session.waitForResponseMessage(first.requestId).mapErr(() => { + firstWaiterRejected = true; + }); + + live.settle(ok(undefined)); // B lands, claims the channel priority + await delay(); + superseded.settle(err(new ExpiryTooLowError(0n, 0n))); // A lands late and is rejected + await delay(); + + expect(errorSpy).not.toHaveBeenCalledWith('submitRequest failed:', expect.anything()); + expect(firstWaiterRejected).toBe(false); // superseded failure must not reject the shared waiter + } finally { + errorSpy.mockRestore(); + session.dispose(); + } + }, 3000); }); describe('dispose', () => { @@ -1205,6 +1347,45 @@ describe('session', () => { expect(queryStatements.mock.calls.length).toBe(callsBeforeDispose); // disposed โ†’ no further init queries }, 3000); + + it('rejects submitRequestMessage after dispose instead of hanging', async () => { + const { session, adapter } = makeSession(); + await delay(); + session.dispose(); + + const result = await session.submitRequestMessage(rawCodec, new Uint8Array([1])); + expect(result.isErr()).toBe(true); // surfaced immediately, not a token left pending forever + expect(adapter.submitStatement).not.toHaveBeenCalled(); + }); + + it('rejects submitResponseMessage after dispose', async () => { + const { session } = makeSession(); + await delay(); + session.dispose(); + + const result = await session.submitResponseMessage('any-id', 'success'); + expect(result.isErr()).toBe(true); + }); + + it('does not re-activate when disposed while init is in flight', async () => { + // dispose() lands during init's query await; init must bail before restoring state / flipping + // phase to 'active', otherwise a torn-down session looks alive and accepts new work. + let resolveQueries!: (s: Statement[]) => void; + const gate = new Promise(resolve => (resolveQueries = resolve)); + const queryStatements = vi.fn(() => ResultAsync.fromSafePromise(gate)); + const { session, adapter } = makeSession({ queryStatements }); + + const queued = await session.submitRequestMessage(rawCodec, new Uint8Array([1])); // queued during init + expect(queued.isOk()).toBe(true); + + session.dispose(); // dispose mid-init + resolveQueries([]); // init resumes โ€” must bail before draining the queue / activating + await settle(); + + expect(adapter.submitStatement).not.toHaveBeenCalled(); // no resurrection-driven submit + const after = await session.submitRequestMessage(rawCodec, new Uint8Array([2])); + expect(after.isErr()).toBe(true); // session stays disposed + }, 3000); }); // The in-memory adapter replicates the store's observable contract; `fidelity` pins the double's diff --git a/packages/statement-store/src/session/session.ts b/packages/statement-store/src/session/session.ts index 3ffdd327..95c9d38c 100644 --- a/packages/statement-store/src/session/session.ts +++ b/packages/statement-store/src/session/session.ts @@ -1,17 +1,20 @@ +import { toHex } from '@novasamatech/scale'; import type { Statement } from '@novasamatech/sdk-statement'; import { nanoid } from 'nanoid'; import { ResultAsync, err, errAsync, fromPromise, fromThrowable, ok, okAsync } from 'neverthrow'; -import { toHex } from 'polkadot-api/utils'; import type { Codec, CodecType } from 'scale-ts'; import { Struct, str } from 'scale-ts'; import type { StatementStoreAdapter } from '../adapter/types.js'; -import { ExpiryTooLowError } from '../adapter/types.js'; import { khash, stringToBytes } from '../crypto.js'; import { nonNullable, toError } from '../helpers.js'; import type { SessionId } from '../model/session.js'; import { createSessionId } from '../model/session.js'; import type { LocalSessionAccount, RemoteSessionAccount } from '../model/sessionAccount.js'; +import type { ExpiryAllocator } from '../submit/allocator.js'; +import { createExpiryAllocator } from '../submit/allocator.js'; +import { isPriorityTooLow, submitWithRetry } from '../submit/retry.js'; +import { submitStatementOnce } from '../submit/submitStatement.js'; import type { Callback } from '../types.js'; import type { Encryption } from './encyption.js'; @@ -42,11 +45,22 @@ export type SessionParams = { * throw. */ sessionKey: Uint8Array; + /** + * Expiry source for this session's submits. Inject ONE shared allocator when + * several writers (sessions, raw submits) sign with the same account, so + * same-second submits cannot tie. Defaults to a private allocator โ€” + * identical to the previous per-session behavior. + */ + allocator?: ExpiryAllocator; maxRequestSize?: number; }; const DEFAULT_MAX_REQUEST_SIZE = 4096; +// Rejection reason shared by dispose() and the disposed guards on submit*, so a torn-down session +// always fails new and in-flight work the same way. +const SESSION_DISPOSED = 'Session disposed'; + // Bounded retry for transient transport failures (the spec mandates retrying queries // and submit_statement on connection failure). The TS adapter doesn't expose connection // state, so we approximate with a short fixed backoff and an attempt cap. @@ -62,20 +76,6 @@ const RETRY_DELAY_MS = 25; */ export const STATEMENT_OVERHEAD = 32 + 32 + 8 + 64 + 32; // 168 bytes -/** - * Statement expiry/priority, u64 = (expiration_epoch << 32) | priority (spec layout). - * We pin the high word to 0xFFFFFFFF (max โ†’ effectively non-expiring, matching iOS & - * Android) and use the low word as a wall-clock-floored monotonic priority, so channel - * supersession is driven by the priority regardless of how the store compares the field. - * Returns a value strictly greater than `current` (i.e. `max(current + 1, now-priority)`). - */ -const NEVER_EXPIRE_HIGH = 0xffff_ffffn; -export function nextExpiry(current: bigint): bigint { - const nowSecs = BigInt(Math.floor(Date.now() / 1000)); - const timestampPriority = (NEVER_EXPIRE_HIGH << 32n) | nowSecs; - return timestampPriority > current ? timestampPriority : current + 1n; -} - type Subscriber = { codec: Codec; callback: Callback[]>; @@ -98,7 +98,6 @@ type OutgoingRequest = { type SessionState = { phase: 'initialization' | 'active' | 'failed'; initError: Error | null; - expiry: bigint; outgoingRequest: OutgoingRequest | null; // Tracks every incoming request by its id with its own responded flag, so an // older request stays answerable after a newer one arrives, and an async @@ -164,23 +163,6 @@ function makeDeferred(): PendingDelivery { return { resolve, reject, promise }; } -// Retry a submit on failure with a short backoff, up to `attemptsLeft` extra attempts. -// A successful submit returns immediately (no delay on the happy path). `shouldRetry` is -// re-checked before each retry: once the submission is superseded, aborted, or the session -// is disposed it returns false, so a stale retry can never resurrect an old statement. -function submitWithRetry( - submit: () => ResultAsync, - attemptsLeft: number, - shouldRetry: () => boolean, -): ResultAsync { - return submit().orElse(error => { - if (attemptsLeft <= 0 || !shouldRetry()) return errAsync(error); - return ResultAsync.fromSafePromise(new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS))).andThen(() => - !shouldRetry() ? errAsync(error) : submitWithRetry(submit, attemptsLeft - 1, shouldRetry), - ); - }); -} - export function createSession({ localAccount, remoteAccount, @@ -188,10 +170,14 @@ export function createSession({ encryption, prover, sessionKey, + allocator = createExpiryAllocator(), maxRequestSize = DEFAULT_MAX_REQUEST_SIZE, }: SessionParams): Session { const outgoingSessionId = createSessionId(sessionKey, localAccount, remoteAccount); const incomingSessionId = createSessionId(sessionKey, remoteAccount, localAccount); + // Session-constant channel hashes โ€” derived once so retries don't re-hash them per attempt. + const requestChannel = createRequestChannel(outgoingSessionId); + const responseChannel = createResponseChannel(outgoingSessionId); // Message bytes must fit within the statement limit minus the fixed wire overhead. const maxPayloadSize = Math.max(0, maxRequestSize - STATEMENT_OVERHEAD); @@ -199,7 +185,6 @@ export function createSession({ const state: SessionState = { phase: 'initialization', initError: null, - expiry: 0n, outgoingRequest: null, incomingRequests: new Map(), messageQueue: [], @@ -220,31 +205,26 @@ export function createSession({ // latest is live โ€” a retry for an older one must not resurrect it). let lastResponseRequestId: string | null = null; - // Submit on `channel`/`topicSessionId` at the next (strictly increasing) expiry. + // Encrypt, then submit on `channel`/`topicSessionId` at the allocator's next (strictly + // increasing) expiry. A priority rejection does NOT resync the allocator here โ€” each caller + // raises the floor to the chain-reported minimum (the submitWithRetry `onPriorityError` hooks + // below, and the one-shot clear in clearOutgoingStatement), so the retry โ€” and every later + // submit โ€” clears it. function submitStatementData( channel: Uint8Array, topicSessionId: SessionId, data: Uint8Array, ): ResultAsync { - state.expiry = nextExpiry(state.expiry); - const expiry = state.expiry; - return encryption - .encrypt(data) - .map(encrypted => ({ - expiry, - channel: toHex(channel) as `0x${string}`, - topics: [toHex(topicSessionId) as `0x${string}`], + return encryption.encrypt(data).asyncAndThen(encrypted => + submitStatementOnce({ + statementStore, + prover, + allocator, + channel, + topics: [topicSessionId], data: encrypted, - })) - .asyncAndThen(prover.generateMessageProof) - .andThen(statementStore.submitStatement) - .orElse(error => { - // The chain is the source of truth for a channel's priority. If our in-memory counter - // drifted behind it (a prior run, another writer, or propagation lag), resync to the - // reported minimum so the retry โ€” and every later submit โ€” clears it. - if (error instanceof ExpiryTooLowError && error.min > state.expiry) state.expiry = error.min; - return errAsync(error); - }); + }), + ); } // Settle and remove the pending-delivery entries for the given tokens. @@ -258,30 +238,42 @@ export function createSession({ } } + // Session retry policy (this and every submitWithRetry call below): priority errors + // (ExpiryTooLow / AccountFull) are retried with `priorityAttempts: 'unbounded'` โ€” they never + // consume the transient-failure budget, because the `onPriorityError` hook raises the allocator + // floor above the chain-reported minimum, so the next attempt submits higher. We keep at it + // until the statement lands or the submission is superseded; once superseded, a priority + // rejection is swallowed as success (it merely lost the channel race to a newer, higher-priority + // statement). The upshot: priority errors never surface to session callers. Other errors keep + // the bounded retry and propagate when exhausted. `shouldRetry` is re-checked before each retry: + // once the submission is superseded, aborted, or the session is disposed it returns false, so a + // stale retry can never resurrect an old statement. function encodeAndSubmitRequest(requestId: string, messages: Uint8Array[]): void { encodeStatementData({ tag: 'request', value: { requestId, data: messages } }) .asyncAndThen(data => - submitWithRetry( - () => submitStatementData(createRequestChannel(outgoingSessionId), outgoingSessionId, data), - MAX_SUBMIT_RETRIES, + submitWithRetry(() => submitStatementData(requestChannel, outgoingSessionId, data), { + attempts: MAX_SUBMIT_RETRIES, + priorityAttempts: 'unbounded', + delaysMs: RETRY_DELAY_MS, + // Adopt the chain-reported floor so the next attempt submits strictly above it. + onPriorityError: error => allocator.raiseFloor(error.min), // Only keep retrying while this is still the live submission (not superseded by a // newer retransmit, aborted via clearOutgoingStatement, or disposed). - () => !disposed && state.outgoingRequest?.requestIds.at(-1) === requestId, - ), + shouldRetry: () => !disposed && state.outgoingRequest?.requestIds.at(-1) === requestId, + }), ) .mapErr(e => { - if (disposed) return; - console.error('submitRequest failed:', e); - // Retries are exhausted. If this is still the live submission (not already - // superseded by a newer retransmit), the request never landed โ€” fail its - // waiters rather than let them hang. A superseded older submission failing - // is expected and must NOT reject, since the newer one carries the same tokens. + // Priority errors never reach here (see the policy note above), so this is a genuine + // failure. If this submission was already superseded by a newer retransmit (same tokens) + // it is not the live request's concern โ€” drop it silently; the newer one carries the + // waiters. Otherwise the bounded retries are exhausted on the LIVE submission: the + // request never landed, so fail its waiters rather than let them hang. const outgoing = state.outgoingRequest; - if (outgoing && outgoing.requestIds[outgoing.requestIds.length - 1] === requestId) { - settleTokens(outgoing.tokens, deferred => deferred.reject(e)); - state.outgoingRequest = null; - processMessageQueue(); - } + if (disposed || !outgoing || outgoing.requestIds.at(-1) !== requestId) return; + console.error('submitRequest failed:', e); + settleTokens(outgoing.tokens, deferred => deferred.reject(e)); + state.outgoingRequest = null; + processMessageQueue(); }); } @@ -489,13 +481,12 @@ export function createSession({ for (const s of ownStatements) { if (s.expiry !== undefined && s.expiry > maxExpiry) maxExpiry = s.expiry; } - // Never regress the counter. The query is a snapshot taken when init() began; a statement - // submitted while init was in flight (e.g. an auto-ACK for a peer request that arrived during - // the query) has already advanced both state.expiry and the on-chain channel past that - // snapshot. Overwriting unconditionally would drop the counter below the on-chain priority, - // making the next submit collide at an equal expiry. - const seeded = nextExpiry(maxExpiry); - if (seeded > state.expiry) state.expiry = seeded; + // Adopt the snapshot's maximum as the allocator floor. raiseFloor is monotonic โ€” the floor + // never regresses โ€” so a statement submitted while init was in flight (e.g. an auto-ACK for a + // peer request that arrived during the query) keeps the counter ahead of this snapshot, the + // same guarantee the old conditional seeding gave. The next submit then draws strictly above + // the seen on-chain maximum and at least the wall-clock priority, so it cannot collide at an equal expiry. + allocator.raiseFloor(maxExpiry); for (const s of [...ownStatements, ...peerStatements]) { if (s.data) state.seenStatements.add(toHex(s.data)); @@ -507,6 +498,7 @@ export function createSession({ ).then(outcomes => outcomes.map(o => (o.kind === 'decoded' ? o.data : null)).filter(nonNullable)); const [ownDecoded, peerDecoded] = await Promise.all([decodeAll(ownStatements), decodeAll(peerStatements)]); + if (disposed) return; // Both parties publish on their own outgoing topic, so the OUTGOING query returns our // requests + OUR responses, and the INCOMING query returns the peer's requests + the @@ -557,6 +549,8 @@ export function createSession({ }, submitRequestMessage(codec: Codec, message: T) { + if (disposed) return errAsync(new Error(SESSION_DISPOSED)); + const encode = fromThrowable(codec.enc, toError); const encodedResult = encode(message); if (encodedResult.isErr()) return errAsync(encodedResult.error); @@ -587,6 +581,7 @@ export function createSession({ }, submitResponseMessage(requestId: string, responseCode: ResponseStatus) { + if (disposed) return errAsync(new Error(SESSION_DISPOSED)); const incoming = state.incomingRequests.get(requestId); if (!incoming) return errAsync(new Error(`No incoming request with id ${requestId}`)); if (incoming.responded) { @@ -605,18 +600,30 @@ export function createSession({ // Responses go on OUR outgoing topic/response-channel (per spec: the responder // publishes on SessionId(self, peer)); the requester reads them from its incoming topic. return ( - submitWithRetry( - () => submitStatementData(createResponseChannel(outgoingSessionId), outgoingSessionId, encoded.value), - MAX_SUBMIT_RETRIES, + submitWithRetry(() => submitStatementData(responseChannel, outgoingSessionId, encoded.value), { + attempts: MAX_SUBMIT_RETRIES, + priorityAttempts: 'unbounded', + delaysMs: RETRY_DELAY_MS, + // Adopt the chain-reported floor so the next attempt submits strictly above it. + onPriorityError: error => allocator.raiseFloor(error.min), // Stop retrying once a newer response supersedes this one (shared response channel) or disposed. - () => !disposed && lastResponseRequestId === requestId, - ) - // Answered: it no longer needs replaying to future subscribers. - .andTee(() => pruneBufferedRequest(requestId)) + shouldRetry: () => !disposed && lastResponseRequestId === requestId, + }) .orElse(error => { + // Priority errors never reach here (see the policy note above), so this is a genuine + // failure. If this is no longer the latest response (superseded) or the session is + // disposed, keep the request marked answered โ€” re-answering would only clobber the + // newer response โ€” and absorb the error. NOTE: the shared response channel still only + // exposes the latest response to the peer, so reliably ACKing several outstanding + // requests needs the protocol-level fix tracked separately. + if (disposed || lastResponseRequestId !== requestId) return okAsync(undefined); + // The live response genuinely failed after exhausting retries โ€” roll back so a later + // peer retransmit can still be answered, and surface the error. incoming.responded = false; return errAsync(error); }) + // Answered (or absorbed as such): it no longer needs replaying to future subscribers. + .andTee(() => pruneBufferedRequest(requestId)) ); }, @@ -725,9 +732,17 @@ export function createSession({ // Supersede the live batch with an empty one. Use submitStatementData so the // empty statement goes out at a STRICTLY higher expiry โ€” the store rejects an - // equal-or-lower expiry on the same channel, so reusing state.expiry would - // leave the original request live on-chain. - return submitStatementData(createRequestChannel(outgoingSessionId), outgoingSessionId, encoded.value); + // equal-or-lower expiry on the same channel, so reusing the last allocated expiry + // would leave the original request live on-chain. One shot, no retry (clearing is a + // supersede, not a request that must land); a priority rejection (ExpiryTooLow / + // AccountFull) means the channel already advanced past us, so the clear already + // happened โ†’ absorb it as success. No retry loop here means no onPriorityError hook, so + // resync the allocator inline: adopt the chain floor before absorbing, so later submits stay above it. + return submitStatementData(requestChannel, outgoingSessionId, encoded.value).orElse(error => { + if (!isPriorityTooLow(error)) return errAsync(error); + allocator.raiseFloor(error.min); + return okAsync(undefined); + }); }, dispose() { @@ -744,9 +759,9 @@ export function createSession({ state.messageQueue = []; // Settle any waitForRequestMessage() promises so callers unwind instead of // hanging forever. Snapshot first โ€” rejecting mutates the set. - for (const rejectWaiter of [...requestWaiters]) rejectWaiter(new Error('Session disposed')); + for (const rejectWaiter of [...requestWaiters]) rejectWaiter(new Error(SESSION_DISPOSED)); requestWaiters.clear(); - rejectAllPending(new Error('Session disposed')); + rejectAllPending(new Error(SESSION_DISPOSED)); }, }; diff --git a/packages/statement-store/src/session/statementProver.ts b/packages/statement-store/src/session/statementProver.ts index 59450a78..cf5dc925 100644 --- a/packages/statement-store/src/session/statementProver.ts +++ b/packages/statement-store/src/session/statementProver.ts @@ -1,9 +1,9 @@ +import { fromHex } from '@novasamatech/scale'; import type { SignedStatement, Statement } from '@novasamatech/sdk-statement'; import { getStatementSigner, statementCodec } from '@novasamatech/sdk-statement'; -import { compact } from '@polkadot-api/substrate-bindings'; import type { ResultAsync } from 'neverthrow'; import { errAsync, fromPromise, fromThrowable, okAsync } from 'neverthrow'; -import { fromHex } from 'polkadot-api/utils'; +import { compact } from 'scale-ts'; import { deriveSlotAccountPublicKey, diff --git a/packages/statement-store/src/submit/allocator.spec.ts b/packages/statement-store/src/submit/allocator.spec.ts new file mode 100644 index 00000000..1ede79d1 --- /dev/null +++ b/packages/statement-store/src/submit/allocator.spec.ts @@ -0,0 +1,61 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { PRIORITY_EPOCH_OFFSET, createExpiryAllocator } from './allocator.js'; + +const NOW_SECS = 1_790_000_000; // 2026-09-22, safely past the 2025-11-15 priority epoch + +describe('createExpiryAllocator', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(NOW_SECS * 1000); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('pins the high 32 bits to 0xFFFFFFFF and counts the low word from the priority epoch', () => { + const allocator = createExpiryAllocator(); + + const expiry = allocator.next(); + + expect(expiry >> 32n).toBe(0xffff_ffffn); + expect(expiry & 0xffff_ffffn).toBe(BigInt(NOW_SECS) - PRIORITY_EPOCH_OFFSET); + }); + + it('is strictly monotonic within the same second', () => { + const allocator = createExpiryAllocator(); + + const first = allocator.next(); + const second = allocator.next(); + + expect(second).toBe(first + 1n); + }); + + it('jumps above a raised floor so the next submit clears the chain minimum', () => { + const allocator = createExpiryAllocator(); + const chainMin = (0xffff_ffffn << 32n) | 4_000_000_000n; // a poisoned account's minimum + + allocator.raiseFloor(chainMin); + + expect(allocator.next()).toBeGreaterThan(chainMin); + }); + + it('ignores a floor below the current value', () => { + const allocator = createExpiryAllocator(); + const before = allocator.next(); + + allocator.raiseFloor(0n); + + expect(allocator.next()).toBeGreaterThan(before); + }); + + it('keeps independent instances independent', () => { + const a = createExpiryAllocator(); + const b = createExpiryAllocator(); + + a.raiseFloor((0xffff_ffffn << 32n) | 4_000_000_000n); + + expect(b.next() & 0xffff_ffffn).toBe(BigInt(NOW_SECS) - PRIORITY_EPOCH_OFFSET); + }); +}); diff --git a/packages/statement-store/src/submit/allocator.ts b/packages/statement-store/src/submit/allocator.ts new file mode 100644 index 00000000..0b94d877 --- /dev/null +++ b/packages/statement-store/src/submit/allocator.ts @@ -0,0 +1,58 @@ +/** + * Statement expiry/priority, u64 = (expiration_epoch << 32) | priority (spec layout). + * We pin the high word to 0xFFFFFFFF (max โ†’ effectively non-expiring, matching iOS & + * Android) and use the low word as a wall-clock-floored monotonic priority, so channel + * supersession is driven by the priority regardless of how the store compares the field. + */ +const NEVER_EXPIRE_HIGH = 0xffffffffn; +/** + * Priority epoch base: seconds at 2025-11-15T00:00:00Z. The low word is a u32 priority counted FROM + * this epoch (spec ยง1), not the raw Unix timestamp. iOS (StatementPriorityFactory.unixOffset) and + * Android subtract the same offset; omitting it makes the TS low word ~1.76e9 larger than every + * mobile client's, so any cross-client or shared-channel priority comparison would always rank a + * TS-written statement above a mobile-written one. Keeping the base aligned removes that landmine. + */ +export const PRIORITY_EPOCH_OFFSET = 1_763_164_800n; + +/** Returns a value strictly greater than `current` (i.e. `max(current + 1, now-priority)`). */ +function nextExpiry(current: bigint): bigint { + const nowSecs = BigInt(Math.floor(Date.now() / 1000)); + const priority = nowSecs > PRIORITY_EPOCH_OFFSET ? nowSecs - PRIORITY_EPOCH_OFFSET : 0n; + const timestampPriority = (NEVER_EXPIRE_HIGH << 32n) | priority; + return timestampPriority > current ? timestampPriority : current + 1n; +} + +/** + * Strictly-increasing source of statement expiries for one signing account, + * with a floor that can be raised to a chain-reported minimum. + * + * Layout: u64 = (0xFFFFFFFF << 32) | priority โ€” see the module doc above. + * Supersession and account-quota eviction compare the whole u64 with + * strictly-greater semantics, so every writer signing with the SAME account + * must draw from ONE allocator instance: independent counters produce + * same-second priority ties that the store rejects. + */ +export type ExpiryAllocator = { + /** Next expiry: wall-clock-floored priority, bumped to stay strictly increasing. */ + next(): bigint; + /** + * Adopt a chain-reported minimum (`AccountFullError` / `ExpiryTooLowError` + * `.min`, or the max expiry seen in channel history) so the next `next()` + * clears it. The chain is the source of truth for the floor; recomputing + * from the wall clock can never clear a pinned-high minimum. + */ + raiseFloor(min: bigint): void; +}; + +export function createExpiryAllocator(): ExpiryAllocator { + let current = 0n; + return { + next() { + current = nextExpiry(current); + return current; + }, + raiseFloor(min) { + if (min > current) current = min; + }, + }; +} diff --git a/packages/statement-store/src/submit/retry.spec.ts b/packages/statement-store/src/submit/retry.spec.ts new file mode 100644 index 00000000..16231bbc --- /dev/null +++ b/packages/statement-store/src/submit/retry.spec.ts @@ -0,0 +1,166 @@ +import { errAsync, okAsync } from 'neverthrow'; +import { describe, expect, it, vi } from 'vitest'; + +import { AccountFullError, ExpiryTooLowError } from '../adapter/types.js'; + +import { isPriorityTooLow, submitWithRetry } from './retry.js'; + +const FAST = { delaysMs: 1 }; // keep wall-clock time negligible + +describe('isPriorityTooLow', () => { + it('matches exactly the two priority error classes', () => { + expect(isPriorityTooLow(new AccountFullError(0n, 1n))).toBe(true); + expect(isPriorityTooLow(new ExpiryTooLowError(0n, 1n))).toBe(true); + expect(isPriorityTooLow(new Error('store rejected'))).toBe(false); + }); +}); + +describe('submitWithRetry', () => { + it("priorityAttempts 'unbounded': priority errors retry past the non-priority budget until they land", async () => { + let calls = 0; + const submit = vi.fn(() => + ++calls <= 6 ? errAsync(new AccountFullError(0n, 1n)) : okAsync(undefined), + ); + + const result = await submitWithRetry(submit, { ...FAST, attempts: 3, priorityAttempts: 'unbounded' }); + + expect(result.isOk()).toBe(true); + expect(calls).toBe(7); + }); + + it("priorityAttempts 'unbounded': a no-longer-live priority rejection settles as success", async () => { + const submit = vi.fn(() => errAsync(new ExpiryTooLowError(0n, 1n))); + + const result = await submitWithRetry(submit, { + ...FAST, + attempts: 3, + priorityAttempts: 'unbounded', + shouldRetry: () => false, + }); + + expect(result.isOk()).toBe(true); // lost the channel race โ€” benign + expect(submit).toHaveBeenCalledTimes(1); + }); + + it('priorityAttempts budgeted: priority errors consume their budget then propagate', async () => { + const submit = vi.fn(() => errAsync(new AccountFullError(0n, 1n))); + + const result = await submitWithRetry(submit, { ...FAST, attempts: 0, priorityAttempts: 3 }); + + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr()).toBeInstanceOf(AccountFullError); + expect(submit).toHaveBeenCalledTimes(4); // 1 initial + 3 retries + }); + + it('onPriorityError fires for every priority rejection, including the terminal one once the budget is exhausted', async () => { + const seen: bigint[] = []; + let calls = 0; + const submit = vi.fn(() => errAsync(new AccountFullError(0n, BigInt(++calls)))); + + const result = await submitWithRetry(submit, { + ...FAST, + attempts: 0, + priorityAttempts: 2, + onPriorityError: error => seen.push(error.min), + }); + + expect(result.isErr()).toBe(true); + expect(submit).toHaveBeenCalledTimes(3); // 1 initial + 2 retries + expect(seen).toEqual([1n, 2n, 3n]); // adopted the floor on all three, including the terminal rejection + }); + + it('onPriorityError is not called for non-priority errors', async () => { + const onPriorityError = vi.fn(); + const submit = vi.fn(() => errAsync(new Error('store rejected'))); + + await submitWithRetry(submit, { ...FAST, attempts: 2, priorityAttempts: 'unbounded', onPriorityError }); + + expect(onPriorityError).not.toHaveBeenCalled(); + }); + + it('attempts 0: a non-priority error propagates immediately', async () => { + const submit = vi.fn(() => errAsync(new Error('store rejected'))); + + const result = await submitWithRetry(submit, { ...FAST, attempts: 0, priorityAttempts: 3 }); + + expect(result.isErr()).toBe(true); + expect(submit).toHaveBeenCalledTimes(1); + }); + + it('non-priority errors consume the attempts budget then propagate', async () => { + const submit = vi.fn(() => errAsync(new Error('store rejected'))); + + const result = await submitWithRetry(submit, { ...FAST, attempts: 2, priorityAttempts: 'unbounded' }); + + expect(result.isErr()).toBe(true); + expect(submit).toHaveBeenCalledTimes(3); // 1 initial + 2 retries + }); + + it('per-attempt delay schedule is honored and reported via onRetry', async () => { + let calls = 0; + const submit = vi.fn(() => + ++calls <= 2 ? errAsync(new AccountFullError(0n, 1n)) : okAsync(undefined), + ); + const seen: { attempt: number; delayMs: number }[] = []; + + const result = await submitWithRetry(submit, { + attempts: 0, + priorityAttempts: 3, + delaysMs: [1, 2, 3], + onRetry: ({ attempt, delayMs }) => seen.push({ attempt, delayMs }), + }); + + expect(result.isOk()).toBe(true); + expect(seen).toEqual([ + { attempt: 0, delayMs: 1 }, + { attempt: 1, delayMs: 2 }, + ]); + }); + + it('a negative budget propagates immediately instead of looping', async () => { + const submit = vi.fn(() => errAsync(new Error('store rejected'))); + + const result = await submitWithRetry(submit, { ...FAST, attempts: -1, priorityAttempts: 3 }); + + expect(result.isErr()).toBe(true); + expect(submit).toHaveBeenCalledTimes(1); + }); + + it('a shouldRetry flip during the backoff settles a priority rejection as success', async () => { + let live = true; + const submit = vi.fn(() => { + queueMicrotask(() => { + live = false; // superseded while the backoff sleep is pending + }); + return errAsync(new ExpiryTooLowError(0n, 1n)); + }); + + const result = await submitWithRetry(submit, { + ...FAST, + attempts: 0, + priorityAttempts: 'unbounded', + shouldRetry: () => live, + }); + + expect(result.isOk()).toBe(true); // settled after the delay, no second attempt + expect(submit).toHaveBeenCalledTimes(1); + }); + + it('shouldRetry is re-checked before each retry and stops the loop', async () => { + let live = true; + const submit = vi.fn(() => { + live = false; // superseded after the first attempt + return errAsync(new Error('store rejected')); + }); + + const result = await submitWithRetry(submit, { + ...FAST, + attempts: 3, + priorityAttempts: 'unbounded', + shouldRetry: () => live, + }); + + expect(result.isErr()).toBe(true); // non-priority + not live โ†’ propagate, no settle + expect(submit).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/statement-store/src/submit/retry.ts b/packages/statement-store/src/submit/retry.ts new file mode 100644 index 00000000..eb78b53d --- /dev/null +++ b/packages/statement-store/src/submit/retry.ts @@ -0,0 +1,90 @@ +import type { Result } from 'neverthrow'; +import { ResultAsync, err, ok } from 'neverthrow'; + +import { AccountFullError, ExpiryTooLowError } from '../adapter/types.js'; + +/** + * AccountFull / ExpiryTooLow are priority errors: never a chain/statement + * failure, only a sign the submitter's expiry lagged the chain's priority + * floor (channel supersession for ExpiryTooLow, account-quota eviction for + * AccountFull โ€” both report the minimum to clear). + */ +export function isPriorityTooLow(error: unknown): error is ExpiryTooLowError | AccountFullError { + return error instanceof ExpiryTooLowError || error instanceof AccountFullError; +} + +export type SubmitRetryOptions = { + /** Retry budget for non-priority (transient infra) errors. 0 = propagate immediately. */ + attempts: number; + /** + * Retry budget for priority errors. A number gives bounded retries then + * propagation (for callers with their own outer retry/outbox). 'unbounded' + * retries while `shouldRetry()` holds and, once it no longer does, settles a + * priority rejection as success โ€” the submission lost the channel race to a + * newer statement, which is benign (session semantics). + */ + priorityAttempts: number | 'unbounded'; + /** Backoff before each retry: a constant, or a per-retry schedule (last entry repeats). */ + delaysMs: number | number[]; + /** Liveness gate, re-checked before every retry. Default: always live. */ + shouldRetry?: () => boolean; + /** Observe each scheduled retry (logging hook). `attempt` is 0-based. */ + onRetry?: (info: { attempt: number; delayMs: number; error: Error }) => void; + /** + * Invoked on every priority rejection (ExpiryTooLow / AccountFull), before the retry + * decision โ€” so it fires even when the priority budget is exhausted or the submission is + * no longer live. This is where the caller adopts the chain-reported floor (`error.min`) + * into its allocator, so the NEXT attempt โ€” here or via an outer retry/outbox โ€” submits + * strictly above it and clears in one step rather than climbing. + */ + onPriorityError?: (error: ExpiryTooLowError | AccountFullError) => void; +}; + +function delayFor(delaysMs: number | number[], attempt: number): number { + if (typeof delaysMs === 'number') return delaysMs; + return delaysMs[Math.min(attempt, delaysMs.length - 1)] ?? 0; +} + +export function submitWithRetry( + submit: () => ResultAsync, + options: SubmitRetryOptions, +): ResultAsync { + const { attempts, priorityAttempts, delaysMs, shouldRetry = () => true, onRetry, onPriorityError } = options; + + // How to settle once we stop retrying: under the 'unbounded' policy a + // no-longer-live submission rejected with a priority error simply lost the + // channel race to a newer, higher-priority statement โ€” benign, so report success. + const settle = (error: Error): Result => + priorityAttempts === 'unbounded' && !shouldRetry() && isPriorityTooLow(error) ? ok() : err(error); + + // Iterative on purpose: a recursive ResultAsync chain holds one pending wrapper + // promise per attempt, which grows without bound under 'unbounded' retries. + const run = async (): Promise> => { + let attemptsLeft = attempts; + let priorityLeft = priorityAttempts; + for (let attempt = 0; ; attempt++) { + const result = await submit(); + if (result.isOk()) return result; + + const error = result.error; + const priorityError = isPriorityTooLow(error) ? error : null; + // Adopt the chain-reported floor before deciding whether to retry, so an exhausted or + // no-longer-live priority rejection still raises it for any outer retry/outbox. + if (priorityError) onPriorityError?.(priorityError); + const budgetLeft = priorityError ? priorityLeft : attemptsLeft; + if (!shouldRetry() || (typeof budgetLeft === 'number' && budgetLeft <= 0)) return settle(error); + + const delayMs = delayFor(delaysMs, attempt); + onRetry?.({ attempt, delayMs, error }); + if (priorityError) { + if (priorityLeft !== 'unbounded') priorityLeft -= 1; + } else { + attemptsLeft -= 1; + } + await new Promise(resolve => setTimeout(resolve, delayMs)); + if (!shouldRetry()) return settle(error); + } + }; + + return new ResultAsync(run()); +} diff --git a/packages/statement-store/src/submit/submitStatement.spec.ts b/packages/statement-store/src/submit/submitStatement.spec.ts new file mode 100644 index 00000000..254c52eb --- /dev/null +++ b/packages/statement-store/src/submit/submitStatement.spec.ts @@ -0,0 +1,124 @@ +import type { Statement } from '@novasamatech/sdk-statement'; +import { errAsync, okAsync } from 'neverthrow'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import type { StatementStoreAdapter } from '../adapter/types.js'; +import { AccountFullError } from '../adapter/types.js'; +import type { StatementProver } from '../session/statementProver.js'; + +import { createExpiryAllocator } from './allocator.js'; +import { signAndSubmitStatement, submitStatementOnce } from './submitStatement.js'; + +const NOW_SECS = 1_790_000_000; + +// Passthrough prover: "signs" by returning the statement unchanged. +const fakeProver = { + generateMessageProof: (stmt: Statement) => okAsync(stmt), +} as unknown as StatementProver; + +function makeStore(failFirstWithMin?: bigint) { + const submitted: Statement[] = []; + let calls = 0; + const adapter = { + submitStatement: vi.fn((stmt: Statement) => { + submitted.push(stmt); + calls += 1; + return failFirstWithMin !== undefined && calls === 1 + ? errAsync(new AccountFullError(stmt.expiry ?? 0n, failFirstWithMin)) + : okAsync(undefined); + }), + } as unknown as StatementStoreAdapter; + return { adapter, submitted }; +} + +const baseParams = (adapter: StatementStoreAdapter) => ({ + statementStore: adapter, + prover: fakeProver, + allocator: createExpiryAllocator(), + channel: new Uint8Array(32), + topics: [new Uint8Array(32)], + data: new Uint8Array([1]), +}); + +describe('submitStatementOnce', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(NOW_SECS * 1000); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('submits with the pinned-high expiry layout', async () => { + const { adapter, submitted } = makeStore(); + + const result = await submitStatementOnce(baseParams(adapter)); + + expect(result.isOk()).toBe(true); + expect(submitted).toHaveLength(1); + expect((submitted[0]!.expiry ?? 0n) >> 32n).toBe(0xffff_ffffn); + }); + + it('raises the allocator floor on a priority rejection so the next attempt clears the minimum', async () => { + const chainMin = (0xffff_ffffn << 32n) | 4_000_000_000n; + const { adapter, submitted } = makeStore(chainMin); + const params = baseParams(adapter); + + const first = await submitStatementOnce(params); + const second = await submitStatementOnce(params); + + expect(first.isErr()).toBe(true); + expect(second.isOk()).toBe(true); + expect(submitted[1]!.expiry ?? 0n).toBeGreaterThan(chainMin); // adopted min, not wall clock + }); +}); + +describe('signAndSubmitStatement', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(NOW_SECS * 1000); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('retries a priority rejection above the chain-reported minimum within the priority budget', async () => { + const chainMin = (0xffff_ffffn << 32n) | 4_000_000_000n; + const { adapter, submitted } = makeStore(chainMin); + + const promise = signAndSubmitStatement({ + ...baseParams(adapter), + retry: { attempts: 0, priorityAttempts: 3, delaysMs: [500, 1500, 3000] }, + }); + await vi.advanceTimersByTimeAsync(600); // cover the 500ms first backoff + const result = await promise; + + expect(result.isOk()).toBe(true); + expect(submitted).toHaveLength(2); + expect(submitted[1]!.expiry ?? 0n).toBeGreaterThan(chainMin); + }); + + it('propagates after exhausting the priority budget on a persistent rejection', async () => { + const submitted: Statement[] = []; + const adapter = { + submitStatement: vi.fn((stmt: Statement) => { + submitted.push(stmt); + // Chain min keeps rising above whatever we submit โ€” never lands. + return errAsync(new AccountFullError(stmt.expiry ?? 0n, (stmt.expiry ?? 0n) + 1_000_000n)); + }), + } as unknown as StatementStoreAdapter; + + const promise = signAndSubmitStatement({ + ...baseParams(adapter), + retry: { attempts: 0, priorityAttempts: 3, delaysMs: 1 }, + }); + await vi.advanceTimersByTimeAsync(50); + const result = await promise; + + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr()).toBeInstanceOf(AccountFullError); + expect(submitted).toHaveLength(4); // 1 initial + 3 priority retries + }); +}); diff --git a/packages/statement-store/src/submit/submitStatement.ts b/packages/statement-store/src/submit/submitStatement.ts new file mode 100644 index 00000000..f882cbb0 --- /dev/null +++ b/packages/statement-store/src/submit/submitStatement.ts @@ -0,0 +1,57 @@ +import { toHex } from '@novasamatech/scale'; +import type { Statement } from '@novasamatech/sdk-statement'; +import type { ResultAsync } from 'neverthrow'; + +import type { StatementStoreAdapter } from '../adapter/types.js'; +import type { StatementProver } from '../session/statementProver.js'; + +import type { ExpiryAllocator } from './allocator.js'; +import type { SubmitRetryOptions } from './retry.js'; +import { isPriorityTooLow, submitWithRetry } from './retry.js'; + +export type SubmitStatementParams = { + statementStore: StatementStoreAdapter; + prover: StatementProver; + /** + * Shared per-signing-account expiry source + **/ + allocator: ExpiryAllocator; + channel: Uint8Array; + topics: Uint8Array[]; + /** + * Opaque payload โ€” encryption (if any) is the caller's concern. + **/ + data: Uint8Array; +}; + +/** + * One submit attempt: allocate the next expiry, build and prove the + * statement, submit it, and on a priority rejection adopt the chain-reported + * minimum into the allocator so the NEXT attempt clears it. + */ +export function submitStatementOnce(params: SubmitStatementParams): ResultAsync { + const { statementStore, prover, allocator, channel, topics, data } = params; + const unsigned: Statement = { + expiry: allocator.next(), + channel: toHex(channel), + topics: topics.map(toHex), + data, + }; + return prover + .generateMessageProof(unsigned) + .andThen(statementStore.submitStatement) + .orTee(error => { + // The chain is the source of truth for the account/channel priority floor. + if (isPriorityTooLow(error)) { + allocator.raiseFloor(error.min); + } + }); +} + +export function signAndSubmitStatement(params: SubmitStatementParams & { retry: SubmitRetryOptions }) { + return submitWithRetry(() => submitStatementOnce(params), { + ...params.retry, + // Adopt the chain-reported floor so the next retry submits strictly above it and clears. + onPriorityError: error => params.allocator.raiseFloor(error.min), + }); +} diff --git a/packages/storage-adapter/package.json b/packages/storage-adapter/package.json index 93c9d09d..49625dca 100644 --- a/packages/storage-adapter/package.json +++ b/packages/storage-adapter/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/storage-adapter", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Statement store integration", "license": "Apache-2.0", "repository": { diff --git a/packages/substrate-slot-sr25519-wasm/package.json b/packages/substrate-slot-sr25519-wasm/package.json index 6a601d49..22f264be 100644 --- a/packages/substrate-slot-sr25519-wasm/package.json +++ b/packages/substrate-slot-sr25519-wasm/package.json @@ -1,7 +1,7 @@ { "name": "@novasamatech/substrate-slot-sr25519-wasm", "type": "module", - "version": "0.8.7-0", + "version": "0.8.7", "description": "Substrate sr25519 slot-account crypto (SecretKey::from_bytes) for mobile SlotAccountKey", "license": "Apache-2.0", "repository": {