jagnani73P.03
[the record]STATUS: NTU SINGAPORE - MSC BLOCKCHAIN

Gantry

NTU INNOVATEX 2026 · 3RD
a hawker integrates once and gets paid by anyone: a tourist scanning a printed QR, or an AI agent paying over x402. both are the same on-chain intent.
fig. 1: different currencies, different amounts, the same S$4.50USDC or EURC in · XSGD out
human · EIP-3009
agent · x402 v2
USDC3.352955
EURC2.980133
GantryCore_settle()
4.50 XSGDmerchant
either currencyone payout
01THE IDEAthe payers PayNow cannot reach

PayNow 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.

02THE RAILone intent · two encodings · one settlement

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.

01

INTENT

the relayer pins a quote on-chain: token, amount, expiry

Solidity · Foundry

02

HUMAN DOOR

printed QR → payer page → one typed-data signature, no gas

EIP-3009 · Next.js 15

03

AGENT DOOR

402 challenge offering the standard scheme and a policy scheme

x402 v2 · gantry-pbm

04

POLICY

the agent's wallet checks signature, caps, category, expiry

EIP-712 · AgentPBMWallet

05

SETTLE

pull → swap → pay the merchant → one event, both doors

GantryCore · viem

STACK: TypeScript monorepo · Solidity · Foundry · Next.js 15 · Express · viem · SQLite · Vercel AI SDK · Base Sepolia
03THE HARD PARTwhere the guarantees actually live

Two payer types on one contract is mostly plumbing. These are the parts that had to hold without anyone trusting the operator:

01

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

02

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

03

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

04

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

05

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

04BY THE NUMBERSrunning on Base Sepolia
0

ETH the payer needs; the relayer submits every settlement

0.5%

protocol fee, against ~2.8% on cards

581

tests: 201 Foundry, 189 backend, 187 shared, 4 agent

4

contracts deployed and verified on Basescan

05IN THE WILDplates 01–05 · both doors, one feed