# LVL LTD Agent Skill Market — Human + Agent Reference

**Canonical product:** x402 AI agent skill marketplace on Base USDC  
**Company:** LVL LTD CO · **Not** merch / POD / Shopify storefront  
**Authority:** [status.json](https://lvlltd.com/status.json) · [api/proof](https://lvlltd.com/api/proof) · [openapi.json](https://lvlltd.com/openapi.json)  
**Machine entry:** [llms.txt](https://lvlltd.com/llms.txt) · [api/shop](https://lvlltd.com/api/shop) · [agent.json](https://lvlltd.com/agent.json)

Never invent unlock counts. Public proof ledger is the only revenue claim source.

---

## Glossary

| Term | Meaning |
|------|---------|
| **x402** | HTTP Payment Required protocol: GET challenge → on-chain settle → POST unlock |
| **challenge** | HTTP 402 body (+ headers) with `maxAmountRequired`, `payTo`, network, asset |
| **atomic USDC** | 6-decimal integer string; `$0.05` → `"50000"` |
| **payTo / treasury** | Canonical receiver `0xa00876513bAA433ce2B58A5341Fd06d2b6f9A6ED` (EIP-55) |
| **sealed pack** | Capability files returned only after verified payment |
| **outline / sample** | Free evaluation (no wallet required) |
| **canary** | `agent-x402-first-buy` at **$0.05** — prove rails before larger buys |
| **proof ledger** | `/api/proof` — confirmed unlocks only, never fabricated |
| **recover** | Re-POST same `txHash` + `skill` if transfer landed but unlock failed |
| **idempotent unlock** | Same verified `txHash` + skill re-downloads pack without re-paying |
| **Base** | Chain id **8453**; gas = tiny **ETH on Base** (not USDC) |
| **USDC** | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` (USDbC also accepted) |
| **quality_score** | 0–100 content depth (not sales); see [CATALOG-QUALITY](./CATALOG-QUALITY.md) |

---

## 7-step agent purchase sequence

```
1. GET  /api/shop                              → contract + shelves + next_action
2. GET  /api/catalog?q=…&max_price=…           → pick skill_id
3. GET  /skills/{id}/outline.json              → FREE evaluate
4. GET  /api/pay?skill={id}                    → HTTP 402 challenge
5. Transfer maxAmountRequired USDC → payTo on Base (8453)
6. POST /api/pay
      Header: X-PAYMENT: {"txHash":"0x…","skill":"{id}"}
      Body:   {"txHash":"0x…","skill":"{id}"}   (optional duplicate)
7. Persist sealed_pack.files + payment.txHash
8. (optional) GET /api/proof                   → verify ledger row
```

Start-here canary: `skill=agent-x402-first-buy` · amount `50000` atomic · **$0.05**.

### curl

```bash
# 1–2 discovery
curl -sS https://lvlltd.com/api/shop | jq .
curl -sS 'https://lvlltd.com/api/catalog?q=orchestration&limit=5' | jq .

# 3 free eval
curl -sS https://lvlltd.com/skills/agent-x402-first-buy/outline.json | jq .
curl -sS https://lvlltd.com/skills/agent-x402-first-buy/sample.md | head

# 4 challenge (expect 402)
curl -si 'https://lvlltd.com/api/pay?skill=agent-x402-first-buy'

# 5 — your wallet: transfer maxAmountRequired to payTo on Base

# 6 unlock
curl -sS -X POST 'https://lvlltd.com/api/pay' \
  -H 'Content-Type: application/json' \
  -H 'X-PAYMENT: {"txHash":"0xYOUR_TX","skill":"agent-x402-first-buy"}' \
  -d '{"txHash":"0xYOUR_TX","skill":"agent-x402-first-buy"}' | jq .

# 7 proof
curl -sS https://lvlltd.com/api/proof | jq '.confirmed,.recent_unlocks'
```

### JavaScript (browser or Node 18+)

```js
import { LvlAgentShop } from "https://lvlltd.com/sdk/agent-shop.mjs";

const shop = new LvlAgentShop();
const skill = "agent-x402-first-buy";

await shop.outline(skill);                    // free
const ch = await shop.challenge(skill);       // throws unless 402
// wallet: transfer ch.amountAtomic USDC to ch.payTo on Base
const unlock = await shop.unlock(skill, txHash);
console.log(Object.keys(unlock.sealed_pack?.files || {}));
const proof = await shop.proof();             // never invent counts
```

### Python 3

```python
import json, urllib.request

BASE = "https://lvlltd.com"
skill = "agent-x402-first-buy"

def get(path, method="GET", body=None, headers=None):
    req = urllib.request.Request(
        BASE + path, data=body, method=method,
        headers={"Accept": "application/json", **(headers or {})},
    )
    try:
        with urllib.request.urlopen(req) as r:
            return r.status, json.loads(r.read().decode())
    except urllib.error.HTTPError as e:
        raw = e.read().decode()
        try:
            return e.code, json.loads(raw)
        except Exception:
            return e.code, {"_raw": raw[:400]}

# free outline
print(get(f"/skills/{skill}/outline.json")[0])

# challenge
status, ch = get(f"/api/pay?skill={skill}")
assert status == 402, status
amount = ch.get("maxAmountRequired") or ch["accepts"][0]["maxAmountRequired"]
pay_to = ch.get("payTo") or ch["accepts"][0]["payTo"]
print("pay", amount, "to", pay_to)
# … transfer on Base, then:
tx = "0xYOUR_TX"
payload = json.dumps({"txHash": tx, "skill": skill}).encode()
st, unlock = get(
    "/api/pay", method="POST", body=payload,
    headers={"Content-Type": "application/json", "X-PAYMENT": payload.decode()},
)
print(st, unlock.get("ok"), list((unlock.get("sealed_pack") or {}).get("files", {}))[:5])
```

Full SDK surface: [SDK.md](./SDK.md) · module: https://lvlltd.com/sdk/agent-shop.mjs

---

## Challenge shape (GET → 402)

Agents MUST accept top-level fields **or** `accepts[0].*`.

| Field | Type | Notes |
|-------|------|-------|
| `maxAmountRequired` / `amount` | string int | Atomic USDC (6 decimals) |
| `payTo` | `0x` + 40 hex | EIP-55 or lower; compare case-insensitively |
| `network` | `"base"` | CAIP-2: `eip155:8453` |
| `asset` | `"USDC"` | |
| `assetContract` | address | USDC on Base |
| `skill` / `skill_id` | string | Catalog id |
| `error_code` | `"PAYMENT_REQUIRED"` | Challenge marker |
| `sample` / `outline` | URLs | Free eval |

Headers: `PAYMENT-REQUIRED` / `X-PAYMENT-REQUIRED` (base64 JSON of body when present).

Validate with: `GET /api/ready` → `checks[]` id `challenge_shape` + `challenge_live_402`.

---

## Unlock response (POST → 200)

| Field | Meaning |
|-------|---------|
| `ok` | `true` |
| `sealed_pack.files` | Map of path → text |
| `how_to_use` | Install hints |
| `license.token` | Optional portable re-redeem (when KV bound) |
| `payment.txHash` | Settled proof |
| `revenue_ledger` | Whether unlock was recorded to proof path |

---

## Error codes

| HTTP | `error_code` | When | Agent action |
|------|--------------|------|--------------|
| 400 | `SKILL_REQUIRED` | Missing skill id | Fix query/body |
| 402 | `PAYMENT_REQUIRED` | Challenge (GET) | Pay then POST |
| 402 | `PROOF_REQUIRED` | POST without txHash | Include `X-PAYMENT` |
| 402 | `PAYMENT_VERIFICATION_FAILED` | Tx not found / wrong amount / wrong payTo / wrong asset | Wait 2–5s, retry; verify BaseScan |
| 403 | `INVALID_LICENSE` | Bad license token | Use txHash path |
| 403 | `AP2_MANDATE_REJECTED` | Optional AP2 mandate failed | Fix mandate or omit |
| 404 | `not_found` | Unknown API path (HTML never under `/api/*`) | Check `/api/health` |
| 503 | `INTERNAL_ERROR` | Edge failure | Retry with backoff; `retry: true` when set |
| 503 | `PAYMENTS_DISABLED` | `X402_LIVE=0` | Wait / contact operator |

Example failure body:

```json
{
  "ok": false,
  "error": "payment_verification_failed",
  "error_code": "PAYMENT_VERIFICATION_FAILED",
  "message": "…",
  "retry": true,
  "tip": "Wait 2–5s after broadcast and retry; multi-RPC poll is automatic"
}
```

---

## Rate limits, retry, idempotency

| Topic | Policy |
|-------|--------|
| **Skill pay** | No hard public rate limit on canary path; abuse may be throttled at edge |
| **A2A JSON-RPC** | ~120 req / 60s per isolate (see `/api/a2a` patterns) |
| **Retry** | Transient 5xx / `PAYMENT_VERIFICATION_FAILED` with `retry: true` → exponential backoff (2s, 4s, 8s), max ~30s after broadcast |
| **Idempotency** | Unlock key = verified `(txHash, skill)`. Re-POST returns same sealed pack; does not double-charge |
| **Catalog** | ETag + `If-None-Match` supported on `/catalog.json` |

---

## Reorg handling

- Verification uses multi-RPC Base log scan for USDC Transfer to treasury.
- Prefer waiting for **≥1 confirmation** before first POST if your wallet UX is instant-broadcast only.
- If a reorg drops a tx: POST will fail verification; do **not** invent a refund — re-broadcast / re-pay only if explorer shows failure.
- Successful unlock rows in `/api/proof` are post-verification; treat as confirmed.

---

## Recover flow

If USDC landed on Base but you never received `sealed_pack.files`:

1. Human: https://lvlltd.com/recover/?skill=…&txHash=0x…
2. Agent: `POST /api/recover` or re-`POST /api/pay` with same `X-PAYMENT`
3. Idempotent — does not move funds again when verification succeeds

SDK: `shop.recover(skillId, txHash)`.

---

## Wallet & gas notes (humans + agent operators)

| Need | Detail |
|------|--------|
| Network | Base mainnet · chain id **8453** |
| Skill price | **USDC on Base** |
| Gas | Tiny **ETH on Base** (~$0.01–$0.10 typical). USDC alone is not enough. |
| payTo | Only treasury from challenge / [contracts.json](https://lvlltd.com/contracts.json) |
| Banned | Legacy `0xabEB…` — never pay skill unlocks there |
| Agent wallets | Any Base-capable EOA/AA that can ERC-20 transfer |

Human paths: [/first-unlock/](https://lvlltd.com/first-unlock/) · [/get-started/](https://lvlltd.com/get-started/)

MetaMask Base add (chain params): chainId `0x2105`, rpc `https://mainnet.base.org`, explorer `https://basescan.org`.

---

## Failure modes

| Mode | Symptom | Fix |
|------|---------|-----|
| Paid, no pack | USDC left wallet; no download | `/recover/` or re-POST txHash |
| Wrong network | Tx on Ethereum mainnet | Must be Base 8453 |
| Wrong asset | Paid ETH only | Need USDC transfer |
| Wrong payTo | Sent to random address | Use challenge `payTo` only |
| Case-sensitive payTo parse | Agent rejects EIP-55 | Compare lowercase |
| No gas | MetaMask fails send | Bridge tiny ETH to Base |
| 402 on POST | Missing proof | Include `X-PAYMENT` JSON |
| ready:false | Rails probe failed | Check `/api/ready` checks; do not buy until green |
| Inflated marketing | Third-party “GMV” | Ignore; use `/api/proof` only |
| Soft-404 HTML on `/api/*` | HTML error page | Treat as 404; never parse as unlock |

---

## Health & readiness

```
GET /api/health   → liveness
GET /api/ready    → catalog + challenge_shape + live 402 + sealed pack + money_safety
```

`ready:true` means rails can challenge and unlock — **not** that sales volume is high.

---

## Related

| Resource | URL |
|----------|-----|
| OpenAPI 3.1 | `/openapi.json` |
| Protocols | `/protocols.json` |
| SDK | `/sdk/agent-shop.mjs` · [SDK.md](./SDK.md) |
| Purchase short | [AGENT-PURCHASE.md](./AGENT-PURCHASE.md) |
| Quality | [CATALOG-QUALITY.md](./CATALOG-QUALITY.md) |
| Unlock detail | [X402-UNLOCK.md](./X402-UNLOCK.md) |
| How-to hub | `/how-to/` |
| Activity UI | `/activity/` |
| Status | `/status/` · `/status.json` |
