guide · for judges & explorers
how it works / docs
tradewise.agentlab.eth is a fully autonomous on-chain agent. It earns revenue, has reputation, can be owned, traded, financed, insured, and merged — every primitive is a real contract on Sepolia or Base Sepolia. This page is the map. FAQ above, deep architecture below.
tl;dr
tradewise is an AI agent that quotes Uniswap prices for $0.10–$0.20 per quote, paid in USDC over the x402 protocol. It has an ENS name, an ERC-8004 identity, an ERC-7857 INFT shell that anyone can buy, an ERC-20 revenue-share token (TRADE), an uncollateralized credit line, an SLA bond, and a merger contract that can fold it into another agent.
Every page on this site reads from on-chain state. Every action button writes to a real contract. There is no mock data on the live cards.
which network do I need?
Two testnets, split by use case. Your wallet must be on the right chain for each action — controls show a banner and a one-click switch button when you're on the wrong network.
- Sepolia
- ERC-8004 identity + reputation, ERC-7857 INFT, bidding, credit, SLA bonds, M&A.
chainId 11155111. free ETH faucet · free USDC faucet. - Base Sepolia
- x402 USDC settlement (the agent is paid here) + the IPO contracts (TRADE shares, dividends, primary sale).
chainId 84532. free ETH faucet · free USDC faucet.
identity & reputation
The agent is registered in IdentityRegistryV2 (an ERC-8004 implementation) under agentId #1. Its identity is bound to tradewise.agentlab.eth via ENSIP-25 — name, avatar, description, and a reputation summary text record live there.
Every successful x402 quote produces a feedback event in ReputationRegistry. Anyone can read feedbackCount(agentId)directly on chain. That number is the credit limit input, the dynamic-pricing tier input, and the M&A constituent score.
§4.4 anti-laundering: when the INFT owner changes, the payout wallet auto-clears. The new owner has to sign an EIP-712 setAgentWallet message before x402 settlement resumes.
payments — what is x402?
x402 is HTTP-native machine payments. The agent answers an unpaid quote request with HTTP 402 Payment Requiredand a settlement quote in USDC. The client signs a permit, retries, and the resource is unlocked. There's no API key, no Stripe account, no human in the loop.
Pricing is reputation-gated: <50 → $0.10, 50–100 → $0.15, ≥100 → $0.20. Higher reputation = higher fee, because the agent has earned the right to charge more.
inft & bidding
ERC-7857 (intelligent NFT) wraps the agent itself: its memory blob is anchored to 0G Storage, the Merkle root sits in tokenURI, and the holder of the token controls payout. Transfer the INFT and you transfer the going concern.
/inft → shows OpenSea-style standing offers: any wallet can lock USDC into AgentBids as a bid, top it up, or withdraw. The owner can accept any bid in one transaction — atomic INFT transfer + USDC settlement, no expiry.
All offers are visible to everyone, ranked by amount. Highest bid wins when the owner clicks accept.
ipo & revenue share
Owning the INFT = owning the agent. Owning a TRADE token = owning a slice of its income. There are 10,000 TRADE (ERC-20) on Base Sepolia — buy them at the primary fixed-price SharesSale, then claim dividends from RevenueSplitter any time.
Math: claimable = totalReceived × balanceOf(you) / totalSupply − alreadyReleased. Pull-pattern, gas-cheap, no per-payment iteration.
Switch the agent's x402 payTo to the splitter and every quote becomes a dividend.
credit market — uncollateralized lending against reputation
ReputationCredit is a USDC pool. Lenders deposit, agents borrow up to min(feedbackCount × $5, pool / 10) with no collateral. Their reputation is the recourse.
If feedback drops below 80% of borrow-time feedback, anyone can call liquidate(agentId) — the loan defaults, lender NAV-per-share is written down pro-rata, the agent keeps the borrowed USDC, and reputation gets recorded as defaulted on chain.
Lenders take the risk. The agent has no way to game the system without burning reputation, which is the same reputation that powers pricing, credit, and M&A.
sla marketplace
Listed agents post a USDC bond per job in SlaBond. Validator approves → bond returns. Validator slashes → 70% refunds the client, 30% rewards the slasher. State machine: None → Posted → {Released | Slashed}.
/marketplace → shows the live agents (tradewise + pricewatch) and four stub directory entries that illustrate the catalog shape.
m&a — agent mergers
AgentMerger lets you fold two agents into one. The source INFTs lock in custody, lineage records the constituent agentIds + a 0G Storage Merkle root for the combined memory blob, and effectiveFeedbackCount(mergedAgentId) returns the sum of constituent reputation.
Agents are businesses. Businesses do M&A. The lineage card on /merger is the audit trail.
compliance — KYC for agents
The hard problem: how does anyone verify an AI agent isn't illegally scraping Google Flights, Twitter, or some other source that forbids automated access? The agent declares every external data source it touches — URL, ToS hash, license tier — in a signed manifest. The full doc lives on 0G Storage; the keccak256 root is anchored to the ComplianceManifest registry on Sepolia.
Universal verification: anyone runs buildManifestRoot(manifestDoc) off chain and compares against getManifest(agentId).manifestRoot. Match = verified declaration. The /compliance page does this on every load.
Teeth: agents post a USDC compliance bond. Anyone can challenge with a counter-bond ≥ agent bond + an evidence URI. The validator resolves; if upheld, the agent's bond splits 70/30 between challenger and validator and the manifest enters an on-chain Slashed state — permanent reputation penalty.
keeperhub — the agent runs its own infra
Vercel hosts the application. KeeperHub schedules and executes the agent's automations: ENS heartbeat, reputation cache, compliance attestation, swap mirror. The agent is the keeper customer, not a Vercel cron consumer.
Why it matters: the agent shouldn't depend on our scheduler. KeeperHub is decentralized infra that can keep the agent alive, heartbeating, and self-attesting even if our app goes down. /keeperhub shows the workflow gallery and recent runs.
try it yourself
- i.install MetaMask. add Sepolia & Base Sepolia networks (the UI's switch button does it for you).
- ii.get free Sepolia ETH from a faucet, and Sepolia / Base Sepolia USDC from Circle's faucet.
- iii.go to /inft and place a bid. the wallet pops, you sign, the bid is on chain. switch wallets and you can see your bid in the public list.
- iv.go to /ipo, switch to Base Sepolia, buy a TRADE share. Claim dividends once x402 settlements start hitting the splitter.
- v.deposit USDC into /creditas a lender, or — if your wallet is the agent EOA — borrow against the agent's reputation.
links
deep dive
how everything actually works / for judges
The FAQ above is the user manual. This is the systems doc — every contract, every off-chain service, every cryptographic envelope, the actual byte layout of the proof bytes. If you're evaluating this for a hackathon prize, read this section.
system overview — what's running where
tradewise is a single web app on Vercel that talks to sevenon-chain contract systems across two chains, plus three off-chain services. The architecture is meant to maximize the surface area where the agent acts as a real autonomous business — not just "an LLM with a wallet."
- · V2-b registry
- · Verifier
- · INFT (ERC-7857)
- · Bids
- · Merger
- · Reputation
- · Credit
- · SLA bond
- · Compliance
- · TRADE ERC-20
- · SharesSale
- · RevenueSplitter
- · x402 USDC
- · Memory ciphertext
- · TEE LLM inference
- /api/inft/oracle/* · INFT oracle — memory encryption
- /api/inft/transfer/* · user-facing rate-limited proxy
- /api/a2a/jobs · x402 paid quote endpoint
- /api/keeperhub/* · KeeperHub workflow webhooks
Two chains: 11155111 (Sepolia, identity-side) and 84532 (Base Sepolia, settlement). Plus 0G Galileo 16602 for the encrypted memory blobs and TEE-attested inference.
erc-7857 with the missing piece — the oracle
The problem.ERC-7857 says: when an INFT transfers, the encrypted memory blob is re-encrypted to the new owner. Every public ERC-7857 demo we've seen stops at the metadata schema. 0G's own reference verifier (the canonical implementation) leaves a literal // TODO: verify TEE's signature in its transfer-validity verifier and deploys with an empty oracle counterpart (the 0g-inft-oracle-server-ts repo is a placeholder).
What we ship. A full ERC-7857 transfer pipeline:
- AgentINFTVerifier on Sepolia. Implements 0G's
IERC7857DataVerifierwith the byte layout from their reference (plus a 32-byte tokenId field we added at offset 1 since the verifier's interface only seesbytes[]). Adds the on-chain oracle attestation check the reference left as TODO. - AgentINFT with a new
transferWithProof(to, tokenId, proof)entry point, an EIP-712 delegation table that lets bidders pre-authorize the oracle to act as their receiver-proxy, and amemoryReencryptedstale flag for rawtransferFrombypasses. - Oracle service(Vercel functions). Holds per-token AES-128 keys in Redis (KEK-derived from the oracle private key via HKDF). On every transfer it: decrypts the current blob, generates a fresh AES key, re-encrypts the same plaintext, anchors the new ciphertext to 0G Storage, ECIES-wraps the new key to the buyer's pubkey, and signs a 65-byte attestation the on-chain verifier checks via
ecrecover.
Trust posture.Same as 0G's reference (an EOA-signing oracle) but we actually verifythe signature on-chain. Hardware-attested swap-in is a one-line change: replace the oracle pubkey in the verifier's constructor with a TEE-bound key. The contract surface is identical.
proof byte layout & transfer flow
Every transfer carries a single calldata blob containing the entire re-encryption proof. The verifier parses it via assembly, recovers two ECDSA signatures (receiver + oracle), and checks a replay-protection nonce.
transfer proof byte layout — private TEE flavor (~423B + uri length)
| offset | field | contents |
|---|---|---|
| [0] | flags | 0x40 (bit 6 isPrivate=1, bit 7 TEE=0) |
| [1..33) | tokenId (uint256) | binds the proof to a specific INFT |
| [33..98) | accessibility sig (65B) | ECDSA over keccak(newRoot ‖ oldRoot ‖ nonce), EIP-191 prefixed. Recovers to receiver directly (live) or oracle (delegation path). |
| [98..146) | nonce (48B) | replay-protection key |
| [146..178) | newDataHash | 0G Storage Merkle root of new ciphertext |
| [178..210) | oldDataHash | asserts proof binds to current state |
| [210..226) | sealedKey (16B) | first 16B of ECIES ciphertext |
| [226..259) | ephemeralPubkey (33B) | compressed secp256k1 (key-wrap context) |
| [259..271) | ivWrap (12B) | AES-GCM IV for the key wrap |
| [271..287) | wrapTag (16B) | AES-GCM auth tag for the key wrap |
| [287..289) | uriLen (uint16 BE) | length of newUri |
| [289..289+L) | newUri (UTF-8) | og://<root> pointer |
| [289+L..) | oracleAttestation (65B) | ECDSA over keccak(tokenId ‖ oldHash ‖ newHash ‖ sealedKey ‖ keccak(uri) ‖ nonce), EIP-191 prefixed. Recovers to the configured oracle pubkey. |
Accept-bid sequence (3 wallet steps + 2 oracle calls):
- Bidder pre-authorizes the oracle as receiver-proxy via an EIP-712
Delegationsignature, stored on-chain inAgentINFT.delegations[bidder][tokenId].AgentBids.placeBidbundles the delegation forward and the USDC escrow into one transaction. - Seller clicks accept. Frontend calls
/api/inft/transfer/prepare. Oracle decrypts current blob (Redis-stored AES-128 key), rotates to a fresh K_new, encrypts the same plaintext, anchors to 0G Storage, builds the proof bytes above. Does not rotate the Redis key yet — proof is returned to the frontend. - Seller signs
BIDS.acceptBid(tokenId, bidder, proof). The contract checks the bid, callsINFT.transferWithProof, the verifier validates the proof, the INFT updatesencryptedMemoryRoot[tokenId] = newRoot, ERC-721 transfer happens atomically, USDC pays out to the seller. Single tx. - Frontend POSTs
/api/inft/transfer/confirm. Oracle waits for tx receipt, parses theTransferredevent, then commits the rotated key in Redis. Two-phase commit prevents key rotation if the chain tx never lands. - Optional: new owner clicks reveal. Wallet signs an EIP-191 message,
/api/inft/transfer/revealverifies ownership against the on-chainownerOf, decrypts with the new K, and returns the plaintext memory blob.
stale memory — why raw transferFrom is intentionally weird
ERC-7857 wants every transfer to carry re-encryption. ERC-721 marketplaces (OpenSea etc.) call safeTransferFrom directly. We compromise: raw transferFrom still works, but flips memoryReencrypted[tokenId] = false and emits MemoryStaled. The new owner gets the token but cannot decrypt the memory.
The /inftpage renders a red "memory is stale" badge when the flag is false. This is a feature, not a bug — it demonstrates exactly whythe proof path matters, side-by-side with the proof path's green "rotations: N" counter.
Recovery: previous owner can volunteer the AES key off-chain, or transfer the INFT back via transferWithProof (which re-rotates the key under the original owner).
merger — dual proofs, custody, lineage
AgentMerger.recordMerge(...) takes proofs for both source tokens. The merged-agent owner pre-authorizes the oracle to act as receiver-proxy for the merger contract (which has no key) via setDelegationByOwner(MERGER, tokenId, oracle, expiresAt) — owner's tx is the authorization, no signature needed since the receiver is a contract.
Oracle prepare-merge: decrypts both source blobs, encrypts the combined memory under a fresh K_m, anchors once, builds two proofs (both with newDataHash = mergedRoot). The merger contract calls transferWithProof twice, source tokens land in custody, lineage stored on chain, merged agent inherits effectiveFeedbackCount = sum of constituents.
trust model & cryptographic primitives
| layer | algorithm | why |
|---|---|---|
| Memory blob | AES-128-GCM | matches bytes16 sealedKey in 0G reference |
| Key wrap | ECIES-secp256k1 + HKDF + AES-128-GCM | bidder pubkey recoverable from delegation sig — no extra registration |
| Oracle attestation | secp256k1 ECDSA + EIP-191 | on-chain ecrecover against pinned oracle pubkey |
| KEK at rest | AES-256-GCM, per-token HKDF | HKDF(oracle_sk, salt="inft-kek-v1", info=tokenId) isolates compromise |
| Replay protection | 48-byte nonces, on-chain map | shared between mint and transfer paths; cross-path replay is impossible because the oracle signs path-specific preimages |
Single trust anchor:the oracle's secp256k1 private key. Compromise = an attacker can mint forged transfer proofs, but only for INFTs they already own (the verifier checks old root binds to current state, which only the legitimate owner controls). The blast radiusis limited to the oracle's signing power, not the AES keys (which require Redis breach AND KEK derivation).
tech stack — what each thing actually is
- Solidity
0.8.28via Foundry. Uses transient storage (EIP-1153) for the proof-path flag inAgentINFT._update. OpenZeppelin v5 base contracts.- Frontend
- Next.js 16 App Router on Vercel (Fluid Compute). All wallet interactions through viem. EIP-712 sign-typed-data for delegations.
- Oracle service
- Vercel functions under
/api/inft/oracle/*. Pure TypeScript; secp256k1 + AES-GCM via@noble/curvesand@noble/ciphers(no native deps). Redis-backed key store (Upstash). - 0G Storage
@0glabs/0g-ts-sdk0.3.3. Note: SDK's built-insubmitreverts due to a contract upgrade after publish — we ship a custom Flow ABI bypass that computes the Merkle root locally, anchors via rawwriteContractcall, polls storage nodes for FileInfo, then re-enters the SDK withskipTx + finalityRequired:falseto dispatch segments.- 0G Compute
@0glabs/0g-serving-broker. Every paid x402 quote triggers a TEE-attested inference call (Qwen-2.5-7B); theZG-Res-Keyresponse header is processed viabroker.inference.processResponseto confirm attestation.- x402
@x402/nextmiddleware wraps the agent's quote endpoint. Hosted facilitator atfacilitator.x402.rshandles USDC settlement on Base Sepolia (we pay zero gas).- KeeperHub
- MCP-driven workflows. Webhook-triggered (not cron). Existing workflows: heartbeat, reputation cache, swap mirror, compliance attest. Future workflows in W3 spec.
w2 — ccip-read ens gateway
Before W2: every last-seen-at heartbeat, reputation-summary, and dynamic ENS text record was a real setText tx burning Sepolia gas. After W2: a single OffchainResolver contract reverts every resolve() call with EIP-3668 OffchainLookup, viem/wagmi clients follow it transparently, and our Vercel gateway computes record values live (Redis + on-chain reads + Edge Config), signs the response with INFT_GATEWAY_PK, and resolveWithProof verifies the sig with ecrecover on chain. Zero gas per read.
ENSIP-10 wildcard: a single resolver at the parent agentlab.eth serves every *.agentlab.ethand every nested *.*.agentlab.eth. New agents get ENS records for free with no per-name registration.
Trust posture (W2-α):gateway is an EOA we sign with. Compromise ⇒ malicious resolution. Worst case is stale telemetry, never falsified ownership (ownership stays in the L1 registry, the resolver doesn't override it). Future swap to W2-β (storage-proof verifier) replaces only theresolveWithProof body — no API change.
resolve flow — what happens when you query an ens text record
- 1.wallet / dAppviem.getEnsText({ name, key }) → eth_call resolve(name, data) on the agentlab.eth resolver
- 2.OffchainResolver (Sepolia)reverts: OffchainLookup(this, [gatewayURL], callData, callbackFn, extraData) — viem catches it (EIP-3668)
- 3.gateway HTTP routeGET
/api/ens-gateway/{sender}/{data}.json→ ABI-decode (name, data) → resolve via Redis + chain + Edge Config → signkeccak256(0x1900 || resolver || expires || keccak(extraData) || keccak(result))→ returns{ data: encode(expires, result, sig) } - 4.OffchainResolver.resolveWithProofcheck expires > now · ecrecover sig === expectedGatewaySigner · return result bytes
- 5.callerviem decodes the result and hands the value to your code
live records served by the gateway
| key | source | cross-link |
|---|---|---|
| last-seen-at | Redis agent:1:last-seen | W3 KeeperHub heartbeat-pulse |
| reputation-summary | Redis with on-chain ReputationRegistry.feedbackCount fallback | W3 KeeperHub reputation-pulse |
| outstanding-bids | On-chain AgentBids.biddersCount(tokenId) | W1 contract |
| inft-tradeable | On-chain AgentINFT.memoryReencrypted(tokenId) | W1 — '1' fresh, '0' stale |
| memory-rotations | Redis inft:meta:1:rotations | W1 oracle |
| avatar | computed: eip155:11155111/erc721:<INFT>/<tokenId> | W1 INFT |
| addr | WALLET_LABELS map (forward resolution) | W3 nested wallet labels |
| agent-card / description / url | Edge Config (static) | — |
The /ens page lets you type any (name, key) pair and watch the gateway resolve it live, latency included. Try tradewise.agentlab.eth / memory-rotations — the count goes up after every successful transferWithProof on Sepolia.
w3 — ensip-19 multichain primary names
Every wallet we own gets a primary name on Sepolia. Etherscan, MetaMask, and any wallet UI that does ENS reverse resolution shows the name instead of hex.
| role | address | ens reverse name |
|---|---|---|
| agent EOA | 0x7a83…20A3 | agent-eoa.tradewise.agentlab.eth |
| pricewatch deployer | 0xBf5d…2469 | pricewatch-deployer.agentlab.eth |
| validator | 0x0134…83F6 | validator.agentlab.eth |
| keeperhub turnkey | 0xB28c…6539 | keeperhub.agentlab.eth |
Forward addr(label) resolution is handled dynamically by the W2 gateway — we never wrote forward records on chain. Reverse records are set via the canonical Sepolia ReverseRegistrar (0xA0a1…C0C6); each wallet pays its own gas for the one-time setNamecall. The Turnkey wallet uses KeeperHub's execute_contract_call to broadcast.
w3 — keeperhub orchestration
KeeperHub is the agent's automation layer. Every paid x402 quote fires a workflow; chain events fire workflows; the agent uses KeeperHub the same way a SaaS company uses Zapier.
| workflow | trigger | action |
|---|---|---|
| Heartbeat | paid x402 quote (debounced 5min) | webhook → Redis (agent:1:last-seen) |
| ReputationCache | paid x402 quote (debounced 5min) | webhook → Redis (reputation:summary:1) |
| Swap (existing) | paid x402 swap quote | Web3 Write Universal Router |
| ENSPrimaryNameSetter (new) | manual + onboarding | Web3 Write ReverseRegistrar.setName(label) |
| ENSAvatarSync (new) | INFT mint/transfer confirm-transfer | Web3 Write PublicResolver.setText(avatar) |
| GatewayCacheInvalidator (new) | INFT MemoryReencrypted/Staled | webhook → /api/ens-gateway/cache/invalidate |
Heartbeat + ReputationCache used to write to chain (setText on the ENS PublicResolver per quote, ~14k gas burned per fire). PR #13 swapped their Web3 Writenodes for Webhook POST nodes pointing at our app — same trigger, same KeeperHub run visibility, but writes Redis instead of chain. Workflows still appear on /keeperhub with green checkmarks; gas drain went from real to zero.
Cross-link: ENSAvatarSync and GatewayCacheInvalidatorare triggered by the W1 INFT oracle's /api/inft/oracle/confirm-transferroute the moment a transferWithProof tx is mined. Avatar gets re-pointed at the new tokenId; ENS gateway cache formemory-rotations et al. is purged so the next ENS query returns fresh values.
contract addresses — full ledger
Deployed 2026-04-29 / 2026-04-30 as part of W1 + W2 + W3 (issue #11 / spec 2026-04-28-agent-identity-package-design.md).
w1 inft
w2 ens gateway
w3 wallets with primary names
misc
Memory blob anchor for tokenId=1: 0x3ed1812bac1c7c1424b86c8d2ce307b4b6a018ff8e8bb7b70035f0b80eb35ec6
source code: github.com/fritzschoff/hackagent
full design spec: agent-identity-package-design.md