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
approveauthorization (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:
npm install -g @aeon-ai-pay/agent-pay@latestUsing the global command instead of
npxavoids 4-5 second cold-start delays. Upgrade:npm update -g @aeon-ai-pay/agent-pay.
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:
agent-pay setup --check
If agent-pay is not found (exit code 127 / "command not found"):
- Output to user exactly:
> Installing agent-pay... - Run (foreground, wait for completion):
npm install -g @aeon-ai-pay/agent-pay@latest - Re-run
agent-pay setup --checkand continue normally from the output templates below.
This is a one-time installation — no user confirmation needed, no error to surface.
CLI behavior:
- Reads
~/.agent-pay/config.json - If
privateKeyis missing → generates a new private key locally withviem.generatePrivateKey()and saves it - 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 - currentBalancewhen 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 returnedaddress- 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
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:
- Validate prompt and wallet config
- Serialize
{ model, inputs: { prompt, aspect_ratio, output_format } }to JSON, URL-encode it as thebodyquery param - Fetch payment requirements via
GET /open/ai/x402/skillBoss/create?body=<encoded-json>(exact USDT amount via 402 response) - Check allowance → if insufficient and local wallet has no BNB, mark BNB needed
- Check USDT balance → if insufficient, mark top-up needed
- If top-up or BNB needed → auto-initiate WalletConnect funding (opens QR page, waits for user to confirm in wallet app, 5-minute timeout)
approveauthorization (on-chain tx, costs small amount of BNB, only on first use or when allowance insufficient)- EIP-712 signature (gasless) → re-send the same
GETURL withPAYMENT-SIGNATUREheader → server submits transfer and returns generated image URLs - CLI downloads each
data.images[].urlto~/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
generatewas 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:
- Run
agent-pay walletto confirm USDT has arrived- If arrived, re-run the original
generate --prompt "<text>"- If not arrived (user didn't actually scan), re-run
generatein foreground
2.2 Scenario Branches
Case A: Successful Generation
On HTTP 200, the server returns JSON shaped as:
{
"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:
{
"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
✅ Generatedon 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-leveltransaction(notpaymentResponse.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:
{"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:
{"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:
{"error":"Missing --prompt. Provide a non-empty image prompt."}
Ask the user to supply a prompt.
See generate-image for detailed field descriptions.
Step 3: Wallet Management
Trigger: User wants to check balance / top up / withdraw funds.
3.1 Check Local Wallet Balance
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
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
generatecommand auto-requests 0.0003 BNB when it detects insufficient allowance and no BNB.
3.3 Withdraw Funds to Main Wallet
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),
withdrawis 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
- CLI argument
--to <address> mainWalletin~/.agent-pay/config.json(only available after user has usedtopup)
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.
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 |
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,TxFrom,To,Amount,Status,completedmain 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
insufficientwithnot enough)
Global Prohibited Behaviors
- Never ask the user for a private key; the local wallet is auto-generated by the CLI
- Never execute
generateortopupwithout the user supplying / confirming the input (prompt or amount) - Never log or display the full private key; addresses are displayed as
0x0...last4format - Never skip
setup --checkand 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