Skip to Content

Agent Payment Protocol

The Agent Payment Protocol standardizes how two agents negotiate and finalize a payment. It defines message types, valid interaction paths, and clear roles for a buyer and a seller. This guide explains the models, interaction rules, and shows runnable examples for both roles.

Understanding the Payment Protocol

1. Core Models

Funds

class Funds(Model): # amount of funds amount: str # currency of the funds (USDC, FET, etc.) currency: str # payment method (skyfire, fet_direct) payment_method: str = "fet_direct"
  • amount: amount expressed as a string (supports arbitrary formats/precisions)
  • currency: the asset or token symbol
  • payment_method: payment rail/method identifier

RequestPayment

class RequestPayment(Model): # funds accepted for the requested payment accepted_funds: list[Funds] # recipient address or identifier recipient: str # deadline for the payment request in seconds deadline_seconds: int # optional reference for the payment reference: str | None = None # optional description of the payment description: str | None = None # optional metadata for the payment metadata: dict[str, str | dict[str, str]] | None = None
  • accepted_funds: one or more acceptable fund options the buyer proposes
  • recipient: where funds should be sent (address, handle, etc.)
  • deadline_seconds: validity window of the request
  • Optional: reference, description, metadata

RejectPayment

class RejectPayment(Model): # optional reason for rejecting the payment reason: str | None = None
  • Sent by the seller to decline a request

CommitPayment

class CommitPayment(Model): # funds to be paid funds: Funds # recipient address or identifier recipient: str # unique transaction token or hash transaction_id: str # optional reference for the payment reference: str | None = None # optional description of the payment description: str | None = None # optional metadata for the payment metadata: dict[str, str | dict[str, str]] | None = None
  • Seller’s commitment response, specifying the exact funds and a transaction_id to track the payment

CancelPayment

class CancelPayment(Model): # unique transaction token or hash transaction_id: str | None = None # optional reason for canceling the payment reason: str | None = None
  • Buyer cancels an in-flight or accepted payment

CompletePayment

class CompletePayment(Model): # unique transaction token or hash transaction_id: str | None = None
  • Buyer declares the payment completed

2. Protocol Specification

payment_protocol_spec = ProtocolSpecification( name="AgentPaymentProtocol", version="0.1.0", interactions={ RequestPayment: {CommitPayment, RejectPayment}, CommitPayment: {CompletePayment, CancelPayment}, CompletePayment: set(), CancelPayment: set(), RejectPayment: set(), }, roles={ "seller": {RequestPayment, CancelPayment, CompletePayment}, "buyer": {CommitPayment, RejectPayment}, }, )
  • Interactions: valid next messages per message type
  • Roles: which message types each role is allowed to send

3. Basic Payment Flow

  1. Seller sends RequestPayment to Buyer
  2. Buyer responds with either CommitPayment or RejectPayment
  3. If committed, Seller sends CompletePayment (or CancelPayment)

Using the Payment Protocol

Import the models and specification from uagents_core.contrib.protocols.payment:

from uagents_core.contrib.protocols.payment import ( Funds, RequestPayment, RejectPayment, CommitPayment, CancelPayment, CompletePayment, payment_protocol_spec, )

Note: Since the specification defines roles, instantiate the protocol with the appropriate role to avoid locked-spec errors.

Example Agents

Below are two minimal agents demonstrating the Buyer and Seller roles using the protocol.

Seller Agent

