Protocol Integration Guide

How a protocol integrates Final Wallet: recognising wallets, ERC-1271 signatures, controller-bound sub-accounts, and what never to assume.

Last updated 2026-09-08 · maintained in the Final Wiki

Both the Pre-Final Chain and Final Chain are fully EVM-compatible: your contracts, tooling and integrations carry over unchanged, whether you move, adapt, or only interface with us. Only strictly sequential parts of a protocol behave differently under Final Chain's asynchronous model, and a dedicated lane lets them persist their ordering.

For teams whose contracts will be called by Final Wallets and sub-accounts, or who want a dedicated, policy-bound sub-account for their protocol. This page is about the surfaces your contracts see. It does not describe how the protocol carries intents internally; you do not need that to integrate.

#1. What calls you

Every Final Wallet action reaches your contract as an ordinary call whose msg.sender is either:

  • the wallet address (stable, identical on every chain), or
  • one of its sub-accounts (a wallet-owned custody address, stable per route).

Both are smart contracts. Neither is an EOA. The call may originate from a bundle carrying many users' intents, but each call is authorized per intent by that wallet's holder, and your contract sees one caller per call as usual.

Consequences for your code

  • Never gate on tx.origin, extcodesize == 0, or "is this an EOA".
  • Roles and ownership you grant must point at the wallet or sub-account address that should hold them, never at a key.
  • Do not assume the caller cannot receive a callback synchronously, the way an EOA cannot; the wallet can, and sub-accounts can, within the rules below.

#2. Recognising a Final Wallet or sub-account

The chain-global wallet factory (same address on every chain) exposes two views:

  • isFinalWallet(address) → bool
  • isFinalSubAccount(address) → bool

Both are dual checks (registry and runtime code hash), so they authenticate; an unregistered address carrying the proxy runtime is refused. Use them to offer Final-specific behaviour, never to grant privileges: authorization is always the caller's own.

Addresses of the factory and the gateway per chain are published in the API's GET /system/config.

#3. Signatures: ERC-1271

A Final Wallet validates signatures through ERC-1271 isValidSignature(hash, signature). Support it wherever your protocol accepts signatures (permits, orders, off-chain approvals). Notes:

  • A wallet in post-quantum mode returns success only for hashes the wallet has explicitly approved on chain. Off-chain signatures in the classical sense are not produced by PQ wallets; design permit-style flows so that an on-chain approval is an acceptable substitute.
  • Sub-accounts are contract actors for ownership and role checks; they are not independent signature wallets unless your integration explicitly grows that surface.
  • Guardian and recovery signatures are typed data only. If your protocol interacts with recovery, sign EIP-712 structures, never prefixed messages.

#4. Sub-accounts for your protocol

A sub-account is an isolated custody route owned by exactly one wallet. There are two kinds:

  • Unbound: general-purpose isolation. The wallet executes through it; your contract sees the sub-account as caller.
  • Controller-bound: the sub-account is bound to a controller contract, your protocol's policy. Execution through that sub-account is checked against your controller's rules.

Controller requirements

  • A controller must be a service contract with code. A codeless controller bricks the sub-account (every controller call site reverts), so it is refused at binding.
  • A controller must not be a Final Wallet or a sub-account: anything carrying the wallet or sub-account proxy runtime is rejected by all three gates (factory, sub-account, preflight).
  • Policy changes go through the wallet's controller policy update request-and-accept flow. The holder must accept; your controller cannot change what the sub-account will do unilaterally.
  • The holder always retains an escape: a maintenance path to unbind and recover custody, with a delay your policy cannot remove.

What binds you, from the holder's side — the wallet treats every controller as untrusted and bounded: you can spend at most what the holder budgeted into the one sub-account (never the wallet, never another route, never a token outside the budget); the lane is closed until the holder has set budgets; the holder's scope sits above your policy; and the holder can escape or rotate you at any time with no callback. Design flows so a refused token or an exhausted cap degrades your UX, never your solvency. A holder may also grant an agent — a bot or an AI agent — a bounded allowance on a sub-account bound to your controller; your policy still runs as the third check on every such dispatch. The holder-side view: Sub-accounts, controllers and agents.

Recommended shape for a protocol controller

  1. Define the allowed targets and selectors for the route.
  2. Define value and rate caps in native terms; the sub-account's own AccountScope (time window, native cap, optional target allowlist) applies on top and is the holder's, not yours.
  3. Emit an event on every accepted policy version so integrators can audit what changed.

#5. Intents and bundles: what you can rely on

  • Each intent executes exactly once and in the wallet's nonce order.
  • An intent may declare require success (its revert propagates instead of being swallowed), skip if a dependency failed, or an in-bundle dependency on another intent of the same wallet in the same bundle.
  • A bundle may be atomic; agent grants can require atomic execution.
  • Cross-chain dependencies are not expressed inside a bundle. If your flow spans chains, use the settlement rail's operations and observe completion events; do not infer a remote leg from a local intent's success.

#6. Fees and gas

Holders do not pay gas directly; the protocol's gateway executes bundles and charges fees from the wallet according to the published schedule. Your contract does not need to be gas-sponsored and receives no fee logic. If you want to sponsor your users' Final DeFi fees, the deposit path on the gateway (depositFor) prefunds a wallet's fee balance permissionlessly.

#7. Sub-account lifecycle you may observe

  • Creation is deterministic per (wallet, route). Addresses are stable; you may precompute or read them from the factory once created.
  • Funding a sub-account from its wallet, executing through it, and binding or unbinding a controller are all wallet actions and emit events on the wallet and sub-account.
  • Dormant-funds routing and recovery apply to sub-accounts through their wallet; a protocol should never rely on a sub-account being immune to its owner's recovery outcome.

#8. Testing your integration

  • Both testnets carry the full stack (Sepolia, OP Sepolia). Wallets there are created the same way as on mainnet.
  • Use POST /preflight to check that an intent to your contract would be admitted before asking a user to sign it.
  • The reference wallet tooling can create a controller-bound sub-account for your controller on a test wallet; ask us for a test identity if you need one.

#9. Checklist

  • [ ] No EOA assumptions anywhere in the path a Final Wallet takes.
  • [ ] ERC-1271 accepted where signatures are accepted; PQ-mode approvals handled.
  • [ ] Roles granted to wallet or sub-account addresses.
  • [ ] Controller has code, is not a wallet proxy, and versions its policy.
  • [ ] Cross-chain flows use settlement operations, not in-bundle dependencies.

#See also

API reference · Architecture overview · Intents · Glossary