diff --git a/.gitignore b/.gitignore index 3214ad8..60c65b8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,11 +8,14 @@ dist/ .playwright* !.env.example !web/.env.example +!web/.env.product-devnet !contracts/evm/.env.example web/node_modules/ web/dist/ web/dist-bulletin* +web/dist-product/ +web/*.car web/.playwright* web/playwright-report/ web/test-results/ diff --git a/CLAUDE.md b/CLAUDE.md index b1a96b0..7d211e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -250,6 +250,14 @@ cd web npm run smoke:production-env ``` +When Product DevNet endpoints, `deployments.json`, or the DevNet build profile +change, also run the read-only endpoint check: + +```bash +cd web +npm run smoke:devnet +``` + For deployment-sensitive work, add the relevant read-only health, chain, contract, IPFS gateway, and wrong-network checks. Record the environment and evidence without exposing credentials. diff --git a/README.md b/README.md index eed1f79..0875db9 100644 --- a/README.md +++ b/README.md @@ -34,20 +34,28 @@ aura lights the whole field (`web/src/styles/aura.css`). creates one personal `SmartRuntime` per artist, and `ArtistDirectory` indexes artist addresses to their runtimes. -**Frontend**: Static React + Vite web app deployed to dot.li. +**Frontend**: Static React + Vite web app deployed to Netlify and, through the +Product profile, Bulletin/DotNS at `dotify-test01.dot`. **WebRTC**: real-time music streaming. **Socket.IO**: signaling for room discovery and SDP/ICE exchange. A future iteration can move signaling to statement-store style infrastructure. -**Product SDK direction**: Dotify remains a standalone web app first. Product -SDK / Playground / Humanity work is a progressive-enhancement track documented -in -[`docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md`](docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md). -The current SDK snapshot is prototype/reference/unaudited and must be proven -against Dotify's Host, key-delivery, room, and contract constraints before it -becomes a production dependency. +**Product SDK direction**: Dotify now has an adaptive Product DevNet build for +`dotify-test01.dot`. It keeps standalone link-first rooms and Free listening intact, +adds explicit app-scoped Product identity, and publishes through +Bulletin/DotNS. The runtime hooks now sit behind typed ports with the current +viem implementation and an experimental Product CDM/PAPI adapter boundary. +The backend key-delivery protocol now has an explicit Product sr25519 +signature scheme that binds the Product account public key to the derived H160 +requester before access checks. The Product frontend can now submit that +Product proof after explicit host-account connection; contract writes remain +passkey/EVM until CDM-installed runtime packages and host-signed transaction +evidence are proven. See +[`docs/explanation/product-devnet-architecture.md`](docs/explanation/product-devnet-architecture.md) +and the +[`Product roadmap`](docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md). ## Deployed @@ -69,7 +77,7 @@ becomes a production dependency. **Gateway URL** — -**DotNS name** — `dotify.dot.li` +**DotNS name** — `dotify-test01.dev-dot.li` ## How to run end-to-end (locally) @@ -118,7 +126,8 @@ npm run dev | Variable | Required | Purpose | | --------------------------- | ---------------- | -------------------------------------------------------- | -| `API_ORIGIN` | Production | Frontend origin allowed by API CORS | +| `API_ORIGIN` | Compatibility | Singular frontend CORS origin fallback | +| `API_ORIGINS` | Production | Comma-separated exact frontend CORS origins | | `PASEO_ASSET_HUB_RPC` | Key requests | Paseo Asset Hub EVM RPC used for access checks | | `DOTIFY_DIRECTORY_ADDRESS` | Key requests | ArtistDirectory address used to resolve artist runtimes | | `DOTIFY_CHAIN_ID` | Key requests | Chain ID expected in wallet-signed key requests | @@ -298,6 +307,10 @@ releasing it. Gated tracks use a signed session or signed key request; the backend verifies the requester, resolves the artist runtime, and calls `musicAccCanAccess` before releasing a per-track key. If access is denied, the UI shows the action needed to unlock the track and plays no protected audio. +Standalone clients sign with the default `eip191` scheme. Product-host clients +can use `product-sr25519-v1` by signing the same canonical Dotify message bytes +with the app-scoped Product account and sending `productPublicKey`; the backend +derives the H160 requester from that public key before any nonce is consumed. For registered artist tracks, users without a connected wallet can play Free tracks. For gated tracks, they see a sign-in/unlock gate. Dev-account fallback @@ -430,9 +443,9 @@ handle: and decide whether a backend read-through gateway is needed. 4. Keep demo-mode browser-exposed Pinata/content secrets out of public deployments. -5. Run Product SDK feasibility spikes: Host detection, Product account signing, - resource allocation, Playground/Bulletin/DotNS deployment, and PolkaVM/CDM - contract portability. +5. Validate the Product host/account and Bulletin/DotNS deployment baseline, + then wire frontend Product-signed key/session requests, resource allocation, + and PolkaVM/CDM contract portability. 6. Add a production artist dashboard on `/artists`: release drafts, edit metadata, royalty analytics, and profile verification state. 7. Deploy and monitor a public signaling server for DotNS / Bulletin builds. diff --git a/contracts/evm/.cdm/cdm.d.ts b/contracts/evm/.cdm/cdm.d.ts new file mode 100644 index 0000000..d44f402 --- /dev/null +++ b/contracts/evm/.cdm/cdm.d.ts @@ -0,0 +1,37 @@ +// Auto-generated by cdm install -- do not edit +import type { HexString, SizedHex } from "polkadot-api"; + +declare module "@parity/cdm-codegen" { + interface CdmContracts { + "@dotify/artist-directory": { + methods: { + artistAtIndex: { args: [index: bigint]; response: HexString }; + artistCount: { args: []; response: bigint }; + artistsPage: { args: [offset: bigint, limit: bigint]; response: { artists: HexString[]; runtimes: HexString[] } }; + deployer: { args: []; response: HexString }; + factory: { args: []; response: HexString }; + register: { args: [artist: HexString, runtime: HexString]; response: undefined }; + runtimeOf: { args: [arg0: HexString]; response: HexString }; + setFactory: { args: [_factory: HexString]; response: undefined }; + }; + }; + "@dotify/artist-runtime-factory": { + methods: { + accessPallet: { args: []; response: HexString }; + createRuntime: { args: []; response: HexString }; + cutPallet: { args: []; response: HexString }; + directory: { args: []; response: HexString }; + initContract: { args: []; response: HexString }; + installRuntimeStep: { args: []; response: number }; + loupePallet: { args: []; response: HexString }; + nftPallet: { args: []; response: HexString }; + ownershipPallet: { args: []; response: HexString }; + pendingRuntimeOf: { args: [arg0: HexString]; response: HexString }; + pendingRuntimeStageOf: { args: [arg0: HexString]; response: number }; + registryPallet: { args: []; response: HexString }; + royaltiesPallet: { args: []; response: HexString }; + runtimeOf: { args: [artist: HexString]; response: HexString }; + }; + }; + } +} diff --git a/contracts/evm/.cdm/contracts.d.ts b/contracts/evm/.cdm/contracts.d.ts new file mode 100644 index 0000000..23f66df --- /dev/null +++ b/contracts/evm/.cdm/contracts.d.ts @@ -0,0 +1,37 @@ +// Auto-generated by cdm install -- do not edit +import type { HexString, SizedHex } from "polkadot-api"; + +declare module "@parity/product-sdk-contracts" { + interface Contracts { + "@dotify/artist-directory": { + methods: { + artistAtIndex: { args: [index: bigint]; response: HexString }; + artistCount: { args: []; response: bigint }; + artistsPage: { args: [offset: bigint, limit: bigint]; response: { artists: HexString[]; runtimes: HexString[] } }; + deployer: { args: []; response: HexString }; + factory: { args: []; response: HexString }; + register: { args: [artist: HexString, runtime: HexString]; response: undefined }; + runtimeOf: { args: [arg0: HexString]; response: HexString }; + setFactory: { args: [_factory: HexString]; response: undefined }; + }; + }; + "@dotify/artist-runtime-factory": { + methods: { + accessPallet: { args: []; response: HexString }; + createRuntime: { args: []; response: HexString }; + cutPallet: { args: []; response: HexString }; + directory: { args: []; response: HexString }; + initContract: { args: []; response: HexString }; + installRuntimeStep: { args: []; response: number }; + loupePallet: { args: []; response: HexString }; + nftPallet: { args: []; response: HexString }; + ownershipPallet: { args: []; response: HexString }; + pendingRuntimeOf: { args: [arg0: HexString]; response: HexString }; + pendingRuntimeStageOf: { args: [arg0: HexString]; response: number }; + registryPallet: { args: []; response: HexString }; + royaltiesPallet: { args: []; response: HexString }; + runtimeOf: { args: [artist: HexString]; response: HexString }; + }; + }; + } +} diff --git a/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/abi.json b/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/abi.json new file mode 100644 index 0000000..0448cfd --- /dev/null +++ b/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/abi.json @@ -0,0 +1,176 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "name": "FactorySet", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "artistAtIndex", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "artistCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "artistsPage", + "outputs": [ + { + "internalType": "address[]", + "name": "artists", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "runtimes", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deployer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "runtimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "setFactory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/info.json b/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/info.json new file mode 100644 index 0000000..d01ceb3 --- /dev/null +++ b/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/info.json @@ -0,0 +1,6 @@ +{ + "name": "@dotify/artist-directory", + "version": 0, + "address": "0xcf1534C6e2B0E43B9436c1e86A076466dC0F2108", + "metadataCid": "bafk2bzacebiynqo7tjvxa3xlvf6pphgq3nzatqjbn3yo2fszrlvxibtsr4ce6" +} \ No newline at end of file diff --git a/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/metadata.json b/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/metadata.json new file mode 100644 index 0000000..be8f5df --- /dev/null +++ b/contracts/evm/.cdm/contracts/@dotify/artist-directory/0/metadata.json @@ -0,0 +1,193 @@ +{ + "name": "@dotify/artist-directory", + "description": "Registry mapping each artist address to their owned SmartRuntime. Entry point for enumerating the Dotify catalog.", + "readme": "# @dotify/artist-directory\n\nMaps an artist address to the address of the SmartRuntime they own, and enumerates\nevery registered artist.\n\nStart here to read the Dotify catalog: `artistCount()` and `artistsPage(offset, limit)`\nenumerate artists with their runtimes, and `runtimeOf(artist)` resolves one directly.\nEach runtime then exposes its own tracks and access policy.\n\nRegistration is performed by the artist runtime factory, not by callers.", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "name": "FactorySet", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "artistAtIndex", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "artistCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "artistsPage", + "outputs": [ + { + "internalType": "address[]", + "name": "artists", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "runtimes", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deployer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "runtimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "setFactory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "homepage": "https://muzinga.netlify.app", + "repository": "https://github.com/knzeng-e/dotify", + "license": "MIT", + "authors": [ + "Dotify" + ], + "keywords": [ + "music", + "dotify", + "artist-runtime", + "access-control" + ] +} \ No newline at end of file diff --git a/contracts/evm/.cdm/contracts/@dotify/artist-directory/latest b/contracts/evm/.cdm/contracts/@dotify/artist-directory/latest new file mode 120000 index 0000000..c227083 --- /dev/null +++ b/contracts/evm/.cdm/contracts/@dotify/artist-directory/latest @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/abi.json b/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/abi.json new file mode 100644 index 0000000..1f380ce --- /dev/null +++ b/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/abi.json @@ -0,0 +1,316 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_directory", + "type": "address" + }, + { + "internalType": "address", + "name": "_initContract", + "type": "address" + }, + { + "internalType": "address", + "name": "_cutPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_loupePallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_ownershipPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_registryPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_royaltiesPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_accessPallet", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRuntimeBootstrapStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint8", + "name": "completedStage", + "type": "uint8" + } + ], + "name": "ArtistRuntimeBootstrapStep", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRuntimeCreated", + "type": "event" + }, + { + "inputs": [], + "name": "accessPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "createRuntime", + "outputs": [ + { + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cutPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "directory", + "outputs": [ + { + "internalType": "contract ArtistDirectory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "installRuntimeStep", + "outputs": [ + { + "internalType": "uint8", + "name": "completedStage", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "loupePallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nftPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ownershipPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pendingRuntimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pendingRuntimeStageOf", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registryPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "royaltiesPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "artist", + "type": "address" + } + ], + "name": "runtimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/info.json b/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/info.json new file mode 100644 index 0000000..7cd09ff --- /dev/null +++ b/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/info.json @@ -0,0 +1,6 @@ +{ + "name": "@dotify/artist-runtime-factory", + "version": 0, + "address": "0xBd1a11cFcE8B5Ef7a37E507bC5109895F8F42a72", + "metadataCid": "bafk2bzaceckm27ft3kvt4mjs67hzklylws5fp5d4z4nrvpa34gmzndtqz7lwg" +} \ No newline at end of file diff --git a/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/metadata.json b/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/metadata.json new file mode 100644 index 0000000..e2af81e --- /dev/null +++ b/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/0/metadata.json @@ -0,0 +1,333 @@ +{ + "name": "@dotify/artist-runtime-factory", + "description": "Deploys one artist-owned SmartRuntime per artist and registers it in the artist directory.", + "readme": "# @dotify/artist-runtime-factory\n\nDeploys a SmartRuntime for an artist and registers it in `@dotify/artist-directory`.\n\nA runtime is a diamond: music registry, royalties, access, and NFT pallets are\ninstalled as facets, and the artist is set as its owner. Because each artist owns\ntheir own runtime, catalog, access policy, and royalty splits stay under the\nartist's control rather than the platform's.\n\nCreation is staged - `createRuntime()` then `installRuntimeStep()` until\n`pendingRuntimeStageOf(artist)` reports completion - so that installation fits\nwithin block limits.", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_directory", + "type": "address" + }, + { + "internalType": "address", + "name": "_initContract", + "type": "address" + }, + { + "internalType": "address", + "name": "_cutPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_loupePallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_ownershipPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_registryPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_royaltiesPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_accessPallet", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRuntimeBootstrapStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint8", + "name": "completedStage", + "type": "uint8" + } + ], + "name": "ArtistRuntimeBootstrapStep", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRuntimeCreated", + "type": "event" + }, + { + "inputs": [], + "name": "accessPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "createRuntime", + "outputs": [ + { + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cutPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "directory", + "outputs": [ + { + "internalType": "contract ArtistDirectory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "installRuntimeStep", + "outputs": [ + { + "internalType": "uint8", + "name": "completedStage", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "loupePallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nftPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ownershipPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pendingRuntimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pendingRuntimeStageOf", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registryPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "royaltiesPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "artist", + "type": "address" + } + ], + "name": "runtimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "homepage": "https://muzinga.netlify.app", + "repository": "https://github.com/knzeng-e/dotify", + "license": "MIT", + "authors": [ + "Dotify" + ], + "keywords": [ + "music", + "dotify", + "artist-runtime", + "access-control" + ] +} \ No newline at end of file diff --git a/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/latest b/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/latest new file mode 120000 index 0000000..c227083 --- /dev/null +++ b/contracts/evm/.cdm/contracts/@dotify/artist-runtime-factory/latest @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/contracts/evm/.cdm/solidity/dotify/artist-directory.sol b/contracts/evm/.cdm/solidity/dotify/artist-directory.sol new file mode 100644 index 0000000..4f7be3c --- /dev/null +++ b/contracts/evm/.cdm/solidity/dotify/artist-directory.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.28; + +// Auto-generated by cdm install. Do not edit by hand. +// CDM package: @dotify/artist-directory +// CDM version: 0 + +interface IDotifyArtistDirectory { + event ArtistRegistered(address indexed artist, address indexed runtime); + event FactorySet(address indexed factory); + function artistAtIndex(uint256 index) external view returns (address); + function artistCount() external view returns (uint256); + function artistsPage(uint256 offset, uint256 limit) external view returns (address[] memory, address[] memory); + function deployer() external view returns (address); + function factory() external view returns (address); + function register(address artist, address runtime) external; + function runtimeOf(address arg0) external view returns (address); + function setFactory(address _factory) external; +} + +library DotifyArtistDirectory { + address internal constant ADDRESS = 0xcf1534C6e2B0E43B9436c1e86A076466dC0F2108; + + function ref() internal pure returns (IDotifyArtistDirectory) { + return IDotifyArtistDirectory(ADDRESS); + } + + function cdm() internal pure returns (IDotifyArtistDirectory) { + return ref(); + } +} diff --git a/contracts/evm/.cdm/solidity/dotify/artist-runtime-factory.sol b/contracts/evm/.cdm/solidity/dotify/artist-runtime-factory.sol new file mode 100644 index 0000000..91c8ed6 --- /dev/null +++ b/contracts/evm/.cdm/solidity/dotify/artist-runtime-factory.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.28; + +// Auto-generated by cdm install. Do not edit by hand. +// CDM package: @dotify/artist-runtime-factory +// CDM version: 0 + +interface IDotifyArtistRuntimeFactory { + event ArtistRuntimeBootstrapStarted(address indexed artist, address indexed runtime); + event ArtistRuntimeBootstrapStep(address indexed artist, address indexed runtime, uint8 indexed completedStage); + event ArtistRuntimeCreated(address indexed artist, address indexed runtime); + function accessPallet() external view returns (address); + function createRuntime() external returns (address); + function cutPallet() external view returns (address); + function directory() external view returns (address); + function initContract() external view returns (address); + function installRuntimeStep() external returns (uint8); + function loupePallet() external view returns (address); + function nftPallet() external view returns (address); + function ownershipPallet() external view returns (address); + function pendingRuntimeOf(address arg0) external view returns (address); + function pendingRuntimeStageOf(address arg0) external view returns (uint8); + function registryPallet() external view returns (address); + function royaltiesPallet() external view returns (address); + function runtimeOf(address artist) external view returns (address); +} + +library DotifyArtistRuntimeFactory { + address internal constant ADDRESS = 0xBd1a11cFcE8B5Ef7a37E507bC5109895F8F42a72; + + function ref() internal pure returns (IDotifyArtistRuntimeFactory) { + return IDotifyArtistRuntimeFactory(ADDRESS); + } + + function cdm() internal pure returns (IDotifyArtistRuntimeFactory) { + return ref(); + } +} diff --git a/contracts/evm/cdm.json b/contracts/evm/cdm.json new file mode 100644 index 0000000..b7560fa --- /dev/null +++ b/contracts/evm/cdm.json @@ -0,0 +1,511 @@ +{ + "dependencies": { + "@dotify/artist-directory": "latest", + "@dotify/artist-runtime-factory": "latest" + }, + "contracts": { + "@dotify/artist-directory": { + "version": 0, + "address": "0xcf1534C6e2B0E43B9436c1e86A076466dC0F2108", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "name": "FactorySet", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "artistAtIndex", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "artistCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "artistsPage", + "outputs": [ + { + "internalType": "address[]", + "name": "artists", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "runtimes", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deployer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "runtimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "setFactory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadataCid": "bafk2bzacebiynqo7tjvxa3xlvf6pphgq3nzatqjbn3yo2fszrlvxibtsr4ce6" + }, + "@dotify/artist-runtime-factory": { + "version": 0, + "address": "0xBd1a11cFcE8B5Ef7a37E507bC5109895F8F42a72", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_directory", + "type": "address" + }, + { + "internalType": "address", + "name": "_initContract", + "type": "address" + }, + { + "internalType": "address", + "name": "_cutPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_loupePallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_ownershipPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_registryPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_nftPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_royaltiesPallet", + "type": "address" + }, + { + "internalType": "address", + "name": "_accessPallet", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRuntimeBootstrapStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint8", + "name": "completedStage", + "type": "uint8" + } + ], + "name": "ArtistRuntimeBootstrapStep", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "artist", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "name": "ArtistRuntimeCreated", + "type": "event" + }, + { + "inputs": [], + "name": "accessPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "createRuntime", + "outputs": [ + { + "internalType": "address", + "name": "runtime", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cutPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "directory", + "outputs": [ + { + "internalType": "contract ArtistDirectory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "installRuntimeStep", + "outputs": [ + { + "internalType": "uint8", + "name": "completedStage", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "loupePallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nftPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ownershipPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pendingRuntimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pendingRuntimeStageOf", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registryPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "royaltiesPallet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "artist", + "type": "address" + } + ], + "name": "runtimeOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "metadataCid": "bafk2bzaceckm27ft3kvt4mjs67hzklylws5fp5d4z4nrvpa34gmzndtqz7lwg" + } + }, + "registry": "0x59b0245778917af55224e5f8fb55f7f8d452619f" +} diff --git a/contracts/evm/contracts/ArtistRuntimeFactory.sol b/contracts/evm/contracts/ArtistRuntimeFactory.sol index ab648d4..1b43c75 100644 --- a/contracts/evm/contracts/ArtistRuntimeFactory.sol +++ b/contracts/evm/contracts/ArtistRuntimeFactory.sol @@ -253,8 +253,12 @@ contract ArtistRuntimeFactory { selectors[4] = MusicRoyaltiesPallet.musicRoyTotalBps.selector; } + /// @dev The two registrar selectors are retained so already-deployed runtimes keep a + /// stable ABI; the setter now reverts. `musicAccPersonhoodInfo` is new, so a + /// runtime created before this change needs a diamond Add cut to gain it — its + /// access decisions already follow the precompile without it. function _musicAccessSelectors() private pure returns (bytes4[] memory selectors) { - selectors = new bytes4[](7); + selectors = new bytes4[](8); selectors[0] = MusicAccessPallet.setPersonhoodRegistrar.selector; selectors[1] = MusicAccessPallet.musicAccSetPersonhoodLevel.selector; selectors[2] = MusicAccessPallet.musicAccCanAccess.selector; @@ -262,5 +266,6 @@ contract ArtistRuntimeFactory { selectors[4] = MusicAccessPallet.musicAccPersonhoodLevel.selector; selectors[5] = MusicAccessPallet.musicAccHasPersonhood.selector; selectors[6] = MusicAccessPallet.musicAccGetRegistrar.selector; + selectors[7] = MusicAccessPallet.musicAccPersonhoodInfo.selector; } } diff --git a/contracts/evm/contracts/interfaces/IPersonhood.sol b/contracts/evm/contracts/interfaces/IPersonhood.sol new file mode 100644 index 0000000..09519c8 --- /dev/null +++ b/contracts/evm/contracts/interfaces/IPersonhood.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +/// @title IPersonhood — Proof of Personhood precompile (Individuality) +/// @notice Minimal interface for the `pallet-revive` personhood precompile, live on +/// Asset Hub at `0x000000000000000000000000000000000A010000`. +/// +/// Mirrors the canonical declaration in +/// `paseo-network/runtimes/precompiles/personhood/sol/IPersonhood.sol`. +/// Only `personhoodStatus` is declared here: Dotify reads status, it does not +/// verify raw ring proofs, so `personhoodInfoByProof` is deliberately omitted +/// rather than carried as unused surface. +/// +/// The precompile reads the alias-accounts pallet, which stores per-context +/// alias mappings backed by ring membership proofs. Ring roots arrive from the +/// People chain by XCM. +interface IPersonhood { + /// @param status Personhood tier: 0 = None, 1 = Lite, 2 = Full. Tiers are + /// incremental, so a future tier leaves these values unchanged. + /// @param contextAlias Per-context 32-byte pseudonym derived from the ring membership + /// proof. Unique per person per context, which is what prevents + /// cross-application linkability. Zero when status is None. + struct PersonhoodInfo { + uint8 status; + bytes32 contextAlias; + } + + /// @notice Personhood info for `account` within a specific application `context`. + /// @param context A fixed 32-byte application identifier. The same person yields a + /// different `contextAlias` under a different context. + function personhoodStatus(address account, bytes32 context) external view returns (PersonhoodInfo memory info); +} diff --git a/contracts/evm/contracts/libraries/LibMusicAccess.sol b/contracts/evm/contracts/libraries/LibMusicAccess.sol index 59adc1c..583b01c 100644 --- a/contracts/evm/contracts/libraries/LibMusicAccess.sol +++ b/contracts/evm/contracts/libraries/LibMusicAccess.sol @@ -2,11 +2,21 @@ pragma solidity ^0.8.28; import { LibMusicRegistry } from './LibMusicRegistry.sol'; +import { LibPersonhood } from './LibPersonhood.sol'; /// @title LibMusicAccess -/// @notice Namespaced storage for listener access records and proof-of-personhood levels. -/// Personhood levels mirror the Individuality Chain DIM tiers; in the current -/// prototype they are set via an admin registrar account. +/// @notice Namespaced storage for listener access records, and personhood gating read +/// from the Individuality precompile. +/// +/// Personhood is no longer stored here. `personhoodLevelOf` and +/// `personhoodRegistrar` remain declared so existing runtimes keep their storage +/// layout intact — a diamond cannot safely reorder occupied slots — but neither +/// participates in an access decision any more. The registrar defaulted to the +/// artist, which meant an artist could grant personhood to their own listeners; +/// the precompile removes that path. +/// +/// PersonhoodLevel maps onto the precompile tiers by ordinal: +/// None(0) -> None(0), DIM1(1) -> Lite(1), DIM2(2) -> Full(2). /// /// Storage slot: keccak256("smart.runtime.pallet.music-access.storage") library LibMusicAccess { @@ -15,9 +25,10 @@ library LibMusicAccess { struct Storage { // contentHash → listener → paid mapping(bytes32 => mapping(address => bool)) paidAccess; - // account → verified personhood tier + // DEPRECATED — no longer read for access. Kept to preserve the storage layout of + // already-deployed runtimes. Personhood now comes from the precompile. mapping(address => LibMusicRegistry.PersonhoodLevel) personhoodLevelOf; - // address authorised to set personhood levels + // DEPRECATED — see above. Retained for layout compatibility only. address personhoodRegistrar; } @@ -32,9 +43,19 @@ library LibMusicAccess { // Internal helpers // ------------------------------------------------------------------------- - function hasRequiredPersonhood(Storage storage s, address account, LibMusicRegistry.PersonhoodLevel required) internal view returns (bool) { - if (required == LibMusicRegistry.PersonhoodLevel.None) return true; - return uint8(s.personhoodLevelOf[account]) >= uint8(required); + /// @notice True when `account` meets `required` personhood, per the Individuality + /// precompile. The storage argument is unused and kept only so existing + /// call sites and the pallet ABI stay unchanged. + /// @dev Fails closed when the precompile cannot answer. See LibPersonhood.hasStatus. + function hasRequiredPersonhood(Storage storage, address account, LibMusicRegistry.PersonhoodLevel required) internal view returns (bool) { + return LibPersonhood.hasStatus(account, uint8(required)); + } + + /// @notice Dotify-context personhood tier and pseudonym for `account`. + /// @dev Exposes the alias so a runtime can later count distinct people rather than + /// distinct addresses. Not used for access decisions today. + function personhoodOf(address account) internal view returns (uint8 status, bytes32 contextAlias, bool live) { + return LibPersonhood.readStatus(account); } function setPersonhoodRegistrar(Storage storage s, address registrar) internal returns (address previousRegistrar) { diff --git a/contracts/evm/contracts/libraries/LibPersonhood.sol b/contracts/evm/contracts/libraries/LibPersonhood.sol new file mode 100644 index 0000000..aaab23c --- /dev/null +++ b/contracts/evm/contracts/libraries/LibPersonhood.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import { IPersonhood } from '../interfaces/IPersonhood.sol'; + +/// @title LibPersonhood +/// @notice Reads proof of personhood from the Individuality precompile. +/// +/// This replaces Dotify's admin-registrar personhood, which could only ever be +/// as trustworthy as the account operating it — and that account defaults to +/// the artist, who therefore had the technical ability to grant personhood to +/// their own listeners. Reading the precompile removes that forgery path +/// entirely: personhood becomes a fact about a person on the People chain, not +/// a row an operator can write. +/// +/// It also earns Dotify a property the registrar could not offer. The precompile +/// returns a per-context alias, so the same listener appears under a different +/// pseudonym in every application. Dotify learns "this is a distinct person" +/// without learning who they are anywhere else. +library LibPersonhood { + /// @dev Fixed precompile address. `pallet-revive` left-shifts the user-defined + /// `AddressMatcher::Fixed(0x0A01)` index by 16 bits to form this suffix. + /// Verified live on EVM chain 420420417 (Paseo Asset Hub, para 1000): a call + /// returns a 64-byte PersonhoodInfo, where absent addresses return empty. + address internal constant PERSONHOOD_PRECOMPILE = 0x000000000000000000000000000000000a010000; + + /// @dev Dotify's application context. Fixed forever: changing it re-pseudonymises + /// every listener, so any change is an identity migration, not a config edit. + bytes32 internal constant DOTIFY_CONTEXT = bytes32('dotify'); + + uint8 internal constant STATUS_NONE = 0; + uint8 internal constant STATUS_LITE = 1; + uint8 internal constant STATUS_FULL = 2; + + /// @notice Read personhood for `account` in Dotify's context. + /// @return status Personhood tier, or 0 when the precompile is unavailable. + /// @return alias_ Per-context pseudonym, zero when status is 0. + /// @return live True when the precompile answered with a decodable struct. + /// + /// @dev Deliberately a low-level staticcall rather than a typed call. The precompile + /// declares `HAS_CONTRACT_INFO = false`, so its `extcodesize` can be zero, and + /// Solidity's high-level call inserts an `extcodesize` check that would revert + /// against it. The staticcall also lets a chain without the precompile — a local + /// Hardhat node — resolve to "not live" instead of reverting every access query. + /// + /// `live` is returned rather than swallowed so callers can distinguish "this + /// person has no personhood" from "this chain cannot answer". Those are the same + /// decision (deny) but not the same diagnosis, and conflating them is how a + /// misconfigured deployment gets mistaken for an empty user base. + function readStatus(address account) internal view returns (uint8 status, bytes32 alias_, bool live) { + bytes memory callData = abi.encodeWithSelector(IPersonhood.personhoodStatus.selector, account, DOTIFY_CONTEXT); + + (bool ok, bytes memory returnData) = PERSONHOOD_PRECOMPILE.staticcall(callData); + + // A call to an address with no code succeeds with empty returndata, so success + // alone proves nothing. Only a full struct counts as an answer. + if (!ok || returnData.length < 64) { + return (STATUS_NONE, bytes32(0), false); + } + + IPersonhood.PersonhoodInfo memory info = abi.decode(returnData, (IPersonhood.PersonhoodInfo)); + return (info.status, info.contextAlias, true); + } + + /// @notice True when `account` holds at least `requiredStatus` in Dotify's context. + /// @dev Fails closed: an unavailable precompile denies every gated track rather than + /// admitting everyone. Product invariant — ambiguous access decisions fail closed. + function hasStatus(address account, uint8 requiredStatus) internal view returns (bool) { + if (requiredStatus == STATUS_NONE) return true; + (uint8 status, , bool live) = readStatus(account); + if (!live) return false; + return status >= requiredStatus; + } +} diff --git a/contracts/evm/contracts/pallets/MusicAccessPallet.sol b/contracts/evm/contracts/pallets/MusicAccessPallet.sol index 27e72f8..a2afafe 100644 --- a/contracts/evm/contracts/pallets/MusicAccessPallet.sol +++ b/contracts/evm/contracts/pallets/MusicAccessPallet.sol @@ -15,10 +15,17 @@ import { LibMusicNFT } from '../libraries/LibMusicNFT.sol'; /// 3. HumanFree track → granted if caller meets personhood level /// 4. Classic track → granted if caller has paid /// -/// Personhood levels (DIM1, DIM2) are set by the personhood registrar — -/// an admin account that in production will mirror the Individuality Chain. -/// The SmartRuntime owner remains the only account allowed to update -/// the registrar assignment. +/// Personhood (DIM1, DIM2) is read from the Individuality precompile at +/// 0x000000000000000000000000000000000A010000, in Dotify's own application +/// context. DIM1 maps to Lite, DIM2 to Full. +/// +/// The former admin registrar is retired. It defaulted to the artist, so an +/// artist could grant personhood to their own listeners — which made +/// `human-free` a claim the contract could not actually support. Reading the +/// precompile removes that path, and adds a per-context alias so Dotify can +/// recognise a distinct person without learning who they are in any other +/// application. Registrar entry points remain declared for ABI stability; +/// the setter reverts. /// /// Storage: LibMusicAccess (owns), LibMusicRegistry (reads), LibMusicNFT (reads) /// Prefix: musicAcc — avoids selector collisions with other pallets @@ -47,14 +54,14 @@ contract MusicAccessPallet { // Personhood level management (registrar-only) // ------------------------------------------------------------------------- - /// @notice Set the proof-of-personhood level for an account. - /// In production this would be called by an oracle reading the Individuality Chain. - function musicAccSetPersonhoodLevel(address account, LibMusicRegistry.PersonhoodLevel level) external { - LibMusicAccess.Storage storage as_ = LibMusicAccess.store(); - LibMusicAccess.requireRegistrar(as_); - require(account != address(0), 'MusicAccess: zero address'); - as_.personhoodLevelOf[account] = level; - emit MusicAccPersonhoodLevelSet(account, level); + /// @notice DEPRECATED — personhood is read from the Individuality precompile and can + /// no longer be assigned by an operator. + /// @dev Reverts rather than writing to storage no access decision reads. Accepting a + /// write that silently changes nothing would leave an operator believing a + /// listener was granted access they do not have. The parameters are retained so + /// the selector and ABI stay stable for already-deployed runtimes. + function musicAccSetPersonhoodLevel(address, LibMusicRegistry.PersonhoodLevel) external pure { + revert('MusicAccess: personhood is read from the Individuality precompile'); } // ------------------------------------------------------------------------- @@ -89,9 +96,24 @@ contract MusicAccessPallet { return LibMusicAccess.store().paidAccess[contentHash][listener]; } - /// @notice Returns the verified personhood level for `account`. + /// @notice Returns the verified personhood level for `account`, read from the + /// Individuality precompile in Dotify's application context. + /// @dev Returns None when the precompile is unavailable, matching the access + /// decision. Use `musicAccPersonhoodInfo` to tell those two cases apart. function musicAccPersonhoodLevel(address account) external view returns (LibMusicRegistry.PersonhoodLevel) { - return LibMusicAccess.store().personhoodLevelOf[account]; + (uint8 status, , bool live) = LibMusicAccess.personhoodOf(account); + if (!live) return LibMusicRegistry.PersonhoodLevel.None; + return LibMusicRegistry.PersonhoodLevel(status); + } + + /// @notice Full personhood reading for `account`: tier, Dotify-context pseudonym, and + /// whether the precompile answered at all. + /// @dev `live == false` means this chain cannot answer, which is a deployment + /// diagnosis, not a statement about the listener. `contextAlias` is the same + /// person under a different pseudonym in every other application, so it can + /// identify a returning listener without revealing who they are elsewhere. + function musicAccPersonhoodInfo(address account) external view returns (uint8 status, bytes32 contextAlias, bool live) { + return LibMusicAccess.personhoodOf(account); } /// @notice Returns true if `account` meets `required` personhood level. diff --git a/contracts/evm/contracts/test/MockPersonhoodPrecompile.sol b/contracts/evm/contracts/test/MockPersonhoodPrecompile.sol new file mode 100644 index 0000000..97be2e7 --- /dev/null +++ b/contracts/evm/contracts/test/MockPersonhoodPrecompile.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +/// @dev TEST-ONLY stand-in for the Individuality personhood precompile. +/// +/// The real precompile lives at a fixed address inside `pallet-revive` and has no +/// deployable bytecode, so a Hardhat node cannot host it. Tests install this +/// contract's runtime code at that same address with `hardhat_setCode`, then write +/// its storage through the ordinary setter below — the storage lands under the +/// precompile address, so the runtime's staticcall reads it exactly as it would +/// read the real thing. +/// +/// This is the only way to exercise the fail-closed path and the granted path on a +/// chain that has no Individuality pallet. +contract MockPersonhoodPrecompile { + struct PersonhoodInfo { + uint8 status; + bytes32 contextAlias; + } + + // account => context => info + mapping(address => mapping(bytes32 => PersonhoodInfo)) private _info; + + /// @notice Set the personhood reading returned for `account` in `context`. + function setPersonhood(address account, bytes32 context, uint8 status, bytes32 contextAlias) external { + _info[account][context] = PersonhoodInfo({ status: status, contextAlias: contextAlias }); + } + + /// @notice Matches the real precompile's signature and return shape. + function personhoodStatus(address account, bytes32 context) external view returns (PersonhoodInfo memory info) { + return _info[account][context]; + } +} diff --git a/contracts/evm/hardhat.config.ts b/contracts/evm/hardhat.config.ts index cfa5c24..1b4af55 100644 --- a/contracts/evm/hardhat.config.ts +++ b/contracts/evm/hardhat.config.ts @@ -2,6 +2,7 @@ import type { HardhatUserConfig } from 'hardhat/config'; import '@nomicfoundation/hardhat-viem'; import '@nomicfoundation/hardhat-verify'; import './tasks/registryUpgrade'; +import './tasks/cdmPublish'; const config: HardhatUserConfig = { solidity: { diff --git a/contracts/evm/package.json b/contracts/evm/package.json index 40c6f18..66be05a 100644 --- a/contracts/evm/package.json +++ b/contracts/evm/package.json @@ -18,7 +18,8 @@ "registry:deploy-facet:testnet": "npx hardhat registry:deploy-facet --network polkadotTestnet", "registry:upgrade:testnet": "npx hardhat registry:upgrade --network polkadotTestnet", "fmt": "prettier --plugin=prettier-plugin-solidity --write 'contracts/**/*.sol' 'scripts/**/*.ts' 'tasks/**/*.ts' 'test/**/*.ts' hardhat.config.ts", - "fmt:check": "prettier --plugin=prettier-plugin-solidity --check 'contracts/**/*.sol' 'scripts/**/*.ts' 'tasks/**/*.ts' 'test/**/*.ts' hardhat.config.ts" + "fmt:check": "prettier --plugin=prettier-plugin-solidity --check 'contracts/**/*.sol' 'scripts/**/*.ts' 'tasks/**/*.ts' 'test/**/*.ts' hardhat.config.ts", + "cdm:publish:testnet": "npx hardhat cdm:publish --network polkadotTestnet" }, "devDependencies": { "@nomicfoundation/hardhat-network-helpers": "^1.0.0", diff --git a/contracts/evm/tasks/cdmPublish.ts b/contracts/evm/tasks/cdmPublish.ts new file mode 100644 index 0000000..fa25492 --- /dev/null +++ b/contracts/evm/tasks/cdmPublish.ts @@ -0,0 +1,277 @@ +// Register Dotify's already-deployed contracts in the Product CDM registry. +// +// Why a task instead of `cdm deploy`: +// +// `cdm deploy` builds, deploys, publishes metadata, and registers in one pass. Dotify's +// contracts are already deployed and already hold the live catalog, so deploying again +// would mint new addresses and orphan every existing artist runtime. The registry's +// `publishLatest(name, address, metadataUri)` registers a name against an arbitrary +// address, which is exactly the operation Dotify needs and the one the CLI does not +// expose on its own. +// +// Registration is first-writer-owns and there is no release or transfer entry point in +// the registry contract, so claiming a name is effectively permanent. This task is +// therefore read-only by default: it reports what it would do and stops. Execution +// requires --confirm plus an explicit private key. + +import { task, types } from 'hardhat/config'; +import type { HardhatRuntimeEnvironment } from 'hardhat/types'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { createPublicClient, createWalletClient, encodeFunctionData, getAddress, http, type Address, type Hex } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import { POLKADOT_TESTNET_CHAIN, readDeployments } from '../scripts/smartRuntime'; + +/// Written by `npm run generate:cdm-metadata` in web/. Holds the deterministic CID of +/// each package's metadata blob, so the CID published on-chain is derived from the +/// generated bytes rather than pasted by hand. +const CID_INDEX_PATH = path.resolve(__dirname, '../../../web/src/generated/contracts/cdm-metadata/cids.json'); + +type CidIndex = Record; + +function readCidIndex(): CidIndex | null { + try { + return JSON.parse(fs.readFileSync(CID_INDEX_PATH, 'utf8')) as CidIndex; + } catch { + return null; + } +} + +/// Community-operated ContractRegistry for the Product `devnet` preset, i.e. Paseo +/// testnet Asset Hub (para 1000, EVM chain 420420417). Sourced from +/// paritytech/contract-dependency-manager `src/lib/env/src/registry.ts`. +/// +/// Deliberately not the `paseo` preset registry: CDM's own docs note that `paseo` +/// targets paseo-next (para 1500), a different network that holds no Dotify contracts. +const DEVNET_REGISTRY_ADDRESS = '0x59b0245778917af55224e5f8fb55f7f8d452619f' as const; + +const REGISTRY_ABI = [ + { + type: 'function', + name: 'publishLatest', + stateMutability: 'nonpayable', + inputs: [ + { name: 'contract_name', type: 'string' }, + { name: 'contract_address', type: 'address' }, + { name: 'metadata_uri', type: 'string' } + ], + outputs: [] + }, + { + type: 'function', + name: 'getAddress', + stateMutability: 'view', + inputs: [{ name: 'contract_name', type: 'string' }], + outputs: [ + { + name: '', + type: 'tuple', + components: [ + { name: 'isSome', type: 'bool' }, + { name: 'value', type: 'address' } + ] + } + ] + }, + { + type: 'function', + name: 'getOwner', + stateMutability: 'view', + inputs: [{ name: 'contract_name', type: 'string' }], + outputs: [ + { + name: '', + type: 'tuple', + components: [ + { name: 'isSome', type: 'bool' }, + { name: 'value', type: 'address' } + ] + } + ] + } +] as const; + +/// Only fixed-address contracts can be registered. Artist runtimes are per-artist +/// diamonds with no single address, so `@dotify/smart-runtime` is intentionally absent: +/// registering one artist's runtime under a shared name would misrepresent the catalog. +const PACKAGES = [ + { name: '@dotify/artist-directory', deploymentKey: 'directory' as const }, + { name: '@dotify/artist-runtime-factory', deploymentKey: 'factory' as const } +]; + +type PackagePlan = { + name: string; + address: Address; + hasCode: boolean; + registeredAddress: Address | null; + owner: Address | null; + action: 'register' | 'already-current' | 'blocked-owned-by-other' | 'update-version' | 'blocked-no-code'; + detail: string; +}; + +/// The registry returns Rust `Option
` as a `(bool, address)` tuple. viem may +/// surface it as either an array or a named object depending on ABI shape, so accept +/// both rather than depending on that detail. +function optional(result: unknown): Address | null { + if (Array.isArray(result)) { + return result[0] ? getAddress(result[1] as string) : null; + } + if (result && typeof result === 'object' && 'isSome' in result) { + const { isSome, value } = result as { isSome: boolean; value: string }; + return isSome ? getAddress(value) : null; + } + return null; +} + +async function buildPlan(hre: HardhatRuntimeEnvironment, registry: Address, signer: Address | null): Promise { + const rpcUrl = (hre.network.config as { url?: string }).url; + const publicClient = createPublicClient({ chain: POLKADOT_TESTNET_CHAIN, transport: http(rpcUrl) }); + const deployments = readDeployments(); + + const plans: PackagePlan[] = []; + + for (const pkg of PACKAGES) { + const configured = deployments[pkg.deploymentKey]; + if (!configured) { + throw new Error(`deployments.json has no "${pkg.deploymentKey}" address. Deploy before registering.`); + } + const address = getAddress(configured); + + const code = await publicClient.getCode({ address }); + const hasCode = Boolean(code && code !== '0x'); + + const [registeredRaw, ownerRaw] = await Promise.all([ + publicClient.readContract({ address: registry, abi: REGISTRY_ABI, functionName: 'getAddress', args: [pkg.name] }), + publicClient.readContract({ address: registry, abi: REGISTRY_ABI, functionName: 'getOwner', args: [pkg.name] }).catch(() => null) + ]); + + const registeredAddress = optional(registeredRaw); + const owner = ownerRaw ? optional(ownerRaw) : null; + + let action: PackagePlan['action']; + let detail: string; + + if (!hasCode) { + action = 'blocked-no-code'; + detail = `${address} has no bytecode on this chain. Registering it would publish a dead pointer.`; + } else if (registeredAddress === null) { + action = 'register'; + detail = `name is free; first publisher becomes its permanent owner`; + } else if (owner && signer && owner.toLowerCase() !== signer.toLowerCase()) { + action = 'blocked-owned-by-other'; + detail = `already owned by ${owner}; the registry rejects a publish from any other account`; + } else if (registeredAddress.toLowerCase() === address.toLowerCase()) { + action = 'already-current'; + detail = `already points at ${address}; nothing to do`; + } else { + action = 'update-version'; + detail = `currently ${registeredAddress}; publishing appends a new version pointing at ${address}`; + } + + plans.push({ name: pkg.name, address, hasCode, registeredAddress, owner, action, detail }); + } + + return plans; +} + +task('cdm:publish', 'Register Dotify contracts in the Product CDM registry. Read-only unless --confirm is passed.') + .addOptionalParam('registry', 'ContractRegistry address', DEVNET_REGISTRY_ADDRESS, types.string) + .addOptionalParam('metadataUri', 'Override the metadata pointer for every package. Defaults to each package generated CID.', '', types.string) + .addOptionalParam('privateKey', 'Override the publisher key. Normally unnecessary: the configured PRIVATE_KEY hardhat var is used.', '', types.string) + .addFlag('confirm', 'Actually submit the registration transactions') + .setAction(async (args, hre: HardhatRuntimeEnvironment) => { + const registry = getAddress(args.registry as string); + const rpcUrl = (hre.network.config as { url?: string }).url; + const publicClient = createPublicClient({ chain: POLKADOT_TESTNET_CHAIN, transport: http(rpcUrl) }); + + const registryCode = await publicClient.getCode({ address: registry }); + if (!registryCode || registryCode === '0x') { + throw new Error(`No ContractRegistry at ${registry} on this network. Check --registry and the RPC endpoint.`); + } + + // The publisher is not just paying fees: publish_latest records `caller` as the + // permanent owner of every name it creates. So this key decides who owns @dotify/* + // for good, which is why it is resolved explicitly and reported before any write. + // + // Default to the account hardhat already has for this network - sourced from the + // encrypted `PRIVATE_KEY` var - rather than asking for a key on the command line, + // where it would land in shell history and process listings. + const overrideAccount = args.privateKey ? privateKeyToAccount(args.privateKey as Hex) : null; + const [configuredWallet] = overrideAccount ? [] : await hre.viem.getWalletClients({ chain: POLKADOT_TESTNET_CHAIN }); + const account = overrideAccount ?? configuredWallet?.account ?? null; + + const plans = await buildPlan(hre, registry, account?.address ?? null); + + const cidIndex = readCidIndex(); + const override = (args.metadataUri as string) || null; + const metadataFor = (name: string): string => override ?? cidIndex?.[name]?.cid ?? ''; + + if (!override && !cidIndex) { + console.log('No generated CID index found. Run `npm run generate:cdm-metadata` in web/ so consumers can fetch an ABI.\n'); + } + + console.log(`\nCDM registry: ${registry}`); + console.log(`Chain: ${await publicClient.getChainId()}`); + console.log(`Publisher: ${account?.address ?? '(none configured — read-only plan)'}${overrideAccount ? ' (--private-key override)' : ''}`); + console.log(` this account becomes the permanent owner of any name it registers\n`); + + for (const plan of plans) { + console.log(`${plan.name}`); + console.log(` address: ${plan.address}`); + console.log(` action: ${plan.action}`); + console.log(` detail: ${plan.detail}`); + console.log(` metadata: ${metadataFor(plan.name) || '(none — cdm install will have no ABI to fetch)'}`); + if (!args.confirm) { + console.log( + ` calldata: ${encodeFunctionData({ + abi: REGISTRY_ABI, + functionName: 'publishLatest', + args: [plan.name, plan.address, metadataFor(plan.name)] + })}` + ); + } + console.log(''); + } + + const blocked = plans.filter(plan => plan.action.startsWith('blocked')); + if (blocked.length > 0) { + throw new Error(`Refusing to proceed: ${blocked.map(plan => `${plan.name} (${plan.action})`).join(', ')}`); + } + + const actionable = plans.filter(plan => plan.action === 'register' || plan.action === 'update-version'); + + if (!args.confirm) { + console.log( + actionable.length === 0 + ? 'Nothing to publish — every name already resolves to the configured address.' + : `Dry run. ${actionable.length} name(s) would be published. Re-run with --confirm to submit.\n` + + 'Registration is first-writer-owns and the registry has no release or transfer entry point, so a claimed name is permanent.' + ); + return; + } + + if (!account) { + throw new Error( + 'No publisher account for this network. Set one with `npx hardhat vars set PRIVATE_KEY`, ' + 'or pass --private-key to override it for this run.' + ); + } + if (actionable.length === 0) { + console.log('Nothing to publish.'); + return; + } + + const walletClient = configuredWallet ?? createWalletClient({ account, chain: POLKADOT_TESTNET_CHAIN, transport: http(rpcUrl) }); + + for (const plan of actionable) { + const hash = await walletClient.writeContract({ + address: registry, + abi: REGISTRY_ABI, + functionName: 'publishLatest', + args: [plan.name, plan.address, metadataFor(plan.name)] + }); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + console.log(`${plan.name} -> ${plan.address} tx ${hash} ${receipt.status}`); + } + + console.log('\nPublished. Verify with: cdm i -n devnet ' + actionable.map(plan => plan.name).join(' ')); + }); diff --git a/contracts/evm/test/ArtistRuntime.test.ts b/contracts/evm/test/ArtistRuntime.test.ts index 05961fa..0b804c4 100644 --- a/contracts/evm/test/ArtistRuntime.test.ts +++ b/contracts/evm/test/ArtistRuntime.test.ts @@ -29,6 +29,20 @@ import { MUSIC_REGISTRY_REGISTER_SELECTOR, buildRegistryHotfixCalldata, registry const FacetCutAction = { Add: 0, Replace: 1, Remove: 2 } as const; const AccessMode = { HumanFree: 0, Classic: 1, Free: 2 } as const; const PersonhoodLevel = { None: 0, DIM1: 1, DIM2: 2 } as const; + +// The Individuality personhood precompile. Fixed inside pallet-revive, so a Hardhat +// node has nothing there until a test installs mock code at the same address. +const PERSONHOOD_PRECOMPILE = '0x000000000000000000000000000000000a010000' as const; +const DOTIFY_CONTEXT = `0x${Buffer.from('dotify').toString('hex').padEnd(64, '0')}` as `0x${string}`; + +/** Install the mock precompile at the real precompile address and return a handle. */ +async function installPersonhoodPrecompile() { + const mock = await hre.viem.deployContract('MockPersonhoodPrecompile'); + const publicClient = await hre.viem.getPublicClient(); + const runtimeCode = await publicClient.getCode({ address: mock.address }); + await hre.network.provider.request({ method: 'hardhat_setCode', params: [PERSONHOOD_PRECOMPILE, runtimeCode] }); + return hre.viem.getContractAt('MockPersonhoodPrecompile', PERSONHOOD_PRECOMPILE); +} const ZERO_ADDR = '0x0000000000000000000000000000000000000000' as const; function selectorsFromAbi(abi: Abi): `0x${string}`[] { @@ -251,7 +265,10 @@ describe('DotifyRuntimeInitializer — bootstrap', () => { expect(registrar.toLowerCase()).to.equal(artistA.account.address.toLowerCase()); }); - it('owner can reassign the personhood registrar and the new registrar can grant levels', async () => { + it('no registrar can grant personhood any more, not even the artist', async () => { + // The registrar defaulted to the artist, so this path let an artist manufacture + // personhood for their own listeners. It must now fail loudly rather than write + // to storage that no access decision reads. const { factory, directory, artistA, other, listener } = await loadFixture(deployDotifySystemFixture); await createArtistRuntime(factory, artistA); @@ -260,13 +277,14 @@ describe('DotifyRuntimeInitializer — bootstrap', () => { const artistAccess = await hre.viem.getContractAt('MusicAccessPallet', runtimeAddr, { client: { wallet: artistA } }); await artistAccess.write.setPersonhoodRegistrar([other.account.address]); - const access = await hre.viem.getContractAt('MusicAccessPallet', runtimeAddr); - expect((await access.read.musicAccGetRegistrar()).toLowerCase()).to.equal(other.account.address.toLowerCase()); - const delegatedRegistrar = await hre.viem.getContractAt('MusicAccessPallet', runtimeAddr, { client: { wallet: other } }); - await delegatedRegistrar.write.musicAccSetPersonhoodLevel([listener.account.address, PersonhoodLevel.DIM1]); - expect(await access.read.musicAccPersonhoodLevel([listener.account.address])).to.equal(PersonhoodLevel.DIM1); + try { + await delegatedRegistrar.write.musicAccSetPersonhoodLevel([listener.account.address, PersonhoodLevel.DIM1]); + expect.fail('Should have reverted'); + } catch (e: unknown) { + expect((e as Error).message).to.include('Individuality precompile'); + } }); it('delegated registrar cannot rotate itself; only the owner can update the registrar', async () => { @@ -365,8 +383,8 @@ describe('Artist SmartRuntime — music pallets', () => { expect((await publicClient.getBalance({ address: royaltyRecip.account.address })) > recipBefore).to.equal(true); }); - it('HumanFree track: access granted after artist sets DIM1 personhood', async () => { - const { registry, royalties, access, artistA, listener, royaltyRecip } = await withArtistRuntime(); + it('HumanFree track: access follows the Individuality precompile, not the artist', async () => { + const { registry, access, artistA, listener, royaltyRecip } = await withArtistRuntime(); const artistRegistry = await hre.viem.getContractAt('MusicRegistryPallet', registry.address, { client: { wallet: artistA } }); await artistRegistry.write.musicRegRegister([ @@ -380,15 +398,74 @@ describe('Artist SmartRuntime — music pallets', () => { [10_000] ]); + // No precompile installed yet: the chain cannot answer, so a gated track denies. + expect(await access.read.musicAccCanAccess([TRACK_HASH2, listener.account.address])).to.equal(false); + + const precompile = await installPersonhoodPrecompile(); + + // Present but with no personhood recorded — still denied. expect(await access.read.musicAccCanAccess([TRACK_HASH2, listener.account.address])).to.equal(false); - // Artist is the registrar (bootstrapped by initializer) — grant DIM1 - const artistAccess = await hre.viem.getContractAt('MusicAccessPallet', access.address, { client: { wallet: artistA } }); - await artistAccess.write.musicAccSetPersonhoodLevel([listener.account.address, PersonhoodLevel.DIM1]); + const alias_ = `0x${'ab'.repeat(32)}` as `0x${string}`; + await precompile.write.setPersonhood([listener.account.address, DOTIFY_CONTEXT, PersonhoodLevel.DIM1, alias_]); + + expect(await access.read.musicAccCanAccess([TRACK_HASH2, listener.account.address])).to.equal(true); + expect(await access.read.musicAccPersonhoodLevel([listener.account.address])).to.equal(PersonhoodLevel.DIM1); + + const [status, contextAlias, live] = await access.read.musicAccPersonhoodInfo([listener.account.address]); + expect(status).to.equal(PersonhoodLevel.DIM1); + expect(contextAlias).to.equal(alias_); + expect(live).to.equal(true); + void artistA; + }); + + it('HumanFree track: a lower tier than required is still denied', async () => { + const { registry, access, artistA, listener, royaltyRecip } = await withArtistRuntime(); + + const artistRegistry = await hre.viem.getContractAt('MusicRegistryPallet', registry.address, { client: { wallet: artistA } }); + await artistRegistry.write.musicRegRegister([ + sampleRegistration({ + contentHash: TRACK_HASH2, + accessMode: AccessMode.HumanFree, + pricePlanck: 0n, + requiredPersonhood: PersonhoodLevel.DIM2 + }), + [royaltyRecip.account.address], + [10_000] + ]); + + const precompile = await installPersonhoodPrecompile(); + await precompile.write.setPersonhood([listener.account.address, DOTIFY_CONTEXT, PersonhoodLevel.DIM1, `0x${'cd'.repeat(32)}`]); + expect(await access.read.musicAccCanAccess([TRACK_HASH2, listener.account.address])).to.equal(false); + await precompile.write.setPersonhood([listener.account.address, DOTIFY_CONTEXT, PersonhoodLevel.DIM2, `0x${'cd'.repeat(32)}`]); expect(await access.read.musicAccCanAccess([TRACK_HASH2, listener.account.address])).to.equal(true); }); + it('personhood granted in another application context does not unlock Dotify', async () => { + // This is the property the registrar could never provide: the same person carries a + // different alias per context, and a proof issued to another app is not Dotify's. + const { registry, access, artistA, listener, royaltyRecip } = await withArtistRuntime(); + + const artistRegistry = await hre.viem.getContractAt('MusicRegistryPallet', registry.address, { client: { wallet: artistA } }); + await artistRegistry.write.musicRegRegister([ + sampleRegistration({ + contentHash: TRACK_HASH2, + accessMode: AccessMode.HumanFree, + pricePlanck: 0n, + requiredPersonhood: PersonhoodLevel.DIM1 + }), + [royaltyRecip.account.address], + [10_000] + ]); + + const precompile = await installPersonhoodPrecompile(); + const otherContext = `0x${Buffer.from('dotns').toString('hex').padEnd(64, '0')}` as `0x${string}`; + await precompile.write.setPersonhood([listener.account.address, otherContext, PersonhoodLevel.DIM2, `0x${'ef'.repeat(32)}`]); + + expect(await access.read.musicAccCanAccess([TRACK_HASH2, listener.account.address])).to.equal(false); + }); + it('NFT owner is the artist; NFT transfer moves ownership', async () => { const { registry, nft, artistA, other, royaltyRecip } = await withArtistRuntime(); @@ -599,7 +676,13 @@ describe('Artist isolation', () => { expect(await registryB.read.musicRegTrackCount()).to.equal(0n); }); - it('personhood granted on artist A has no effect on artist B', async () => { + it('personhood is a property of the person, so it reads the same on every runtime', async () => { + // This deliberately inverts the previous expectation. Personhood used to be + // per-runtime state an artist wrote, so it could differ between two artists for the + // same listener - which is exactly what made it forgeable. It is now one fact about + // a person in Dotify's context, so every runtime reads the same answer and no + // artist can change it. Catalog and payment state stay per-runtime; only the + // question "is this a distinct human" became global. const ctx = await loadFixture(deployDotifySystemFixture); await createArtistRuntime(ctx.factory, ctx.artistA); @@ -608,13 +691,27 @@ describe('Artist isolation', () => { const runtimeAddrA = (await ctx.directory.read.runtimeOf([ctx.artistA.account.address])) as `0x${string}`; const runtimeAddrB = (await ctx.directory.read.runtimeOf([ctx.artistB.account.address])) as `0x${string}`; - // Artist A grants listener DIM1 - const accessA = await hre.viem.getContractAt('MusicAccessPallet', runtimeAddrA, { client: { wallet: ctx.artistA } }); - await accessA.write.musicAccSetPersonhoodLevel([ctx.listener.account.address, PersonhoodLevel.DIM1]); - - // Listener's level on B's runtime is still None + const accessA = await hre.viem.getContractAt('MusicAccessPallet', runtimeAddrA); const accessB = await hre.viem.getContractAt('MusicAccessPallet', runtimeAddrB); + + // No precompile on this chain yet: both runtimes agree the answer is None. + expect(await accessA.read.musicAccPersonhoodLevel([ctx.listener.account.address])).to.equal(PersonhoodLevel.None); expect(await accessB.read.musicAccPersonhoodLevel([ctx.listener.account.address])).to.equal(PersonhoodLevel.None); + + const precompile = await installPersonhoodPrecompile(); + await precompile.write.setPersonhood([ctx.listener.account.address, DOTIFY_CONTEXT, PersonhoodLevel.DIM2, `0x${'11'.repeat(32)}`]); + + expect(await accessA.read.musicAccPersonhoodLevel([ctx.listener.account.address])).to.equal(PersonhoodLevel.DIM2); + expect(await accessB.read.musicAccPersonhoodLevel([ctx.listener.account.address])).to.equal(PersonhoodLevel.DIM2); + + // And neither artist can alter it. + const artistAccessA = await hre.viem.getContractAt('MusicAccessPallet', runtimeAddrA, { client: { wallet: ctx.artistA } }); + try { + await artistAccessA.write.musicAccSetPersonhoodLevel([ctx.listener.account.address, PersonhoodLevel.None]); + expect.fail('Should have reverted'); + } catch (e: unknown) { + expect((e as Error).message).to.include('Individuality precompile'); + } }); }); diff --git a/contracts/evm/tsconfig.json b/contracts/evm/tsconfig.json index ec2afe1..6efc2d8 100644 --- a/contracts/evm/tsconfig.json +++ b/contracts/evm/tsconfig.json @@ -1,14 +1,22 @@ { - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "rootDir": ".", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "resolveJsonModule": true, - "types": ["node", "mocha"] - }, - "include": ["hardhat.config.ts", "scripts/**/*.ts", "test/**/*.ts"] + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "types": [ + "node", + "mocha" + ] + }, + "include": [ + "hardhat.config.ts", + "scripts/**/*.ts", + "test/**/*.ts", + "./.cdm/**/*" + ] } diff --git a/docs/README.md b/docs/README.md index 9b47fa3..c50c5c6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ Conceptual documents that help you understand why Dotify works the way it does. | [Content Protection](./explanation/content-protection.md) | All | Audio encryption pipeline, what it protects, and what it does not | | [Royalty Settlement](./explanation/royalty-settlement.md) | All | How DOT payments flow from listener wallet to artist wallet | | [Listening Rooms](./explanation/listening-rooms.md) | All | WebRTC peer-to-peer streaming, signaling protocol, known limitations | +| [Product DevNet Architecture](./explanation/product-devnet-architecture.md) | Maintainers | Dual-host boundaries, Product account capabilities, rooms, storage, and the proposed contract port | --- @@ -40,6 +41,7 @@ Runbooks for hosted configuration and production validation. | Document | Summary | |---|---| | [Deployment Configuration](./operations/deployment-configuration.md) | Netlify and Fly dashboard settings, secrets, catalog persistence, validation, and the update checklist for future env/config changes | +| [Product DevNet Deployment](./operations/product-devnet-deployment.md) | Build, publish, validate, and roll back the `dotify-test01.dot` Product DevNet app | --- diff --git a/docs/backlog/24-access-streaming-v2.md b/docs/backlog/24-access-streaming-v2.md index 556fccd..1267a60 100644 --- a/docs/backlog/24-access-streaming-v2.md +++ b/docs/backlog/24-access-streaming-v2.md @@ -139,12 +139,14 @@ P3 first vertical slice delivered (`agent/audio-v2-p3`): the browser/device validation matrix, startup telemetry export, and the backend read-through gateway decision. -Product SDK replanning note (2026-07-14): +Product SDK adaptation note (2026-07-26): -- Product SDK (`@parity/product-sdk` 0.17.0 at - `2f359bba28ca72855207a0a519d4118b37b4438c`) is prototype/reference/unaudited. +- Product SDK 0.19.1 and deploy tooling 0.13.1 remain + prototype/reference/unaudited. - Host APIs are progressive enhancement for Product containers; standalone web remains a supported mode. +- Host detection, explicit Product account identity, a Product DevNet build, + canonical room links, and dual-origin Fly configuration are implemented. - Product SDK contracts use `pallet-revive`, PolkaVM artifacts, and CDM manifests. Dotify's current Hardhat + viem + Paseo Asset Hub EVM path needs a portability spike before adopting that layer. diff --git a/docs/backlog/README.md b/docs/backlog/README.md index 5afea40..4d45a60 100644 --- a/docs/backlog/README.md +++ b/docs/backlog/README.md @@ -104,18 +104,22 @@ ticket 18 preview assets are consciously retired by access model v2. `improvement-plan.md` tracks the July 2026 review of the implementation against the product/technical/philosophical memory and the current Parity Product SDK direction. The plan is now dual-mode: standalone web remains the -first public listening path, while Product SDK / Playground / Humanity -integration is a gated feasibility track. Nothing in that track may imply live -Host, Statement Store, Product account, Humanity, or `.dot` deployment support -until the relevant spike proves the current API, environment, and security -boundary. +first public listening path, while the Product DevNet build adds +`dotify-test01.dot`, +explicit Host detection, app-scoped Product identity, and canonical +Product-origin room links. The typed runtime ports and experimental +Product CDM/PAPI adapter boundary are implementation preparation only; they do +not imply Product-signed contract writes, Statement Store rooms, or Humanity +decisions. API-side Product-signed key/session verification now exists through +`product-sr25519-v1`, but the Product frontend still needs to send host-signed +requests before protected playback can use that identity path. The Product SDK evidence snapshot used for this replanning is -`paritytech/product-sdk@2f359bba28ca72855207a0a519d4118b37b4438c` -(`@parity/product-sdk` 0.17.0), fetched on 2026-07-14. It is explicitly -prototype / reference / unaudited code. Paseo and Summit are the live preset -environments; Product SDK contracts target `pallet-revive` / PolkaVM CDM flows, -not Dotify's current viem + EVM RPC path; Statement Store is useful for small +`@parity/product-sdk` 0.19.1 and +`@polkadot-community-foundation/polkadot-app-deploy` 0.13.1, +verified on 2026-07-26. They remain prototype / reference / unaudited code. +Product SDK contracts target `pallet-revive` / PolkaVM CDM flows, not Dotify's +current viem + EVM RPC write path; Statement Store is useful for small ephemeral presence, not full chat, SDP/ICE, durable media metadata, or guest reactions. @@ -160,9 +164,10 @@ on `main`. The remaining order is: signaling and production-env evidence are closed through #36/#37. 3. Improve room resilience and shared-listening depth only where it preserves the link-first guest doctrine. -4. Run Product SDK feasibility spikes: Host capability detection, Product - account signing, resource allocation, PolkaVM/CDM contract portability, - Playground/Bulletin/DotNS deployment, and Statement Store presence. +4. Validate the delivered Product host/account and Bulletin/DotNS baseline, + then wire real CDM-installed runtime packages through the Product CDM/PAPI + adapter, wire frontend Product-signed key/session requests, and run bounded + resource-allocation/Statement Store spikes. 5. Build live Humanity / Individuality only after the research ticket proves a privacy-preserving source, proof shape, address-binding story, and fallback UX. diff --git a/docs/backlog/improvement-plan.md b/docs/backlog/improvement-plan.md index 49937b0..23fd81f 100644 --- a/docs/backlog/improvement-plan.md +++ b/docs/backlog/improvement-plan.md @@ -62,25 +62,24 @@ Where it falls short of its own standards: ## Product SDK feasibility track -This track runs in parallel with standalone hardening, but it does not block -first sound and must not be sold as a delivered capability. +This track runs in parallel with standalone hardening and does not block first +sound. Only the baseline rows marked delivered may be presented as live. Product SDK snapshot used for this plan: -- `paritytech/product-sdk@2f359bba28ca72855207a0a519d4118b37b4438c` - (fetched 2026-07-14); -- `@parity/product-sdk` 0.17.0; +- `@parity/product-sdk` 0.19.1 (verified 2026-07-26); +- `@polkadot-community-foundation/polkadot-app-deploy` 0.13.1; - explicit prototype / reference / unaudited status; -- live preset environments: Paseo and Summit; +- Product target: DevNet Asset Hub / People / Bulletin; - contracts package: `pallet-revive`, PolkaVM artifacts, and CDM manifests; - Statement Store: 512-byte statement payload, 1024-byte user total, default 30-second TTL. | Item | Tracking | Status | | --- | --- | --- | -| Product SDK baseline: pin SDK versions, document compatible Host surfaces, and add feature detection for Host local storage, signing, permissions, resource allocation, payments, and chain support. | #85, `polkadot-product-readiness-and-killer-dapp-roadmap.md` | Proposed | +| Product SDK baseline: pin SDK versions, detect Host availability, connect an app-scoped account explicitly, and separate presence identity from EVM signing authority. | #85, `polkadot-product-readiness-and-killer-dapp-roadmap.md` | Delivered on Product adaptation branch | | Contract portability spike: compare Dotify's current Paseo Asset Hub EVM / viem / Hardhat flow with Product SDK contracts on `pallet-revive`, PolkaVM artifacts, and CDM manifests. | #85 | Proposed | -| Playground deployment spike: determine whether Dotify's static build can use Playground/Bulletin/DotNS deploy flows without weakening current secret and publication boundaries. | #85 | Proposed | +| Product deployment baseline: build a browser-safe multi-file bundle, publish through Bulletin/DotNS tooling, preserve backend key custody, and use a canonical public room URL. | #85 | Delivered on Product adaptation branch; live publication pending operator credentials | | Statement Store presence spike: use it for small, signed, ephemeral discovery/presence only. Do not move SDP/ICE, full chat history, media metadata, or link-only guest reactions there until signer, TTL, and size constraints are solved. | #89, `20-room-social-layer.md`, `21-room-collaborative-queue.md` | Proposed | | Humanity / Individuality research rewrite: prove the canonical live source, privacy-preserving proof shape, product-account/identity-account binding, and fallback UX before promoting Human free from research to build. | #12, `11-proof-of-personhood-integration-research.md` | Open | diff --git a/docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md b/docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md index d6d497f..380a7e4 100644 --- a/docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md +++ b/docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md @@ -1,15 +1,16 @@ # Polkadot product readiness and killer dapp roadmap -Status: active planning note, supersedes the stale draft from PR #91. +Status: active execution note; the Product DevNet baseline is implemented on +`feat/product-devnet-adaptation`. -Last Product SDK verification: 2026-07-14 against -`paritytech/product-sdk@2f359bba28ca72855207a0a519d4118b37b4438c` -(`@parity/product-sdk` 0.17.0). +Last Product SDK verification: 2026-07-26 against +`@parity/product-sdk` 0.19.1 and +`@polkadot-community-foundation/polkadot-app-deploy` 0.13.1. ## Verdict -Dotify should align with the Polkadot product ecosystem, but it should not -replace its standalone production path with Product SDK assumptions yet. +Dotify should align with the Polkadot product ecosystem without replacing its +standalone production path with Product SDK assumptions. The right product shape is dual-mode: @@ -23,6 +24,19 @@ The right product shape is dual-mode: product failure state. It must not fall back to demo secrets, hidden signers, or bypassed access checks. +The first adaptive slice is now implemented: + +- a separate Product DevNet build and `dotify-test01.dot` manifest; +- explicit Host detection and app-scoped Product account connection; +- Product identity for room presence without claiming EVM/EIP-191 authority; +- canonical `.dev-dot.li` room links; +- shared Fly API/signaling allowlists for Netlify and Product origins; +- a pinned build/deploy workflow and operator rollback guide. + +Typed runtime ports are now extracted in the follow-up branch. Product-native +contract writes, Product-signed key requests, Product personhood, and Product +presence transport remain gated follow-up work. + ## Product ecosystem evidence The current Parity product direction is coherent: Levity for publishing, @@ -35,9 +49,8 @@ The SDK details matter for Dotify: - Product SDK and Playground are explicitly prototype / reference / unaudited code. -- Product SDK preset chains are live for Paseo and Summit. Polkadot and Kusama - preset paths are gated because Bulletin / Individuality descriptors are not - live there. +- Product DevNet exposes the Asset Hub, People, and Bulletin system-chain + topology used by the current Product tooling. - Product SDK contract helpers target `pallet-revive`, PolkaVM artifacts, and CDM manifests. Dotify currently uses Hardhat Solidity, generated EVM ABIs, viem, and Paseo Asset Hub EVM RPCs. @@ -131,24 +144,57 @@ Goal: deepen rooms without breaking the room-guest doctrine. Goal: prove the Product host path with small spikes before committing the app. -- Pin Product SDK versions and add a compatibility matrix. -- Detect Host availability and supported chain/capability surfaces. -- Prototype Product account connection, signing, identity prompt behavior, and - resource allocation. +- Delivered: pin Product SDK/deploy versions and add a compatibility matrix. +- Delivered: detect Host availability without blocking standalone first sound. +- Delivered: connect the app-scoped Product account only on explicit action and + separate identity capability from EVM signing capability. +- Delivered: publishable Bulletin/DotNS build and dual-origin Fly boundary. +- Remaining: prototype host transaction signing and resource allocation. - Compare Dotify's Hardhat/EVM runtime with Product SDK PolkaVM/CDM contracts. -- Prototype Playground deployment against Dotify's single-file build and secret - boundary. -- Prototype Statement Store presence with strict payload, TTL, and signer - limits. +- Delivered on the room-beacon branch: Statement Store presence with strict + payload, TTL, and signer limits. Host-only publication, per-room last-write- + wins channels, 512-byte and 1024-byte budgets enforced before writing, and + expiry-based eviction on the reading side. Ships dormant + (`VITE_DOTIFY_ROOM_BEACONS=off`) until discovery has a reader and live host + evidence exists; joining stays on Socket.IO/WebRTC because moving it would + require every guest to hold an identity. ### Phase 4 - Product integration -Goal: ship Product mode as progressive enhancement. - -- Add Product-mode adapters behind explicit ports, leaving standalone adapters - intact. -- Use Host signing and Product accounts only when the Host path is available. -- Surface Host permission denial as actionable UI state. +Goal: deepen the delivered Product mode one adapter at a time. + +- Delivered: keep standalone adapters intact and lazy-load Product host code. +- Delivered: use the Product account as presence identity only when available. +- Delivered: surface host absence and unsupported signer boundaries explicitly. +- Delivered on the follow-up branch: extract typed runtime read/write ports and + move the current viem runtime implementation behind `RuntimeReadPort` / + `RuntimeWritePort`. +- Delivered on the next follow-up branch: add an experimental CDM/PAPI adapter + behind those ports. It is not selected by default until Dotify has + CDM-installed Product runtime packages and host-signed transaction evidence. +- Delivered on the next follow-up branch: add an API-side Product sr25519 + signature scheme for key delivery and session sign-in. It binds the Product + account public key to the derived H160 requester before nonce consumption and + access checks. +- Delivered on the next follow-up branch: wire Product-host frontend key and + session requests to that signature scheme, while keeping contract writes on + the standalone EVM/passkey signer path. +- Delivered on the next follow-up branch: generate the CDM manifest and typed + contract augmentation from the same Hardhat artifacts as the viem bindings, + and implement the real Product contract resolver behind the runtime ports. + Selection stays opt-in behind `VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm`. +- Settled: the chain question. Product DevNet is a preset over the Paseo system + parachains (Asset Hub 1000, People 1004, Bulletin 1010) at EVM chain + 420420417, not a separate network. Dotify's contracts are already there, + verified by byte-identical ArtistDirectory code served from both the DevNet + and Hub TestNet endpoints. No contract redeploy is needed to port to DevNet. + The SDK's `paseo` preset is Paseo Next (1500/1502), a different network, so + `devnet` is the only environment Dotify can serve a catalog from. +- Next: `pallet-revive` account mapping plus real host-signed transaction smoke + tests before Product writes can replace the EVM wallet path. This is now the + only gate left for Product contract mode. +- Next: run real Product host smoke tests for protected playback and capture the + Product sr25519 request evidence. - Keep backend key delivery authoritative unless a Product-host design proves a stronger key-custody boundary. - Keep `.dot`/Playground deployment separate from access enforcement. @@ -219,8 +265,10 @@ Recommended Project 5 fields: - #36: closed after hosted signaling operation evidence. - #37: closed after #99 and manually checked deploy-host production env evidence. -- #85: split into Product SDK baseline, contract portability, Playground deploy, - Statement Store presence, and integration adapter spikes. +- #85: Product SDK baseline, Product DevNet deployment slice, and typed runtime + port extraction implemented; keep open for CDM/PAPI contract portability, + backend Product signatures, resource allocation, and bounded Statement Store + presence. - #86: implementation active on `codex/86-catalog-read-model`; keep In Progress until review and public performance evidence close the warm/cold budgets. - #87: keep for responsive cover/gateway pipeline. diff --git a/docs/context/dotify-technical-memory.md b/docs/context/dotify-technical-memory.md index a341a1b..11d139b 100644 --- a/docs/context/dotify-technical-memory.md +++ b/docs/context/dotify-technical-memory.md @@ -57,8 +57,9 @@ Wallet-gated onboarding, runtime creation, upload, encryption, IPFS publication, - Product SDK / Playground / Humanity integration is a progressive enhancement track. The current verified Product SDK snapshot is prototype/reference/ unaudited, Host APIs require a compatible container, contracts target - `pallet-revive` / PolkaVM CDM flows, and Statement Store is constrained to - small signed ephemeral data. + `pallet-revive` / PolkaVM CDM flows, Product sr25519 key/session signatures + are wired for Product frontend protected playback after explicit host-account + connection, and Statement Store is constrained to small signed ephemeral data. ## Production spine @@ -87,7 +88,8 @@ Introduce a lean backend service for: - Pinata uploads; - content-key custody and delivery; -- wallet signature verification; +- wallet signature verification, including EIP-191 and Product sr25519 + request schemes; - nonce/replay protection; - access checks against SmartRuntime; - room host key requests; @@ -211,7 +213,9 @@ Contracts already have meaningful tests; frontend and e2e must catch up. - No dev fallback signer in public flows. - Access checks must fail closed. - Backend must not trust frontend-provided access results. -- Wallet signatures must include nonce, chain ID, content hash, requester address, request purpose, and expiration. +- Wallet signatures must include nonce, chain ID, content hash, requester + address, request purpose, and expiration; Product signatures must also bind + the Product account public key to the derived H160 requester. - Replay protection is mandatory for key requests. - Room listeners must never receive content keys or encrypted source files. - Logs must never expose secrets, keys, or raw uploaded contents. diff --git a/docs/design/dotify-product-stack-alignment.md b/docs/design/dotify-product-stack-alignment.md new file mode 100644 index 0000000..22b88d8 --- /dev/null +++ b/docs/design/dotify-product-stack-alignment.md @@ -0,0 +1,278 @@ +# Dotify On The Product Stack: Assessment And Proposed Architecture + +Status: proposal. No code changes implied by this document alone. + +Sources: [Product docs](https://docs.polkadotcommunity.foundation/), +[Product SDK](https://paritytech.github.io/product-sdk/), +[resources](https://docs.polkadotcommunity.foundation/reference/resources/), +[Polkadot Community Foundation](https://github.com/Polkadot-Community-Foundation). +Claims below are quoted or cited; where the documentation is silent, this +document says so rather than guessing. + +## 1. What The Official Stack Actually Is + +Ten architecture layers, each with a defined owner: + +| Layer | What it provides | Where it lives | +| --- | --- | --- | +| Client tier | The Polkadot app; apps run *inside* a host container | Desktop / Mobile / `dev-dot.li` | +| Identity | Device attestation -> JWT, Lite usernames, Full personhood | `identity-backend` (centralized HTTP), `people-lite`, `proof-of-ink` | +| Naming | `.dot` names; usernames mirror into DotNS | DotNS | +| App delivery | build -> Bulletin -> DotNS bind -> Browse listing | Bulletin + DotNS | +| Storage | Content-addressed CIDs; authorization is a byte/tx quota with expiry | Bulletin (para 1010) | +| Contracts | PolkaVM via `pallet-revive`; CDM builds, deploys, registers, resolves | Asset Hub (1000) | +| Identity in contracts | **Personhood precompile** returning a per-app privacy-preserving alias | Asset Hub | +| Money | CASH (pUSD asset 1) spent through Coinage; PAS pays fees | People chain (1004) | +| Messaging & calls | Encrypted chat, 1:1 voice/video; **signaling travels on-chain** | People statement store + platform TURN | +| Discovery | Browse | `browse.dev-dot.li` | + +Three properties matter more than the inventory. + +**The host is the runtime.** `createApp` "requires a host and will throw on boot +without one". The chain client has no direct-WebSocket fallback. An app on this +stack is not a website that talks to chains; it is a guest process inside the +Polkadot app. + +**Writing is gated by personhood.** Statement Store is a custom RPC on People +chain nodes, 512 bytes per statement, 1 KiB per account, ~48h retention, and an +account "MUST have a Statement Store allowance to write - granted via +Individuality runtime registration". Publishing is a privilege attached to an +attested person. + +**The stack keeps its own centralized pieces.** `identity-backend` is "a +centralized HTTP service handling device attestation, username allocation, and +JWT sessions". Calls get "temporary TURN credentials" from platform +infrastructure. This is not hypocrisy; it is an honest admission that some roles +have no decentralized implementation yet. Dotify is entitled to the same honesty. + +## 2. Where Dotify Already Aligns + +More than the roadmap assumed. + +**Contracts are already in the right execution environment.** Dotify's Solidity +contracts are deployed through Asset Hub's `eth-rpc`, which is a compatibility +layer over `pallet-revive` - the exact pallet the stack specifies. Dotify is not +on a neighbouring chain; it is on the same VM, reached through a different +toolchain. Verified: identical ArtistDirectory bytecode from both the DevNet and +Hub TestNet endpoints. + +**App delivery is on-stack.** Bulletin chunked upload, DotNS binding to +`dotify-test01.dot`, `dev-dot.li` gateway. Delivered. + +**Identity is on-stack.** App-scoped Product account, SS58 plus derived H160, +connected only on explicit user action. + +**Content addressing matches.** Dotify already treats audio as immutable CIDs. +Bulletin is the same idea with a different authorizer. + +**Encryption already assumes ungated reads.** Bulletin "reading never needs" +authorization - it gates storing, not retrieval. Dotify's DAV2 encryption is +therefore not redundant with a move to Bulletin; it is the *precondition* for +one. Protected audio on a public content-addressed store must be encrypted, and +Dotify already does that. + +## 3. Where Dotify Diverges + +| Concern | Dotify today | Stack model | Real gap? | +| --- | --- | --- | --- | +| Contract toolchain | Solidity, Hardhat, viem, hand-built manifest | PolkaVM, CDM, `@org/name` resolution | Yes - composability and discoverability | +| Personhood | Dev-operated registrar, unused | Personhood precompile, contextual alias | Yes - and the stack's answer is better | +| Payments | `payForAccess` in native token | CASH via Coinage, host payment APIs | Yes - wrong asset, wrong surface | +| Catalog metadata | Fly read model over EVM logs | Bulletin CIDs + CDM resolution | Partly - a cache is legitimate | +| Audio storage | Pinata / IPFS pinning | Bulletin | Contested - see §6 | +| Room signaling | Socket.IO on Fly | People statement store | **Blocked** - see §4 | +| Content-key custody | Fly, `CONTENT_KEY_MASTER_SECRET` | No equivalent | **No stack answer exists** | + +## 4. The Constraint That Shapes Everything + +Dotify's first product invariant: + +> A room guest can join from a link without a wallet, signature, or payment. + +The stack's messaging model is the opposite by construction: + +- statements require an allowance, granted by Individuality registration; +- official calls are **1:1**, and "voice and video calls are a mobile-only + feature today"; +- signaling rides the People statement store, 512 B per statement, 1 KiB per + account. + +A WebRTC offer is roughly 1.5-4 KB. That is 3-8x the per-statement ceiling, and +the per-account ceiling is 1 KiB - so a peer cannot hold even one SDP in the +store. Chunking does not rescue it; the budget is the wall, not the chunk size. + +And the arithmetic is the *lesser* problem. The greater one is that a guest must +publish an answer to complete a handshake, which requires an attested identity. + +**Moving Dotify's rooms onto the official messaging layer would convert every +listener into a registered, attested person.** That does not degrade the +product; it deletes it. The gesture Dotify exists to protect - "someone lets +another person listen with them" - becomes an onboarding funnel. + +This is where the word *convivial* earns its keep. A convivial tool, in Illich's +sense, is one people can use without first submitting to an institution. A +listening room that demands attestation at the door is a well-engineered +enclosure. The north star is explicit that Web3 here is "invisible trust", not +decoration - and an identity checkpoint is the most visible decoration there is. + +So: **the anonymous guest is not a legacy compromise to be migrated away. It is +the design constraint the architecture must be built around.** + +## 5. Proposed Architecture: Three Rings + +Organise every component by how much trust it requires, and shrink the inner +rings rather than pretending they are empty. + +```text +Ring 1 On-stack, no compromise + contracts (CDM) · personhood (precompile) · payments (CASH) + app delivery (Bulletin/DotNS/Browse) · catalog metadata (Bulletin) + room discovery + presence (statement store) + +Ring 2 Minimal necessary infrastructure + stateless SDP rendezvous · TURN relay + +Ring 3 The stated exception + content-key custody +``` + +The rule: a component may only sit in an outer ring if no inner-ring mechanism +can hold it, and the reason is written down. + +### Ring 1 - move these, they are strictly better on-stack + +**Contracts into CDM.** Register the runtime family as `@dotify/*`. The +generated manifest already exists; CDM registration adds name-based resolution, +Bulletin-hosted ABIs, and composability - another product can resolve +`@dotify/artist-runtime` and read a catalog without asking Dotify. First-writer +-owns makes the name a durable asset. Solidity stays; the target is already +PolkaVM. + +**Personhood onto the precompile.** Replace the dev registrar. The runtime's +`requiredPersonhood` reads the precompile directly, receiving "a +per-application, privacy-preserving pseudonym: the same person yields a +different alias in a different context". This is the single strongest alignment +available: Dotify's `human-free` access mode becomes real, private, and +unlinkable across apps, and the registrar disappears. It also retires the +project's weakest claim. + +**Payments to CASH.** Users see CASH as their balance; PAS is a fee token they +should not think about. Charging in PAS is a category error on this stack. The +runtime stays the authority on entitlement; settlement moves to host payment +APIs. + +*Open problem, stated plainly:* CASH lives on People chain, the runtime lives on +Asset Hub. Cross-chain settlement is unsolved here. Two candidate shapes - a +host-signed payment receipt the runtime verifies, or an Asset-Hub-side +entitlement credited from an attested People-chain transfer. Both need design +work. Do not ship a payment path until this is settled. + +**Catalog metadata to Bulletin.** Release metadata, artwork, and manifests are +small, immutable, and public. Exactly Bulletin's shape. The Fly read model +becomes a cache with a provable source, not the source. + +**Room discovery and presence to the statement store.** A `{room, host, +listeners, ts}` record is ~100 B, well inside 512 B, and `ChannelStore`'s +last-write-wins is the right primitive. The *host* is identified and can hold an +allowance, so this works without touching the guest. Rooms become discoverable +without Dotify's servers - a genuine decentralization win that costs the product +nothing. + +### Ring 2 - shrink, do not eliminate + +The current signaling service does rooms state, presence, chat, reactions, +requests, and SDP relay. Most of that moves to Ring 1. What is left: + +**A stateless SDP rendezvous.** No room registry, no chat, no persistence - +short-TTL mailboxes keyed by room code, so an anonymous guest can hand its +answer to a host. This is the irreducible remainder of "let a stranger connect +without an account". + +**TURN**, for peers behind symmetric NAT. + +The stack does the same thing for its own calls: platform-issued TURN +credentials, because NAT traversal has no on-chain answer. Ring 2 is not +Dotify's deviation from the stack; it is the same concession the stack makes, +kept as small as the product allows. + +*Open question worth asking the Foundation:* can third-party products obtain +TURN credentials from platform infrastructure? If yes, Ring 2 halves. + +### Ring 3 - name the exception + +Content-key custody cannot move. Protected audio must be encrypted at rest on a +publicly readable store, the key must be released only after a server-side +access check, and the stack offers no confidential compute to run that check. +Putting the key in the client defeats the encryption; putting it on-chain +publishes it. + +The honest position is to say so, and to reduce the blast radius rather than +claim it away: + +- **Per-artist custody** - an artist's runtime designates its keyholder, so + Dotify is not one master secret for the whole commons. This follows directly + from artist sovereignty: an artist who controls catalog, access, and rights + should control the key too. +- **Threshold shares**, so no single operator can unilaterally release. +- **Narrow the window** - keys scoped per track, per session, short-lived. + +Ranked by fit with the north star, per-artist custody is the strongest: it turns +the platform's most centralized component into an expression of the project's +central political claim. + +## 6. What I Would Not Do + +**Do not move audio to Bulletin yet.** Bulletin authorization is "a bounded +quota with an expiry, not a permanent grant", and the docs give no size limits +or retention guarantee for MB-scale media. A growing catalog would need +perpetual re-authorization, and an expired quota on a music library is a dead +catalog. Move metadata now; move audio when quota economics for large media and +indefinite retention are demonstrated. Revisit, do not assume. + +**Do not adopt the official calls layer.** 1:1 and mobile-only cannot serve one +host with many listeners. + +**Do not rewrite the contracts to ink!.** They already run on the target VM. +Rewriting spends the project's scarcest resource on zero user-visible gain. + +**Do not delete the Fly API to look decentralized.** It would move key custody +into the browser - strictly worse for artists and listeners, and dishonest about +where trust sits. The stack runs a centralized identity backend for the same +class of reason. + +## 7. Sequence + +Ordered by value per unit of risk: + +1. **Personhood precompile** - retires the weakest claim, unlocks `human-free`, + no user-facing regression. Highest value, self-contained. +2. **CDM registration of `@dotify/*`** - claims the names, makes the catalog + composable. Manifest work already done. +3. **Presence and discovery to the statement store** - real decentralization, + guest path untouched. +4. **Catalog metadata to Bulletin** - Fly read model demoted to cache. +5. **Shrink signaling to a rendezvous** - only after 3 lands. +6. **Per-artist key custody** - the deepest change; do it when the runtime work + above has settled. +7. **CASH settlement** - last, and only after the cross-chain design is proven. + +Steps 1-4 are additive and independently shippable. Nothing before step 5 +touches the walletless guest path. + +## 8. Honest Summary + +Dotify is closer to the official stack than the roadmap assumed - same VM, same +delivery path, same content addressing, and an encryption model that Bulletin +would require anyway. The genuine gaps are personhood, contract registration, +payments, and metadata storage, and all four are improvements Dotify should +want. + +One gap will not close: the stack's messaging assumes attested participants, and +Dotify's rooms assume strangers. That is not a defect on either side. It is two +products with different social contracts. Dotify should adopt the stack +everywhere it fits, and keep the smallest possible amount of infrastructure to +protect the one promise the stack cannot make - that you can send someone a +link, and they can just listen. + +Build infrastructure for relation, not a casino wearing headphones, and not a +turnstile either. diff --git a/docs/design/dotify-v2-access-and-streaming.md b/docs/design/dotify-v2-access-and-streaming.md index 19bb0ab..f794472 100644 --- a/docs/design/dotify-v2-access-and-streaming.md +++ b/docs/design/dotify-v2-access-and-streaming.md @@ -390,7 +390,7 @@ Dotify mapping: | Product SDK / Host API | Replace bespoke chain, signing, storage, and permission glue only where the SDK gives equivalent or better behavior. | | Proof of Personhood | Replace the current admin/personhood mock with the live verified-human source for `human-free`. | | Coinage | Candidate future payment rail for paid access; EVM runtime remains the settlement record until Coinage design is explicit. | -| DotNS | Keep `dotify.dot.li` / `.dot` resolution aligned with the Bulletin single-file build. | +| DotNS | Publish the Product profile as `dotify-test01.dot` / `https://dotify-test01.dev-dot.li`; keep the legacy single-file path separate. | | Bulletin Chain | Continue as a publication and availability layer for product bundles and manifests. | | Statement Store | Future presence/chat/room-discovery layer; Socket.IO remains SDP/ICE relay until a separate migration is designed. | diff --git a/docs/explanation/architecture-overview.md b/docs/explanation/architecture-overview.md index 13d95e2..d3439a1 100644 --- a/docs/explanation/architecture-overview.md +++ b/docs/explanation/architecture-overview.md @@ -74,7 +74,7 @@ Track selected → access checked │ ├── Has access? ──► Content key requested, full audio decrypted and played │ - └── No access? ──► 42 % preview played, access gate shown + └── No access? ──► Unlock/personhood gate shown, no protected audio │ ├── Pay DOT → musicRoyPayAccess() → access granted └── Prove PoP → registrar confirms personhood → access granted @@ -102,6 +102,11 @@ src/ │ ├── useSession.ts # WebRTC + Socket.IO room management │ ├── useArtistConsole.ts # /artists registration, releases, royalties │ └── useWallet.ts # Wallet tiers: passkey → EIP-6963 extension +├── features/runtime/ +│ ├── runtimePorts.ts # RuntimeReadPort / RuntimeWritePort contracts +│ ├── viemRuntimeAdapter.ts # Current EVM implementation behind the ports +│ └── productCdmRuntimeAdapter.ts +│ # Experimental Product CDM/PAPI adapter ├── views/ # One file per screen / tab │ ├── ListenView.tsx │ ├── PlayerView.tsx @@ -174,4 +179,7 @@ The signaling server is a lightweight Socket.IO process (`server/signaling.mjs`) - Your payments — they go directly to your EVM address via smart contract. - Your track records — they live on Paseo Asset Hub (and optionally Bulletin Chain). -The frontend is itself distributed via IPFS/DotNS at `dotify.dot.li`. +The standalone frontend is deployed through Netlify. The Product DevNet build +is publishable through Bulletin/DotNS as `dotify-test01.dot` and resolves +publicly at `https://dotify-test01.dev-dot.li`; the older `dotify.dot.li` +artifact remains legacy deployment evidence. diff --git a/docs/explanation/product-devnet-architecture.md b/docs/explanation/product-devnet-architecture.md new file mode 100644 index 0000000..c6a7575 --- /dev/null +++ b/docs/explanation/product-devnet-architecture.md @@ -0,0 +1,451 @@ +# Product DevNet Architecture + +## Decision + +Dotify uses an adaptive dual-host architecture: + +- the standalone Netlify app remains a complete public entry point; +- the Product DevNet build publishes the same listener and room experience as + `dotify-test01.dot`; +- Product-host capabilities are added through explicit adapters; +- a missing or denied host capability never enables a demo secret, hidden + signer, or weaker access path. + +This keeps Dotify's north star intact. A guest can still follow a room link and +hear a host without first adopting wallet infrastructure. An artist's access +policy and protected source remain authoritative regardless of which frontend +host serves the app. + +## Why The Host Is An Adapter + +The Product environment and Dotify's existing runtime do not expose the same +signing contract. + +The Product SDK returns an app-scoped account and a PAPI `PolkadotSigner`. +Dotify's deployed contract writes and content-key requests currently use viem, +EIP-1193, and EIP-191. Treating those signers as interchangeable would either +fail at runtime or create an unverifiable access claim. + +The first Product adaptation therefore uses the host account for: + +- an explicit, user-initiated Product account connection; +- an SS58 account for display and future Product-native adapters; +- a derived H160 address for local room-name persistence and read-only + runtime/catalog correlation. + +It does not use that account for: + +- Classic payments; +- artist runtime creation or release publication; +- protected content-key requests; +- Bulletin artist publication through the existing PAPI v1 integration. + +Those actions continue to require the existing passkey or EVM wallet until the +chain and backend adapters described below are delivered. + +## Runtime Topology + +```text +Standalone browser Product host +https://muzinga.netlify.app https://dotify-test01.dev-dot.li + | | + +---------------+----------------------+ + | + same Dotify frontend + | + +-----------+-----------+ + | | + dotify-api.fly.dev dotify-signal.fly.dev + catalog, uploads, room discovery, SDP/ICE, + access, content keys chat and presence + | | + +-----------+-----------+ + | + Product DevNet Asset Hub + existing Dotify runtimes +``` + +The Product build is a normal relative-path Vite bundle. `pad` publishes its +files to Bulletin and binds the result to DotNS. Keeping multiple static chunks +allows incremental uploads; the older single-file Bulletin build remains +available for its original workflow. + +The two frontend origins share the same Fly services. `API_ORIGINS` and +`SIGNAL_ORIGINS` are explicit comma-separated allowlists. This is required for +cross-origin catalog reads, key requests, Socket.IO, and WebRTC signaling. + +## Capability Matrix + +| Capability | Standalone | Product build now | Product-native target | +| --- | --- | --- | --- | +| Browse catalog | Fly cache + EVM RPC | Same | Host-routed read adapter where it improves reliability | +| Play Free track | No wallet | No wallet | Same | +| Join room link | No wallet | No wallet | Same | +| Host room | Socket.IO + WebRTC | Same | Keep until a multiparty replacement proves equivalent UX | +| Product identity | Not applicable | App-scoped SS58/H160 | Host identity with explicit capability grants | +| Classic payment | Passkey/EVM wallet | Passkey/EVM wallet | CDM/PAPI write adapter | +| Protected key request | EIP-191 or session token | `product-sr25519-v1` when a Product account is connected; EIP-191 or session token otherwise | Frontend-host signed Product key/session requests, with captured host signing evidence | +| Artist publication | viem/EVM | viem/EVM | Generated CDM contract adapter | +| Personhood | Current on-chain policy source | No new claim | Privacy-preserving Product proof after verification | +| Static delivery | Netlify | Bulletin + DotNS | Bulletin + DotNS | + +## Rooms Stay Host-Neutral + +Rooms are a product primitive, not a deployment detail. The current signaling +service supports anonymous discovery, one host with multiple listeners, +short-lived chat/reactions/requests, and WebRTC negotiation. Product messaging +and Statement Store do not currently provide a verified drop-in replacement +for that wallet-free multiparty flow. + +The Product build therefore keeps the Socket.IO/WebRTC room layer. It adds one +important boundary: `VITE_PUBLIC_APP_URL` makes every copied room link point to +the public `.dev-dot.li` origin rather than an internal container or content +gateway URL. + +### Room Beacons + +That presence spike is now implemented, and ships dormant. A host inside the +Product container can publish a compact beacon to the Statement Store so a room +is discoverable without Dotify's signaling server. It carries the room code, +host name, and an aggregate listener count - never SDP, ICE, audio, chat, +listener identities, or source keys. Now-playing is opt-in per host, because a +beacon is globally readable and outlives the room by up to the statement TTL. + +Joining deliberately does not move here, and cannot. A WebRTC offer is 1.5-4 KB +against a 512-byte statement ceiling and a 1024-byte per-account total, so a +peer cannot hold even one. More decisively, a guest would have to publish an +answer to complete the handshake, which requires an identity and an allowance - +turning every listener into a registered person. Only the host publishes, +because the host is already identified. + +Beacons are per-room channels for last-write-wins, so one hosted room occupies +exactly one live statement no matter how often it refreshes. Host mode signs +through the product's allowance account on the RFC-10 sponsored path, so +hosting does not require the host to hold an Individuality allowance. + +`VITE_DOTIFY_ROOM_BEACONS` is `off` in the tracked Product profile: nothing +reads beacons yet, so publishing room records would be exposure with no +consumer, and the publish path has no live host evidence. See the deployment +runbook for the opt-in build and the evidence procedure. + +## Storage Boundaries + +Product static hosting replaces the web server for the Product build. It does +not replace: + +- Pinata-backed artist uploads; +- DAV2 audio encryption; +- backend-held `CONTENT_KEY_MASTER_SECRET`; +- server-side access verification; +- the durable catalog snapshot. + +Product cloud storage is host-scoped and experimental. Moving encrypted media +or key custody there requires a separate threat model, Range/startup evidence, +and a recovery plan. Until then, Fly remains the security boundary and IPFS +gateways remain the delivery boundary. + +## Runtime Port + +The contract integration is split into two typed ports rather than Product +conditionals throughout feature hooks: + +```text +RuntimeReadPort + resolveArtistRuntime() + listArtistRuntimes() + listRuntimeTracks() + canAccess() + hasPaid() + listRoyaltyPaymentLogs() + +RuntimeWritePort + createRuntime() + installRuntimeStep() + registerTrack() + setAccessMode() + setReleaseActive() + payForAccess() +``` + +Adapters: + +- `ViemRuntimeAdapter`: current standalone EVM implementation behind the typed + ports; +- `ProductCdmRuntimeAdapter`: CDM/PAPI implementation behind the same ports, + now backed by a real contract resolver (`productCdmContracts.ts`) over a + generated snapshot manifest. It remains opt-in behind + `VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm` until host transaction evidence + exists; +- `CatalogApiAdapter`: the existing server-side read model, shared by both + frontends. + +The CDM adapter has one deliberate gap: royalty payment history is not read +through Product contract handles because the current SDK surface exposes +method queries and transactions, not the viem-style historical log query used +by the artist console. Product mode must use the backend catalog/read-model +indexer, or a future Product event/indexer API, for that history. + +### The CDM Manifest Is Generated, Not Installed + +Dotify has no CDM-registered packages, and `cdm install` is not available. It +also does not need them. Dotify's Solidity contracts are deployed through Asset +Hub's `eth-rpc`, which is a compatibility layer over `pallet-revive` - the same +pallet the Product SDK contract helpers target. The deployed H160 addresses are +therefore already reachable through `@parity/product-sdk-contracts` with no +PolkaVM recompilation and no registry entry. + +`CdmJsonContract` needs only `version`, `address`, and `abi` for +`getContract()`, and `new ContractManager(...)` is documented as snapshot-only. +`web/scripts/generate-cdm-manifest.mjs` emits exactly that snapshot from the +same Hardhat artifacts the viem bindings come from, so the two adapters cannot +disagree about an ABI: + +| Output | Contents | +| --- | --- | +| `cdm.json` | `@dotify/artist-directory` and `@dotify/artist-runtime-factory` with their `deployments.json` addresses | +| `smartRuntime.ts` | merged artist-runtime diamond facet ABI, bound to a per-artist address at call time | +| `cdm.d.ts` | `Contracts` module augmentation for typed `getContract()` handles | + +Artist runtimes are deliberately absent from the manifest: a diamond is +deployed per artist, so its address is known at call time, not build time. +Inventing a placeholder address would misrepresent the deployment. +`productCdmContracts.ts` resolves those through `createContract`, which needs no +manifest entry. + +### Registering `@dotify/*` Without Redeploying + +`cdm deploy` builds, deploys, publishes metadata, and registers in one pass. +Dotify cannot use it: the contracts are already deployed and already hold the +live catalog, so deploying again would mint new addresses and orphan every +existing artist runtime. + +The registry contract itself provides the operation that is actually needed. +`publishLatest(contract_name, contract_address, metadata_uri)` binds a name to +an arbitrary address, and the contract's own comment states the rule: "The +caller only has permission to publish a new version of `contract_name` if +either the name is available or they are already the owner of the name." So a +free name is claimable by anyone, and afterwards only by its owner. +`metadata_uri` is stored verbatim and never validated - it is a pointer, not a +checked reference. + +`npm run cdm:publish:testnet` (task `cdm:publish`) performs that registration +for the addresses in `deployments.json`. It is read-only by default: it prints +the plan and the exact calldata, and stops. Registration is first-writer-owns +and the registry exposes no release or transfer entry point, so a claimed name +is permanent - execution therefore requires `--confirm` and an explicit key. + +The task refuses to proceed when a target address has no bytecode on the +connected chain, or when a name is already owned by another account. Publishing +a name that points at nothing would be worse than not publishing it. + +| Registry | Address | Network | +| --- | --- | --- | +| `devnet` preset | `0x59b0245778917af55224e5f8fb55f7f8d452619f` | Paseo Asset Hub, para 1000, chain 420420417 | + +CDM's own documentation confirms the preset distinction that +`VITE_DOTIFY_PRODUCT_CHAIN` encodes: "the `paseo` preset targets **paseo-next** +... para 1500 - not the Paseo testnet. The `devnet` preset targets the Paseo +testnet Asset Hub (para 1000, EVM chain id 420420417)." Publishing against the +`paseo` registry would register Dotify's names on a network where its contracts +do not exist. + +Note also that CDM does support Solidity, through a `/// @custom:cdm @org/name` +NatSpec tag and first-pass Hardhat and Foundry templates. The architecture page +mentions only PolkaVM bytecode, so this is easy to miss - it means Dotify's +existing toolchain is not an obstacle to CDM participation. + +### Why `cdm deploy` Cannot Be Used, Even With A Fresh Redeploy + +The obvious objection to the task above is that a redeploy would avoid all of +it. Dotify's on-chain data is test data, so that was worth checking properly +rather than assuming. It does not work, and the reason is a hard chain limit +rather than a preference. + +CDM's Solidity path compiles with `resolc` to PolkaVM, not with `solc` to EVM +bytecode. `resolc` compiles Dotify's contracts successfully - all 24 files, +including the diamond's `delegatecall` fallback and every one of its 17 inline +assembly blocks, with only an informational `extcodesize` warning from +`LibDiamond`. Feasibility is therefore not the blocker. + +Size is. The Asset Hub initcode limit is 49,152 bytes, and `resolc` emits +roughly 4-10x more bytecode than `solc` for the same source: + +| Contract | Deployed EVM | resolc PolkaVM | Against the 48 KB limit | +| --- | --- | --- | --- | +| `MusicRegistryPallet` | 8,855 | 71,252 | **over by 45%** | +| `SmartRuntime` | n/a | 41,142 | under | +| `DiamondCutPallet` | 4,753 | 39,408 | under | +| `ArtistRuntimeFactory` | 9,999 | 38,926 | under | +| `ArtistDirectory` | 1,829 | 17,325 | under | +| `MusicRightsRegistry` | not deployed | 88,955 | **over by 81%** | + +`MusicRegistryPallet` is the pallet that holds the catalog, so this is not an +optional component. Clearing the limit would mean splitting it into a +storage-only contract and a logic contract - and the practitioner report that +documents that workaround also records that diamond-style generic mappings were +*ineffective* at reducing size, which is precisely Dotify's architecture. + +So the ordering is: Asset Hub's `pallet-revive` accepts both EVM bytecode +through `eth-rpc` and PolkaVM blobs through `resolc`, and for Dotify the EVM +path is not a legacy compromise - it is the only one that currently fits. The +existing deployment sits comfortably inside the limit on every contract. + +`publishLatest` registration is therefore the correct mechanism, not a +workaround for an unwillingness to redeploy. Revisit only if `resolc` output +size improves substantially, or if the registry pallet is split for reasons of +its own. + +### Two Constraints On Product Contract Mode + +**It only runs inside a Product host.** `createChainClient`/`getChainAPI` route +exclusively through the host provider and throw when none is present - there is +no direct-WebSocket fallback. Product CDM mode is therefore impossible in the +standalone build, and `validateProductionEnvironment` rejects +`VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm` unless `VITE_DOTIFY_HOST_MODE` is +enabled. + +**The host decides which chain an environment resolves to**, and only one +environment is correct. See "DevNet Is Not A Separate Chain" below. +`verifyDeployment()` queries `artistCount` on the directory before any catalog +read, so a wrong-chain connection fails closed with a named error instead of +looking like a catalog of artists with no releases. + +### DevNet Is Not A Separate Chain + +Product DevNet is a *preset*, not a network. It targets the Paseo system +parachains - Asset Hub (1000), People (1004), Bulletin (1010) - with EVM chain +id `420420417` and the `dev-dot.li` web gateway. + +That is the chain Dotify is already deployed on. Verified read-only on +2026-07-29 by querying both endpoints for the ArtistDirectory at +`0xcf1534c6e2b0e43b9436c1e86a076466dc0f2108`: + +| Endpoint | `eth_chainId` | Block | Directory bytecode | +| --- | --- | --- | --- | +| `https://eth-rpc-testnet.polkadot.io/` | `0x190f1b41` | 11546347 | 3660 chars, sha256 `36707b24…` | +| `https://paseo-assethub-rpc.laissez-faire.trade` | `0x190f1b41` | 11546348 | 3660 chars, sha256 `36707b24…` | + +Same chain id, blocks one apart, byte-identical contract code. The two URLs are +different providers for one chain. + +**No contract redeploy is required to port Dotify to Product DevNet.** The +addresses in `deployments.json` are already DevNet addresses. + +The trap is the SDK's `paseo` preset, which points at the Paseo **Next** v2 +deployment (Asset Hub Next 1500 / People Next 1502). The Product documentation +is explicit that those "belong to a different network" and that "funds sent +there will not appear on this Devnet". Dotify has no deployment there, so +`ProductChainEnvironment` admits only `devnet` - a wrong preset is not a +configuration option, it is a bug. + +**Selection is build-time, and reads only.** `VITE_DOTIFY_RUNTIME_ADAPTER` is +inlined by Vite, so a `viem` build tree-shakes the entire Product contract graph +away - 4.4 MB output versus 10 MB when opted in. The difference is +`@parity/product-sdk-descriptors`, whose shared descriptors module references +every chain's metadata; only one chunk is ever fetched, but all are published, +and Bulletin storage is a finite quota. Contract *writes* stay on the viem +signer path in every mode, since routing a payment or a publication through an +unproven signer is not a reasonable default. + +The remaining gate for Product contract *writes* is now narrow: `pallet-revive` +account mapping for the signing account, and real host-signed transaction smoke +evidence from inside the container. The chain question is settled, the manifest +and types exist, and reads are wired. Until that write evidence exists, +`VITE_DOTIFY_RUNTIME_ADAPTER` defaults to `viem`. + +The backend authentication protocol now has an explicit signature scheme field. +Standalone clients use the default `eip191` scheme. Product-host clients can +use `product-sr25519-v1` after signing the same canonical Dotify message bytes +with the app-scoped Product account; the server binds the signature to the +Product public key, derived H160 requester, chain, nonce, purpose, and expiry +before consuming the nonce or running access checks. Unknown schemes fail +closed. The Product frontend now sends this proof shape after explicit +Product-host account connection; real Host smoke evidence is still required +for each Product publication before gated listening is treated as +production-ready. + +### Host Signing Envelope + +The SDK does not pin the `signRaw` wire format. `HostSignPayloadResponse` +carries an untagged signature, and a Substrate host may sign a raw payload +verbatim or inside the conventional `...` envelope. Guessing one +shape would make every Product key request fail on a wrong guess, and the +failure would be indistinguishable from a wrong signer. + +Verification therefore accepts a bounded set: + +- the canonical message verbatim, or wrapped in ``; +- a bare 64-byte sr25519 signature, or a 65-byte value carrying the + MultiSignature sr25519 tag `0x01`. + +This is not a weakening. Every accepted variant carries the identical +domain-bound message, so no new replay, cross-app, cross-chain, or cross-track +surface is created; an ed25519 or ECDSA tag is still rejected. A request whose +key parses and derives to the requester but verifies under no variant returns +`PRODUCT_SIGNATURE_REJECTED`, kept distinct from `SIGNATURE_INVALID` so +operators can separate an envelope problem from a wrong-account problem. + +`product-sr25519-v1` additionally rejects EVM-derived account ids - a 20-byte +H160 padded with `0xee`. Such a value derives straight back to the H160 it +contains, so accepting it would let a caller name any paying EVM listener as +the requester and rest the whole boundary on the curve check alone. A real +Product account is a native `AccountId32`, so that shape is refused outright. + +Once live host evidence records which envelope the host actually produces, the +accepted set can be narrowed to it. + +This avoids a second frontend business model and allows Product mode to replace +one infrastructure adapter at a time. + +## Permission And Failure Rules + +1. Host detection may run on startup; account access only runs after the user + chooses **Use Polkadot app**. +2. The integration does not request a username, identity proof, transaction + permission, or personhood proof before value is visible. +3. If the host is absent, catalog browsing, Free playback, and room links still + work. The wallet modal explains why the Product account is unavailable. +4. A Product account without an EVM signing adapter can request protected keys + only through `product-sr25519-v1`; contract writes still require a + passkey/EVM signer until Product CDM transaction evidence lands. +5. A denied key, RPC failure, or unsupported signature never falls back to a + browser content secret. +6. The Product SDK and deploy tooling are prototype/reference dependencies. + Version changes require the compatibility checks below. + +## Compatibility Gate + +The initial baseline is: + +| Component | Pinned/target value | +| --- | --- | +| Node | 22 | +| `@parity/product-sdk` | `0.19.1` | +| `@polkadot-community-foundation/polkadot-app-deploy` | `0.13.1` in the deploy command | +| Product network | `devnet` | +| Product domain | `dotify-test01.dot` | +| Public gateway | `https://dotify-test01.dev-dot.li` | +| Asset Hub EVM chain ID | `420420417` | + +For every SDK or deploy-tool upgrade: + +1. verify host detection outside and inside the container; +2. connect the Product account only on explicit action; +3. verify SS58 and derived H160 stability; +4. run normal and Product builds; +5. join one room across Netlify and Product origins; +6. verify Free playback remains walletless; +7. verify protected actions still fail closed without a supported signer; +8. inspect the static bundle and npm audit delta; +9. update this document, the environment reference, and the deployment runbook. + +## Source References + +- [Product documentation](https://docs.polkadotcommunity.foundation/) +- [Build and publish guide](https://docs.polkadotcommunity.foundation/guides/build-and-publish/) +- [Deploy and register contracts with CDM](https://docs.polkadotcommunity.foundation/guides/deploy-contracts-cdm/) +- [Smart contracts and CDM](https://docs.polkadotcommunity.foundation/architecture/contracts/) +- [Platform Services SDK guide](https://docs.polkadotcommunity.foundation/guides/platform-services-sdk/) +- [Product network reference](https://docs.polkadotcommunity.foundation/reference/networks/) +- [Product identity architecture](https://docs.polkadotcommunity.foundation/architecture/identity/) +- [Product messaging architecture](https://docs.polkadotcommunity.foundation/architecture/messaging/) diff --git a/docs/index.html b/docs/index.html index 3b9ae43..aeeade6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1537,11 +1537,16 @@

Operate the spine and validate first sound

  • -

    Sequenced later

    -

    Product SDK, personhood, and cultural transmission

    +

    Adaptive Product path

    +

    Product DevNet now, sovereignty adapters next

    - Product SDK mode, Humanity/Individuality proofs, consented provenance, and ambassador - mechanics remain future work until the current APIs and privacy boundaries are proven. + Dotify now has a publishable dotify-test01.dot build, explicit app-scoped Product + identity, public room links that preserve wallet-free entry, typed runtime ports around the + current viem implementation, an experimental CDM/PAPI adapter boundary, and API-side Product + sr25519 verification with frontend Product proof submission for protected key/session requests. + Product contract writes, real Host smoke evidence for gated playback, Humanity/Individuality + proofs, consented provenance, and ambassador mechanics remain sequenced behind verified security + and privacy boundaries.

  • @@ -1601,11 +1606,20 @@

    Explore the project

    +
  • + + + Product DevNet architecture + What the Product host adapts now, what remains on Fly, and how runtime ports prepare CDM/PAPI. + + + +
  • Product SDK roadmap - How Dotify aligns with Product SDK, Playground, Statement Store, and Humanity. + The delivered Product baseline and the remaining CDM, signature, presence, and Humanity work. diff --git a/docs/operations/deployment-configuration.md b/docs/operations/deployment-configuration.md index a6c239f..3f7b86a 100644 --- a/docs/operations/deployment-configuration.md +++ b/docs/operations/deployment-configuration.md @@ -1,7 +1,7 @@ # Deployment Configuration Runbook This runbook is the operator checklist for Dotify's hosted configuration across -Netlify and Fly.io. Use it when changing dashboard values, deploy contexts, +Netlify, Product DevNet, and Fly.io. Use it when changing dashboard values, deploy contexts, `*.toml` settings, hosted origins, secrets, catalog persistence, or production smoke settings. @@ -30,16 +30,18 @@ Keep this document aligned with | Surface | Host | App/project | Source config | Purpose | | --- | --- | --- | --- | --- | | Frontend | Netlify | `muzinga` | `netlify.toml` | Static Vite web app | +| Product frontend | Bulletin + DotNS | `dotify-test01.dot` | `web/.env.product-devnet`, `web/polkadot-app-deploy.config.ts` | Product-host static app | | Backend API | Fly.io | `dotify-api` | `services/api/fly.toml` | Uploads, key delivery, catalog read model, health | | Signaling | Fly.io | `dotify-signal` | `web/fly.signal.toml` | Socket.IO room discovery and WebRTC signaling | Production URLs currently assumed by the app and docs: ```txt -Frontend: https:// +Standalone: https://muzinga.netlify.app +Product: https://dotify-test01.dev-dot.li Backend API: https://dotify-api.fly.dev Signaling: https://dotify-signal.fly.dev -IPFS gateway: https://paseo-ipfs.polkadot.io +Product IPFS: https://devnet-ipfs.api.polkadotcommunity.foundation Asset Hub RPC: https://eth-rpc-testnet.polkadot.io/ ``` @@ -94,6 +96,7 @@ Required production variables: | Key | Value | Notes | | --- | --- | --- | | `VITE_DOTIFY_DEPLOYMENT` | `production` | Enables fail-closed production env validation. | +| `VITE_DOTIFY_HOST_MODE` | `off` | Prevents the standalone build from probing Product host APIs. | | `VITE_SIGNAL_URL` | `https://dotify-signal.fly.dev` | Public Socket.IO signaling origin. | | `VITE_DOTIFY_API_URL` | `https://dotify-api.fly.dev` | Backend API for uploads, key delivery, and cached catalog reads. | | `VITE_PINATA_GATEWAY` | `https://paseo-ipfs.polkadot.io` | Primary browser read gateway. | @@ -115,10 +118,62 @@ Optional production variables: Deploy-preview note: Netlify deploy previews usually have their own origin. The signaling service -can allow multiple origins with `SIGNAL_ORIGINS`, but the backend API currently -accepts one `API_ORIGIN`. For PR evidence, use a stable frontend origin, a -dedicated staging site, or temporarily set `API_ORIGIN` to the deploy-preview -origin and restore it after validation. +and backend both allow multiple exact origins with `SIGNAL_ORIGINS` and +`API_ORIGINS`. Add only the specific preview origin needed for evidence, then +remove it after validation. Never use `*` on the backend. + +## Product DevNet Frontend + +The browser-safe Product build profile is tracked in +`web/.env.product-devnet`. The manifest is +`web/polkadot-app-deploy.config.ts`. + +Required Product values: + +| Key | Current value | +| --- | --- | +| `VITE_DOTIFY_DEPLOYMENT` | `production` | +| `VITE_DOTIFY_HOST_MODE` | `required` | +| `VITE_DOTIFY_PRODUCT_ID` | `dotify-test01.dot` | +| `VITE_PUBLIC_APP_URL` | `https://dotify-test01.dev-dot.li` | +| `VITE_DOTIFY_API_URL` | `https://dotify-api.fly.dev` | +| `VITE_SIGNAL_URL` | `https://dotify-signal.fly.dev` | +| `VITE_DOTIFY_ROOM_BEACONS` | `off` | + +`VITE_DOTIFY_ROOM_BEACONS` is off in the tracked profile, so the standard +publication announces no rooms on the Statement Store. The capability ships +dormant on purpose: nothing reads beacons yet, so publishing room records to a +public chain would be exposure with no consumer, and the publish path has no +live host evidence. Enabling also adds about 24 KB to every publication, against +a finite Bulletin quota. + +To publish a build that does announce: + +```bash +cd web +npm run deploy:product-devnet:beacons +``` + +Rolling back is a normal republication with the flag absent - the standard +`npm run deploy:product-devnet` produces the `off` build. Beacons already +published expire on their own within the statement TTL; there is no revocation +step, and none is needed. + +`VITE_PINATA_JWT` and `VITE_CONTENT_SECRET` are explicitly empty in that +profile so a developer's generic local `.env` cannot leak demo credentials +into the Product bundle. + +Build and publication: + +```bash +cd web +npm run build:product-devnet +npm run deploy:product-devnet +``` + +Use +[`docs/operations/product-devnet-deployment.md`](product-devnet-deployment.md) +for authentication, publication, validation, and rollback. ## Fly Backend API @@ -136,6 +191,7 @@ Non-secret runtime values are tracked in `services/api/fly.toml`: | --- | --- | | `API_PORT` | `8790` | | `NODE_ENV` | `production` | +| `API_ORIGINS` | `https://muzinga.netlify.app,https://dotify-test01.dev-dot.li` | | `PASEO_ASSET_HUB_RPC` | `https://eth-rpc-testnet.polkadot.io/` | | `DOTIFY_FACTORY_ADDRESS` | `0xbd1a11cfce8b5ef7a37e507bc5109895f8f42a72` | | `DOTIFY_DIRECTORY_ADDRESS` | `0xcf1534c6e2b0e43b9436c1e86a076466dc0f2108` | @@ -145,7 +201,6 @@ Set server-side values in the app's Secrets area: | Secret | Required | Notes | | --- | --- | --- | -| `API_ORIGIN` | Production | Exact frontend origin allowed by API CORS. One URL only. | | `PINATA_JWT` | Uploads | Backend-only Pinata token. Never expose in Netlify. | | `CONTENT_KEY_MASTER_SECRET` | Audio upload and key delivery | 64+ hex chars, at least 32 random bytes. Do not rotate casually. | | `GIT_COMMIT_SHA` | Optional | Set by CI/build automation when available; `/version` can fall back in dev checkouts. | @@ -175,6 +230,25 @@ For production-grade catalog evidence: - keep at least one machine warm while measuring catalog p75 performance, then record whether the trace was warm or cold. +### Backend Signature Schemes + +No Netlify or Fly dashboard variable enables Product signatures. The API +accepts two explicit schemes on session sign-in and protected key requests: + +| Scheme | Client | Required proof fields | Backend binding | +| --- | --- | --- | --- | +| `eip191` | Standalone EVM/passkey wallet path | `signature` | `viem.verifyMessage` against the requester H160 | +| `product-sr25519-v1` | Product-host app-scoped account path | `signature`, `productPublicKey` | sr25519 signature over the canonical Dotify message bytes, then Product public-key-to-H160 derivation matching the requester | + +Unknown schemes fail at the API schema boundary. Product requests must still +pass the same nonce, chain, purpose, expiry, and `musicAccCanAccess` checks as +standalone requests. The Product frontend submits this proof shape only after +an explicit Product-host account connection; contract writes remain on the +standalone EVM/passkey signer path until the Product CDM transaction adapter has +real host-signed transaction evidence. Validate Product protected playback +through host smoke tests after each Product publication before treating Product +identity as production-ready for gated listening. + ## Fly Signaling Open app `dotify-signal`. @@ -188,12 +262,12 @@ Non-secret runtime values are tracked in `web/fly.signal.toml`: | `SIGNAL_ROOM_TTL_MS` | `21600000` | | `SIGNAL_HOST_TIMEOUT_MS` | `120000` | | `SIGNAL_MAX_LISTENERS` | `24` | +| `SIGNAL_ORIGINS` | `https://muzinga.netlify.app,https://dotify-test01.dev-dot.li` | -Set hosted frontend origins in the app's Secrets area: - -| Secret | Value | -| --- | --- | -| `SIGNAL_ORIGINS` | Exact comma-separated frontend origins, for example `https://muzinga.netlify.app,https://` | +The production origins are public configuration tracked in +`web/fly.signal.toml`; they are not secrets. Temporary preview origins may be +set through Fly configuration, but the tracked production allowlist must be +restored after validation. Keep `dotify-signal` on one active machine until a shared Socket.IO adapter is added. Rooms, chat, reactions, request queues, and solo-presence aggregates are @@ -227,9 +301,14 @@ curl -s https://dotify-signal.fly.dev/status cd web npm run smoke:production-env npm run smoke:signal -- --url https://dotify-signal.fly.dev --origin https:// +npm run build:product-devnet ``` -6. For explicit origin rejection evidence, include a denied origin: +6. For a Product release, complete the cross-origin room and host-account +checks in +[`docs/operations/product-devnet-deployment.md`](product-devnet-deployment.md). + +7. For explicit origin rejection evidence, include a denied origin: ```bash cd web diff --git a/docs/operations/product-devnet-deployment.md b/docs/operations/product-devnet-deployment.md new file mode 100644 index 0000000..c93401a --- /dev/null +++ b/docs/operations/product-devnet-deployment.md @@ -0,0 +1,399 @@ +# Deploy Dotify To Product DevNet + +This runbook publishes the Product build to Bulletin/DotNS and connects it to +the existing Fly API and signaling services. It does not deploy contracts or +change production secrets. + +## Contracts Need No Redeploy + +Product DevNet is a preset over the Paseo system parachains - Asset Hub (1000), +People (1004), Bulletin (1010) - at EVM chain `420420417`. Dotify's contracts +are already deployed on that chain, so porting to DevNet is a configuration +change, not a migration. The addresses in `deployments.json` are DevNet +addresses. + +Confirm before every publish: + +```bash +cd web +npm run smoke:devnet +``` + +It reads `web/.env.product-devnet` and `deployments.json` and checks, read-only, +that the configured Asset Hub reports chain `420420417`, is producing blocks +past the 2026-07 halt, still serves bytecode for the ArtistDirectory and +ArtistRuntimeFactory, and that the Bulletin RPC and IPFS gateway respond. It +sends no transaction and prints no credential. + +Do not point the build at **Asset Hub Next (1500)** or **People Next (1502)**. +The Product documentation is explicit that those belong to a different network; +Dotify has no contracts there, and the catalog would load empty. + +## Publishing CDM Metadata And Registering `@dotify/*` + +Separate from the frontend publish below, and only needed when the contracts or +their ABIs change. + +The registry stores `(name -> address)` and `(name -> metadata_uri)`. Without +the second, another product can resolve where Dotify's contracts are but not +what they expose, so `cdm install` fails and the registration is nominal. + +**1. Generate the metadata blobs and their CIDs.** + +```bash +cd web +npm run generate:cdm-metadata +``` + +Writes `web/src/generated/contracts/cdm-metadata/` - one JSON blob per package +plus `cids.json`. Output is deterministic: the same contracts produce the same +bytes and therefore the same CIDs, so the published blob can always be checked +against the repository. `published_at` is omitted for that reason; pass +`--published-at ` if a publication date is wanted, accepting that it +changes the CID. + +**2. Upload the blobs to Bulletin.** + +This needs a live Bulletin storage authorization for the uploading account - +the same finite, expiring quota the frontend publish uses. Upload each JSON file +from that directory and confirm the returned CID matches `cids.json`. A mismatch +means the bytes changed in transit and must not be registered. + +**3. Register the names.** + +```bash +cd contracts/evm +npm run cdm:publish:testnet # dry run, prints the plan +npx hardhat cdm:publish --network polkadotTestnet --confirm +``` + +No key needs to be supplied. The task signs with the account hardhat already +holds for this network, sourced from the encrypted `PRIVATE_KEY` var +(`npx hardhat vars set PRIVATE_KEY`). Pass `--private-key` only to publish from +a different account than the deployer. + +That account matters beyond paying fees: `publish_latest` records the caller as +the **permanent owner** of every name it creates, so whoever signs owns +`@dotify/*` from then on. The dry run prints the resolved publisher for exactly +that reason - check it before confirming. + +The task reads `cids.json`, so the CID published on-chain is derived from the +generated bytes rather than pasted by hand. It is read-only without `--confirm`. + +Registration is first-writer-owns and the registry exposes no release or +transfer entry point, so **a claimed name is permanent**. Confirm the namespace +before the first publish. The task refuses to register an address with no +bytecode, or a name owned by another account. + +Verify afterwards from a consuming project: + +```bash +cdm i -n devnet @dotify/artist-directory @dotify/artist-runtime-factory +``` + +Use `-n devnet`, never `-n paseo`: the `paseo` preset targets paseo-next +(para 1500), which holds none of Dotify's contracts. + +## Prerequisites + +- Node.js 22 and npm 10+ +- a clean build from the intended commit +- access to the `dotify-test01.dot` deployment account +- Fly access for `dotify-api` and `dotify-signal` +- the current `@polkadot-community-foundation/polkadot-app-deploy` DevNet prerequisites + +The CLI is reference/experimental tooling. Do not store a mnemonic in the +repository, shell history, `.env` files, Netlify, or Fly. + +Before the first publish, the signing account also needs: + +- DevNet native tokens on Asset Hub; +- an EVM account mapping (`dotns account map --env devnet`); +- a live Bulletin storage authorization for the same SS58 account; +- ownership of `dotify-test01.dot`, or eligibility to register it during deploy. + +`dotify.dot` currently requires full personhood on Product DevNet. Until the +project has that proof level, use `dotify-test01.dot` and +`https://dotify-test01.dev-dot.li` for operator deployments. + +Bulletin authorization is a finite quota and may expire. A deploy that starts +failing at the upload stage after previously working should recheck that quota. +See the official +[build and publish guide](https://docs.polkadotcommunity.foundation/guides/build-and-publish/) +for the current faucet, storage console, mapping, and DotNS registration steps. + +## 1. Verify The Fly Origin Boundary + +The tracked Fly configuration must contain: + +```txt +API_ORIGINS=https://muzinga.netlify.app,https://dotify-test01.dev-dot.li,polkadot://app.dotify-test01.dot +SIGNAL_ORIGINS=https://muzinga.netlify.app,https://dotify-test01.dev-dot.li,polkadot://app.dotify-test01.dot +``` + +Three frontends reach these services: Netlify, the DotNS web gateway, and the +app as served inside the Product host container, which uses a custom scheme. +Both lists must carry all three - a container with only the signaling origin +gets rooms but no content keys, because catalog and key delivery go to the API. + +`polkadot:` is a non-special scheme, so its origin is opaque and a browser may +send `Origin: null` rather than the literal value. If a host request is still +refused after this change, read the actual `Origin` header from the Fly log +before widening either list. Never add a bare `null`: that admits every +sandboxed iframe and `file://` page on the web to the authenticated upload and +content-key routes. A regression test in `services/api/src/cors.test.ts` pins +that refusal. + +Deploy both services before publishing the frontend. `cd` into each service +first - this is not cosmetic: + +```bash +cd services/api +flyctl deploy + +cd ../../web +flyctl deploy -c fly.signal.toml +``` + +`-c` selects the config file only; it does not set the Docker build context, +which is always the shell's working directory. Running +`flyctl deploy -c services/api/fly.toml` from the repository root fails at +`COPY src ./src`, because the Dockerfile is written against `services/api` as +its context and there is no `src/` at the root. It also uploads a ~1.3 GB +context, since Docker reads `.dockerignore` from the context root and only the +service directories have one. Passing the directory positionally +(`flyctl deploy services/api`) works too, because that sets the context. + +An earlier cached layer can hide the mistake: `COPY package*.json ./` and +`npm ci` may report `CACHED` from a previous correct build, so the failure +surfaces at the first genuinely uncached step rather than the first wrong one. + +Keep backend secrets unchanged. `API_ORIGINS` supersedes singular +`API_ORIGIN`; the latter remains only as a compatibility fallback. + +## 2. Verify The Browser-Safe Build Profile + +Review `web/.env.product-devnet`. It must contain only public endpoints and +identifiers. In particular: + +```txt +VITE_DOTIFY_HOST_MODE=required +VITE_DOTIFY_PRODUCT_ID=dotify-test01.dot +VITE_PUBLIC_APP_URL=https://dotify-test01.dev-dot.li +VITE_DOTIFY_API_URL=https://dotify-api.fly.dev +VITE_SIGNAL_URL=https://dotify-signal.fly.dev +VITE_PINATA_JWT= +VITE_CONTENT_SECRET= +``` + +`VITE_PUBLIC_APP_URL` is the URL copied for room invitations. Do not replace it +with an internal host URL or a raw CID gateway. + +## 3. Build Locally + +```bash +cd web +npm ci +npm run test:unit +npm run smoke:devnet +npm run build:product-devnet +``` + +Expected output is `web/dist-product`. The production guard must fail if a +browser upload token or content secret is present. + +The default build keeps the viem runtime adapter, which tree-shakes the Product +contract graph away and publishes at roughly 4.4 MB. Building with +`VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm` pulls in the Product SDK descriptors +and roughly doubles that. Bulletin storage is a finite quota, so only opt in +when the Product contract path is actually being exercised. + +## 4. Authenticate The Deploy Tool + +The repository pins the CLI version in the npm deploy command but does not add +the experimental deploy tool to the application dependency tree. + +```bash +npx --yes --package @polkadot-community-foundation/polkadot-app-deploy@0.13.1 pad login --env devnet +npx --yes --package @polkadot-community-foundation/polkadot-app-deploy@0.13.1 pad whoami --env devnet +``` + +Follow the mobile-wallet flow. Confirm the selected account owns, or can +receive, `dotify-test01.dot` and satisfies the DevNet registration/funding +rules. + +## 5. Publish + +```bash +npm run deploy:product-devnet +``` + +The command: + +1. rebuilds `dist-product`; +2. validates `polkadot-app-deploy.config.ts`; +3. creates content-addressed chunks with the JavaScript merkle implementation; +4. uploads changed content to Product DevNet Bulletin; +5. binds `dotify-test01.dot`; +6. writes the Product manifest and executable records. + +Publisher listing is deliberately not part of the default deploy. It requires +the current Product proof-of-personhood level and signer support, and the +0.13.1 CLI help still describes environment-specific limitations. After the +app URL is verified, follow the current official **List it in Browse** guide +and record that result separately. A listing failure must not obscure a +successful static deployment. + +Record the commit, CLI version, resulting CID, DotNS transaction references, +and final public URL in the release evidence. + +## 6. Validate + +Check service CORS from both origins: + +```bash +curl -s -D - -o /dev/null \ + -H 'Origin: https://dotify-test01.dev-dot.li' \ + https://dotify-api.fly.dev/health + +curl -s -D - -o /dev/null \ + -H 'Origin: https://muzinga.netlify.app' \ + https://dotify-api.fly.dev/health +``` + +Then verify in the Product host: + +1. `https://dotify-test01.dev-dot.li` opens and shows catalog tracks. +2. Free playback starts without connecting an account. +3. **Use Polkadot app** connects an app-scoped Product account only after the + button is selected. +4. A protected track requests its key through the Product identity using + `product-sr25519-v1`. Record which happened: + - accepted, and playback starts: capture the request/response pair as the + Product signing evidence this build needs; + - denied with `PRODUCT_SIGNATURE_REJECTED`: the key and requester bound + correctly but the host signing envelope is not one this API accepts. + Capture the Fly log line and the raw host signature length before + changing anything; + - denied with any other code: treat as a normal fail-closed denial. + + In every rejected case, playback must stop and offer a passkey/EVM wallet. + No path may release a key without a verified signature. +5. A Product-origin host creates a room and copies a + `https://dotify-test01.dev-dot.li/#/rooms/` link. +6. A wallet-free browser joins that link from outside the Product host. +7. A Netlify-origin host and Product-origin guest also connect. +8. Closing the host ends the room as before. + +Inspect the browser console and Fly logs for CORS, catalog, Socket.IO, and +WebRTC failures. + +## Room Beacons (Dormant By Default) + +The Statement Store beacon capability ships but is **not enabled** by the +standard publication. `web/.env.product-devnet` sets +`VITE_DOTIFY_ROOM_BEACONS=off`, and `npm run deploy:product-devnet` rebuilds in +that mode, so a normal publish announces no rooms. + +That is deliberate. Nothing reads beacons yet, so publishing room records to a +public chain would be exposure with no consumer, and the publish path has no +live host evidence. Treat this section as the procedure for collecting that +evidence, not as part of a routine release. + +### Prerequisites + +- `VITE_DOTIFY_HOST_MODE` must be `auto` or `required`. The statement store + client runs only inside the Product host container, and the production guard + refuses `VITE_DOTIFY_ROOM_BEACONS=on` without it. +- No Individuality allowance is needed for the host account: host mode signs + through the product's allowance account on the RFC-10 sponsored path. +- Expect about 24 KB of extra publication weight against the Bulletin quota. + +### Publish an announcing build + +```bash +cd web +npm run deploy:product-devnet:beacons +``` + +### Collect live evidence + +Inside the Product host, with the announcing build open: + +1. Create a room. Watch the browser console. A refused publish logs + `[dotify] room beacon not published (): `; nothing is logged + on success. +2. Record which happened: + - **published** - capture the room code and the fact that no warning + appeared. This is the first evidence the publish path works end to end; + - **`rejected`** - the statement store refused the write. Most often the + account-wide quota, which the client cannot observe. Capture the detail + line before changing anything; + - **`transport`** - the client could not reach the store at all; + - **`quota-local`** - this instance's own beacons already fill the + 1024-byte account budget. +3. Confirm hosting is unaffected in every case: the room must still be + joinable from its share link by a wallet-free browser. A beacon failure that + degrades hosting is a defect, not a limitation. +4. With a second client in the host, confirm the room appears through + `subscribeRoomBeacons` and disappears within roughly the statement TTL plus + one sweep after the host stops. + +Record the outcome in the release evidence. Until step 2 shows a published +beacon, treat the capability as unproven regardless of unit coverage. + +### Rollback + +Republish without the flag: + +```bash +cd web +npm run deploy:product-devnet +``` + +Beacons already published expire on their own within the statement TTL. There +is no revocation step and none is needed - a beacon carries no key, no +identity, and no durable claim. + +## Rollback + +The Product deployment is static. To roll back: + +1. switch to the last known-good commit; +2. run `npm ci`; +3. run the full build and smoke checks; +4. republish with `npm run deploy:product-devnet`; +5. confirm DotNS resolves to the restored content; +6. record the replacement CID and incident reason. + +Do not roll back Fly origin allowlists while either public frontend remains +active. + +## Known Limits + +- Product account signing is accepted by the API only through the explicit + `product-sr25519-v1` session/key-request scheme. The Product UI now submits + that proof shape after an explicit host-account connection, but each published + Product build still needs real Host smoke evidence before gated playback is + considered production-ready on Product DevNet. +- The Host `signRaw` wire format is not pinned by the SDK: the response + signature is untagged, and a Substrate host may sign the payload verbatim or + inside a `` envelope. The API accepts both envelopes and both a bare + 64-byte and a MultiSignature-tagged 65-byte sr25519 signature, so a correct + host signature verifies regardless of which shape it uses. Step 6.4 above + records which shape the live host actually produced - that observation is the + evidence, and until it is captured the accepted set stays deliberately wide. +- Contract writes still require passkey/EVM signing in the shipped UI. The + Product CDM/PAPI runtime adapter now has its generated manifest, contract + types, and a live resolver, so the only thing still missing before it can be + selected is `pallet-revive` account mapping plus real host-signed transaction + evidence. +- Rooms still depend on one in-memory Fly signaling machine. +- Product-host cloud storage does not hold Dotify audio or content keys. +- Product personhood is not yet an access decision source. +- A durable `CATALOG_SNAPSHOT_PATH` remains recommended for production-grade + catalog recovery but is not required for API startup. +- Product contract mode (`VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm`) covers + catalog reads only, and only inside the Product host. Contract writes stay on + the passkey/EVM signer in every mode until `pallet-revive` account mapping and + host-signed transaction evidence exist. diff --git a/docs/product/ux-signature-flows.md b/docs/product/ux-signature-flows.md index 8f6e892..95b0549 100644 --- a/docs/product/ux-signature-flows.md +++ b/docs/product/ux-signature-flows.md @@ -25,6 +25,21 @@ Dotify must avoid wallet pop-up fatigue. Wallet prompts should appear only when | Human Free unlock | Yes | Maybe session signature | No, unless proving/linking personhood requires one | | Artist publishing | Yes | Yes/transaction depending on step | Yes for runtime/register actions | +## Backend signature schemes + +Signed session and protected key requests carry an explicit `signatureScheme`. +If the field is omitted, the backend treats the request as `eip191` for +backward compatibility. + +| Scheme | Signer | Extra fields | Verification | +| --- | --- | --- | --- | +| `eip191` | Standalone EVM/passkey wallet | `signature` | Verify the canonical Dotify message with the requester H160 address. | +| `product-sr25519-v1` | App-scoped Product account | `signature`, `productPublicKey` | Verify sr25519 over the same canonical message bytes, derive H160 from the Product public key, and require it to match the requester. | + +Unknown schemes and Product public-key mismatches fail closed before nonce +consumption. Every successful signature path still runs the runtime access +check before the backend releases a content key. + ## Individual playback flow ```mermaid diff --git a/docs/reference/environment-variables.md b/docs/reference/environment-variables.md index e255b55..133bf90 100644 --- a/docs/reference/environment-variables.md +++ b/docs/reference/environment-variables.md @@ -36,14 +36,166 @@ production build contract without printing real secret values. --- +### `VITE_DOTIFY_HOST_MODE` + +| Property | Value | +| ------------ | ---------------------------- | +| **Type** | `off`, `auto`, or `required` | +| **Required** | Product builds | +| **Default** | `off` | +| **Example** | `required` | + +Controls Product host discovery. `off` keeps the standalone app independent +from the Product SDK. `auto` enables progressive host detection. `required` +marks a Product-targeted build but does not block catalog, Free playback, or +wallet-free room entry when opened outside the host. + +Host detection does not request an account. The account is requested only when +the listener selects **Use Polkadot app**. + +--- + +### `VITE_DOTIFY_RUNTIME_ADAPTER` + +| Property | Value | +| ------------ | ----------------------- | +| **Type** | `viem` or `product-cdm` | +| **Required** | No | +| **Default** | `viem` | +| **Example** | `viem` | + +Selects which adapter backs the runtime contract ports. `viem` is the only path +with production evidence. `product-cdm` routes reads and writes through the +Product SDK contract handles over the generated `cdm.json` snapshot. + +This selects **reads only**. Contract writes stay on the viem signer path in +every mode, because the Product write path has no host-signed transaction +evidence yet. + +Any unrecognised value falls back to `viem`, so a typo cannot silently disable +contract reads. `product-cdm` additionally requires `VITE_DOTIFY_HOST_MODE` to +be `auto` or `required`: the Product chain client connects only through a host +container and has no direct-WebSocket fallback. The production guard rejects +that combination rather than shipping a frontend that cannot read the catalog. + +**Build size.** This flag is read at build time, not runtime. A `viem` build +tree-shakes the entire Product contract graph away; opting in pulls it back in +along with `@parity/product-sdk-descriptors`, whose shared descriptors module +references every chain's metadata. Measured on this branch: + +| Build | Output size | +| --------------------------------------------- | ----------- | +| `VITE_DOTIFY_RUNTIME_ADAPTER` unset or `viem` | 4.4 MB | +| `VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm` | 10 MB | + +Only one metadata chunk is ever fetched at runtime, but all of them are +published. Weigh that against the Bulletin storage quota before enabling this +for a `.dot` deployment. + +--- + +### `VITE_DOTIFY_ROOM_BEACONS` + +| Property | Value | +| ------------ | ------------ | +| **Type** | `on` or `off` | +| **Required** | No | +| **Default** | `off` | +| **Example** | `off` | + +Publishes a small beacon to the Statement Store while hosting a room, so the +room can be discovered without Dotify's signaling server. + +This is **discovery only**. A beacon never carries SDP, ICE, chat, or audio, and +is never required to join: a share link still works with no wallet, no account, +and no chain. Joining cannot move here - a WebRTC offer is 1.5-4 KB against a +512-byte statement ceiling, and a guest would have to publish an answer, which +needs an identity and an allowance. That would turn every listener into a +registered person. + +Only a host publishes, and only while hosting. Requires `VITE_DOTIFY_HOST_MODE` +to be `auto` or `required`: the statement store client runs only inside the +Product host container, so enabling beacons without it would ship chain code +that can never connect. The production guard rejects that combination. + +A beacon carries the room code, host display name, and an aggregate listener +count - never listener identities. Now-playing is opt-in per host, because a +beacon is globally readable and outlives the room by up to the retention window, +which is a different exposure than sharing a link. + +**Build size.** Enabling this adds about 24 KB. A build with it `off` still +carries a ~69 KB statement-store chunk that is never fetched at runtime: Rollup +emits a chunk for the nested dynamic import before it can prove the build-time +guard makes it unreachable. That is ~1.5% of the bundle, and the code never +executes, but it is published weight against the Bulletin quota. + +--- + +### `VITE_DOTIFY_PRODUCT_CHAIN` + +| Property | Value | +| ------------ | -------- | +| **Type** | `devnet` | +| **Required** | No | +| **Default** | `devnet` | +| **Example** | `devnet` | + +Product chain preset used only when `VITE_DOTIFY_RUNTIME_ADAPTER=product-cdm`. + +`devnet` is the only accepted value, and that is a correctness constraint. +Product DevNet is a preset over the Paseo system parachains - Asset Hub (1000), +People (1004), Bulletin (1010) - at EVM chain `420420417`, which is exactly +where Dotify's contracts are deployed. + +The SDK's `paseo` preset is *not* an alternative: it targets Paseo Next +(Asset Hub Next 1500 / People Next 1502), which the Product documentation calls +a different network. Selecting it would resolve every manifest address to an +account with no code - indistinguishable from artists with no releases. +`verifyDeployment()` turns that into an explicit error at startup, and the +config layer refuses the value outright. + +Regenerate the manifest with `npm run generate:cdm` after any contract +redeploy, or the addresses in `cdm.json` go stale. + +--- + +### `VITE_DOTIFY_PRODUCT_ID` + +| Property | Value | +| ------------ | ---------------------- | +| **Type** | Lowercase `.dot` name | +| **Required** | Host mode is not `off` | +| **Default** | `dotify-test01.dot` | +| **Example** | `dotify-test01.dot` | + +DotNS identifier used by the Product host to derive Dotify's app-scoped +account. Changing it changes the Product account boundary and requires an +identity/access migration review. + +--- + +### `VITE_PUBLIC_APP_URL` + +| Property | Value | +| ------------ | ---------------------------------- | +| **Type** | HTTPS URL | +| **Required** | Product production builds | +| **Default** | Current browser URL | +| **Example** | `https://dotify-test01.dev-dot.li` | + +Canonical public origin used when copying room links. Product builds must set +this so invitations never expose an internal host/container or raw gateway URL. + +--- + ### `VITE_DOTIFY_DEBUG_PANEL` -| Property | Value | -| ------------ | ----------------- | -| **Type** | Boolean string | -| **Required** | No | -| **Default** | `false` | -| **Example** | `true` | +| Property | Value | +| ------------ | -------------- | +| **Type** | Boolean string | +| **Required** | No | +| **Default** | `false` | +| **Example** | `true` | Enables the optional Production readiness panel under the `You` tab. The panel performs read-only checks for the backend readiness endpoint, signaling health, @@ -78,8 +230,10 @@ Production deployments must use a publicly reachable HTTPS endpoint. Backend API base URL. When set, audio, cover, and metadata uploads go through the backend. Full-track playback can request content keys with wallet-signed -requests. When unset, the web app falls back to local/demo browser-side Pinata -upload and `VITE_CONTENT_SECRET` encryption. +requests. The backend accepts the default `eip191` signature scheme and the +Product-host `product-sr25519-v1` scheme without an additional env flag. When +unset, the web app falls back to local/demo browser-side Pinata upload and +`VITE_CONTENT_SECRET` encryption. --- @@ -241,12 +395,12 @@ Network interface to bind. ### `SIGNAL_ORIGINS` -| Property | Value | -| ------------ | --------------------------------------------------- | -| **Type** | Comma-separated URL list or `*` | -| **Required** | No | -| **Default** | `*` | -| **Example** | `https://muzinga.netlify.app,https://dotify.dot.li` | +| Property | Value | +| ------------ | ----------------------------------------------------------------------------------------------- | +| **Type** | Comma-separated URL list or `*` | +| **Required** | No | +| **Default** | `*` | +| **Example** | `https://muzinga.netlify.app,https://dotify-test01.dev-dot.li,polkadot://app.dotify-test01.dot` | CORS allowed origins for Socket.IO and status endpoints. Set explicit frontend origins in production. `SIGNAL_ORIGIN` is still accepted as a backwards-compatible @@ -327,7 +481,23 @@ Port the backend API listens on. | **Required** | Production | | **Default** | `http://localhost:5273` | -Frontend origin allowed by backend CORS. +Singular frontend origin allowed by backend CORS. This remains as a +backwards-compatible fallback when `API_ORIGINS` is not set. + +--- + +### `API_ORIGINS` + +| Property | Value | +| ------------ | ----------------------------------------------------------------------------------------------- | +| **Type** | Comma-separated HTTPS origin list | +| **Required** | Multiple hosted frontends | +| **Default** | The single `API_ORIGIN` value | +| **Example** | `https://muzinga.netlify.app,https://dotify-test01.dev-dot.li,polkadot://app.dotify-test01.dot` | + +Exact frontend origins accepted by backend CORS. When set, it takes precedence +over `API_ORIGIN`. Do not use `*`: the API carries authenticated upload and +content-key routes. --- diff --git a/services/api/.env.example b/services/api/.env.example index b86aba5..bcdf622 100644 --- a/services/api/.env.example +++ b/services/api/.env.example @@ -6,6 +6,8 @@ API_PORT=8790 # Frontend origin allowed by CORS. API_ORIGIN=http://localhost:5273 +# Comma-separated origins take precedence over API_ORIGIN when set. +# API_ORIGINS=https://muzinga.netlify.app,https://dotify-test01.dev-dot.li # Paseo Asset Hub EVM RPC. Required for wallet-signed content-key requests: # the key route resolves the owning artist runtime via the directory and calls @@ -30,9 +32,10 @@ CATALOG_CONFIRMATIONS=2 # Master secret for per-track content-key derivation. Used by BOTH # /api/uploads/audio (server-side AES-256-GCM encryption before pinning) and -# /api/tracks/:contentHash/key-request (key delivery after a wallet-signed, -# on-chain-verified access check). Must be at least 32 random bytes encoded as -# hex. Never expose this value; rotating it re-keys every track at once. +# /api/tracks/:contentHash/key-request (key delivery after an eip191 or +# product-sr25519-v1 signed, on-chain-verified access check). Must be at least +# 32 random bytes encoded as hex. Never expose this value; rotating it re-keys +# every track at once. # Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" CONTENT_KEY_MASTER_SECRET= diff --git a/services/api/fly.toml b/services/api/fly.toml index 6b061eb..baa8017 100644 --- a/services/api/fly.toml +++ b/services/api/fly.toml @@ -10,6 +10,17 @@ primary_region = "ams" # checks target the owner-guarded factory/directory pair. API_PORT = "8790" NODE_ENV = "production" + # Three frontends reach this API: Netlify, the DotNS web gateway, and the app + # as served inside the Polkadot Product host container, which uses a custom + # scheme. Keep this in step with SIGNAL_ORIGINS in web/fly.signal.toml. + # + # `polkadot:` is a non-special scheme, so `new URL(...).origin` is opaque and + # a browser may send `Origin: null` instead of the literal value below. If a + # host request is still refused, read the actual Origin header off the Fly log + # before widening this list - never add a bare `null`, which would admit every + # sandboxed iframe and file:// page on the web to authenticated upload and + # content-key routes. + API_ORIGINS = "https://muzinga.netlify.app,https://dotify-test01.dev-dot.li,polkadot://app.dotify-test01.dot" PASEO_ASSET_HUB_RPC = "https://eth-rpc-testnet.polkadot.io/" DOTIFY_FACTORY_ADDRESS = "0xbd1a11cfce8b5ef7a37e507bc5109895f8f42a72" DOTIFY_DIRECTORY_ADDRESS = "0xcf1534c6e2b0e43b9436c1e86a076466dc0f2108" @@ -20,7 +31,11 @@ primary_region = "ams" force_https = true auto_stop_machines = "stop" auto_start_machines = true - min_machines_running = 0 + # Keep one machine warm. A stopped machine cold-starts on the first content + # key request, and that request sits directly in front of first sound: a + # measured 8.2s to /health cold against 0.11s warm. Scaling to zero saves + # nothing a listener would trade eight silent seconds for. + min_machines_running = 1 processes = ["app"] [[vm]] diff --git a/services/api/package-lock.json b/services/api/package-lock.json index 9a9f4ca..25f9f9b 100644 --- a/services/api/package-lock.json +++ b/services/api/package-lock.json @@ -12,6 +12,7 @@ "@fastify/multipart": "^10.0.0", "@fastify/rate-limit": "^10.3.0", "@noble/hashes": "1.8.0", + "@scure/sr25519": "1.0.0", "fastify": "^5.8.5", "viem": "^2.52.2", "zod": "^3.23.8" @@ -757,6 +758,49 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@scure/sr25519": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@scure/sr25519/-/sr25519-1.0.0.tgz", + "integrity": "sha512-b+uhK5akMINXZP95F3gJGcb5CMKYxf+q55fwMl0GoBwZDbWolmGNi1FrBSwuaZX5AhqS2byHiAueZgtDNpot2A==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~2.0.0", + "@noble/hashes": "~2.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/sr25519/node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/sr25519/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@types/node": { "version": "22.19.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", diff --git a/services/api/package.json b/services/api/package.json index 65f0f21..4fa05fc 100644 --- a/services/api/package.json +++ b/services/api/package.json @@ -12,13 +12,14 @@ "start": "node dist/index.js", "catalog:reindex": "tsx src/scripts/reindexCatalog.ts", "typecheck": "tsc --noEmit", - "test": "node --import tsx --test src/services/audioV2.test.ts src/services/replayProtection.test.ts src/services/signatures.test.ts src/services/sessionTokens.test.ts src/services/catalog/readModel.test.ts src/routes/keys.test.ts src/routes/uploads.test.ts src/routes/auth.test.ts src/routes/health.test.ts src/routes/catalog.test.ts src/app.test.ts" + "test": "node --import tsx --test src/services/audioV2.test.ts src/services/replayProtection.test.ts src/services/signatures.test.ts src/services/sessionTokens.test.ts src/services/catalog/readModel.test.ts src/routes/keys.test.ts src/routes/uploads.test.ts src/routes/auth.test.ts src/routes/health.test.ts src/routes/catalog.test.ts src/app.test.ts src/cors.test.ts" }, "dependencies": { "@fastify/cors": "^11.2.0", "@fastify/multipart": "^10.0.0", "@fastify/rate-limit": "^10.3.0", "@noble/hashes": "1.8.0", + "@scure/sr25519": "1.0.0", "fastify": "^5.8.5", "viem": "^2.52.2", "zod": "^3.23.8" diff --git a/services/api/src/app.ts b/services/api/src/app.ts index 89044c4..09a1111 100644 --- a/services/api/src/app.ts +++ b/services/api/src/app.ts @@ -39,6 +39,7 @@ export type BuildAppOptions = { // Tests disable logging; production always logs. logging?: boolean; catalog?: CatalogReadModel; + apiOrigins?: string[]; }; export async function buildApp(options: BuildAppOptions = {}): Promise { @@ -48,9 +49,9 @@ export async function buildApp(options: BuildAppOptions = {}): Promise + typeof value === 'string' + ? value + .split(',') + .map(origin => origin.trim()) + .filter(Boolean) + : value, + z.array(z.string().url()).min(1).optional(), +); + const envSchema = z.object({ API_PORT: z.coerce.number().int().min(1).max(65535).default(8790), API_ORIGIN: z.string().url().default('http://localhost:5273'), + API_ORIGINS: optionalOriginList, PASEO_ASSET_HUB_RPC: z.string().url().optional(), DOTIFY_FACTORY_ADDRESS: optionalNonEmptyString, DOTIFY_DIRECTORY_ADDRESS: optionalNonEmptyString, @@ -65,7 +77,10 @@ function parseEnv() { console.error(`[dotify-api] Invalid environment configuration:\n${issues}`); process.exit(1); } - return result.data; + return { + ...result.data, + API_ORIGINS: result.data.API_ORIGINS ?? [result.data.API_ORIGIN], + }; } export const config = parseEnv(); diff --git a/services/api/src/cors.test.ts b/services/api/src/cors.test.ts new file mode 100644 index 0000000..f61ac66 --- /dev/null +++ b/services/api/src/cors.test.ts @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict'; +import { afterEach, describe, it } from 'node:test'; +import type { FastifyInstance } from 'fastify'; +import { buildApp } from './app.js'; + +let app: FastifyInstance | null = null; + +afterEach(async () => { + if (app) await app.close(); + app = null; +}); + +describe('frontend origin boundary', () => { + it('allows each configured Dotify frontend and rejects unrelated origins', async () => { + app = await buildApp({ + logging: false, + apiOrigins: ['https://muzinga.netlify.app', 'https://dotify-test01.dev-dot.li'], + }); + const server = app; + + for (const origin of ['https://muzinga.netlify.app', 'https://dotify-test01.dev-dot.li']) { + const response = await server.inject({ + method: 'GET', + url: '/health', + headers: { origin }, + }); + assert.equal(response.headers['access-control-allow-origin'], origin); + } + + const unrelated = await server.inject({ + method: 'GET', + url: '/health', + headers: { origin: 'https://unrelated.example' }, + }); + assert.equal(unrelated.headers['access-control-allow-origin'], undefined); + }); + + it('allows the Product host container origin, which uses a custom scheme', async () => { + // Inside the Product host the app is served from polkadot://, not the DotNS + // web gateway. Without this the container gets rooms but no content keys. + const hostOrigin = 'polkadot://app.dotify-test01.dot'; + app = await buildApp({ logging: false, apiOrigins: [hostOrigin] }); + + const response = await app.inject({ + method: 'GET', + url: '/health', + headers: { origin: hostOrigin }, + }); + + assert.equal(response.headers['access-control-allow-origin'], hostOrigin); + }); + + it('refuses a null origin even when a custom-scheme origin is allowed', async () => { + // `polkadot:` is a non-special scheme, so browsers may send `Origin: null`. + // Answering that would admit every sandboxed iframe and file:// page to the + // authenticated upload and content-key routes, so it must stay refused + // until the real header is observed and allowlisted deliberately. + app = await buildApp({ logging: false, apiOrigins: ['polkadot://app.dotify-test01.dot'] }); + + const response = await app.inject({ + method: 'GET', + url: '/health', + headers: { origin: 'null' }, + }); + + assert.equal(response.headers['access-control-allow-origin'], undefined); + }); +}); diff --git a/services/api/src/routes/auth.test.ts b/services/api/src/routes/auth.test.ts index e3490d8..8bf4c13 100644 --- a/services/api/src/routes/auth.test.ts +++ b/services/api/src/routes/auth.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import { afterEach, describe, it } from 'node:test'; import Fastify, { type FastifyInstance } from 'fastify'; import { createAuthRoutes, type AuthRouteDeps } from './auth.js'; +import { PRODUCT_SR25519_SIGNATURE_SCHEME, type SignInRequest } from '../services/signatures.js'; const ADDRESS = '0x1111111111111111111111111111111111111111'; @@ -80,6 +81,34 @@ describe('POST /api/auth/session', () => { assert.equal(body.address, ADDRESS); }); + it('passes Product sr25519 proof fields to sign-in verification', async () => { + let verifiedRequest: SignInRequest | null = null; + const server = await buildApp({ + verifySignInRequest: async request => { + verifiedRequest = request; + return { valid: true }; + } + }); + const productPublicKey = `0x${'22'.repeat(32)}`; + const signature = `0x${'33'.repeat(64)}`; + const response = await server.inject({ + method: 'POST', + url: '/api/auth/session', + payload: sessionBody({ + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + productPublicKey, + signature + }) + }); + + assert.equal(response.statusCode, 200); + const productRequest = verifiedRequest as Extract | null; + assert.ok(productRequest); + assert.equal(productRequest.signatureScheme, PRODUCT_SR25519_SIGNATURE_SCHEME); + assert.equal(productRequest.productPublicKey, productPublicKey); + assert.equal(productRequest.signature, signature); + }); + it('rejects an invalid signature with 401 and the verification code', async () => { const server = await buildApp({ verifySignInRequest: async () => ({ valid: false, code: 'SIGNATURE_INVALID', reason: 'bad signature' }) @@ -106,6 +135,30 @@ describe('POST /api/auth/session', () => { assert.equal(response.statusCode, 400); }); + it('rejects unknown sign-in signature schemes before verification or token issuance', async () => { + let verificationCalled = false; + let issuanceCalled = false; + const server = await buildApp({ + verifySignInRequest: async () => { + verificationCalled = true; + return { valid: true }; + }, + issueSessionToken: () => { + issuanceCalled = true; + return { ok: true, token: 'payload.signature', expiresAt: new Date(Date.now() + 1000).toISOString() }; + } + }); + const response = await server.inject({ + method: 'POST', + url: '/api/auth/session', + payload: sessionBody({ signatureScheme: 'product-unknown-v1' }) + }); + + assert.equal(response.statusCode, 400); + assert.equal(verificationCalled, false); + assert.equal(issuanceCalled, false); + }); + it('rejects a different-chain sign-in before verification or token issuance', async () => { let verificationCalled = false; let issuanceCalled = false; diff --git a/services/api/src/routes/auth.ts b/services/api/src/routes/auth.ts index 90d0845..9089c1e 100644 --- a/services/api/src/routes/auth.ts +++ b/services/api/src/routes/auth.ts @@ -8,6 +8,8 @@ import { z } from 'zod'; import { config } from '../config.js'; import { checkDotifyChainId } from '../services/chainDomain.js'; import { + EIP191_SIGNATURE_SCHEME, + PRODUCT_SR25519_SIGNATURE_SCHEME, createWalletNonceChallenge, verifySignInRequest as defaultVerifySignInRequest, type SignInRequest, @@ -25,14 +27,28 @@ const nonceRequestSchema = z.object({ chainId: z.number().int().positive().optional() }); -const sessionRequestSchema = z.object({ +const sessionBaseRequestSchema = z.object({ address: z.string().regex(/^0x[0-9a-fA-F]{40}$/, 'Invalid EVM address'), - signature: z.string().regex(/^0x[0-9a-fA-F]+$/, 'Invalid signature'), nonce: z.string().min(16, 'Nonce is required'), chainId: z.number().int().positive(), expiresAt: z.string().datetime() }); +const eip191SessionRequestSchema = sessionBaseRequestSchema.extend({ + signatureScheme: z.literal(EIP191_SIGNATURE_SCHEME).optional(), + signature: z.string().regex(/^0x[0-9a-fA-F]+$/, 'Invalid signature') +}); + +// 128 hex = bare 64-byte sr25519; 130 hex = MultiSignature-tagged 65-byte +// value. The tag itself is validated in verifySignInRequest, not here. +const productSr25519SessionRequestSchema = sessionBaseRequestSchema.extend({ + signatureScheme: z.literal(PRODUCT_SR25519_SIGNATURE_SCHEME), + signature: z.string().regex(/^0x([0-9a-fA-F]{128}|[0-9a-fA-F]{130})$/, 'Invalid Product sr25519 signature'), + productPublicKey: z.string().regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid Product account public key') +}); + +const sessionRequestSchema = z.union([productSr25519SessionRequestSchema, eip191SessionRequestSchema]); + const logoutRequestSchema = z.object({ sessionToken: z.string().min(16, 'Session token is required') }); @@ -103,13 +119,27 @@ export function createAuthRoutes(deps: AuthRouteDeps = defaultDeps) { return reply.status(400).send({ error: domain.reason, code: domain.code }); } - const verification = await deps.verifySignInRequest({ - requester: parsed.data.address, - chainId: parsed.data.chainId, - nonce: parsed.data.nonce, - expiresAt: parsed.data.expiresAt, - signature: parsed.data.signature - }); + const signInRequest: SignInRequest = + parsed.data.signatureScheme === PRODUCT_SR25519_SIGNATURE_SCHEME + ? { + requester: parsed.data.address, + chainId: parsed.data.chainId, + nonce: parsed.data.nonce, + expiresAt: parsed.data.expiresAt, + signature: parsed.data.signature, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + productPublicKey: parsed.data.productPublicKey + } + : { + requester: parsed.data.address, + chainId: parsed.data.chainId, + nonce: parsed.data.nonce, + expiresAt: parsed.data.expiresAt, + signature: parsed.data.signature, + signatureScheme: EIP191_SIGNATURE_SCHEME + }; + + const verification = await deps.verifySignInRequest(signInRequest); if (!verification.valid) { return reply.status(401).send({ error: verification.reason, code: verification.code }); } diff --git a/services/api/src/routes/keys.test.ts b/services/api/src/routes/keys.test.ts index 210abf8..95c8fc4 100644 --- a/services/api/src/routes/keys.test.ts +++ b/services/api/src/routes/keys.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import { afterEach, describe, it } from 'node:test'; import Fastify, { type FastifyInstance } from 'fastify'; import { createKeyRoutes, type KeyRouteDeps } from './keys.js'; +import { PRODUCT_SR25519_SIGNATURE_SCHEME, type KeySignatureRequest } from '../services/signatures.js'; const CONTENT_HASH = `0x${'ab'.repeat(32)}`; const REQUESTER = '0x1111111111111111111111111111111111111111'; @@ -85,6 +86,105 @@ describe('POST /api/tracks/:contentHash/key-request', () => { assert.equal(response.json().code, 'SIGNATURE_INVALID'); }); + it('passes Product sr25519 proof fields to signature verification', async () => { + let verifiedRequest: KeySignatureRequest | null = null; + const server = await buildApp({ + verifySignedRequest: async request => { + verifiedRequest = request; + return { valid: true }; + } + }); + const productPublicKey = `0x${'22'.repeat(32)}`; + const signature = `0x${'33'.repeat(64)}`; + const response = await server.inject({ + method: 'POST', + url: `/api/tracks/${CONTENT_HASH}/key-request`, + payload: baseBody({ + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + productPublicKey, + signature + }) + }); + + assert.equal(response.statusCode, 200); + const productRequest = verifiedRequest as Extract | null; + assert.ok(productRequest); + assert.equal(productRequest.signatureScheme, PRODUCT_SR25519_SIGNATURE_SCHEME); + assert.equal(productRequest.productPublicKey, productPublicKey); + assert.equal(productRequest.signature, signature); + }); + + it('rejects unknown signature schemes before verification or access checks', async () => { + let verificationCalled = false; + let accessChecked = false; + const server = await buildApp({ + verifySignedRequest: async () => { + verificationCalled = true; + return { valid: true }; + }, + checkTrackAccess: async () => { + accessChecked = true; + return { allowed: true, runtime: RUNTIME }; + } + }); + const response = await server.inject({ + method: 'POST', + url: `/api/tracks/${CONTENT_HASH}/key-request`, + payload: baseBody({ signatureScheme: 'product-unknown-v1' }) + }); + + assert.equal(response.statusCode, 400); + assert.equal(verificationCalled, false); + assert.equal(accessChecked, false); + }); + + it('forwards a MultiSignature-tagged Product signature to verification', async () => { + // 65-byte tagged signatures are a legitimate Substrate signRaw shape; the + // route must not reject them at the schema before the verifier can check + // the tag. + let verifiedRequest: KeySignatureRequest | null = null; + const server = await buildApp({ + verifySignedRequest: async request => { + verifiedRequest = request; + return { valid: true }; + } + }); + const signature = `0x01${'33'.repeat(64)}`; + const response = await server.inject({ + method: 'POST', + url: `/api/tracks/${CONTENT_HASH}/key-request`, + payload: baseBody({ + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + productPublicKey: `0x${'22'.repeat(32)}`, + signature + }) + }); + + assert.equal(response.statusCode, 200); + assert.equal((verifiedRequest as KeySignatureRequest | null)?.signature, signature); + }); + + it('requires Product public key for Product sr25519 requests', async () => { + let verificationCalled = false; + const server = await buildApp({ + verifySignedRequest: async () => { + verificationCalled = true; + return { valid: true }; + } + }); + const response = await server.inject({ + method: 'POST', + url: `/api/tracks/${CONTENT_HASH}/key-request`, + payload: baseBody({ + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature: `0x${'33'.repeat(64)}` + }) + }); + + assert.equal(response.statusCode, 400); + assert.equal(verificationCalled, false); + }); + it('answers a denied individual listener with an unlock CTA, never a key or a preview mode', async () => { const server = await buildApp({ checkTrackAccess: async () => ({ allowed: false, code: 'LISTENER_ACCESS_REQUIRED', reason: 'no access' }) diff --git a/services/api/src/routes/keys.ts b/services/api/src/routes/keys.ts index a4e56e4..79a5677 100644 --- a/services/api/src/routes/keys.ts +++ b/services/api/src/routes/keys.ts @@ -21,26 +21,46 @@ import { type TrackAccessResult } from '../services/chainAccess.js'; import { deriveContentKey as defaultDeriveContentKey, type ContentKeyResult } from '../services/keyVault.js'; -import { verifySignedRequest as defaultVerifySignedRequest, type KeySignatureRequest, type SignatureVerification } from '../services/signatures.js'; +import { + EIP191_SIGNATURE_SCHEME, + PRODUCT_SR25519_SIGNATURE_SCHEME, + verifySignedRequest as defaultVerifySignedRequest, + type KeySignatureRequest, + type SignatureVerification +} from '../services/signatures.js'; import { verifySessionToken as defaultVerifySessionToken, type SessionVerification } from '../services/sessionTokens.js'; const paramsSchema = z.object({ contentHash: z.string().regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid content hash') }); -const signedBodySchema = z.object({ +const signedBaseBodySchema = z.object({ requester: z.string().regex(/^0x[0-9a-fA-F]{40}$/, 'Invalid EVM address'), - signature: z.string().regex(/^0x[0-9a-fA-F]+$/, 'Invalid signature'), nonce: z.string().min(16, 'Nonce is required'), chainId: z.number().int().positive(), expiresAt: z.string().datetime() }); // 'room_listener' is intentionally not accepted; room listeners never get keys. -const keyRequestBodySchema = signedBodySchema.extend({ +const keyRequestPurposeSchema = z.object({ purpose: z.enum(['individual', 'room_host']) }); +const eip191KeyRequestBodySchema = signedBaseBodySchema.merge(keyRequestPurposeSchema).extend({ + signatureScheme: z.literal(EIP191_SIGNATURE_SCHEME).optional(), + signature: z.string().regex(/^0x[0-9a-fA-F]+$/, 'Invalid signature') +}); + +// 128 hex = bare 64-byte sr25519; 130 hex = MultiSignature-tagged 65-byte +// value. The tag itself is validated in verifySignedRequest, not here. +const productSr25519KeyRequestBodySchema = signedBaseBodySchema.merge(keyRequestPurposeSchema).extend({ + signatureScheme: z.literal(PRODUCT_SR25519_SIGNATURE_SCHEME), + signature: z.string().regex(/^0x([0-9a-fA-F]{128}|[0-9a-fA-F]{130})$/, 'Invalid Product sr25519 signature'), + productPublicKey: z.string().regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid Product account public key') +}); + +const keyRequestBodySchema = z.union([productSr25519KeyRequestBodySchema, eip191KeyRequestBodySchema]); + // Session path (ticket 24 P2): after the one-per-session sign-in, a key // request carries the bearer token instead of a fresh wallet signature. The // on-chain access check still runs on every request. @@ -145,16 +165,33 @@ export function createKeyRoutes(deps: KeyRouteDeps = defaultDeps) { return reply.status(401).send({ error: domain.reason, code: domain.code }); } - const signature = await deps.verifySignedRequest({ - action: 'REQUEST_CONTENT_KEY', - purpose: body.data.purpose, - contentHash: params.data.contentHash, - requester: body.data.requester, - chainId: body.data.chainId, - nonce: body.data.nonce, - expiresAt: body.data.expiresAt, - signature: body.data.signature - }); + const signatureRequest: KeySignatureRequest = + body.data.signatureScheme === PRODUCT_SR25519_SIGNATURE_SCHEME + ? { + action: 'REQUEST_CONTENT_KEY', + purpose: body.data.purpose, + contentHash: params.data.contentHash, + requester: body.data.requester, + chainId: body.data.chainId, + nonce: body.data.nonce, + expiresAt: body.data.expiresAt, + signature: body.data.signature, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + productPublicKey: body.data.productPublicKey + } + : { + action: 'REQUEST_CONTENT_KEY', + purpose: body.data.purpose, + contentHash: params.data.contentHash, + requester: body.data.requester, + chainId: body.data.chainId, + nonce: body.data.nonce, + expiresAt: body.data.expiresAt, + signature: body.data.signature, + signatureScheme: EIP191_SIGNATURE_SCHEME + }; + + const signature = await deps.verifySignedRequest(signatureRequest); if (!signature.valid) { return reply.status(401).send({ error: signature.reason, code: signature.code }); diff --git a/services/api/src/services/signatures.test.ts b/services/api/src/services/signatures.test.ts index 373066b..3c8943f 100644 --- a/services/api/src/services/signatures.test.ts +++ b/services/api/src/services/signatures.test.ts @@ -1,11 +1,14 @@ import assert from 'node:assert/strict'; import { beforeEach, describe, it } from 'node:test'; +import { getPublicKey, secretFromSeed, sign as signSr25519 } from '@scure/sr25519'; import { privateKeyToAccount } from 'viem/accounts'; import { resetNonceStore } from './replayProtection.js'; import { + PRODUCT_SR25519_SIGNATURE_SCHEME, buildSignedRequestMessage, buildSignInMessage, createWalletNonceChallenge, + deriveProductAccountH160, verifySignInRequest, verifySignedRequest, type SignInPayload, @@ -17,6 +20,15 @@ const signer = privateKeyToAccount('0xac0974bec39a37e36980911eda47a06fcd4ee8d3a8 const CONTENT_HASH = `0x${'ab'.repeat(32)}` as const; const CHAIN_ID = 420420417; +const productSecretKey = secretFromSeed(new Uint8Array(32).fill(7)); +const productPublicKey = getPublicKey(productSecretKey); +const productPublicKeyHex = `0x${bytesToHex(productPublicKey)}` as const; + +function bytesToHex(bytes: Uint8Array): string { + return Array.from(bytes) + .map(byte => byte.toString(16).padStart(2, '0')) + .join(''); +} async function signedPayload(overrides: Partial = {}) { const challenge = createWalletNonceChallenge({ address: signer.address, chainId: CHAIN_ID }); @@ -34,6 +46,40 @@ async function signedPayload(overrides: Partial = {}) { return { payload, signature }; } +// The Host may sign the canonical message verbatim or inside the conventional +// Substrate `` envelope, and may return the signature bare or with a +// MultiSignature tag. Tests cover every shape the verifier accepts. +type ProductEnvelope = 'raw' | 'bytes-wrapped'; +type ProductSignatureShape = 'bare' | 'multisignature'; + +function encodeProductPayload(message: string, envelope: ProductEnvelope): Uint8Array { + return new TextEncoder().encode(envelope === 'bytes-wrapped' ? `${message}` : message); +} + +function encodeProductSignature(raw: Uint8Array, shape: ProductSignatureShape): string { + return shape === 'multisignature' ? `0x01${bytesToHex(raw)}` : `0x${bytesToHex(raw)}`; +} + +async function productSignedPayload( + overrides: Partial = {}, + options: { envelope?: ProductEnvelope; shape?: ProductSignatureShape } = {}, +) { + const requester = overrides.requester ?? deriveProductAccountH160(productPublicKey); + const challenge = createWalletNonceChallenge({ address: requester, chainId: CHAIN_ID }); + const payload: SignedRequestPayload = { + action: 'REQUEST_CONTENT_KEY', + purpose: 'individual', + contentHash: CONTENT_HASH, + requester, + chainId: CHAIN_ID, + nonce: challenge.nonce, + expiresAt: challenge.expiresAt, + ...overrides + }; + const raw = signSr25519(productSecretKey, encodeProductPayload(buildSignedRequestMessage(payload), options.envelope ?? 'raw')); + return { payload, signature: encodeProductSignature(raw, options.shape ?? 'bare'), productPublicKey: productPublicKeyHex }; +} + describe('verifySignedRequest', () => { beforeEach(() => { resetNonceStore(); @@ -45,6 +91,17 @@ describe('verifySignedRequest', () => { assert.equal(result.valid, true); }); + it('accepts a Product sr25519 request bound to the derived H160 requester', async () => { + const { payload, signature, productPublicKey } = await productSignedPayload(); + const result = await verifySignedRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey + }); + assert.equal(result.valid, true); + }); + it('rejects a replayed nonce', async () => { const { payload, signature } = await signedPayload(); const first = await verifySignedRequest({ ...payload, signature }); @@ -63,6 +120,118 @@ describe('verifySignedRequest', () => { assert.equal(!result.valid && result.code, 'SIGNATURE_INVALID'); }); + it('accepts a Product signature made over the envelope', async () => { + const { payload, signature, productPublicKey } = await productSignedPayload({}, { envelope: 'bytes-wrapped' }); + const result = await verifySignedRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey + }); + assert.equal(result.valid, true); + }); + + it('accepts a MultiSignature-tagged Product signature in either envelope', async () => { + for (const envelope of ['raw', 'bytes-wrapped'] as const) { + resetNonceStore(); + const { payload, signature, productPublicKey } = await productSignedPayload({}, { envelope, shape: 'multisignature' }); + assert.equal(signature.length, 2 + 130, 'expected a 65-byte tagged signature'); + const result = await verifySignedRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey + }); + assert.equal(result.valid, true, `envelope ${envelope} should verify`); + } + }); + + it('rejects a 65-byte signature whose MultiSignature tag is not sr25519', async () => { + const { payload, signature, productPublicKey } = await productSignedPayload(); + const ed25519Tagged = `0x00${signature.slice(2)}`; + const result = await verifySignedRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature: ed25519Tagged, + productPublicKey + }); + assert.equal(result.valid, false); + assert.equal(!result.valid && result.code, 'PRODUCT_SIGNATURE_INVALID'); + }); + + it('rejects an EVM-derived account id claiming an arbitrary requester H160', async () => { + // 20-byte H160 padded with 0xee derives straight back to that H160, so + // without this guard a caller could name any paying EVM listener. + const victim = '742d35cc6634c0532925a3b844bc9e7595f0beb0'; + const forgedKey = `0x${victim}${'ee'.repeat(12)}`; + const { payload, signature } = await productSignedPayload({ requester: `0x${victim}` }); + + const result = await verifySignedRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey: forgedKey + }); + + assert.equal(result.valid, false); + assert.equal(!result.valid && result.code, 'PRODUCT_KEY_NOT_NATIVE'); + }); + + it('reports an envelope/account failure distinctly from a malformed request', async () => { + const { payload, productPublicKey } = await productSignedPayload(); + const wrongKey = secretFromSeed(new Uint8Array(32).fill(9)); + const signature = `0x${bytesToHex(signSr25519(wrongKey, new TextEncoder().encode(buildSignedRequestMessage(payload))))}`; + + const result = await verifySignedRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey + }); + + assert.equal(result.valid, false); + assert.equal(!result.valid && result.code, 'PRODUCT_SIGNATURE_REJECTED'); + }); + + it('rejects a Product signature when the payload changes', async () => { + const { payload, signature, productPublicKey } = await productSignedPayload(); + const result = await verifySignedRequest({ + ...payload, + contentHash: `0x${'cd'.repeat(32)}`, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey + }); + assert.equal(result.valid, false); + assert.equal(!result.valid && result.code, 'PRODUCT_SIGNATURE_REJECTED'); + }); + + it('rejects a Product public key that does not derive to the requester H160', async () => { + const { payload, signature, productPublicKey } = await productSignedPayload({ + requester: '0x1111111111111111111111111111111111111111' + }); + const result = await verifySignedRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey + }); + assert.equal(result.valid, false); + assert.equal(!result.valid && result.code, 'PRODUCT_ADDRESS_MISMATCH'); + }); + + it('rejects malformed Product proof bytes before nonce consumption', async () => { + const { payload, signature } = await productSignedPayload(); + const result = await verifySignedRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey: '0x1234' + }); + assert.equal(result.valid, false); + assert.equal(!result.valid && result.code, 'PRODUCT_SIGNATURE_INVALID'); + }); + it('rejects a signature over a different purpose', async () => { const { payload, signature } = await signedPayload({ purpose: 'individual' }); const result = await verifySignedRequest({ ...payload, purpose: 'room_host', signature }); @@ -124,4 +293,34 @@ describe('verifySignedRequest', () => { assert.equal(result.valid, false); assert.equal(!result.valid && result.code, 'CHAIN_ID_MISMATCH'); }); + + it('accepts a Product sr25519 sign-in bound to the derived H160 requester', async () => { + const requester = deriveProductAccountH160(productPublicKey); + const challenge = createWalletNonceChallenge({ address: requester, chainId: CHAIN_ID }); + const payload: SignInPayload = { + requester, + chainId: CHAIN_ID, + nonce: challenge.nonce, + expiresAt: challenge.expiresAt + }; + const signature = `0x${bytesToHex(signSr25519(productSecretKey, new TextEncoder().encode(buildSignInMessage(payload))))}`; + const result = await verifySignInRequest({ + ...payload, + signatureScheme: PRODUCT_SR25519_SIGNATURE_SCHEME, + signature, + productPublicKey: productPublicKeyHex + }); + + assert.equal(result.valid, true); + }); + + it('matches the Product SDK H160 derivation vector for a native Substrate public key', () => { + const alicePublicKey = new Uint8Array([ + 0xd4, 0x35, 0x93, 0xc7, 0x15, 0xfd, 0xd3, 0x1c, 0x61, 0x14, 0x1a, 0xbd, 0x04, 0xa9, 0x9f, + 0xd6, 0x82, 0x2c, 0x85, 0x58, 0x85, 0x4c, 0xcd, 0xe3, 0x9a, 0x56, 0x84, 0xe7, 0xa5, 0x6d, + 0xa2, 0x7d + ]); + + assert.equal(deriveProductAccountH160(alicePublicKey), '0x9621dde636de098b43efb0fa9b61facfe328f99d'); + }); }); diff --git a/services/api/src/services/signatures.ts b/services/api/src/services/signatures.ts index 34c9105..a34901b 100644 --- a/services/api/src/services/signatures.ts +++ b/services/api/src/services/signatures.ts @@ -1,4 +1,4 @@ -// Wallet-signed request verification (EIP-191 personal_sign). +// Wallet-signed request verification. // // The signed payload is a structured, domain-bound text message that binds: // app, action, purpose, content hash, requester address, chain ID, nonce, @@ -6,11 +6,24 @@ // (web/src/services/keyService.ts); any drift between the two breaks // verification, which fails closed. // -// Security boundary: EIP-191 is used instead of EIP-712 for the first -// production spine because it is supported uniformly across the wallets we -// target. The message is structured and domain-bound, so it cannot be -// replayed against another app, chain, purpose, or track. +// Security boundary: standalone wallets use EIP-191 instead of EIP-712 for +// the first production spine because it is supported uniformly across the +// wallets we target. Product-host requests sign the same canonical message +// bytes with the app-scoped Product account and must prove that account's +// public key derives to the requester H160 used by runtime access checks. +// The message is structured and domain-bound, so it cannot be replayed +// against another app, chain, purpose, or track. +// +// The Product Host `signRaw` wire format is not pinned by the SDK: the +// response signature is untagged, and a Substrate host may sign a raw payload +// verbatim or inside the conventional `` envelope. Rather than guess +// one shape and fail every request on a wrong guess, verification accepts the +// bounded set of shapes below. Each still carries the identical domain-bound +// message, so tolerance costs no security - it only removes an unverifiable +// assumption. See docs/explanation/product-devnet-architecture.md. +import { keccak_256 } from '@noble/hashes/sha3'; +import { verify as verifySr25519Signature } from '@scure/sr25519'; import { verifyMessage } from 'viem'; import { config } from '../config.js'; import { checkDotifyChainId } from './chainDomain.js'; @@ -20,6 +33,16 @@ import { consumeNonce, issueNonce } from './replayProtection.js'; // content keys, they only receive the host's ephemeral WebRTC stream. export type KeyRequestPurpose = 'individual' | 'room_host'; export type SignedAction = 'REQUEST_CONTENT_KEY' | 'SIGN_IN'; +export const EIP191_SIGNATURE_SCHEME = 'eip191'; +export const PRODUCT_SR25519_SIGNATURE_SCHEME = 'product-sr25519-v1'; +export type SignatureScheme = typeof EIP191_SIGNATURE_SCHEME | typeof PRODUCT_SR25519_SIGNATURE_SCHEME; + +const PRODUCT_PUBLIC_KEY_BYTES = 32; +const PRODUCT_SR25519_SIGNATURE_BYTES = 64; +const H160_BYTES = 20; +const EVM_DERIVED_MARKER = 0xee; +// Substrate MultiSignature enum tag for sr25519 (0 = ed25519, 1 = sr25519). +const MULTISIGNATURE_SR25519_TAG = 0x01; export type NonceChallengeRequest = { address: string; @@ -42,10 +65,19 @@ export type SignedRequestPayload = { expiresAt: string; }; -export type KeySignatureRequest = SignedRequestPayload & { +export type Eip191SignatureFields = { + signatureScheme?: typeof EIP191_SIGNATURE_SCHEME; + signature: string; +}; + +export type ProductSr25519SignatureFields = { + signatureScheme: typeof PRODUCT_SR25519_SIGNATURE_SCHEME; signature: string; + productPublicKey: string; }; +export type SignatureFields = Eip191SignatureFields | ProductSr25519SignatureFields; +export type KeySignatureRequest = SignedRequestPayload & SignatureFields; export type SignatureVerification = { valid: true } | { valid: false; code: string; reason: string }; /** @@ -73,9 +105,7 @@ export type SignInPayload = { expiresAt: string; }; -export type SignInRequest = SignInPayload & { - signature: string; -}; +export type SignInRequest = SignInPayload & SignatureFields; /** * Canonical EIP-191 message for the one-per-session Dotify sign-in @@ -96,6 +126,196 @@ export function buildSignInMessage(payload: SignInPayload): string { ].join('\n'); } +function bytesToHex(bytes: Uint8Array): string { + return Array.from(bytes) + .map(byte => byte.toString(16).padStart(2, '0')) + .join(''); +} + +function fixedHexToBytes(hex: string, expectedBytes: number): Uint8Array { + const clean = hex.startsWith('0x') ? hex.slice(2) : hex; + if (clean.length !== expectedBytes * 2 || !/^[0-9a-fA-F]+$/.test(clean)) { + throw new Error(`Expected ${expectedBytes} bytes of hex`); + } + return new Uint8Array(Buffer.from(clean, 'hex')); +} + +function hexToBytes(hex: string): Uint8Array { + const clean = hex.startsWith('0x') ? hex.slice(2) : hex; + if (clean.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(clean)) { + throw new Error('Expected an even-length hex string'); + } + return new Uint8Array(Buffer.from(clean, 'hex')); +} + +/** + * The Host `signRaw` response carries an opaque signature with no scheme tag + * (truapi `HostSignPayloadResponse.signature`). Accept the two shapes a + * Substrate signer can return for sr25519 - a bare 64-byte signature, or a + * 65-byte MultiSignature-tagged value - and reject everything else. The tag is + * checked, not skipped, so an ed25519 or ECDSA signature still fails closed. + */ +function parseProductSignatureBytes(hex: string): Uint8Array { + const bytes = hexToBytes(hex); + if (bytes.length === PRODUCT_SR25519_SIGNATURE_BYTES) { + return bytes; + } + if (bytes.length === PRODUCT_SR25519_SIGNATURE_BYTES + 1 && bytes[0] === MULTISIGNATURE_SR25519_TAG) { + return bytes.slice(1); + } + throw new Error('Unsupported Product signature length'); +} + +/** + * A Substrate host may sign a raw payload either verbatim or wrapped in the + * conventional `...` envelope. Both variants carry the same + * canonical Dotify message, which is already bound to app, action, purpose, + * content hash, requester, chain, nonce, and expiry - so accepting either + * envelope adds no replay surface, it only removes a guess about host + * behaviour. Nothing outside these two shapes is accepted. + */ +function productSignedMessageVariants(message: string): Uint8Array[] { + const encoder = new TextEncoder(); + return [encoder.encode(message), encoder.encode(`${message}`)]; +} + +/** + * True when the 32-byte account id is a pallet-revive EVM-derived account + * (a 20-byte H160 padded with 0xee). Such an account is not a native + * sr25519 keypair, so it can never legitimately produce a Product signature. + */ +function isEvmDerivedAccountId(publicKey: Uint8Array): boolean { + return publicKey.slice(H160_BYTES).every(byte => byte === EVM_DERIVED_MARKER); +} + +/** + * Match Product SDK / pallet-revive AccountId32 -> H160 derivation: + * native Substrate accounts use keccak256(publicKey), last 20 bytes; accounts + * already derived from H160 strip the trailing 0xee padding. + */ +export function deriveProductAccountH160(publicKey: Uint8Array): `0x${string}` { + if (publicKey.length !== PRODUCT_PUBLIC_KEY_BYTES) { + throw new Error(`Expected ${PRODUCT_PUBLIC_KEY_BYTES}-byte Product public key`); + } + + const addressBytes = isEvmDerivedAccountId(publicKey) + ? publicKey.slice(0, H160_BYTES) + : keccak_256(publicKey).slice(PRODUCT_PUBLIC_KEY_BYTES - H160_BYTES); + return `0x${bytesToHex(addressBytes)}`; +} + +function verifyProductSr25519Payload(args: { + requester: string; + message: string; + signature: string; + productPublicKey: string | undefined; +}): SignatureVerification { + if (!args.productPublicKey) { + return { + valid: false, + code: 'PRODUCT_PUBLIC_KEY_REQUIRED', + reason: 'Product signed requests must include the Product account public key.' + }; + } + + let publicKey: Uint8Array; + let signature: Uint8Array; + try { + publicKey = fixedHexToBytes(args.productPublicKey, PRODUCT_PUBLIC_KEY_BYTES); + signature = parseProductSignatureBytes(args.signature); + } catch { + return { + valid: false, + code: 'PRODUCT_SIGNATURE_INVALID', + reason: 'Product signature payload is malformed.' + }; + } + + // An EVM-derived account id would let a caller name any H160 as the + // requester and lean entirely on the curve check to stop the takeover. + // A real Product account is a native AccountId32, so reject that shape + // before deriving anything from it. + if (isEvmDerivedAccountId(publicKey)) { + return { + valid: false, + code: 'PRODUCT_KEY_NOT_NATIVE', + reason: 'Product signed requests require a native Product account key, not an EVM-derived account id.' + }; + } + + const derivedRequester = deriveProductAccountH160(publicKey); + if (derivedRequester.toLowerCase() !== args.requester.toLowerCase()) { + return { + valid: false, + code: 'PRODUCT_ADDRESS_MISMATCH', + reason: 'Product account public key does not derive to the requester H160 address.' + }; + } + + const signatureValid = productSignedMessageVariants(args.message).some(payload => { + try { + return verifySr25519Signature(payload, signature, publicKey); + } catch { + return false; + } + }); + + if (!signatureValid) { + // Deliberately distinct from SIGNATURE_INVALID: the key parsed and derives + // to the requester, so this is a signing-envelope or wrong-account problem, + // not a malformed request. Operators need those apart in Fly logs. + return { + valid: false, + code: 'PRODUCT_SIGNATURE_REJECTED', + reason: 'Product host signature did not verify against the Dotify request payload in any supported signing envelope.' + }; + } + + return { valid: true }; +} + +async function verifySignatureEnvelope( + request: SignatureFields & { requester: string }, + message: string, + invalidSignatureReason: string +): Promise { + const signatureScheme = request.signatureScheme ?? EIP191_SIGNATURE_SCHEME; + + if (signatureScheme === EIP191_SIGNATURE_SCHEME) { + let signatureValid = false; + try { + signatureValid = await verifyMessage({ + address: request.requester as `0x${string}`, + message, + signature: request.signature as `0x${string}` + }); + } catch { + signatureValid = false; + } + + if (!signatureValid) { + return { valid: false, code: 'SIGNATURE_INVALID', reason: invalidSignatureReason }; + } + + return { valid: true }; + } + + if (signatureScheme === PRODUCT_SR25519_SIGNATURE_SCHEME) { + return verifyProductSr25519Payload({ + requester: request.requester, + message, + signature: request.signature, + productPublicKey: 'productPublicKey' in request ? request.productPublicKey : undefined + }); + } + + return { + valid: false, + code: 'SIGNATURE_SCHEME_UNSUPPORTED', + reason: 'Signature scheme is not supported for Dotify key delivery.' + }; +} + /** * Verify a sign-in request: expiry, signature, then nonce consumption - * the same fail-closed order as verifySignedRequest. @@ -111,19 +331,13 @@ export async function verifySignInRequest(request: SignInRequest): Promise + + =16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-pb/node_modules/multiformats": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", + "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", + "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.2.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@novasamatech/host-api": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/@novasamatech/host-api/-/host-api-0.6.18.tgz", + "integrity": "sha512-5U5tYRbY/v49BqHH+iHPIP6OH7KJjXUMypaXSXtZK/J3IsQAqDLBlu/LqpDrNxHqEK1sKY5EyYla840mqmkwPg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@novasamatech/scale": "0.6.18", + "@polkadot-api/utils": "^0.2.0", + "nanoevents": "9.1.0", + "nanoid": "5.1.7", + "neverthrow": "^8.2.0", + "scale-ts": "1.6.1" + } + }, + "node_modules/@novasamatech/host-api/node_modules/@novasamatech/scale": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/@novasamatech/scale/-/scale-0.6.18.tgz", + "integrity": "sha512-xRvBrzJSvCseQ62zLReS3EtiQjuiTY+c+yOyx6If9dBRzX5FL52OazFLsdSaq3wOe8441TPXL1vediotYFZlRg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@polkadot-api/utils": "^0.2.0", + "scale-ts": "1.6.1" + } + }, + "node_modules/@novasamatech/host-api/node_modules/nanoid": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.7.tgz", + "integrity": "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@parity/product-sdk": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk/-/product-sdk-0.19.1.tgz", + "integrity": "sha512-vZnXh5FUc/KSXBeMYd5v5ZTit9f4PLlXkXD7rvvESdV7L2djTlDij4uOKkr1oWg9NrRmcH4cvkHD1wAbs2Zqzg==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-address": "0.1.1", + "@parity/product-sdk-chain-client": "0.9.1", + "@parity/product-sdk-cloud-storage": "0.8.1", + "@parity/product-sdk-contracts": "0.9.2", + "@parity/product-sdk-crypto": "0.1.1", + "@parity/product-sdk-errors": "0.2.0", + "@parity/product-sdk-host": "0.14.1", + "@parity/product-sdk-keys": "0.3.16", + "@parity/product-sdk-local-storage": "0.3.2", + "@parity/product-sdk-logger": "0.1.1", + "@parity/product-sdk-signer": "0.11.1", + "@parity/product-sdk-tx": "0.3.2", + "@parity/result": "0.2.0", + "polkadot-api": "^2.1.6" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@parity/product-sdk-address": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-address/-/product-sdk-address-0.1.1.tgz", + "integrity": "sha512-sSymun3alNGdvawhdc0Ha0KEkuqMwBZui1bsUVeZIZRJAfWvQzrV1AVaf8aah5JFlcaRdg8FYyp7xL2eP+ZplA==", + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "^1.7.1", + "@polkadot-api/substrate-bindings": "^0.12.0" + } + }, + "node_modules/@parity/product-sdk-address/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk-address/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.12.0.tgz", + "integrity": "sha512-cIjDeJRHW6g3z+/55UzpoG4LG1N0HbT4x3NvZsQkYg4eoio9Sw7Pw2aZZX86pWemxc7vQbNw7WSz2Gz+ckdX6Q==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.8.0", + "@polkadot-api/utils": "0.1.2", + "@scure/base": "^1.2.5", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-address/node_modules/@polkadot-api/utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.1.2.tgz", + "integrity": "sha512-yhs5k2a8N1SBJcz7EthZoazzLQUkZxbf+0271Xzu42C5AEM9K9uFLbsB+ojzHEM72O5X8lPtSwGKNmS7WQyDyg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-address/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk-chain-client": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-chain-client/-/product-sdk-chain-client-0.9.1.tgz", + "integrity": "sha512-NXMJAYqLGdFp0VAbNfn2HeGhcP6n78jxtVEpcv9084ZlldhuwFKJLGwXOj81xbw2QHATbufCNLDY9IsTM+9Pew==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-descriptors": "0.8.0", + "@parity/product-sdk-host": "0.14.1", + "@parity/product-sdk-logger": "0.1.1", + "polkadot-api": "^2.1.6" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-chain-client/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-cloud-storage": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-cloud-storage/-/product-sdk-cloud-storage-0.8.1.tgz", + "integrity": "sha512-yRMY8stewHA/ImbJD+PX/ao7JGniLomj3PPyPE22+aGZXpCcYwXdxMcDFOJgNtDK69Fyr483ejaew9tAFRzXNQ==", + "license": "Apache-2.0", + "dependencies": { + "@parity/bulletin-sdk": "^0.3.0", + "@parity/product-sdk-chain-client": "0.9.1", + "@parity/product-sdk-descriptors": "0.8.0", + "@parity/product-sdk-errors": "0.2.0", + "@parity/product-sdk-host": "0.14.1", + "@parity/product-sdk-logger": "0.1.1", + "@parity/product-sdk-tx": "0.3.2", + "@parity/result": "0.2.0", + "multiformats": "^13.3.0", + "polkadot-api": "^2.1.6" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@parity/bulletin-sdk": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@parity/bulletin-sdk/-/bulletin-sdk-0.3.0.tgz", + "integrity": "sha512-sxVwBzyH/egXze1muPXbaGwQuOkP8efVB4Lxunshixf18gJ6WT2tedgUy08QOfQ1848BDQS4wVpRRQfPfb09/g==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "@ipld/dag-pb": "^4.1.3", + "@noble/hashes": "^2.2.0", + "@polkadot-labs/hdkd-helpers": "^0.0.29", + "ipfs-unixfs": "^12.0.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "multiformats": "^13.4.1", + "polkadot-api": "^2.1.2" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/@polkadot-labs/hdkd-helpers": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@polkadot-labs/hdkd-helpers/-/hdkd-helpers-0.0.29.tgz", + "integrity": "sha512-yiLm1Gj3j5NrQV+VFMlFzkBgcRBNfq2Sd/U3S8iau2bzhDwgsn4gy6FDt94TRPD5xLxOzi1I3wSLOrgOs2eLVw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "^2.2.0", + "@noble/hashes": "^2.2.0", + "@scure/base": "^2.0.0", + "@scure/sr25519": "^1.0.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-cloud-storage/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-contracts": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-contracts/-/product-sdk-contracts-0.9.2.tgz", + "integrity": "sha512-4svBhyBOoNfV4K5f9feizZSPl1Hn5frYpJvf5hjR9z7zp+t+1ruM/DjUM5QCgwrAmwkpfw7oxHDFTN3SlBo0tw==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-address": "0.1.1", + "@parity/product-sdk-errors": "0.2.0", + "@parity/product-sdk-keys": "0.3.16", + "@parity/product-sdk-logger": "0.1.1", + "@parity/product-sdk-signer": "0.11.1", + "@parity/product-sdk-tx": "0.3.2", + "@parity/result": "0.2.0", + "@polkadot-labs/hdkd-helpers": "^0.0.30", + "polkadot-api": "^2.1.6", + "viem": "^2.52.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/@polkadot-labs/hdkd-helpers": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@polkadot-labs/hdkd-helpers/-/hdkd-helpers-0.0.30.tgz", + "integrity": "sha512-qWmmD6ayj14RenDuDFfjF3sHS7ObqPzwIIMPcSVoDeKFSeQV7RY0HwyhC5CG4i6FoguMzak2dbtjYpNN5XQiwQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "^2.2.0", + "@noble/hashes": "^2.2.0", + "@scure/base": "^2.2.0", + "@scure/sr25519": "^1.0.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-contracts/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-crypto": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-crypto/-/product-sdk-crypto-0.1.1.tgz", + "integrity": "sha512-No6AyTLw1Nv3ym8SDdXh/tnezdClNOL9pJgaciVr9Ny6hIL5rs6MQiXsP0+1bc1Nwymz5Q4FqsYg/htE4lejNg==", + "license": "Apache-2.0", + "dependencies": { + "@noble/ciphers": "^1.2.1", + "@noble/curves": "^1.8.0", + "@noble/hashes": "^1.7.1", + "tweetnacl": "^1.0.3" + } + }, + "node_modules/@parity/product-sdk-crypto/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk-crypto/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk-descriptors": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-descriptors/-/product-sdk-descriptors-0.8.0.tgz", + "integrity": "sha512-DfdrtzjBqiS4A/fnqoDOyW+KiBVKkOtfDDl1/BLHtvYxp3TanPkS952Sd28F7v1Rk/0xnqm8d7shD06/XoLqhg==", + "license": "Apache-2.0", + "dependencies": { + "polkadot-api": "^2.1.6" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-descriptors/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-errors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-errors/-/product-sdk-errors-0.2.0.tgz", + "integrity": "sha512-2rvJV0iJyNAxSjm+RHcoch3GRGYgfMDd2wCha+LmykIDZ06oUfFo+wY6Jf8z56ZMMqHvBvDO1ZNrstVgUZxlEQ==", + "license": "Apache-2.0" + }, + "node_modules/@parity/product-sdk-host": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-host/-/product-sdk-host-0.14.1.tgz", + "integrity": "sha512-PF87O0Kb35TyZo+sDlcYo9ZvaUedR8NNbcZvfBf8PBL65Yck10jIDuXE386hl9pgpgOn6o0Y1z6iJfEolhNXsg==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-errors": "0.2.0", + "@parity/product-sdk-logger": "0.1.1", + "@parity/result": "0.2.0", + "@parity/truapi": "^0.5.0", + "@polkadot-api/json-rpc-provider": "^0.2.0", + "@polkadot-api/substrate-bindings": "^0.20.3", + "neverthrow": "^8.2.0", + "polkadot-api": "^2.1.6" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-host/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-host/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-keys": { + "version": "0.3.16", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-keys/-/product-sdk-keys-0.3.16.tgz", + "integrity": "sha512-dnaHPQVOyxE7yEET/NsHi/0l8rI+vkH0m1OaZQ+qMkv4zwrFqXbhcXO7z6Kj+RHp5hswkmrcpEmjl4DeV5Z2xQ==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-address": "0.1.1", + "@parity/product-sdk-crypto": "0.1.1", + "@parity/product-sdk-local-storage": "0.3.2", + "@polkadot-labs/hdkd": "^0.0.28", + "@polkadot-labs/hdkd-helpers": "^0.0.30", + "@scure/sr25519": "^2.2.0", + "polkadot-api": "^2.1.6", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-labs/hdkd": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/@polkadot-labs/hdkd/-/hdkd-0.0.28.tgz", + "integrity": "sha512-LpdqtQRpcgZQ5Mr8J0ddMA5ZufsbI4W3KuJkVdoYMnSmWs4179LigDb1rTYAOtyCg2jWUjf7rWP0mGxQQvNrHw==", + "license": "MIT", + "dependencies": { + "@polkadot-labs/hdkd-helpers": "~0.0.29" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-labs/hdkd-helpers": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@polkadot-labs/hdkd-helpers/-/hdkd-helpers-0.0.30.tgz", + "integrity": "sha512-qWmmD6ayj14RenDuDFfjF3sHS7ObqPzwIIMPcSVoDeKFSeQV7RY0HwyhC5CG4i6FoguMzak2dbtjYpNN5XQiwQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "^2.2.0", + "@noble/hashes": "^2.2.0", + "@scure/base": "^2.2.0", + "@scure/sr25519": "^1.0.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-labs/hdkd-helpers/node_modules/@scure/sr25519": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@scure/sr25519/-/sr25519-1.0.0.tgz", + "integrity": "sha512-b+uhK5akMINXZP95F3gJGcb5CMKYxf+q55fwMl0GoBwZDbWolmGNi1FrBSwuaZX5AhqS2byHiAueZgtDNpot2A==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~2.0.0", + "@noble/hashes": "~2.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-labs/hdkd-helpers/node_modules/@scure/sr25519/node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@polkadot-labs/hdkd-helpers/node_modules/@scure/sr25519/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/@scure/sr25519": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@scure/sr25519/-/sr25519-2.2.0.tgz", + "integrity": "sha512-UTOZb6Hzw44REQdl2SWNBhBFIoqOIhMLNIz3zYyVQLbqdshhuyuuxYoibKHlDg9oqdwdCHQe5LkTsevugPpUbw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~2.2.0", + "@noble/hashes": "~2.2.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-keys/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-local-storage": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-local-storage/-/product-sdk-local-storage-0.3.2.tgz", + "integrity": "sha512-1KJGOZrf6pj1P19j8AVbVC5NTmQe5KGE2VJ0gzJDYCHGWPYECtm7Fc0zfq6AAZbyPJkgsuZz/K4+MRijf4lf2A==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-host": "0.14.1", + "@parity/product-sdk-logger": "0.1.1" + } + }, + "node_modules/@parity/product-sdk-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-logger/-/product-sdk-logger-0.1.1.tgz", + "integrity": "sha512-AiSV3TTNlMZJftLQsO78BZsEymGFuJtGMSpGrJ+vUtqaZavWaW/Hc6MICBLnEYgeCrdNpv7QBso3dRsTfnAZXQ==", + "license": "Apache-2.0" + }, + "node_modules/@parity/product-sdk-signer": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-signer/-/product-sdk-signer-0.11.1.tgz", + "integrity": "sha512-9wGaazcmqVaSkJckcZhHFkhpPQJSNVgvFRbH2qIXkvAmxKMfW9xzdplsEoMnxRcvUree6I1YK2m3kn61/dBpjw==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-address": "0.1.1", + "@parity/product-sdk-errors": "0.2.0", + "@parity/product-sdk-host": "0.14.1", + "@parity/product-sdk-keys": "0.3.16", + "@parity/product-sdk-logger": "0.1.1", + "@parity/result": "0.2.0", + "polkadot-api": "^2.1.6" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-signer/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-signer/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-statement-store": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-statement-store/-/product-sdk-statement-store-0.6.2.tgz", + "integrity": "sha512-n7FJ2lvKMuJ6oINLVYPlSH8UCVd68XIBqFNmK5VliaJ7i+7HcfB90NyYdD4TDHQAxO5wFZ5lUo3xYfvVCB5ARw==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-errors": "0.2.0", + "@parity/product-sdk-host": "0.14.1", + "@parity/product-sdk-logger": "0.1.1", + "@parity/product-sdk-utils": "0.1.1", + "@parity/result": "0.2.0", + "@polkadot-api/substrate-client": "^0.7.0", + "polkadot-api": "^2.1.6" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-statement-store/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-tx": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-tx/-/product-sdk-tx-0.3.2.tgz", + "integrity": "sha512-Y10Sw/ZluIAA6+zB9Ty+y0bSwbrEVIeBKN3umrQXHyseDfBmxWEXkwlhjCxnusJ44wtUgrIN9BsNRGI/51ffKQ==", + "license": "Apache-2.0", + "dependencies": { + "@parity/product-sdk-errors": "0.2.0", + "@parity/product-sdk-keys": "0.3.16", + "@parity/product-sdk-logger": "0.1.1", + "@parity/result": "0.2.0", + "@polkadot-labs/hdkd-helpers": "^0.0.30", + "polkadot-api": "^2.1.6" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/@polkadot-labs/hdkd-helpers": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@polkadot-labs/hdkd-helpers/-/hdkd-helpers-0.0.30.tgz", + "integrity": "sha512-qWmmD6ayj14RenDuDFfjF3sHS7ObqPzwIIMPcSVoDeKFSeQV7RY0HwyhC5CG4i6FoguMzak2dbtjYpNN5XQiwQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "^2.2.0", + "@noble/hashes": "^2.2.0", + "@scure/base": "^2.2.0", + "@scure/sr25519": "^1.0.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" + }, + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/@parity/product-sdk-tx/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@parity/product-sdk-utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@parity/product-sdk-utils/-/product-sdk-utils-0.1.1.tgz", + "integrity": "sha512-vu/af1K7k7p0/aLKALrGVxz7K94xnY800DxmhnOhu6Hy4+y+8e9WHPecMjbYBBJby4FPMOkWLqEVRN/sk3Ucgw==", + "dependencies": { + "@noble/hashes": "^1.7.1", + "@parity/product-sdk-logger": "0.1.1" + } + }, + "node_modules/@parity/product-sdk-utils/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@parity/product-sdk/node_modules/@commander-js/extra-typings": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-15.0.0.tgz", + "integrity": "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA==", + "license": "MIT", + "peerDependencies": { + "commander": "~15.0.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/cli": { + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.21.9.tgz", + "integrity": "sha512-9aVdF/ocF3DBb8b/gdkcicBIaQkak2kkU4EGW+TunXLIvbIj2X9hH5geOlx8uWNwt0O4O1DIHD/nkXvycG/AcA==", + "license": "MIT", + "dependencies": { + "@commander-js/extra-typings": "^15.0.0", + "@polkadot-api/codegen": "0.22.5", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/wasm-executor": "^0.2.3", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@types/node": "^25.9.4", + "commander": "^15.0.0", + "execa": "^9.6.1", + "fs.promises.exists": "^1.1.4", + "ora": "^9.4.1", + "read-pkg": "^10.1.0", + "rollup": "^4.62.2", + "rollup-plugin-esbuild": "^6.2.1", + "rxjs": "^7.8.2", + "tsc-prog": "^2.3.0", + "typescript": "^6.0.3", + "write-package": "^7.2.0" + }, + "bin": { + "papi": "dist/main/src/main.js", + "polkadot-api": "dist/main/src/main.js" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/codegen": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.22.5.tgz", + "integrity": "sha512-zwZJAlviI211zhj5i6oXkrr0crrbO3GZjBd2vC9AshY1pRmyiNLV9DZsXw4E6l4JQwdAAyNWtPdnvvB9T3TpKg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/ink-contracts": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.6.3.tgz", + "integrity": "sha512-XqnM1VDzI5L62xgg+f8le2yEoz8QZbUKEfAfPnHMOgBj9tJiyF15FcOJmnLMO/vq3cGixqh18tzJekLG5YTxtA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/known-chains": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.12.1.tgz", + "integrity": "sha512-ZW9TYD2y5IhgNy28zi9sByMr0CaP2I9x8HcAI0pwZR2qDC0kysCG6brDlg1UoZKCjRO99SsKO/iU1o434JgMnQ==", + "license": "MIT" + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/logs-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.2.0.tgz", + "integrity": "sha512-BH9YdxZu+ZBPPAUwGrvqHPn1hQStL2Im3MmTwYkwXOWW2HlGHULcF65QKvyK+T6/mj2vDvl3MgivnGkUw4pVxg==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/merkleize-metadata": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.2.3.tgz", + "integrity": "sha512-WkPbz0p2XQ9c8yXagdnwCHEB70Gnm91okcsd6IXU393//3aPgkxKgb+/Efnz7C5/KQmg02P0zXo7q/n/W/yVCA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/metadata-builders": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.14.3.tgz", + "integrity": "sha512-m7CACsiqHzgVEh5WBZGkTV8AQ3CBQKR1YpPQMnlsJfCr/IkgKU0UyWM6WxCmBiReLFVkOfXMtGlpN8+GxpHmww==", + "license": "MIT", + "dependencies": { + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/metadata-compatibility": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.6.3.tgz", + "integrity": "sha512-/Y0uF8nDk60ijydp8Bd37YexPFdB8hBXJWwEgOJHsVlhiny8sVKXiMg+UkJ9BiEk2z+yMbZRCKmhNpTpizo7aw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/observable-client": { + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.18.7.tgz", + "integrity": "sha512-/eQi3D8jbXLg/L1hZX4eX0/+nO3kTxkOfYeWEtFhtOvFdfqiiy2DdXG3Zg5QzgHmfG+11p4vSlLiPCC2UvS5Qw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/pjs-signer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.7.3.tgz", + "integrity": "sha512-U7BLFZfnpFMxCh/scJoLXT6oSbfZtZgMTkiu+TbuWQljAb/1ttrQOfshub5VihNyD5rHajp/2Fq0ONZoL5N5PA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/signer": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.3.3.tgz", + "integrity": "sha512-bmEV65TwgwbMfiecl7ZQ3k5lfkCOx9FPwPYkVvALcPiqb/d3zYwT9LL/E00hj+EB6IKMlMelEQVuchcW5i/92w==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/merkleize-metadata": "1.2.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signers-common": "0.2.3", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/signers-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.2.3.tgz", + "integrity": "sha512-SzGLJMxug31Y1P8+0I809ICpDrayztUXFFh2TV02GuwjEnY6mDGnmX56wowZF5yCn+WteeRdcTJTK7Whk3AGYQ==", + "license": "MIT", + "dependencies": { + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/sm-provider": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.3.8.tgz", + "integrity": "sha512-jIvzBNsBsh6LIpCrUOa7miZdQPwLPns5IUQyz/8v84R3ON7URSIVbtywGSR8O83BZGyW/DmVkqa5lClLE2fkqw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1" + }, + "peerDependencies": { + "@polkadot-api/smoldot": ">=0.3" + } + }, + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/smoldot": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.4.6.tgz", + "integrity": "sha512-gOXMJ10fXOub0zPP1cGAYeyf29BA2fB+qukrjXGcGN9b0Ya+lDgV7A7Q7y9JOrlGLeKbd1YdrYLAe/F43V7b9Q==", + "license": "MIT", + "dependencies": { + "@types/node": "^25.9.4", + "smoldot": "~3.3.1" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/substrate-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.7.0.tgz", + "integrity": "sha512-TWCc4MAMa5SLVQXmomLHknbj+bztQ/Yclgwm8ENBhz8hR7c9rw9FBAkCa02jMBMCAygPhp3ayGRq+UFcF8KIxQ==", "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/utils": "0.4.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@parity/product-sdk/node_modules/@polkadot-api/ws-provider": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.9.1.tgz", + "integrity": "sha512-Ft2QJEjLZgTyKiCEbz3urSOxNfMdqNkg+QKqJhRbOFB7JtR1qMTvLE3TjM+5d2mNlEpOc3j2KDK3APNSj7uQ2Q==", "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" } }, - "node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "node_modules/@parity/product-sdk/node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", "license": "MIT", "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=22.12.0" } }, - "node_modules/@noble/curves": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", - "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "node_modules/@parity/product-sdk/node_modules/polkadot-api": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-2.2.1.tgz", + "integrity": "sha512-eiZVUhI1gY4ycBtsCo/yaDXyohUeuqnyKun+bwj90ADSn+jlfjZ4sajAqIhJFwPxbCT75+Zzbln4ZjuZ5PWNWA==", "license": "MIT", "dependencies": { - "@noble/hashes": "2.2.0" + "@polkadot-api/cli": "0.21.9", + "@polkadot-api/ink-contracts": "0.6.3", + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/known-chains": "0.12.1", + "@polkadot-api/logs-provider": "0.2.0", + "@polkadot-api/metadata-builders": "0.14.3", + "@polkadot-api/metadata-compatibility": "0.6.3", + "@polkadot-api/observable-client": "0.18.7", + "@polkadot-api/pjs-signer": "0.7.3", + "@polkadot-api/polkadot-signer": "0.1.6", + "@polkadot-api/signer": "0.3.3", + "@polkadot-api/sm-provider": "0.3.8", + "@polkadot-api/smoldot": "0.4.6", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/substrate-client": "0.7.0", + "@polkadot-api/utils": "0.4.0", + "@polkadot-api/ws-middleware": "0.3.6", + "@polkadot-api/ws-provider": "0.9.1", + "@rx-state/core": "^0.1.4" }, - "engines": { - "node": ">= 20.19.0" + "bin": { + "papi": "bin/cli.js", + "polkadot-api": "bin/cli.js" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "rxjs": ">=7.8.0" } }, - "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node_modules/@parity/product-sdk/node_modules/smoldot": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-3.3.2.tgz", + "integrity": "sha512-Zl4h/0gsw8cfTZzuJ7LV7mtR6QjxltwYjMY7MsVw0oXBXrLK8zyOS6DS9Vjsy57pX1vBMg6UVhHxjbH3W905zA==", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "dependencies": { + "ws": "^8.8.1" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "node_modules/@parity/product-sdk/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">= 8" + "node": ">=14.17" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } + "node_modules/@parity/result": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@parity/result/-/result-0.2.0.tgz", + "integrity": "sha512-QCrhYPeVVaPIjnlsfBIk5GHPRqfuCjy6MjqKkoCP4kwS1LIo5YtJCSFeB5mGtvMG7hiaRNl4lHQcd5YqDfJ1tQ==", + "license": "Apache-2.0" }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@parity/truapi": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@parity/truapi/-/truapi-0.5.1.tgz", + "integrity": "sha512-5AV6YoqnUKXj2wJ/qt/J2i3jpFawhEVkY165V5fSDccGkgK2oVHxlSfLwscXpZp4DxwFTL1FmvkhwhuqPDIdtA==", "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "@noble/hashes": "^2.2.0", + "neverthrow": "^8.2.0", + "scale-ts": "^1.6.1" } }, "node_modules/@playwright/test": { @@ -1416,6 +5176,61 @@ "integrity": "sha512-B2h1o+Qlo9idpASaHvMSoViB2I5ko5OAfwfhYF8LQDkTADK0B+SeStzNj1Qn+FG34wqTuv7HzBCdjaUgzYINJQ==", "license": "MIT" }, + "node_modules/@polkadot-api/ws-middleware": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@polkadot-api/ws-middleware/-/ws-middleware-0.3.6.tgz", + "integrity": "sha512-IMoJB572DdSYPshCQa2JmmehUEzX2Uwg5vKQafubbTMEFacXbifc6LTTVvi9Ue67rBuDy2VOWXBAm3BBpfKpDA==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0", + "@polkadot-api/json-rpc-provider-proxy": "0.4.1", + "@polkadot-api/raw-client": "0.3.0", + "@polkadot-api/substrate-bindings": "0.20.3", + "@polkadot-api/utils": "0.4.0" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@polkadot-api/ws-middleware/node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.2.0.tgz", + "integrity": "sha512-lhkuBS/x06i3djQIN7p8jVkMnYuGsUAEMS6RhdWeEpz7X/8/APER4Wdih7MEBovCuwVSCTjOxl8f+alH7AZHZg==", + "license": "MIT" + }, + "node_modules/@polkadot-api/ws-middleware/node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.4.1.tgz", + "integrity": "sha512-F1Hw01C60jn98KQ0vBbgcwAvEcMcKLsJ5kFzq600sgjc6Rnbn1VF/XjTdrjcIqvfpR9sXyGIrpImrov2Usbrsw==", + "license": "MIT" + }, + "node_modules/@polkadot-api/ws-middleware/node_modules/@polkadot-api/raw-client": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.3.0.tgz", + "integrity": "sha512-u/wM9W7ugIXxBSOEV8+zvQI43b5vgSI0pvE0Rg8PV0G65BTLK0SMc2o2SQL0HtS5+bi5sw/gg4reBJ/0a4U0cw==", + "license": "MIT", + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.2.0" + } + }, + "node_modules/@polkadot-api/ws-middleware/node_modules/@polkadot-api/substrate-bindings": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.20.3.tgz", + "integrity": "sha512-9iqC71fx1ee9ld1NZV8PFime5vryi0kt1bKCSlvNgO6dqMc06sMZuZ8WPjOzWLCHiKHLuphdMs3rVBBaeCP3yg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@polkadot-api/utils": "0.4.0", + "@scure/base": "^2.2.0", + "scale-ts": "^1.6.1" + } + }, + "node_modules/@polkadot-api/ws-middleware/node_modules/@polkadot-api/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-9b/hwRM0UloLWV7SfpNaSD/4k8UQAHoaACAk7Xe+1MlfAm2JtnmPiB1GfGrfTyBlsrJVUIBCZpEmbmxVMaIqBA==", + "license": "MIT" + }, "node_modules/@polkadot-api/ws-provider": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.7.5.tgz", @@ -1583,9 +5398,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.3.tgz", + "integrity": "sha512-c0wdcekXtQvvn5Tsrk/+op/gUArrbWaFduBnTLP2l1cKLSQs4diMWjJw3m6A0DdzT8dAAX95KpkJ3qynCePbmw==", "cpu": [ "arm" ], @@ -1596,9 +5411,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.3.tgz", + "integrity": "sha512-3YjElDdWN+qXAFbJ/CzPV+0wspLqh54k/I6GfdYtEJRqg7buSgc1yPM3B+93j1M4neobtkATHZTmxK2AMVGfnA==", "cpu": [ "arm64" ], @@ -1609,9 +5424,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.3.tgz", + "integrity": "sha512-Pch2pFNOxxz1hTjypIdPyRTR6riiwRl84+VcN9djS680fw+Co1nAJINrdpqp7KV0NvyuU8ilZXZCjd7ykJl1GQ==", "cpu": [ "arm64" ], @@ -1622,9 +5437,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.3.tgz", + "integrity": "sha512-LEuncFUHFiF8t4yZVZvvZA1wk0pjAscRnsrn1EfTEmN4HXotBi2YtcnLRyaK6UbuczW7xZS5ES+81Rdz8Z0T6g==", "cpu": [ "x64" ], @@ -1635,9 +5450,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.3.tgz", + "integrity": "sha512-zvBUvsQUpOWALdDsk6qbS8bXf2VxmPisuudNDrY7x0p0jBdsoZl8HsHczIOgkQiZldmcacMKtBzpoGVNeIe2bQ==", "cpu": [ "arm64" ], @@ -1648,9 +5463,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.3.tgz", + "integrity": "sha512-C2KmNrcSem/AMg984H/dev+si0lieQGdXdR/lYGJnuumXnFb9Y7QdiI62obFdLlxRYLBv4P0eUVIDbD4c1vVvw==", "cpu": [ "x64" ], @@ -1661,9 +5476,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.3.tgz", + "integrity": "sha512-ggXnsTAEzNQx74XpunRsiZ9aBZDsI7XIa0hm2nzR9f4WzH5/f/d73ZSDaC5ejJ8YLY4NW+V3wr0tjOaeCq8hqA==", "cpu": [ "arm" ], @@ -1674,9 +5489,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.3.tgz", + "integrity": "sha512-2vng+FlzNUhKZxtej3IUqJgbZoQk2M/dwQM20+ULV0R/E/8tr9/P6uEf2iiGIk4HL0zMKh5Jry7mUHdUOvyGgA==", "cpu": [ "arm" ], @@ -1687,9 +5502,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.3.tgz", + "integrity": "sha512-LLLFZKt4/Nraf9rxDkhiU8QVgLF4WmCkfr0L4fj0fPfIZFBib0DeiFk1hhaYKd03LFAFJcxHslhDFlNJLylf5Q==", "cpu": [ "arm64" ], @@ -1700,9 +5515,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.3.tgz", + "integrity": "sha512-WJkdQCvS9sWNOUBJZfQRKpZGFBztRzcowI+nndmflKgU4XY+3a420FgTOSKTsVqJbnzSxeT4vaJalpOaPo2YCQ==", "cpu": [ "arm64" ], @@ -1713,9 +5528,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.3.tgz", + "integrity": "sha512-PwHXCCS2n64/1Ot6rP1YEYA02MGYBcQlr8CSZZyrUG2O7NH6NklYmvr9v3Jy+5e/eDeNchc/ukmKJi9LuflMIQ==", "cpu": [ "loong64" ], @@ -1726,9 +5541,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.3.tgz", + "integrity": "sha512-vUjxINQu3RC8NZS3ykk1gN65gIz8pAopOq2HXuZhiIxHdx7TFvDG+jgrdSgInu1Eza4/Rfi2VzZgyIgEH4WOaw==", "cpu": [ "loong64" ], @@ -1739,9 +5554,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.3.tgz", + "integrity": "sha512-wzko4aJ13+0G3kGnviCg5gnXFKd40izKsrf2uOw12US4XqprkDrmwOpeW14aSNa37V8bfPcz5Fkob6LZ3BAPmA==", "cpu": [ "ppc64" ], @@ -1752,9 +5567,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.3.tgz", + "integrity": "sha512-8120ue0JUMSwy11stlwnfdX3pPd+WZYGCDBwEHWtIHi6pOpZmsEF5QKB7a/UN+XFdqvobxz98kv8RTqikyCEBw==", "cpu": [ "ppc64" ], @@ -1765,9 +5580,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.3.tgz", + "integrity": "sha512-XLFHnR3tXMjbOCh2vtVJHmxt+995uJsTERQyseFDRA0xxMxyTZPLa3OIUlyFaO4mF/Lu0FjmWHCuPXJT1n/IOg==", "cpu": [ "riscv64" ], @@ -1778,9 +5593,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.3.tgz", + "integrity": "sha512-se6yXvNGMIl0f+RQzyh7XAmia8/9kplQx424wnG2w0C1oi6XgO6Y8otKhdXFHbHs88Ihavzmvh1NWjuovE76BQ==", "cpu": [ "riscv64" ], @@ -1791,9 +5606,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.3.tgz", + "integrity": "sha512-gNoxRefktVIiGflpONuxWWXZAzIQG++z9qHO3xKwk4WdDMuQja3JHGfE1u0i3PfPDyvhypdk+WrgIJqLhGG7sg==", "cpu": [ "s390x" ], @@ -1804,9 +5619,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.3.tgz", + "integrity": "sha512-V4KtWtQfAFMU7+9/A/VDps/VI8CHd3cYz0L8sgJzz8qK7eY7wI4ruFD82UYIYvW9Z4DtlTfhQcsl4XyPHW5uSg==", "cpu": [ "x64" ], @@ -1817,9 +5632,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.3.tgz", + "integrity": "sha512-LBx9LYXvj2CBkMkjLdNAWLwH0MLMin7do2VcVo9kVPibGLkY0BQQut2fv7NVqkXqZ/CrAu9LqDHVV1xHCMpCPw==", "cpu": [ "x64" ], @@ -1830,9 +5645,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.3.tgz", + "integrity": "sha512-ABVf3Q0RCu7NcyCCOZQI0pJ3GuSdfSl8EXcy88QtdceIMIoCUdfhsJChZ64L9zVM2aJHjde1Bhn5uqSRcX9ySA==", "cpu": [ "x64" ], @@ -1843,9 +5658,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.3.tgz", + "integrity": "sha512-+2Cy/ldweGBLlPIKsQLF8U5N44a0KDdbrk1rAjHOM9M2K+kGdIVjHLmmrZIcx+9Ny3ke/1JomCsDI1ocb11+sg==", "cpu": [ "arm64" ], @@ -1856,9 +5671,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.3.tgz", + "integrity": "sha512-dtZvzc8BedpSaFNy75x6uiWwAGTH+aZHDtdrqP6qk+WcLJrfti6sGje1ZJ9UxyzDLF23d/mV+PaMwuC0hL7UVA==", "cpu": [ "arm64" ], @@ -1869,9 +5684,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.3.tgz", + "integrity": "sha512-Rj8Ra4noo+aYy7sKBggCx0407mws34kAb1ySyWuq5DAtFBQdkSwnsjCgPrhPe9cvgBKZIukpE+CVHvORCS93kQ==", "cpu": [ "ia32" ], @@ -1882,9 +5697,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", - "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.3.tgz", + "integrity": "sha512-vp7N084ew/odXn2gi/mzm9mUkQu9l6AiN6dt4IeUM2Uvm9o+cVmP+YkqbMOteLbiGgqBBlJZjIMYVCfOOIVbVQ==", "cpu": [ "x64" ], @@ -1895,9 +5710,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", - "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.3.tgz", + "integrity": "sha512-MOG/3gTOn4Fwf574RVOaY61I5o6P90legkFADiTyn1hyjNydT+cerU2rLUwPdZkKKyJ+iT+K9p7WXK4LM1Ka6g==", "cpu": [ "x64" ], @@ -2145,9 +5960,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/json-schema": { @@ -2158,12 +5973,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "25.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", + "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/normalize-package-data": { @@ -2407,29 +6222,6 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -2831,11 +6623,14 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64id": { "version": "2.0.0", @@ -2872,14 +6667,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/braces": { @@ -3203,23 +7000,16 @@ "node": ">=20" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/concurrently": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.3.tgz", - "integrity": "sha512-ihjs0E2SxvDgq/MK418hX6YycQgKhsqxpbZuZbHo0yKfqDWdymWMjWYIpCIzqDDLLKClHlXev8whW/8WXmJ0BA==", + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", "dev": true, "license": "MIT", "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", - "shell-quote": "1.8.4", + "shell-quote": "1.9.0", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" @@ -3474,7 +7264,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, "license": "MIT" }, "node_modules/esbuild": { @@ -4026,6 +7815,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4170,6 +7971,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ipfs-unixfs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-12.0.2.tgz", + "integrity": "sha512-uZ3rutVVZZ+tw52P+sgDSgOSK6ztExJVlfCjKvSD+NIEVlWQPDeKgdSFm+Kxchmgp7t6g1h+dzir+NgY+VsQXg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "protons-runtime": "^6.0.1", + "uint8arraylist": "^2.4.8" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4336,9 +8147,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, "funding": [ { @@ -4633,6 +8444,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -4644,10 +8461,21 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nanoevents": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/nanoevents/-/nanoevents-9.1.0.tgz", + "integrity": "sha512-Jd0fILWG44a9luj8v5kED4WI+zfkkgwKyRQKItTtlPfEsh7Lznfi1kr8/iZ+XAIss4Qq5GqRB0qtWbaz9ceO/A==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -4678,6 +8506,18 @@ "node": ">= 0.6" } }, + "node_modules/neverthrow": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-8.2.0.tgz", + "integrity": "sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "^4.24.0" + } + }, "node_modules/node-releases": { "version": "2.0.37", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", @@ -4801,9 +8641,9 @@ } }, "node_modules/ora": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.3.0.tgz", - "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.1.tgz", + "integrity": "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==", "license": "MIT", "dependencies": { "chalk": "^5.6.2", @@ -4812,7 +8652,7 @@ "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.1", + "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" }, "engines": { @@ -5188,9 +9028,9 @@ } }, "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "funding": [ { "type": "opencollective", @@ -5207,7 +9047,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5389,6 +9229,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/protons-runtime": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-6.0.2.tgz", + "integrity": "sha512-hiyjyANwGcgmzc+tXc1/ZcSZhKnl5MDjaVNWkISHBgadaU0sjTgKIKZMZ62d9J9zlSTyKHCs/osPkQ/3Z+7yeA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.1.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5724,6 +9575,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -5752,12 +9612,12 @@ } }, "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.3.tgz", + "integrity": "sha512-Gu0c0iH9FzgX1L1t7ByIbbS3Vmdz+6KHm/EsqmmC71gUQ82yvZRkTK6XzrFObSka91WUVdynqp6nsfilzr5k6Q==", "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -5767,34 +9627,53 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", + "@rollup/rollup-android-arm-eabi": "4.62.3", + "@rollup/rollup-android-arm64": "4.62.3", + "@rollup/rollup-darwin-arm64": "4.62.3", + "@rollup/rollup-darwin-x64": "4.62.3", + "@rollup/rollup-freebsd-arm64": "4.62.3", + "@rollup/rollup-freebsd-x64": "4.62.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.3", + "@rollup/rollup-linux-arm-musleabihf": "4.62.3", + "@rollup/rollup-linux-arm64-gnu": "4.62.3", + "@rollup/rollup-linux-arm64-musl": "4.62.3", + "@rollup/rollup-linux-loong64-gnu": "4.62.3", + "@rollup/rollup-linux-loong64-musl": "4.62.3", + "@rollup/rollup-linux-ppc64-gnu": "4.62.3", + "@rollup/rollup-linux-ppc64-musl": "4.62.3", + "@rollup/rollup-linux-riscv64-gnu": "4.62.3", + "@rollup/rollup-linux-riscv64-musl": "4.62.3", + "@rollup/rollup-linux-s390x-gnu": "4.62.3", + "@rollup/rollup-linux-x64-gnu": "4.62.3", + "@rollup/rollup-linux-x64-musl": "4.62.3", + "@rollup/rollup-openbsd-x64": "4.62.3", + "@rollup/rollup-openharmony-arm64": "4.62.3", + "@rollup/rollup-win32-arm64-msvc": "4.62.3", + "@rollup/rollup-win32-ia32-msvc": "4.62.3", + "@rollup/rollup-win32-x64-gnu": "4.62.3", + "@rollup/rollup-win32-x64-msvc": "4.62.3", "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-esbuild": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-6.2.1.tgz", + "integrity": "sha512-jTNOMGoMRhs0JuueJrJqbW8tOwxumaWYq+V5i+PD+8ecSCVkuX27tGW7BXqDgoULQ55rO7IdNxPcnsWtshz3AA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "get-tsconfig": "^4.10.0", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=14.18.0" + }, + "peerDependencies": { + "esbuild": ">=0.18.0", + "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5880,9 +9759,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", "dev": true, "license": "MIT", "engines": { @@ -6599,10 +10478,38 @@ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "license": "MIT" }, + "node_modules/uint8-varint": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.5.tgz", + "integrity": "sha512-jeFLbL/x30wBRnWjKE1qVBXeumG46r7XmYkpis955lTQ+blccGKFrOsSMHlxePwYB1pI7L8YPHz1t4jLxEs3nA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arraylist": "^2.0.0", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/uint8arraylist": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.9.tgz", + "integrity": "sha512-KxWjyEFzchzik3aoQlK66oaoxIReoMo5bQRm1fcjBUZvE8xv/tyR3CTKhjh6K/faV8VaF6hd5pjr45CzbwuwkA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^5.0.1" + } + }, + "node_modules/uint8arrays": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", + "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/unicorn-magic": { @@ -6617,6 +10524,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unplugin-utils": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.5.tgz", + "integrity": "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-utils/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -7277,6 +11212,23 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/web/package.json b/web/package.json index e221bc7..43b8b5a 100644 --- a/web/package.json +++ b/web/package.json @@ -16,17 +16,28 @@ "dev:listen": "concurrently -n signal,web -c magenta,cyan \"npm:signal\" \"npm:dev\"", "build": "tsc -b && vite build", "build:bulletin": "tsc -b && vite build --config vite.bulletin.config.ts", + "build:product-devnet": "tsc -b && vite build --config vite.product.config.ts --mode product-devnet", + "build:product-devnet:beacons": "VITE_DOTIFY_ROOM_BEACONS=on npm run build:product-devnet", "deploy:bulletin": "node scripts/deploy-bulletin.cjs", + "deploy:product-devnet": "npm run build:product-devnet && npx --yes --package @polkadot-community-foundation/polkadot-app-deploy@0.13.1 pad ./dist-product dotify-test01.dot --env devnet --js-merkle --config ./polkadot-app-deploy.config.ts", + "deploy:product-devnet:beacons": "VITE_DOTIFY_ROOM_BEACONS=on npm run deploy:product-devnet", "smoke:production-env": "node scripts/production-env-smoke.mjs", + "smoke:devnet": "node scripts/devnet-endpoint-smoke.mjs", "smoke:signal": "node scripts/signaling-smoke.mjs", "lint": "eslint .", "fmt": "prettier --write 'src/**/*.{ts,tsx}' 'server/**/*.mjs' 'scripts/**/*.mjs' README.md", "fmt:check": "prettier --check 'src/**/*.{ts,tsx}' 'server/**/*.mjs' 'scripts/**/*.mjs' README.md", + "generate:cdm": "node scripts/generate-cdm-manifest.mjs", + "generate:cdm-metadata": "node scripts/generate-cdm-metadata.mjs", + "upload:cdm-metadata": "node scripts/upload-cdm-metadata.mjs", "update-types": "papi update", "codegen": "papi generate", "preview": "vite preview --host 0.0.0.0 --port 4273" }, "dependencies": { + "@parity/product-sdk": "0.19.1", + "@parity/product-sdk-descriptors": "0.8.0", + "@parity/product-sdk-statement-store": "0.6.2", "@polkadot-api/descriptors": "file:.papi/descriptors", "@polkadot-apps/chain-client": "^2.0.5", "@polkadot-apps/descriptors": "^1.0.1", @@ -45,6 +56,7 @@ "viem": "^2.53.1" }, "overrides": { + "brace-expansion": "5.0.8", "ws": "8.21.0" }, "devDependencies": { diff --git a/web/polkadot-app-deploy.config.ts b/web/polkadot-app-deploy.config.ts new file mode 100644 index 0000000..4f90ead --- /dev/null +++ b/web/polkadot-app-deploy.config.ts @@ -0,0 +1,16 @@ +export default { + domain: 'dotify-test01.dot', + displayName: 'Dotify', + description: 'Shared musical presence with artist-owned access and value flows.', + icon: { + path: './product-icon.png', + format: 'png' + }, + executables: [ + { + kind: 'app', + path: './dist-product', + appVersion: [0, 1, 0] + } + ] +}; diff --git a/web/product-icon.png b/web/product-icon.png new file mode 100644 index 0000000..1206ffa Binary files /dev/null and b/web/product-icon.png differ diff --git a/web/product-icon.svg b/web/product-icon.svg new file mode 100644 index 0000000..cb5ed43 --- /dev/null +++ b/web/product-icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/web/scripts/deploy-bulletin.cjs b/web/scripts/deploy-bulletin.cjs index 0fbe5df..ba52aa1 100644 --- a/web/scripts/deploy-bulletin.cjs +++ b/web/scripts/deploy-bulletin.cjs @@ -41,10 +41,16 @@ function base32lower(bytes) { return out; } +// Multihash code for blake2b-256 is 0xb220, which is 45600 and therefore needs three +// varint bytes: 0xa0 0xe4 0x02. The single byte 0x1e used here previously is blake3 - an +// algorithm the Bulletin Chain does not even accept - so the digest was correct but +// tagged as the wrong function, and the resulting CID resolved nowhere. Verified against +// @parity/bulletin-sdk `calculateCid(bytes, 0x55, 45600)`, which produces this prefix. +const BLAKE2B_256_MULTIHASH = [0xa0, 0xe4, 0x02, 0x20]; + function cidFromBytes(bytes) { const hash = blake2b(bytes, null, 32); - const mh = new Uint8Array([0x1e, 0x20, ...hash]); // blake2b-256 multihash - const cid = new Uint8Array([0x01, 0x55, ...mh]); // CIDv1 + raw codec + const cid = new Uint8Array([0x01, 0x55, ...BLAKE2B_256_MULTIHASH, ...hash]); // CIDv1 + raw codec return 'b' + base32lower(cid); } diff --git a/web/scripts/devnet-endpoint-smoke.mjs b/web/scripts/devnet-endpoint-smoke.mjs new file mode 100644 index 0000000..b736c14 --- /dev/null +++ b/web/scripts/devnet-endpoint-smoke.mjs @@ -0,0 +1,145 @@ +// Read-only DevNet endpoint smoke check. +// +// Answers one question with evidence rather than assertion: does the Product +// DevNet build profile point at a chain that actually holds Dotify's contracts? +// +// Product DevNet is a preset over the Paseo system parachains - Asset Hub +// (1000), People (1004), Bulletin (1010) - at EVM chain 420420417. Dotify is +// already deployed there, so porting to DevNet is a configuration question, not +// a redeploy. This check proves the configuration. +// +// Run: npm run smoke:devnet +// +// Network-dependent and therefore not part of `npm run test:unit`. It performs +// only eth_chainId / eth_getCode reads and unauthenticated GETs; it sends no +// transaction, reads no secret, and prints no credential. + +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(scriptDir, '../..'); + +const EXPECTED_CHAIN_ID = 420420417; +const REQUEST_TIMEOUT_MS = 20_000; + +function parseEnvFile(path) { + const env = {}; + for (const line of readFileSync(path, 'utf8').split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + const eq = trimmed.indexOf('='); + if (eq === -1) continue; + env[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim(); + } + return env; +} + +async function withTimeout(run) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + try { + return await run(controller.signal); + } finally { + clearTimeout(timer); + } +} + +async function ethCall(rpcUrl, method, params) { + return withTimeout(async signal => { + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), + signal + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const body = await response.json(); + if (body.error) throw new Error(body.error.message ?? 'RPC error'); + return body.result; + }); +} + +async function reachable(url) { + return withTimeout(async signal => { + const response = await fetch(url, { method: 'GET', signal }); + // A Substrate WS RPC answers a plain GET with 405, and an IPFS gateway + // redirects. Both prove the endpoint is serving. + return response.status; + }); +} + +const results = []; +function record(ok, label, detail) { + results.push({ ok, label, detail }); + console.log(`${ok ? 'ok ' : 'FAIL'} - ${label}${detail ? ` (${detail})` : ''}`); +} + +const env = parseEnvFile(resolve(repoRoot, 'web/.env.product-devnet')); +const deployments = JSON.parse(readFileSync(resolve(repoRoot, 'deployments.json'), 'utf8')); +const rpcUrl = env.VITE_ETH_RPC_URL; + +if (!rpcUrl) { + console.error('VITE_ETH_RPC_URL is not set in web/.env.product-devnet'); + process.exit(1); +} + +console.log(`Dotify Product DevNet endpoint smoke\nAsset Hub RPC: ${rpcUrl}\n`); + +try { + const chainIdHex = await ethCall(rpcUrl, 'eth_chainId', []); + const chainId = Number.parseInt(chainIdHex, 16); + record(chainId === EXPECTED_CHAIN_ID, `Asset Hub reports EVM chain ${EXPECTED_CHAIN_ID}`, `got ${chainId}`); +} catch (error) { + record(false, 'Asset Hub reports the expected EVM chain', error.message); +} + +try { + const blockHex = await ethCall(rpcUrl, 'eth_blockNumber', []); + const block = Number.parseInt(blockHex, 16); + // The chain stalled at 10612201 on 2026-07-01 and later resumed. A head at or + // below that is the signature of a frozen chain, not a healthy one. + record(block > 10_612_201, 'Asset Hub is producing blocks past the 2026-07 halt', `head ${block}`); +} catch (error) { + record(false, 'Asset Hub is producing blocks', error.message); +} + +// The contracts Dotify reads on every catalog load. Code present here is the +// evidence that no redeploy is needed to serve the catalog on DevNet. +for (const [label, address] of [ + ['ArtistDirectory', deployments.directory], + ['ArtistRuntimeFactory', deployments.factory] +]) { + try { + const code = await ethCall(rpcUrl, 'eth_getCode', [address, 'latest']); + const deployed = typeof code === 'string' && code !== '0x' && code.length > 2; + record(deployed, `${label} is deployed at ${address}`, deployed ? `${code.length} chars of bytecode` : 'no code'); + } catch (error) { + record(false, `${label} is deployed at ${address}`, error.message); + } +} + +for (const [label, url] of [ + ['Bulletin RPC', env.VITE_BULLETIN_WS_URL?.replace(/^wss:/, 'https:')], + ['IPFS gateway', env.VITE_PINATA_GATEWAY] +]) { + if (!url) { + record(false, `${label} is configured`, 'missing'); + continue; + } + try { + const status = await reachable(url); + record(status > 0 && status < 500, `${label} responds`, `HTTP ${status}`); + } catch (error) { + record(false, `${label} responds`, error.message); + } +} + +const failed = results.filter(result => !result.ok); +if (failed.length > 0) { + console.error(`\nDotify DevNet endpoint smoke failed: ${failed.length} of ${results.length} checks.`); + process.exit(1); +} + +console.log(`\nDotify DevNet endpoint smoke passed (${results.length} checks).`); diff --git a/web/scripts/generate-cdm-manifest.mjs b/web/scripts/generate-cdm-manifest.mjs new file mode 100644 index 0000000..dfe5469 --- /dev/null +++ b/web/scripts/generate-cdm-manifest.mjs @@ -0,0 +1,157 @@ +// Generate the Product CDM manifest and typed contract augmentation for the +// frontend from Hardhat artifacts + deployments.json. +// +// Run via `npm run generate:cdm` from web/. Plain Node ESM, like +// contracts/evm/scripts/generate-abis.mjs: this only reads JSON and writes +// JSON/TS, so it needs no Hardhat runtime. +// +// It lives in web/ rather than next to generate-abis.mjs because it needs +// @parity/product-sdk-contracts/codegen, which is a frontend dependency. +// Adding the Product SDK tree to contracts/evm just to emit types would be a +// worse trade. Compile first: `cd contracts/evm && npm run compile`. +// +// Why a hand-generated manifest instead of `cdm install`: +// +// Dotify's contracts are Solidity deployed through Asset Hub's eth-rpc, which +// is a compatibility layer over pallet-revive - the same pallet the Product SDK +// contract helpers target. So the deployed H160 addresses are already +// addressable through @parity/product-sdk-contracts without recompiling to +// PolkaVM or registering CDM packages. `CdmJsonContract` only needs `version`, +// `address`, and `abi` for getContract(), and `new ContractManager(...)` is +// documented as snapshot-only. This produces exactly that snapshot, from the +// same artifacts the viem bindings come from, so the two adapters can never +// disagree about an ABI. +// +// The manifest carries only fixed-address contracts. Artist runtimes are +// diamonds deployed per artist, so their address is known at call time, not +// build time; their merged facet ABI is emitted separately for +// createContract(runtime, artistRuntimeAddress, abi). + +import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { generateContractTypes } from '@parity/product-sdk-contracts/codegen'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(scriptDir, '../..'); +const artifactsRoot = resolve(repoRoot, 'contracts/evm/artifacts/contracts'); +const outDir = resolve(repoRoot, 'web/src/generated/contracts'); +const deploymentsPath = resolve(repoRoot, 'deployments.json'); + +const HEADER = '// Auto-generated by web/scripts/generate-cdm-manifest.mjs. Do not edit manually.'; + +// CDM library name -> { deployments.json key, artifact path }. +const FIXED_CONTRACTS = [ + { library: '@dotify/artist-directory', deployment: 'directory', artifact: 'ArtistDirectory.sol/ArtistDirectory.json' }, + { library: '@dotify/artist-runtime-factory', deployment: 'factory', artifact: 'ArtistRuntimeFactory.sol/ArtistRuntimeFactory.json' } +]; + +// Facets whose selectors are installed on every artist runtime diamond. Merged +// into one ABI so a runtime address can be called through a single handle, the +// same way the viem adapter calls facet ABIs at the runtime address. +const RUNTIME_FACETS = [ + 'pallets/MusicRegistryPallet.sol/MusicRegistryPallet.json', + 'pallets/MusicRoyaltiesPallet.sol/MusicRoyaltiesPallet.json', + 'pallets/MusicAccessPallet.sol/MusicAccessPallet.json', + 'pallets/MusicNFTPallet.sol/MusicNFTPallet.json' +]; + +const RUNTIME_LIBRARY = '@dotify/smart-runtime'; + +function readArtifactAbi(artifact) { + const artifactPath = resolve(artifactsRoot, artifact); + let parsed; + try { + parsed = JSON.parse(readFileSync(artifactPath, 'utf8')); + } catch { + throw new Error(`Missing artifact ${artifact}. Run "cd contracts/evm && npm run compile" first.`); + } + if (!Array.isArray(parsed.abi)) { + throw new Error(`Artifact ${artifact} has no abi array.`); + } + return parsed.abi; +} + +/** Stable identity for an ABI entry: selector-equivalent for functions, plus type and name. */ +function entryKey(entry) { + const inputs = (entry.inputs ?? []).map(input => input.type).join(','); + return `${entry.type}:${entry.name ?? ''}(${inputs})`; +} + +/** + * Merge facet ABIs into one runtime ABI. A diamond cannot install two facets + * with the same selector, so a collision here means the facet set is wrong - + * fail loudly rather than silently keeping whichever came first. + */ +function mergeRuntimeAbi(facets) { + const merged = new Map(); + for (const { artifact, abi } of facets) { + for (const entry of abi) { + const key = entryKey(entry); + const existing = merged.get(key); + if (!existing) { + merged.set(key, { entry, artifact }); + continue; + } + // Shared constructors/events across facets are expected and identical. + if (JSON.stringify(existing.entry) === JSON.stringify(entry)) continue; + throw new Error(`Runtime facet collision on ${key}: ${existing.artifact} and ${artifact} disagree. Check the diamond facet set.`); + } + } + return Array.from(merged.values(), ({ entry }) => entry); +} + +/** + * Solidity leaves auto-generated getter params unnamed, and + * generateContractTypes interpolates the name straight into a tuple label - + * emitting `args: [: HexString]`, which does not parse. Name them positionally + * for codegen only. The manifest ABI stays byte-faithful to the artifact: + * argument encoding is positional, so names there are cosmetic, and rewriting + * them would make cdm.json diverge from the compiled contract. + */ +function withNamedParams(abi) { + return abi.map(entry => ({ + ...entry, + inputs: (entry.inputs ?? []).map((input, index) => ({ ...input, name: input.name || `arg${index}` })) + })); +} + +function requireAddress(deployments, key) { + const address = deployments[key]; + if (typeof address !== 'string' || !/^0x[0-9a-fA-F]{40}$/.test(address)) { + throw new Error(`deployments.json has no valid "${key}" address. Deploy the contracts before generating the CDM manifest.`); + } + return address.toLowerCase(); +} + +const deployments = JSON.parse(readFileSync(deploymentsPath, 'utf8')); + +const contracts = {}; +const typeInputs = []; + +for (const { library, deployment, artifact } of FIXED_CONTRACTS) { + const abi = readArtifactAbi(artifact); + contracts[library] = { version: 1, address: requireAddress(deployments, deployment), abi }; + typeInputs.push({ library, abi: withNamedParams(abi) }); +} + +const runtimeAbi = mergeRuntimeAbi(RUNTIME_FACETS.map(artifact => ({ artifact, abi: readArtifactAbi(artifact) }))); +typeInputs.push({ library: RUNTIME_LIBRARY, abi: withNamedParams(runtimeAbi) }); + +mkdirSync(outDir, { recursive: true }); + +// The manifest deliberately omits the artist runtime: it has no build-time +// address, and inventing a placeholder one would misrepresent the deployment. +const manifest = { dependencies: Object.fromEntries(Object.keys(contracts).map(library => [library, 1])), contracts }; +writeFileSync(resolve(outDir, 'cdm.json'), `${JSON.stringify(manifest, null, 2)}\n`); + +writeFileSync( + resolve(outDir, 'smartRuntime.ts'), + `${HEADER}\n// Merged artist-runtime diamond facet ABI. Bound to a per-artist address at\n// call time via createContract(), so it carries no address of its own.\n// Source facets:\n${RUNTIME_FACETS.map(artifact => `// contracts/evm/artifacts/contracts/${artifact}`).join('\n')}\n\nexport const SMART_RUNTIME_LIBRARY = '${RUNTIME_LIBRARY}';\n\nexport const smartRuntimeAbi = ${JSON.stringify(runtimeAbi, null, 2)} as const;\n` +); + +writeFileSync(resolve(outDir, 'cdm.d.ts'), `${HEADER}\n\n${generateContractTypes(typeInputs)}`); + +console.log( + `Generated cdm.json (${Object.keys(contracts).length} fixed contracts), smartRuntime.ts (${runtimeAbi.length} merged entries), and cdm.d.ts into web/src/generated/contracts/` +); diff --git a/web/scripts/generate-cdm-metadata.mjs b/web/scripts/generate-cdm-metadata.mjs new file mode 100644 index 0000000..573de69 --- /dev/null +++ b/web/scripts/generate-cdm-metadata.mjs @@ -0,0 +1,135 @@ +// Build the CDM metadata blobs that `cdm install` fetches, and compute their CIDs. +// +// The registry stores `(name -> address)` and `(name -> metadata_uri)`. Without the +// second, another product can resolve where Dotify's contracts are but not what they +// expose, so `cdm install` fails and composability is nominal only. These blobs close +// that gap. +// +// Shape comes from CDM's own consumers, not guesswork: +// - install validates exactly one thing: `abi` must be a non-empty array +// (contract-dependency-manager `src/lib/contracts/src/install.ts`); +// - contracts.dot.li additionally renders `description`, `readme`, `homepage`, +// `repository`, `license`, `keywords`, `authors`, `dependencies`, `published_at` +// (`src/apps/frontend/src/data/registry-queries.ts`). +// Everything else is stored verbatim and ignored. +// +// Output is deterministic on purpose. A content-addressed blob whose bytes depend on +// wall-clock time gets a new CID on every run, which makes it impossible to check that +// what is published still matches the repository. `published_at` is therefore opt-in +// via --published-at; omitted by default so `npm run generate:cdm-metadata` twice gives +// byte-identical output and the same CIDs. +// +// Run: npm run generate:cdm-metadata +// Publishing the blobs to Bulletin needs a storage authorization and is a separate, +// credentialed step - see docs/operations/product-devnet-deployment.md. + +import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { calculateCid } from '@parity/product-sdk-cloud-storage'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(scriptDir, '../..'); +const artifactsRoot = resolve(repoRoot, 'contracts/evm/artifacts/contracts'); +const outDir = resolve(repoRoot, 'web/src/generated/contracts/cdm-metadata'); + +// Bulletin's raw codec + blake2b-256, matching @parity/bulletin-sdk defaults. +const CID_CODEC_RAW = 0x55; +const HASH_BLAKE2B_256 = 45600; + +const publishedAtArg = process.argv.indexOf('--published-at'); +const publishedAt = publishedAtArg !== -1 ? process.argv[publishedAtArg + 1] : null; + +const COMMON = { + homepage: 'https://muzinga.netlify.app', + repository: 'https://github.com/knzeng-e/dotify', + license: 'MIT', + authors: ['Dotify'], + keywords: ['music', 'dotify', 'artist-runtime', 'access-control'] +}; + +const PACKAGES = [ + { + name: '@dotify/artist-directory', + artifact: 'ArtistDirectory.sol/ArtistDirectory.json', + description: 'Registry mapping each artist address to their owned SmartRuntime. Entry point for enumerating the Dotify catalog.', + readme: [ + '# @dotify/artist-directory', + '', + 'Maps an artist address to the address of the SmartRuntime they own, and enumerates', + 'every registered artist.', + '', + 'Start here to read the Dotify catalog: `artistCount()` and `artistsPage(offset, limit)`', + 'enumerate artists with their runtimes, and `runtimeOf(artist)` resolves one directly.', + 'Each runtime then exposes its own tracks and access policy.', + '', + 'Registration is performed by the artist runtime factory, not by callers.' + ].join('\n') + }, + { + name: '@dotify/artist-runtime-factory', + artifact: 'ArtistRuntimeFactory.sol/ArtistRuntimeFactory.json', + description: 'Deploys one artist-owned SmartRuntime per artist and registers it in the artist directory.', + readme: [ + '# @dotify/artist-runtime-factory', + '', + 'Deploys a SmartRuntime for an artist and registers it in `@dotify/artist-directory`.', + '', + 'A runtime is a diamond: music registry, royalties, access, and NFT pallets are', + 'installed as facets, and the artist is set as its owner. Because each artist owns', + 'their own runtime, catalog, access policy, and royalty splits stay under the', + "artist's control rather than the platform's.", + '', + 'Creation is staged - `createRuntime()` then `installRuntimeStep()` until', + '`pendingRuntimeStageOf(artist)` reports completion - so that installation fits', + 'within block limits.' + ].join('\n') + } +]; + +function readAbi(artifact) { + const artifactPath = resolve(artifactsRoot, artifact); + let parsed; + try { + parsed = JSON.parse(readFileSync(artifactPath, 'utf8')); + } catch { + throw new Error(`Missing artifact ${artifact}. Run "cd contracts/evm && npm run compile" first.`); + } + if (!Array.isArray(parsed.abi) || parsed.abi.length === 0) { + // install.ts rejects an empty ABI, so fail here rather than publish a blob that + // every consumer will reject after it is already immutable on Bulletin. + throw new Error(`Artifact ${artifact} has no usable abi array.`); + } + return parsed.abi; +} + +mkdirSync(outDir, { recursive: true }); + +const index = {}; + +for (const pkg of PACKAGES) { + const metadata = { + name: pkg.name, + description: pkg.description, + readme: pkg.readme, + abi: readAbi(pkg.artifact), + ...COMMON, + ...(publishedAt ? { published_at: publishedAt } : {}) + }; + + // Two spaces, trailing newline: the bytes are the identity, so the formatting is + // part of the contract with the CID and must not drift. + const bytes = new TextEncoder().encode(`${JSON.stringify(metadata, null, 2)}\n`); + const cid = (await calculateCid(bytes, CID_CODEC_RAW, HASH_BLAKE2B_256)).toString(); + + const fileName = `${pkg.name.replace(/^@/, '').replace(/\//g, '-')}.json`; + writeFileSync(resolve(outDir, fileName), bytes); + + index[pkg.name] = { file: fileName, cid, bytes: bytes.length }; + console.log(`${pkg.name}\n file: ${fileName}\n size: ${bytes.length} bytes\n cid: ${cid}\n`); +} + +writeFileSync(resolve(outDir, 'cids.json'), `${JSON.stringify(index, null, 2)}\n`); + +console.log(`Wrote ${PACKAGES.length} metadata blobs + cids.json into web/src/generated/contracts/cdm-metadata/`); +console.log('CIDs are computed locally and are deterministic; publishing the blobs to Bulletin is a separate credentialed step.'); diff --git a/web/scripts/upload-cdm-metadata.mjs b/web/scripts/upload-cdm-metadata.mjs new file mode 100644 index 0000000..3efd684 --- /dev/null +++ b/web/scripts/upload-cdm-metadata.mjs @@ -0,0 +1,179 @@ +// Upload the generated CDM metadata blobs to the Bulletin Chain. +// +// Why not the Product SDK: `CloudStorageClient.create()` resolves its chain connection +// through `getChainAPI`/`createChainClient`, which route exclusively through the Product +// host container and have no direct-WebSocket fallback. Uploading from a terminal is +// therefore impossible through that path. The Bulletin chain itself accepts a plain +// signed `TransactionStorage.store` extrinsic over WebSocket, which is what this uses - +// the same approach as the existing `deploy-bulletin.cjs`. +// +// Read-only by default: it checks the account's authorization and reports what it would +// upload. Uploading needs --confirm. +// +// Run: +// npm run upload:cdm-metadata # dry run, dev account +// BULLETIN_SURI='//Alice' npm run upload:cdm-metadata -- --confirm +// BULLETIN_MNEMONIC='...' npm run upload:cdm-metadata -- --confirm + +import { readFileSync, readdirSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createClient, Binary, Enum } from 'polkadot-api'; +import { withPolkadotSdkCompat } from 'polkadot-api/polkadot-sdk-compat'; +import { getWsProvider } from 'polkadot-api/ws-provider/node'; +import { bulletin } from '@polkadot-api/descriptors'; +import { getPolkadotSigner } from 'polkadot-api/signer'; +import { sr25519CreateDerive } from '@polkadot-labs/hdkd'; +import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy, ss58Address } from '@polkadot-labs/hdkd-helpers'; +import { calculateCid } from '@parity/product-sdk-cloud-storage'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const metadataDir = resolve(scriptDir, '../src/generated/contracts/cdm-metadata'); + +// Product DevNet Bulletin (Paseo Bulletin, para 1010). +const BULLETIN_WS = process.env.VITE_BULLETIN_WS_URL || 'wss://bulletin-paseo.tservices.es:8443'; +const UPLOAD_TIMEOUT_MS = 180_000; + +const CID_CODEC_RAW = 0x55; +const HASH_BLAKE2B_256 = 45600; + +const confirm = process.argv.includes('--confirm'); + +function loadSigner() { + const mnemonic = process.env.BULLETIN_MNEMONIC; + const suri = process.env.BULLETIN_SURI || '//Alice'; + const phrase = mnemonic || DEV_PHRASE; + const derive = sr25519CreateDerive(entropyToMiniSecret(mnemonicToEntropy(phrase))); + const keypair = derive(mnemonic ? '' : suri); + return { + address: ss58Address(keypair.publicKey), + signer: getPolkadotSigner(keypair.publicKey, 'Sr25519', keypair.sign), + source: mnemonic ? 'BULLETIN_MNEMONIC' : `dev account ${suri}` + }; +} + +function loadBlobs() { + const index = JSON.parse(readFileSync(resolve(metadataDir, 'cids.json'), 'utf8')); + const files = readdirSync(metadataDir).filter(name => name.endsWith('.json') && name !== 'cids.json'); + + return files.map(file => { + const bytes = new Uint8Array(readFileSync(resolve(metadataDir, file))); + const entry = Object.entries(index).find(([, value]) => value.file === file); + if (!entry) throw new Error(`${file} is not listed in cids.json. Re-run npm run generate:cdm-metadata.`); + return { name: entry[0], file, bytes, expectedCid: entry[1].cid }; + }); +} + +async function main() { + const blobs = loadBlobs(); + const { address, signer, source } = loadSigner(); + + // Recompute every CID from the bytes on disk. A blob edited after generation would + // otherwise be uploaded under a CID the registry no longer matches, and the registry + // entry is immutable once published. + let drifted = false; + for (const blob of blobs) { + const actual = (await calculateCid(blob.bytes, CID_CODEC_RAW, HASH_BLAKE2B_256)).toString(); + blob.actualCid = actual; + if (actual !== blob.expectedCid) drifted = true; + } + + console.log(`\nBulletin: ${BULLETIN_WS}`); + console.log(`Account: ${address} (${source})`); + console.log(`Blobs: ${blobs.length}\n`); + + for (const blob of blobs) { + const ok = blob.actualCid === blob.expectedCid; + console.log(`${blob.name}`); + console.log(` file: ${blob.file} (${blob.bytes.length} bytes)`); + console.log(` cid: ${blob.actualCid}${ok ? '' : ` MISMATCH — cids.json says ${blob.expectedCid}`}`); + console.log(''); + } + + if (drifted) { + throw new Error('A blob no longer matches its recorded CID. Re-run npm run generate:cdm-metadata before uploading.'); + } + + const totalBytes = blobs.reduce((sum, blob) => sum + blob.bytes.length, 0); + const client = createClient(withPolkadotSdkCompat(getWsProvider(BULLETIN_WS))); + + try { + const api = client.getTypedApi(bulletin); + + let authorized = false; + let detail = 'no authorization found'; + try { + const auth = await api.query.TransactionStorage.Authorizations.getValue(Enum('Account', address)); + if (auth) { + const haveTx = BigInt(auth.extent.transactions ?? 0n); + const haveBytes = BigInt(auth.extent.bytes ?? 0n); + authorized = haveTx >= BigInt(blobs.length) && haveBytes >= BigInt(totalBytes); + detail = `${haveTx} transactions / ${haveBytes} bytes remaining; need ${blobs.length} / ${totalBytes}`; + } + } catch (error) { + detail = `authorization query failed: ${error.message}`; + } + + console.log(`Authorization: ${authorized ? 'OK' : 'INSUFFICIENT'} — ${detail}\n`); + + if (!authorized) { + throw new Error( + `${address} cannot store ${totalBytes} bytes on Bulletin.\n` + + 'Grant a quota first, then re-run:\n' + + ' dotns bulletin authorize ' + + address + + ' --transactions 1000 --bytes 104857600 --env devnet\n' + + ' or use the Bulletin console: https://paritytech.github.io/polkadot-bulletin-chain/ (Products Devnet)' + ); + } + + if (!confirm) { + console.log('Dry run. Re-run with --confirm to upload.'); + console.log( + 'After uploading, register the names with: cd contracts/evm && npx hardhat cdm:publish --network polkadotTestnet --confirm --private-key ' + ); + return; + } + + for (const blob of blobs) { + process.stdout.write(`Uploading ${blob.name} … `); + const tx = api.tx.TransactionStorage.store({ data: Binary.fromBytes(blob.bytes) }); + + await new Promise((resolvePromise, rejectPromise) => { + const timer = setTimeout(() => { + subscription.unsubscribe(); + rejectPromise(new Error(`upload of ${blob.name} timed out after ${UPLOAD_TIMEOUT_MS / 1000}s`)); + }, UPLOAD_TIMEOUT_MS); + + const subscription = tx.signSubmitAndWatch(signer).subscribe({ + next: event => { + if (event.type === 'txBestBlocksState' && event.found) { + clearTimeout(timer); + subscription.unsubscribe(); + if (event.ok === false) { + rejectPromise(new Error(`${blob.name} rejected on chain: ${JSON.stringify(event.dispatchError ?? 'unknown')}`)); + return; + } + resolvePromise(); + } + }, + error: error => { + clearTimeout(timer); + rejectPromise(error); + } + }); + }); + + console.log(`stored as ${blob.actualCid}`); + } + + console.log('\nAll blobs uploaded. The CIDs above are what cdm:publish will register.'); + } finally { + client.destroy(); + } +} + +main().catch(error => { + console.error(`\n${error.message}`); + process.exit(1); +}); diff --git a/web/src/app/providers/SessionProvider.tsx b/web/src/app/providers/SessionProvider.tsx index 49c01fa..968b4ca 100644 --- a/web/src/app/providers/SessionProvider.tsx +++ b/web/src/app/providers/SessionProvider.tsx @@ -13,19 +13,21 @@ import { useNavigation } from './NavigationProvider'; import { useCatalogContext } from './CatalogProvider'; const signalUrl = import.meta.env.VITE_SIGNAL_URL ?? `${window.location.protocol}//${window.location.hostname}:8788`; +const publicAppUrl = import.meta.env.VITE_PUBLIC_APP_URL?.trim() || null; type SessionValue = ReturnType; const SessionContext = createContext(null); export function SessionProvider({ children }: { children: ReactNode }) { - const { listenerEvmAddress } = useWalletContext(); + const { activeIdentityAddress } = useWalletContext(); const { navigateToView } = useNavigation(); const catalog = useCatalogContext(); const session = useSession({ signalUrl, - identityAddress: listenerEvmAddress, + publicAppUrl, + identityAddress: activeIdentityAddress, audioSource: catalog.audioSource, trackInfo: catalog.trackInfo, setTrackInfo: catalog.setTrackInfo, @@ -50,7 +52,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { useEffect(() => { const initialRoomCode = getInitialRoomCode(); if (!initialRoomCode || session.roomId) return; - const remembered = getStoredDisplayName(listenerEvmAddress); + const remembered = getStoredDisplayName(activeIdentityAddress); if (!remembered) return; session.setDisplayName(remembered); session.joinRoom(initialRoomCode, { displayName: remembered }); @@ -68,11 +70,11 @@ export function SessionProvider({ children }: { children: ReactNode }) { // write a partial name to storage on every keystroke. const setDisplayName = session.setDisplayName; useEffect(() => { - const stored = getStoredDisplayName(listenerEvmAddress); + const stored = getStoredDisplayName(activeIdentityAddress); if (stored) setDisplayName(stored); // Re-run only when the connected address changes. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [listenerEvmAddress]); + }, [activeIdentityAddress]); return {children}; } diff --git a/web/src/app/providers/WalletProvider.tsx b/web/src/app/providers/WalletProvider.tsx index aef0e7d..5619848 100644 --- a/web/src/app/providers/WalletProvider.tsx +++ b/web/src/app/providers/WalletProvider.tsx @@ -18,6 +18,7 @@ import { devAccounts, type DevAccount } from '../../hooks/useDevAccounts'; import { getDefaultEthRpcUrl } from '../../shared/config/network'; import { resolveEvmChain, getWalletClient } from '../../shared/config/contracts'; import { chainMismatchMessage } from '../../features/wallet/network'; +import type { ProductHostMode, ProductHostStatus } from '../../features/productHost/productHost'; import { useUiFeedback } from './UiFeedbackProvider'; type WalletContextValue = { @@ -25,6 +26,7 @@ type WalletContextValue = { connectedWallet: ConnectedWallet | null; activeEvmAddress: `0x${string}`; listenerEvmAddress: `0x${string}` | null; + activeIdentityAddress: string | null; activeSubstrateAddress: string | null; activeSubstrateSigner: PolkadotSigner | null; currentBulletinAccount: DevAccount; @@ -37,25 +39,35 @@ type WalletContextValue = { switchNetwork: () => Promise; connectPasskey: () => Promise; connectExtension: () => Promise; + connectProductHost: () => Promise; disconnect: () => void; forgetPasskey: () => void; hasPrfSupport: boolean; hasStoredPasskey: boolean; + productHostMode: ProductHostMode; + productHostStatus: ProductHostStatus; }; const WalletContext = createContext(null); +function canRequestProtectedPlayback(wallet: ConnectedWallet | null): boolean { + return Boolean(wallet?.createEvmClient || wallet?.keyRequestSigner); +} + export function WalletProvider({ children }: { children: ReactNode }) { const { setTransactionFeedback, setShowWalletModal } = useUiFeedback(); const { state: walletState, connectPasskey, connectExtension, + connectProductHost, switchExtensionNetwork, disconnect: disconnectWalletOnly, hasPrfSupport, hasStoredPasskey, - forgetPasskey + forgetPasskey, + productHostMode, + productHostStatus } = useWallet(); const [ethRpcUrl] = useState(getDefaultEthRpcUrl); @@ -68,7 +80,7 @@ export function WalletProvider({ children }: { children: ReactNode }) { // Disconnecting the wallet also signs out of the Dotify session (ticket 24 // P2): revoke the server-side token and forget the stored one, so a shared // machine does not keep listening rights after the wallet leaves. - const connectedAddress = connectedWallet?.evmAddress; + const connectedAddress = canRequestProtectedPlayback(connectedWallet) ? connectedWallet?.evmAddress : undefined; const lastConnectedAddressRef = useRef<`0x${string}` | null>(null); const disconnect = useCallback(() => { if (connectedAddress) void signOutOfDotifySession(connectedAddress); @@ -88,7 +100,10 @@ export function WalletProvider({ children }: { children: ReactNode }) { const currentBulletinAccount = devAccounts[bulletinAccountIndex]; const activeEvmAddress = connectedWallet?.evmAddress ?? zeroAddress; - const listenerEvmAddress = connectedWallet?.evmAddress ?? null; + const listenerEvmAddress = canRequestProtectedPlayback(connectedWallet) ? (connectedWallet?.evmAddress ?? null) : null; + // Local room-name persistence lowercases its key, so use the H160 identity + // for both EVM wallets and Product accounts rather than case-sensitive SS58. + const activeIdentityAddress = connectedWallet?.evmAddress ?? null; const devBulletinFallback = import.meta.env.DEV ? currentBulletinAccount : null; const activeSubstrateAddress = connectedWallet ? (connectedWallet.substrateAddress ?? null) : (devBulletinFallback?.address ?? null); const activeSubstrateSigner = connectedWallet ? (connectedWallet.substrateSigner ?? null) : (devBulletinFallback?.signer ?? null); @@ -97,6 +112,9 @@ export function WalletProvider({ children }: { children: ReactNode }) { if (!connectedWallet) { throw new Error('Connect a wallet before signing this transaction.'); } + if (!connectedWallet.createEvmClient) { + throw new Error('This action still requires a passkey or EVM wallet while Dotify contracts are being ported to the Product DevNet host signer.'); + } const chain = await resolveEvmChain(ethRpcUrl); if (connectedWallet.chainId !== undefined && connectedWallet.chainId !== chain.id) { throw new Error(chainMismatchMessage(chain.id, connectedWallet.chainId)); @@ -166,6 +184,7 @@ export function WalletProvider({ children }: { children: ReactNode }) { connectedWallet, activeEvmAddress, listenerEvmAddress, + activeIdentityAddress, activeSubstrateAddress, activeSubstrateSigner, currentBulletinAccount, @@ -178,16 +197,20 @@ export function WalletProvider({ children }: { children: ReactNode }) { switchNetwork, connectPasskey, connectExtension, + connectProductHost, disconnect, forgetPasskey, hasPrfSupport, - hasStoredPasskey + hasStoredPasskey, + productHostMode, + productHostStatus }), [ walletState, connectedWallet, activeEvmAddress, listenerEvmAddress, + activeIdentityAddress, activeSubstrateAddress, activeSubstrateSigner, currentBulletinAccount, @@ -199,10 +222,13 @@ export function WalletProvider({ children }: { children: ReactNode }) { switchNetwork, connectPasskey, connectExtension, + connectProductHost, disconnect, forgetPasskey, hasPrfSupport, - hasStoredPasskey + hasStoredPasskey, + productHostMode, + productHostStatus ] ); diff --git a/web/src/components/WalletModal.tsx b/web/src/components/WalletModal.tsx index 989d2e1..58416bc 100644 --- a/web/src/components/WalletModal.tsx +++ b/web/src/components/WalletModal.tsx @@ -1,4 +1,4 @@ -import { ExternalLink, KeyRound, LockKeyhole, Music2, Power, RefreshCw, Users, Wallet, X } from 'lucide-react'; +import { Box, ExternalLink, KeyRound, LockKeyhole, Music2, Power, RefreshCw, Users, Wallet, X } from 'lucide-react'; import { Dialog } from './Dialog'; import type { WalletState } from '../hooks/useWallet'; import type { CatalogTrack } from '../shared/types'; @@ -58,6 +58,9 @@ export function WalletModal({ isSwitchingNetwork, connectPasskey, connectExtension, + connectProductHost, + productHostMode, + productHostStatus, switchNetwork, forgetPasskey: onForgetPasskey, disconnect: onDisconnect @@ -69,6 +72,7 @@ export function WalletModal({ const onClose = () => setShowWalletModal(false); const onPasskey = () => void connectPasskey(); const onExtension = () => void connectExtension(); + const onProductHost = () => void connectProductHost(); const onSwitchNetwork = () => void switchNetwork(); if (state.status === 'connected') { @@ -95,7 +99,7 @@ export function WalletModal({
    {wallet.label} - {wallet.evmAddress ? ( + {wallet.method !== 'product-host' ? ( {shortenAddress(identityAddress)} @@ -108,7 +112,15 @@ export function WalletModal({
    Connection - {walletChainMismatch ? 'Needs attention' : wallet.method === 'passkey' ? 'This device' : 'Wallet app'} + + {walletChainMismatch + ? 'Needs attention' + : wallet.method === 'passkey' + ? 'This device' + : wallet.method === 'product-host' + ? 'Product host' + : 'Wallet app'} + {walletChainMismatch && Choose the right network to continue} {walletChainMismatch && wallet.method === 'extension' && onSwitchNetwork && (
    + {wallet.method === 'product-host' && ( + <> +

    + Your app-scoped Polkadot identity is active for presence, rooms, and protected playback. If the host signature is rejected, protected playback + fails closed - add a passkey or EVM wallet below. Paying for access and artist publishing still require an EVM signer during the contract port. +

    +
    + {hasPrfSupport && ( + + )} + +
    + + )} +
    {unlockedCount} @@ -254,15 +297,39 @@ export function WalletModal({ {state.status === 'error' &&

    {state.message}

    } {state.status === 'connecting' && ( -

    {state.via === 'passkey' ? 'Check your browser prompt to continue.' : 'Check your wallet to approve the connection.'}

    +

    + {state.via === 'passkey' + ? 'Check your browser prompt to continue.' + : state.via === 'product-host' + ? 'Check the Polkadot Product host to continue.' + : 'Check your wallet to approve the connection.'} +

    )} {state.status === 'needs-reconnect' && state.via === 'passkey' && (

    Your saved passkey is ready. Use passkey to reconnect when you are ready.

    )}
    + {productHostMode !== 'off' && ( + + )} + {hasPrfSupport && ( -