# Agent Pay

> Trigger this skill when the user expresses intent to generate, create, draw, or render an AI image, illustration, picture, or artwork. Payment is handled automatically via the x402 protocol with USDT on BSC — the user just supplies a prompt. This includes intents such as: - "generate an image of …" - "draw a picture of …" - "create an illustration" - "make an AI image" - "render this scene" - "I want a poster / wallpaper / artwork of …" Also, any request involving x402-protocol crypto payments for AI image generation.

- Skill: `aeon-project/agent-pay` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add aeon-project/agent-pay`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aeon-project/agent-pay/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: aeon-project (https://skillmd.com/u/aeon-project)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aeon-project/agent-pay

---


# Agent-Pay Skill — AI Image Generation (x402)

Generate AI images from a text prompt, paying per request with USDT on BSC via the x402 HTTP payment protocol.

> ⚡ **Gas Model**:
> BSC USDT does not support EIP-3009. The client must perform a one-time `approve` authorization (on-chain tx) before the first generation; the actual USDT transfer is executed by the server.
> - **Generate (x402)**: Check allowance → if insufficient and no BNB, auto-transfer 0.0003 BNB via WalletConnect for approve gas → if USDT insufficient, auto-transfer USDT → EIP-712 signature (gasless) → server submits transfer (server pays gas) → image returned
> - **Top up (topup)**: Single WalletConnect session, transfers USDT to local wallet. User confirms **1 transaction** in wallet app
> - **Withdraw (withdraw)**: Local wallet sends ERC20 transfer + BNB directly on-chain, requires BNB for gas
> - **Gas top-up (gas)**: Transfers BNB only (used when withdraw reports "No BNB for gas" or additional BNB is needed)

---

## Opening Line (Required)

Whenever entering this skill for the first time, output this opening line:

> Let me check the environment first.

Then **immediately** proceed to "Step 1: Pre-check".

---

## Command Overview

All operations use the global command `agent-pay`.

> 📦 **One-time installation**:
> ```bash
> npm install -g @aeon-ai-pay/agent-pay@latest
> ```
> Using the global command instead of `npx` avoids 4-5 second cold-start delays.
> Upgrade: `npm update -g @aeon-ai-pay/agent-pay`.

```bash
agent-pay setup --check                          # Pre-check / auto-create wallet
agent-pay setup --show                           # Show configuration
agent-pay generate --prompt "<text>"             # Generate AI image (x402-paid)
agent-pay wallet                                 # Check local wallet balance
agent-pay topup --amount <usdt>                  # Top up USDT (WalletConnect, 1 confirmation)
agent-pay gas [--amount <bnb>]                   # Top up BNB for local wallet (WalletConnect, for approve/withdraw)
agent-pay clean                                  # Uninstall skill, clear cache
agent-pay withdraw [--to <addr>] [--amount <usdt>]  # Withdraw funds
```

Config is stored in `~/.agent-pay/config.json` (file permissions 600).
**Never ask the user for a private key; the local wallet private key is auto-generated by the CLI.**

---

## Step 1: Pre-check (Auto Wallet Initialization)

Regardless of user intent, **always** run first:

```bash
agent-pay setup --check
```

**If `agent-pay` is not found (exit code 127 / "command not found")**:

1. Output to user exactly:
   ```
   > Installing agent-pay...
   ```
2. Run (foreground, wait for completion):
   ```bash
   npm install -g @aeon-ai-pay/agent-pay@latest
   ```
3. Re-run `agent-pay setup --check` and continue normally from the output templates below.

This is a one-time installation — no user confirmation needed, no error to surface.

CLI behavior:
1. Reads `~/.agent-pay/config.json`
2. If `privateKey` is missing → generates a new private key locally with `viem.generatePrivateKey()` and saves it
3. Returns JSON: `{ ready, created, mode, address, mainWallet, serviceUrl }`

> 💰 **Pricing**: per-call USDT is decided by the x402 server (returned in the 402 response). Top-up covers exactly `requiredUsdt - currentBalance` when the wallet is short. No client-side minimum / floor.

### Output Templates

Always output a progress line first:

```
> Pre-check in progress...
```

#### Branch A: Wallet already exists (`ready: true`, `created: false`)

```
0x0...{last4} Ready. Tell me what image you'd like to generate.
```

#### Branch B: Auto-created this time (`ready: true`, `created: true`)

```
Auto-creating your designated wallet...
0x0...{last4} Ready. Tell me what image you'd like to generate.
```

> - `{last4}` is the last 4 characters of the returned `address`
> - Pre-check is **offline** — no on-chain balance queries, no server calls

### Edge Cases

| User Question | Response |
| --- | --- |
| "What's my wallet address?" | Show the `address` returned by `setup --check` |
| "I want to import my own private key" | Not supported. CLI only auto-generates local wallets; for customization, manually edit `~/.agent-pay/config.json` |
| "Can I recover my wallet?" | No. The private key is stored locally only; back up the config file before withdrawing funds |

---

## Step 2: Generate Image (with Auto Top-up When Insufficient)

Trigger: User wants to **generate / create / draw / render** an image.

### 2.0 Prompt Confirmation

- The user must provide a **non-empty prompt** describing the image they want.
- If the user has not yet supplied a prompt, ask (verbatim):
  > What image would you like me to generate? Describe it in a sentence or two.
- Once the user gives a prompt, **execute immediately** — no second confirmation needed. Proceed to 2.1.
- Actual deduction per generation is decided by the x402 server (returned in the 402 response). If the wallet has less than the server-required amount, the CLI auto-triggers WalletConnect funding in 2.1 (covering exactly the shortfall) — do not pre-call `topup`.

### 2.1 Execute Generation

```bash
agent-pay generate --prompt "<text>" \
  [--aspect-ratio 16:9] \
  [--output-format png] \
  [--model replicate/black-forest-labs/flux-schnell]
