Deutsch한국어日本語中文EspañolFrançaisՀայերենNederlandsРусскийItalianoPortuguêsTürkçePortfolio TrackerSwapCryptocurrenciesPricingIntegrationsNewsEarnBlogNFTWidgetsDeFi Portfolio TrackerOpen API24h ReportPress KitAPI Docs

Google A2A/AP2 → Fetch.ai Chat/Payment Protocol: a hands-on bridge you can run today

1M ago
bullish:

1

bearish:

0

Share
I wanted my agent to go beyond “I found you some options” and actually check out. This is the small, practical bridge I built from A2A/AP2 (mandates) to uAgents (Chat + Payment protocol) so an agent can chat → build a cart → settle. It comes with a runnable example (a2a-cart-store) and the exact conversion code I use.

What we’re actually stitching together

  • A2A + AP2 give us interoperable agent messaging and a trust layer for payments via mandates (Intent → Cart → Payment). Mandates are designed to be verifiable/auditable, independent of any specific rails.
  • uAgents give us two working pieces:
    Chat protocol (typed chat messages + acknowledgements) and a Payment protocol with a strict buyer/seller handshake:RequestPayment → (CommitPayment | RejectPayment) → (CompletePayment | CancelPayment) .

My adapter runs as a uAgent that includes both protocols. It forwards normal chat to an A2A/AP2 executor over HTTP. When it sees AP2 payment artifacts (e.g., a CartMandate), it switches to the Payment protocol and continues the transaction with standard RequestPayment/CommitPayment messages. (That makes it legible to other uAgents and tools in the ecosystem.)

The minimal bridge (AP2 ↔ uAgents) you need

CartMandate → RequestPayment (seller asks for funds)

# ap2/bridge_mapping.py (excerpt)
from .artifacts import CartMandate
from .fetchai_payment_protocol import Funds, RequestPayment
from datetime import datetime, timezone

def expiry_to_deadline_seconds(expiry_iso: str) -> int:
trimmed = expiry_iso.rstrip("Z")
dt = datetime.fromisoformat(trimmed)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return int((dt - datetime.now(timezone.utc)).total_seconds())

def cartmandate_to_requestpayment(cart: CartMandate, recipient: str) -> RequestPayment:
total = cart.contents.payment_request.details.total
funds = Funds(
amount=str(total.amount.value),
currency=total.amount.currency,
payment_method=cart.contents.payment_request.method_data[0].supported_methods,
)
return RequestPayment(
accepted_funds=[funds],
recipient=recipient, # buyer/wallet agent address
deadline_seconds=expiry_to_deadline_seconds(cart.contents.cart_expiry),
reference=cart.contents.id,
description=(total.label if total.label != "Total"
else f"Cart from {cart.contents.merchant_name}"),
metadata={"cart_hash": cart.cart_hash or ""},
)

PaymentMandate → CommitPayment (buyer commits with proof)

from .artifacts import PaymentMandate
from .fetchai_payment_protocol import Funds, CommitPayment

def paymentmandate_to_commitpayment(pm: PaymentMandate, recipient: str) -> CommitPayment:
total = pm.payment_mandate_contents.payment_details_total
funds = Funds(
amount=str(total.amount.value),
currency=total.amount.currency,
payment_method=pm.payment_mandate_contents.payment_response.method_name,
)
token = (pm.payment_mandate_contents.payment_response.details.get("transaction_token")
or pm.payment_mandate_contents.payment_response.details.get("token")
or "unknown_token")
return CommitPayment(
funds=funds,
recipient=recipient,
transaction_id=token,
reference=pm.payment_mandate_contents.payment_details_id,
description=(total.label if total.label != "Total"
else "Commit corresponding to PaymentMandate"),
metadata={"payment_mandate_hash": pm.payment_mandate_hash or ""},
)

Why these fields: amounts/currency come straight from the mandate (no drift), reference carries the cart/payment ids so both sides can reconcile, and metadata carries tamper-evident hashes (cart_hash / payment_mandate_hash) for integrity checks. The Payment protocol stays neutral about the rail you use.

The example you can run: a2a-cart-store

I wrapped the bridge into a tiny “store” so you can see the full chat→cart→pay loop. The code lives here: a2a-cart-store in the Innovation Lab examples repo.

What it does?

  • A store A2A server (JSON-RPC) that supports list / add / cart / checkout
  • A uAgent adapter (SingleA2AAdapter) that includes Chat + Payment protocols, forwards chat to the store, and converts AP2 ↔ Payment messages on the fly
  • Optional integration with Skyfire to verify/charge a token during commit (so you can see a realistic “transaction_id” flow)
This is intentionally small: you can read the whole payment path in one sitting.

Setup & run

  1. Clone & install
git clone https://github.com/fetchai/innovation-lab-examples.git
cd innovation-lab-examples/a2a-cart-store
python3 -m venv .venv && source .venv/bin/activate
pip install -r ../requirements.txt -r requirements.txt

2. Configure env

cp .env.example .env

Key knobs (defaults shown):

# --- Single seller example ---
SELLER_A2A_PORT=10020
SELLER_UAGENT_PORT=8220

