From 1263bdc712d11b05f37eb5695c0d62a2787872dd Mon Sep 17 00:00:00 2001 From: Tiago Tavares Date: Sun, 14 Jun 2026 21:52:06 +0100 Subject: [PATCH 1/4] feat: sign with legacy accounts via host-papp signRawLegacy and createTransactionLegacy --- bun.lock | 3 + package.json | 3 + packages/auth/src/account.ts | 9 + packages/auth/src/signing.ts | 225 ++++++++++ packages/ui/src/container.ts | 147 +++---- patches/@novasamatech%2Fhost-papp@0.8.7.patch | 392 ++++++++++++++++++ 6 files changed, 679 insertions(+), 100 deletions(-) create mode 100644 patches/@novasamatech%2Fhost-papp@0.8.7.patch diff --git a/bun.lock b/bun.lock index 4a61ef18..de68e2ea 100644 --- a/bun.lock +++ b/bun.lock @@ -358,6 +358,9 @@ ], }, }, + "patchedDependencies": { + "@novasamatech/host-papp@0.8.7": "patches/@novasamatech%2Fhost-papp@0.8.7.patch", + }, "overrides": { "smoldot": "3.2.0", }, diff --git a/package.json b/package.json index f87854f1..10648af6 100644 --- a/package.json +++ b/package.json @@ -36,5 +36,8 @@ }, "overrides": { "smoldot": "3.2.0" + }, + "patchedDependencies": { + "@novasamatech/host-papp@0.8.7": "patches/@novasamatech%2Fhost-papp@0.8.7.patch" } } diff --git a/packages/auth/src/account.ts b/packages/auth/src/account.ts index 2ff2da98..479b0bef 100644 --- a/packages/auth/src/account.ts +++ b/packages/auth/src/account.ts @@ -52,6 +52,15 @@ const ss58Codec = AccountId(); export const productPublicKeyToAddress = (publicKey: Uint8Array): string => ss58Codec.dec(publicKey); +/** + * Inverse of `productPublicKeyToAddress`: decode an SS58 address string back to + * its 32-byte public key. Used to turn a legacy account's `signer` address + * (the wire format products send) into the `AccountId` bytes host-papp's + * `signRawLegacy` expects. Throws on a malformed address. + */ +export const productAddressToPublicKey = (address: string): Uint8Array => + ss58Codec.enc(address); + // NOTE: Uncomment when derived product accounts get their own network // allowance (quota) on People Chain. Currently only the root session account // has allowance, so createProof signs with the root ssSecret directly. diff --git a/packages/auth/src/signing.ts b/packages/auth/src/signing.ts index 9e516c52..31b5818f 100644 --- a/packages/auth/src/signing.ts +++ b/packages/auth/src/signing.ts @@ -19,6 +19,7 @@ import { } from "@novasamatech/host-api"; import { log } from "@dotli/shared/log"; import type { UserSession } from "@novasamatech/host-papp"; +import { productPublicKeyToAddress } from "./account"; export interface SigningResult { signature: `0x${string}`; @@ -61,6 +62,20 @@ export interface ContainerCreateTransactionPayload { txExtVersion: number; } +/** + * Legacy-account variant of `ContainerCreateTransactionPayload`. The signer is + * a raw 32-byte public key (host-api `AccountId = Bytes(32)`) rather than the + * `[dotNsIdentifier, derivationIndex]` product tuple, matching host-papp's + * `LegacyTransaction = GenericTxPayloadV1(AccountId)`. + */ +export interface ContainerCreateTransactionLegacyPayload { + signer: Uint8Array; + genesisHash: Uint8Array; + callData: Uint8Array; + extensions: { id: string; extra: Uint8Array; additionalSigned: Uint8Array }[]; + txExtVersion: number; +} + /** Timeout for the wallet to respond (ms). Covers WS drops and unresponsive wallets. */ const SIGN_TIMEOUT_MS = 300_000; // 300 seconds @@ -433,3 +448,213 @@ export function showCreateTransactionModal( }); }); } + +/** + * Sign-raw modal for a true legacy (imported) account. + * + * Unlike `showSignRawModal`, the wallet is asked to sign with a concrete + * account public key (`signRawLegacy`) rather than deriving a product key from + * a `[dotNsIdentifier, index]` tuple. host-papp's `signRawLegacy` returns the + * raw signature only (no signed transaction). + */ +export function showSignRawLegacyModal( + session: UserSession, + data: ContainerSignRawRequest["payload"], + appLabel: string, + account: Uint8Array, +): Promise { + return new Promise((resolve, reject) => { + const signerLabel = productPublicKeyToAddress(account); + const message = data.tag === "Payload" ? data.value : toHex(data.value); + + const fields: { label: string; value: string; mono?: boolean }[] = [ + { label: "App", value: appLabel }, + { label: "Signer", value: signerLabel, mono: true }, + { label: "Message", value: message, mono: true }, + ]; + + const { backdrop, signBtn, cancelBtn } = createModalDOM( + "Sign Message", + fields, + ); + + cancelBtn.addEventListener("click", () => { + removeModal(backdrop); + reject(new SigningErr.Rejected()); + }); + + signBtn.addEventListener("click", () => { + signBtn.disabled = true; + signBtn.textContent = "Signing..."; + + void withTimeout( + session.signRawLegacy({ account, data }), + SIGN_TIMEOUT_MS, + ).then( + (result) => { + result.match( + (signature) => { + removeModal(backdrop); + resolve({ signature: toHex(signature) }); + }, + (e) => { + log.error("[dot.li signing] signRawLegacy FAILED:", e.message, e); + removeModal(backdrop); + reject(new SigningErr.Unknown({ reason: e.message })); + }, + ); + }, + (e: unknown) => { + log.error("[dot.li signing] signRawLegacy timed out:", e); + removeModal(backdrop); + const msg = e instanceof Error ? e.message : "Request timed out"; + reject(new SigningErr.Unknown({ reason: msg })); + }, + ); + }); + }); +} + +/** + * Create-transaction modal for a true legacy (imported) account. Mirrors + * `showCreateTransactionModal` but routes through host-papp's + * `createTransactionLegacy`, which carries the signer as a raw public key. + */ +export function showCreateTransactionLegacyModal( + session: UserSession, + payload: ContainerCreateTransactionLegacyPayload, + appLabel: string, +): Promise { + return new Promise((resolve, reject) => { + const signerLabel = productPublicKeyToAddress(payload.signer); + const genesisHashHex = toHex(payload.genesisHash); + const callDataHex = toHex(payload.callData); + const callDataPreview = + callDataHex.length > 80 ? `${callDataHex.slice(0, 80)}...` : callDataHex; + + const fields: { label: string; value: string; mono?: boolean }[] = [ + { label: "App", value: appLabel }, + { label: "Signer", value: signerLabel, mono: true }, + { label: "Genesis Hash", value: genesisHashHex, mono: true }, + { label: "Call Data", value: callDataPreview, mono: true }, + { label: "Tx Ext Version", value: String(payload.txExtVersion) }, + ]; + + const { backdrop, signBtn, cancelBtn } = createModalDOM( + "Sign Transaction", + fields, + ); + + cancelBtn.addEventListener("click", () => { + removeModal(backdrop); + reject(new CreateTransactionErr.Rejected()); + }); + + signBtn.addEventListener("click", () => { + signBtn.disabled = true; + signBtn.textContent = "Signing..."; + + void withTimeout( + session.createTransactionLegacy({ + payload: enumValue("v1", payload), + }), + SIGN_TIMEOUT_MS, + ).then( + (result) => { + result.match( + (signedTransaction) => { + removeModal(backdrop); + resolve(signedTransaction); + }, + (e) => { + log.error( + "[dot.li signing] createTransactionLegacy FAILED:", + e.message, + e, + ); + removeModal(backdrop); + reject(new CreateTransactionErr.Unknown({ reason: e.message })); + }, + ); + }, + (e: unknown) => { + log.error("[dot.li signing] createTransactionLegacy timed out:", e); + removeModal(backdrop); + const msg = e instanceof Error ? e.message : "Request timed out"; + reject(new CreateTransactionErr.Unknown({ reason: msg })); + }, + ); + }); + }); +} + +/** + * Sign-payload modal for a true legacy (imported) account. host-papp has no + * `signPayloadLegacy`, so the JSON-encoded payload is signed through + * `signRawLegacy` (Payload variant), mirroring polkadot-desktop. Returns the + * raw signature only (no signed transaction). + */ +export function showSignPayloadLegacyModal( + session: UserSession, + payload: ContainerSignPayloadRequest["payload"], + appLabel: string, + account: Uint8Array, +): Promise { + return new Promise((resolve, reject) => { + const signerLabel = productPublicKeyToAddress(account); + + const fields: { label: string; value: string; mono?: boolean }[] = [ + { label: "App", value: appLabel }, + { label: "Signer", value: signerLabel, mono: true }, + { label: "Genesis Hash", value: payload.genesisHash, mono: true }, + { label: "Call Data", value: payload.method, mono: true }, + ]; + + const { backdrop, signBtn, cancelBtn } = createModalDOM( + "Sign Transaction", + fields, + ); + + cancelBtn.addEventListener("click", () => { + removeModal(backdrop); + reject(new SigningErr.Rejected()); + }); + + signBtn.addEventListener("click", () => { + signBtn.disabled = true; + signBtn.textContent = "Signing..."; + + void withTimeout( + session.signRawLegacy({ + account, + data: { tag: "Payload", value: JSON.stringify(payload) }, + }), + SIGN_TIMEOUT_MS, + ).then( + (result) => { + result.match( + (signature) => { + removeModal(backdrop); + resolve({ signature: toHex(signature) }); + }, + (e) => { + log.error( + "[dot.li signing] signPayloadLegacy FAILED:", + e.message, + e, + ); + removeModal(backdrop); + reject(new SigningErr.Unknown({ reason: e.message })); + }, + ); + }, + (e: unknown) => { + log.error("[dot.li signing] signPayloadLegacy timed out:", e); + removeModal(backdrop); + const msg = e instanceof Error ? e.message : "Request timed out"; + reject(new SigningErr.Unknown({ reason: msg })); + }, + ); + }); + }); +} diff --git a/packages/ui/src/container.ts b/packages/ui/src/container.ts index 35062af1..e6aaf4f4 100644 --- a/packages/ui/src/container.ts +++ b/packages/ui/src/container.ts @@ -68,13 +68,17 @@ import { type AuthState, } from "@dotli/auth/auth"; import { + showCreateTransactionLegacyModal, showCreateTransactionModal, + showSignPayloadLegacyModal, showSignPayloadModal, + showSignRawLegacyModal, showSignRawModal, - type ContainerCreateTransactionPayload, + type ContainerCreateTransactionLegacyPayload, } from "@dotli/auth/signing"; import { deriveProductPublicKey, + productAddressToPublicKey, productPublicKeyToAddress, } from "@dotli/auth/account"; import { @@ -465,11 +469,14 @@ function wireContainerHandlers( }), ); - // Legacy-account signing wires. Re-derive the same `(session, identifier, 0)` - // public key, SS58-encode it, and require it equals the product-supplied - // `signer: string` before opening the regular signing modal with a synthetic - // `[identifier, 0]` tuple. Mirrors the desktop host's wire-up at - // browser/src/widgets/ProductContainerBinding/integrations/signing.tsx. + // Legacy-account signing wires. A `*WithLegacyAccount` request always targets + // a genuine imported account, so decode the supplied signer to its raw + // AccountId and relay to the wallet via host-papp's legacy SDK calls. The + // non-legacy handlers above own the product-account (derived) path. Mirrors + // polkadot-desktop#632. + // + // host-papp has no `signPayloadLegacy`, so signPayload is signed through + // `signRawLegacy` (Payload variant) — see `showSignPayloadLegacyModal`. container.handleSignPayloadWithLegacyAccount((payload, { ok, err }) => queueWalletFlow(() => { log.warn(`[${label}] handleSignPayloadWithLegacyAccount invoked:`, { @@ -486,21 +493,12 @@ function wireContainerHandlers( return errAsync(new SigningErr.Rejected(undefined)); } - const identifier = labelToProductIdentifier(label); - const derivedPk = deriveProductPublicKey( - session.rootAccountId, - identifier, - 0, - ); - const derivedAddress = productPublicKeyToAddress(derivedPk); - if (derivedAddress !== payload.signer) { - log.warn( - `[${label}] handleSignPayloadWithLegacyAccount — signer mismatch (expected ${derivedAddress}, got ${payload.signer})`, - ); + let account: Uint8Array; + try { + account = productAddressToPublicKey(payload.signer); + } catch { return errAsync( - new SigningErr.Unknown({ - reason: "Account can't be derived from product account id", - }), + new SigningErr.Unknown({ reason: "Invalid legacy signer address" }), ); } @@ -513,28 +511,21 @@ function wireContainerHandlers( return err(new SigningErr.PermissionDenied(undefined)); } return fromPromise( - showSignPayloadModal(session, payload.payload, label, [ - identifier, - 0, - ]), + showSignPayloadLegacyModal( + session, + payload.payload, + label, + account, + ), (e) => e as never, ) - .andThen((result) => { - log.warn( - `[${label}] handleSignPayloadWithLegacyAccount — resolved OK`, - ); - return ok({ + .andThen((result) => + ok({ signature: result.signature, signedTransaction: result.signedTransaction, - }); - }) - .orElse((e) => { - log.warn( - `[${label}] handleSignPayloadWithLegacyAccount — rejected:`, - e, - ); - return err(e); - }); + }), + ) + .orElse((e) => err(e)); }, ); }), @@ -555,47 +546,31 @@ function wireContainerHandlers( return errAsync(new SigningErr.Rejected(undefined)); } - const identifier = labelToProductIdentifier(label); - const derivedPk = deriveProductPublicKey( - session.rootAccountId, - identifier, - 0, - ); - const derivedAddress = productPublicKeyToAddress(derivedPk); - if (derivedAddress !== payload.signer) { - log.warn( - `[${label}] handleSignRawWithLegacyAccount — signer mismatch (expected ${derivedAddress}, got ${payload.signer})`, - ); + let account: Uint8Array; + try { + account = productAddressToPublicKey(payload.signer); + } catch { return errAsync( - new SigningErr.Unknown({ - reason: "Account can't be derived from product account id", - }), + new SigningErr.Unknown({ reason: "Invalid legacy signer address" }), ); } return fromPromise( - showSignRawModal(session, payload.payload, label, [identifier, 0]), + showSignRawLegacyModal(session, payload.payload, label, account), (e) => e as never, ) - .andThen((result) => { - log.warn(`[${label}] handleSignRawWithLegacyAccount — resolved OK`); - return ok({ + .andThen((result) => + ok({ signature: result.signature, signedTransaction: result.signedTransaction, - }); - }) - .orElse((e) => { - log.warn(`[${label}] handleSignRawWithLegacyAccount — rejected:`, e); - return err(e); - }); + }), + ) + .orElse((e) => err(e)); }), ); - // Legacy-account create-transaction. The host-papp SSO message only carries - // the product-account flavor, so we re-route the request through the same - // wallet flow using a synthetic `[identifier, 0]` tuple. Mirrors the trust - // model of `handleSignPayloadWithLegacyAccount` above. `payload.signer` is - // the raw 32-byte public key (codec is `AccountId = Bytes(32)`). + // Legacy-account create-transaction: relay the raw 32-byte signer + // (`AccountId = Bytes(32)`) to the wallet via createTransactionLegacy. container.handleCreateTransactionWithLegacyAccount((payload, { ok, err }) => queueWalletFlow(() => { log.warn(`[${label}] handleCreateTransactionWithLegacyAccount invoked:`, { @@ -614,23 +589,6 @@ function wireContainerHandlers( return errAsync(new CreateTransactionErr.Rejected()); } - const identifier = labelToProductIdentifier(label); - const derivedPk = deriveProductPublicKey( - session.rootAccountId, - identifier, - 0, - ); - if (toHex(derivedPk) !== toHex(payload.signer)) { - log.warn( - `[${label}] handleCreateTransactionWithLegacyAccount — signer mismatch (expected ${productPublicKeyToAddress(derivedPk)}, got pk=${toHex(payload.signer)})`, - ); - return errAsync( - new CreateTransactionErr.Unknown({ - reason: "Account can't be derived from product account id", - }), - ); - } - return promptCachedSubmitPermission(label, "ChainSubmit").andThen( (granted) => { if (!granted) { @@ -639,30 +597,19 @@ function wireContainerHandlers( ); return err(new CreateTransactionErr.PermissionDenied()); } - const productPayload: ContainerCreateTransactionPayload = { - signer: [identifier, 0], + const legacyPayload: ContainerCreateTransactionLegacyPayload = { + signer: payload.signer, genesisHash: payload.genesisHash, callData: payload.callData, extensions: payload.extensions, txExtVersion: payload.txExtVersion, }; return fromPromise( - showCreateTransactionModal(session, productPayload, label), + showCreateTransactionLegacyModal(session, legacyPayload, label), (e) => e as never, ) - .andThen((signedTx) => { - log.warn( - `[${label}] handleCreateTransactionWithLegacyAccount — resolved OK`, - ); - return ok(signedTx); - }) - .orElse((e) => { - log.warn( - `[${label}] handleCreateTransactionWithLegacyAccount — rejected:`, - e, - ); - return err(e); - }); + .andThen((signedTx) => ok(signedTx)) + .orElse((e) => err(e)); }, ); }), diff --git a/patches/@novasamatech%2Fhost-papp@0.8.7.patch b/patches/@novasamatech%2Fhost-papp@0.8.7.patch new file mode 100644 index 00000000..4eb2367b --- /dev/null +++ b/patches/@novasamatech%2Fhost-papp@0.8.7.patch @@ -0,0 +1,392 @@ +diff --git a/dist/index.d.ts b/dist/index.d.ts +index 1bd2a09bfc584e7368da35215a6ed12dbae54bd9..381d2cb6f2b271712a4367b57cbb34434afa99b4 100644 +--- a/dist/index.d.ts ++++ b/dist/index.d.ts +@@ -9,6 +9,6 @@ export { AllowanceError } from './sso/allowance/index.js'; + export type { UserSession } from './sso/sessionManager/userSession.js'; + export type { StoredUserSession } from './sso/userSessionRepository.js'; + export type { Identity } from './identity/types.js'; +-export type { SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, SigningRequest, } from './sso/sessionManager/scale/signing.js'; ++export type { SignRawLegacyRequest, SignRawLegacyResponse, SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, SigningRequest, } from './sso/sessionManager/scale/signing.js'; + export type { RingVrfAliasRequest, RingVrfAliasResponse } from './sso/sessionManager/scale/ringVrf.js'; +-export type { CreateTransactionRequest, CreateTransactionResponse, } from './sso/sessionManager/scale/createTransaction.js'; ++export type { CreateTransactionLegacyRequest, CreateTransactionRequest, CreateTransactionResponse, } from './sso/sessionManager/scale/createTransaction.js'; +diff --git a/dist/sso/auth/attestationService.d.ts b/dist/sso/auth/attestationService.d.ts +index e9abeb9879cce07e48074cd30a1e99186e642b7c..904a20834db5f5e5e74a344938f6b9b5a46aa4c3 100644 +--- a/dist/sso/auth/attestationService.d.ts ++++ b/dist/sso/auth/attestationService.d.ts +@@ -3,7 +3,7 @@ import type { ResultAsync } from 'neverthrow'; + import type { DerivedSr25519Account } from '../../crypto.js'; + export declare function createSudoAliceVerifier(): DerivedSr25519Account; + export declare function withRetry(fn: () => Promise, maxRetries?: number): Promise; +-export declare const createAttestationService: (lazyClient: LazyClient) => { ++export declare const createAttestationService: (lazyClient: LazyClient, debugFlowId?: string) => { + claimUsername(): string; + grantVerifierAllowance(verifier: DerivedSr25519Account): ResultAsync; + getRingRfKey(candidate: DerivedSr25519Account): Uint8Array; +diff --git a/dist/sso/auth/attestationService.js b/dist/sso/auth/attestationService.js +index 08b81aa0865019a94cf67ea29db2b6de1f3dba97..ea19c3cad34148cb0ecb3ac3e78d5fc078c1c674 100644 +--- a/dist/sso/auth/attestationService.js ++++ b/dist/sso/auth/attestationService.js +@@ -9,6 +9,7 @@ import { mergeUint8 } from 'polkadot-api/utils'; + import { Bytes, Option, Tuple, str } from 'scale-ts'; + import { member_from_entropy, sign } from 'verifiablejs/bundler'; + import { deriveSr25519Account, getEncrPub, stringToBytes } from '../../crypto.js'; ++import { emitHostPappDebugMessage } from '../../debugBus.js'; + import { toError } from '../../helpers/utils.js'; + const accountId = AccountId(); + export function createSudoAliceVerifier() { +@@ -22,11 +23,21 @@ export function withRetry(fn, maxRetries = 1) { + throw error; + }); + } +-export const createAttestationService = (lazyClient) => { ++export const createAttestationService = (lazyClient, debugFlowId) => { + const service = { + claimUsername() { + const nameSuffixFactory = customAlphabet('abcdefghijklmnopqrstuvwxyz', 4); +- return `guest${nameSuffixFactory()}.${createNumericSuffix(4)}`; ++ const username = `guest${nameSuffixFactory()}.${createNumericSuffix(4)}`; ++ if (debugFlowId !== undefined) { ++ emitHostPappDebugMessage({ ++ layer: 'attestation', ++ event: 'username_claimed', ++ flowId: debugFlowId, ++ timestamp: Date.now(), ++ payload: { username }, ++ }); ++ } ++ return username; + }, + grantVerifierAllowance(verifier) { + const client = lazyClient.getClient(); +@@ -46,7 +57,19 @@ export const createAttestationService = (lazyClient) => { + }); + return withRetry(() => sudoCall.signAndSubmit(createPeopleSigner(verifier)).then(() => undefined)); + }, toError); +- return verifierAllowance.andThen(verifierAllowance => (verifierAllowance > 0 ? okAsync() : getAllowance())); ++ return verifierAllowance ++ .andThen(verifierAllowance => (verifierAllowance > 0 ? okAsync() : getAllowance())) ++ .andTee(() => { ++ if (debugFlowId !== undefined) { ++ emitHostPappDebugMessage({ ++ layer: 'attestation', ++ event: 'allowance_granted', ++ flowId: debugFlowId, ++ timestamp: Date.now(), ++ payload: { verifierAccountId: verifierAddress }, ++ }); ++ } ++ }); + }, + getRingRfKey(candidate) { + const verifiableEntropy = blake2b256(candidate.entropy); +@@ -65,6 +88,15 @@ export const createAttestationService = (lazyClient) => { + const usernameWithoutDigits = username.split('.')[0] ?? username; + const candidateSignature = candidate.sign(message); + const proofOfOwnership = sign(verifiableEntropy, message); ++ if (debugFlowId !== undefined) { ++ emitHostPappDebugMessage({ ++ layer: 'attestation', ++ event: 'vrf_proof_generated', ++ flowId: debugFlowId, ++ timestamp: Date.now(), ++ payload: { candidateAccountId: accountId.dec(candidate.publicKey) }, ++ }); ++ } + const ResourceSignatureCodec = Tuple( + // candidate PublicKey (32 bytes) + Bytes(32), +@@ -141,7 +173,21 @@ export const createAttestationService = (lazyClient) => { + }); + return fromPromise(withRetry(submitAttestation), toError).map(() => undefined); + }) +- .andTee(() => console.log(`Attestation for ${accountId.dec(candidate.publicKey)} successfully passed.`)); ++ .andTee(() => { ++ console.log(`Attestation for ${accountId.dec(candidate.publicKey)} successfully passed.`); ++ if (debugFlowId !== undefined) { ++ emitHostPappDebugMessage({ ++ layer: 'attestation', ++ event: 'person_registered', ++ flowId: debugFlowId, ++ timestamp: Date.now(), ++ payload: { ++ username, ++ candidateAccountId: accountId.dec(candidate.publicKey), ++ }, ++ }); ++ } ++ }); + }, + }; + return service; +diff --git a/dist/sso/sessionManager/scale/createTransaction.d.ts b/dist/sso/sessionManager/scale/createTransaction.d.ts +index 9ba4944c703dca63598dceeedc8e25cd9bb0dd04..4bb23cab1e60cf675de3d01d04bdee54c09b7610 100644 +--- a/dist/sso/sessionManager/scale/createTransaction.d.ts ++++ b/dist/sso/sessionManager/scale/createTransaction.d.ts +@@ -16,6 +16,23 @@ export declare const CreateTransactionRequestCodec: import("scale-ts").Codec<{ + }; + }; + }>; ++export type CreateTransactionLegacyRequest = CodecType; ++export declare const CreateTransactionLegacyRequestCodec: import("scale-ts").Codec<{ ++ payload: { ++ tag: "v1"; ++ value: { ++ signer: Uint8Array; ++ genesisHash: Uint8Array; ++ callData: Uint8Array; ++ extensions: { ++ id: string; ++ extra: Uint8Array; ++ additionalSigned: Uint8Array; ++ }[]; ++ txExtVersion: number; ++ }; ++ }; ++}>; + export type CreateTransactionResponse = CodecType; + export declare const CreateTransactionResponseCodec: import("scale-ts").Codec<{ + respondingTo: string; +diff --git a/dist/sso/sessionManager/scale/createTransaction.js b/dist/sso/sessionManager/scale/createTransaction.js +index 2098c0a47b417bae549f99f65c2344de8bc82233..1d3796771fca3ac0f94c09cc052509bf18de5a6a 100644 +--- a/dist/sso/sessionManager/scale/createTransaction.js ++++ b/dist/sso/sessionManager/scale/createTransaction.js +@@ -1,4 +1,4 @@ +-import { ProductAccountTransaction } from '@novasamatech/host-api'; ++import { LegacyTransaction, ProductAccountTransaction } from '@novasamatech/host-api'; + import { Enum } from '@novasamatech/scale'; + import { Bytes, Result, Struct, str } from 'scale-ts'; + export const CreateTransactionRequestCodec = Struct({ +@@ -6,6 +6,11 @@ export const CreateTransactionRequestCodec = Struct({ + v1: ProductAccountTransaction, + }), + }); ++export const CreateTransactionLegacyRequestCodec = Struct({ ++ payload: Enum({ ++ v1: LegacyTransaction, ++ }), ++}); + export const CreateTransactionResponseCodec = Struct({ + // referencing to RemoteMessage.messageId + respondingTo: str, +diff --git a/dist/sso/sessionManager/scale/remoteMessage.d.ts b/dist/sso/sessionManager/scale/remoteMessage.d.ts +index 545a5ae4b66e54277f1509b781cffcf25719a469..e651b3047460e4e41b3577ae9acbff7b0a5441ed 100644 +--- a/dist/sso/sessionManager/scale/remoteMessage.d.ts ++++ b/dist/sso/sessionManager/scale/remoteMessage.d.ts +@@ -143,6 +143,42 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{ + respondingTo: string; + signedTransaction: import("scale-ts").ResultPayload, string>; + }; ++ } | { ++ tag: "CreateTransactionLegacyRequest"; ++ value: { ++ payload: { ++ tag: "v1"; ++ value: { ++ signer: Uint8Array; ++ genesisHash: Uint8Array; ++ callData: Uint8Array; ++ extensions: { ++ id: string; ++ extra: Uint8Array; ++ additionalSigned: Uint8Array; ++ }[]; ++ txExtVersion: number; ++ }; ++ }; ++ }; ++ } | { ++ tag: "SignRawLegacyRequest"; ++ value: { ++ account: Uint8Array; ++ data: { ++ tag: "Bytes"; ++ value: Uint8Array; ++ } | { ++ tag: "Payload"; ++ value: string; ++ }; ++ }; ++ } | { ++ tag: "SignRawLegacyResponse"; ++ value: { ++ respondingTo: string; ++ signature: import("scale-ts").ResultPayload, string>; ++ }; + }; + }; + }>; +diff --git a/dist/sso/sessionManager/scale/remoteMessage.js b/dist/sso/sessionManager/scale/remoteMessage.js +index e89bb16e7a2e939be3548c724bbafca8ae42df58..8fccd1a378afd9f065eae94aa8fd44ccf351513b 100644 +--- a/dist/sso/sessionManager/scale/remoteMessage.js ++++ b/dist/sso/sessionManager/scale/remoteMessage.js +@@ -1,8 +1,8 @@ + import { Enum, Struct, _void, str } from 'scale-ts'; +-import { CreateTransactionRequestCodec, CreateTransactionResponseCodec } from './createTransaction.js'; ++import { CreateTransactionLegacyRequestCodec, CreateTransactionRequestCodec, CreateTransactionResponseCodec, } from './createTransaction.js'; + import { ResourceAllocationRequestCodec, ResourceAllocationResponseCodec } from './resourceAllocation.js'; + import { RingVrfAliasRequestCodec, RingVrfAliasResponseCodec } from './ringVrf.js'; +-import { SigningRequestCodec, SigningResponseCodec } from './signing.js'; ++import { SignRawLegacyRequestCodec, SignRawLegacyResponseCodec, SigningRequestCodec, SigningResponseCodec, } from './signing.js'; + export const RemoteMessageCodec = Struct({ + messageId: str, + data: Enum({ +@@ -16,6 +16,9 @@ export const RemoteMessageCodec = Struct({ + ResourceAllocationResponse: ResourceAllocationResponseCodec, + CreateTransactionRequest: CreateTransactionRequestCodec, + CreateTransactionResponse: CreateTransactionResponseCodec, ++ CreateTransactionLegacyRequest: CreateTransactionLegacyRequestCodec, ++ SignRawLegacyRequest: SignRawLegacyRequestCodec, ++ SignRawLegacyResponse: SignRawLegacyResponseCodec, + }), + }), + }); +diff --git a/dist/sso/sessionManager/scale/signing.d.ts b/dist/sso/sessionManager/scale/signing.d.ts +index b2787476cd2afed4a3914304425470fa10d730b9..b8f0f220835b6e50ec24107e7f6e6d18412564ff 100644 +--- a/dist/sso/sessionManager/scale/signing.d.ts ++++ b/dist/sso/sessionManager/scale/signing.d.ts +@@ -29,6 +29,22 @@ export declare const SigningRawRequestCodec: import("scale-ts").Codec<{ + value: string; + }; + }>; ++export type SignRawLegacyRequest = CodecType; ++export declare const SignRawLegacyRequestCodec: import("scale-ts").Codec<{ ++ account: Uint8Array; ++ data: { ++ tag: "Bytes"; ++ value: Uint8Array; ++ } | { ++ tag: "Payload"; ++ value: string; ++ }; ++}>; ++export type SignRawLegacyResponse = CodecType; ++export declare const SignRawLegacyResponseCodec: import("scale-ts").Codec<{ ++ respondingTo: string; ++ signature: import("scale-ts").ResultPayload, string>; ++}>; + export type SigningRequest = CodecType; + export declare const SigningRequestCodec: import("scale-ts").Codec<{ + tag: "Payload"; +diff --git a/dist/sso/sessionManager/scale/signing.js b/dist/sso/sessionManager/scale/signing.js +index 80726670653efc6df6e3a0fe50ef83d45b296456..81e9ef7010e7ab53720cc76796b012666effbd6b 100644 +--- a/dist/sso/sessionManager/scale/signing.js ++++ b/dist/sso/sessionManager/scale/signing.js +@@ -1,4 +1,4 @@ +-import { ProductAccountId } from '@novasamatech/host-api'; ++import { AccountId, ProductAccountId } from '@novasamatech/host-api'; + import { Enum, Hex, OptionBool } from '@novasamatech/scale'; + import { Bytes, Option, Result, Struct, Vector, str, u32 } from 'scale-ts'; + export const SigningPayloadRequestCodec = Struct({ +@@ -26,6 +26,18 @@ export const SigningRawRequestCodec = Struct({ + Payload: str, + }), + }); ++export const SignRawLegacyRequestCodec = Struct({ ++ account: AccountId, ++ data: Enum({ ++ Bytes: Bytes(), ++ Payload: str, ++ }), ++}); ++export const SignRawLegacyResponseCodec = Struct({ ++ // referencing to RemoteMessage.messageId ++ respondingTo: str, ++ signature: Result(Bytes(), str), ++}); + export const SigningRequestCodec = Enum({ + Payload: SigningPayloadRequestCodec, + Raw: SigningRawRequestCodec, +diff --git a/dist/sso/sessionManager/userSession.d.ts b/dist/sso/sessionManager/userSession.d.ts +index 15deafd950d7296ed96e6e67501f9d067bbfaf95..ecfbafebb1e92ba972ba4fff71064795ee6ab498 100644 +--- a/dist/sso/sessionManager/userSession.d.ts ++++ b/dist/sso/sessionManager/userSession.d.ts +@@ -5,16 +5,18 @@ import { ResultAsync } from 'neverthrow'; + import type { CodecType } from 'scale-ts'; + import type { Callback } from '../../types.js'; + import type { StoredUserSession } from '../userSessionRepository.js'; +-import type { CreateTransactionRequest } from './scale/createTransaction.js'; ++import type { CreateTransactionLegacyRequest, CreateTransactionRequest } from './scale/createTransaction.js'; + import { RemoteMessageCodec } from './scale/remoteMessage.js'; + import type { ApAllocationOutcome, ResourceAllocationRequest } from './scale/resourceAllocation.js'; +-import type { SigningPayloadRequest, SigningPayloadResponseData, SigningRawRequest } from './scale/signing.js'; ++import type { SignRawLegacyRequest, SigningPayloadRequest, SigningPayloadResponseData, SigningRawRequest } from './scale/signing.js'; + export type UserSession = StoredUserSession & { + sendDisconnectMessage(): ResultAsync; + abortPendingRequests(): ResultAsync; + signPayload(payload: SigningPayloadRequest): ResultAsync; + signRaw(payload: SigningRawRequest): ResultAsync; ++ signRawLegacy(payload: SignRawLegacyRequest): ResultAsync; + createTransaction(payload: CreateTransactionRequest): ResultAsync; ++ createTransactionLegacy(payload: CreateTransactionLegacyRequest): ResultAsync; + getRingVrfAlias(productAccountId: CodecType, productId: string): ResultAsync, Error>; + requestResourceAllocation(request: ResourceAllocationRequest): ResultAsync; + subscribe(callback: Callback, ResultAsync>): VoidFunction; +diff --git a/dist/sso/sessionManager/userSession.js b/dist/sso/sessionManager/userSession.js +index e75b6a664f828b82e1e32c1d59443ee72779ed32..3de5815e5ea13a318c1be1364405ac00f3d902a6 100644 +--- a/dist/sso/sessionManager/userSession.js ++++ b/dist/sso/sessionManager/userSession.js +@@ -162,6 +162,31 @@ export function createUserSession({ userSession, statementStore, encryption, sto + return withHostActionTrace(withQueueTimeout(inner, 'signRaw'), messageId, userSession.id); + }); + }, ++ signRawLegacy(payload) { ++ return enqueue(() => { ++ const messageId = nanoid(); ++ const data = enumValue('v1', enumValue('SignRawLegacyRequest', payload)); ++ emitHostAction(messageId, actionKindFromMessageData(data), userSession.id); ++ const responseFilter = (message) => { ++ if (message.data.tag === 'v1' && ++ message.data.value.tag === 'SignRawLegacyResponse' && ++ message.data.value.value.respondingTo === messageId) { ++ return message.data.value.value.signature; ++ } ++ }; ++ const request = session.request(RemoteMessageCodec, { messageId, data }); ++ const reply = session.waitForRequestMessage(RemoteMessageCodec, responseFilter); ++ const inner = awaitReplyOrAckFailure(request, reply).andThen(message => { ++ if (message.success) { ++ return ok(message.value); ++ } ++ else { ++ return err(new Error(message.value)); ++ } ++ }); ++ return withHostActionTrace(withQueueTimeout(inner, 'signRawLegacy'), messageId, userSession.id); ++ }); ++ }, + createTransaction(payload) { + return enqueue(() => { + const messageId = nanoid(); +@@ -186,6 +211,30 @@ export function createUserSession({ userSession, statementStore, encryption, sto + return withQueueTimeout(inner, 'createTransaction'); + }); + }, ++ createTransactionLegacy(payload) { ++ return enqueue(() => { ++ const messageId = nanoid(); ++ const data = enumValue('v1', enumValue('CreateTransactionLegacyRequest', payload)); ++ const responseFilter = (message) => { ++ if (message.data.tag === 'v1' && ++ message.data.value.tag === 'CreateTransactionResponse' && ++ message.data.value.value.respondingTo === messageId) { ++ return message.data.value.value.signedTransaction; ++ } ++ }; ++ const request = session.request(RemoteMessageCodec, { messageId, data }); ++ const reply = session.waitForRequestMessage(RemoteMessageCodec, responseFilter); ++ const inner = awaitReplyOrAckFailure(request, reply).andThen(message => { ++ if (message.success) { ++ return ok(message.value); ++ } ++ else { ++ return err(new Error(message.value)); ++ } ++ }); ++ return withQueueTimeout(inner, 'createTransactionLegacy'); ++ }); ++ }, + getRingVrfAlias(productAccountId, productId) { + return enqueue(() => { + const messageId = nanoid(); From d5c44e707c2f52a3267afc5c1eabf0878d85e41f Mon Sep 17 00:00:00 2001 From: Tiago Tavares Date: Sun, 14 Jun 2026 23:19:51 +0100 Subject: [PATCH 2/4] chore(deps): bump @novasamatech host SDK to 0.8.8 and drop the vendored host-papp patch --- bun.lock | 39 +- package.json | 3 - packages/auth/package.json | 8 +- packages/truapi-debug/package.json | 6 +- packages/ui/package.json | 8 +- patches/@novasamatech%2Fhost-papp@0.8.7.patch | 392 ------------------ 6 files changed, 29 insertions(+), 427 deletions(-) delete mode 100644 patches/@novasamatech%2Fhost-papp@0.8.7.patch diff --git a/bun.lock b/bun.lock index de68e2ea..43bcab8e 100644 --- a/bun.lock +++ b/bun.lock @@ -104,11 +104,11 @@ "@dotli/resolver": "workspace:*", "@dotli/shared": "workspace:*", "@noble/hashes": "^2.2.0", - "@novasamatech/host-api": "0.8.7", - "@novasamatech/host-papp": "0.8.7", + "@novasamatech/host-api": "0.8.8", + "@novasamatech/host-papp": "0.8.8", "@novasamatech/sdk-statement": "^0.6.0", - "@novasamatech/statement-store": "0.8.7", - "@novasamatech/storage-adapter": "0.8.7", + "@novasamatech/statement-store": "0.8.8", + "@novasamatech/storage-adapter": "0.8.8", "@scure/sr25519": "^2.2.0", "neverthrow": "^8.2.0", "polkadot-api": "^2.1.6", @@ -298,9 +298,9 @@ "version": "0.5.0", "dependencies": { "@dotli/shared": "workspace:*", - "@novasamatech/host-api": "0.8.7", - "@novasamatech/host-container": "0.8.7", - "@novasamatech/host-papp": "0.8.7", + "@novasamatech/host-api": "0.8.8", + "@novasamatech/host-container": "0.8.8", + "@novasamatech/host-papp": "0.8.8", "nanoevents": "^9.1.0", }, "devDependencies": { @@ -328,11 +328,11 @@ "@dotli/storage": "workspace:*", "@dotli/truapi-debug": "workspace:*", "@noble/hashes": "^2.2.0", - "@novasamatech/host-api": "0.8.7", - "@novasamatech/host-container": "0.8.7", - "@novasamatech/host-papp": "0.8.7", + "@novasamatech/host-api": "0.8.8", + "@novasamatech/host-container": "0.8.8", + "@novasamatech/host-papp": "0.8.8", "@novasamatech/sdk-statement": "^0.6.0", - "@novasamatech/statement-store": "0.8.7", + "@novasamatech/statement-store": "0.8.8", "@polkadot-api/json-rpc-provider": "^0.2.0", "neverthrow": "^8.2.0", "polkadot-api": "^2.1.6", @@ -358,9 +358,6 @@ ], }, }, - "patchedDependencies": { - "@novasamatech/host-papp@0.8.7": "patches/@novasamatech%2Fhost-papp@0.8.7.patch", - }, "overrides": { "smoldot": "3.2.0", }, @@ -699,21 +696,21 @@ "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], - "@novasamatech/host-api": ["@novasamatech/host-api@0.8.7", "", { "dependencies": { "@novasamatech/scale": "0.8.7", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", "scale-ts": "1.6.1" } }, "sha512-Kjvo5Y3Ad8EGDQPfEUnotS9hPl2SOBHNavw2+I8t+wQDPYepSTNIQZxzKOF2iS8JXhHTVuwIiaM1AM/OvfS8+A=="], + "@novasamatech/host-api": ["@novasamatech/host-api@0.8.8", "", { "dependencies": { "@novasamatech/scale": "0.8.8", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", "scale-ts": "1.6.1" } }, "sha512-dTO5tQGJnyy6SOZaR1Z6+yzH2CmJp8wcZScVF1HHqqeMe1uVWcioyTH6jf9Rw46U01uLaa5unembuNmJIOC2ew=="], - "@novasamatech/host-container": ["@novasamatech/host-container@0.8.7", "", { "dependencies": { "@noble/hashes": "2.2.0", "@novasamatech/host-api": "0.8.7", "@polkadot-api/substrate-client": "^0.7.0", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", "polkadot-api": ">=2" } }, "sha512-8c+1Dr7HvUxX67Z8TW2z6qb86PAK/54KkEknIz0aQSsN8OJFFVWKnr/QhrZFN0ztmj982atIZvfu3HpfIEcfHQ=="], + "@novasamatech/host-container": ["@novasamatech/host-container@0.8.8", "", { "dependencies": { "@noble/hashes": "2.2.0", "@novasamatech/host-api": "0.8.8", "@polkadot-api/substrate-client": "^0.7.0", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", "polkadot-api": ">=2" } }, "sha512-Caof2YgqJlr96r5mBFNkYQ7Gpfi6pytJdxj7Zqq1o2np8Gc3LzWz6DHLxmQvx64abu9rqR3m5sapCI8lUZrUSg=="], - "@novasamatech/host-papp": ["@novasamatech/host-papp@0.8.7", "", { "dependencies": { "@noble/ciphers": "2.2.0", "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@novasamatech/host-api": "0.8.7", "@novasamatech/scale": "0.8.7", "@novasamatech/statement-store": "0.8.7", "@novasamatech/storage-adapter": "0.8.7", "@polkadot-api/utils": "^0.4.0", "@polkadot-labs/hdkd-helpers": "^0.0.30", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", "polkadot-api": ">=2", "rxjs": "^7.8.2", "scale-ts": "1.6.1" } }, "sha512-og8wsnquiZIU+qJC1zzhGldujT1Jy9kB2psdpf94V2UkTa7BbkqFI9lBdI9n3gO2T7elTnFqf9rs/rrU2s7ZXQ=="], + "@novasamatech/host-papp": ["@novasamatech/host-papp@0.8.8", "", { "dependencies": { "@noble/ciphers": "2.2.0", "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@novasamatech/host-api": "0.8.8", "@novasamatech/scale": "0.8.8", "@novasamatech/statement-store": "0.8.8", "@novasamatech/storage-adapter": "0.8.8", "@polkadot-api/utils": "^0.4.0", "@polkadot-labs/hdkd-helpers": "^0.0.30", "nanoevents": "9.1.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", "polkadot-api": ">=2", "rxjs": "^7.8.2", "scale-ts": "1.6.1" } }, "sha512-WNHHP3rkUkAB/6Ddm4zc1wywrwVjRC/2eWWLDol7uz8WKbWZnB8tpmYGFmnWLZQttelpM7kIOp8Kg1Vd//wc/w=="], - "@novasamatech/scale": ["@novasamatech/scale@0.8.7", "", { "dependencies": { "@polkadot-api/utils": "^0.4.0", "scale-ts": "1.6.1" } }, "sha512-t9p5YyXkGzXvUaT2mltgcnTHdt32XB3kodNEhWAty9uFZiVfwLB6PYNRsW/CHUviof7Gqqjgw/LrPZQmys8jdg=="], + "@novasamatech/scale": ["@novasamatech/scale@0.8.8", "", { "dependencies": { "@polkadot-api/utils": "^0.4.0", "scale-ts": "1.6.1" } }, "sha512-cWg4RkrUoysc+q7zQnr2vC+JmcuRLZFft3ZITkq1U6d7wDAYGJjzTYGKLDxIe2HZ+mRhU7otFCz1aaFtBEu98w=="], "@novasamatech/sdk-statement": ["@novasamatech/sdk-statement@0.6.0", "", { "dependencies": { "@polkadot-api/substrate-bindings": "0.20.1", "@polkadot-api/utils": "^0.4.0", "polkadot-api": "^2.0.2" } }, "sha512-NTqM+yS45iHgy87lVSWIpFozrTfCUWb7r4ZpsDtN1eFnfixXpDKpPXDWQsvPs+nh18r/jmoMgtlTjUltrS6mYQ=="], - "@novasamatech/statement-store": ["@novasamatech/statement-store@0.8.7", "", { "dependencies": { "@noble/ciphers": "2.2.0", "@noble/hashes": "2.2.0", "@novasamatech/scale": "0.8.7", "@novasamatech/sdk-statement": "^0.6.0", "@novasamatech/substrate-slot-sr25519-wasm": "0.8.7", "@polkadot-api/substrate-bindings": "^0.20.3", "@polkadot-api/substrate-client": "^0.7.0", "@polkadot-labs/hdkd-helpers": "^0.0.30", "@polkadot-labs/schnorrkel-wasm": "0.0.9", "@scure/sr25519": "2.2.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", "polkadot-api": ">=2", "scale-ts": "1.6.1" } }, "sha512-vRlcTHpdI0UqUHq5lvWG4WhSTbbvf/fW7nfTJmF2k97pdI4bR0VPGlGdvHNyIKFHXx4OPQWGzA5Cdbf85d7sWA=="], + "@novasamatech/statement-store": ["@novasamatech/statement-store@0.8.8", "", { "dependencies": { "@noble/ciphers": "2.2.0", "@noble/hashes": "2.2.0", "@novasamatech/scale": "0.8.8", "@novasamatech/sdk-statement": "^0.6.0", "@novasamatech/substrate-slot-sr25519-wasm": "0.8.8", "@polkadot-api/substrate-bindings": "^0.20.3", "@polkadot-api/substrate-client": "^0.7.0", "@polkadot-labs/hdkd-helpers": "^0.0.30", "@polkadot-labs/schnorrkel-wasm": "0.0.9", "@scure/sr25519": "2.2.0", "nanoid": "5.1.11", "neverthrow": "^8.2.0", "polkadot-api": ">=2", "scale-ts": "1.6.1" } }, "sha512-2D71j3VzTIknlUlCfrnQG3l1lzxkZrASNh+w1dp0HClMrKhlcdSiZDGrPPRkVjcOi2zt3KSLH+i5+5EOuZ66Yw=="], - "@novasamatech/storage-adapter": ["@novasamatech/storage-adapter@0.8.7", "", { "dependencies": { "nanoevents": "^9.1.0", "neverthrow": "^8.2.0" } }, "sha512-cA8VL6ghEETja1Q4rt11/njW3mCvkT9HdgQo5Hv7WBrRTcyb0rV34sdQvsbr+PpLBem/xLYBRTXzOK518ce8ww=="], + "@novasamatech/storage-adapter": ["@novasamatech/storage-adapter@0.8.8", "", { "dependencies": { "nanoevents": "^9.1.0", "neverthrow": "^8.2.0" } }, "sha512-q83gWxMUyooDZGDlriZ+NXFCk8MZHHCFJBiCXMMnAs6CzUAIlnFxyi8/PpUgXZUinEQCgpmKkEbx8hYMwKRdrw=="], - "@novasamatech/substrate-slot-sr25519-wasm": ["@novasamatech/substrate-slot-sr25519-wasm@0.8.7", "", {}, "sha512-FLjoXY05IBKGIF9KHO3488NFkSGBg5yqL8jl47VaC4YE8CiW5E5rxLRH55l0FXMnYPc8gdP/fvI7JH3wt6ac4g=="], + "@novasamatech/substrate-slot-sr25519-wasm": ["@novasamatech/substrate-slot-sr25519-wasm@0.8.8", "", {}, "sha512-clW1YI19AXiR28/DT79IyoXL9MukJlkT2bOSfmuzwwVPZ3QYmkIZ7RUiz/74r7edy56W1rM8NK9F/s5bWAmwYQ=="], "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], diff --git a/package.json b/package.json index 10648af6..f87854f1 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,5 @@ }, "overrides": { "smoldot": "3.2.0" - }, - "patchedDependencies": { - "@novasamatech/host-papp@0.8.7": "patches/@novasamatech%2Fhost-papp@0.8.7.patch" } } diff --git a/packages/auth/package.json b/packages/auth/package.json index 839a9606..c5c60765 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -29,11 +29,11 @@ "@dotli/resolver": "workspace:*", "@dotli/shared": "workspace:*", "@noble/hashes": "^2.2.0", - "@novasamatech/host-api": "0.8.7", - "@novasamatech/host-papp": "0.8.7", + "@novasamatech/host-api": "0.8.8", + "@novasamatech/host-papp": "0.8.8", "@novasamatech/sdk-statement": "^0.6.0", - "@novasamatech/statement-store": "0.8.7", - "@novasamatech/storage-adapter": "0.8.7", + "@novasamatech/statement-store": "0.8.8", + "@novasamatech/storage-adapter": "0.8.8", "@scure/sr25519": "^2.2.0", "neverthrow": "^8.2.0", "polkadot-api": "^2.1.6", diff --git a/packages/truapi-debug/package.json b/packages/truapi-debug/package.json index acc1d867..ee708d72 100644 --- a/packages/truapi-debug/package.json +++ b/packages/truapi-debug/package.json @@ -20,9 +20,9 @@ }, "dependencies": { "@dotli/shared": "workspace:*", - "@novasamatech/host-api": "0.8.7", - "@novasamatech/host-container": "0.8.7", - "@novasamatech/host-papp": "0.8.7", + "@novasamatech/host-api": "0.8.8", + "@novasamatech/host-container": "0.8.8", + "@novasamatech/host-papp": "0.8.8", "nanoevents": "^9.1.0" } } diff --git a/packages/ui/package.json b/packages/ui/package.json index a7b47c68..6508a663 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -32,11 +32,11 @@ "@dotli/storage": "workspace:*", "@dotli/truapi-debug": "workspace:*", "@noble/hashes": "^2.2.0", - "@novasamatech/host-api": "0.8.7", - "@novasamatech/host-container": "0.8.7", - "@novasamatech/host-papp": "0.8.7", + "@novasamatech/host-api": "0.8.8", + "@novasamatech/host-container": "0.8.8", + "@novasamatech/host-papp": "0.8.8", "@novasamatech/sdk-statement": "^0.6.0", - "@novasamatech/statement-store": "0.8.7", + "@novasamatech/statement-store": "0.8.8", "@polkadot-api/json-rpc-provider": "^0.2.0", "neverthrow": "^8.2.0", "polkadot-api": "^2.1.6", diff --git a/patches/@novasamatech%2Fhost-papp@0.8.7.patch b/patches/@novasamatech%2Fhost-papp@0.8.7.patch deleted file mode 100644 index 4eb2367b..00000000 --- a/patches/@novasamatech%2Fhost-papp@0.8.7.patch +++ /dev/null @@ -1,392 +0,0 @@ -diff --git a/dist/index.d.ts b/dist/index.d.ts -index 1bd2a09bfc584e7368da35215a6ed12dbae54bd9..381d2cb6f2b271712a4367b57cbb34434afa99b4 100644 ---- a/dist/index.d.ts -+++ b/dist/index.d.ts -@@ -9,6 +9,6 @@ export { AllowanceError } from './sso/allowance/index.js'; - export type { UserSession } from './sso/sessionManager/userSession.js'; - export type { StoredUserSession } from './sso/userSessionRepository.js'; - export type { Identity } from './identity/types.js'; --export type { SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, SigningRequest, } from './sso/sessionManager/scale/signing.js'; -+export type { SignRawLegacyRequest, SignRawLegacyResponse, SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, SigningRequest, } from './sso/sessionManager/scale/signing.js'; - export type { RingVrfAliasRequest, RingVrfAliasResponse } from './sso/sessionManager/scale/ringVrf.js'; --export type { CreateTransactionRequest, CreateTransactionResponse, } from './sso/sessionManager/scale/createTransaction.js'; -+export type { CreateTransactionLegacyRequest, CreateTransactionRequest, CreateTransactionResponse, } from './sso/sessionManager/scale/createTransaction.js'; -diff --git a/dist/sso/auth/attestationService.d.ts b/dist/sso/auth/attestationService.d.ts -index e9abeb9879cce07e48074cd30a1e99186e642b7c..904a20834db5f5e5e74a344938f6b9b5a46aa4c3 100644 ---- a/dist/sso/auth/attestationService.d.ts -+++ b/dist/sso/auth/attestationService.d.ts -@@ -3,7 +3,7 @@ import type { ResultAsync } from 'neverthrow'; - import type { DerivedSr25519Account } from '../../crypto.js'; - export declare function createSudoAliceVerifier(): DerivedSr25519Account; - export declare function withRetry(fn: () => Promise, maxRetries?: number): Promise; --export declare const createAttestationService: (lazyClient: LazyClient) => { -+export declare const createAttestationService: (lazyClient: LazyClient, debugFlowId?: string) => { - claimUsername(): string; - grantVerifierAllowance(verifier: DerivedSr25519Account): ResultAsync; - getRingRfKey(candidate: DerivedSr25519Account): Uint8Array; -diff --git a/dist/sso/auth/attestationService.js b/dist/sso/auth/attestationService.js -index 08b81aa0865019a94cf67ea29db2b6de1f3dba97..ea19c3cad34148cb0ecb3ac3e78d5fc078c1c674 100644 ---- a/dist/sso/auth/attestationService.js -+++ b/dist/sso/auth/attestationService.js -@@ -9,6 +9,7 @@ import { mergeUint8 } from 'polkadot-api/utils'; - import { Bytes, Option, Tuple, str } from 'scale-ts'; - import { member_from_entropy, sign } from 'verifiablejs/bundler'; - import { deriveSr25519Account, getEncrPub, stringToBytes } from '../../crypto.js'; -+import { emitHostPappDebugMessage } from '../../debugBus.js'; - import { toError } from '../../helpers/utils.js'; - const accountId = AccountId(); - export function createSudoAliceVerifier() { -@@ -22,11 +23,21 @@ export function withRetry(fn, maxRetries = 1) { - throw error; - }); - } --export const createAttestationService = (lazyClient) => { -+export const createAttestationService = (lazyClient, debugFlowId) => { - const service = { - claimUsername() { - const nameSuffixFactory = customAlphabet('abcdefghijklmnopqrstuvwxyz', 4); -- return `guest${nameSuffixFactory()}.${createNumericSuffix(4)}`; -+ const username = `guest${nameSuffixFactory()}.${createNumericSuffix(4)}`; -+ if (debugFlowId !== undefined) { -+ emitHostPappDebugMessage({ -+ layer: 'attestation', -+ event: 'username_claimed', -+ flowId: debugFlowId, -+ timestamp: Date.now(), -+ payload: { username }, -+ }); -+ } -+ return username; - }, - grantVerifierAllowance(verifier) { - const client = lazyClient.getClient(); -@@ -46,7 +57,19 @@ export const createAttestationService = (lazyClient) => { - }); - return withRetry(() => sudoCall.signAndSubmit(createPeopleSigner(verifier)).then(() => undefined)); - }, toError); -- return verifierAllowance.andThen(verifierAllowance => (verifierAllowance > 0 ? okAsync() : getAllowance())); -+ return verifierAllowance -+ .andThen(verifierAllowance => (verifierAllowance > 0 ? okAsync() : getAllowance())) -+ .andTee(() => { -+ if (debugFlowId !== undefined) { -+ emitHostPappDebugMessage({ -+ layer: 'attestation', -+ event: 'allowance_granted', -+ flowId: debugFlowId, -+ timestamp: Date.now(), -+ payload: { verifierAccountId: verifierAddress }, -+ }); -+ } -+ }); - }, - getRingRfKey(candidate) { - const verifiableEntropy = blake2b256(candidate.entropy); -@@ -65,6 +88,15 @@ export const createAttestationService = (lazyClient) => { - const usernameWithoutDigits = username.split('.')[0] ?? username; - const candidateSignature = candidate.sign(message); - const proofOfOwnership = sign(verifiableEntropy, message); -+ if (debugFlowId !== undefined) { -+ emitHostPappDebugMessage({ -+ layer: 'attestation', -+ event: 'vrf_proof_generated', -+ flowId: debugFlowId, -+ timestamp: Date.now(), -+ payload: { candidateAccountId: accountId.dec(candidate.publicKey) }, -+ }); -+ } - const ResourceSignatureCodec = Tuple( - // candidate PublicKey (32 bytes) - Bytes(32), -@@ -141,7 +173,21 @@ export const createAttestationService = (lazyClient) => { - }); - return fromPromise(withRetry(submitAttestation), toError).map(() => undefined); - }) -- .andTee(() => console.log(`Attestation for ${accountId.dec(candidate.publicKey)} successfully passed.`)); -+ .andTee(() => { -+ console.log(`Attestation for ${accountId.dec(candidate.publicKey)} successfully passed.`); -+ if (debugFlowId !== undefined) { -+ emitHostPappDebugMessage({ -+ layer: 'attestation', -+ event: 'person_registered', -+ flowId: debugFlowId, -+ timestamp: Date.now(), -+ payload: { -+ username, -+ candidateAccountId: accountId.dec(candidate.publicKey), -+ }, -+ }); -+ } -+ }); - }, - }; - return service; -diff --git a/dist/sso/sessionManager/scale/createTransaction.d.ts b/dist/sso/sessionManager/scale/createTransaction.d.ts -index 9ba4944c703dca63598dceeedc8e25cd9bb0dd04..4bb23cab1e60cf675de3d01d04bdee54c09b7610 100644 ---- a/dist/sso/sessionManager/scale/createTransaction.d.ts -+++ b/dist/sso/sessionManager/scale/createTransaction.d.ts -@@ -16,6 +16,23 @@ export declare const CreateTransactionRequestCodec: import("scale-ts").Codec<{ - }; - }; - }>; -+export type CreateTransactionLegacyRequest = CodecType; -+export declare const CreateTransactionLegacyRequestCodec: import("scale-ts").Codec<{ -+ payload: { -+ tag: "v1"; -+ value: { -+ signer: Uint8Array; -+ genesisHash: Uint8Array; -+ callData: Uint8Array; -+ extensions: { -+ id: string; -+ extra: Uint8Array; -+ additionalSigned: Uint8Array; -+ }[]; -+ txExtVersion: number; -+ }; -+ }; -+}>; - export type CreateTransactionResponse = CodecType; - export declare const CreateTransactionResponseCodec: import("scale-ts").Codec<{ - respondingTo: string; -diff --git a/dist/sso/sessionManager/scale/createTransaction.js b/dist/sso/sessionManager/scale/createTransaction.js -index 2098c0a47b417bae549f99f65c2344de8bc82233..1d3796771fca3ac0f94c09cc052509bf18de5a6a 100644 ---- a/dist/sso/sessionManager/scale/createTransaction.js -+++ b/dist/sso/sessionManager/scale/createTransaction.js -@@ -1,4 +1,4 @@ --import { ProductAccountTransaction } from '@novasamatech/host-api'; -+import { LegacyTransaction, ProductAccountTransaction } from '@novasamatech/host-api'; - import { Enum } from '@novasamatech/scale'; - import { Bytes, Result, Struct, str } from 'scale-ts'; - export const CreateTransactionRequestCodec = Struct({ -@@ -6,6 +6,11 @@ export const CreateTransactionRequestCodec = Struct({ - v1: ProductAccountTransaction, - }), - }); -+export const CreateTransactionLegacyRequestCodec = Struct({ -+ payload: Enum({ -+ v1: LegacyTransaction, -+ }), -+}); - export const CreateTransactionResponseCodec = Struct({ - // referencing to RemoteMessage.messageId - respondingTo: str, -diff --git a/dist/sso/sessionManager/scale/remoteMessage.d.ts b/dist/sso/sessionManager/scale/remoteMessage.d.ts -index 545a5ae4b66e54277f1509b781cffcf25719a469..e651b3047460e4e41b3577ae9acbff7b0a5441ed 100644 ---- a/dist/sso/sessionManager/scale/remoteMessage.d.ts -+++ b/dist/sso/sessionManager/scale/remoteMessage.d.ts -@@ -143,6 +143,42 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{ - respondingTo: string; - signedTransaction: import("scale-ts").ResultPayload, string>; - }; -+ } | { -+ tag: "CreateTransactionLegacyRequest"; -+ value: { -+ payload: { -+ tag: "v1"; -+ value: { -+ signer: Uint8Array; -+ genesisHash: Uint8Array; -+ callData: Uint8Array; -+ extensions: { -+ id: string; -+ extra: Uint8Array; -+ additionalSigned: Uint8Array; -+ }[]; -+ txExtVersion: number; -+ }; -+ }; -+ }; -+ } | { -+ tag: "SignRawLegacyRequest"; -+ value: { -+ account: Uint8Array; -+ data: { -+ tag: "Bytes"; -+ value: Uint8Array; -+ } | { -+ tag: "Payload"; -+ value: string; -+ }; -+ }; -+ } | { -+ tag: "SignRawLegacyResponse"; -+ value: { -+ respondingTo: string; -+ signature: import("scale-ts").ResultPayload, string>; -+ }; - }; - }; - }>; -diff --git a/dist/sso/sessionManager/scale/remoteMessage.js b/dist/sso/sessionManager/scale/remoteMessage.js -index e89bb16e7a2e939be3548c724bbafca8ae42df58..8fccd1a378afd9f065eae94aa8fd44ccf351513b 100644 ---- a/dist/sso/sessionManager/scale/remoteMessage.js -+++ b/dist/sso/sessionManager/scale/remoteMessage.js -@@ -1,8 +1,8 @@ - import { Enum, Struct, _void, str } from 'scale-ts'; --import { CreateTransactionRequestCodec, CreateTransactionResponseCodec } from './createTransaction.js'; -+import { CreateTransactionLegacyRequestCodec, CreateTransactionRequestCodec, CreateTransactionResponseCodec, } from './createTransaction.js'; - import { ResourceAllocationRequestCodec, ResourceAllocationResponseCodec } from './resourceAllocation.js'; - import { RingVrfAliasRequestCodec, RingVrfAliasResponseCodec } from './ringVrf.js'; --import { SigningRequestCodec, SigningResponseCodec } from './signing.js'; -+import { SignRawLegacyRequestCodec, SignRawLegacyResponseCodec, SigningRequestCodec, SigningResponseCodec, } from './signing.js'; - export const RemoteMessageCodec = Struct({ - messageId: str, - data: Enum({ -@@ -16,6 +16,9 @@ export const RemoteMessageCodec = Struct({ - ResourceAllocationResponse: ResourceAllocationResponseCodec, - CreateTransactionRequest: CreateTransactionRequestCodec, - CreateTransactionResponse: CreateTransactionResponseCodec, -+ CreateTransactionLegacyRequest: CreateTransactionLegacyRequestCodec, -+ SignRawLegacyRequest: SignRawLegacyRequestCodec, -+ SignRawLegacyResponse: SignRawLegacyResponseCodec, - }), - }), - }); -diff --git a/dist/sso/sessionManager/scale/signing.d.ts b/dist/sso/sessionManager/scale/signing.d.ts -index b2787476cd2afed4a3914304425470fa10d730b9..b8f0f220835b6e50ec24107e7f6e6d18412564ff 100644 ---- a/dist/sso/sessionManager/scale/signing.d.ts -+++ b/dist/sso/sessionManager/scale/signing.d.ts -@@ -29,6 +29,22 @@ export declare const SigningRawRequestCodec: import("scale-ts").Codec<{ - value: string; - }; - }>; -+export type SignRawLegacyRequest = CodecType; -+export declare const SignRawLegacyRequestCodec: import("scale-ts").Codec<{ -+ account: Uint8Array; -+ data: { -+ tag: "Bytes"; -+ value: Uint8Array; -+ } | { -+ tag: "Payload"; -+ value: string; -+ }; -+}>; -+export type SignRawLegacyResponse = CodecType; -+export declare const SignRawLegacyResponseCodec: import("scale-ts").Codec<{ -+ respondingTo: string; -+ signature: import("scale-ts").ResultPayload, string>; -+}>; - export type SigningRequest = CodecType; - export declare const SigningRequestCodec: import("scale-ts").Codec<{ - tag: "Payload"; -diff --git a/dist/sso/sessionManager/scale/signing.js b/dist/sso/sessionManager/scale/signing.js -index 80726670653efc6df6e3a0fe50ef83d45b296456..81e9ef7010e7ab53720cc76796b012666effbd6b 100644 ---- a/dist/sso/sessionManager/scale/signing.js -+++ b/dist/sso/sessionManager/scale/signing.js -@@ -1,4 +1,4 @@ --import { ProductAccountId } from '@novasamatech/host-api'; -+import { AccountId, ProductAccountId } from '@novasamatech/host-api'; - import { Enum, Hex, OptionBool } from '@novasamatech/scale'; - import { Bytes, Option, Result, Struct, Vector, str, u32 } from 'scale-ts'; - export const SigningPayloadRequestCodec = Struct({ -@@ -26,6 +26,18 @@ export const SigningRawRequestCodec = Struct({ - Payload: str, - }), - }); -+export const SignRawLegacyRequestCodec = Struct({ -+ account: AccountId, -+ data: Enum({ -+ Bytes: Bytes(), -+ Payload: str, -+ }), -+}); -+export const SignRawLegacyResponseCodec = Struct({ -+ // referencing to RemoteMessage.messageId -+ respondingTo: str, -+ signature: Result(Bytes(), str), -+}); - export const SigningRequestCodec = Enum({ - Payload: SigningPayloadRequestCodec, - Raw: SigningRawRequestCodec, -diff --git a/dist/sso/sessionManager/userSession.d.ts b/dist/sso/sessionManager/userSession.d.ts -index 15deafd950d7296ed96e6e67501f9d067bbfaf95..ecfbafebb1e92ba972ba4fff71064795ee6ab498 100644 ---- a/dist/sso/sessionManager/userSession.d.ts -+++ b/dist/sso/sessionManager/userSession.d.ts -@@ -5,16 +5,18 @@ import { ResultAsync } from 'neverthrow'; - import type { CodecType } from 'scale-ts'; - import type { Callback } from '../../types.js'; - import type { StoredUserSession } from '../userSessionRepository.js'; --import type { CreateTransactionRequest } from './scale/createTransaction.js'; -+import type { CreateTransactionLegacyRequest, CreateTransactionRequest } from './scale/createTransaction.js'; - import { RemoteMessageCodec } from './scale/remoteMessage.js'; - import type { ApAllocationOutcome, ResourceAllocationRequest } from './scale/resourceAllocation.js'; --import type { SigningPayloadRequest, SigningPayloadResponseData, SigningRawRequest } from './scale/signing.js'; -+import type { SignRawLegacyRequest, SigningPayloadRequest, SigningPayloadResponseData, SigningRawRequest } from './scale/signing.js'; - export type UserSession = StoredUserSession & { - sendDisconnectMessage(): ResultAsync; - abortPendingRequests(): ResultAsync; - signPayload(payload: SigningPayloadRequest): ResultAsync; - signRaw(payload: SigningRawRequest): ResultAsync; -+ signRawLegacy(payload: SignRawLegacyRequest): ResultAsync; - createTransaction(payload: CreateTransactionRequest): ResultAsync; -+ createTransactionLegacy(payload: CreateTransactionLegacyRequest): ResultAsync; - getRingVrfAlias(productAccountId: CodecType, productId: string): ResultAsync, Error>; - requestResourceAllocation(request: ResourceAllocationRequest): ResultAsync; - subscribe(callback: Callback, ResultAsync>): VoidFunction; -diff --git a/dist/sso/sessionManager/userSession.js b/dist/sso/sessionManager/userSession.js -index e75b6a664f828b82e1e32c1d59443ee72779ed32..3de5815e5ea13a318c1be1364405ac00f3d902a6 100644 ---- a/dist/sso/sessionManager/userSession.js -+++ b/dist/sso/sessionManager/userSession.js -@@ -162,6 +162,31 @@ export function createUserSession({ userSession, statementStore, encryption, sto - return withHostActionTrace(withQueueTimeout(inner, 'signRaw'), messageId, userSession.id); - }); - }, -+ signRawLegacy(payload) { -+ return enqueue(() => { -+ const messageId = nanoid(); -+ const data = enumValue('v1', enumValue('SignRawLegacyRequest', payload)); -+ emitHostAction(messageId, actionKindFromMessageData(data), userSession.id); -+ const responseFilter = (message) => { -+ if (message.data.tag === 'v1' && -+ message.data.value.tag === 'SignRawLegacyResponse' && -+ message.data.value.value.respondingTo === messageId) { -+ return message.data.value.value.signature; -+ } -+ }; -+ const request = session.request(RemoteMessageCodec, { messageId, data }); -+ const reply = session.waitForRequestMessage(RemoteMessageCodec, responseFilter); -+ const inner = awaitReplyOrAckFailure(request, reply).andThen(message => { -+ if (message.success) { -+ return ok(message.value); -+ } -+ else { -+ return err(new Error(message.value)); -+ } -+ }); -+ return withHostActionTrace(withQueueTimeout(inner, 'signRawLegacy'), messageId, userSession.id); -+ }); -+ }, - createTransaction(payload) { - return enqueue(() => { - const messageId = nanoid(); -@@ -186,6 +211,30 @@ export function createUserSession({ userSession, statementStore, encryption, sto - return withQueueTimeout(inner, 'createTransaction'); - }); - }, -+ createTransactionLegacy(payload) { -+ return enqueue(() => { -+ const messageId = nanoid(); -+ const data = enumValue('v1', enumValue('CreateTransactionLegacyRequest', payload)); -+ const responseFilter = (message) => { -+ if (message.data.tag === 'v1' && -+ message.data.value.tag === 'CreateTransactionResponse' && -+ message.data.value.value.respondingTo === messageId) { -+ return message.data.value.value.signedTransaction; -+ } -+ }; -+ const request = session.request(RemoteMessageCodec, { messageId, data }); -+ const reply = session.waitForRequestMessage(RemoteMessageCodec, responseFilter); -+ const inner = awaitReplyOrAckFailure(request, reply).andThen(message => { -+ if (message.success) { -+ return ok(message.value); -+ } -+ else { -+ return err(new Error(message.value)); -+ } -+ }); -+ return withQueueTimeout(inner, 'createTransactionLegacy'); -+ }); -+ }, - getRingVrfAlias(productAccountId, productId) { - return enqueue(() => { - const messageId = nanoid(); From f4537842653f67dc2987883f99078688c5d5bdbb Mon Sep 17 00:00:00 2001 From: Tiago Tavares Date: Mon, 15 Jun 2026 11:38:06 +0100 Subject: [PATCH 3/4] feat: bridge relay and People chains in rpc-gateway mode so login and identity resolution work without smoldot --- packages/resolver/src/rpc-chain.ts | 74 +++++++++++++++++------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/packages/resolver/src/rpc-chain.ts b/packages/resolver/src/rpc-chain.ts index c175e843..34a3d034 100644 --- a/packages/resolver/src/rpc-chain.ts +++ b/packages/resolver/src/rpc-chain.ts @@ -1,46 +1,58 @@ // Copyright 2026 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: AGPL-3.0-only -// dot.li WSS JSON-RPC chain provider (gateway mode). -// -// Produces a `JsonRpcProvider` backed by a public Polkadot RPC node instead -// of smoldot. Used by the protocol host iframe when running in `rpc` submode -// so sandboxed apps can issue chain calls via `chainConnect` without -// requiring a light client. -// -// Trust model: the RPC endpoints are trusted. This is the same trade-off -// gateway-mode name resolution already makes in `./rpc-resolve.ts`. -// -// Intentionally does not import smoldot so Vite can tree-shake the worker -// out of any bundle that only pulls this module. -// -// Currently only Asset Hub Paseo has curated RPC endpoints in -// `@dotli/config`. Other supported chains (relay, Bulletin) fall back to -// `null`, meaning sandboxed apps will see an immediate "chain unsupported" -// error instead of silently hanging. Add more endpoints to config to widen -// coverage. +/** + * WSS JSON-RPC chain providers for gateway mode. + * + * Produces a `JsonRpcProvider` backed by a public Polkadot RPC node instead + * of smoldot. The protocol host iframe uses these in `rpc` submode so + * sandboxed apps can issue chain calls via `chainConnect` without a light + * client. The endpoints are trusted, the same posture `./rpc-resolve.ts` + * already takes for gateway-mode name resolution. + * + * smoldot is never imported here, so Vite tree-shakes the light client out of + * any bundle that only pulls this module. + * + * Coverage is the active network's relay, Asset Hub, and People chains, each + * dialled through its configured `rpcs`. Login and identity resolution live + * on the People chain, so it must be reachable for auth to work in gateway + * mode. Bulletin is deliberately absent. Its content (IPFS or bitswap) is + * served through IPFS gateways, not a chain RPC connection. + */ import { getWsProvider } from "polkadot-api/ws"; import type { JsonRpcProvider } from "polkadot-api"; import { getActiveServicesConfig } from "@dotli/config/network"; +import type { ChainService } from "@dotli/config/network"; -export function isRpcChainSupported(genesisHash: string): boolean { - return ( - genesisHash.toLowerCase() === - getActiveServicesConfig().assethub.genesis.toLowerCase() +/** Resolve a genesis hash to its active-network chain, or `null` when gateway mode cannot reach it. */ +function gatewayChain(genesisHash: string): ChainService | null { + const cfg = getActiveServicesConfig(); + const key = genesisHash.toLowerCase(); + const chain = [cfg.relay, cfg.assethub, cfg.people].find( + (c) => c.genesis.toLowerCase() === key, ); + if (chain === undefined || chain.rpcs.length === 0) { + return null; + } + return chain; +} + +/** Whether gateway mode can serve chain calls for `genesisHash`. */ +export function isRpcChainSupported(genesisHash: string): boolean { + return gatewayChain(genesisHash) !== null; } +/** A WSS JSON-RPC provider for `genesisHash`, or `null` when gateway mode does not support that chain. */ export function createRpcChainProvider( genesisHash: string, ): JsonRpcProvider | null { - const assethub = getActiveServicesConfig().assethub; - if (genesisHash.toLowerCase() === assethub.genesis.toLowerCase()) { - // Single active endpoint, no silent round-robin. - // Public RPC endpoints are occasionally tunnel-gated, so the default 40s - // heartbeat is too tight. Match the timeout used in `./rpc-resolve.ts`. - return getWsProvider([...assethub.rpcs], { - heartbeatTimeout: 120_000, - }); + const chain = gatewayChain(genesisHash); + if (chain === null) { + return null; } - return null; + // Public RPC endpoints are occasionally tunnel-gated, so the default 40s + // heartbeat is too tight. Match the timeout used in `./rpc-resolve.ts`. + return getWsProvider([...chain.rpcs], { + heartbeatTimeout: 120_000, + }); } From ff99f3f26d4834cf13f46978af3b51484076005c Mon Sep 17 00:00:00 2001 From: Tiago Tavares Date: Mon, 15 Jun 2026 11:56:16 +0100 Subject: [PATCH 4/4] fix: warm the People chain at boot so legacy-account auth reads don't race a cold parachain sync --- apps/protocol/src/main.ts | 8 ++ apps/protocol/src/protocol-shared-worker.ts | 19 +++++ packages/config/src/timeouts.ts | 2 + packages/resolver/src/resolve.ts | 85 +++++++++++++++++++++ 4 files changed, 114 insertions(+) diff --git a/apps/protocol/src/main.ts b/apps/protocol/src/main.ts index c432cdf9..4d278502 100644 --- a/apps/protocol/src/main.ts +++ b/apps/protocol/src/main.ts @@ -675,6 +675,7 @@ async function initDirectMode(): Promise { resolveOwner, resolveRootManifest, setResolverAssetHubProvider, + waitForPeopleFinalized, } = resolve; const { terminateSmoldot, onSmoldotFatal } = smoldotMod; @@ -718,6 +719,13 @@ async function initDirectMode(): Promise { onWarmup: async () => { getSmoldot(); await getRelayChain(); + // Warm People in the background so legacy-account auth reads do not race + // a cold parachain warp sync. Not needed for resolution, so do not await. + void waitForPeopleFinalized().catch((err: unknown) => { + log.warn( + `[dot.li protocol] People chain warm failed (retried on demand): ${String(err)}`, + ); + }); }, resolveDotName, resolveOwner, diff --git a/apps/protocol/src/protocol-shared-worker.ts b/apps/protocol/src/protocol-shared-worker.ts index 3289390c..5d00cbc7 100644 --- a/apps/protocol/src/protocol-shared-worker.ts +++ b/apps/protocol/src/protocol-shared-worker.ts @@ -30,6 +30,7 @@ import { resolveRootManifest, setResolverAssetHubProvider, waitForAssetHubFinalized, + waitForPeopleFinalized, } from "@dotli/resolver/resolve"; import { onSmoldotFatal } from "@dotli/resolver/smoldot"; import { m } from "@dotli/metrics/metrics"; @@ -214,6 +215,24 @@ async function presync(): Promise { port.postMessage(readyMsg); } pendingPorts.length = 0; + + // Warm the People chain in the background. Legacy-account auth reads the + // username -> account map on People, and on a cold start that read races + // the parachain warp sync (the source of the intermittent failures). Start + // syncing it now so it is ready by the time auth runs. People is not needed + // for resolution, so this must not gate the ready signal above. + swLog("Warming People chain in background..."); + void waitForPeopleFinalized((msg) => { + swLog(`People warm status: ${msg}`); + }) + .then(() => { + swLog("People chain warmed"); + }) + .catch((err: unknown) => { + swLog( + `People chain warm failed (retried on demand): ${serializeError(err)}`, + ); + }); } catch (err: unknown) { const msg = serializeError(err); swError(`Pre-sync failed: ${msg}`); diff --git a/packages/config/src/timeouts.ts b/packages/config/src/timeouts.ts index 66cad02f..e6fa2114 100644 --- a/packages/config/src/timeouts.ts +++ b/packages/config/src/timeouts.ts @@ -18,4 +18,6 @@ export const TIMEOUTS = { SHARED_WORKER_READY: 210_000, /** Upper bound on `getFinalizedBlock()` while bootstrapping smoldot. */ ASSET_HUB_FINALIZED_SYNC: 180_000, + /** Upper bound on the background People-chain warm for legacy-account auth. */ + PEOPLE_FINALIZED_SYNC: 180_000, } as const; diff --git a/packages/resolver/src/resolve.ts b/packages/resolver/src/resolve.ts index b1128a71..d878e654 100644 --- a/packages/resolver/src/resolve.ts +++ b/packages/resolver/src/resolve.ts @@ -20,9 +20,12 @@ import { } from "./errors"; import { dur } from "@dotli/shared/perf"; import { log } from "@dotli/shared/log"; +import { getSmProvider } from "polkadot-api/sm-provider"; import { getSmoldot, getRelayChain, + getPeopleChain, + makeNonRemovingChain, onConnectionIssue, onSmoldotFatal, } from "./smoldot"; @@ -266,6 +269,88 @@ export async function waitForAssetHubFinalized( await ensureClient(onStatus, onPhase); } +// People chain warm-keep for legacy-account auth. +// +// Auth reads the username -> account map on the People chain. On a cold start +// that read races the parachain warp sync, which is the source of the +// intermittent "People read sometimes fails" reports. This mirrors the Asset +// Hub bootstrap above (open a follow, drive it to a finalized block with a +// bounded wait, invalidate on stop, keep the client alive) so the chain is +// already synced when auth connects. The one difference from Asset Hub: this is +// meant to run in the background and must NOT gate the ready signal, because +// resolution does not need People. +let peopleClientInstance: SubstrateClient | null = null; +let peopleApiInstance: Api | null = null; +let peoplePromise: Promise | null = null; + +function destroyPeopleClient(): void { + peopleApiInstance?.destroy(); + peopleClientInstance?.destroy(); + peopleApiInstance = null; + peopleClientInstance = null; + peoplePromise = null; +} + +export async function waitForPeopleFinalized( + onStatus?: StatusCallback, +): Promise { + if (peopleApiInstance) { + return; + } + peoplePromise ??= (async () => { + const initStart = performance.now(); + onStatus?.("Warming People chain..."); + // `makeNonRemovingChain` so a `getSmProvider` disconnect on this warm + // follow does not remove the shared smoldot chain out from under the + // dApp follows that connect through the broker. + const provider = getSmProvider(() => + getPeopleChain().then((chain) => makeNonRemovingChain(chain)), + ); + const client = createClient(provider); + const api = createRawApi(client); + try { + await Promise.race([ + api.whenReady(), + new Promise((_, reject) => { + setTimeout(() => { + reject( + new NetworkSyncTimeoutError( + "People Paseo", + TIMEOUTS.PEOPLE_FINALIZED_SYNC, + ), + ); + }, TIMEOUTS.PEOPLE_FINALIZED_SYNC); + }), + ]); + } catch (err) { + try { + api.destroy(); + client.destroy(); + // eslint-disable-next-line no-restricted-syntax -- best-effort teardown of a never-fully-initialised client; the real cause is rethrown on the next line. + } catch { + /* already dead, real cause rethrown below */ + } + peoplePromise = null; + throw err; + } + + // If the follow dies, invalidate so the next warm rebuilds against a fresh + // smoldot chain instead of reusing a dead one. + api.onStop(() => { + log.warn( + "[dot.li resolve] People chainHead follow stopped, invalidating warm client", + ); + destroyPeopleClient(); + }); + + peopleClientInstance = client; + peopleApiInstance = api; + log.warn(`[dot.li resolve] People chain warmed (${dur(initStart)})`); + return api; + })(); + await peoplePromise; +} + export async function resolveDotName( label: string, onStatus?: StatusCallback,