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

Comment on lines +1 to +3
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing X402_NETWORK environment variable.

The invoice_agent.py handler references os.getenv("X402_NETWORK", "base-sepolia") at line 131, but this variable is not documented in the .env.example. While it has a default, users should know it's configurable.

📝 Proposed fix
 AGENT_WALLET_ADDRESS=0x_your_wallet_here
 OPENROUTER_API_KEY=your_openrouter_api_key_here
+X402_NETWORK=base-sepolia  # optional, defaults to base-sepolia
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AGENT_WALLET_ADDRESS=0x_your_wallet_here
OPENROUTER_API_KEY=your_openrouter_api_key_here
AGENT_WALLET_ADDRESS=0x_your_wallet_here
OPENROUTER_API_KEY=your_openrouter_api_key_here
X402_NETWORK=base-sepolia # optional, defaults to base-sepolia
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/invoice-agent/.env.example` around lines 1 - 3, Add the missing
X402_NETWORK environment variable to the example env file so users can configure
the network used by the invoice agent; update
examples/invoice-agent/.env.example to include a line like
X402_NETWORK=base-sepolia (or another default) and mention it aligns with the
os.getenv("X402_NETWORK", "base-sepolia") call referenced in invoice_agent.py so
readers know the variable exists and can override the default.

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"
}
}
```
Comment on lines +60 to +81
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Example output doesn't match actual implementation.

The documented payment_header is a string ("X402 0xE5bC3b8796432A70aC8450E2aaD54055d9e2DBb8:90"), but the actual implementation in invoice_agent.py lines 128-133 returns an object:

"payment_header": {
    "amount": str(invoice["total"]),
    "token": invoice.get("currency", "USDC"),
    "network": os.getenv("X402_NETWORK", "base-sepolia"),
    "pay_to_address": invoice["recipient_wallet"],
}

Update the example to reflect the actual response format.

📝 Proposed fix
 ## Example Output:

 ```json
 {
   "invoice_id": "inv_66ac3b32-6cb4-4588-bd06-f71160ba206c",
   "total": 90,
-  "payment_header": "X402 0xE5bC3b8796432A70aC8450E2aaD54055d9e2DBb8:90"
+  "payment_header": {
+    "amount": "90",
+    "token": "USDC",
+    "network": "base-sepolia",
+    "pay_to_address": "0xE5bC3b8796432A70aC8450E2aaD54055d9e2DBb8"
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/invoice-agent/README.md` around lines 60 - 81, The example JSON in
README.md incorrectly shows payment_header as a string; update the example to
match the actual implementation in invoice_agent.py (payment_header returned as
an object) by replacing the string value with an object containing keys amount
(stringified invoice["total"]), token (invoice.get("currency","USDC")), network
(os.getenv("X402_NETWORK","base-sepolia")), and pay_to_address
(invoice["recipient_wallet"]) so the README reflects the real response shape.



## 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}")

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"],
},
}

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
4 changes: 4 additions & 0 deletions examples/portfolio-rebalancer/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
COINGECKO_API_KEY=optional
USE_FAKE_DATA=true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
REQUEST_TIMEOUT=5
REBALANCE_FRACTION=0.25
Loading