Skip to main content
Version: Next

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.