canton-x402
Let your AI agent pay for x402 APIs on Canton.
Two paths: a self-custody MCP where the agent holds its own key, and a hosted HTTP flow any agent can call with no install and no signing. Every payment settles on-ledger on Canton MainNet.
Products
Two ways to pay
Self-custody MCP
The agent generates and holds its own Canton wallet. Connect the server once to a host that can run a local process, then the agent funds and pays itself.
Best for: desktop and IDE agents, self-hosted agents.
Set up the MCPHosted HTTP
No installThe agent calls our endpoints over plain HTTP. Works with any agent that can make an HTTP request, including browser-only hosts like ChatGPT. Payment settles from a hosted purse or a hosted ephemeral wallet, not from the agent’s own self-custody wallet.
Best for: ChatGPT, no-install agents, one-off calls.
Quickstart belowWhich should I use?
Hosted quickstart
One paid call (simplest)
One HTTP call, settled from a hosted purse on Canton MainNet. No wallet to provision.
curl -s https://pay.ftptech.xyz/v1/demo/ask \
-H "content-type: application/json" \
-d '{"prompt":"a photorealistic red panda coding at a laptop"}'Returns 200 with { answer, model, spentCc, updateId, remainingDailyCc }, plus an image when the merchant returns one. Errors are flat { ok: false, error: <code> }, where <code> is one of bad_request, rate_limited, budget_exhausted, proxy_busy, paid_but_no_response, upstream_error.
curl -s https://pay.ftptech.xyz/Returns the live service descriptor: network, the one-shot method and path, and docs.
Agent gets its own funded wallet (two steps)
Use this when the agent should hold a real, funded wallet for the call. Every POST is async: it returns 202 with a job id. Poll the matching GET every 3 seconds until the status is terminal. Do not re-POST in a loop.
Step 1 creates and funds a Canton wallet for the agent. You get back its party id, a balance, and a secret, which is the wallet’s private key. Keep the secret; it is the only key to that wallet.
Paste into your agent's chat›
STEP 1 of 2: create and fund a Canton wallet via an HTTP API. Base URL: https://pay.ftptech.xyz. This step is async: the POST returns HTTP 202 with a job id, then you GET the poll URL every 3 seconds until status is terminal ("funded" or "failed"). Each HTTP call returns quickly; the slow work happens in the background, so just keep polling. All requests/responses are JSON.
1. POST https://pay.ftptech.xyz/v1/quest/wallet/create with header `content-type: application/json` and body `{}`. Read `walletJobId` from the 202 response.
2. Poll GET https://pay.ftptech.xyz/v1/quest/wallet/result?walletJobId=<walletJobId> every 3s:
- {"status":"pending"} -> keep polling.
- {"ok":false,...} -> stop and report the `error`.
- {"status":"funded",...} -> done. SAVE `walletToken`, `party`, `balanceCc`, and `secret` (the secret is the wallet's PRIVATE KEY — it IS the wallet; whoever holds it controls the funds).
Do NOT re-POST create in a loop; if you must retry, re-issue the SAME POST (the server coalesces a create retry, so it never makes a second wallet). On 429 report rate_limited; on 503 report budget_exhausted.
REPORT BACK: party + balanceCc, then a "WALLET BACKUP — SAVE THIS" section that hands your user the FULL `secret` PEM in a code block plus the restore instructions from the response's `backup` field. The wallet belongs to the USER and keeps working after this chat; delivering the secret to them here IS the backup — without it the wallet is unrecoverable. Never send the secret to any URL or tool, and if the user asks for a backup later, give them this SAME saved secret — never create a new wallet for a backup. KEEP the walletToken; you need it for step 2.Step 2 generates an image and settles its 0.25 CC payment on Canton from that wallet. You get back the image plus an updateId, the on-ledger transaction that is independently verifiable on the ledger.
Paste into your agent's chat›
STEP 2 of 2: generate an image and settle the on-ledger payment for it from the wallet you funded in step 1. The merchant is the CanTrust image API; one call settles 0.25 CC and returns a generated image. Same base URL https://pay.ftptech.xyz and the same async pattern (the POST returns HTTP 202 with a job id, then you GET the poll URL every 3 seconds until terminal). Use the `walletToken` you saved in step 1 (it is valid for about 15 minutes, so do this promptly). Do NOT send the secret.
3. POST https://pay.ftptech.xyz/v1/quest/wallet/pay with header `content-type: application/json` and body `{"walletToken":"<walletToken from step 1>","prompt":"<describe the image you want>"}`. Read `payJobId` from the 202 response.
4. Poll GET https://pay.ftptech.xyz/v1/quest/wallet/pay-result?payJobId=<payJobId> every 3s (this step is slower, allow about 20 to 30 seconds):
- {"status":"pending"} -> keep polling.
- {"ok":false,...} -> stop and report the `error`.
- {"status":"done",...} -> done. SAVE `image` (the URL of the generated image) and `updateId` (the on-ledger settlement proof).
5. (Optional) Confirm it: GET https://pay.ftptech.xyz/v1/quest/verify?updateId=<updateId> and check `valid==true`.
If a pay POST returns 404 it means wallet_not_found (the walletToken expired or was already used); 409 means already_used (re-poll your existing pay job instead of re-POSTing). Do NOT re-POST pay in a loop; the server rejects a duplicate pay, so it never makes a second transaction.
REPORT BACK to the user and LEAD with the image: render it inline as a markdown image so the user actually SEES the picture -> `` -> then put the raw `image` URL on its own line, then `updateId` and verify.valid. The generated image is the whole point of this task; never omit it or replace it with a text summary. End with a wallet reminder: the step-1 wallet still holds the change and KEEPS WORKING — if the "WALLET BACKUP" block (the step-1 `secret` PEM + restore instructions) has not been delivered to the user yet, deliver it now; any later backup request means handing over that SAME saved secret, never creating a new wallet.The secret is the wallet private key. It is returned exactly once, in the step-1 funded body, and never again. The walletToken is single-use and expires in about 15 minutes. The featured merchant is the CanTrust image API, which settles 0.25 CC and returns an image. Single-call variant: POST /v1/quest/run then GET /v1/quest/result runs both legs in one job if you do not need the wallet token between steps.
API reference
Base https://pay.ftptech.xyz. Every call is async: POST returns 202 plus a job id, then you poll the GET every 3 seconds. 429 means wait, 503 means the daily budget is reached. Do not re-POST in a loop.
/v1/quest/wallet/createBody {} → 202 { walletJobId }.
/v1/quest/wallet/result?walletJobId=...Poll to status:"funded" → party, balanceCc, secret, walletToken.
/v1/quest/wallet/payBody { walletToken, prompt } → 202 { payJobId }. No secret is sent. The server pays from your funded wallet.
/v1/quest/wallet/pay-result?payJobId=...Poll to status:"done"(about 20 to 30 seconds) → { party, updateId, answer, image?, model, balanceCc }.
/v1/quest/verify?updateId=...Public, no-auth → { valid, party, completedAt }.
Pay-result errors: 404 is wallet_not_found (the token expired or was used), 409 is already_used (re-poll the existing job, do not re-POST).
# Kick off, returns 202 { walletJobId }
curl -s https://pay.ftptech.xyz/v1/quest/wallet/create \
-H "content-type: application/json" -d '{}'
# Poll every 3s until status is "funded" (or "failed")
curl -s "https://pay.ftptech.xyz/v1/quest/wallet/result?walletJobId=<walletJobId>"# Generate an image and settle 0.25 CC, returns 202 { payJobId }. No secret is sent.
curl -s https://pay.ftptech.xyz/v1/quest/wallet/pay \
-H "content-type: application/json" \
-d '{"walletToken":"<walletToken from step 1>","prompt":"a photorealistic red panda coding at a laptop"}'
# Poll every 3s until status is "done" (the slow leg, about 20 to 30 seconds). The result has an "image" URL.
curl -s "https://pay.ftptech.xyz/v1/quest/wallet/pay-result?payJobId=<payJobId>"
# Verify the on-ledger proof (public, no auth)
curl -s "https://pay.ftptech.xyz/v1/quest/verify?updateId=<updateId>"Run it from a terminal
Prefer to run it by hand, with no agent? This script does both steps with curl and jq. Verified working end to end.
#!/usr/bin/env bash
# Canton wallet + payment in one paste. Needs: curl, jq. Run it in your terminal.
set -euo pipefail
BASE="https://pay.ftptech.xyz"
echo "STEP 1 - create + fund a Canton wallet..."
WJ=$(curl -s "$BASE/v1/quest/wallet/create" -H "content-type: application/json" -d '{}' \
| jq -r .walletJobId)
while :; do
R=$(curl -s "$BASE/v1/quest/wallet/result?walletJobId=$WJ")
S=$(echo "$R" | jq -r '.status // empty')
[ "$(echo "$R" | jq -r '.ok')" = "false" ] && { echo "FAILED: $(echo "$R" | jq -r .error)"; exit 1; }
[ "$S" = "funded" ] && break
sleep 3
done
TOKEN=$(echo "$R" | jq -r .walletToken)
PARTY=$(echo "$R" | jq -r .party)
echo "$R" | jq -r .secret > canton-wallet-key.pem && chmod 600 canton-wallet-key.pem
echo " funded wallet: party=$PARTY balance=$(echo "$R" | jq -r .balanceCc) CC"
echo " wallet PRIVATE KEY saved to ./canton-wallet-key.pem - back it up; it IS the wallet."
echo " restore later: CANTON_AGENT_HOME=<empty dir> npx @ftptech/canton-agent-wallet import \\"
echo " --relay-url https://facilitator.ftptech.xyz --key-file canton-wallet-key.pem"
echo "STEP 2 - generate an image and settle 0.25 CC on Canton..."
PJ=$(curl -s "$BASE/v1/quest/wallet/pay" -H "content-type: application/json" \
-d "{\"walletToken\":\"$TOKEN\",\"prompt\":\"a photorealistic red panda coding at a laptop\"}" | jq -r .payJobId)
while :; do
R=$(curl -s "$BASE/v1/quest/wallet/pay-result?payJobId=$PJ")
S=$(echo "$R" | jq -r '.status // empty')
[ "$(echo "$R" | jq -r '.ok')" = "false" ] && { echo "FAILED: $(echo "$R" | jq -r .error)"; exit 1; }
[ "$S" = "done" ] && break
sleep 3
done
UPDATE=$(echo "$R" | jq -r .updateId)
IMG=$(echo "$R" | jq -r '.image // empty')
echo " generated image: $IMG"
echo " on-ledger updateId=$UPDATE"
echo "VERIFY..."
curl -s "$BASE/v1/quest/verify?updateId=$UPDATE" | jq '{valid,party,completedAt}'
echo "Done. image=$IMG updateId=$UPDATE"For agent frameworks / discovery: