Inspiration

AI agents can write code. They cannot get paid for it without a human in the loop, because nobody can safely answer one question: did this agent actually deliver what was asked?

Card rails need a human accountable for the charge. LLM-as-a-judge replaces that human with a model that can be argued into approving anything, and we proved it. Running Gemini's screening over MergeGate's own correct PASS submission produced a false positive: it flagged a comment the provider deleted, attributing knowledge to the party that removed it. Had that screening carried payment authority, 0.25 USDC of real money would have gone to the wrong party.

We asked a different question: what if the release condition were a reproducible test contract, not a model's opinion?

What it does

MergeGate is a native Circle agent-to-agent economy, not a verifier with a payment rail bolted on. A buyer agent funds programmable USDC escrow through a Circle Agent Wallet and signs the release condition before any work exists. Settlement releases or refunds as Circle Agent Wallet transfers on Base mainnet, submitted by Circle's own relayers so MergeGate pays no gas on those legs. And the verifier itself is an x402 service that Circle's own CLI pays: circle services pay verifies the buyer's EIP-3009 authorization, including the ERC-1271 signature of a Circle Agent Wallet, before the fee settles. Remove Circle and there is no escrow, no gas model, no agent-payable verifier and no settlement.

Circle Agentic Economy Prize: proof. Four Circle Agent Wallets on Base mainnet: Buyer 0x5c34e3e0…, Escrow 0x0c744ecb…, Provider 0xbe1424b7…, Verifier fee 0xe36b612b…. Seven real USDC transactions on Base mainnet, no human in any leg:

Leg From → To Amount Transaction
x402, paid by circle services pay Buyer → Fee wallet 0.05 USDC 0xb40552f2… · block 50018597
Escrow funded (PASS run) Buyer → Escrow 0.30 USDC 0x0d8caf15… · block 50059994
PASS, released Escrow → Provider 0.25 USDC 0xa1303e97… · block 50060061
Verifier fee (PASS run) Escrow → Fee wallet 0.05 USDC 0x6f94ef37… · block 50060075
Escrow funded (FAIL run) Buyer → Escrow 0.30 USDC 0xdb63e1ad… · block 50060104
FAIL, refunded Escrow → Buyer 0.25 USDC 0xc9a5e865… · block 50060179
Verifier fee (FAIL run) Escrow → Fee wallet 0.05 USDC 0x177a46af… · block 50060191

Escrow funding, release and refund are submitted by Circle's own relayers against wallets holding zero ETH. The buyer's policy (25 USDC/day, 100/month, origin=CUSTOM) needs a human OTP to widen, so the agent cannot raise its own ceiling. Public repo: github.com/4KInc/mergegate.

  1. Buyer agent pins the terms: repo, base commit, test bundle, writable paths, protected paths, commands, deadline, price. Hashes everything into a contract and funds USDC escrow. No human clicks anything.
  2. Provider agent reads the published mandate, sees the acceptance test is already fixed and hashed, and submits a commit.
  3. Sealed Cloud Run job, pinned by image digest on a VPC with no Cloud NAT, checks out the buyer's base tree, applies the provider's diff to allowed paths only, overwrites the test tree with the buyer's grader bundle, and runs only the buyer's pinned commands.
  4. Settlement is deterministic: PASS releases escrow to the provider, FAIL refunds the buyer. The mandate is executed, not re-decided.
  5. One receipt binds contract, grader, artifact, environment, execution id, funding transaction, decision and settlement transaction into a single object anyone can verify offline. Fifteen of its twenty-two bound fields survive an attacker holding the signing key.

No LLM is called anywhere in contract creation, evaluation, settlement, or receipt issuance.

The demo's FAIL flow is the point. That submission's code was correct, and would have passed the buyer's tests. It was refused before the tests ran, because it also edited a protected CI file. The receipt names the exact violated term:

