Skip to content
Merged
202 changes: 202 additions & 0 deletions .agents/plans/configurable-accounts-tempo-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Plan: sync ox configurable accounts with Tempo main

Status: in progress · Owner: jxom · Last reviewed: 2026-10-20

Progress:
- Phases 1, 2, 3 and 4b are done. Phase 2 shipped as `AccountConfig.fromUpgrade` only; the salt is
available as `fromUpgrade(...).salt`, so a separate `getUpgradeSalt` was not added.
- Phase 4a is done: `account` is no longer required for configurable key grants, and no new
requirement was added for `isAdmin` grants, since viem's root-signed admin grants omit it and
TIP-1049 allows that.
- Phase 3 is reversed. The node on `joshie/configurable-t14-devnet-checkpoint` still allows admin keys at
T14, including configurable accounts as admin delegates and as admin-grant authorizers (test
`configurable_account_admin_can_sign_inline_grant`), and has no TIP-1137 code. ox no longer rejects
those grants. Revisit if TIP-1137 lands in the node.
- Phase 5: the e2e runs against `sha-04e67d3`, built from `liam/t14-checkpoint-recovery-factory`
(the devnet checkpoint plus a recovery-factory genesis option).
- Phase 6 wire names are done: the key type is `configurableAccount` and the request field is
`configurableAccountSimulation`; the simulation spec gained the optional grant `signer`.
- Not done, from the checkpoint audit: ox still accepts a keychain nested in a keychain and a
configurable signature in a Tempo authorization list, which the node rejects or skips. Both
predate this work.

Bring `ox/tempo` configurable-account APIs up to date with Tempo main and the
current TIP cluster, and add TIP-1113 in-place migration helpers. viem depends
on this work (`ox@0.14.56`), so ox goes first and every fix ships on both
`main` (1.x) and `0.x`.

## Sources of truth

Newest first. When they disagree, prefer the spec and main over the open
implementation PRs.