```

CLI executes the following steps internally:
1. Validate prompt and wallet config
2. Serialize `{ model, inputs: { prompt, aspect_ratio, output_format } }` to JSON, URL-encode it as the `body` query param
3. Fetch payment requirements via `GET /open/ai/x402/skillBoss/create?body=<encoded-json>` (exact USDT amount via 402 response)
4. Check allowance → if insufficient and local wallet has no BNB, mark BNB needed
5. Check USDT balance → if insufficient, mark top-up needed
6. **If top-up or BNB needed** → auto-initiate WalletConnect funding (opens QR page, waits for user to confirm in wallet app, 5-minute timeout)
7. `approve` authorization (on-chain tx, costs small amount of BNB, only on first use or when allowance insufficient)
8. EIP-712 signature (gasless) → re-send the same `GET` URL with `PAYMENT-SIGNATURE` header → server submits transfer and returns generated image URLs
9. CLI downloads each `data.images[].url` to `~/agent-pay-images/` (override with `--output <dir>`) and reads format/size/dimensions

**Defaults**: `aspect-ratio=16:9`, `output-format=png`, `model=replicate/black-forest-labs/flux-schnell`. Only `--prompt` is required; pass other flags only when the user explicitly asks for a different aspect ratio / format / model.

Output first line:

```
> Generating image...
```

⚠️ **The `generate` command includes an interactive WalletConnect flow (when balance insufficient); it must run in foreground synchronously**:
- Do not use `run_in_background: true`
- Do not kill the process before the user finishes scanning

> 🔧 **If `generate` was accidentally run in background and killed**:
> The user's on-chain transaction **may already have been sent** (USDT actually arrived in local wallet).
> In this case, **do not re-topup**. Instead:
> 1. Run `agent-pay wallet` to confirm USDT has arrived
> 2. If arrived, re-run the original `generate --prompt "<text>"`
> 3. If not arrived (user didn't actually scan), re-run `generate` in foreground

### 2.2 Scenario Branches

#### Case A: Successful Generation

On HTTP 200, the server returns JSON shaped as:
```json
{
  "transaction": "0x...",
  "data": { "images": [ { "url": "https://assets.skillboss.co/....png" } ] }
}
```

The CLI automatically downloads every `data.images[].url`, then reads each file's format/dimensions/size and emits:

```json
{
  "success": true,
  "prompt": "<original>",
  "aspectRatio": "16:9",
  "outputFormat": "png",
  "model": "replicate/black-forest-labs/flux-schnell",
  "transaction": "0x...",
  "images": [
    {
      "url": "https://...png",
      "localPath": "/Users/<user>/agent-pay-images/<file>.png",
      "format": "png",
      "width": 1344,
      "height": 768,
      "sizeBytes": 1016287,
      "sizeHuman": "992.4 KB"
    }
  ],
  "data": { /* full server payload */ },
  "paymentResponse": { "txHash": "0x..." }
}
```

Display to the user as a **key-value list** (no fixed-width box, so long paths / tx hashes never overflow). Use this exact form (**verbatim**, variable substitution only):

```
✅ Generated