seller.py
import os from uuid import uuid4 from uagents import Agent, Protocol, Context from uagents_core.contrib.protocols.payment import ( Funds, RequestPayment, CommitPayment, RejectPayment, CompletePayment, payment_protocol_spec, ) SELLER_PORT = int(os.getenv("SELLER_PORT", "8091")) BUYER_ADDRESS = "agent1q...replace_with_buyer_address..." seller = Agent(name="seller", port=SELLER_PORT, endpoint=[f"http://localhost:{SELLER_PORT}/submit"]) payment_proto = Protocol(spec=payment_protocol_spec, role="seller") @seller.on_event("startup") async def startup(ctx: Context): ctx.logger.info(f"Seller address: {ctx.agent.address}") req = RequestPayment( accepted_funds=[Funds(currency="USDC", amount="0.001", payment_method="skyfire")], recipient=ctx.agent.address, deadline_seconds=300, reference=str(uuid4()), description="demo payment", metadata={}, ) await ctx.send(BUYER_ADDRESS, req) @payment_proto.on_message(CommitPayment) async def on_commit(ctx: Context, sender: str, msg: CommitPayment): ctx.logger.info(f"Seller received CommitPayment: {msg}") await ctx.send(sender, CompletePayment(transaction_id=msg.transaction_id)) @payment_proto.on_message(RejectPayment) async def on_reject(ctx: Context, sender: str, msg: RejectPayment): ctx.logger.info(f"Buyer rejected: {msg.reason}") seller.include(payment_proto, publish_manifest=True) if __name__ == "__main__": seller.run()

Buyer Agent

buyer.py
import os from uagents import Agent, Protocol, Context from uagents_core.contrib.protocols.payment import ( Funds, RequestPayment, CommitPayment, RejectPayment, CompletePayment, CancelPayment, payment_protocol_spec, ) BUYER_PORT = int(os.getenv("BUYER_PORT", "8092")) BUYER_MODE = os.getenv("BUYER_MODE", "commit").lower() # commit | reject buyer = Agent(name="buyer", port=BUYER_PORT, endpoint=[f"http://localhost:{BUYER_PORT}/submit"]) payment_proto = Protocol(spec=payment_protocol_spec, role="buyer") @payment_proto.on_message(RequestPayment) async def on_request(ctx: Context, sender: str, msg: RequestPayment): ctx.logger.info(f"Buyer received RequestPayment: {msg}") if not msg.accepted_funds: await ctx.send(sender, RejectPayment(reason="no accepted funds provided")) return selected = msg.accepted_funds[0] if BUYER_MODE == "reject": await ctx.send(sender, RejectPayment(reason="demo reject")) return commit = CommitPayment( funds=Funds(currency=selected.currency, amount=selected.amount, payment_method=selected.payment_method), recipient=msg.recipient, transaction_id="demo-txn-001", reference=msg.reference, description=msg.description, metadata=msg.metadata or {}, ) await ctx.send(sender, commit) @payment_proto.on_message(CompletePayment) async def on_complete(ctx: Context, sender: str, msg: CompletePayment): ctx.logger.info(f"Buyer received CompletePayment: {msg}") @payment_proto.on_message(CancelPayment) async def on_cancel(ctx: Context, sender: str, msg: CancelPayment): ctx.logger.info(f"Buyer received CancelPayment: {msg}") buyer.include(payment_proto, publish_manifest=True) if __name__ == "__main__": buyer.run()

Running the Agents

To run locally, initialize explicit ports as needed (optional):

# Seller seller = Agent(name="seller", port=8002, endpoint=["http://localhost:8002/submit"]) # Buyer buyer = Agent(name="buyer", port=8003, endpoint=["http://localhost:8003/submit"])
  1. Start the Seller first and copy its address from logs.
  2. Paste the seller address into SELLER_ADDRESS in buyer.py.
  3. Start the Buyer.

You should see the Buyer send a RequestPayment, the Seller respond with a CommitPayment, and the Buyer send a CompletePayment.

To adapt for your own project structure, import from uagents_core.contrib.protocols.payment as shown above, or vendor the models/spec locally if needed.

Expected Output

When both agents are running, you should see logs similar to the following.

Buyer logs