| Source | What it settles |
| --- | --- |
| [tempoxyz/tempo#7510](https://github.com/tempoxyz/tempo/pull/7510) (TIP-1107–1114) | Spec: domains, encodings, migration, T14 gate |
| Tempo main `20c4412` ([#8502](https://github.com/tempoxyz/tempo/pull/8502), [#8497](https://github.com/tempoxyz/tempo/pull/8497)) | Rust constants and test vectors under the new names |
| `crates/primitives/src/transaction/configurable_account/mod.rs` | Domain constants, limits, `CONFIGURABLE_ACCOUNT_UPGRADE_DOMAIN` |
| `crates/primitives/tests/configurable_account.rs` (`configurable_account_hashes_follow_vectors_and_documented_layout`) | Byte-exact vectors to pin in ox tests |
| `crates/contracts/src/precompiles/configurable_account.rs` | `IConfigurableAccount` ABI, including `upgradeAccount` / `AccountUpgraded` |
| [tempoxyz/tempo#8204](https://github.com/tempoxyz/tempo/pull/8204) (TIP-1137) | Admin access keys removed at T14 |
| Open impl PRs [#7577](https://github.com/tempoxyz/tempo/pull/7577), [#7579](https://github.com/tempoxyz/tempo/pull/7579), [#7581](https://github.com/tempoxyz/tempo/pull/7581), [#7894](https://github.com/tempoxyz/tempo/pull/7894), [#8183](https://github.com/tempoxyz/tempo/pull/8183) | RPC wire names. These still use "multisig" naming and have not been rebased onto #8502. |
| [#8486](https://github.com/tempoxyz/tempo/pull/8486) | The precompile. Stacked on #8502, so it uses the new domains, but it does not accept configurable signatures in transactions. |

## Current state

- ox was last synced in `42a84e6` (#508, Oct 12). That predates Tempo's
domain and ABI renames (Oct 20).
- The byte layouts, limits, `0x05` envelope, CREATE2 derivation and init-code
hash all match main. The domain strings are the only hashing difference.
- ox has no TIP-1113 migration support.

## Phase 1: hashing domains (breaking)

`src/tempo/AccountConfig.ts:36,39,46` uses `tempo:multisig:{account,config,signature}`.
Main uses `tempo:configurable-account:{account,config,signature}`. Every
`getAddress`, `getCommitment`, `getSignPayload` and `AccountOperation.getHash`
result is wrong against the node.

Tasks:
- Rename `accountDomain`, `configDomain` and `signatureDomain` to the
`tempo:configurable-account:*` strings.
- Update the TSDoc preimage descriptions at `AccountConfig.ts:439` and `:490`.
- Rename `recoveryWalletInitCodeHash` (`:42`) to `accountProxyInitCodeHash`. Keep
the value `0x583cc6…`, and note that it is a placeholder until TIP-1110
fixes the canonical account proxy.
- Replace the commitment, address and digest snapshots in `AccountConfig.test.ts`
and `AccountOperation.test.ts`. Also add a test that pins the Rust vectors
(factory `0x99…99`, owner `0x11…11`, weight 1, threshold 1, zero salt):
- account salt `0x641ad6eccc3adf35fac828d5cbb727e2489c904a905420e794e8275b6150bfe1`
- address `0x87c346a9029d675f296db533b12e146439cf20d5`
- v0 commitment `0x79cc3a5fcbe18cb8cbbd2b41393750ccf3bbc20533a846755767fa10068ae01d`
- v0 digest (inner `0x42…42`, account `0x91847576f406d0842ad7c1a0c97c22a122e64278`)
`0xe6fa513b5ab32da6f116443f13191096bce77b4850f4d51f3aa436fa7650c964`
- v1 digest `0x285c850cb2803beb10f2f212dac73784a1a03b3991ba79081b24bfa91beb9182`
- v1 commitment `0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3`
- 48 owners of weight 1, threshold 8 → `0x7a4279bb3f1b8b54d703484317f64eae6814e827`
- Add a changeset. Use `patch`, following #508's precedent for breaking
configurable-account changes. It must say that addresses, commitments and
approval digests change.

Done when: the new vector test passes, and
`pnpm test --project tempo-unit --bail=1` and `pnpm check:types` are green.

## Phase 2: TIP-1113 migration helpers (new API)

Migration needs no new signature format. The root key signs an ordinary `0x76`
transaction whose only call is
`upgradeAccount(uint8 threshold, (address owner, uint8 weight)[] owners)` on
`0xAACC000000000000000000000000000000000000`. The resulting account config is
`{ salt: keccak256("tempo:configurable-account:upgrade" || account), version: 1, threshold, owners }`.

Tasks in `src/tempo/AccountConfig.ts`:
- Add a private `upgradeDomain = 'tempo:configurable-account:upgrade'`.
- Add `getUpgradeSalt(account)`, which returns `keccak256(upgradeDomain || account20)`.
- Add `fromUpgrade(account, { owners, threshold })`, which returns
`from({ salt: getUpgradeSalt(account), version: 1n, owners, threshold })`
with owners sorted and validated. The root may appear as an owner, because
self-ownership is only rejected at version 0.
- Add TSDoc examples. Add tests that cross-check the salt and commitment
against #8183's node test `crates/node/tests/it/tempo_transaction/multisig/migration.rs`,
after re-deriving them with the new domains.

Optional in `src/tempo/TxEnvelopeTempo.ts` or `TransactionRequest.ts`:
- Add `assertUpgrade(envelope)`, which checks the transaction is valid for
migration: exactly one call to `0xAACC…` with selector `0x5f5fdc31`, no
`keyAuthorization`, an empty `authorizationList`, and, if signed, a
primitive signature (`secp256k1`, `p256` or `webAuthn`). A fee payer is
allowed.

Out of scope for ox: the ABI and the actions. Those belong in viem.

Done when: the new exports have TSDoc and tests, `pnpm zile examples:check`
passes, and a changeset exists (`patch`, following #508, which added
`AccountConfig.update` as a patch).

## Phase 3: TIP-1137 admin access keys

At T14, `is_admin = true` and admin-signed `key_authorization` are invalid.
Configurable accounts only exist from T14, so they can never use admin keys.

Tasks:
- `KeyAuthorization.ts:50,116`: document `isAdmin` as removed from T14 per
TIP-1137. Do not tag it `@deprecated`: admin keys are still valid before
T14, and TIP-1137 is a draft. Leave encode and decode in place for pre-T14
chains.
- Add a check that rejects `isAdmin: true` when the authorizing account is
configurable, wherever ox can tell (a sign payload with a configurable
`account` binding).

## Phase 4: correctness nits against the node

- **Configurable grants without `account`:** `KeyAuthorization.ts:1218-1223`
(reached from `deserialize`) and zod `KeyAuthorization.ts:50` throw
`MissingAccountError` for a configurable `keyType` with no `account`. The
node only checks the binding when one is present, so ox should accept these
grants. Checked against #7894: the node requires `account` only for
admin-signed grants (signer != caller), never for a configurable key type.
Not changed yet, because relaxing it also drops a signing-side guard.
Decide whether to relax decoding only, or both directions.
- **Simulation `keyData`:** `AccountSimulation.ts:170-174` (`shimKeyData`) passes
any value up to 4 bytes through unchanged. The node rejects owner-approval
hints that are not 1, 2 or 4 bytes. Throw `InvalidSimulationError` for 0
or 3 bytes.

## Phase 5: e2e infrastructure

- `test/tempo/setup.global.configurableAccounts.ts:19`: rename the genesis key
`multisigRecoveryFactory` to `configurableAccountRecoveryFactory`.
- `test/tempo/configurableAccounts.genesis.json`: add `t14Time`.
- `test/tempo/configurableAccounts.ts:3`: replace image `sha-83f3ccd` with a
build that has the new domains and T14 integration.

**Blocked:** neither main nor #8486 accepts configurable-account signatures
in transactions yet. The only integration branches, #7581 and #7894, still
use the old domains. Until one of them rebases onto #8502, the e2e suite cannot
pass with Phase 1 applied. Either skip the configurable e2e project in CI
with a link to the tracking PR, or build a local image from a rebased
integration branch. Do not reintroduce the old domains to keep e2e green.

## Phase 6: deferred until the Tempo PRs rebase

Track these. Do not change them before the node does.

- **RPC key type name:** ox maps `configurable` to the wire value `'multisig'`
(`TransactionRequest.ts:73-75,150,295`; `KeyAuthorization.ts:119-120,554,1011`;
zod `TransactionRequest.ts:29-34`, `KeyAuthorization.ts:40,50`). TIP-1111
names it `ConfigurableAccount = 3`, and #8502 says there will be no aliases.
Expect the wire value to become `configurableAccount`. When it does, decide
whether to accept both values for one release.
- **Simulation field:** `multisigSimulation` is likely to become
`configurableAccountSimulation` or `accountSimulation`.
- **Account proxy hash:** the init-code hash will change when TIP-1110 is
final, which changes every derived address again.
- **Reserved-address checks:** TIP-1110 also rejects derived addresses that are
virtual addresses, active precompiles, TIP-20 or ZonePortal addresses. Main
leaves this to callers, so it is low priority. Consider an
`AccountConfig.getAddress` option or a separate `assertAddress`.
- **TIP-1108 account `extension`:** add an optional `extension`
(`0x00 || configHash`, 33 bytes) to the `eth_getAccount` /
`eth_getAccountInfo` schemas once the RPC lands.
- **TIP-1142 passport signers:** owner approvals may need to accept the
TIP-1131 ZK signature type (`SignatureEnvelope.ts:735-736`).

## Release and backport

1. Land Phases 1–4 on `main` (1.x) in one PR, or as Phase 1 followed by
Phases 2–4.
2. Cherry-pick the same commits onto `0.x` in a `jxom/<topic>-v0` branch
(following the existing `*-v0` convention) and release `0.14.57`.
3. Hand off to the viem plan (`viem/.agents/plans/configurable-accounts-tempo-sync.md`),
which bumps to that release.

## Open questions

- Activation: #7510 says T14. #8182 and #8183 add a separate
`accountMigrationTime` and a `MigrationNotActive` error. ox should not encode
availability. Confirm which one Tempo keeps.
- Wire names: confirm the final `keyType` and simulation field names with
joshieDo before Phase 6.
- Whether ox should ship the `IConfigurableAccount` ABI and address constant,
or leave them to viem (today they only appear inline in
`configurableAccounts.e2e.test.ts`).
5 changes: 5 additions & 0 deletions .changeset/tempo-account-config-from-upgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ox": patch
Comment thread
jxom marked this conversation as resolved.
Resolved
---

Added `AccountConfig.fromUpgrade` for deriving the version-one config an account installs when it migrates in place with `upgradeAccount` (TIP-1113).
5 changes: 5 additions & 0 deletions .changeset/tempo-account-simulation-key-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ox": patch
---

Fixed `AccountSimulation.toRpc` passing through empty and 3-byte `keyData`, which the Tempo node rejects, by shimming them into a 2-byte length hint.
5 changes: 5 additions & 0 deletions .changeset/tempo-configurable-account-domains.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ox": patch
---

Renamed the Tempo configurable account hash domains from `tempo:multisig:*` to `tempo:configurable-account:*` to match Tempo main (tempoxyz/tempo#8502), which changes every `AccountConfig.getAddress`, `AccountConfig.getCommitment`, and `AccountConfig.getSignPayload` result.
10 changes: 10 additions & 0 deletions .changeset/tempo-configurable-account-wire-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"ox": patch
---

Renamed the configurable account RPC key type from `multisig` to `configurableAccount` and `TransactionRequest.multisigSimulation` to `configurableAccountSimulation`, and added the optional `AccountSimulation` grant `signer`, to match the Tempo node.

```diff
- { keyType: 'multisig', multisigSimulation: spec }
+ { keyType: 'configurableAccount', configurableAccountSimulation: spec }
```
5 changes: 5 additions & 0 deletions .changeset/tempo-key-authorization-unbound-configurable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ox": patch
---

Stopped requiring an `account` binding on configurable account key grants, so `KeyAuthorization` and its zod schemas accept every grant the Tempo node accepts, and deprecated `KeyAuthorization.MissingAccountError`, which is no longer thrown.
3 changes: 2 additions & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ jobs:

test-browser:
name: Test Runtime (browser)
runs-on: ubuntu-latest
# Playwright does not support Ubuntu 26.04, which `ubuntu-latest` now resolves to.
runs-on: ubuntu-24.04

steps:
- name: Clone repository
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
- **Trusted setup artifacts are generated** -- `src/trusted-setups/internal/setups/mainnet.json` is canonical. Refresh it from an explicit `c-kzg-4844` release with `pnpm trusted-setups:update <tag>`; `pnpm trusted-setups:check` verifies its recorded hash and packed TypeScript artifact. See `src/trusted-setups/README.md`.
- **KZG WASM disables LTO** -- full LTO makes valid c-kzg trusted-setup initialization trap. Preserve `-fno-lto` and the KZG conformance tests.
- **Secrets are local** -- `.env` is local. Do not print, rewrite, or commit secrets.
- **Tempo configurable accounts** -- TIP-1107 accounts are modeled by `AccountConfig` and the `configurable` signature type. Every configurable signature carries `[account, config, primitive approvals]`; config includes a uint64 version. RPC uses the RLP bytes without the `0x05` prefix. Account derivation requires an explicit recovery factory; configurable delegates require keychain V2. The relay layer is configurable-account scoped too (`AccountOperation`, the `account_` RPC namespace), since every configurable transaction carries its config. Protocol and node names stay `multisig` (the `tempo:multisig:*` hash domains, the node's `multisig` key type, and `multisigSimulation`), so map them at the RPC boundary instead of renaming them.
- **Tempo configurable accounts** -- TIP-1107 accounts are modeled by `AccountConfig` and the `configurable` signature type. Every configurable signature carries `[account, config, primitive approvals]`; config includes a uint64 version. RPC uses the RLP bytes without the `0x05` prefix. Account derivation requires an explicit recovery factory; configurable delegates require keychain V2. The relay layer is configurable-account scoped too (`AccountOperation`, the `account_` RPC namespace), since every configurable transaction carries its config. Hash domains follow Tempo main (`tempo:configurable-account:*`, renamed in tempoxyz/tempo#8502). On the wire the key type is `configurableAccount` and the simulation field is `configurableAccountSimulation` (tempoxyz/tempo `joshie/configurable-t14-devnet-checkpoint`); ox maps the `configurable` key type to `configurableAccount` at the RPC boundary. The node allows configurable accounts as admin keys and as admin-key authorizers.
- **Zone chain IDs** -- Presto and Moderato use separate fixed bases and ranges. Derive chain IDs as `base + (zoneId % range)`.
- **Offline core tests** -- Set `SKIP_GLOBAL_SETUP=1` for focused core tests that do not need Anvil.
- **Docgen export comments** -- `extractNamespaceDocComments` should read the nearest JSDoc on an export declaration. ts-morph can include earlier file-level JSDoc descendants on the first export.
Expand Down
10 changes: 10 additions & 0 deletions src/tempo/AccountConfig.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ test('from returns a complete configuration', () => {
expectTypeOf(zero.version).toEqualTypeOf<0n>()
})

test('fromUpgrade returns a version-one configuration', () => {
const config = AccountConfig.fromUpgrade(
'0x3333333333333333333333333333333333333333',
input,
)

expectTypeOf(config).toMatchTypeOf<AccountConfig.Config>()
expectTypeOf(config.version).toEqualTypeOf<1n>()
})

test('update returns the next complete configuration', () => {
const config = AccountConfig.update(AccountConfig.from(input), {
owners: input.owners,
Expand Down
Loading
Loading