Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/invoice-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
AGENT_WALLET_ADDRESS=0x_your_wallet_here
OPENROUTER_API_KEY=your_openrouter_api_key_here

96 changes: 96 additions & 0 deletions examples/invoice-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Invoice Agent with X402 Payment Flow
====================================

## Overview

This example implements a billing agent that:

* Generates invoices with structured line items
* Emits X402-compatible payment requests
* Verifies payments and updates invoice state


It demonstrates a complete payment lifecycle:
```bash
create → pay → verify → settled
```

## Features

* Invoice creation with structured payload
* X402 payment header generation
* Payment verification (mocked for demo)
* In-memory (non-persistent) invoice state tracking

## Setup

Install dependencies:
```bash
pip install bindu python-dotenv
```

Create .env:
```bash
AGENT_WALLET_ADDRESS=0x_your_wallet_here
OPENROUTER_API_KEY=sk-xxxx #optional
```

Run the agent:
```bash
python invoice_agent.py
```

## Example Input
```json
{
"type": "generate_invoice",
"payload": {
"recipient": "akash@example.com",
"items": [
{ "description": "API access", "quantity": 1, "unit_price": 50 },
{ "description": "Compute", "quantity": 2, "unit_price": 20 }
],
"currency": "USDC"
}
}
```

## Example Output:

```json
{
"invoice_id": "inv_66ac3b32-6cb4-4588-bd06-f71160ba206c",
"total": 90,
"payment_header": "X402 0xE5bC3b8796432A70aC8450E2aaD54055d9e2DBb8:90"
}
{
"invoice": {
"id": "inv_66ac3b32-6cb4-4588-bd06-f71160ba206c",
"recipient": "acme@example.com",
"recipient_wallet": "0xE5bC3b8796432A70aC8450E2aaD54055d9e2DBb8",
"items": [
{ "description": "API", "quantity": 1, "unit_price": 50 },
{ "description": "Compute", "quantity": 2, "unit_price": 20 }
],
"currency": "USDC",
"total": 90,
"status": "paid",
"tx_hash": "0xabc123"
}
}
```


## Skills

* generate\_invoice – create invoice and emit X402 payment request
* get\_invoice – fetch invoice by ID
* list\_invoices – list invoices
* verify\_payment – verify payment and update invoice state


## Notes

* Payment verification is mocked for demonstration
* Storage is in-memory and can be replaced with a database
* Wallet address can be any valid EVM address
167 changes: 167 additions & 0 deletions examples/invoice-agent/invoice_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
from bindu.penguin.bindufy import bindufy
from dotenv import load_dotenv
import os
import logging
import uuid
import json

load_dotenv()
logger = logging.getLogger(__name__)
# Simple in-memory storage

db = {}


def save_invoice(invoice):
db[invoice["id"]] = invoice


def get_invoice_by_id(invoice_id):
return db.get(invoice_id)


def list_invoices():
return list(db.values())


# Core logic


def create_invoice(payload):
if not isinstance(payload, dict) or "items" not in payload:
raise ValueError("Invalid payload")

if not isinstance(payload["items"], list) or not payload["items"]:
raise ValueError("items must be a non-empty list")

total = 0
for i, item in enumerate(payload["items"]):
qty = item.get("quantity")
price = item.get("unit_price")

if not isinstance(qty, (int, float)) or not isinstance(price, (int, float)):
raise ValueError(f"Invalid item at index {i}")

if qty <= 0 or price < 0:
raise ValueError(f"Invalid values at index {i}")

Comment thread
coderabbitai[bot] marked this conversation as resolved.
total += qty * price

recipient_wallet = payload.get("recipient_wallet") or os.getenv(
"AGENT_WALLET_ADDRESS"
)

if not recipient_wallet:
raise ValueError("recipient_wallet is required")
invoice = {
"id": f"inv_{uuid.uuid4()}",
"recipient": payload.get("recipient"),
"recipient_wallet": recipient_wallet,
"items": payload["items"],
"currency": payload.get("currency", "USDC"),
"total": total,
"status": "pending",
}

save_invoice(invoice)
return invoice


def verify_payment(invoice_id, tx_hash):
invoice = get_invoice_by_id(invoice_id)

if not invoice:
return {"verified": False, "reason": "Invoice not found"}

# mock verification
invoice["status"] = "paid"
invoice["tx_hash"] = tx_hash
save_invoice(invoice)

return {
"verified": True,
"settled_amount": invoice["total"],
"reason": None,
}


# Bindu config

config = {
"author": "akash",
"name": "invoice-agent",
"deployment": {"url": "http://localhost:3773", "expose": True},
"description": "Invoice agent with X402 payment flow",
"version": "1.0.0",
"capabilities": {
"payments": ["invoice", "x402"],
},
"skills": ["skills/invoice-agent-skill"],
"auth": {"enabled": False},
"storage": {"type": "memory"},
"scheduler": {"type": "memory"},
}

# Handler (Bindu entry point)


def handler(messages):
try:
user_messages = [m for m in messages if m.get("role") == "user"]

if not user_messages:
return "No user message found"

raw = user_messages[-1].get("parts", [{}])[0].get("text", "{}")

try:
input_data = json.loads(raw)
except json.JSONDecodeError:
return {"error": "bad_request", "message": "Invalid JSON input"}

if input_data.get("type") == "generate_invoice":
invoice = create_invoice(input_data.get("payload", {}))

return {
"invoice_id": invoice["id"],
"total": invoice["total"],
"payment_header": {
"amount": str(invoice["total"]),
"token": invoice.get("currency", "USDC"),
"network": os.getenv("X402_NETWORK", "base-sepolia"),
"pay_to_address": invoice["recipient_wallet"],
},
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if input_data.get("type") == "get_invoice":
invoice = get_invoice_by_id(input_data.get("invoice_id"))

if not invoice:
return {
"error": "not_found",
"message": f"Invoice not found: {input_data.get('invoice_id')}",
}

return {"invoice": invoice}

if input_data.get("type") == "list_invoices":
return {"invoices": list_invoices()}

if input_data.get("type") == "verify_payment":
return verify_payment(
input_data.get("invoice_id"),
input_data.get("tx_hash"),
)

return "Unknown request type"

except Exception:
logger.exception("Unhandled error")
return {"error": "internal_error", "message": "Internal Server Error"}


# Run agent

if __name__ == "__main__":
print("Invoice Agent running...")
bindufy(config, handler)
15 changes: 15 additions & 0 deletions examples/invoice-agent/skills/invoice-agent-skill/skill.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
id: invoice-agent-skill
name: invoice-agent-skill
description: Invoice agent for X402 payment flow
version: 1.0.0

tags:
- billing
- payments
- x402

input_modes:
- application/json

output_modes:
- application/json