Architecture Overview
TezosX Wallet is a Chrome Manifest V3 extension composed of four runtime components, each living in a different execution boundary.
Component diagram
Components
Injected Provider — MAIN world
File: packages/wallet/src/injected/provider.ts
Runs in the same JavaScript context as the web page. Exposes window.ethereum as a minimal EIP-1193 provider. Has no access to chrome.* APIs — it communicates exclusively via window.postMessage.
Every call to provider.request() is assigned a unique requestId, forwarded to the content bridge, and resolved or rejected when the bridge replies.
Content Bridge — ISOLATED world
File: packages/wallet/src/content/bridge.ts
Runs in the extension's isolated world — it can see the page DOM but cannot access page globals. Bridges two channels:
- Page → SW: receives
TEZOSX_WALLET_REQUESTpostMessages, forwards them viachrome.runtime.sendMessage - SW → Page: receives
PROVIDER_EVENTpush messages from the SW, relays them asTEZOSX_WALLET_EVENTpostMessages
Service Worker
File: packages/wallet/src/background/service-worker.ts
The extension's backend. Since version 0.7.0 the service worker is intentionally thin (~95 lines) — it instantiates the long-lived state and delegates every incoming message to composition/sw-wiring.ts's dispatch(). The state that persists across popup opens:
Keyring— in-memoryUnlockedSession(the activeAccountplus its decoded secret key). Handles AES-GCM vault encryption and the V1 → V2 upgrade-on-read described in Keyring & Vault.Container— a freshly built{ signer, provider, balanceFetcher, vaultStore, sessionStore, notifications }rebuilt on every unlock bycomposition/container.ts, with the adapter set wired to the active account's kind. Tezos accounts getTezosSigner+RelayerProvider+TezosBalanceFetcher; EVM-native accounts getEvmSigner+EvmProvider+EvmBalanceFetcher(plus the NAC precompile builder for cross-runtime sends).ApprovalQueue— pending dApp requests awaiting user consent (connect,transaction,signature).
Handles three message categories, routed by dispatch() to the matching use case under src/use-cases/:
- PopupRequest — from the popup UI (
UNLOCK,LOCK,CREATE_WALLET,IMPORT_WALLET,IMPORT_SECRET_KEY,IMPORT_EVM_PRIVKEY,SEND_TX,RESOLVE_TX,LIST_SESSIONS,DISCONNECT, etc.) - ApproveRequest — from
approve.html(GET_PENDING,RESOLVE_PENDING) - EthereumRequest — from the content bridge (dApp's
provider.request()calls).eth_requestAccounts,eth_sendTransaction,personal_sign, andeth_signTypedData_v4are gated by the approval queue.
Popup UI
File: packages/wallet/src/ui/
A React + React Router app rendered in popup.html. Reads and mutates state exclusively via chrome.runtime.sendMessage to the service worker. Never touches the keyring or signer directly.
See User Flows for per-page documentation.
Data flow — dApp transaction request
See also
- Runtime Boundaries — detailed MAIN vs ISOLATED world rules
- Keyring — encryption and key derivation
- dApp Bridge & Approval Queue — approval popup lifecycle