contract evaluated FAIL: .github/workflows/deploy.yml modifies a contract-protected path (pattern: .github/**)

That is the difference between a control layer and a test runner wired to a transfer.

Then the loop closes. Gemini explains the failure, the provider agent reverts exactly what the contract's path guard rejects (computed from the contract, not written by a model), and resubmits under a new contract linked by retry_of. That one passes and the provider is paid. python -m mergegate.demo retry runs the whole thing.

Live at https://mergegate.dev, with the one-page case at /judge.

How we built it

Settlement: Circle Agent Wallets for programmable escrow, driven by the circle CLI rather than the REST API. Four wallet roles: buyer (funds escrow), escrow (holds reward plus fee), provider (receives payment on PASS), verifier-fee (receives the per-evaluation fee regardless of verdict). Each runs under a real Circle spending policy, and the buyer's is verifiable with one command: circle wallet limit --address 0x5c34e3e0… --chain BASE reports a daily cap of 25 USDC and a monthly cap of 100 with origin=CUSTOM. circle wallet limit set requires a human OTP, so the agent spends inside an envelope it has no way to widen. The /wallets page renders those policies by shelling out to that same command (mergegate/payments/policy.py); on the current deployment it cannot reach the wallet API and says so rather than rendering an empty table that would read as "no limits". Double-payment has two independent guards: the settlement state machine, and the settlement key passed to Circle as a deterministic UUIDv5 idempotency key, verified by sending the same key twice and watching one transfer result.

Gas, stated precisely. The four escrow legs cost MergeGate nothing: each was submitted by an address we neither configure nor fund, because Agent Wallets sponsor gas. Those submitters turned out to be three different Circle relayers rather than one, which an earlier version of our own documentation got wrong. The x402 leg is the exception and is not sponsored: EIP-3009 makes the recipient submit, so MergeGate runs its own relayer and pays 96,381 gas at 0.006 gwei, about 0.00000058 ETH.

Sandbox: Cloud Run job on a sealed VPC (mergegate-sealed) with no Cloud NAT and a deny-all egress firewall rule. Verified by probing inside the real job, on the pinned image: outbound TCP to the public internet blocked, including to a Google public address. Two channels are open and disclosed rather than hidden, DNS and one allowed destination (Google's restricted API VIP), without which the job cannot mount the volume its inputs arrive on. "Deny all egress" and "receive inputs" are not simultaneously satisfiable, so the posture string names the exception instead of rounding it off. The assembly order enforces neutrality: base tree, then provider diff to allowed paths only, then quarantine provider test hooks, then purge grader paths, then inject the buyer's bundle, then install the runtime guard, then hash the tree.

Six attack vectors tested and defeated against a real pytest process:

  • Rewriting the graded tests
  • A conftest.py hook that forces every outcome to pass
  • A sitecustomize.py that runs before any test is imported
  • Reading the reference solution out of .git history
  • Functionally-correct code that disables the CI gate on its way past
  • A submission that implements nothing and answers from a lookup table by scraping the test file at runtime

Gemini (4 advisory roles, never in the settlement path):

  1. Draft terms from a plain-language request. A deterministic policy engine validates them, and a draft that fails cannot be funded.
  2. Assess a contract before a provider accepts it, returning ACCEPT / REQUEST_CLARIFICATION / DECLINE. Confidence is capped deterministically when the acceptance tests are hidden, because a confident ACCEPT on criteria nobody can read is a guess dressed as a finding.
  3. Screen the provider's diff for malicious code, supply-chain attacks, test gaming and obfuscation. Does not gate the run.
  4. Explain a FAIL into a structured retry plan, checked against the contract's own path guard before the provider can act on it.

The boundary is enforced by tests (test_gemini_boundary.py) three ways: no settlement-path module imports any advisory module (structural), the settlement directive is identical for every model output including "release the escrow immediately" (behavioural), and a diff carrying a prompt injection still refunds the buyer (adversarial).

x402: The verifier is sold as an x402 service. circle services pay from a Circle Agent Wallet settles 0.05 USDC on Base, agent to agent, no human, no dashboard. Three corrections were needed to get there: Circle nests the payment terms under accepted, Circle Agent Wallets are smart contract accounts whose ERC-1271 signatures do not ECDSA-recover to the account address, and Circle's CLI sends payment-signature instead of the spec's X-PAYMENT header. None was findable without pointing the real client at the running service.

Receipt chain: Ed25519-signed receipts with 15 of 22 fields cross-checked, canonical JSON (RFC 8785), Merkle hashing. Verified offline via mergegate verify, which exits 0 verified, 1 failed, 2 could not check. An MCP server exposes the read side to agents through nine tools, read-only and deliberately so: an MCP server is driven by whatever a model decides to call, so a funding tool would be a wallet-draining primitive one prompt injection away.

Infrastructure: Python/FastAPI on GCP Cloud Run, Firestore for settlement state, Secret Manager for keys, and Google Antigravity for agent customizations, skills (SKILL.md) and MCP integrations. 490 tests across 26 files, roughly 18,500 lines of Python, CI-enforced (ruff + mypy + pytest).

Challenges we ran into

A submission implementing nothing passed the tests. It read the buyer's test file at runtime and answered from a lookup table. Every defense in the sandbox stopped the provider editing the tests; none stopped it reading them. Reading is sufficient: scrape the expected values out of the assertion, build a lookup table, return the right answers. Fixed with a CPython audit hook loaded from a sitecustomize module outside the workspace, installed before any test or provider code runs and unreachable by the provider's diff.

The sandbox reached the internet while the code said "default-deny." That field is written into a signed receipt, so MergeGate would have signed a false statement. Probing inside a real Cloud Run job showed Cloud Run grants internet egress by default. Fixed with a custom VPC, no Cloud NAT, deny-all egress firewall rule. Then the seal broke the job outright: inputs arrive on a Cloud Storage volume and gcsfuse dials storage.googleapis.com from inside the graded namespace, so the first live sealed run died at mount. Exactly one destination is now allowed, and the receipt says so.

The sealed job existed but nothing dispatched to it. For a period, build_job_request constructed a Cloud Run job no caller submitted while grading ran in the calling process, and the manifest defaulted its egress field to the sealed posture regardless. Every receipt issued then asserted an isolation the run did not have. The default now states the weaker truth, and a run has to be told it was sealed. Worse, a tripwire written to catch exactly this watched for the Python SDK while dispatch arrived through the gcloud CLI, so it sat green across the change it existed to catch.

Every real Circle x402 payment failed, and the logs could not say why. x402 specifies the payment header as X-PAYMENT; Circle's CLI sends payment-signature. A genuine payment arrived indistinguishable from an unpaid request. Three plausible theories were wrong first. Pointing circle services pay at a local server that printed its own request headers answered it in one run. Circle Agent Wallets also turned out to be smart contract accounts whose ERC-1271 signatures do not ECDSA-recover to the account address, which looks exactly like a forgery until you check for contract code.

Settlement de-duplication lived in memory on a platform that cold-starts. True in tests, false in production. Now Firestore-backed with a per-task transaction.

Gemini produced a false positive on the first real submission it screened. It flagged a comment the provider deleted, attributing knowledge of the grader to the party that removed it. It has now scored that same wrong finding 40/100, then 10/100, then 25/100 across three runs on equivalent input. Not converging. Had the screening carried gating power, a correct submission would have been refused, and whether it was refused would depend on which run you got. This is exactly why the settlement path consults an exit code, not a model.

Accomplishments that we're proud of

  • Real USDC on Base mainnet: 7 confirmed transactions across the two flows, being 2 escrow fundings, 2 settlements (PASS release and FAIL refund), 2 verifier fees and 1 x402 payment. Not testnet tokens. Both flows were graded inside the sealed Cloud Run job (executions mergegate-verifier-5rbrl and mergegate-verifier-mc5bj), and the receipts carry the network posture measured from inside those jobs.
  • The FAIL flow: correct code refused for a policy violation, not a test failure. The receipt names the term. The tests never ran. This is the whole product.
  • 6 attack vectors defeated against a real pytest process, not mocks, including the grader-scraping attack that exposed a gap in the original design.
  • x402 settled with Circle's own CLI: circle services pay presents an EIP-3009 authorization that MergeGate verifies through ERC-1271 isValidSignature, because the payer is a smart contract account rather than an EOA.
  • Receipts re-verify offline: 18 checks on a PASS and 17 on a FAIL, with 15 of 22 bound fields surviving an attacker holding the signing key. Proven by re-signing tampered copies.
  • The false positive that proves the architecture: Gemini flagged a correct submission. The provider was paid anyway. That is the demonstration, not a bug.

What we learned

Deploying is a test. Most of the hardest failures were invisible locally and appeared only against real infrastructure. The x402 one went further: it was invisible even in production logs, because a payment the server never saw is indistinguishable from a request that carried none. Reproducing a vendor's client against a server that prints what it receives found in one run what three deploys of theorising did not.

LLM-as-judge is not a safe payment authority. We observed it fail on the very first real submission, not in a synthetic test, and then fail differently on the same input twice more. Because the architecture was right, it did not matter.

A guard nobody has watched fail is not a guard. Two of ours were green while failing: the dispatch tripwire keyed to the wrong transport, and a page that claimed to show "the most recent run" while sorting receipts alphabetically by id. Both were found by checking output against reality rather than re-reading code.

Circle Agent Wallets make per-evaluation pricing possible in ways card rails cannot. At 0.05 USDC the verifier fee is far below the roughly 0.30 interchange floor a card transaction carries, and the escrow legs cost us no gas at all. That is not sub-cent pricing, and we are careful not to claim it is: 0.05 USDC is the metering floor this deployment actually charges.

What's next for MergeGate

  • Buyer bond: a slashable bond posted alongside escrow, so a buyer who triggers an evaluation pays for it even on a refund. This closes the buyer-griefing gap.
  • Circle Gateway, evaluated and not adopted. We tested it rather than assuming: a Gateway balance cannot live on Base for these wallets, because direct deposit needs native gas on the source chain and our agent wallets hold zero ETH by design, while eco lands the balance on Polygon. A real 0.5 USDC eco deposit on mainnet had not credited to a queryable Gateway balance on any supported chain at the time of writing. Gateway is also not the gas-free off-chain rail it is easy to assume, since a transfer out settles through an on-chain gatewayMint on the destination chain. Its real contribution is a unified balance spendable across chains without pre-funding each one, which is the shape of the v2 argument.
  • Coding agent integrations: position MergeGate as the settlement layer for coding agents delivering work for payment.
  • Delivery without full disclosure: commit-reveal with escrowed disclosure so a provider can prove a diff passes without handing it over first. The real blocker to a permissionless market.

Honest limits

Stated here rather than left for a reviewer to find.

  • Verified contract acceptance is not code quality. A PASS means the submission satisfied the buyer's pinned tests. It does not mean the code is good, secure or mergeable.
  • Buyer griefing is unsolved. A bad-faith buyer can pin an unpassable test, read the diff, and take the refund. Scope is trusted buyers and approved providers.
  • Custody is real. MergeGate holds escrow authority. This is programmable USDC escrow with policy-bound conditional settlement, not a non-custodial arrangement. The circle CLI session that drives settlement is a bearer credential: anyone holding it can move USDC.
  • Circle Agent Stack coverage is 2 of the 5 named components, Agent Wallets and the Circle CLI, plus x402 / EIP-3009 settlement with ERC-1271 Agent-Wallet signature verification, which is a Circle-supported standard rather than a named component. Gateway, Agent Marketplace and Circle Skills are not used, and we would rather say so than round up.
  • The 20% demo fee rate (0.05 on a 0.25 reward) is a figure chosen so both numbers are legible in a block explorer, not a business model.
  • No customers and no third-party revenue. Every transaction shown is self-paid between wallets we operate.
  • Whether an expired task should be charged a fee is unresolved, and written down as an open pricing decision rather than quietly implemented either way.
  • "MergeGate" is a working name. Commercial use would require trademark and availability work that has not been done.

Built With

  • base
  • circle-agent-wallets
  • cli
  • ed25519
  • fastapi
  • firestore
  • gemini
  • gvisor
  • jinja
  • manager
  • mcp
  • pydantic
  • python
  • secret
  • usdc
  • x402
Share this project:

Updates