# X402 Card

> Trigger this skill when the user expresses intent to create, manage, or query a virtual card, or wants to know what they can buy / do with the card. This includes intents such as: - "get a virtual card" - "create a card" - "card status" - "set up a card for an agent" - "what can I buy" - "show me what's available" - "what can I do?" - "what can I use the card for" Also, any request involving the creation of a one-time-use virtual Visa/Mastercard funded with cryptocurrency for agent use.

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

---


# x402 Virtual Card Skill

Create one-time-use virtual debit cards (Visa/Mastercard) for agents using 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 card creation; the actual USDT transfer is executed by the server.
> - **Create card (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)
> - **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 load the tool and check the existing environment first.

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

---

## Command Overview

All operations use the global command `x402-card`.

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

```bash
x402-card setup --check                # Pre-check / auto-create wallet
x402-card setup --show                 # Show configuration
x402-card create --amount <usd> --poll # Create virtual card
x402-card status --order-no <orderNo>  # Query card status
x402-card wallet                       # Check local wallet balance
x402-card topup --amount <usdt>        # Top up USDT (WalletConnect, 1 confirmation)
x402-card gas [--amount <bnb>]         # Top up BNB for local wallet (WalletConnect, for approve/withdraw)
x402-card clean                        # Uninstall skill, clear cache
x402-card withdraw [--to <addr>] [--amount <usdt>]  # Withdraw funds
```

Config is stored in `~/.x402-card/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
x402-card setup --check
```

CLI behavior:
1. Reads `~/.x402-card/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, amountLimits }`

### Output Templates

Always output a progress line first:

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

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

```
0x0...{last4} Ready. Proceed to create a card for your agent.
```

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

```
Auto-creating your designated wallet...
0x0...{last4} Ready. Proceed to create a card for your agent.
```

> - `{last4}` is the last 4 characters of the returned `address`
> - Record `amountLimits.{min,max}` for subsequent amount validation
> - 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 `~/.x402-card/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: Create Virtual Card (with Auto Top-up When Insufficient)

Trigger: User wants to **buy / create / get a virtual card**.

### 2.0 Amount Confirmation

- Amount must be within `amountLimits.min ~ amountLimits.max` (from Step 1 response; never hardcode)
- If user does not specify an amount, use this exact copy (**verbatim**, variable substitution only):
  > You can create a card of up to ${min}~${max}. How much would you like to load onto the card？
- Once the user specifies an amount, **execute immediately** — no second confirmation needed. Proceed to 2.1.

### 2.1 Execute Creation

```bash
x402-card create --amount <usd> --poll
```

CLI executes the following steps internally:
1. Parameter and limit validation
2. Fetch payment requirements from server (exact USDT amount)
3. Check allowance → if insufficient and local wallet has no BNB, mark BNB needed
4. Check USDT balance → if insufficient, mark top-up needed
5. **If top-up or BNB needed** → auto-initiate WalletConnect funding (opens QR page, waits for user to confirm in wallet app, 5-minute timeout)
6. After funding completes, auto-continue
7. `approve` authorization (on-chain tx, costs small amount of BNB, only on first use or when allowance insufficient)
8. EIP-712 signature (gasless) → server submits actual transfer
9. With `--poll` → polls up to 42 times (first 5 at 2-second intervals, then every 5 seconds)

Output first line:

```
> Creating Agent Card...
```

⚠️ **The `create` 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 `create` 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 `x402-card wallet` to confirm USDT has arrived
> 2. If arrived, re-run the original `create --amount <usd> --poll`
> 3. If not arrived (user didn't actually scan), re-run `create` in foreground

### 2.2 Scenario Branches

#### Case A: Amount Out of Range

CLI returns:
```json
{"error":"Amount must be at least $0.6 ...","min":0.6,"max":800}
```
Show the valid range to the user and ask for a new amount.

#### Case B: Creation Successful

CLI outputs JSON containing `success: true` and `orderNo`.

Fetching card details may take about 30 seconds. Output a waiting prompt first:

```
> Fetching card details, please wait...
```

Once details are returned, display (**copy must be verbatim**, variable substitution only):

```
Order No: {orderNo}
Card: {cardScheme} •••• {last4}
State: Active
Remaining balance: ${amount} USD
Usage: 0 / 1 (single-use)
```

Always **record the orderNo** — it's the only identifier for subsequent status queries.

#### Case C: 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 C.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 C.2: Insufficient Balance After Funding

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

#### Case D: 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 E: Polling Timeout (`--poll` exhausted 42 attempts)

CLI outputs:
```
Polling timeout after 42 attempts. Check manually with: x402-card status --order-no {orderNo}
```
Inform user the card is still being processed. Note the `orderNo` and use Step 3 to query later. **Do not continue polling.**

See [create-card](references/create-card.md) for detailed field descriptions.

---

## Step 3: Query Card Status

Trigger: User wants to **check / query card status**.

### 3.1 Command

```bash
x402-card status --order-no <orderNo>
x402-card status --order-no <orderNo> --poll  # Poll until terminal status
```

### 3.2 Output Template

```
> Fetching card status...