INFO: [demo_buyer]: Starting agent with address: agent1qdyxc5qr9lwawcdshwy9za8gen9l72llevpuv590xt2vmh07ef6jcjq7u6m INFO: [demo_buyer]: Agent inspector available at https://agentverse.ai/inspect/?uri=http%3A//127.0.0.1%3A8092&address=agent1qdyxc5qr9lwawcdshwy9za8gen9l72llevpuv590xt2vmh07ef6jcjq7u6m INFO: [demo_buyer]: Starting server on http://0.0.0.0:8092 (Press CTRL+C to quit) INFO: [uagents.registration]: Registration on Almanac API successful INFO: [uagents.registration]: Almanac contract registration is up to date! INFO: [demo_buyer]: Manifest published successfully: AgentPaymentProtocol INFO: [demo_buyer]: [buyer] got RequestPayment from agent1qv708v7970lzq6gmunvrz9xr4erfxamrc7mw9tf0ffj50nfc7tpkkaa4lrm: accepted_funds=[Funds(amount='0.001', currency='USDC', payment_method='skyfire')] recipient='agent1qv708v7970lzq6gmunvrz9xr4erfxamrc7mw9tf0ffj50nfc7tpkkaa4lrm' deadline_seconds=300 reference='f16bb6eb-3643-4990-badd-f456122a489d' description='demo-simple payment' metadata={} INFO: [demo_buyer]: [buyer] sent CommitPayment INFO: [demo_buyer]: [buyer] received CompletePayment from agent1qv708v7970lzq6gmunvrz9xr4erfxamrc7mw9tf0ffj50nfc7tpkkaa4lrm: transaction_id='demo-txn-001'

Seller logs

INFO: [demo_seller]: Starting agent with address: agent1qv708v7970lzq6gmunvrz9xr4erfxamrc7mw9tf0ffj50nfc7tpkkaa4lrm INFO: [demo_seller]: [seller] starting up… INFO: [demo_seller]: [seller] sending RequestPayment to agent1qdyxc5qr9lwawcdshwy9za8gen9l72llevpuv590xt2vmh07ef6jcjq7u6m: accepted_funds=[Funds(amount='0.001', currency='USDC', payment_method='skyfire')] recipient='agent1qv708v7970lzq6gmunvrz9xr4erfxamrc7mw9tf0ffj50nfc7tpkkaa4lrm' deadline_seconds=300 reference='f16bb6eb-3643-4990-badd-f456122a489d' description='demo-simple payment' metadata={} INFO: [demo_seller]: Agent inspector available at https://agentverse.ai/inspect/?uri=http%3A//127.0.0.1%3A8091&address=agent1qv708v7970lzq6gmunvrz9xr4erfxamrc7mw9tf0ffj50nfc7tpkkaa4lrm INFO: [demo_seller]: Starting server on http://0.0.0.0:8091 (Press CTRL+C to quit) INFO: [demo_seller]: Manifest published successfully: AgentPaymentProtocol INFO: [uagents.registration]: Registration on Almanac API successful INFO: [uagents.registration]: Almanac contract registration is up to date! INFO: [demo_seller]: [seller] got CommitPayment from agent1qdyxc5qr9lwawcdshwy9za8gen9l72llevpuv590xt2vmh07ef6jcjq7u6m: funds=Funds(amount='0.001', currency='USDC', payment_method='skyfire') recipient='agent1qv708v7970lzq6gmunvrz9xr4erfxamrc7mw9tf0ffj50nfc7tpkkaa4lrm' transaction_id='demo-txn-001' reference='f16bb6eb-3643-4990-badd-f456122a489d' description='demo-simple payment' metadata={} INFO: [demo_seller]: [seller] sent CompletePayment

Conclusion

The Payment Protocol provides a clear, role-driven workflow for negotiating and finalizing payments between agents. By defining message types, valid interactions, and explicit roles, it helps you implement predictable flows: the seller initiates with RequestPayment, the buyer either CommitPayments or RejectPayments, and the seller completes or cancels as appropriate.

  • Always instantiate your protocol with the correct role to match the allowed messages.
  • Use the examples to bootstrap local testing, then adapt handlers to perform real transfers, persistence, retries, and idempotency.
  • For complementary patterns, see the Agent Chat Protocol and the agent setup notes in Agent Creation.

With this foundation, you can extend validation, add signatures or receipts, and integrate with on-chain or off-chain payment rails as needed.

Last updated on