PageSourceSearch

https://innovationlab.fetch.ai/resources/assets/js/3b30e0b5.c6ec71de.js

js fetch.ai collected 2026-09-24 09:40:19 UTC 32,216 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkinnovation_labs=self.webpackChunkinnovation_labs||[]).push([[2905],{50037:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>s,toc:()=>l});const s=JSON.parse('{"id":"examples/agent-transaction/image-agent-payment-protocol","title":"Image Agent Payment Protocol Example","description":"This example demonstrates a seller agent that requests a small payment (USDC via Skyfire) before generating an image and sending it back as a chat resource. Based on the image-agent-payment-protocol example in innovation-lab-examples.","source":"@site/versioned_docs/version-1.0.5/examples/agent-transaction/image-agent-payment-protocol.md","sourceDirName":"examples/agent-transaction","slug":"/examples/agent-transaction/image-agent-payment-protocol","permalink":"/resources/docs/examples/agent-transaction/image-agent-payment-protocol","draft":false,"unlisted":false,"tags":[],"version":"1.0.5","frontMatter":{"id":"image-agent-payment-protocol","title":"Image Agent Payment Protocol Example"},"sidebar":"tutorialSidebar","previous":{"title":"A2A Cart Store (AP2 + Skyfire)","permalink":"/resources/docs/examples/adapters/a2a-cart-store-example"},"next":{"title":"FET Image Agent Payment Protocol Example","permalink":"/resources/docs/examples/agent-transaction/fet-image-agent-payment-protocol"}}');var r=t(74848),a=t(28453);const i={id:"image-agent-payment-protocol",title:"Image Agent Payment Protocol Example"},o="Image Agent Payment Protocol Example",c={},l=[{value:"What it shows",id:"what-it-shows",level:2},{value:"Getting Started (clone first)",id:"getting-started-clone-first",level:2},{value:"Project Structure",id:"project-structure",level:2},{value:"Payment Protocol (imports)",id:"payment-protocol-imports",level:2},{value:"Skyfire helpers",id:"skyfire-helpers",level:2},{value:"Payment logic (seller)",id:"payment-logic-seller",level:2},{value:"Chat Protocol integration",id:"chat-protocol-integration",level:2},{value:"Agent setup",id:"agent-setup",level:2},{value:"Environment",id:"environment",level:2},{value:"Skyfire checkout checklist",id:"skyfire-checkout-checklist",level:3},{value:"Run locally",id:"run-locally",level:2},{value:"Expected flow",id:"expected-flow",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Related examples",id:"related-examples",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",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,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"image-agent-payment-protocol-example",children:"Image Agent Payment Protocol Example"})}),"\n",(0,r.jsxs)(n.p,{children:["This example demonstrates a seller agent that requests a small payment (USDC via Skyfire) before generating an image and sending it back as a ",(0,r.jsx)(n.strong,{children:"chat resource"}),". Based on the ",(0,r.jsx)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples/tree/main/image-agent-payment-protocol",children:"image-agent-payment-protocol"})," example in ",(0,r.jsx)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples",children:"innovation-lab-examples"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The code blocks below are a ",(0,r.jsx)(n.strong,{children:"reference"})," to the full flow (pay \u2192 prompt \u2192 Pollinations \u2192 Agentverse storage \u2192 ",(0,r.jsx)(n.code,{children:"ResourceContent"}),"). Clone the example and run it; do not assemble files from snippets alone."]}),"\n",(0,r.jsx)(n.h2,{id:"what-it-shows",children:"What it shows"}),"\n",(0,r.jsx)("div",{style:{textAlign:"center"},children:(0,r.jsx)("img",{src:"/resources/img/agent-transaction/image-agent-payment-flow.png",alt:"Image agent payment protocol: chat, Skyfire, Pollinations, storage",style:{width:"100%",maxWidth:"1000px"}})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Seller-side ",(0,r.jsx)(n.code,{children:"AgentPaymentProtocol"})," (",(0,r.jsx)(n.code,{children:"payment_protocol_spec"}),", ",(0,r.jsx)(n.code,{children:'role="seller"'}),")."]}),"\n",(0,r.jsxs)(n.li,{children:["Skyfire JWT verification and USDC charge (",(0,r.jsx)(n.code,{children:"verify_and_charge"}),")."]}),"\n",(0,r.jsxs)(n.li,{children:["After payment: one image prompt, generation via ",(0,r.jsx)(n.a,{href:"https://image.pollinations.ai/",children:"Pollinations"}
1),", upload with ",(0,r.jsx)(n.code,{children:"ExternalStorage"}),", return as ",(0,r.jsx)(n.code,{children:"ResourceContent"}),"."]}),"\n",(0,r.jsx)(n.li,{children:"Chat protocol: acknowledgements, payment gating, and post-payment image delivery."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"getting-started-clone-first",children:"Getting Started (clone first)"}),"\n",(0,r.jsxs)(n.p,{children:["Requires ",(0,r.jsx)(n.strong,{children:"Python 3.10+"}),", a ",(0,r.jsx)(n.a,{href:"https://www.skyfire.xyz/",children:"Skyfire"})," seller account, and an ",(0,r.jsx)(n.a,{href:"/docs/agentverse/agentverse-api-key",children:"Agentverse API key"})," (mailbox plus image storage)."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"macOS / Linux:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/fetchai/innovation-lab-examples.git\ncd innovation-lab-examples/image-agent-payment-protocol\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\ncp .env.example .env\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Windows (Command Prompt):"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bat",children:"git clone https://github.com/fetchai/innovation-lab-examples.git\ncd innovation-lab-examples\\image-agent-payment-protocol\npython -m venv .venv\n.venv\\Scripts\\activate\npip install -r requirements.txt\ncopy .env.example .env\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Edit ",(0,r.jsx)(n.code,{children:".env"})," with your keys (see ",(0,r.jsx)(n.a,{href:"#environment",children:"Environment"}),"), then:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"python agent.py\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The example repo may still pin older ",(0,r.jsx)(n.code,{children:"uagents"})," versions. This page is tested against ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"uagents==0.25.5"})})," and ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"uagents-core==0.4.9"})})," (Payment Protocol + ",(0,r.jsx)(n.code,{children:"ResourceContent"})," / ",(0,r.jsx)(n.code,{children:"ExternalStorage"}),"). If you hit import errors, install those pins in the venv:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",metastring:'title="requirements.txt"',children:"uagents==0.25.5\nuagents-core==0.4.9\naiohttp==3.10.9\npython-jose[cryptography]==3.3.0\npython-dotenv==1.0.1\nrequests\n"})}),"\n",(0,r.jsx)(n.h2,{id:"project-structure",children:"Project Structure"}),"\n",(0,r.jsx)(n.p,{children:"Key files in the example:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"agent.py"})," \u2013 Agent setup; includes chat and payment protocols"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"chat_proto.py"})," \u2013 Chat protocol, ack, payment gate, post-payment prompt"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"payment_proto.py"})," \u2013 Seller payment logic, Pollinations, storage, ",(0,r.jsx)(n.code,{children:"ResourceContent"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"skyfire.py"})," \u2013 JWT verification and charge against the Skyfire API"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:".env.example"})," \u2013 Canonical environment variable names"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Full sources:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples/tree/main/image-agent-payment-protocol",children:"image-agent-payment-protocol/"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples/blob/main/image-agent-payment-protocol/skyfire.py",children:(0,r.jsx)(n.code,{children:"skyfire.py"})})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples/blob/main/image-agent-payment-protocol/payment_proto.py",children:(0,r.jsx)(n.code,{children:"payment_proto.py"})})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples/blob/main/image-agent-payment-protocol/chat_proto.py",children:(0,r.jsx)(n.code,{children:"chat_proto.py"})})}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"payment-protocol-imports",children:"Payment Protocol (imports)"}),"\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.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"CancelPayment"})," is part of the spec; this seller example handles ",(0,r.jsx)(n.code,{children:"CommitPayment"})," and ",(0,r.jsx)(n.code,{children:"RejectPayment"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"skyfire-helpers",children:"Skyfire helpers"}),"\n",(0,r.jsxs)(n.p,{children:["Use the full ",(0,r.jsx)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples/blob/main/image-agent-payment-protocol/skyfire.py",children:(0,r.jsx)(n.code,{children:"skyfire.py"})})," from the cloned repo. It:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Reads ",(0,r.jsx)(n.strong,{children:"canonical"})," seller vars ",(0,r.jsx)(n.code,{children:"SELLER_SKYFIRE_API_KEY"})," and ",(0,r.jsx)(n.code,{children:"SELLER_SERVICE_ID"}),", with fallback aliases ",(0,r.jsx)(n.code,{children:"SKYFIRE_API_KEY"})," and ",(0,r.jsx)(n.code,{children:"SKYFIRE_SERVICE_ID"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Uses ",(0,r.jsx)(n.code,{children:"SELLER_ACCOUNT_ID"})," or ",(0,r.jsx)(n.code,{children:"JWT_AUDIENCE"})," as the JWT audience."]}),"\n",(0,r.jsxs)(n.li,{children:["Normalizes ",(0,r.jsx)(n.code,{children:"JWT_ISSUER"})," with ",(0,r.jsx)(n.code,{children:'.rstrip("/")'})," (do not add a trailing slash in ",(0,r.jsx)(n.code,{children:".env"}),")."]}),"\n",(0,r.jsxs)(n.li,{children:["Fetches JWKS, verifies the JWT (",(0,r.jsx)(n.code,{children:"ssi"})," must match the service id), then ",(0,r.jsx)(n.code,{children:"POST"}),"s ",(0,r.jsx)(n.code,{children:"SKYFIRE_TOKENS_API_URL"})," with headers ",(0,r.jsx)(n.code,{children:"skyfire-api-key"})," and ",(0,r.jsx)(n.code,{children:"skyfire-api-version: 2"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Exposes ",(0,r.jsx)(n.code,{children:"verify_and_charge(token, amount_usdc, logger)"})," and ",(0,r.jsx)(n.code,{children:"get_skyfire_service_id()"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Do not copy empty ",(0,r.jsx)(n.code,{children:"pass"})," stubs; keep that file next to ",(0,r.jsx)(n.code,{children:"agent.py"})," by cloning the repo."]}),"\n",(0,r.jsx)(n.h2,{id:"payment-logic-seller",children:"Payment logic (seller)"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",metastring:'title="payment_proto.py"',children:'import os\nfrom datetime import datetime, timezone\nfrom uuid import uuid4\nfrom urllib.parse import quote\n\nfrom uagents import Context, Protocol\nfrom uagents_core.contrib.protocols.chat import ChatMessage, Resource, ResourceContent\nfrom uagents_core.contrib.protocols.payment import (\n    Funds,\n    RequestPayment,\n    RejectPayment,\n    CommitPayment,\n    CompletePayment,\n    payment_protocol_spec,\n)\nfrom uagents_core.storage import ExternalStorage\n\nfrom skyfire import verify_and_charge, get_skyfire_service_id\nfrom chat_proto import create_text_chat\n\n_agent_wallet = None\n\ndef set_agent_wallet(wallet):\n    global _agent_wallet\n    _agent_wallet = wallet\n\npayment_proto = Protocol(spec=payment_protocol_spec, role="seller")\n\nUSDC_FUNDS = Funds(currency="USDC", amount="0.001", payment_method="skyfire")\n\nasync def request_payment_from_user(ctx: Context, user_address: str):\n    accepted_funds = [USDC_FUNDS]\n    skyfire_service_id = get_skyfire_service_id()\n\n    metadata = {}\n    if skyfire_service_id:\n        metadata["skyfire_service_id"] = skyfire_service_id\n    if _agent_wallet:\n        metadata["provider_agent_wallet"] = str(_agent_wallet.address())\n\n    payment_request = RequestPayment(\n        accepted_funds=accepted_funds,\n        recipient=ctx.agent.address,\n        deadline_seconds=300,\n        reference=str(uuid4()),\n        description="ASI1 Image Gen: after payment, send your image prompt (one image per payment)",\n        metadata=metadata,\n    )\n    await ctx.send(user_address, payment_request)\n\n@payment_proto.on_message(CommitPayment)\nasync def handle_commit_payment(ctx: Context, sender: str, msg: CommitPayment):\n    payment_verified = False\n    if msg.funds.payment_method == "skyfire" and msg.funds.currency == "USDC":\n        try:\n            payment_verified = await verify_and_charge(\n                msg.transaction_id, "0.001", ctx.logger\n            )\n        except Exception as e:\n            ctx.logger.error(f"Skyfire verify/c
1harge error: {e}")\n            payment_verified = False\n    else:\n        ctx.logger.error(f"Unsupported payment method: {msg.funds.payment_method}")\n\n    if payment_verified:\n        session_id = str(ctx.session)\n        ctx.storage.set(f"{sender}:{session_id}:awaiting_prompt", True)\n        ctx.storage.set(f"{sender}:{session_id}:verified_payment", True)\n        await ctx.send(sender, CompletePayment(transaction_id=msg.transaction_id))\n        await ctx.send(\n            sender,\n            create_text_chat("Payment verified. Please send your image prompt."),\n        )\n    else:\n        await ctx.send(sender, RejectPayment(reason="Payment verification failed"))\n\n@payment_proto.on_message(RejectPayment)\nasync def handle_reject_payment(ctx: Context, sender: str, msg: RejectPayment):\n    await ctx.send(\n        sender,\n        create_text_chat(\n            "You rejected the payment. Reply if you want a new payment request."\n        ),\n    )\n\nasync def generate_image_after_payment(ctx: Context, user_address: str):\n    session_id = str(ctx.session)\n    prompt = ctx.storage.get(f"prompt:{user_address}:{session_id}")\n    if not prompt:\n        await ctx.send(user_address, create_text_chat("Error: No prompt found"))\n        return\n\n    clean_prompt = " ".join(str(prompt).split())[:200] or "an image"\n    pollinations_url = (\n        f"https://image.pollinations.ai/prompt/{quote(clean_prompt)}?width=512&height=512"\n    )\n\n    try:\n        import requests\n\n        resp = requests.get(pollinations_url, timeout=90)\n        ctype = resp.headers.get("Content-Type", "")\n        if resp.status_code != 200 or not resp.content or not ctype.startswith("image/"):\n            await ctx.send(user_address, create_text_chat("Image generation failed"))\n            return\n\n        api_key = os.getenv("AGENTVERSE_API_KEY")\n        base_url = os.getenv("AGENTVERSE_URL", "https://agentverse.ai")\n        if not api_key:\n            await ctx.send(\n                user_address,\n                create_text_chat(\n                    "Storage not configured. Please set AGENTVERSE_API_KEY to deliver the image."\n                ),\n            )\n            return\n\n        storage = ExternalStorage(\n            api_token=api_key, storage_url=f"{base_url}/v1/storage"\n        )\n        asset_id = storage.create_asset(\n            name=str(ctx.session), content=resp.content, mime_type=ctype or "image/png"\n        )\n        storage.set_permissions(asset_id=asset_id, agent_address=user_address)\n        asset_uri = f"agent-storage://{storage.storage_url}/{asset_id}"\n\n        await ctx.send(\n            user_address,\n            ChatMessage(\n                timestamp=datetime.now(timezone.utc),\n                msg_id=uuid4(),\n                content=[\n                    ResourceContent(\n                        type="resource",\n                        resource_id=asset_id,\n                        resource=Resource(\n                            uri=asset_uri,\n                            metadata={\n                                "mime_type": ctype or "image/png",\n                                "role": "generated-image",\n                            },\n                        ),\n                    )\n                ],\n            ),\n        )\n    except Exception as e:\n        ctx.logger.error(f"Image generation error: {e}")\n        await ctx.send(user_address, create_text_chat(f"Error generating image: {e}"))\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"generate_image_after_payment"})," is required for the documented image path. The repo file also sanitizes prompts that contain extra ASI",":One"," context blocks; see the ",(0,r.jsxs)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples/blob/main/image-agent-payment-protocol/payment_proto.py",children:["full ",(0,r.jsx)(n.code,{children:"payment_proto.py"})]}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"chat-protocol-integration",children:"Chat Protocol integration"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",metastring:'title="chat_proto.py"',children:'from datetime import datetime, timezone\nfrom uuid import uuid4\n\nfrom uagents import Context, Protocol\nfrom uagents_core.contrib.protocols.chat import (\n    AgentContent,\n    ChatAcknowledgement,\n    ChatMessage,\n    EndSessionContent,\n    TextContent,\n    chat_protocol_spec,\n)\n\ndef create_text_chat(text: str, end_session: bool = False) -> ChatMessage:\n    content: list[AgentContent] = [TextContent(type="text", text=text)]\n    if end_session:\n        content.append(EndSessionContent(type="end-session"))\n    return ChatMessage(\n        timestamp=datetime.now(timezone.utc),\n        msg_id=uuid4(),\n        content=content,\n    )\n\nchat_proto = Protocol(spec=chat_protocol_spec)\n\n@chat_proto.on_message(ChatMessage)\nasync def handle_message(ctx: Context, sender: str, msg: ChatMessage):\n    await ctx.send(\n        sender,\n        ChatAcknowledgement(\n            timestamp=datetime.now(timezone.utc), acknowledged_msg_id=m
1sg.msg_id\n        ),\n    )\n\n    from payment_proto import request_payment_from_user, generate_image_after_payment\n\n    for item in msg.content:\n        if isinstance(item, TextContent):\n            text = item.text.strip()\n            session_id = str(ctx.session)\n            awaiting_key = f"{sender}:{session_id}:awaiting_prompt"\n            verified_key = f"{sender}:{session_id}:verified_payment"\n\n            if ctx.storage.get(awaiting_key) and ctx.storage.get(verified_key):\n                ctx.storage.remove(awaiting_key)\n                ctx.storage.remove(verified_key)\n                ctx.storage.set(f"prompt:{sender}:{session_id}", text)\n                await generate_image_after_payment(ctx, sender)\n                return\n\n            await ctx.send(\n                sender,\n                create_text_chat(\n                    "Please complete a small payment first. After that, send your image prompt."\n                ),\n            )\n            await request_payment_from_user(ctx, sender)\n\n@chat_proto.on_message(ChatAcknowledgement)\nasync def handle_ack(ctx: Context, sender: str, msg: ChatAcknowledgement):\n    ctx.logger.info(\n        f"Got an acknowledgement from {sender} for {msg.acknowledged_msg_id}"\n    )\n'})}),"\n",(0,r.jsxs)(n.p,{children:["After ",(0,r.jsx)(n.code,{children:"CompletePayment"}),", storage flags ",(0,r.jsx)(n.code,{children:"awaiting_prompt"})," and ",(0,r.jsx)(n.code,{children:"verified_payment"})," must be set so the ",(0,r.jsx)(n.strong,{children:"next"})," text message is treated as the image prompt."]}),"\n",(0,r.jsx)(n.h2,{id:"agent-setup",children:"Agent setup"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",metastring:'title="agent.py"',children:'import os\nimport dotenv\nfrom uagents import Agent, Context\n\ndotenv.load_dotenv()\n\nfrom chat_proto import chat_proto\nfrom payment_proto import payment_proto, set_agent_wallet\n\nagent = Agent(\n    name=os.getenv("AGENT_NAME", "ASI1ImageAgent"),\n    seed=os.getenv("AGENT_SEED"),\n    port=int(os.getenv("AGENT_PORT", "8021")),\n    mailbox=True,\n    agentverse=os.getenv("AGENTVERSE_URL", "https://agentverse.ai"),\n)\n\nset_agent_wallet(agent.wallet)\n\[email protected]_event("startup")\nasync def startup(ctx: Context):\n    ctx.logger.info(f"ASI1 Image Agent started: {agent.wallet.address()}")\n    ctx.logger.info("=== ASI1 Image Generation Agent ===")\n    ctx.logger.info("Accepted: $0.001 USDC (via Skyfire)")\n\nagent.include(chat_proto, publish_manifest=True)\nagent.include(payment_proto, publish_manifest=True)\n\nif __name__ == "__main__":\n    agent.run()\n'})}),"\n",(0,r.jsx)(n.admonition,{title:"Unique identity",type:"warning",children:(0,r.jsxs)(n.p,{children:["The example repo uses a fixed ",(0,r.jsx)(n.code,{children:'name="ASI1ImageAgent"'})," and ",(0,r.jsx)(n.strong,{children:"no"})," ",(0,r.jsx)(n.code,{children:"seed"}),". Every reader then derives the same agent address (for example ",(0,r.jsx)(n.code,{children:"agent1qvc7umyf0zspmx7wtm6d27pk3lfcj0ackwn99hghv9jwp3wfxesvkjsf0qk"}),"). Set a unique ",(0,r.jsx)(n.code,{children:"AGENT_NAME"})," and/or ",(0,r.jsx)(n.code,{children:"AGENT_SEED"})," in ",(0,r.jsx)(n.code,{children:".env"})," so your agent does not collide with other copies on Agentverse."]})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"mailbox=True"})," registers the local process with the Agentverse mailbox so ASI",":One"," and other agents can reach it without exposing a public IP. Sign in to Agentverse and connect the mailbox from the inspector (see ",(0,r.jsx)(n.a,{href:"/docs/agent-creation/uagent-creation#mailbox-agents",children:"Mailbox agents"}),"). That login is separate from ",(0,r.jsx)(n.code,{children:"AGENTVERSE_API_KEY"}),", which this example uses to ",(0,r.jsx)(n.strong,{children:"upload"})," the generated image to Agentverse External Storage."]}),"\n",(0,r.jsx)(n.h2,{id:"environment",children:"Environment"}),"\n",(0,r.jsxs)(n.p,{children:["Copy ",(0,r.jsx)(n.a,{href:"https://github.com/fetchai/innovation-lab-examples/blob/main/image-agent-payment-protocol/.env.example",children:(0,r.jsx)(n.code,{children:".env.example"})})," to ",(0,r.jsx)(n.code,{children:".env"}),". Canonical names match the example repo:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-text",metastring:'title=".env"',children:"AGENTVERSE_URL=https://agentverse.ai\nAGENTVERSE_API_KEY=\n\nSELLER_ACCOUNT_ID=\nSELLER_SERVICE_ID=\nSELLER_SKYFIRE_API_KEY=\nJWKS_URL=https://app.skyfire.xyz/.well-known/jwks.json\nJWT_ISSUER=https://app.skyfire.xyz\nSKYFIRE_TOKENS_API_URL=https://api.skyfire.xyz/api/v1/tokens/charge\nSKYFIRE_ENV=production\n\n# Optional: avoid address collisions with other copies of this example\nAGENT_NAME=ASI1ImageAgent\nAGENT_SEED=\nAGENT_PORT=8021\n"})}),"\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:"Variable"}),(0,r.jsx)(n.th,{children:"Purpose"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"AGENTVERSE_API_KEY"})}),(0,r.jsxs)(n.td,{children:["Required to deliver the image. Without it the agent replies ",(0,r.jsx)(n.code,{children:"Storage not configured. Please set AGENTVERSE_API_KEY to deliver the image."})," Create a key in ",(0,r.jsx)(n.a,{href:"/docs/agentverse/agentverse-api-key",children:"Agentverse API keys"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"AGENTVERSE_URL"})}),(0,r.jsxs)(n.td,{children:["Agentverse host (default ",(0,r.jsx)(n.code,{children:"https://agentverse.ai"}),"). Storage uses ",(0,r.jsx)(n.code,{children:"{AGENTVERSE_URL}/v1/storage"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"SELLER_SKYFIRE_API_KEY"})}),(0,r.jsxs)(n.td,{children:["Skyfire seller API key (alias: ",(0,r.jsx)(n.code,{children:"SKYFIRE_API_KEY"}),")."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"SELLER_SERVICE_ID"})}),(0,r.jsxs)(n.td,{children:["Skyfire service id; must match JWT claim ",(0,r.jsx)(n.code,{children:"ssi"})," (alias: ",(0,r.jsx)(n.code,{children:"SKYFIRE_SERVICE_ID"}),")."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"SELLER_ACCOUNT_ID"})}),(0,r.jsxs)(n.td,{children:["JWT audience (alias: ",(0,r.jsx)(n.code,{children:"JWT_AUDIENCE"})," or ",(0,r.jsx)(n.code,{children:"SKYFIRE_ACCOUNT_ID"}),")."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"JWT_ISSUER"})}),(0,r.jsxs)(n.td,{children:["Must be ",(0,r.jsx)(n.code,{children:"https://app.skyfire.xyz"})," ",(0,r.jsx)(n.strong,{children:"without"})," a trailing slash. ",(0,r.jsx)(n.code,{children:"skyfire.py"})," also strips ",(0,r.jsx)(n.code,{children:"/"})," at runtime."]})]})]})]}),"\n",(0,r.jsx)(n.h3,{id:"skyfire-checkout-checklist",children:"Skyfire checkout checklist"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Create a Skyfire seller service and copy ",(0,r.jsx)(n.code,{children:"SELLER_SKYFIRE_API_KEY"}),", ",(0,r.jsx)(n.code,{children:"SELLER_SERVICE_ID"}),", and ",(0,r.jsx)(n.code,{children:"SELLER_ACCOUNT_ID"})," into ",(0,r.jsx)(n.code,{children:".env"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Keep ",(0,r.jsx)(n.code,{children:'USDC_FUNDS = Funds(currency="USDC", amount="0.001", payment_method="skyfire")'})," and put ",(0,r.jsx)(n.code,{children:"skyfire_service_id"})," on ",(0,r.jsx)(n.code,{children:"RequestPayment.metadata"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["On ",(0,r.jsx)(n.code,{children:"CommitPayment"}),", call ",(0,r.jsx)(n.code,{children:'verify_and_charge(msg.transaction_id, "0.001", ctx.logger)'})," and only send ",(0,r.jsx)(n.code,{children:"CompletePayment"})," on success."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"run-locally",children:"Run locally"}),"\n",(0,r.jsxs)(n.p,{children:["After ",(0,r.jsx)(n.a,{href:"#getting-started-clone-first",children:"Getting Started"})," and filling ",(0,r.jsx)(n.code,{children:".env"}),":"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"macOS / Linux:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"source .venv/bin/activate\npython agent.py\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Windows:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bat",children:".venv\\Scripts\\activate\npython agent.py\n"})}),"\n",(0,r.jsx)(n.h3,{id:"expected-flow",children:"Expected flow"}),"\n",(0,r.jsxs)(n.p,{children:["Use ",(0,r.jsx)(n.strong,{children:(0,r.jsxs)(n.a,{href:"https://asi1.ai/",children:["ASI",":One"]})})," (or another chat client that implements Agent Chat + Payment Protocol, including Skyfire Pay and ",(0,r.jsx)(n.code,{children:"ResourceContent"}),"). Local inspector URL is printed at startup (typically based on port ",(0,r.jsx)(n.code,{children:"8021"}),")."]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Chat with the agent \u2192 it sends ",(0,r.jsx)(n.code,{children:"RequestPayment"})," ($0.001 USDC via Skyfire)."]}),"\n",(0,r.jsxs)(n.li,{children:["Complete Skyfire Pay in the client \u2192 ",(0,r.jsx)(n.code,{children:"CommitPayment"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Agent verifies, sends ",(0,r.jsx)(n.code,{children:"CompletePayment"}),", then asks for an image prompt."]}),"\n",(0,r.jsxs)(n.li,{children:["Send a prompt \u2192 Pollinations generates an image \u2192 Agentverse storage \u2192 chat ",(0,r.jsx)(n.code,{children:"ResourceContent"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\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:"Symptom"}),(0,r.jsx)(n.th,{children:"What to check"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"Storage not configured. Please set AGENTVERSE_API_KEY\u2026"})}),(0,r.jsxs)(n.td,{children:["Set ",(0,r.jsx)(n.code,{children:"AGENTVERSE_API_KEY"})," in ",(0,r.jsx)(n.code,{children:".env"}),". This is required to upload the image; mailbox login alone is not enough."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Skyfire not configured / charge fails"}),(0,r.jsxs)(n.td,{children:["Use ",(0,r.jsx)(n.code,{children:"SELLER_SKYFIRE_API_KEY"})," and ",(0,r.jsx)(n.code,{children:"SELLER_SERVICE_ID"}),". Legacy aliases ",(0,r.jsx)(n.code,{children:"SKYFIRE_API_KEY"})," / ",(0,r.jsx)(n.code,{children:"SKYFIRE_SERVICE_ID"})," work in ",(0,r.jsx)(n.code,{children:"skyfire.py"})," but are not the names in ",(0,r.jsx)(n.code,{children:".env.example"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"JWT issuer mismatch"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"JWT_ISSUER=https://app.skyfire.xyz"})," with ",(0,r.jsx)(n.strong,{children:"no"})," trailing slash."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Same address as everyone else"}),(0,r.jsxs)(n.td,{children:["Change ",(0,r.jsx)(n.code,{children:"AGENT_NAME"})," and/or set ",(0,r.jsx)(n.code,{children:"AGENT_SEED"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Client never shows Pay"}),(0,r.jsxs)(n.td,{children:["Use ASI",":One"," or another client that renders Skyfire from ",(0,r.jsx)(n.code,{children:"RequestPayment.metadata"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Image never arrives after pay"}),(0,r.jsxs)(n.td,{children:["Next message must be the prompt while ",(0,r.jsx)(n.code,{children:"awaiting_prompt"})," is set; missing storage key fails before delivery."]})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:"related-examples",children:"Related examples"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/examples/agent-transaction/fet-image-agent-payment-protocol",children:"FET Image Agent Payment Protocol"})," \u2014 same paid-image idea, ",(0,r.jsx)(n.strong,{children:"on-chain FET"})," instead of Skyfire USDC."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/examples/chat-protocol/image-generation-agent",children:"Image Generation Agent"})," \u2014 chat-protocol image generation ",(0,r.jsx)(n.strong,{children:"without"})," payment."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/examples/adapter
1s/a2a-cart-store-example",children:"A2A Cart Store"})," \u2014 Skyfire checkout in an A2A cart flow."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/agent-transaction/agent-payment-protocol",children:"Agent Payment Protocol"})," \u2014 protocol roles and message types."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/examples/agent-transaction/stripe-horoscope-payment-protocol",children:"Stripe Horoscope Agent"})," \u2014 Payment Protocol with Stripe instead of Skyfire."]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,a.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:()=>o});var s=t(96540);const r={},a=s.createContext(r);function i(e){const n=s.useContext(a);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),s.createElement(a.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.