📁 Path        {localPath}
🎨 Format      {format}
📐 Dimensions  {width} × {height}
💾 Size        {sizeHuman}
🔗 Tx          {transaction}
```

Rules:
- Title `✅ Generated` on its own line, then one blank line, then the 5 rows.
- Each row: emoji + single space + label padded with spaces to the longest label width (`Dimensions` = 10 chars) + **two spaces** + value. This keeps values visually aligned in monospace fonts.
- `{format}`: uppercase the CLI's lowercase value (e.g. `png` → `PNG`).
- `{width} × {height}`: render with `×` (U+00D7) and single spaces around it.
- `{transaction}`: full on-chain tx hash from the top-level `transaction` (not `paymentResponse.txHash`).
- Multiple images: render one block per image, separated by a blank line.
- Failed download: replace the whole block of that image with one line `❌ Download failed: {error} (source: {imageUrl})`.

#### Case B: Funding Signature Timeout (5 minutes)

CLI returns:
```json
{"error":"Payment approval timed out. Please try again."}
```
Relay to user and ask if they want to retry. **Do not auto-retry.**

#### Case B.1: User Rejected Signature

CLI returns:
```json
{"error":"Payment approval was rejected. Please try again if you'd like to proceed."}
```
Relay to user. **Do not auto-retry.**

#### Case B.2: Insufficient Balance After Funding

CLI returns `Still insufficient USDT after funding` error. Relay to user.

#### Case C: Server Network/Call Failure

CLI returns `success: false` with HTTP error. Show the raw error and suggest the user retry later or check `serviceUrl`.

#### Case D: Empty / Invalid Prompt

CLI returns:
```json
{"error":"Missing --prompt. Provide a non-empty image prompt."}
```
Ask the user to supply a prompt.

See [generate-image](references/generate-image.md) for detailed field descriptions.

---

## Step 3: Wallet Management

Trigger: User wants to **check balance / top up / withdraw funds**.

### 3.1 Check Local Wallet Balance

```bash
agent-pay wallet
```

Shows local wallet USDT balance and address. If user has previously used `topup`, main wallet balance will also be displayed.

### 3.2 Top Up

```bash
agent-pay topup --amount <usdt>               # Top up USDT to local wallet
```

`topup` transfers USDT from the main wallet to local wallet via WalletConnect. User confirms **1 transaction** in wallet app.

> 💡 No need to top up BNB separately — the `generate` command auto-requests 0.0003 BNB when it detects insufficient allowance and no BNB.

### 3.3 Withdraw Funds to Main Wallet

```bash
agent-pay withdraw                                  # Withdraw all USDT to recorded mainWallet
agent-pay withdraw --amount <usdt>                  # Specify amount
agent-pay withdraw --to 0xMainWallet                # Specify destination address
agent-pay withdraw --to 0xMainWallet --amount <usdt>
```

> ⚠️ **Withdraw requires BNB for gas**:
> Unlike x402 generation (gasless), `withdraw` is a **direct on-chain ERC20 transfer** from the local wallet,
> which must pay BNB gas itself (recommended >= 0.0005 BNB).
> Users need to transfer a small amount of BNB to the local wallet address from an exchange or their own wallet.

#### Destination Address Resolution Priority

1. CLI argument `--to <address>`
2. `mainWallet` in `~/.agent-pay/config.json` (**only available after user has used `topup`**)

#### Output Template (**copy must be verbatim**, variable substitution only)

```
> Reclaiming funds...

From: 0x0...{session_last4}
To: main wallet (0x0...{main_last4})

Amount: {amount} USDT
Status: completed
```

> The literal "main wallet" label is a spec requirement — **do not omit it**; the address in parentheses lets the user confirm the transfer target.

#### Edge Cases

| Error | Meaning | Action |
| --- | --- | --- |
| `No main wallet address found. Use --to <address>` | No mainWallet in config and no `--to` provided | Ask user to provide destination address |
| `No USDT to withdraw.` | Local wallet USDT balance is 0 | Inform user nothing to withdraw, suggest `topup` first |
| `No BNB for gas. ...` | Local wallet has no BNB, cannot pay gas | Prompt user to run `agent-pay gas` to top up BNB via WalletConnect; see 3.4 |
| `Requested X USDT but only Y available` | `--amount` exceeds actual balance | Show actual balance, ask user to confirm a new amount |
| `Withdraw failed: ...` | On-chain transaction failed | Show raw error, suggest retrying later |

### 3.4 Top Up Gas for Local Wallet (BNB)

When `withdraw` reports `No BNB for gas` or additional BNB is needed, use the dedicated `gas` subcommand to transfer a small amount of BNB from the main wallet via WalletConnect.

```bash
agent-pay gas                    # Default 0.001 BNB
agent-pay gas --amount 0.002     # Custom amount
```

⚠️ **This command uses an interactive WalletConnect flow** (same mechanism as `topup`):
- Terminal prints QR code + `wc:` URI
- User scans with wallet app to connect main wallet
- Confirms 1 BNB transfer in wallet (amount = `<amount>`, target = local wallet)
- Maximum wait 5 minutes, **must not run in background**

On success, `mainWallet` is automatically saved to config (so subsequent `withdraw` can omit `--to`).

#### Output Template

```
> Topping up gas...
Initializing WalletConnect session...
Waiting for wallet confirmation...
BNB transfer confirmed.

