You are two senior engineers working as a team:
Engineer A — Full-Stack Backend Developer (10+ years experience with Node.js/TypeScript, REST APIs, event-driven architectures, cloud deployment, and key management systems)
Engineer B — Blockchain Developer (10+ years experience with Solidity, EVM chains, ethers.js/viem, cross-chain bridges, and oracle systems)
Your task: Implement and deploy the oracle backend service for the Tokamak AI Layer optimistic execution protocol. This service is the critical infrastructure that enables cross-chain bond-backed optimistic execution between Ethereum L1 and HyperEVM (chain 999).
Before writing any code, read the full OPTIMISTIC_EXECUTION.md documentation at contracts/src/OPTIMISTIC_EXECUTION.md. Pay special attention to the "Oracle & Relayer Architecture" section. Understand:
-
Why the oracle exists: The protocol decouples ZK proof generation from action execution. Actions execute immediately (optimistic), backed by WSTON bonds on Ethereum L1. The oracle bridges trust between chains — it attests that bonds were locked on L1 before allowing optimistic execution on HyperEVM.
-
The two oracle roles:
- Role A (Price Feed Oracle): Signs
keccak256(feedHash || timestamp || chainId || vaultAddress)— attests input data freshness. Optional, medium-trust. - Role B (Bond Attestation Oracle): Signs
keccak256("BOND_LOCK_V1" || operator || vault || nonce || amount || chainId)— attests L1 bond lock. Required, high-trust.
- Role A (Price Feed Oracle): Signs
-
The relayer role: Monitors HyperEVM events (
ProofSubmitted,ExecutionSlashed) on all OptimisticKernelVaults and relays them to Ethereum L1 by callingreleaseBondByRelayer()orslashBondByRelayer()on WSTONBondManager. -
Multi-vault architecture: Anyone can deploy an OptimisticKernelVault via
VaultFactory.deployOptimisticVault(). The oracle must dynamically discover new vaults by listening toOptimisticVaultDeployedevents on VaultFactory, then subscribe to events on each vault. The oracle does NOT hardcode vault addresses. -
The signature formats: Exact EIP-191 signing formats used by
OracleVerifier.solon-chain (provided below).
Build a production-ready TypeScript backend service with four modules:
Long-running service — the foundation for all other modules:
-
On startup, query all existing optimistic vaults from VaultFactory:
- Scan historical
OptimisticVaultDeployed(address indexed vault, bytes32 indexed agentId, address indexed owner, uint256 bondChainId)events from VaultFactory on HyperEVM. - Store each vault's
{ address, agentId, owner, bondChainId, chainId }in a persistent registry (PostgreSQL).
- Scan historical
-
Subscribe to new
OptimisticVaultDeployedevents in real-time on VaultFactory.- When a new vault is deployed, add it to the registry and dynamically start event listeners for Module 3 (relayer) on the new vault.
-
The VaultFactory exists on multiple chains (HyperEVM mainnet chain 999 and potentially Ethereum mainnet chain 1). The registry must track which chain each vault lives on.
-
Provide an internal API for other modules to query:
getVaults()→ all registered vaultsgetVault(address, chainId)→ vault infoisRegisteredVault(address, chainId)→ boolean
VaultFactory addresses:
| Chain | Address |
|---|---|
| HyperEVM (999) | 0xc7Fc0dD5f1B03E3De0C313eE0D3b06Cb2Dc017BB |
| Ethereum (1) | 0x9cF9828Fd6253Df7C9497fd06Fa531E0CCc1d822 |
Endpoint: POST /api/v1/attest-bond
Flow:
- Receive request:
{ operator, vault, nonce, amount, chainId } - Validate vault: Check the vault registry — the vault address must be a registered OptimisticKernelVault deployed by VaultFactory. Reject requests for unknown vaults. This prevents signing attestations for rogue contracts.
- Verify on L1: Query
WSTONBondManager.bonds(operator, vault, nonce)on Ethereum mainnet. Confirm status isLockedandamountmatches. - Sign attestation: Compute
keccak256(abi.encodePacked("BOND_LOCK_V1", operator, vault, nonce, amount, chainId)), then EIP-191 sign with\x19Ethereum Signed Message:\n32prefix. - Return:
{ attestation: "0x...(65 bytes r||s||v)", bondHash: "0x...", signer: "0x..." }
Critical: This MUST verify the bond exists on-chain AND the vault is registered before signing. A false attestation enables unbonded execution (vault drain). Rate limit: max 10 requests/minute per operator.
Long-running service — monitors ALL registered OptimisticKernelVaults:
-
For each vault in the registry, subscribe to events:
ProofSubmitted(uint64 indexed executionNonce, address prover)→ callreleaseBondByRelayer(operator, vault, nonce)on L1ExecutionSlashed(uint64 indexed executionNonce, address slasher, uint256 bondAmount)→ callslashBondByRelayer(operator, vault, nonce, slasher)on L1
-
Dynamic subscription: When Module 1 (Vault Registry) discovers a new vault, the relayer must start listening to it immediately — no restart required.
-
Multi-vault event aggregation: Use a single WebSocket/polling connection per chain, filtering for events across all vault addresses. As the number of vaults grows, avoid opening one connection per vault. Instead:
- Batch event queries with multiple addresses in a single
eth_getLogscall - Or use a single subscription with a topic filter and match vault addresses locally
- Batch event queries with multiple addresses in a single
-
Idempotency: Track processed events by
(vaultAddress, chainId, eventHash)in PostgreSQL. Never relay the same event twice. -
Historical replay: On startup, for each vault, scan from its last checkpoint block to head. Process any missed events.
-
Confirmation depth: Wait 10 blocks on HyperEVM before relaying (reorg safety).
-
Retry logic: If L1 transaction fails, retry with exponential backoff (max 5 retries). Alert on permanent failure.
-
Operator resolution: Map
(vault, nonce)→operatorby indexingBondLockedevents from WSTONBondManager on L1, or by querying the bond storage directly:WSTONBondManager.bonds(operator, vault, nonce). Since the operator is not in the HyperEVM events, maintain a local index ofOptimisticExecutionSubmittedevents which include the vault address — the vault'sowner()is the operator.
Endpoint: POST /api/v1/sign-feed
Flow:
- Receive request:
{ feedHash, timestamp, chainId, vaultAddress } - Validate vault: Check the vault registry — the vault must be registered.
- Validate timestamp is within
maxOracleAge(900s) of current time - Sign:
keccak256(abi.encodePacked(feedHash, timestamp, chainId, vaultAddress))with EIP-191 prefix - Return:
{ signature: "0x...(65 bytes)", signer: "0x..." }
Engineer A deploys the service with:
-
Environment configuration:
# Oracle Keys (separate keys for Role A and Role B) BOND_ORACLE_PRIVATE_KEY= PRICE_ORACLE_PRIVATE_KEY= # Relayer Key RELAYER_PRIVATE_KEY= # RPC Endpoints ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/.. HYPER_RPC_URL=https://hyperliquid-mainnet.g.alchemy.com/v2/.. # Contract Addresses WSTON_BOND_MANAGER=0x46a92cDC8530fd1C4D46891625a718458856Bc14 VAULT_FACTORY_HYPER=0xc7Fc0dD5f1B03E3De0C313eE0D3b06Cb2Dc017BB VAULT_FACTORY_ETH=0x9cF9828Fd6253Df7C9497fd06Fa531E0CCc1d822 # Configuration CONFIRMATION_DEPTH=10 MAX_ORACLE_AGE=900 RATE_LIMIT_PER_MINUTE=10 PORT=3000 DATABASE_URL=postgresql://oracle:password@db:5432/oracle_service -
Project structure:
oracle-service/ ├── src/ │ ├── index.ts # Entry: start API + vault registry + relayer │ ├── api/ │ │ ├── server.ts # Express/Fastify HTTP server │ │ ├── bond-attestation.ts # POST /api/v1/attest-bond │ │ └── price-feed.ts # POST /api/v1/sign-feed │ ├── registry/ │ │ ├── vault-registry.ts # Dynamic vault discovery from VaultFactory events │ │ └── vault-store.ts # PostgreSQL persistence for vault registry │ ├── relayer/ │ │ ├── multi-vault-listener.ts # Aggregated event subscription across all vaults │ │ ├── relay-executor.ts # L1 transaction submission │ │ └── checkpoint.ts # Per-vault block tracking │ ├── signing/ │ │ ├── bond-signer.ts # EIP-191 bond attestation signing │ │ └── feed-signer.ts # EIP-191 price feed signing │ ├── verification/ │ │ └── l1-verifier.ts # On-chain bond status verification │ ├── db/ │ │ ├── migrations/ # SQL migrations │ │ └── client.ts # Database connection │ └── config.ts # Environment + contract ABIs ├── package.json ├── tsconfig.json ├── Dockerfile └── docker-compose.yml -
Database schema (PostgreSQL):
-- Registered optimistic vaults CREATE TABLE vaults ( address TEXT NOT NULL, chain_id INTEGER NOT NULL, agent_id TEXT NOT NULL, owner TEXT NOT NULL, bond_chain_id INTEGER NOT NULL, discovered_at TIMESTAMP DEFAULT NOW(), PRIMARY KEY (address, chain_id) ); -- Relayed events (idempotency) CREATE TABLE relayed_events ( vault_address TEXT NOT NULL, chain_id INTEGER NOT NULL, event_hash TEXT NOT NULL UNIQUE, event_type TEXT NOT NULL, -- 'ProofSubmitted' or 'ExecutionSlashed' execution_nonce BIGINT NOT NULL, l1_tx_hash TEXT, status TEXT DEFAULT 'pending', -- pending, relayed, failed created_at TIMESTAMP DEFAULT NOW(), relayed_at TIMESTAMP ); -- Per-vault checkpoint for historical replay CREATE TABLE checkpoints ( vault_address TEXT NOT NULL, chain_id INTEGER NOT NULL, last_block BIGINT NOT NULL, updated_at TIMESTAMP DEFAULT NOW(), PRIMARY KEY (vault_address, chain_id) );
-
Deployment target: Docker container with health checks. Include a
docker-compose.ymlwith the service + PostgreSQL. -
Health endpoint:
GET /healthreturns:{ "status": "healthy", "registeredVaults": 12, "lastRelayedBlock": { "hyper": 1234567, "ethereum": 9876543 }, "pendingRelays": 0, "signers": { "bondOracle": "0x...", "priceOracle": "0x...", "relayer": "0x..." } } -
Admin endpoints (authenticated):
GET /api/v1/admin/vaults— list all registered vaultsGET /api/v1/admin/relays?status=pending— pending relay queuePOST /api/v1/admin/replay?vault=0x...&fromBlock=N— force historical replay for a vault
event OptimisticVaultDeployed(
address indexed vault,
bytes32 indexed agentId,
address indexed owner,
uint256 bondChainId
);The on-chain verification in requireValidBondAttestation() expects:
bondHash = keccak256(abi.encodePacked("BOND_LOCK_V1", operator, vault, nonce, amount, chainId))
ethSignedHash = keccak256("\x19Ethereum Signed Message:\n32" || bondHash)
signature = sign(ethSignedHash) → (r, s, v) → packed as r[32] || s[32] || v[1]
Types: operator: address, vault: address, nonce: uint64, amount: uint256, chainId: uint256
CRITICAL encoding note: nonce is uint64 in abi.encodePacked, which produces 8 bytes (not 32). operator and vault are address (20 bytes). amount and chainId are uint256 (32 bytes). The total packed encoding is: "BOND_LOCK_V1"(12) + operator(20) + vault(20) + nonce(8) + amount(32) + chainId(32) = 124 bytes.
The on-chain verification in requireValidOracleSignature() expects:
domainFeedHash = keccak256(abi.encodePacked(feedHash, oracleTimestamp, chainId, vaultAddress))
ethSignedHash = keccak256("\x19Ethereum Signed Message:\n32" || domainFeedHash)
signature = sign(ethSignedHash) → (r, s, v) → packed as r[32] || s[32] || v[1]
Types: feedHash: bytes32, oracleTimestamp: uint64 (8 bytes packed), chainId: uint256 (32 bytes), vaultAddress: address (20 bytes)
function releaseBondByRelayer(address operator, address vault, uint64 nonce) external;
function slashBondByRelayer(address operator, address vault, uint64 nonce, address slasher) external;
// Both require msg.sender == trustedRelayerevent ProofSubmitted(uint64 indexed executionNonce, address prover);
event ExecutionSlashed(uint64 indexed executionNonce, address slasher, uint256 bondAmount);
event OptimisticExecutionSubmitted(uint64 indexed executionNonce, bytes32 journalHash, uint256 bondAmount, uint256 deadline);| Contract | Chain | Address |
|---|---|---|
| WSTONBondManager (v2) | Ethereum (1) | 0x46a92cDC8530fd1C4D46891625a718458856Bc14 |
| WSTON Proxy | Ethereum (1) | 0x26C8F112769fb3A3A8de267CfFf60E9f317445e5 |
| VaultFactory (proxy) | HyperEVM (999) | 0xc7Fc0dD5f1B03E3De0C313eE0D3b06Cb2Dc017BB |
| VaultFactory | Ethereum (1) | 0x9cF9828Fd6253Df7C9497fd06Fa531E0CCc1d822 |
| OracleVerifier lib | HyperEVM (999) | 0x49D2F7419f15eD00700dE325FE9F945C26353c18 |
Provide the complete implementation as production-ready code:
- All source files with full TypeScript code (not pseudocode, not stubs)
- package.json with exact dependencies
- Dockerfile and docker-compose.yml
- SQL migration files for the database schema
- README.md with setup instructions, key generation, and deployment steps
- Test files with unit tests for:
- Signing functions (verify signatures match on-chain
abi.encodePackedformat) - Vault registry (mock discovery and dynamic subscription)
- Relay idempotency (duplicate event rejection)
- Signing functions (verify signatures match on-chain
Ensure the signing logic produces signatures that pass on-chain verification in OracleVerifier.sol. Use ethers.js v6 or viem for Ethereum interactions. Use the exact abi.encodePacked encoding that Solidity uses — this is critical for signature compatibility. Pay special attention to uint64 being 8 bytes in encodePacked (not 32).