From 7006e201eb7f556b89f5c899aea090b59b4f2d62 Mon Sep 17 00:00:00 2001 From: Zygimantas <5236121+Zygimantass@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:30:46 +0200 Subject: [PATCH 1/2] Revert "fix(zones): zone auth key (#306)" This reverts commit 5fef06860e4b065bec6a81fa59e2b024eead039b. --- src/lib/useRootWebAuthnAccount.ts | 78 +++---------------------------- src/lib/useZoneAuthorization.ts | 2 +- 2 files changed, 7 insertions(+), 73 deletions(-) diff --git a/src/lib/useRootWebAuthnAccount.ts b/src/lib/useRootWebAuthnAccount.ts index 16a36da6..bdd661db 100644 --- a/src/lib/useRootWebAuthnAccount.ts +++ b/src/lib/useRootWebAuthnAccount.ts @@ -2,19 +2,15 @@ import { useQuery } from '@tanstack/react-query' import { Account } from 'viem/tempo' -import type { WebAuthnP256 } from 'viem/tempo' import { useConnection } from 'wagmi' -import { config, webAuthnRpId } from '../wagmi.config' type RootWebAuthnAccount = ReturnType -type RootWebAuthnCredential = WebAuthnP256.P256Credential type RootWebAuthnAccountProvider = { getAccount: (options: { accessKey?: boolean | undefined address?: `0x${string}` | undefined signable?: boolean | undefined }) => RootWebAuthnAccount - request: (args: { method: 'eth_accounts' }) => Promise } export function useRootWebAuthnAccount() { @@ -27,19 +23,12 @@ export function useRootWebAuthnAccount() { if (!address) throw new Error('account address not ready') if (!connector) throw new Error('connector not ready') - const provider = await connector.getProvider() - if (isRootWebAuthnAccountProvider(provider)) { - await waitForProviderAccount(provider, address as `0x${string}`) - - return provider.getAccount({ - accessKey: false, - address: address as `0x${string}`, - signable: true, - }) - } - - const credential = await waitForStoredCredential(address as `0x${string}`) - return accountFromCredential(credential) + const provider = (await connector.getProvider()) as RootWebAuthnAccountProvider + return provider.getAccount({ + accessKey: false, + address: address as `0x${string}`, + signable: true, + }) }, refetchOnReconnect: false, refetchOnWindowFocus: false, @@ -47,58 +36,3 @@ export function useRootWebAuthnAccount() { staleTime: Number.POSITIVE_INFINITY, }) } - -function accountFromCredential(credential: RootWebAuthnCredential) { - return Account.fromWebAuthnP256(credential, webAuthnRpId ? { rpId: webAuthnRpId } : undefined) -} - -function isRootWebAuthnAccountProvider(value: unknown): value is RootWebAuthnAccountProvider { - return Boolean( - value && - typeof value === 'object' && - 'getAccount' in value && - typeof value.getAccount === 'function' && - 'request' in value && - typeof value.request === 'function', - ) -} - -async function waitForProviderAccount( - provider: RootWebAuthnAccountProvider, - address: `0x${string}`, - timeoutMs = 5_000, -) { - const deadline = Date.now() + timeoutMs - const normalizedAddress = address.toLowerCase() - - while (Date.now() < deadline) { - const accounts = await provider.request({ method: 'eth_accounts' }) - if (accounts.some((account) => account.toLowerCase() === normalizedAddress)) return - - await new Promise((resolve) => setTimeout(resolve, 100)) - } - - throw new Error(`webauthn account ${address} not ready`) -} - -async function waitForStoredCredential( - address: `0x${string}`, - timeoutMs = 5_000, -): Promise { - const deadline = Date.now() + timeoutMs - const normalizedAddress = address.toLowerCase() - - while (Date.now() < deadline) { - const credential = await config.storage?.getItem('webAuthn.activeCredential') - if (credential) { - const account = accountFromCredential(credential as RootWebAuthnCredential) - if (account.address.toLowerCase() === normalizedAddress) { - return credential as RootWebAuthnCredential - } - } - - await new Promise((resolve) => setTimeout(resolve, 100)) - } - - throw new Error(`webauthn credential for ${address} not ready`) -} diff --git a/src/lib/useZoneAuthorization.ts b/src/lib/useZoneAuthorization.ts index 5fd3bea1..e236c7d6 100644 --- a/src/lib/useZoneAuthorization.ts +++ b/src/lib/useZoneAuthorization.ts @@ -94,7 +94,7 @@ export function useZoneAuthorization(parameters: { authorizeMutation, error: authorizeMutation.error ?? statusQuery.error, isAuthorized: statusQuery.data !== null && statusQuery.data !== undefined, - isChecking: statusQuery.fetchStatus === 'fetching', + isChecking: statusQuery.isPending, statusQuery, } } From ef85828c507283a6481be42101e7512dfa290c26 Mon Sep 17 00:00:00 2001 From: Zygimantas <5236121+Zygimantass@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:30:48 +0200 Subject: [PATCH 2/2] Revert "fix: correctness" This reverts commit bba04e1321c462de711898ef3404b299d0408997. --- e2e/deposit-to-a-zone.test.ts | 5 +-- e2e/send-tokens-across-zones.test.ts | 10 ++---- e2e/send-tokens-within-a-zone.test.ts | 5 +-- e2e/swap-across-zones.test.ts | 10 ++---- e2e/withdraw-from-a-zone.test.ts | 5 +-- src/components/guides/Demo.tsx | 3 -- .../guides/zones/SendTokensAcrossZones.tsx | 5 ++- src/lib/useRootWebAuthnAccount.ts | 27 ++++++--------- src/lib/useZoneAuthorization.ts | 24 ++------------ src/pages/protocol/zones/proving.mdx | 33 +++++++++---------- 10 files changed, 37 insertions(+), 90 deletions(-) diff --git a/e2e/deposit-to-a-zone.test.ts b/e2e/deposit-to-a-zone.test.ts index 56b2e73e..a57f5847 100644 --- a/e2e/deposit-to-a-zone.test.ts +++ b/e2e/deposit-to-a-zone.test.ts @@ -25,11 +25,8 @@ test('prepare zone access and deposit to Zone A', async ({ page }) => { timeout: 30000, }) - const authorizeButton = page - .getByRole('button', { name: /^Authoriz(?:e|ing) Zone A reads$/i }) - .first() + const authorizeButton = page.getByRole('button', { name: 'Authorize Zone A reads' }).first() await expect(authorizeButton).toBeVisible({ timeout: 30000 }) - await expect(authorizeButton).toBeEnabled({ timeout: 90000 }) await authorizeButton.click() const getFundsButton = page.getByRole('button', { name: /^Get testnet pathUSD$/i }).first() diff --git a/e2e/send-tokens-across-zones.test.ts b/e2e/send-tokens-across-zones.test.ts index 5a88b4a2..7e1ea954 100644 --- a/e2e/send-tokens-across-zones.test.ts +++ b/e2e/send-tokens-across-zones.test.ts @@ -25,19 +25,14 @@ test('send pathUSD from Zone A into Zone B', async ({ page }) => { timeout: 30000, }) - const authorizeSourceButton = page - .getByRole('button', { name: /^Authoriz(?:e|ing) Zone A reads$/i }) - .first() + const authorizeSourceButton = page.getByRole('button', { name: 'Authorize Zone A reads' }).first() await expect(authorizeSourceButton).toBeVisible({ timeout: 30000 }) - await expect(authorizeSourceButton).toBeEnabled({ timeout: 90000 }) await authorizeSourceButton.click() const getFundsButton = page.getByRole('button', { name: /^Get testnet pathUSD$/i }).first() const topUpButton = page.getByRole('button', { name: /^Approve \+ top up Zone A$/i }).first() const sendButton = page.getByRole('button', { name: /^Send 25 pathUSD into Zone B$/i }).first() - const authorizeTargetButton = page - .getByRole('button', { name: /^Authoriz(?:e|ing) Zone B reads$/i }) - .first() + const authorizeTargetButton = page.getByRole('button', { name: 'Authorize Zone B reads' }).first() await expect .poll( @@ -66,7 +61,6 @@ test('send pathUSD from Zone A into Zone B', async ({ page }) => { await sendButton.click() await expect(authorizeTargetButton).toBeVisible({ timeout: 120000 }) - await expect(authorizeTargetButton).toBeEnabled({ timeout: 90000 }) await authorizeTargetButton.click() await expect( diff --git a/e2e/send-tokens-within-a-zone.test.ts b/e2e/send-tokens-within-a-zone.test.ts index a8b9a743..f656dabe 100644 --- a/e2e/send-tokens-within-a-zone.test.ts +++ b/e2e/send-tokens-within-a-zone.test.ts @@ -25,11 +25,8 @@ test('prepare zone balance and send tokens within Zone A', async ({ page }) => { timeout: 30000, }) - const authorizeButton = page - .getByRole('button', { name: /^Authoriz(?:e|ing) Zone A reads$/i }) - .first() + const authorizeButton = page.getByRole('button', { name: 'Authorize Zone A reads' }).first() await expect(authorizeButton).toBeVisible({ timeout: 30000 }) - await expect(authorizeButton).toBeEnabled({ timeout: 90000 }) await authorizeButton.click() const getFundsButton = page.getByRole('button', { name: /^Get testnet pathUSD$/i }).first() diff --git a/e2e/swap-across-zones.test.ts b/e2e/swap-across-zones.test.ts index 2cf3edcf..c6db47bb 100644 --- a/e2e/swap-across-zones.test.ts +++ b/e2e/swap-across-zones.test.ts @@ -25,11 +25,8 @@ test('swap pathUSD from Zone A into betaUSD on Zone B', async ({ page }) => { timeout: 30000, }) - const authorizeSourceButton = page - .getByRole('button', { name: /^Authoriz(?:e|ing) Zone A reads$/i }) - .first() + const authorizeSourceButton = page.getByRole('button', { name: 'Authorize Zone A reads' }).first() await expect(authorizeSourceButton).toBeVisible({ timeout: 30000 }) - await expect(authorizeSourceButton).toBeEnabled({ timeout: 90000 }) await authorizeSourceButton.click() const getFundsButton = page.getByRole('button', { name: /^Get testnet pathUSD$/i }).first() @@ -37,9 +34,7 @@ test('swap pathUSD from Zone A into betaUSD on Zone B', async ({ page }) => { const swapButton = page .getByRole('button', { name: /^Swap 25 pathUSD into Zone B betaUSD$/i }) .first() - const authorizeTargetButton = page - .getByRole('button', { name: /^Authoriz(?:e|ing) Zone B reads$/i }) - .first() + const authorizeTargetButton = page.getByRole('button', { name: 'Authorize Zone B reads' }).first() await expect .poll( @@ -68,7 +63,6 @@ test('swap pathUSD from Zone A into betaUSD on Zone B', async ({ page }) => { await swapButton.click() await expect(authorizeTargetButton).toBeVisible({ timeout: 120000 }) - await expect(authorizeTargetButton).toBeEnabled({ timeout: 90000 }) await authorizeTargetButton.click() await expect( diff --git a/e2e/withdraw-from-a-zone.test.ts b/e2e/withdraw-from-a-zone.test.ts index 3f07c303..38741e9e 100644 --- a/e2e/withdraw-from-a-zone.test.ts +++ b/e2e/withdraw-from-a-zone.test.ts @@ -27,11 +27,8 @@ test('prepare zone balance and withdraw from Zone A', async ({ page }) => { timeout: 20000, }) - const authorizeButton = page - .getByRole('button', { name: /^Authoriz(?:e|ing) Zone A reads$/i }) - .first() + const authorizeButton = page.getByRole('button', { name: 'Authorize Zone A reads' }).first() await expect(authorizeButton).toBeVisible({ timeout: 30000 }) - await expect(authorizeButton).toBeEnabled({ timeout: 90000 }) await authorizeButton.click() const getFundsButton = page.getByRole('button', { name: /^Get testnet pathUSD$/i }).first() diff --git a/src/components/guides/Demo.tsx b/src/components/guides/Demo.tsx index 6c21f5ce..ceb60a3c 100644 --- a/src/components/guides/Demo.tsx +++ b/src/components/guides/Demo.tsx @@ -598,8 +598,6 @@ export function Button( ) { const { className, disabled, render, size, static: static_, variant, ...rest } = props const Element = render ? (p: typeof props) => React.cloneElement(render, p) : 'button' - const accessibilityProps = render ? { 'aria-disabled': disabled || undefined } : { disabled } - return ( ) diff --git a/src/components/guides/zones/SendTokensAcrossZones.tsx b/src/components/guides/zones/SendTokensAcrossZones.tsx index 7841ed48..89f13bf7 100644 --- a/src/components/guides/zones/SendTokensAcrossZones.tsx +++ b/src/components/guides/zones/SendTokensAcrossZones.tsx @@ -19,8 +19,7 @@ import { } from '../../../lib/private-zones.ts' import { useRootWebAuthnAccount } from '../../../lib/useRootWebAuthnAccount.ts' import { useZoneAuthorization, type ZoneAuthClientLike } from '../../../lib/useZoneAuthorization.ts' -import { Button, ExplorerLink, Logout, ReceiptHash, Step } from '../Demo' -import { SignInButtons } from '../EmbedPasskeys' +import { Button, ExplorerLink, Login, Logout, ReceiptHash, Step } from '../Demo' import { pathUsd } from '../tokens' import { useStickyStepCompletion } from './useStickyStepCompletion.ts' @@ -78,7 +77,7 @@ export function SendTokensAcrossZones() { : } + actions={connected ? : } error={undefined} number={1} title="Create or use a passkey account on the public chain." diff --git a/src/lib/useRootWebAuthnAccount.ts b/src/lib/useRootWebAuthnAccount.ts index bdd661db..2ca157d4 100644 --- a/src/lib/useRootWebAuthnAccount.ts +++ b/src/lib/useRootWebAuthnAccount.ts @@ -3,31 +3,24 @@ import { useQuery } from '@tanstack/react-query' import { Account } from 'viem/tempo' import { useConnection } from 'wagmi' +import { config, webAuthnRpId } from '../wagmi.config.ts' -type RootWebAuthnAccount = ReturnType -type RootWebAuthnAccountProvider = { - getAccount: (options: { - accessKey?: boolean | undefined - address?: `0x${string}` | undefined - signable?: boolean | undefined - }) => RootWebAuthnAccount -} +type RootWebAuthnCredential = Parameters[0] export function useRootWebAuthnAccount() { const { address, connector } = useConnection() return useQuery({ - enabled: Boolean(address && connector?.id === 'webAuthn'), - queryKey: ['root-webauthn-account', address], + enabled: Boolean(address && connector?.id === 'webAuthn' && webAuthnRpId), + queryKey: ['root-webauthn-account', address, webAuthnRpId], queryFn: async () => { - if (!address) throw new Error('account address not ready') - if (!connector) throw new Error('connector not ready') + if (!webAuthnRpId) throw new Error('webauthn RP ID is not configured') + + const credential = await config.storage?.getItem('webAuthn.activeCredential') + if (!credential) throw new Error('webauthn credential not available') - const provider = (await connector.getProvider()) as RootWebAuthnAccountProvider - return provider.getAccount({ - accessKey: false, - address: address as `0x${string}`, - signable: true, + return Account.fromWebAuthnP256(credential as RootWebAuthnCredential, { + rpId: webAuthnRpId, }) }, refetchOnReconnect: false, diff --git a/src/lib/useZoneAuthorization.ts b/src/lib/useZoneAuthorization.ts index e236c7d6..b41d1e7f 100644 --- a/src/lib/useZoneAuthorization.ts +++ b/src/lib/useZoneAuthorization.ts @@ -4,8 +4,6 @@ import { useMutation, useQuery } from '@tanstack/react-query' import type { Hex } from 'viem' import { Storage as ZoneStorage } from 'viem/tempo' -const zoneAuthorizationInfoTimeoutMs = 5_000 - export type ZoneAuthClientLike = { zone: { getAuthorizationTokenInfo: () => Promise<{ @@ -46,10 +44,7 @@ export function useZoneAuthorization(parameters: { if (accountToken) await storage.setItem(chainStorageKey, accountToken) try { - const info = await withTimeout( - zoneClient.zone.getAuthorizationTokenInfo(), - zoneAuthorizationInfoTimeoutMs, - ) + const info = await zoneClient.zone.getAuthorizationTokenInfo() const expired = info.expiresAt <= BigInt(Math.floor(Date.now() / 1000)) const matchesAccount = info.account.toLowerCase() === lowerAddress @@ -99,27 +94,12 @@ export function useZoneAuthorization(parameters: { } } -function withTimeout(promise: Promise, timeoutMs: number) { - return Promise.race([ - promise, - new Promise((_, reject) => { - const timeout = setTimeout(() => { - const error = new Error('zone authorization info request timed out') - error.name = 'TimeoutError' - reject(error) - }, timeoutMs) - - promise.finally(() => clearTimeout(timeout)) - }), - ]) -} - function isZoneAuthorizationError(error: unknown) { const status = getErrorStatus(error) if (status === 401 || status === 403) return true const name = getErrorName(error) - if (name === 'HttpRequestError' || name === 'TimeoutError') return true + if (name === 'HttpRequestError') return true const message = getErrorMessage(error) return /authorization token/i.test(message) diff --git a/src/pages/protocol/zones/proving.mdx b/src/pages/protocol/zones/proving.mdx index d20ac4e6..a63e39b4 100644 --- a/src/pages/protocol/zones/proving.mdx +++ b/src/pages/protocol/zones/proving.mdx @@ -3,8 +3,6 @@ title: Zone Proving description: Batch submission and proof verification for Tempo zones, including the state transition function, ZK and TEE deployment modes, and ancestry proofs. --- -import { StaticMermaidDiagram } from '../../../components/StaticMermaidDiagram' - # Zone Proving :::info @@ -73,22 +71,23 @@ pub fn prove_zone_batch(witness: BatchWitness) -> Result ### Execution Flow - B["Verify Tempo state proofs"] - B --> C["Initialize zone state from previous block hash"] - C --> D{"Next zone block"} - D --> E["Check parent hash and block number"] - E --> F["Verify beneficiary is the sequencer"] - F --> G["Execute advanceTempo system transaction if present"] - G --> H["Execute user transactions via revm"] - H --> I{"Final block in batch?"} - I -- No --> J["Compute simplified zone block hash"] +```mermaid +flowchart TD + A[Batch witness] --> B[Verify Tempo state proofs] + B --> C[Initialize zone state from previous block hash] + C --> D{Next zone block} + D --> E[Check parent hash and block number] + E --> F[Verify beneficiary is the sequencer] + F --> G[Execute advanceTempo system transaction if present] + G --> H[Execute user transactions via revm] + H --> I{Final block in batch?} + I -- No --> J[Compute simplified zone block hash] J --> D - I -- Yes --> K["Execute finalizeWithdrawalBatch"] - K --> L["Compute simplified zone block hash"] - L --> M["Extract output commitments"] - M --> N["Return batch output for verification"] -`} /> + I -- Yes --> K[Execute finalizeWithdrawalBatch] + K --> L[Compute simplified zone block hash] + L --> M[Extract output commitments] + M --> N[Return batch output for verification] +``` 1. **Verify Tempo state proofs.** Validate MPT proofs for all Tempo storage reads against Tempo state roots. 2. **Initialize zone state.** Load the zone state from the witness, binding the initial state root to the previous block hash.