x402 · USDC on Base + Solana

x402-github-bounty

Put money on a GitHub issue and get a tamper-evident record of it — certificate, verification, receipt. $0.01 to mint, $0.002 to verify. Non-custodial: the service never touches the bounty.

git clone https://github.com/nirholas/x402-github-bounty && cd x402-github-bounty && npm install && npm run dev
GitHub Tutorial API Reference For AI Agents

Pricing

RoutePriceWhat lands in the 200 body
POST /bounties$0.01A signed bounty certificate — issue URL, title, pledged amount, terms, expiry — minted only after the issue is confirmed real and open through the live GitHub API. Plus a one-time settleKey.
GET /verify/:bountyId$0.002A signed verification report from live GitHub state: is the issue closed, which merged PRs reference it, who wrote them, is the bounty eligible
POST /settle/:bountyIdfreeA signed payout receipt. Closing out costs nothing — you already paid to mint
GET /bounties · POST /check-signature · GET / · /health · /skill.md · /.well-known/x402 · /openapi.jsonfreePublic board, signature validation, discovery

How a paid call works

Agent GET /resource 402 Payment Required accepts: [ two rails ] USDC on Base EIP-3009 signature USDC on Solana signed SPL transfer the client picks X-PAYMENT facilitator: verify → settle 200 artifact
One round trip of overhead. Every 402 quotes both rails; the client signs whichever chain it holds USDC on.

Two rails, one price

USDC on Base
network base-sepolia / base
scheme exact · EIP-3009 transferWithAuthorization
payTo 0x40252CFDF8B20Ed757D61ff157719F33Ec332402
USDC on Solana
network solana / solana-devnet
scheme exact · SPL transferChecked, fee sponsored
payTo WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW

Both rails appear in the accepts array of every 402. Settlement runs through the x402 facilitator, and the 200 carries an X-PAYMENT-RESPONSE receipt naming the rail and the transaction.

Quickstart

# 1. run it — .env.example already has working defaults
npm install && npm run dev              # http://localhost:4027

# 2. mint without paying → the dual-rail challenge
curl -s -X POST localhost:4027/bounties -H 'content-type: application/json' \
  -d '{"issueUrl":"https://github.com/nodejs/node/issues/1","amount":25}' \
  | jq '.accepts[] | {network, payTo, maxAmountRequired}'
# { "network": "base-sepolia", "payTo": "0x40252CF…2402", "maxAmountRequired": "10000" }
# { "network": "solana",       "payTo": "WwwuGbqH…T3WwW", "maxAmountRequired": "10000" }

# 3. pay $0.01 → the signed certificate lands in the 200 body
const pay = wrapFetchWithPayment(fetch, wallet);
const res = await pay(`${BASE}/bounties`, { method: "POST", body: … });
const { certificate, signature, settleKey } = await res.json();

# 4. later — buy proof that someone fixed it ($0.002)
await pay(`${BASE}/verify/${certificate.bountyId}`);
// { report: { issue: { state: "closed" }, mergedPrs: [{ number: 418, author: "…" }],
//             eligible: true }, signature: "…" }

Non-custodial, and that is the point

This service never holds your money. The $0.01 buys the certificate; the amount you declare is a pledge signed into it. You pay the claimant yourself and record it with POST /settle/:bountyId, which returns the signed receipt.

Custody is the hard part of every bounty platform — it is where the fees, the lock-ups, the disputes and the regulatory surface come from. Strip it out and what is actually scarce remains: a tamper-evident record that this issue was open, this amount was pledged, this PR merged, this payout happened. That record is the product, and it costs a cent.

Signed artifacts

Certificates, reports and receipts are HMAC-SHA256 over canonical JSON — keys sorted recursively, so the signature is stable regardless of field order. Validate any of them for free:

curl -s -X POST localhost:4027/check-signature \
  -H 'content-type: application/json' \
  -d '{"payload": <the signed object>, "signature": "<hex>"}'

{ "valid": true, "type": "x402-bounty-certificate", "checkedAt": "…" }

Set SIGNING_SECRET in production — with the default dev secret (the server warns at boot) anyone running this code can forge a certificate that looks like yours. And read a signature for what it is: proof that this deployment observed that GitHub state at that time, not proof that anyone escrowed funds.

Live GitHub, no fixtures

Every certificate and every report is built from the GitHub REST API at request time. Minting checks the issue exists, is an issue rather than a PR, and is still open — otherwise you get a 404, 400 or 409 and no certificate. Verification reads the issue timeline for cross-referenced pull requests and confirms each one actually merged.

GITHUB_TOKEN is optional: without it you get GitHub's 60 requests/hour anonymous quota, with it 5,000/hour plus issues in private repos the token can read. There is no fixture mode.