diff --git a/CHANGELOG.md b/CHANGELOG.md index 80410e9..a52752a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.12.0 + +### Breaking changes + +- **Upstream `@novasamatech/*` → `^0.9.1`; the wire is incompatible with `0.8.x`.** A product on `@novasamatech/host-api-wrapper@^0.8.x` will no longer talk to this test host, and vice versa. Upgrade both sides in lockstep, the same way 0.9.0 required. The break is RFC-0022: `DerivationIndex` — the selector identifying an account inside a product's subtree — changed from a bare `u32` to `Enum{Index(u32), Raw([u8; 32])}`, one extra tag byte on every request carrying a product account. +- **`ProductAccount` no longer carries `name`.** `handleAccountGet` used to return `{ publicKey, name }`, but the protocol struct only has `publicKey`, so the encoder dropped the name and it was never on the wire. The field is gone from the response; `productAccounts: { "myapp.dot/0": { name, uri } }` still accepts `name`, it just documents the mapping rather than reaching the product. +- **`handleAccountGetAlias` now rejects with `GetAliasErr`, not `RequestCredentialsErr`.** Upstream split the alias errors out into their own enum, with `RingNotFound` / `NotMember` / `Rejected` / `Unknown`. An unsigned host (`accounts: []`) now answers unmapped alias requests with `GetAliasErr.Unknown`. + +### Changed + +- **`@parity/truapi` → `^0.6.0`** for the truapi-product integration coverage, matching what [product-sdk 0.20.0](https://github.com/paritytech/product-sdk/pull/271) ships. truapi 0.6 encodes the selector tag as `Left`/`Right` where `@novasamatech/host-api` decodes it as `Index`/`Raw` — different names, identical SCALE wire, so both connect to the same container. +- **Product-account resolution accepts either selector form.** `Index(n)` resolves exactly as the plain `n` did before, so `productAccounts` keys (`"myapp.dot/0"`) and derived addresses (`//Bob//myapp.dot/0`) are **unchanged** — no test that asserts an address needs updating. `Raw(bytes)`, previously inexpressible, is keyed and derived by its hex. Products calling through `host-api-wrapper` need no code change either: it takes the ergonomic `AccountSelector` (`number | Uint8Array`) and normalises internally. Only code that builds protocol requests by hand has to wrap its index in `derivationIndexOf()`. +- **`handleAccountGetAlias` request shape.** Now `[ProductProofContext, RingLocation]` instead of a bare `ProductAccountId`. The context is `[productId, suffix]` — structurally the same as a product account id, and the identity mapping on it — so the account a request resolves to is unchanged. `RingLocation` also changed (`{ genesisHash, ringRootHash, hints }` → `{ chainId, junctions }`); this host ignores it. +- **`handleAccountCreateProof` response shape.** Now `{ proof, contextualAlias: { context, alias }, ringIndex, ringRevision }` instead of bare proof bytes. It remains a deterministic stand-in — an sr25519 signature over the message rather than a real ring VRF, as before — with `contextualAlias` matching what `handleAccountGetAlias` returns for the same account, and `ringIndex` / `ringRevision` fixed at `0`. +- **`AllocatableResource.SmartContractAllowance` carries a selector**, so `requestResourceAllocation([{ tag: 'SmartContractAllowance', value: 0 }])` becomes `value: { tag: 'Index', value: 0 }`. + ## 0.11.0 ### Added diff --git a/README.md b/README.md index 3ce16df..cf3a734 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Lightweight test host for E2E testing embedded Polkadot dapps that use the Spektr host-container protocol (`@novasamatech/host-container`). -> **Upstream contract:** `0.9.x` tracks `@novasamatech/host-api`, `host-container`, and `host-api-wrapper` at `^0.8.0`. v0.8 is wire-incompatible with v0.7 — your product side must be on the same major as the test host. +> **Upstream contract:** `0.12.x` tracks `@novasamatech/host-api`, `host-container`, and `host-api-wrapper` at `^0.9.1`, and serves products on `@parity/truapi` `^0.6.0`. v0.9 is wire-incompatible with v0.8 (RFC-0022 changed the product-account selector on the wire) — your product side must be on the same minor as the test host. ## Why @@ -277,7 +277,9 @@ createTestHostFixture({ > }); > ``` > -> Unmapped identities fall back to production-style derivation (`//Bob//dotnsId/index`). If `accounts: []` (unsigned host), unmapped `getProductAccount` / `getProductAccountAlias` calls return `err(RequestCredentialsErr.NotConnected)`, matching `polkadot-desktop`. Pre-mapped entries in `productAccounts` are still served. +> Unmapped identities fall back to production-style derivation (`//Bob//dotnsId/index`). If `accounts: []` (unsigned host), unmapped `getProductAccount` calls return `err(RequestCredentialsErr.NotConnected)` and `getProductAccountAlias` returns `err(GetAliasErr.Unknown)`, matching `polkadot-desktop`. Pre-mapped entries in `productAccounts` are still served. +> +> **Selector keys**: since RFC-0022 a product addresses its accounts with `Index(n)` or `Raw(32 bytes)` rather than a bare number. `productAccounts` keys are unchanged for plain indices — `"myapp.dot/0"` still means `Index(0)`, and the derived address is the same as before. A raw selector is keyed by its hex, e.g. `"myapp.dot/0x1234…"`. ### Payment control diff --git a/forum-post.md b/forum-post.md index 314aa6d..d291a34 100644 --- a/forum-post.md +++ b/forum-post.md @@ -702,3 +702,50 @@ permission model. 1. Upgrade to `0.11.0`. 2. Nothing else — both product generations connect without configuration. + +--- + +# host-api-test-sdk 0.12.0 + +Tracks upstream `@novasamatech/*@^0.9.1` ([triangle-js-sdks#239](https://github.com/paritytech/triangle-js-sdks/pull/239)) and serves products on `@parity/truapi@^0.6.0`. v0.9 is **wire-incompatible** with v0.8: RFC-0022 changes `DerivationIndex` from a bare `u32` to `Enum{Index(u32), Raw([u8; 32])}`, so your product side has to move in the same commit. + +Against `0.11.0` a truapi-0.6 product fails quietly: the wrong account for any index other than `0`, `signRaw` over a different payload than the one requested, and `createTransaction` dropped without a reply. `product-sdk`'s E2E suite goes from 2 failed to 54 passed on this release. + +## What changed on our side + +### Product accounts take a selector, not a number (breaking) + +`Index(n)` resolves exactly as the plain `n` did, so `productAccounts` keys and derived addresses are unchanged. Raw 32-byte selectors are new, keyed by hex (`"myapp.dot/0x1234…"`). The wrapper takes `number | Uint8Array` and normalises for you; hand-built requests wrap the index: + +```diff +-hostApi.signRaw(enumValue("v1", { account: [dotnsId, 0], payload })); ++hostApi.signRaw(enumValue("v1", { account: [dotnsId, derivationIndexOf(0)], payload })); +``` + +### `accountCreateProof` returns a struct (breaking) + +```diff +-const proofHex = u8aToHex(result.value); ++const proofHex = u8aToHex(result.value.proof); +``` + +Plus `contextualAlias`, `ringIndex`, `ringRevision`. + +### `accountGetAlias` rejects with `GetAliasErr` (breaking) + +Alias errors moved out of `RequestCredentialsErr` into their own enum (`RingNotFound` / `NotMember` / `Rejected` / `Unknown`). + +### `SmartContractAllowance` carries a selector (breaking) + +```diff +-{ tag: "SmartContractAllowance", value: 0 } ++{ tag: "SmartContractAllowance", value: { tag: "Index", value: 0 } } +``` + +`handleAccountSignVrf` (RFC-0023, new upstream) is not implemented — the container answers `SignVrfErr.Unknown`. + +## What you need to do + +1. Upgrade to `0.12.0` and move your product side to `@parity/truapi@^0.6.0` or `@novasamatech/host-api-wrapper@^0.9.1` in the same commit. +2. Through the wrapper, nothing else. Hand-built requests: wrap indices in `derivationIndexOf()` and adjust the three shapes above. +3. If your fixtures still pass `chain:`, see 0.10.0 — it is silently ignored, so any `rpcUrl` override you set is being dropped. diff --git a/package.json b/package.json index 5deb8cf..73e7558 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@parity/host-api-test-sdk", - "version": "0.11.0", + "version": "0.12.0", "description": "Lightweight test host for Spektr product E2E testing — embeds dapps with auto-signing dev accounts, no Docker needed", "license": "MIT", "repository": { @@ -42,12 +42,12 @@ } }, "dependencies": { - "@novasamatech/host-api": "^0.8.8" + "@novasamatech/host-api": "^0.9.1" }, "devDependencies": { - "@novasamatech/host-api-wrapper": "^0.8.8", - "@novasamatech/host-container": "^0.8.8", - "@parity/truapi": "^0.4.1", + "@novasamatech/host-api-wrapper": "^0.9.1", + "@novasamatech/host-container": "^0.9.1", + "@parity/truapi": "^0.6.0", "@polkadot/keyring": "^14.0.0", "@polkadot/types": "^16.0.0", "@polkadot/util": "^14.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cda34bb..64dc68b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,21 +9,21 @@ importers: .: dependencies: '@novasamatech/host-api': - specifier: ^0.8.8 - version: 0.8.8 + specifier: ^0.9.1 + version: 0.9.1 '@playwright/test': specifier: '>=1.0.0' version: 1.58.2 devDependencies: '@novasamatech/host-api-wrapper': - specifier: ^0.8.8 - version: 0.8.8(@polkadot/api@16.5.6)(@polkadot/util@14.0.1)(esbuild@0.25.12)(rxjs@7.8.2) + specifier: ^0.9.1 + version: 0.9.1(@polkadot/api@16.5.6)(@polkadot/util@14.0.1)(esbuild@0.25.12)(rxjs@7.8.2) '@novasamatech/host-container': - specifier: ^0.8.8 - version: 0.8.8(esbuild@0.25.12)(rxjs@7.8.2) + specifier: ^0.9.1 + version: 0.9.1(esbuild@0.25.12)(rxjs@7.8.2) '@parity/truapi': - specifier: ^0.4.1 - version: 0.4.1 + specifier: ^0.6.0 + version: 0.6.0 '@polkadot/keyring': specifier: ^14.0.0 version: 14.0.1(@polkadot/util-crypto@14.0.1(@polkadot/util@14.0.1))(@polkadot/util@14.0.1) @@ -235,20 +235,20 @@ packages: resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} engines: {node: '>= 20.19.0'} - '@novasamatech/host-api-wrapper@0.8.8': - resolution: {integrity: sha512-/ym2kK9pPubq3iyJettReNb6R4YnfJwDbcdz4Hlxz7uVKu3+XxjqjLgEGb6klPcC+N+0f6bmFB3JSUo6QXHTzw==} + '@novasamatech/host-api-wrapper@0.9.1': + resolution: {integrity: sha512-eKQyQf0wcwsjDba/NOwJm0JEj7wE5AIyRPG0vE2Olm/sSiRpe1XtcMH3LiiYxhartGh6qkro11A4ALiykJO2YA==} - '@novasamatech/host-api@0.8.8': - resolution: {integrity: sha512-dTO5tQGJnyy6SOZaR1Z6+yzH2CmJp8wcZScVF1HHqqeMe1uVWcioyTH6jf9Rw46U01uLaa5unembuNmJIOC2ew==} + '@novasamatech/host-api@0.9.1': + resolution: {integrity: sha512-G5Gu9wIc+VS6RfM+OMPv50XfS16uhC6Mwvb5CVArs+EVTRHUE3I4H4xCQDqxV8JcJ+tl5OtGOSrL4dyZs0xYmA==} - '@novasamatech/host-container@0.8.8': - resolution: {integrity: sha512-Caof2YgqJlr96r5mBFNkYQ7Gpfi6pytJdxj7Zqq1o2np8Gc3LzWz6DHLxmQvx64abu9rqR3m5sapCI8lUZrUSg==} + '@novasamatech/host-container@0.9.1': + resolution: {integrity: sha512-OEErnw9iU6AOaz1SDnqfn22Fag87VNVPxPyMPX4jqkV/b51rj+y4XMGmRaV/VFeVLMM7NnF+KuTj6NMZhX/wAw==} - '@novasamatech/scale@0.8.8': - resolution: {integrity: sha512-cWg4RkrUoysc+q7zQnr2vC+JmcuRLZFft3ZITkq1U6d7wDAYGJjzTYGKLDxIe2HZ+mRhU7otFCz1aaFtBEu98w==} + '@novasamatech/scale@0.9.1': + resolution: {integrity: sha512-NAypDvPGY5fsv224URVzLzcszBrg+jv65t9NXeD7t/bE/SUZ2o2d9axl6LAGH/cIyBKj3Js4c2FVj4KwGUz+yg==} - '@parity/truapi@0.4.1': - resolution: {integrity: sha512-q050QvYUOBEJsGBOvmJn81G0c7ht5+8zm7IyWPa/PtuQEMiCaf7f/CwqH3Dv0hESm/rBgQ1hpF7Pt4ZtSrs0aA==} + '@parity/truapi@0.6.0': + resolution: {integrity: sha512-fhjnlyuZbS4QSYD+G037APq23/YpkkkAmkYUj4F7EXxhl5G3X5cGh1ieBRkPPHOkKIUjoDiQPbJRQgqiSbqbGQ==} '@playwright/test@1.58.2': resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} @@ -943,13 +943,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoevents@9.1.0: - resolution: {integrity: sha512-Jd0fILWG44a9luj8v5kED4WI+zfkkgwKyRQKItTtlPfEsh7Lznfi1kr8/iZ+XAIss4Qq5GqRB0qtWbaz9ceO/A==} - engines: {node: ^18.0.0 || >=20.0.0} + nanoevents@10.0.0: + resolution: {integrity: sha512-PmIJ3BNxOzgVgnS1r/Pvyj6eZx/xUV2JCPogh0brD6smDIwjRlr1KBl9yoRX323PlfaNrBDDnfCgC9SL00OjSg==} + engines: {node: ^22.0.0 || ^24.0.0 || >=26.0.0} - nanoid@5.1.11: - resolution: {integrity: sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==} - engines: {node: ^18 || >=20} + nanoid@6.0.0: + resolution: {integrity: sha512-mkUH+rPkwU2qPadJ0oJZOjeZ5Mxn8Q1UhevwkTRWNuUZzyia3h4rhzK39hxaHTk0o2OxB8W2SQ6A8k23ZDi1pQ==} + engines: {node: ^22 || ^24 || >=26} hasBin: true neverthrow@8.2.0: @@ -1320,9 +1320,9 @@ snapshots: '@noble/hashes@2.2.0': {} - '@novasamatech/host-api-wrapper@0.8.8(@polkadot/api@16.5.6)(@polkadot/util@14.0.1)(esbuild@0.25.12)(rxjs@7.8.2)': + '@novasamatech/host-api-wrapper@0.9.1(@polkadot/api@16.5.6)(@polkadot/util@14.0.1)(esbuild@0.25.12)(rxjs@7.8.2)': dependencies: - '@novasamatech/host-api': 0.8.8 + '@novasamatech/host-api': 0.9.1 '@polkadot-api/json-rpc-provider-proxy': 0.4.0 '@polkadot-api/substrate-bindings': 0.20.3 '@polkadot/extension-inject': 0.63.1(@polkadot/api@16.5.6)(@polkadot/util@14.0.1) @@ -1337,21 +1337,21 @@ snapshots: - supports-color - utf-8-validate - '@novasamatech/host-api@0.8.8': + '@novasamatech/host-api@0.9.1': dependencies: - '@novasamatech/scale': 0.8.8 - nanoevents: 9.1.0 - nanoid: 5.1.11 + '@novasamatech/scale': 0.9.1 + nanoevents: 10.0.0 + nanoid: 6.0.0 neverthrow: 8.2.0 scale-ts: 1.6.1 - '@novasamatech/host-container@0.8.8(esbuild@0.25.12)(rxjs@7.8.2)': + '@novasamatech/host-container@0.9.1(esbuild@0.25.12)(rxjs@7.8.2)': dependencies: '@noble/hashes': 2.2.0 - '@novasamatech/host-api': 0.8.8 + '@novasamatech/host-api': 0.9.1 '@polkadot-api/substrate-client': 0.7.0 - nanoevents: 9.1.0 - nanoid: 5.1.11 + nanoevents: 10.0.0 + nanoid: 6.0.0 neverthrow: 8.2.0 polkadot-api: 2.0.2(esbuild@0.25.12)(rxjs@7.8.2) transitivePeerDependencies: @@ -1361,12 +1361,12 @@ snapshots: - supports-color - utf-8-validate - '@novasamatech/scale@0.8.8': + '@novasamatech/scale@0.9.1': dependencies: '@polkadot-api/utils': 0.4.0 scale-ts: 1.6.1 - '@parity/truapi@0.4.1': + '@parity/truapi@0.6.0': dependencies: '@noble/hashes': 2.2.0 neverthrow: 8.2.0 @@ -2363,9 +2363,9 @@ snapshots: ms@2.1.3: {} - nanoevents@9.1.0: {} + nanoevents@10.0.0: {} - nanoid@5.1.11: {} + nanoid@6.0.0: {} neverthrow@8.2.0: optionalDependencies: diff --git a/src/browser/host-runtime.ts b/src/browser/host-runtime.ts index 100e937..a46cc21 100644 --- a/src/browser/host-runtime.ts +++ b/src/browser/host-runtime.ts @@ -19,10 +19,12 @@ import { NavigateToErr, PaymentRequestErr, PaymentTopUpErr, + GetAliasErr, PreimageSubmitErr, RequestCredentialsErr, SigningErr, } from "@novasamatech/host-api"; +import type { CodecType, DerivationIndex } from "@novasamatech/host-api"; import type { Container } from "@novasamatech/host-container"; import { createContainer, @@ -279,13 +281,26 @@ function getPairByAddress(address: string): KeyringPair | undefined { return undefined; } +/** + * Canonical string form of an RFC-0022 account selector. + * + * `Index(n)` renders as the plain number, so `productAccounts` keys and the + * derivation URIs this host builds are byte-identical to the pre-RFC-0022 + * ones — existing configs and derived addresses keep working. `Raw(bytes)` + * renders as its hex, the form byte-valued selectors take everywhere else. + */ +function selectorKey(index: CodecType): string { + return index.tag === "Index" ? String(index.value) : u8aToHex(index.value); +} + /** Resolve a product account [dotnsId, derivationIndex] to a keypair. */ function getPairForProductAccount( config: HostConfig, pairs: { pair: KeyringPair; name: string }[], dotnsId: string, - idx: number, + index: CodecType, ): KeyringPair | undefined { + const idx = selectorKey(index); const key = `${dotnsId}/${idx}`; const override = config.productAccounts?.[key]; if (override) { @@ -509,15 +524,13 @@ function setupContainer( // productAccounts: { 'myapp.dot/0': 'bob' } // → getProductAccount("myapp.dot", 0) returns //Bob's keypair container.handleAccountGet((params, { ok, err }) => { - const key = `${params[0]}/${params[1]}`; + const idx = selectorKey(params[1]); + const key = `${params[0]}/${idx}`; const override = config.productAccounts?.[key]; if (override) { const pair = getPair(override.uri); - return ok({ - publicKey: pair.publicKey, - name: override.name, - }); + return ok({ publicKey: pair.publicKey }); } if (pairs.length === 0) { @@ -527,11 +540,8 @@ function setupContainer( // Default: derive from the selected account (production behavior) const selectedPair = pairs[0]; const selectedAccUri = urisByPair.get(selectedPair.pair); - const productPair = getPair(`${selectedAccUri}//${params[0]}/${params[1]}`); - return ok({ - publicKey: productPair.publicKey, - name: undefined, - }); + const productPair = getPair(`${selectedAccUri}//${params[0]}/${idx}`); + return ok({ publicKey: productPair.publicKey }); }); container.handleAccountConnectionStatusSubscribe((_, send) => { @@ -545,16 +555,21 @@ function setupContainer( // (context, alias) pair derived from the product account — stable across // runs so tests can assert exact values if needed. container.handleAccountGetAlias((params, { ok, err }) => { - const key = `${params[0]}/${params[1]}`; + // RFC-0022: the request is now [ProductProofContext, RingLocation], where + // the context is [productId, suffix] — the same shape (and the identity + // mapping) as a ProductAccountId, so the account lookup is unchanged. + const [[productId, suffix]] = params; + const idx = selectorKey(suffix); + const key = `${productId}/${idx}`; const override = config.productAccounts?.[key]; if (!override && pairs.length === 0) { - return err(new RequestCredentialsErr.NotConnected(undefined)); + return err(new GetAliasErr.Unknown({ reason: "No accounts connected" })); } const pair = override ? getPair(override.uri) - : getPair(`${urisByPair.get(pairs[0].pair)}//${params[0]}/${params[1]}`); + : getPair(`${urisByPair.get(pairs[0].pair)}//${productId}/${idx}`); // Deterministic 32-byte context and alias from the account's public key. const context = blake2AsU8a( @@ -574,10 +589,29 @@ function setupContainer( // Ring VRF proof: real hosts use an actual ring VRF; for test purposes, // sign the message with the product account's sr25519 key. container.handleAccountCreateProof((params, { ok }) => { - const [[dotnsId, idx], _ringLocation, message] = params; - const pair = getPairForProductAccount(config, pairs, dotnsId, idx); + const [[productId, suffix], _ringLocation, message] = params; + const pair = getPairForProductAccount(config, pairs, productId, suffix); const signature = pair ? pair.sign(message) : new Uint8Array(64); - return ok(signature); + const publicKey = pair?.publicKey ?? new Uint8Array(32); + const encoder = new TextEncoder(); + // RFC-0022: the proof now carries the contextual alias plus the ring + // coordinates it was produced against. Deterministic stand-ins, matching + // what handleAccountGetAlias returns for the same account. + return ok({ + proof: signature, + contextualAlias: { + context: blake2AsU8a( + new Uint8Array([...publicKey, ...encoder.encode("context")]), + 256, + ), + alias: blake2AsU8a( + new Uint8Array([...publicKey, ...encoder.encode("alias")]), + 256, + ), + }, + ringIndex: 0, + ringRevision: 0, + }); }); container.handleCreateTransaction((params, { ok, err }) => { @@ -586,7 +620,7 @@ function setupContainer( if (!pair) { return err( new CreateTransactionErr.Unknown({ - reason: `No keypair for product account: ${dotnsId}/${idx}`, + reason: `No keypair for product account: ${dotnsId}/${selectorKey(idx)}`, }), ); } @@ -605,7 +639,7 @@ function setupContainer( if (!pair) { return err( new SigningErr.Unknown({ - reason: `No keypair for product account: ${dotnsId}/${idx}`, + reason: `No keypair for product account: ${dotnsId}/${selectorKey(idx)}`, }), ); } @@ -647,7 +681,7 @@ function setupContainer( if (!pair) { return err( new SigningErr.Unknown({ - reason: `No keypair for product account: ${dotnsId}/${idx}`, + reason: `No keypair for product account: ${dotnsId}/${selectorKey(idx)}`, }), ); } @@ -853,7 +887,6 @@ function setupContainer( }); container.handleChatListSubscribe((_, send) => { - // Send current rooms on subscribe for (const room of chatRooms.values()) { send({ roomId: room.roomId, participatingAs: room.participatingAs }); } diff --git a/test/integration.spec.ts b/test/integration.spec.ts index 17a54fd..6d0d925 100644 --- a/test/integration.spec.ts +++ b/test/integration.spec.ts @@ -1213,7 +1213,8 @@ test.describe('Resource allocation', () => { window.__TEST_PRODUCT__.requestResourceAllocation([ { tag: 'StatementStoreAllowance', value: undefined }, { tag: 'BulletinAllowance', value: undefined }, - { tag: 'SmartContractAllowance', value: 0 }, + // RFC-0022: the allowance is scoped to an account selector, not a bare index. + { tag: 'SmartContractAllowance', value: { tag: 'Index', value: 0 } }, { tag: 'AutoSigning', value: undefined }, ])); expect(result.ok).toBe(true); diff --git a/test/test-product-truapi.ts b/test/test-product-truapi.ts index 8a8f596..00fbde2 100644 --- a/test/test-product-truapi.ts +++ b/test/test-product-truapi.ts @@ -47,15 +47,17 @@ async function main(): Promise { } const account = await client.account.getAccount({ - productAccountId: { dotNsIdentifier: DOTNS_ID, derivationIndex: 0 }, + productAccountId: { dotNsIdentifier: DOTNS_ID, derivationIndex: { tag: 'Left', value: 0 } }, }); if (account.isErr()) { setResult('account', `error:${JSON.stringify(account.error)}`); } else { setResult('account', account.value.account.publicKey); } + } main().catch((err) => { setResult('status', `boot-error:${err instanceof Error ? err.message : String(err)}`); }); + diff --git a/test/test-product.ts b/test/test-product.ts index 42f9052..20fa948 100644 --- a/test/test-product.ts +++ b/test/test-product.ts @@ -9,7 +9,7 @@ */ import { createAccountsProvider, hostApi, paymentManager, sandboxTransport } from '@novasamatech/host-api-wrapper'; -import { enumValue } from '@novasamatech/host-api'; +import { derivationIndexOf, enumValue } from '@novasamatech/host-api'; import { hexToU8a, u8aToHex } from '@polkadot/util'; const DOTNS_ID = 'test-product.dot'; @@ -25,6 +25,16 @@ interface TestResult { error?: string; } +/** + * Ring the alias / proof requests are scoped to (RFC-0022 shape: a chain plus + * the junctions locating the ring on it). The test host ignores it, so a fixed + * placeholder is enough to exercise the call. + */ +const RING_LOCATION = { + chainId: '0x0000000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, + junctions: [{ tag: 'PalletInstance' as const, value: 0 }], +}; + /** Extract a readable error string from versioned protocol results. */ function extractError(err: unknown): string { if (!err || typeof err !== 'object') return String(err); @@ -231,7 +241,9 @@ async function init() { async getAccountAlias(dotnsId: string, index: number): Promise { try { - const r = await hostApi.accountGetAlias(enumValue('v1', [dotnsId, index])); + const r = await hostApi.accountGetAlias( + enumValue('v1', [[dotnsId, derivationIndexOf(index)], RING_LOCATION]), + ); if (r.isOk()) { return { ok: true, @@ -414,7 +426,9 @@ async function init() { topics: [], data, }; - const r = await hostApi.statementStoreCreateProof(enumValue('v1', [[dotnsId, index], statement])); + const r = await hostApi.statementStoreCreateProof( + enumValue('v1', [[dotnsId, derivationIndexOf(index)], statement]), + ); if (r.isOk()) return { ok: true, proof: r.value.value }; return { ok: false, error: extractError(r.error) }; } catch (err) { @@ -531,7 +545,7 @@ async function init() { async createTransaction(dotnsId: string, index: number) { try { const payload = { - signer: [dotnsId, index] as [string, number], + signer: [dotnsId, derivationIndexOf(index)] as const, genesisHash: new Uint8Array(32), callData: new Uint8Array([0, 0]), extensions: [] as Array<{ id: string; extra: Uint8Array; additionalSigned: Uint8Array }>, @@ -631,7 +645,7 @@ async function init() { async signRawProduct(dotnsId: string, index: number, payloadHex: string) { try { const r = await hostApi.signRaw(enumValue('v1', { - account: [dotnsId, index] as [string, number], + account: [dotnsId, derivationIndexOf(index)] as const, payload: { tag: 'Bytes' as const, value: hexToU8a(payloadHex) }, })); if (r.isOk()) { @@ -669,13 +683,12 @@ async function init() { async accountCreateProof(dotnsId: string, index: number) { try { const message = new TextEncoder().encode('test-proof'); - const location = { - genesisHash: '0x0000000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, - ringRootHash: '0x0000000000000000000000000000000000000000000000000000000000000000' as `0x${string}`, - hints: undefined, - }; - const r = await hostApi.accountCreateProof(enumValue('v1', [[dotnsId, index], location, message])); - if (r.isOk()) return { ok: true, proofHex: u8aToHex(r.value.value) }; + const r = await hostApi.accountCreateProof( + enumValue('v1', [[dotnsId, derivationIndexOf(index)], RING_LOCATION, message]), + ); + // RFC-0022: the proof is now a struct carrying the contextual alias + // and the ring coordinates alongside the signature bytes. + if (r.isOk()) return { ok: true, proofHex: u8aToHex(r.value.value.proof) }; return { ok: false, error: extractError(r.error) }; } catch (err) { return { ok: false, error: extractError(err) };