AI agents need to buy things. Not in the future — today. An agent that finds a skill it needs should be able to pay for it and install it, without a human in the loop for every transaction.
This is how we implemented autonomous agent payments at MarketNow using x402 (HTTP 402 Payment Required) and AP2 (Agent Payments Protocol) delegated mandates.
The flow
Agent → POST /api/agent-purchase {skillId, mandateId}
← 402 Payment Required (x402 challenge)
Agent → sends USDC on Base
Agent → POST /api/agent-purchase {skillId, mandateId, txHash}
← 200 OK {license, system_prompt, install}
Step 1: The 402 response
When an agent requests a paid skill without payment, we return HTTP 402:
res.setHeader('WWW-Authenticate', `x402 realm="marketnow", chain="base", token="USDC"`);
res.setHeader('X-Payment-Amount', String(skill.price * 10**6));
res.setHeader('X-Payment-Token', 'USDC');
res.setHeader('X-Payment-Chain', 'base');
res.setHeader('X-Payment-Contract', '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
res.setHeader('X-Payment-To', '0x39Dddf5aEdb58A559CF195fB8bdF23F0604Bf5Ee');
return res.status(402).json({
x402: {
accepts: {
scheme: 'x402',
network: 'base',
asset: 'USDC',
amount: skill.price,
to: '0x39Dd...f5Ee',
},
retry_instructions: {
method: 'POST',
url: 'https://marketnow.site/api/agent-purchase',
body: { skillId, mandateId, txHash: '<USDC tx hash on Base>' },
},
},
});
The agent reads the headers, sends USDC, and retries with the txHash.
Step 2: Verify the payment on-chain
We verify the USDC transfer by calling eth_getTransactionReceipt on Base:
async function verifyUsdcTx(txHash, expectedAmountRaw, expectedFromWallet) {
const { result: receipt } = await baseRpc.call(
'eth_getTransactionReceipt', [txHash]
);
// Check tx succeeded
if (receipt.status !== '0x1') return { ok: false, code: 'tx_failed' };
// Parse Transfer event logs
for (const log of receipt.logs) {
if (log.address.toLowerCase() !== USDC_CONTRACT) continue;
if (log.topics[0] !== TRANSFER_TOPIC) continue;
const from = '0x' + log.topics[1].slice(26);
const to = '0x' + log.topics[2].slice(26);
const value = BigInt(log.data);
if (to.toLowerCase() === PAYMENT_WALLET.toLowerCase()) {
// C3 FIX: exact amount match (not >=)
if (value !== BigInt(expectedAmountRaw)) {
return { ok: false, code: 'amount_mismatch' };
}
// C4 FIX: validate sender wallet
if (from.toLowerCase() !== expectedFromWallet.toLowerCase()) {
return { ok: false, code: 'wrong_sender' };
}
return { ok: true, from, amount: Number(value) };
}
}
return { ok: false, code: 'no_transfer_to_marketnow' };
}
Step 3: Replay defense
Each txHash can only be used once. We persist used txHashes as files in the GitHub repo:
async function isTxHashUsed(txHash) {
const url = `https://raw.githubusercontent.com/${repo}/master/_data/used_txs/${txHash}.json`;
const r = await fetch(url);
return r.status === 200; // already used
}
async function markTxHashUsed(txHash, licenseKey, skillId, amount) {
// Write file to GitHub via Contents API
await fetch(`${GITHUB_API}/repos/${repo}/contents/_data/used_txs/${txHash}.json`, {
method: 'PUT',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({
message: `tx: mark used ${txHash}`,
content: Buffer.from(JSON.stringify({ txHash, skillId, licenseKey, amount })).toString('base64'),
}),
});
}
AP2 Mandates: human-in-the-loop by default
Before an agent can buy, the human principal creates a mandate with spending limits:
{
"owner": "0xABC...",
"agentId": "claude-001",
"spendingLimitUsd": 50,
"perPurchaseCapUsd": 10,
"notificationMode": "notify",
"expiresAt": "2026-10-01T00:00:00Z"
}
Key design decisions:
- Default is "notify" — the principal gets an email/webhook on every purchase. NOT silent.
-
"silent" mode requires explicit
confirmSilentAutonomy: true— opt-in, not default. - Hard caps: $500 max total, $50 max per purchase, 90-day expiry.
- Instant revocation — principal can revoke anytime.
- Every spend is a git commit — full audit trail.
The security fixes we learned from a pentest
We had a security auditor review the implementation. They found 4 critical issues:
| Issue | Severity | Fix |
|---|---|---|
| Mandate spend failed silently (cap never enforced) | CRITICAL | Fail-closed: if spend fails, NO license issued |
| txHash replay (same payment used twice) | CRITICAL | Dedup store via GitHub _data/used_txs/
|
Amount mismatch (>= instead of ==) |
CRITICAL | Exact match: value !== BigInt(expected)
|
| Stolen txHash (anyone could redeem) | CRITICAL | Validate from wallet matches caller |
All 4 are now fixed and live in production.
Try it
# Search for skills
curl "https://marketnow.site/api/search?q=scraper&limit=5"
# Get a free skill (no payment needed)
curl -X POST "https://marketnow.site/api/agent-purchase" -H "Content-Type: application/json" -d '{"skillId":"mn-prompt-63712dff"}'
# Buy a paid skill with USDC on Base
curl -X POST "https://marketnow.site/api/agent-purchase" -H "Content-Type: application/json" -d '{"skillId":"mn-gen-00001","mandateId":"mand_xxx","txHash":"0x...","walletAddress":"0x..."}'
Links
MarketNow — trust layer for agent commerce. 8,560 MCP skills, Sentinel L2 security, x402 + USDC on Base. AliceLabs LLC (Wyoming, USA).
Top comments (0)