เรียกเอเจนต์ AI100WEB จากเอเจนต์ของคุณCall the AI100WEB agent from your own agent
ค้นพบ Agent Card · เซ็นคำขอด้วย Ed25519 · จ่ายสกิลเสียเงินด้วย x402 บน TON · รับผลลัพธ์Discover the Agent Card · sign requests with Ed25519 · pay paid skills with x402 on TON · get the result
ตรวจตัวอย่างล่าสุดExamples last verified 04 Nov 2026, 22:30 ICT · https://a2a-docs-preview.ai100web.pages.dev · 14/14 ผ่านpassed
01 ค้นพบเอเจนต์Discover the agent
เริ่มจาก Agent Card สาธารณะ (ไม่ต้องยืนยันตัวตน) — มี endpoint, สกิล, ราคา, รูปแบบการยืนยันตัวตน และที่อยู่ TONStart with the public Agent Card (no auth) — it lists the endpoint, skills, prices, the auth scheme and the TON address.
curl -s https://ai100web.pages.dev/.well-known/agent-card.json # also /agent-card.json and /.well-known/agent.json curl -s -H "accept: application/json" https://ai100web.pages.dev/a2a # gateway status: skills, auth scheme, x402 settings
Agent Card
- URL
/.well-known/agent-card.json- HTTP
- 200 ·
application/json; charset=utf-8 - A2A endpoint
https://ai100web.pages.dev/a2a- Transport
- JSONRPC · JSON-RPC 2.0
message/send - สกิลSkills
- 12
สถานะเกตเวย์Gateway status
- GET
/a2a(accept: application/json)- HTTP
- 200
- x402.simulation
true- MCP
https://ai100web.pages.dev/mcp· อ่านอย่างเดียวread-only- TON payTo
- UQBzg5yX7h5m-DLINTeWzxc5lwmPjJrgMtWisw4rRUDYdSXZ
02 สกิลและราคาSkills and prices
12 สกิล: 4 สกิลเสียเงิน 0.01 TON (= 10000000 nanoTON) ต่อการเรียกผ่าน x402 ที่เหลือฟรี ระบุสกิลด้วย data part {"skill": "<id>", ...}12 skills: 4 cost 0.01 TON (= 10000000 nanoTON) per call via x402, the rest are free. Select a skill with a data part {"skill": "<id>", ...}.
| สกิลSkill | ราคาPrice | คำอธิบาย (จาก Agent Card)Description (from the Agent Card) |
|---|---|---|
check-balance | ฟรีfree | Get balance of the owner wallet, its Agentic Wallets, or any address |
resolve-domain | ฟรีfree | Resolve .ton domain to wallet or records |
transfer-ton | ฟรีfree | Prepare an UNSIGNED TON transfer (simulation / ready-to-sign preview). Never signs or broadcasts; execute always becomes pending_approval. |
transfer-simulate | ฟรีfree | Dry-run a TON transfer: address checksum, amount and comment validation plus a fee estimate. simulated:true, never signed or broadcast. JSON-RPC: thin alias of transfer-ton with mode forced to "simulation". Free. |
ton-balance | ฟรีfree | TON balance of an address. Thin alias of check-balance (same handler, same params: {address?}). Free, answered at the gateway (simulated data). |
ton-dns-resolve | ฟรีfree | Resolve a .ton domain to its wallet. Thin alias of resolve-domain (same handler, same params: {domain}). Free, answered at the gateway (simulated data). |
ai-analyze | 0.01 TON | Analyse a wallet, token, or dataset and return a structured report |
ai-summarize | 0.01 TON | Condense text or documents into a faithful summary |
ai-content | 0.01 TON | Produce polished, ready-to-use content from a brief |
ai-orchestrate | 0.01 TON | Plan a goal into subtasks, run an agent team, and synthesize a complete result |
ai-orchestrate-plan | ฟรีfree | Free PLAN step of ai-orchestrate: returns which sub-agents (ai-orchestrate.sub_agents) and which skills would be used for a goal, with ready JSON-RPC calls per step. Executes, forwards, signs and charges nothing. Params: {goal (1..500 chars), address?, to?, amount?}. |
mcp-tool-call | ฟรีfree | Run an allow-listed read-only MCP tool (ton_get_balance, ton_dns_resolve) on the AI100WEB MCP server and get its result back as A2A task artifacts. Signed envelope required. Nothing is signed or sent. |
สกิลเสียเงินทำงานจริงหลังจ่ายPaid skills run after payment
หลังหลักฐานการจ่ายผ่านการตรวจ เกตเวย์จะรันสกิลที่ edge (engine ai100web-edge-v1) และส่งผลจริงกลับใน artifact ไม่มีการเซ็นหรือส่งธุรกรรม ยังไม่ได้ตั้งค่า LLM บน staging ผลที่เป็นงานภาษาจึงระบุ mode: "degraded" พร้อม needs ว่าต้องใช้อะไรเพิ่มOnce the payment proof verifies, the gateway runs the skill at the edge (engine ai100web-edge-v1) and returns the real result in the artifact. Nothing is signed or broadcast. No LLM is configured on staging, so language tasks report mode: "degraded" and list what is missing in needs.
| สกิลSkill | อินพุต (ใน data part)Input (data part) | ผลลัพธ์Result | mode |
|---|---|---|---|
ai-analyze | address (TON) · values (number[]) · text | มีที่อยู่ → รายงานกระเป๋าแบบสด (อ่านอย่างเดียว ผ่าน tonapi.io); มีตัวเลข → สถิติ; อย่างอื่น → โปรไฟล์ข้อความaddress → live read-only wallet report (tonapi.io); numbers → statistics; otherwise → text profile | full / partial / degraded |
ai-summarize | text · maxSentences (1–10) | สรุปแบบดึงประโยค (extractive)extractive summary | degraded |
ai-content | text or brief · format | ร่างจากเทมเพลต: หัวข้อ โครงร่าง ร่าง ข้อความสั้น ≤280 ตัวอักษร แฮชแท็กtemplate draft: title, outline, draft, ≤280-char short form, hashtags | degraded |
ai-orchestrate | text or goal | วางแผนตามกฎ แล้วรันขั้นย่อย (analyze, DNS, summarize, content) ในคำขอเดียวภายใต้การจ่ายครั้งเดียว ไม่โอนเงินเด็ดขาดrule-based plan, then runs the sub-steps (analyze, DNS, summarize, content) in-process under the one payment; never transfers | full / partial / degraded |
03 การเซ็นคำขอด้วย Ed25519Signing requests with Ed25519
ทุก POST ไปที่ /a2a และ /mcp ต้องมี 3 เฮดเดอร์นี้ ไม่มีลายเซ็น = 401 ก่อนถึงการคิดเงินหรือสกิลใด ๆEvery POST to /a2a and /mcp needs these three headers. No signature = 401 before any pricing or skill runs.
X-A2A-Key-Id
key id ที่ลงทะเบียนไว้ (ชื่อไคลเอนต์)your registered key id (client name)
X-A2A-Timestamp
เวลา unix เป็นวินาที ต่างจากเวลาเซิร์ฟเวอร์ได้ไม่เกิน ±300 วินาทีunix seconds, within ±300s of server time
X-A2A-Signature
base64 ของลายเซ็น Ed25519 (64 ไบต์)base64 Ed25519 signature (64 bytes)
- ข้อความที่เซ็นSigned string
${ts}.${rawBody}— UTF-8, ไบต์ของ body ตรงตามที่ส่งจริงthe exact body bytes you send- ช่วงเวลาWindow
- ±300s → นอกช่วงoutside:
401 stale-timestamp - กันส่งซ้ำReplay
- คำขอที่มีลายเซ็นเดิมถูกปฏิเสธ (
401 replayed) ให้ใส่id/messageIdที่ไม่ซ้ำ และเซ็นใหม่ทุกครั้ง แม้แต่ตอนส่งซ้ำพร้อมหลักฐานการจ่ายan identical signed request is refused (401 replayed). Use a uniqueid/messageIdand sign again for every request, including the retry with the payment proof. - อัตราRate
- 5 POST/นาที ต่อ IP และ 5/นาที ต่อ key id →
429+Retry-After5 POST/min per IP and 5/min per key id →429+Retry-After
04 ลงทะเบียนกุญแจของไคลเอนต์Registering your client key
สร้างกุญแจบนเครื่องของคุณ แล้วส่งเฉพาะ public key กับ key id ที่ต้องการให้ผู้ดูแล บน staging ไม่มีการลงทะเบียนเอง: key id ถูกใส่ใน allow-list ที่ /config/client-keys.json (public key แบบ raw 32 ไบต์ base64) และมีผลหลัง deploy ครั้งถัดไป อย่าส่ง private key หรือ seed ให้ใครGenerate a key on your machine and send only the public key plus the key id you want to the operator. Staging has no self-service registration: key ids are allow-listed in /config/client-keys.json (raw 32-byte public keys, base64) and take effect with the next deploy. Never send anyone your private key or seed.
# Generate an Ed25519 key LOCALLY (Node 18+). Only the public key leaves your machine.
node -e "
const {generateKeyPairSync}=require('node:crypto'),fs=require('node:fs');
const {publicKey,privateKey}=generateKeyPairSync('ed25519');
fs.writeFileSync('a2a-key.json',JSON.stringify({seed:privateKey.export({format:'der',type:'pkcs8'}).subarray(16).toString('base64')}),{mode:0o600});
console.log('public key (raw 32 bytes, base64):',publicKey.export({format:'der',type:'spki'}).subarray(12).toString('base64'));"ขั้นตอนแนะนำแบบทีละขั้นอยู่ที่A guided walkthrough is at /start.
05 การจ่ายแบบ x402The x402 payment flow
- เรียกสกิลเสียเงินด้วยคำขอที่เซ็นแล้ว โดยไม่แนบการจ่ายCall the paid skill with a signed request and no payment. → HTTP 402, task อยู่ในสถานะtask state
input-required, เฮดเดอร์headersPAYMENT-REQUIRED+X-PAYMENT-REQUIRED(ค่าเดียวกันsame value) = base64(JSON). - เลือก requirement ใน
accepts[](TON หรือ USD₮) อ่านextra.nonceและextra.expiryPick a requirement fromaccepts[](TON or USD₮); readextra.nonceandextra.expiry. - จ่าย
maxAmountRequiredไปที่payToพร้อมคอมเมนต์x402:<nonce>— บน staging ข้ามขั้นนี้: หลักฐานเป็นแบบจำลองpayload.simulated: truePaymaxAmountRequiredtopayTowith commentx402:<nonce>— on staging skip this: the proof is simulated,payload.simulated: true. - ส่งคำขอเดิมอีกครั้ง (เซ็นใหม่) พร้อม
X-PAYMENTหรือPAYMENT-SIGNATURESend the same request again (newly signed) withX-PAYMENTorPAYMENT-SIGNATURE. → HTTP 200completed, ใบเสร็จในreceipt inPAYMENT-RESPONSE/X-PAYMENT-RESPONSE, ผลของสกิลในthe skill result inresult.artifacts[0].
ใบเสนอราคา (PAYMENT-REQUIRED ถอดรหัสแล้ว)The quote (PAYMENT-REQUIRED, decoded)
{
"x402Version": 1,
"error": "X-PAYMENT (or PAYMENT-SIGNATURE) header is required",
"accepts": [
{
"scheme": "exact",
"network": "ton-mainnet",
"payTo": "UQBzg5yX7h5m-DLINTeWzxc5lwmPjJrgMtWisw4rRUDYdSXZ",
"resource": "https://a2a-docs-preview.ai100web.pages.dev/a2a#ai-summarize",
"description": "One call to paid skill ai-summarize",
"mimeType": "application/json",
"maxTimeoutSeconds": 300,
"asset": "TON",
"maxAmountRequired": "10000000",
"extra": {
"nonce": "<uuid>",
"comment": "x402:<uuid>",
"expiry": "<ISO time, +300 s>",
"simulationMode": true,
"binding": {
"payer": null,
"requesterBound": true,
"note": "no payer declared: real (non-simulated) proofs will be rejected; re-quote with message.metadata[\"x402.payer\"]"
},
"decimals": 9,
"amount": "0.01"
}
},
{
"asset": "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs",
"network": "ton-mainnet",
"payTo": "UQBzg5yX7h5m-DLINTeWzxc5lwmPjJrgMtWisw4rRUDYdSXZ",
"maxAmountRequired": "10000",
"…": "same nonce/expiry"
}
]
}แนบหลักฐานAttaching the proof
| เฮดเดอร์Header | ค่า base64(JSON)Value: base64(JSON) | ตรวจแล้วVerified |
|---|---|---|
X-PAYMENT (x402 v1) | {"x402Version":1,"scheme":"exact","network":"ton-mainnet","payload":{"simulated":true,"nonce":"<nonce>"}} | HTTP 200 · executed |
PAYMENT-SIGNATURE (x402 v2) | {"x402Version":2,"accepted":<the TON accepts[] entry>,"payload":{"simulated":true,"nonce":"<nonce>"}} | HTTP 200 · executed |
ส่งเพียงเฮดเดอร์เดียว ถ้าส่งทั้งสองและไม่ตรงกันจะได้ invalid_payment_header การจ่ายจริงบน mainnet ใช้ payload.txHash + payload.payer แทน simulated และตรวจแบบอ่านอย่างเดียวผ่าน tonapi.io — บน staging เปิดเฉพาะโหมดจำลองSend one header. Sending both with different values gives invalid_payment_header. A real mainnet payment uses payload.txHash + payload.payer instead of simulated and is verified read-only via tonapi.io — staging runs in simulation mode only.
หมดอายุExpiry
ใบเสนอราคาใช้ได้ 300 วินาที (extra.expiry) หลังจากนั้น → 402 expired_nonce ขอใบใหม่A quote is valid for 300s (extra.expiry). After that → 402 expired_nonce; request a new quote.
ใช้ครั้งเดียวSingle use
จ่ายหนึ่งครั้งเรียกได้หนึ่งครั้ง ส่งหลักฐานเดิมซ้ำ → 402 replayed_nonce และสกิลไม่ทำงาน nonce ผูกกับ key id ที่ขอใบเสนอราคา และกับ payer ถ้าประกาศไว้ใน message.metadata["x402.payer"]One payment buys one call. The same proof again → 402 replayed_nonce and the skill does not run. The nonce is bound to the key id that asked for the quote, and to the payer if you declared one in message.metadata["x402.payer"].
ใบเสร็จReceipt
PAYMENT-RESPONSE = base64(JSON) มี receiptId, nonce, skillId, amount, payTo, simulated, txHash (null ในโหมดจำลอง) เมตาดาตาของ task มี x402.payment.statusPAYMENT-RESPONSE = base64(JSON) with receiptId, nonce, skillId, amount, payTo, simulated and txHash (null in simulation). Task metadata carries x402.payment.status.
ใบเสร็จและผลลัพธ์ที่ได้จริง (ตัวอย่างที่ตรวจแล้ว)Receipt and real result (verified run)
{
"receiptId": "<uuid>",
"nonce": "<the quote nonce>",
"skillId": "ai-summarize",
"resource": "https://a2a-docs-preview.ai100web.pages.dev/a2a#ai-summarize",
"payTo": "UQBzg5yX7h5m-DLINTeWzxc5lwmPjJrgMtWisw4rRUDYdSXZ",
"payer": null,
"asset": "TON",
"amount": "10000000",
"txHash": null,
"network": "ton-mainnet",
"simulated": true,
"verifiedAt": "<ISO time>",
"proof": {
"alg": "SHA-256",
"signed": false,
"digest": "<sha-256 hex>",
"note": "unsigned digest; set RECEIPT_SECRET for HMAC-SHA256"
}
}{
"executed": true,
"paid": true,
"skill": "ai-summarize",
"engine": "ai100web-edge-v1",
"mode": "degraded",
"readOnly": true,
"signed": false,
"broadcast": false,
"result": {
"kind": "extractive",
"summary": "Agents discover the card, sign each request with Ed25519, get an x402 quote, attach the proof and receive the result. On staging the payment is simulated. Nothing is signed on-chain.",
"sentencesIn": 3,
"sentencesOut": 3,
"selected": [
0,
1,
2
],
"charactersIn": 182,
"charactersOut": 182,
"compression": 1,
"keywords": [
{
"term": "agents",
"count": 1
},
{
"term": "attach",
"count": 1
},
{
"term": "card",
"count": 1
},
{
"term": "discover",
"count": 1
},
{
"term": "ed25519",
"count": 1
},
{
"term": "get",
"count": 1
},
{
"term": "nothing",
"count": 1
},
{
"term": "on-chain",
"count": 1
}
]
},
"limitations": [
"Extractive: sentences are selected verbatim from the input by term-frequency scoring; nothing is paraphrased."
],
"needs": [
"abstractive / LLM-quality output needs an LLM provider API key (Pages secret) plus a provider integration; neither is configured"
],
"receipt": "…same as PAYMENT-RESPONSE…",
"quote": "…the quote terms…",
"verification": {
"checks": [
{
"check": "SIMULATED proof accepted (X402_SIMULATION=true); no on-chain verification performed",
"ok": true
}
],
"source": null
}
}06 ตัวอย่างExamples
ใช้ค่าตัวแทนเท่านั้น: <your-key-id> และไฟล์ a2a-key.json ของคุณเอง ห้ามใส่ private key ในหน้าเว็บหรือโค้ดฝั่งเบราว์เซอร์Placeholders only: <your-key-id> and your own a2a-key.json. Never put a private key in a web page or browser code.
ตัวช่วยเซ็น (สำหรับ curl)Signing helper (for curl)
# sign.mjs — print the X-A2A-Signature for "$TS.$BODY" (your seed stays on your machine)
# node sign.mjs "$TS" "$BODY"
import { createPrivateKey, sign } from 'node:crypto'; import { readFileSync } from 'node:fs';
const seed = Buffer.from(JSON.parse(readFileSync(process.env.KEY_FILE || 'a2a-key.json', 'utf8')).seed, 'base64');
const key = createPrivateKey({ key: Buffer.concat([Buffer.from('302e020100300506032b657004220420', 'hex'), seed]), format: 'der', type: 'pkcs8' });
process.stdout.write(sign(null, Buffer.from(process.argv[2] + '.' + process.argv[3]), key).toString('base64'));curl: สกิลฟรีcurl: free skill
# Free skill. Placeholders only: <your-key-id> is the id the operator registered for your public key.
BODY='{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{"role":"user","kind":"message","messageId":"m-'"$(date +%s)"'","parts":[{"kind":"data","data":{"skill":"resolve-domain","params":{"domain":"a2aprotocol.ton"}}}]}}}'
TS=$(date +%s) # unix seconds; must be within ±300s of server time
SIG=$(node sign.mjs "$TS" "$BODY") # base64 Ed25519 over "$TS.$BODY" (exact bytes sent)
curl -s https://ai100web.pages.dev/a2a -H "content-type: application/json" \
-H "X-A2A-Key-Id: <your-key-id>" -H "X-A2A-Timestamp: $TS" -H "X-A2A-Signature: $SIG" \
--data-binary "$BODY"
# → HTTP 200, result.status.state = "completed"curl: สกิลเสียเงิน (402 → จ่าย → ผลลัพธ์)curl: paid skill (402 → pay → result)
# Paid skill, step 1: same signed call, no payment → HTTP 402 + quote
BODY='{"jsonrpc":"2.0","id":"2","method":"message/send","params":{"message":{"role":"user","kind":"message","messageId":"m-'"$(date +%s)"'","parts":[{"kind":"data","data":{"skill":"ai-summarize","text":"Text to summarise ..."}}]}}}'
TS=$(date +%s); SIG=$(node sign.mjs "$TS" "$BODY")
curl -s -D headers.txt https://ai100web.pages.dev/a2a -H "content-type: application/json" \
-H "X-A2A-Key-Id: <your-key-id>" -H "X-A2A-Timestamp: $TS" -H "X-A2A-Signature: $SIG" --data-binary "$BODY"
NONCE=$(grep -i '^payment-required:' headers.txt | cut -d' ' -f2 | tr -d '\r' | base64 -d \
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).accepts.find(a=>a.asset==="TON").extra.nonce))')
# Step 2: attach the proof. On staging the proof is SIMULATED (no transaction, no funds).
X_PAYMENT=$(printf '{"x402Version":1,"scheme":"exact","network":"ton-mainnet","payload":{"simulated":true,"nonce":"%s"}}' "$NONCE" | base64 | tr -d '\n')
TS=$(date +%s); SIG=$(node sign.mjs "$TS" "$BODY") # fresh timestamp + signature (an identical signature is a replay)
curl -s -D receipt.txt https://ai100web.pages.dev/a2a -H "content-type: application/json" -H "X-PAYMENT: $X_PAYMENT" \
-H "X-A2A-Key-Id: <your-key-id>" -H "X-A2A-Timestamp: $TS" -H "X-A2A-Signature: $SIG" --data-binary "$BODY"
# → HTTP 200, task "completed", header PAYMENT-RESPONSE = base64 receipt,
# result.artifacts[0].parts[0].data = { executed: true, paid: true, mode, result, ... }Node: ไคลเอนต์ครบวงจรNode: complete client
สคริปต์นี้ถูกรันทั้งไฟล์กับเกตเวย์ก่อนเผยแพร่ (ฟรี → 402 → จ่าย → ผลลัพธ์ → replay → MCP)This exact file was run against the gateway before publishing (free → 402 → pay → result → replay → MCP).
// a2a-client.mjs — minimal AI100WEB A2A client (Node 18+, no dependencies).
// KEY_ID=<your-registered-key-id> KEY_FILE=./a2a-key.json node a2a-client.mjs
// The key file holds YOUR Ed25519 seed ({"seed":"<base64 32 bytes>"}); it never leaves your machine.
import { createPrivateKey, sign, randomUUID } from 'node:crypto';
import { readFileSync } from 'node:fs';
const BASE = process.env.A2A_BASE || 'https://ai100web.pages.dev';
const KEY_ID = process.env.KEY_ID;
const seed = Buffer.from(JSON.parse(readFileSync(process.env.KEY_FILE || 'a2a-key.json', 'utf8')).seed, 'base64');
const key = createPrivateKey({ key: Buffer.concat([Buffer.from('302e020100300506032b657004220420', 'hex'), seed]), format: 'der', type: 'pkcs8' });
const b64 = (o) => Buffer.from(JSON.stringify(o)).toString('base64');
const unb64 = (s) => (s ? JSON.parse(Buffer.from(s, 'base64').toString('utf8')) : null);
// Sign the exact bytes you send: Ed25519 over `${ts}.${rawBody}`, ts = unix seconds (±300 s).
// Limit: 5 POST/min per IP and per key id. On 429 wait Retry-After, then sign again (a re-sent signature is a replay).
async function post(path, body, extra = {}) {
const raw = JSON.stringify(body);
for (let attempt = 0; ; attempt++) {
const ts = String(Math.floor(Date.now() / 1000));
const sig = sign(null, Buffer.from(`${ts}.${raw}`), key).toString('base64');
const r = await fetch(BASE + path, { method: 'POST', body: raw, headers: { 'content-type': 'application/json',
'X-A2A-Key-Id': KEY_ID, 'X-A2A-Timestamp': ts, 'X-A2A-Signature': sig, ...extra } });
if (r.status === 429 && attempt < 2) { await new Promise((ok) => setTimeout(ok, 1000 * (Number(r.headers.get('retry-after')) || 60))); continue; }
return { status: r.status, headers: r.headers, json: await r.json().catch(() => null) };
}
}
const message = (data) => ({ jsonrpc: '2.0', id: randomUUID(), method: 'message/send',
params: { message: { role: 'user', kind: 'message', messageId: randomUUID(), parts: [{ kind: 'data', data }] } } });
// 1) Free skill → HTTP 200, task "completed".
const free = await post('/a2a', message({ skill: 'resolve-domain', params: { domain: 'a2aprotocol.ton' } }));
console.log('free:', free.status, free.json?.result?.status?.state);
// 2) Paid skill → HTTP 402 quote → attach the (simulated) proof → HTTP 200 with the real result.
const task = { skill: 'ai-summarize', maxSentences: 2,
text: 'Agents discover the card. Every request is signed with Ed25519. Paid skills answer 402 with an x402 quote. The proof is attached on the retry. The skill runs only after the proof verifies.' };
const quote = await post('/a2a', message(task));
if (quote.status !== 402) throw new Error(`expected HTTP 402 quote, got ${quote.status}: ${JSON.stringify(quote.json)}`);
const ton = unb64(quote.headers.get('payment-required')).accepts.find((a) => a.asset === 'TON');
console.log('quote:', quote.status, ton.maxAmountRequired, 'nano to', ton.payTo, 'expires', ton.extra.expiry);
const proof = b64({ x402Version: 1, scheme: 'exact', network: 'ton-mainnet', payload: { simulated: true, nonce: ton.extra.nonce } });
const paid = await post('/a2a', message(task), { 'X-PAYMENT': proof });
const receipt = unb64(paid.headers.get('payment-response'));
const out = paid.json?.result?.artifacts?.[0]?.parts?.find((p) => p.kind === 'data')?.data;
console.log('paid:', paid.status, paid.json?.result?.status?.state, 'receipt', receipt?.receiptId, 'simulated', receipt?.simulated);
console.log('result:', out?.executed, out?.mode, out?.result?.summary);
// 3) The same proof again is refused (single-use nonce) → HTTP 402 replayed_nonce, nothing runs.
const again = await post('/a2a', message(task), { 'X-PAYMENT': proof });
console.log('replay:', again.status, again.json?.result?.metadata?.['x402.payment.error']);
// 4) Read-only MCP bridge (same signature scheme).
const tools = await post('/mcp', { jsonrpc: '2.0', id: 1, method: 'tools/list' });
console.log('mcp tools:', tools.status, tools.json?.result?.tools?.map((t) => t.name).join(', '));
07 MCP bridge (อ่านอย่างเดียว)MCP bridge (read-only)
POST /mcp คือเซิร์ฟเวอร์ MCP แบบ JSON-RPC 2.0 (ตอบเป็น JSON ไม่มี SSE) ใช้ลายเซ็นแบบเดียวกับ /a2a รองรับ initialize, ping, tools/list, tools/call มีเฉพาะเครื่องมืออ่านอย่างเดียว: ton_get_balance, ton_dns_resolve ไม่มีเครื่องมือเซ็นหรือโอนเงินPOST /mcp is a JSON-RPC 2.0 MCP server (JSON responses, no SSE) behind the same signature as /a2a: initialize, ping, tools/list, tools/call. Read-only tools only: ton_get_balance, ton_dns_resolve. No signing or transfer tools exist.
BODY='{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
TS=$(date +%s); SIG=$(node sign.mjs "$TS" "$BODY")
curl -s https://ai100web.pages.dev/mcp -H "content-type: application/json" -H "accept: application/json" \
-H "X-A2A-Key-Id: <your-key-id>" -H "X-A2A-Timestamp: $TS" -H "X-A2A-Signature: $SIG" --data-binary "$BODY"
# tools/call: {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"ton_dns_resolve","arguments":{"domain":"a2aprotocol.ton"}}}- tools/list
- HTTP 200 ·
ton_get_balanceton_dns_resolve - tools/call
- HTTP 200
- ไม่เซ็นUnsigned
- HTTP 401
- Versions
- 2025-06-18, 2025-03-26, 2024-11-05
- Via A2A
- สกิล
mcp-tool-callที่/a2a:{"skill":"mcp-tool-call","params":{"tool":"ton_dns_resolve","arguments":{...}}}skillmcp-tool-callon/a2a:{"skill":"mcp-tool-call","params":{"tool":"ton_dns_resolve","arguments":{...}}}
08 รหัสข้อผิดพลาดError codes
401/403/429 ตอบเป็น JSON {"error", "reason"?, "detail"?} ส่วน 402 และผลของ x402 อยู่ใน task ของ JSON-RPC (result.metadata["x402.payment.error"]) ข้อผิดพลาดไม่เปิดเผยข้อความภายในหรือ URL ต้นทาง401/403/429 return JSON {"error", "reason"?, "detail"?}. 402 and x402 outcomes are inside the JSON-RPC task (result.metadata["x402.payment.error"]). Errors never expose internal messages or upstream URLs.
| HTTP | รหัสCode | เมื่อไรWhen | ที่มาSource |
|---|---|---|---|
| 401 | unauthorized / missing-signature-headers | ไม่มีเฮดเดอร์ลายเซ็นsignature headers missing | ตรวจแล้วverified E3 |
| 401 | unauthorized / stale-timestamp | เวลาเกิน ±300 วินาทีtimestamp outside ±300s | ตรวจแล้วverified E4 |
| 401 | unauthorized / invalid-signature | ลายเซ็นไม่ตรงกับกุญแจของ key id นั้น (รวมถึง key id ที่ไม่ได้ลงทะเบียน)signature does not verify for that key id (also unregistered key ids) | ตรวจแล้วverified E5 |
| 401 | unauthorized / replayed | ส่งคำขอที่มีลายเซ็นเดิมซ้ำthe exact same signed request sent again | จากโค้ดfrom code |
| 403 | skill_not_declared | สกิลไม่อยู่ใน Agent Cardskill not in the Agent Card | ตรวจแล้วverified E7 |
| 403 | skill_not_allowed_for_agent | key id ของ sub-agent เรียกสกิลนอกขอบเขตa sub-agent key id called a skill outside its scope | จากโค้ดfrom code |
| 429 | rate_limited | เกิน 5 คำขอ/นาที ต่อ IP หรือต่อ key id — ดู Retry-Afterover 5 requests/min per IP or per key id — see Retry-After | จากโค้ดfrom code |
| 402 | input-required (x402 quote) | สกิลเสียเงินโดยยังไม่แนบหลักฐานpaid skill without a payment proof | ตรวจแล้วverified E8 |
| 402 | replayed_nonce | ใช้ nonce ที่จ่ายไปแล้วซ้ำ — สกิลไม่ทำงานnonce already used — the skill does not run | ตรวจแล้วverified E10 |
| 402 | expired_nonce | ใบเสนอราคาเกิน 300 วินาทีquote older than 300s | ตรวจแล้วverified paid-tests N6 |
| 402 | unknown_nonce | nonce ที่ไม่เคยออกnonce never issued | ตรวจแล้วverified paid-tests N5 |
| 402 | invalid_payment_header | เฮดเดอร์หลักฐานถอดรหัสไม่ได้ หรือ X-PAYMENT กับ PAYMENT-SIGNATURE ขัดกันproof header not decodable, or X-PAYMENT and PAYMENT-SIGNATURE conflict | ตรวจแล้วverified paid-tests N4 |
| 402 | requester_mismatch · payer_mismatch · nonce_skill_mismatch | nonce ผูกกับ key id ผู้ขอ ผู้จ่ายที่ประกาศ และสกิลthe nonce is bound to the requesting key id, the declared payer and the skill | จากโค้ดfrom code |
| 200 | state "failed" | จ่ายแล้วแต่สกิลรันไม่ได้ (เช่น invalid_input, upstream_unavailable) — ได้ใบเสร็จpayment verified but the skill could not run (e.g. invalid_input, upstream_unavailable) — receipt still returned | จากโค้ดfrom code |
| 401 | /mcp unauthorized | เรียก /mcp โดยไม่เซ็นunsigned /mcp call | ตรวจแล้วverified E14 |
JSON-RPC /a2a: -32700 parse error · -32600 invalid request · -32601 method not found · -32602 invalid params · -32001 task not found. /mcp ตอบข้อผิดพลาดเป็น JSON-RPC พร้อม HTTP statusreturns JSON-RPC errors with the HTTP status: -32001 Unauthorized (401, error.data.reason) · -32005 Rate limited (429).