diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8460a7f2..6696935f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ settings: overrides: vocs: https://pkg.pr.new/vocs@1c023ba + viem@2.57.5>ox: https://pkg.pr.new/ox@bf010ee tapimo>accounts: 0.18.4 baseline-browser-mapping@<2.11.0: 2.11.0 browserslist@<4.28.7: 4.28.7 @@ -5087,8 +5088,9 @@ packages: typescript: optional: true - ox@0.14.56: - resolution: {integrity: sha512-Feinlj+deWEam6RIwurRE+eHZ2N3hoUb3AAjQ10BmxRCYIymfqNkv0eZ+4ln4GkgDmSGLD2rGtTMqsFYSVDC6w==} + ox@https://pkg.pr.new/ox@bf010ee: + resolution: {tarball: https://pkg.pr.new/ox@bf010ee} + version: 0.14.57-755f436.0 peerDependencies: typescript: '>=5.4.0' peerDependenciesMeta: @@ -11411,7 +11413,7 @@ snapshots: transitivePeerDependencies: - zod - ox@0.14.56(typescript@6.0.3)(zod@4.6.5): + ox@https://pkg.pr.new/ox@bf010ee(typescript@6.0.3)(zod@4.6.5): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -12669,7 +12671,7 @@ snapshots: '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@6.0.3)(zod@4.6.5) isows: 1.0.7(ws@8.21.0) - ox: 0.14.56(typescript@6.0.3)(zod@4.6.5) + ox: https://pkg.pr.new/ox@bf010ee(typescript@6.0.3)(zod@4.6.5) ws: 8.21.0 optionalDependencies: typescript: 6.0.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 19ae7941..807714c7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -31,6 +31,8 @@ onlyBuiltDependencies: overrides: vocs: https://pkg.pr.new/vocs@1c023ba + # Configurable account hash domains matching the t14-checkpoint devnet (wevm/ox#519). + 'viem@2.57.5>ox': https://pkg.pr.new/ox@bf010ee # accounts 0.18.5 was published with unresolved catalog dependencies. 'tapimo>accounts': 0.18.4 'baseline-browser-mapping@<2.11.0': 2.11.0 diff --git a/src/components/guides/UpgradeAccountDemo.tsx b/src/components/guides/UpgradeAccountDemo.tsx index 4274dd9b..c6b12b19 100644 --- a/src/components/guides/UpgradeAccountDemo.tsx +++ b/src/components/guides/UpgradeAccountDemo.tsx @@ -41,7 +41,7 @@ async function loadDemo() { ]), // The configuration salt Tempo assigns an upgraded account. upgradeSalt: (address: Address) => - viem.keccak256(viem.concat([viem.toHex('tempo:multisig:upgrade'), address])), + viem.keccak256(viem.concat([viem.toHex('tempo:configurable-account:upgrade'), address])), } } diff --git a/src/components/guides/account-sandbox-devnet.test.ts b/src/components/guides/account-sandbox-devnet.test.ts index ccde4ccd..ba837d74 100644 --- a/src/components/guides/account-sandbox-devnet.test.ts +++ b/src/components/guides/account-sandbox-devnet.test.ts @@ -488,7 +488,9 @@ describe('upgrading a plain key account', () => { it('uses Tempo’s upgrade salt for the address', async () => { const { concat, keccak256, toHex } = await import('viem') - expect(upgradeSalt(address)).toBe(keccak256(concat([toHex('tempo:multisig:upgrade'), address]))) + expect(upgradeSalt(address)).toBe( + keccak256(concat([toHex('tempo:configurable-account:upgrade'), address])), + ) }) it('records owners sorted by address, at version 1', () => { diff --git a/src/components/guides/account-sandbox-devnet.ts b/src/components/guides/account-sandbox-devnet.ts index da953222..e6289589 100644 --- a/src/components/guides/account-sandbox-devnet.ts +++ b/src/components/guides/account-sandbox-devnet.ts @@ -1,6 +1,6 @@ /** * Devnet adapter for the account sandbox. It sends the sandbox's actions as real transactions on - * the internal multisig-1113 devnet, which runs configurable accounts (TIP-1107 to TIP-1114) and + * the internal t14-checkpoint devnet, which runs configurable accounts (TIP-1107 to TIP-1114) and * configurable access keys (TIP-1111). * * - Only real signing runs on devnet. Passkeys are real WebAuthn passkeys, so creating and signing @@ -29,6 +29,7 @@ import { isAddressEqual, keccak256, parseAbi, + parseEventLogs, toHex, } from 'viem' import { generatePrivateKey } from 'viem/accounts' @@ -256,10 +257,15 @@ const nativeMultisig: Address = '0xAACC000000000000000000000000000000000000' const upgradeAbi = parseAbi([ 'function upgradeAccount(uint8 threshold, (address owner, uint8 weight)[] owners) returns (bytes32)', ]) +// viem's `Abis.nativeMultisig` still names this event `MultisigConfigUpdated`, so +// `accounts.updateConfigSync` can't find it. Remove once viem ships the renamed ABI. +const configUpdatedAbi = parseAbi([ + 'event ConfigurableAccountConfigUpdated(address indexed account, bytes32 salt, uint64 version, uint8 threshold, (address owner, uint8 weight)[] owners)', +]) /** The configuration salt Tempo assigns an account upgraded in place. */ export function upgradeSalt(address: Address) { - return keccak256(concat([toHex('tempo:multisig:upgrade'), address])) + return keccak256(concat([toHex('tempo:configurable-account:upgrade'), address])) } /** @@ -997,11 +1003,18 @@ export function createDevnetAdapter( const next = { owners, threshold: account.committed.threshold } if (sameConfig(next, session.config)) return undefined const approvers = updateApprovers(session, account) - const { config, receipt } = await client.accounts.updateConfigSync({ + const receipt = await client.writeContractSync({ account: signingAccount(session, localAccounts(approvers)), - nextConfig: next, + ...Tempo.Actions.accounts.updateConfig.call({ + currentConfig: session.config, + nextConfig: next, + }), + throwOnReceiptRevert: true, }) - session.config = config + const [log] = parseEventLogs({ abi: configUpdatedAbi, logs: receipt.logs, strict: true }) + if (!log) + throw new DevnetRejection('invalid-config', 'Devnet recorded no configuration update.') + session.config = Tempo.AccountConfig.from(log.args) session.lastUpdate = receipt.transactionHash return receipt.transactionHash } diff --git a/src/components/guides/devnet-network.ts b/src/components/guides/devnet-network.ts index 14a95f96..be212b26 100644 --- a/src/components/guides/devnet-network.ts +++ b/src/components/guides/devnet-network.ts @@ -2,12 +2,12 @@ import type { Address } from 'viem' import { developersApiPath } from '../../marketing/app/_lib/developersPaths' /** - * Where the Accounts demos reach the multisig-1113 devnet. Kept apart from + * Where the Accounts demos reach the t14-checkpoint devnet. Kept apart from * `account-sandbox-devnet.ts` so small demos can import these values without bundling the adapter. */ /** The demo devnet's Tailscale RPC, for Node scripts on the tailnet. */ -export const devnetTailnetRpcUrl = 'http://multisig-1113-nodes-rpc-service.tail388b2e.ts.net:8545' +export const devnetTailnetRpcUrl = 'http://t14-checkpoint-nodes-rpc-service.tail388b2e.ts.net:8545' /** * Pages reach the devnet through the docs site's same-origin proxy (`/api/devnet-rpc`), which holds diff --git a/src/pages/_api/api/devnet-rpc.ts b/src/pages/_api/api/devnet-rpc.ts index adc4b561..824792cc 100644 --- a/src/pages/_api/api/devnet-rpc.ts +++ b/src/pages/_api/api/devnet-rpc.ts @@ -1,9 +1,10 @@ -// Same-origin JSON-RPC proxy for the Accounts demos on the multisig-1113 devnet. +// Same-origin JSON-RPC proxy for the Accounts demos on the t14-checkpoint devnet. // Its public endpoint sits behind the shared `previews` API key, which must stay server-side, // and HTTPS pages can't call the Tailscale-only HTTP endpoint. Without a key (local dev on the // tailnet), it forwards to the Tailscale RPC instead. -const publicUpstream = process.env.DEVNET_RPC_URL ?? 'https://rpc-multisig-1113.devnet.tempoxyz.dev' -const tailnetUpstream = 'http://multisig-1113-nodes-rpc-service.tail388b2e.ts.net:8545' +const publicUpstream = + process.env.DEVNET_RPC_URL ?? 'https://rpc-t14-checkpoint.devnet.tempoxyz.dev' +const tailnetUpstream = 'http://t14-checkpoint-nodes-rpc-service.tail388b2e.ts.net:8545' const maxRequestBytes = 256 * 1024 const maxResponseBytes = 4 * 1024 * 1024 @@ -24,6 +25,7 @@ export async function POST(request: Request): Promise { const calls = Array.isArray(parsed) ? parsed : [parsed] if (calls.length === 0 || calls.length > maxBatch || !calls.every(isAllowedCall)) return failure(400, 'Unsupported demo RPC request') + if (calls.map(renameSimulation).includes(true)) body = JSON.stringify(parsed) } catch { return failure(400, 'Invalid JSON-RPC request') } @@ -73,6 +75,20 @@ function isAllowedCall(call: unknown) { return allowedMethods.has(method) || allowedPrefixes.some((prefix) => method.startsWith(prefix)) } +// ox still sends the simulation hint as `multisigSimulation`, and the t14-checkpoint node reads +// `configurableAccountSimulation`. Without it, the node underestimates a new account's first +// transaction and it runs out of gas. Remove once ox sends the new name. +function renameSimulation(call: unknown) { + const [request] = (call as { params?: unknown[] }).params ?? [] + if (!request || typeof request !== 'object' || !('multisigSimulation' in request)) return false + const { multisigSimulation, ...rest } = request as Record + ;(call as { params: unknown[] }).params[0] = { + ...rest, + configurableAccountSimulation: multisigSimulation, + } + return true +} + function failure(status: number, message: string) { return Response.json( { jsonrpc: '2.0', id: null, error: { code: -32000, message } }, diff --git a/src/snippets/accounts-devnet.ts b/src/snippets/accounts-devnet.ts index e6be86f5..f6796104 100644 --- a/src/snippets/accounts-devnet.ts +++ b/src/snippets/accounts-devnet.ts @@ -11,7 +11,7 @@ const rpcKey = process.env.TEMPO_DEVNET_RPC_KEY export const client = createClient({ chain: tempoDevnet, - transport: http('https://rpc-multisig-1113.devnet.tempoxyz.dev', { + transport: http('https://rpc-t14-checkpoint.devnet.tempoxyz.dev', { fetchOptions: { headers: { Authorization: `Basic ${btoa(`previews:${rpcKey}`)}` } }, }), })