Local wallet: 0x0...{last4}
Balance: {bnb} BNB
```

#### Edge Cases

| Error | Action |
| --- | --- |
| `Transaction rejected in wallet.` | Inform user it was cancelled, ask if they want to retry. **Do not auto-retry** |
| `BNB transfer failed: ...` | Main wallet BNB insufficient or on-chain revert; prompt user to prepare BNB in main wallet first |
| WalletConnect 5-minute timeout | Inform user of timeout, suggest re-running `gas` |

---

## Decision Routing Overview

| User Intent | Entry Command |
| --- | --- |
| Any first entry / uncertain state | `setup --check` |
| View current config / wallet address | `setup --show` |
| Generate AI image | `generate --prompt "<text>"` |
| Session key USDT insufficient, top up | `topup --amount <n>` |
| Check local wallet balance | `wallet` |
| Withdraw funds to main wallet | `withdraw [--to <addr>] [--amount <n>]` |
| Top up BNB for local wallet (pre-withdraw) | `gas [--amount <bnb>]` |
| Learn about x402 protocol | Read [x402-protocol](references/x402-protocol.md) |

---

## Copy Consistency Constraints (Required Reading)

The following **key phrases** and **line-level output templates** must be **verbatim** — no rewording, translation, character additions/removals (including punctuation, spaces, `>` prefix, and casing):

### Line-Level Templates (must be exact)

| Step | Template First Line |
| --- | --- |
| Pre-check | `> Pre-check in progress...` |
| Auto-create wallet | `Auto-creating your designated wallet...` |
| Wallet ready | `0x0...{last4} Ready. Tell me what image you'd like to generate.` |
| Generate image | `> Generating image...` |
| Generation success header | `✅ Generated` (followed by blank line + 5-row key-value list; see Case A) |
| Signature timeout | `Payment approval timed out. Please try again.` |
| Signature rejected | `Payment approval was rejected. Please try again if you'd like to proceed.` |
| Funding flow | `> Funding flow triggered...` |
| Withdraw funds | `> Reclaiming funds...` |
| Withdraw target line | `To: main wallet (0x0...{last4})` |
| Withdraw status line | `Status: completed` |

### Key Phrases (must be preserved as-is)

- `Payment approval timed out. Please try again.`
- `Payment approval was rejected. Please try again if you'd like to proceed.`
- `Prompt`, `Image`, `Tx`
- `From`, `To`, `Amount`, `Status`, `completed`
- `main wallet` (literal text in the withdraw target line)

### Variable Mapping

| Placeholder | Source |
| --- | --- |
| `{last4}` | Last 4 characters of `address` from `setup --check` / `wallet` / `withdraw` output |
| `{prompt}` | `prompt` field from `generate` output |
| `{localPath}` | `images[].localPath` from `generate` output |
| `{format}` | `images[].format` (uppercase when displayed) |
| `{width}` / `{height}` | `images[].width` / `images[].height` |
| `{sizeHuman}` | `images[].sizeHuman` |
| `{transaction}` | top-level `transaction` field |
| `{amount}` | `withdrawn` field from `withdraw` output |

### Prohibited Deviations

- ❌ Translate to other languages
- ❌ Change casing (e.g., "Generating Image")
- ❌ Abbreviate
- ❌ Add extra decorations (e.g., emoji, bold, `✅`)
- ❌ Split or merge lines
- ❌ Use synonyms (e.g., replace `insufficient` with `not enough`)

---

## Global Prohibited Behaviors

- **Never** ask the user for a private key; the local wallet is auto-generated by the CLI
- **Never** execute `generate` or `topup` without the user supplying / confirming the input (prompt or amount)
- **Never** log or display the full private key; addresses are displayed as `0x0...last4` format
- **Never** skip `setup --check` and directly execute other commands
- **Never** run `generate` / `topup` / `gas` / any command with WalletConnect flow in the background (must run in foreground synchronously). For "paid but not detected" issues caused by accidental backgrounding, follow the recovery instructions in Step 2.1
- **Do not** auto-retry after funding/signature failure; relay the error to the user and stop
- **Do not** invent prompt content for the user; if no prompt is given, ask

