Gantry
NTU INNOVATEX 2026 · 3RDPayNow is free and it works, if you hold a Singapore bank account. Roughly 16 million people visit Singapore each year without one, and software cannot open one at all.
Gantry is one payment rail with two doors. A merchant registers once, on-chain, and gets back a printed QR and an HTTP endpoint. A tourist scans the standee and signs an EIP-3009 authorization on their phone, holding no ETH. An AI agent hits the same endpoint, gets a 402 Payment Required carrying an x402 v2 challenge, and pays inside the limits its owner set on-chain. Both are the same PaymentIntent, both land in the same merchant feed, and the shop is paid in XSGD whatever token arrived.
Each door is a thin encoding layer over the same settlement path. Everything below GantryCore._settle is written once: pull the funds, swap to XSGD behind a min-out guard, pay the merchant, emit the event the dashboard is listening to.
INTENT
the relayer pins a quote on-chain: token, amount, expiry
Solidity · Foundry
HUMAN DOOR
printed QR → payer page → one typed-data signature, no gas
EIP-3009 · Next.js 15
AGENT DOOR
402 challenge offering the standard scheme and a policy scheme
x402 v2 · gantry-pbm
POLICY
the agent's wallet checks signature, caps, category, expiry
EIP-712 · AgentPBMWallet
SETTLE
pull → swap → pay the merchant → one event, both doors
GantryCore · viem
Two payer types on one contract is mostly plumbing. These are the parts that had to hold without anyone trusting the operator:
the nonce is the intent
the EIP-3009 nonce is the intent id, so a signature can only settle the payment it was signed for. vanilla x402 clients generate their own nonce, so a facilitator bridge collects and re-signs instead of forking the standard
refusal by revert
an agent buying outside its category is refused by AgentPBMWallet, not by a backend if-statement; the revert is caught by simulate-before-send, so CategoryNotAllowed rides out on the cancel and reaches the agent verbatim as the x402 error reason
no key can raise a cap
agent wallets are payer-owned and setPolicy/revoke are onlyOwner, so there is no policy endpoint and no server key that can arm or lift a spend limit
zero gas for the payer
the relayer holds the only gas key and submits every settlement, so a tourist with a stablecoin balance and no ETH can still pay a hawker
labelled mocks
XSGD exists on no testnet, so it is mocked and the UI says so. the FX rate is owner-set, merchant categories are self-attested, and the one custodial hop on the standard scheme is named on the screen where it happens
ETH the payer needs; the relayer submits every settlement
protocol fee, against ~2.8% on cards
tests: 201 Foundry, 189 backend, 187 shared, 4 agent
contracts deployed and verified on Basescan