Card: {cardScheme} •••• {last4}
State: {Active | Used | Expired | Pending | Failed}
Remaining balance: ${balance} USD
Usage: {used} / {total} (single-use)
```

### 3.3 Edge Cases

| Scenario | Action |
| --- | --- |
| User has no orderNo | Ask for the `orderNo` from the most recent `create` output; if unavailable, inform them query is not possible |
| Invalid orderNo / empty server response | Show raw error, suggest user verify the orderNo |
| Status is Pending | Inform user the card is still processing; optional polling, but no more than 42 attempts |
| Status is Failed | Show failure reason; order is invalid, need to `create` again |

See [check-status](references/check-status.md) for detailed field descriptions.

---

## Step 4: Wallet Management

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

### 4.1 Check Local Wallet Balance

```bash
x402-card wallet
```

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

### 4.2 Top Up

```bash
x402-card 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 `create` command auto-requests 0.0003 BNB when it detects insufficient allowance and no BNB.

### 4.3 Withdraw Funds to Main Wallet

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

> ⚠️ **Withdraw requires BNB for gas**:
> Unlike x402 card creation (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 `~/.x402-card/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 `x402-card gas` to top up BNB via WalletConnect; see 4.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 |

### 4.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
x402-card gas                    # Default 0.001 BNB
x402-card 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` |
| Create virtual card | `create --amount <n> --poll` |
| Session key USDT insufficient, top up | `topup --amount <n>` |
| Query card status | `status --order-no <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) |
| What can I buy / what features are available | Read [store](references/store.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. Proceed to create a card for your agent.` |
| Create card | `> Creating Agent Card...` |
| Creation success | `Order No: {orderNo}` + card details (see Case B) |
| 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...` |
| Fetching card details | `> Fetching card details, please wait...` |
| Query status | `> Fetching card status...` |
| Withdraw funds | `> Reclaiming funds...` |
| Withdraw target line | `To: main wallet (0x0...{last4})` |
| Withdraw status line | `Status: completed` |

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

- `Fetching card details, please wait...`
- `Virtual card ready with`, `loaded!`
- `Payment approval timed out. Please try again.`
- `Payment approval was rejected. Please try again if you'd like to proceed.`
- `Card`, `State`, `Remaining balance`, `Usage`, `single-use`
- `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 |
| `{required}` | `required` field from `create` error response |
| `{available}` | `available` field from `create` error response; or explicit value in `topup` error |
| `{amount}` | `withdrawn` field from `withdraw` output |
| `{orderNo}` | `orderNo` field from `create` output |

### Prohibited Deviations

- ❌ Translate to other languages (e.g., Chinese "余额检查：不足")
- ❌ Change casing (e.g., "Balance Check")
- ❌ Abbreviate (e.g., "BNB insuff.")
- ❌ 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 `create` or `topup` without the user confirming the 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 `create` / `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** poll `status` more than 42 times; stop on timeout and prompt user to note the `orderNo` for manual querying
- **Do not** fabricate `amountLimits`; always use `min/max` returned by `setup --check`