# --- Cart store example ---
STORE_A2A_PORT=10030
STORE_UAGENT_PORT=8230

# Optional LLM routing for multi-server
ASI_API_KEY=ASI_API_KEY

# Skyfire payments
JWKS_URL=https://app.skyfire.xyz/.well-known/jwks.json
JWT_ISSUER=https://app.skyfire.xyz
SELLER_ACCOUNT_ID=YOUR_SKYFIRE_ACCOUNT_ID
SELLER_SERVICE_ID=YOUR_SKYFIRE_SERVICE_ID

SKYFIRE_TOKENS_CHARGE_API_URL=https://api.skyfire.xyz/api/v1/tokens/charge
SELLER_SKYFIRE_API_KEY=YOUR_SKYFIRE_API_KEY

3. Start the adapter + store

python3 av_adapter.py

By default this spins up:

The adapter is a uAgent with Chat and Payment specs, following the documented handler pattern.

Kick the tires (chat first, then pay)

Send chat messages via your preferred uAgents client (or another agent that speaks the Chat protocol) we are using ASI:One agentic LLM:

  • list / catalog
  • add [qty] (e.g., add book 1 pen 2 watch 1)
  • cart
  • checkout → this emits an AP2 CartMandate. The adapter converts it to a uAgents RequestPayment and sends that back to your buyer.

To simulate a buyer committing, reply with a CommitPayment:

  • If you set supported_methods="skyfire", the store expects a Skyfire token as CommitPayment.transaction_id.
  • The adapter converts CommitPayment → AP2 PaymentMandate, forwards to the store, and the store verifies/charges via Skyfire; on success it responds with AP2 PaymentSuccess, which the adapter maps to CompletePayment. (This is the exact Payment protocol handshake.)

Under the hood (files worth skimming)

  • av_adapter.py – bootstraps a SingleA2AAdapter (a uAgent) and points it at the store executor’s A2A port; reads ports from .env.
  • store_executor.py – tiny “merchant” that keeps a cart, builds the AP2 CartMandate on checkout, and handles AP2 PaymentMandate on commit.
  • skyfire_payment.py – optional Skyfire integration (JWT verify via JWKS + charge API). It returns a transaction_id you’ll see reflected in PaymentSuccess.
  • requirements.txt – includes uagents, a2a-sdk, httpx, aiohttp, jose for JWTs, etc.
The store emits AP2 objects via A2A. The adapter always tries to parse data parts first (AP2), then text, then artifacts text (fallback), so you get strongly-typed flows when available.

What the code looks like at runtime

Chat → A2A → (maybe) Payment

  • The adapter registers Chat and Payment protocol handlers (same pattern as the official docs).
  • On ChatMessage, it forwards text to the A2A store and inspects the response:
    a. If it finds CartMandate, it converts to RequestPayment and sends that back.
    b. If it finds PaymentSuccess/Failure, it maps to CompletePayment/CancelPayment so the buyer sees the canonical result.
    c. Otherwise it replies with plain chat text.

Seller role (Commit → Complete/Cancel)

  • On CommitPayment, the adapter converts to AP2 PaymentMandate and posts it to the A2A store.
  • The store verifies/charges (Skyfire branch if configured) and returns AP2 PaymentSuccess (or PaymentFailure). The adapter maps those to CompletePayment (or CancelPayment) respectively.
  • This follows the Payment Protocol sequence: RequestPayment → CommitPayment → Complete/Cancel.

Debugging tips that actually helped

  • Pretty-print everything. Dump mandates/messages as JSON; check amount/currency/reference and hashes line up.
  • Carry hashes in metadata. I pass cart_hash and payment_mandate_hash in the uAgents messages for quick integrity checks until full signature verification is wired.
  • Start narrow. Chat only → emit a fake CartMandate → see the adapter produce RequestPayment → manually craft a CommitPayment → then turn on Skyfire.

Hardening for production

  • Signature verification on mandates (AP2 is designed for cryptographically signed, replay-resistant artifacts; verify before you accept or emit payment messages).
  • Back transaction_id with real rails (the protocol is rail-agnostic—FET, stablecoins, or PSPs; the important bit is that the commit proof maps to real settlement).
  • Refunds/chargebacks & audit: persist mandates + payment envelopes so you can reason about disputes later — this is where AP2 shines.

Pointers if you want to go deeper

Agents that can pay need two things:

  1. A trustable record of what the human actually approved (AP2 mandates)
  2. A clean execution handshake to move money (uAgents Payment protocol). The bridge above keeps both sides honest and interoperable.

If you want the buyer-side wallet sample (auto-approve with spend limits) or a front-end snippet that turns RequestPayment into a nice “Pay” button (with Skyfire), tell me and I’ll publish a follow-up.


Google A2A/AP2 → Fetch.ai Chat/Payment Protocol: a hands-on bridge you can run today was originally published in Fetch.ai on Medium, where people are continuing the conversation by highlighting and responding to this story.

1M ago
bullish:

1

bearish:

0

Share
Manage all your crypto, NFT and DeFi from one place

Securely connect the portfolio you’re using to start.