PageSourceSearch

https://innovationlab.fetch.ai/resources/assets/js/7cdf60ff.de99284c.js

js fetch.ai collected 2026-10-01 11:54:50 UTC 32,160 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkinnovation_labs=self.webpackChunkinnovation_labs||[]).push([[841],{77980:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"agent-transaction/agent-payment-protocol","title":"Agent Payment Protocol","description":"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 working examples for both roles.","source":"@site/versioned_docs/version-1.0.5/agent-transaction/payment-protocol.mdx","sourceDirName":"agent-transaction","slug":"/agent-transaction/agent-payment-protocol","permalink":"/resources/docs/agent-transaction/agent-payment-protocol","draft":false,"unlisted":false,"tags":[],"version":"1.0.5","frontMatter":{"id":"agent-payment-protocol","title":"Agent Payment Protocol"},"sidebar":"tutorialSidebar","previous":{"title":"Agent Chat Protocol","permalink":"/resources/docs/agent-communication/agent-chat-protocol"},"next":{"title":"Agentverse","permalink":"/resources/docs/agentverse/"}}');var r=t(74848),o=t(28453);const i={id:"agent-payment-protocol",title:"Agent Payment Protocol"},l="Agent Payment Protocol",a={},c=[{value:"Understanding the Payment Protocol",id:"understanding-the-payment-protocol",level:2},{value:"1. Core Models",id:"1-core-models",level:3},{value:"Funds",id:"funds",level:4},{value:"Payment methods",id:"payment-methods",level:3},{value:"RequestPayment",id:"requestpayment",level:4},{value:"RejectPayment",id:"rejectpayment",level:4},{value:"CommitPayment",id:"commitpayment",level:4},{value:"CancelPayment",id:"cancelpayment",level:4},{value:"CompletePayment",id:"completepayment",level:4},{value:"Glossary (who invoices vs who pays)",id:"glossary-who-invoices-vs-who-pays",level:3},{value:"2. Protocol Specification",id:"2-protocol-specification",level:3},{value:"3. Basic Payment Flow",id:"3-basic-payment-flow",level:3},{value:"Using the Payment Protocol",id:"using-the-payment-protocol",level:2},{value:"Prerequisites",id:"prerequisites",level:3},{value:"Example Agents",id:"example-agents",level:2},{value:"Seller Agent",id:"seller-agent",level:3},{value:"Buyer Agent",id:"buyer-agent",level:3},{value:"Running the Agents",id:"running-the-agents",level:3},{value:"Hosted (Agentverse)",id:"hosted-agentverse",level:4},{value:"Local",id:"local",level:4},{value:"Expected Output",id:"expected-output",level:3},{value:"Troubleshooting",id:"troubleshooting",level:3},{value:"Conclusion",id:"conclusion",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",input:"input",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"agent-payment-protocol",children:"Agent Payment Protocol"})}),"\n",(0,r.jsx)(n.p,{children:"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 working examples for both roles."}),"\n",(0,r.jsx)(n.h2,{id:"understanding-the-payment-protocol",children:"Understanding the Payment Protocol"}),"\n",(0,r.jsx)(n.h3,{id:"1-core-models",children:"1. Core Models"}),"\n",(0,r.jsx)(n.h4,{id:"funds",children:"Funds"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'class Funds(Model):\n    # amount of funds\n    amount: str\n\n    # currency of the funds (USDC, FET, etc.)\n    currency: str\n\n    # payment method (skyfire, fet_direct, stripe, \u2026)\n    payment_method: str = "fet_direct"\n'})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"amount"}),": amount expressed as a string (supports arbitrary formats/precisions)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"currency"}),": the asset or token symbol"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"payment_method"}),": payment rail/method identifier (conventional string; not an enum)"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"payment-methods",children:"Payment methods"}),"\n",(0,r.jsxs)(n.p,{children:["The payment protocol is payment-rail agnostic. Use ",(0,r.jsx)(n.code,{children:"Funds.payment_method"})," to describe ",(0,r.jsx)(n.strong,{children:"how"})," the buyer will pay."]}),"\n",(0,r.jsxs)(n.p,{children:["Common values (conventional strings \u2014 library comments may list a subset such as ",(0,r.jsx)(n.code,{children:"skyfire"})," / ",(0,r.jsx)(n.code,{children:"fet_direct"}),"):"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"stripe"})}),": card payment via Stripe Checkout (recommended for fiat). In practice, ",(0,r.jsx)(n.code,{children:"CommitPayment.transaction_id"})," is usually set to the ",(0,r.jsx)(n.strong,{children:"Stripe Checkout Session ID"})," (e.g. ",(0,r.jsx)(n.code,{children:"cs_test_..."}),") so the seller can verify payment status via Stripe."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"skyfire"})}),": USDC payment via Skyfire."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"fet_direct"})}),": direct on-chain FET transfer."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["You can also include rail-specific UI/verification hints via ",(0,r.jsx)(n.code,{children:"RequestPayment.metadata"}),"."]}
1),"\n",(0,r.jsxs)(n.p,{children:["For example, to support ",(0,r.jsx)(n.strong,{children:"Stripe embedded Checkout"})," experiences, you can include a ",(0,r.jsx)(n.code,{children:"stripe"})," object in ",(0,r.jsx)(n.code,{children:"metadata"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n  "stripe": {\n    "ui_mode": "embedded",\n    "publishable_key": "pk_test_...",\n    "client_secret": "cs_test_...",\n    "checkout_session_id": "cs_test_...",\n    "currency": "usd",\n    "amount_cents": 100\n  }\n}\n'})}),"\n",(0,r.jsx)(n.admonition,{title:"Protocol-only demo vs real rails",type:"note",children:(0,r.jsxs)(n.p,{children:["The seller/buyer scripts below are a ",(0,r.jsx)(n.strong,{children:"protocol-only demo"})," (",(0,r.jsx)(n.code,{children:'payment_method="skyfire"'})," + a fake ",(0,r.jsx)(n.code,{children:"transaction_id"}),"). They intentionally omit rail metadata. For a real Stripe path, see ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"../examples/agent-transaction/stripe-horoscope-payment-protocol",children:"Stripe Horoscope Agent (Payment Protocol)"})}),"."]})}),"\n",(0,r.jsx)(n.h4,{id:"requestpayment",children:"RequestPayment"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"class RequestPayment(Model):\n    # list of Funds options the counterparty may pay with\n    accepted_funds: list[Funds]\n\n    # recipient address or identifier\n    recipient: str\n\n    # deadline for the payment request in seconds\n    deadline_seconds: int\n\n    # optional reference for the payment\n    reference: str | None = None\n\n    # optional description of the payment\n    description: str | None = None\n\n    # optional metadata for the payment\n    metadata: dict[str, str | dict[str, str]] | None = None\n"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"accepted_funds"}),": list of ",(0,r.jsx)(n.code,{children:"Funds"})," options the requester accepts for payment (the invoice\u2019s accepted rails)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"recipient"}),": where funds should be sent (address, handle, etc.)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"deadline_seconds"}),": validity window of the request"]}),"\n",(0,r.jsxs)(n.li,{children:["Optional: ",(0,r.jsx)(n.strong,{children:"reference"}),", ",(0,r.jsx)(n.strong,{children:"description"}),", ",(0,r.jsx)(n.strong,{children:"metadata"})]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"rejectpayment",children:"RejectPayment"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"class RejectPayment(Model):\n    # optional reason for rejecting the payment\n    reason: str | None = None\n"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Sent by the ",(0,r.jsx)(n.strong,{children:"buyer"})," to decline a ",(0,r.jsx)(n.code,{children:"RequestPayment"})]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"commitpayment",children:"CommitPayment"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"class CommitPayment(Model):\n    # funds to be paid\n    funds: Funds\n\n    # recipient address or identifier\n    recipient: str\n\n    # unique transaction token or hash\n    transaction_id: str\n\n    # optional reference for the payment\n    reference: str | None = None\n\n    # optional description of the payment\n    description: str | None = None\n\n    # optional metadata for the payment\n    metadata: dict[str, str | dict[str, str]] | None = None\n"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Buyer\u2019s"})," commitment response: the exact funds chosen and a ",(0,r.jsx)(n.strong,{children:"transaction_id"})," to track the payment"]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"cancelpayment",children:"CancelPayment"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"class CancelPayment(Model):\n    # unique transaction token or hash\n    transaction_id: str | None = None\n\n    # optional reason for canceling the payment\n    reason: str | None = None\n"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Seller"})," may cancel after a commit (instead of completing). Prefer echoing ",(0,r.jsx)(n.code,{children:"CommitPayment.transaction_id"})," when known; ",(0,r.jsx)(n.code,{children:"None"})," is allowed when no transaction was recorded yet."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"completepayment",children:"CompletePayment"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"class CompletePayment(Model):\n    # unique transaction token or hash\n    transaction_id: str | None = None\n"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Seller"})," confirms the payment completed (typically after verifying the commit). Prefer echoing ",(0,r.jsx)(n.code,{children:"CommitPayment.transaction_id"}),"; ",(0,r.jsx)(n.code,{children:"None"})," is allowed only when there is no transaction to reference."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"glossary-who-invoices-vs-who-pays",children:"Glossary (wh
1o invoices vs who pays)"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Message"}),(0,r.jsx)(n.th,{children:"Meaning"}),(0,r.jsx)(n.th,{children:"Typical sender"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"RequestPayment"})}),(0,r.jsx)(n.td,{children:"Invoice / request to be paid"}),(0,r.jsx)(n.td,{children:"Seller"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"CommitPayment"})}),(0,r.jsxs)(n.td,{children:["Payer commits funds (with ",(0,r.jsx)(n.code,{children:"transaction_id"}),")"]}),(0,r.jsx)(n.td,{children:"Buyer"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"RejectPayment"})}),(0,r.jsx)(n.td,{children:"Payer declines the request"}),(0,r.jsx)(n.td,{children:"Buyer"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"CompletePayment"})}),(0,r.jsx)(n.td,{children:"Merchant confirms completion"}),(0,r.jsx)(n.td,{children:"Seller"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"CancelPayment"})}),(0,r.jsx)(n.td,{children:"Merchant cancels after commit"}),(0,r.jsx)(n.td,{children:"Seller"})]})]})]}),"\n",(0,r.jsx)(n.h3,{id:"2-protocol-specification",children:"2. Protocol Specification"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'payment_protocol_spec = ProtocolSpecification(\n    name="AgentPaymentProtocol",\n    version="0.1.0",\n    interactions={\n        RequestPayment: {CommitPayment, RejectPayment},\n        CommitPayment: {CompletePayment, CancelPayment},\n        CompletePayment: set(),\n        CancelPayment: set(),\n        RejectPayment: set(),\n    },\n    roles={\n        # Messages each role implements handlers for (inbound)\n        "seller": {CommitPayment, RejectPayment},\n        "buyer": {RequestPayment, CancelPayment, CompletePayment},\n    },\n)\n'})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Interactions"}),": valid next messages per message type"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Roles"}),": which ",(0,r.jsx)(n.strong,{children:"inbound"})," message types each role must handle (not a list of messages the role is limited to send)"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Verify the installed protocol version:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'python -c "from uagents_core.contrib.protocols.payment import payment_protocol_spec; print(payment_protocol_spec.name, payment_protocol_spec.version)"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Expected: ",(0,r.jsx)(n.code,{children:"AgentPaymentProtocol 0.1.0"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"3-basic-payment-flow",children:"3. Basic Payment Flow"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Seller sends ",(0,r.jsx)(n.code,{children:"RequestPayment"})," to Buyer"]}),"\n",(0,r.jsxs)(n.li,{children:["Buyer responds with either ",(0,r.jsx)(n.code,{children:"CommitPayment"})," or ",(0,r.jsx)(n.code,{children:"RejectPayment"})]}),"\n",(0,r.jsxs)(n.li,{children:["If committed, Seller sends ",(0,r.jsx)(n.code,{children:"CompletePayment"})," (or ",(0,r.jsx)(n.code,{children:"CancelPayment"})," if aborting after commit)"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"using-the-payment-protocol",children:"Using the Payment Protocol"}),"\n",(0,r.jsx)(n.h3,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Agentverse-hosted"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Packages are provided by the platform; no local ",(0,r.jsx)(n.code,{children:"pip install"})," required."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Local"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Python ",(0,r.jsx)(n.strong,{children:"3.10+"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:'pip install "uagents>=0.25.5"'})," (pulls compatible ",(0,r.jsx)(n.code,{children:"uagents_core"}),"), or ",(0,r.jsx)(n.code,{children:"pip install uagents uagents-core"})," if needed"]}),"\n",(0,r.jsxs)(n.li,{children:["Payment protocol version ",(0,r.jsx)(n.strong,{children:"0.1.0"})," (see verify command above)"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Import the models and specification from ",(0,r.jsx)(n.code,{children:"uagents_core.contrib.protocols.payment"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:"from uagents_core.contrib.protocols.payment import (\n    Funds,\n    RequestPayment,\n    RejectPayment,\n    CommitPayment,\n    CancelPayment,\n    CompletePayment,\n    payment_protocol_spec,\n)\n"})}),"\n",(0,r.jsx)(n.admonition,{title:"Locked-spec errors",type:"note",children:(0,r.jsxs)(n.p,{children:["Because the specification defines roles, instantiate the protocol with ",(0,r.jsx)(n.code,{children:'role="buyer"'})," or ",(0,r.jsx)(n.code,{children:'role="seller"'})," so it matches the ",(0,r.jsx)(n.strong,{children:"inbound handlers"})," you implement. A locked-spec error usually means you registered a handler (or interaction) that your ",(0,r.jsx)(n.code,{children:"role="})," does not include \u2014 for example, a ",(0,r.jsx)(n.code,{children:'role="seller"'})," agent must handle ",(0,r.jsx)(n.code,{children:"CommitPayment"})," / ",(0,r.jsx)(n.code,{children:"RejectPayment"}),", while a ",(0,r.jsx)(n.code,{children:'role="buyer"'})," agent must handle ",(0,r.jsx)(n.code,{children:"RequestPayment"})," / ",(0,r.jsx)(n.code,{children:"CompletePayment"})," / ",(0,r.jsx)(n.code,{children:"CancelPayment"}),"."]})}),"\n",(0,r.jsx)(n.h2,{id:"example-agents",children:"Example Agents"}),"\n",(0,r.jsx)(n.p,{children:"Below are two minimal agents demonstrating the Buyer and Seller roles. These are local-runnable once you replace the buyer address placeholder."}),"\n",(0,r.jsx)(n.h3,{id:"seller-agent",children:"Seller Agent"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",metastring:'title="seller.py"',children:'import os\nfrom uuid import uuid4\nfrom uagents import Agent, Protocol, Context\n\nfrom uagents_core.contrib.protocols.payment import (\n    Funds,\n    RequestPayment,\n    CommitPayment,\n    RejectPayment,\n    CancelPayment,\n    CompletePayment,\n    payment_protocol_spec,\n)\n\nSELLER_PORT = int(os.getenv("SELLER_PORT", "8091"))\n# REQUIRED: paste Buyer address from its startup logs before starting Seller\nBUYER_ADDRESS = "PASTE_BUYER_ADDRESS_HERE"\n\nseller = Agent(\n    name="demo_seller",\n    port=SELLER_PORT,\n    endpoint=[f"http://localhost:{SELLER_PORT}/submit"],\n    seed="demo_seller_seed",\n)\n\npayment_proto = Protocol(spec=payment_protocol_spec, role="seller")\n\n\[email protected]_event("startup")\nasync def startup(ctx: Context):\n    ctx.logger.info(f"Seller address: {ctx.agent.address}")\n\n    if BUYER_ADDRESS.startswith("PASTE_") or not BUYER_ADDRESS.startswith("agent"):\n        ctx.logger.error("Set BUYER_ADDRESS to the Buyer\'s real address before running Seller")\n        return\n\n    # Protocol-only demo: no Stripe/Skyfire metadata. Real rails verify before CompletePayment.\n    # Optional Str
1ipe-shaped metadata (commented):\n    # metadata={"stripe": {"ui_mode": "embedded", "checkout_session_id": "cs_test_..."}}\n    req = RequestPayment(\n        accepted_funds=[Funds(currency="USDC", amount="0.001", payment_method="skyfire")],\n        recipient=ctx.agent.address,\n        deadline_seconds=300,\n        reference=str(uuid4()),\n        description="demo payment",\n        metadata={},\n    )\n    ctx.logger.info(f"Seller sending RequestPayment to {BUYER_ADDRESS}: {req}")\n    await ctx.send(BUYER_ADDRESS, req)\n\n\n@payment_proto.on_message(CommitPayment)\nasync def on_commit(ctx: Context, sender: str, msg: CommitPayment):\n    ctx.logger.info(f"Seller received CommitPayment: {msg}")\n    # Demo only \u2014 real integrations must verify Stripe/Skyfire/on-chain payment first.\n    # To abort instead: await ctx.send(sender, CancelPayment(transaction_id=msg.transaction_id, reason="demo cancel"))\n    await ctx.send(sender, CompletePayment(transaction_id=msg.transaction_id))\n    ctx.logger.info("Seller sent CompletePayment")\n\n\n@payment_proto.on_message(RejectPayment)\nasync def on_reject(ctx: Context, sender: str, msg: RejectPayment):\n    ctx.logger.info(f"Buyer rejected: {msg.reason}")\n\n\nseller.include(payment_proto, publish_manifest=True)\n\nif __name__ == "__main__":\n    seller.run()\n'})}),"\n",(0,r.jsx)(n.h3,{id:"buyer-agent",children:"Buyer Agent"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",metastring:'title="buyer.py"',children:'import os\nfrom uagents import Agent, Protocol, Context\n\nfrom uagents_core.contrib.protocols.payment import (\n    Funds,\n    RequestPayment,\n    CommitPayment,\n    RejectPayment,\n    CompletePayment,\n    CancelPayment,\n    payment_protocol_spec,\n)\n\nBUYER_PORT = int(os.getenv("BUYER_PORT", "8092"))\nBUYER_MODE = os.getenv("BUYER_MODE", "commit").lower()  # commit | reject\n\nbuyer = Agent(\n    name="demo_buyer",\n    port=BUYER_PORT,\n    endpoint=[f"http://localhost:{BUYER_PORT}/submit"],\n    seed="demo_buyer_seed",\n)\n\npayment_proto = Protocol(spec=payment_protocol_spec, role="buyer")\n\n\[email protected]_event("startup")\nasync def startup(ctx: Context):\n    ctx.logger.info(f"Buyer address: {ctx.agent.address}")\n\n\n@payment_proto.on_message(RequestPayment)\nasync def on_request(ctx: Context, sender: str, msg: RequestPayment):\n    ctx.logger.info(f"Buyer received RequestPayment: {msg}")\n\n    if not msg.accepted_funds:\n        await ctx.send(sender, RejectPayment(reason="no accepted funds provided"))\n        return\n\n    selected = msg.accepted_funds[0]\n\n    if BUYER_MODE == "reject":\n        await ctx.send(sender, RejectPayment(reason="demo reject"))\n        return\n\n    # Demo only: hardcoded transaction_id \u2014 no real Stripe/Skyfire/on-chain verification occurs.\n    commit = CommitPayment(\n        funds=Funds(\n            currency=selected.currency,\n            amount=selected.amount,\n            payment_method=selected.payment_method,\n        ),\n        recipient=msg.recipient,\n        transaction_id="demo-txn-001",\n        reference=msg.reference,\n        description=msg.description,\n        metadata=msg.metadata or {},\n    )\n    await ctx.send(sender, commit)\n    ctx.logger.info("Buyer sent CommitPayment")\n\n\n@payment_proto.on_message(CompletePayment)\nasync def on_complete(ctx: Context, sender: str, msg: CompletePayment):\n    ctx.logger.info(f"Buyer received CompletePayment: {msg}")\n\n\n@payment_proto.on_message(CancelPayment)\nasync def on_cancel(ctx: Context, sender: str, msg: CancelPayment):\n    # Seller may send CancelPayment instead of CompletePayment after a commit\n    ctx.logger.info(f"Buyer received CancelPayment: {msg}")\n\n\nbuyer.include(payment_proto, publish_manifest=True)\n\nif __name__ == "__main__":\n    buyer.run()\n'})}),"\n",(0,r.jsx)(n.h3,{id:"running-the-agents",children:"Running the Agents"}),"\n",(0,r.jsx)(n.h4,{id:"hosted-agentverse",children:"Hosted (Agentverse)"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Create two hosted agents (Buyer and Seller). See ",(0,r.jsx)(n.a,{href:"/docs/agent-creation/uagent-creation#hosted-agents",children:"Hosted Agents"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Paste each script into the matching editor. For hosted agents, ",(0,r.jsx)(n.strong,{children:"omit"})," the ",(0,r.jsx)(n.code,{children:'if __name__ == "__main__": \u2026 .run()'})," block and you can simplify ",(0,r.jsx)(n.code,{children:"Agent(...)"})," to ",(0,r.jsx)(n.code,{children:"Agent()"})," (Agentverse provides networking)."]}),"\n",(0,r.jsxs)(n.li,{children:["Start ",(0,r.jsx)(n.strong,{children:"Buyer"})," first, copy its address from logs into Seller\u2019s ",(0,r.jsx)(n.code,{children:"BUYER_ADDRESS"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Start ",(0,r.jsx)(n.strong,{children:"Seller"}),". Seller sends ",(0,r.jsx)(n.code,{children:"RequestPayment"})," on startup, so Buyer must already be running."]}),"\n"]}),"\n",(0,r.jsx)(n.h4,{id:"local",children:"Local"}),"\n",(0,r.jsxs)(n.p,{children:["Defaults: Seller port ",(0,r.jsx)(n.strong,{children:"8091"}),", Buyer port ",(0,r.jsx)(n.strong,{children:"8092"})," (override with ",(0,r.jsx)(n.code,{children:"SELLER_PORT"})," / ",(0,r.jsx)(n.code,{children:"BUYER_PORT"}),")."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'# Seller \u2014 already shown in seller.py\nseller = Agent(\n    name="demo_seller",\n    port=SELLER_PORT,\n    endpoint=[f"http://localhost:{SELLER_PORT}/submit"],\n    seed="demo_seller_seed",\n)\n\n# Buyer \u2014 already shown in buyer.py\nbuyer = Agent(\n    name="demo_buyer",\n    port=BUYER_PORT,\n    endpoint=[f"http://localhost:{BUYER_PORT}/submit"],\n    seed="demo_buyer_seed",\n)\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsxs)(n.strong,{children:["Seller will not work until ",(0,r.jsx)(n.code,{children:"BUYER_ADDRESS"})," is replaced"]})," with the Buyer\u2019s real address."]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Start the ",(0,r.jsx)(n.strong,{children:"Buyer"})," first and copy its address from logs (",(0,r.jsx)(n.code,{children:"Buyer address: agent1q..."}),")."]}),"\n",(0,r.jsxs)(n.li,{children:["Paste that address into ",(0,r.jsx)(n.code,{children:"BUYER_ADDRESS"})," in ",(0,r.jsx)(n.code,{children:"seller.py"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Start the ",(0,r.jsx)(n.strong,{children:"Seller"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["You should see the ",(0,r.jsx)(n.strong,{children:"Seller"})," send a ",(0,r.jsx)(n.code,{children:"RequestPayment"}),", the ",(0,r.jsx)(n.strong,{children:"Buyer"})," respond with a ",(0,r.jsx)(n.code,{children:"CommitPayment"}),", and the ",(0,r.jsx)(n.strong,{children:"Seller"})," send a ",(0,r.jsx)(n.code,{children:"CompletePayment"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Prefer importing from ",(0,r.jsx)(n.code,{children:"uagents_core.contrib.protocols.payment"})," as shown above. Vendoring (copying) the models/spec into your repo is only needed if you must pin or customize the protocol offline; otherwise depend on ",(0,r.jsx)(n.code,{children:"uagents_core"})," so you stay aligned with published version ",(0,r.jsx)(n.code,{children:"0.1.0"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"expected-output",children:"Expected Output"}),"\n",(0,r.jsx)(n.p,{children:"When both agents are running, you should see logs similar to the following."}),"\n",(0,r.jsx)(n.p,{children:"Buyer logs"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"INFO:     [demo_buyer]: Starting agent with address: agent1q...\nINFO:     [demo_buyer]: Agent inspector available at https://agentverse.ai/inspect/?uri=http%3A//127.0.0.1%3A8092&address=agent1q...\nINFO:     [demo_buyer]: Starting server on http://0.0.0.0:8092 (Press CTRL+C to quit)\nINFO:     [demo_buyer]: Buyer address: agent1q...\nINFO:     [demo_buyer]: Buyer received RequestPayment: accepted_funds=[Funds(amount='0.001', currency='USDC', payment_method='skyfire')] recipient='agent1q...' deadline_seconds=300 reference='...' description='demo payment' metadata={}\nINFO:     [demo_buyer]: Buyer sent CommitPayment\nINFO:     [demo_buyer]: Buyer received CompletePayment: transaction_id='demo-txn-001'\n"})}),"\n",(0,r.jsx)(n.p,{children:"Seller logs"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",children:"INFO:     [demo_seller]: Starting agent with address: agent1q...\nINFO:     [demo_seller]: Seller address: agent1q...\nINFO:     [demo_seller]: Seller sending RequestPayment to agent1q...: accepted_funds=[Funds(amount='0.001', currency='USDC', payment_method='skyfire')] recipient='agent1q...' deadline_seconds=300 reference='...' description='demo payment' metadata={}\nINFO:     [demo_seller]: Agent inspector available at https://agentverse.ai/inspect/?uri=http%3A//127.0.0.1%3A8091&address=agent1q...\nINFO:     [demo_seller]: Starting server on http://0.0.0.0:8091 (Press CTRL+C to quit)\nINFO:     [demo_seller]: Seller received CommitPayment: funds=Funds(amount='0.001', currency='USDC', payment_method='skyfire') re
1cipient='agent1q...' transaction_id='demo-txn-001' reference='...' description='demo payment' metadata={}\nINFO:     [demo_seller]: Seller sent CompletePayment\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Success checklist"})}),"\n",(0,r.jsxs)(n.ul,{className:"contains-task-list",children:["\n",(0,r.jsxs)(n.li,{className:"task-list-item",children:[(0,r.jsx)(n.input,{type:"checkbox",disabled:!0})," ","Buyer received ",(0,r.jsx)(n.code,{children:"RequestPayment"})]}),"\n",(0,r.jsxs)(n.li,{className:"task-list-item",children:[(0,r.jsx)(n.input,{type:"checkbox",disabled:!0})," ","Buyer sent ",(0,r.jsx)(n.code,{children:"CommitPayment"})]}),"\n",(0,r.jsxs)(n.li,{className:"task-list-item",children:[(0,r.jsx)(n.input,{type:"checkbox",disabled:!0})," ","Seller sent ",(0,r.jsx)(n.code,{children:"CompletePayment"})]}),"\n",(0,r.jsxs)(n.li,{className:"task-list-item",children:[(0,r.jsx)(n.input,{type:"checkbox",disabled:!0})," ","Buyer received ",(0,r.jsx)(n.code,{children:"CompletePayment"})]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Seller errors about buyer address / no reply"})," \u2192 Buyer is not running, or ",(0,r.jsx)(n.code,{children:"BUYER_ADDRESS"})," still starts with ",(0,r.jsx)(n.code,{children:"PASTE_"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"NameError: seller"})," / ",(0,r.jsx)(n.code,{children:"buyer"})]})," \u2192 Ensure each script constructs ",(0,r.jsx)(n.code,{children:"seller = Agent(...)"})," / ",(0,r.jsx)(n.code,{children:"buyer = Agent(...)"})," before decorators."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Locked-spec errors"})," \u2192 Set ",(0,r.jsx)(n.code,{children:'role="seller"'})," or ",(0,r.jsx)(n.code,{children:'role="buyer"'})," to match the inbound handlers you implement (see note above)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:["Missing ",(0,r.jsx)(n.code,{children:"Agent()"})," / wrong ports"]})," \u2192 Wire ",(0,r.jsx)(n.code,{children:"SELLER_PORT"})," / ",(0,r.jsx)(n.code,{children:"BUYER_PORT"})," into ",(0,r.jsx)(n.code,{children:"Agent(port=..., endpoint=[...])"})," as in the samples."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Import / version failures"})," \u2192 Install prerequisites and verify ",(0,r.jsx)(n.code,{children:"payment_protocol_spec.version"})," is ",(0,r.jsx)(n.code,{children:"0.1.0"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,r.jsxs)(n.p,{children:["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 ",(0,r.jsx)(n.code,{children:"RequestPayment"}),", the buyer either sends ",(0,r.jsx)(n.code,{children:"CommitPayment"})," or ",(0,r.jsx)(n.code,{children:"RejectPayment"}),", and the seller completes or cancels as appropriate."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Always instantiate your protocol with the correct ",(0,r.jsx)(n.code,{children:"role"})," so inbound handlers match the role\u2019s required messages."]}),"\n",(0,r.jsx)(n.li,{children:"Use the examples to bootstrap local testing, then adapt handlers to perform real transfers, persistence, retries, and idempotency."}),"\n",(0,r.jsxs)(n.li,{children:["For complementary patterns, see the ",(0,r.jsx)(n.a,{href:"/docs/agent-communication/agent-chat-protocol",children:"Agent Chat Protocol"})," and the agent setup notes in ",(0,r.jsx)(n.a,{href:"/docs/agent-creation/uagent-creation",children:"Agent Creation"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"With this foundation, you can extend validation, add signatures or receipts, and integrate with on-chain or off-chain payment rails as needed."})]})}function m(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453:(e,n,t)=>{t.d(n,{R:()=>i,x:()=>l});var s=t(96540);const r={},o=s.createContext(r);function i(e){const n=s.useContext(o);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.