Skill: jefdiesel/clawphunks (version 1.0.0)

--- name: clawphunks version: 1.0.0 description: Mint and trade ClawPhunks NFTs. The first collection designed for AI agents. homepage: https://clawphunks.vercel.app user-invocable: true command-dispatch: tool metadata: { "openclaw": { "emoji": "👾", "requires": { "env": ["WALLET_PRIVATE_KEY"] }, "primaryEnv": "WALLET_PRIVATE_KEY", "homepage": "https://clawphunks.vercel.app" } } --- # ClawPhunks 10,000 unique left-facing pixel punks with OpenClaw red (#C83232) backgrounds. The first NFT collection designed for AI agents. ## Rarity & Value ClawPhunks mirror the original CryptoPunks rarity. Rare types command significant premiums. ### Types (5) | Type | Count | % | Rarity Rank | |------|-------|---|-------------| | Alien | 9 | 0.09% | ★★★★★ | | Ape | 24 | 0.24% | ★★★★☆ | | Zombie | 88 | 0.88% | ★★★☆☆ | | Female | 3,840 | 38.4% | ★★☆☆☆ | | Male | 6,039 | 60.39% | ★☆☆☆☆ | ### Legendary Aliens (9 total - 0.09%) The rarest type. Only 9 exist: | Token ID | Accessories | |----------|-------------| | #0635 | Bandana, Regular Shades | | #2890 | Cap | | #3100 | Headband | | #3443 | Earring, Cowboy Hat | | #5822 | Bandana | | #5905 | Do-rag, Small Shades | | #6089 | Earring, Knitted Cap | | #7523 | Earring, Knitted Cap, Medical Mask | | #7804 | Cap Forward, Pipe, Small Shades | ### Rare Apes (24 total - 0.24%) | Token ID | Accessories | |----------|-------------| | #0372 | Cap Forward | | #1021 | Cap, Eye Patch | | #2140 | Knitted Cap, Small Shades | | #2243 | Bandana, Nerd Glasses | | #2386 | Headband, Small Shades | | #2460 | Bandana, VR | | #2491 | Cap | | #2711 | Cap Forward, Earring | | #2924 | Hoodie | | #4156 | Bandana | | #4178 | Do-rag | | #4464 | Eye Mask, Vape, Do-rag | | #5217 | Gold Chain, Knitted Cap | | #5314 | Horned Rim Glasses, Do-rag | | #5577 | Cowboy Hat | | #5795 | Police Cap | | #6145 | Cigarette, Cap, Earring | | #6915 | Cap, Earring, Eye Patch | | #6965 | Fedora | | #7191 | Nerd Glasses, Knitted Cap | | #8219 | Knitted Cap | | #8498 | Top Hat, Regular Shades | | #9265 | Bandana, Big Shades | | #9280 | 3D Glasses, Cowboy Hat | ### Zombies (88 total - 0.88%) All 88 Zombie token IDs: | Token ID | Accessories | |----------|-------------| | #0117 | Messy Hair, Front Beard Dark | | #0987 | Wild Hair, Horned Rim Glasses | | #1119 | Shadow Beard, Do-rag, Eye Patch | | #1190 | Cigarette, Bandana, Handlebars, Earring | | #1374 | Big Shades, Earring, Mohawk Dark | | #1478 | Shadow Beard, Wild Hair | | #1526 | Cap, Gold Chain, Eye Patch | | #1658 | Stringy Hair | | #1748 | Front Beard, Frown, Knitted Cap | | #1886 | Messy Hair, Shadow Beard | | #1935 | Earring, Shaved Head | | #2066 | Knitted Cap | | #2132 | Normal Beard Black, Hoodie, Nerd Glasses | | #2249 | Bandana, Eye Patch | | #2306 | Cigarette, Mohawk Thin, Earring | | #2329 | Peak Spike, Earring | | #2338 | Mohawk Thin | | #2424 | Bandana, Frown, Earring | | #2484 | Wild Hair, Classic Shades | | #2560 | Front Beard, Earring, Headband, VR | | #2566 | Messy Hair, Normal Beard | | #2681 | Clown Eyes Blue, Cap | | #2708 | Bandana, Earring | | #2938 | Wild Hair | | #2967 | Mohawk Thin, Chinstrap | | #3211 | Goat, Headband | | #3328 | Cigarette, Messy Hair | | #3393 | Frown, Crazy Hair | | #3489 | Stringy Hair, Eye Patch | | #3493 | Peak Spike, Shadow Beard | | #3609 | Earring, Do-rag | | #3636 | Front Beard Dark, Earring, Top Hat | | #3831 | Vampire Hair, Big Shades, Medical Mask | | #4472 | Cigarette, Purple Hair | | #4513 | Beanie, Luxurious Beard, Earring | | #4559 | Stringy Hair, Earring | | #4747 | Clown Eyes Blue, Headband | | #4830 | Wild Hair, Classic Shades, Medical Mask | | #4850 | Purple Hair | | #4874 | Cigarette, Messy Hair, Clown Nose, Mustache, Earring (5 acc!) | | #5066 | Earring, Knitted Cap, Smile | | #5234 | Big Shades, Earring, Crazy Hair | | #5253 | Messy Hair, Mole | | #5299 | Cigarette, Handlebars, Earring, Mohawk Dark | | #5312 | Luxurious Beard, Knitted Cap | | #5336 | Police Cap | | #5412 | Nerd Glasses, Crazy Hair | | #5489 | Fedora | | #5573 | Luxurious Beard, Mohawk, 3D Glasses | | #5742 | Mohawk Dark | | #5761 | Bandana, Horned Rim Glasses | | #5944 | Mohawk | | #6275 | Shadow Beard, Mohawk Dark | | #6297 | Cigarette, Nerd Glasses, Top Hat | | #6304 | Crazy Hair, Regular Shades | | #6491 | Cap Forward, Shadow Beard, Earring | | #6515 | Cigarette, Wild Hair | | #6586 | Knitted Cap, Smile | | #6649 | Front Beard Dark, Crazy Hair | | #6704 | Cigarette, Earring, Rosy Cheeks | | #6784 | Cigarette, Bandana, Frown | | #7014 | Cigarette, Frumpy Hair | | #7121 | Frumpy Hair, Horned Rim Glasses | | #7127 | Bandana, Eye Mask, Earring | | #7252 | Chinstrap, Earring, Crazy Hair | | #7337 | Normal Beard Black, Peak Spike | | #7458 | Shadow Beard, Knitted Cap, Regular Shades | | #7660 | Smile, Do-rag | | #7756 | Shadow Beard, Horned Rim Glasses, Do-rag | | #7914 | Normal Beard Black, Knitted Cap | | #8127 | Headband | | #8307 | Stringy Hair, Mustache | | #8386 | Classic Shades, Crazy Hair | | #8472 | Mohawk Thin, Small Shades | | #8531 | Stringy Hair, Goat, Regular Shades | | #8553 | Front Beard Dark | | #8780 | Frumpy Hair, Shadow Beard | | #8857 | Wild Hair, 3D Glasses | | #8909 | Luxurious Beard, Police Cap, Regular Shades | | #8957 | Frumpy Hair, Luxurious Beard | | #9203 | Clown Nose, Cap Forward, Goat, Mole | | #9368 | Hoodie, Earring | | #9474 | Peak Spike | | #9804 | Stringy Hair, Shadow Beard, Smile, Small Shades | | #9838 | Peak Spike, Front Beard Dark, Earring | | #9909 | Cap | | #9955 | Shaved Head | | #9997 | Front Beard, Cap Forward | ### Rare Accessories (87 unique) | Accessory | Count | Rarity | |-----------|-------|--------| | Beanie | 44 | ★★★★★ | | Choker | 48 | ★★★★★ | | Pilot Helmet | 54 | ★★★★★ | | Tiara | 55 | ★★★★★ | | Orange Side | 68 | ★★★★☆ | | Buck Teeth | 78 | ★★★★☆ | | Welding Goggles | 86 | ★★★★☆ | | Top Hat | 115 | ★★★☆☆ | | Cowboy Hat | 142 | ★★★☆☆ | | Tassle Hat | 178 | ★★★☆☆ | | Fedora | 186 | ★★★☆☆ | | Police Cap | 203 | ★★☆☆☆ | | Clown Nose | 212 | ★★☆☆☆ | | Hoodie | 259 | ★★☆☆☆ | | Bandana | 481 | ★☆☆☆☆ | | Earring | 2,459 | Common | ### Accessory Count Rarity Phunks with more accessories are rarer: - 7 accessories: ~1 phunk - 6 accessories: ~5 phunks - 5 accessories: ~26 phunks - 4 accessories: ~227 phunks - 3 accessories: ~1,340 phunks - 2 accessories: ~3,254 phunks - 1 accessory: ~3,046 phunks - 0 accessories: ~8 phunks (also rare!) ## How It Works ### 1. Create a Wallet ```typescript import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts'; import { createWalletClient, http } from 'viem'; import { mainnet } from 'viem/chains'; const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); console.log('Address:', account.address); // Store privateKey securely! const walletClient = createWalletClient({ account, chain: mainnet, transport: http('https://eth.llamarpc.com'), }); ``` ### 2. Mint a Phunk **Endpoint:** `POST https://clawphunks.vercel.app/mint` **Payment:** $1.99 USDC on Base via x402 protocol **Request:** ```json { "recipient": "0xYourWalletAddress" } ``` **Response:** ```json { "success": true, "tokenId": 1234, "txHash": "0x...", "ethscriptionId": "0x...", "gasStipendWei": "13333333333333", "viewerUrl": "https://ethscriptions.com/ethscriptions/0x...", "nextSteps": { "trade": "Use escrow contract on L1", "list": "depositAndList(ethscriptionId, priceWei)", "buy": "buy(ethscriptionId) with msg.value = price" } } ``` You receive: - A random unminted ClawPhunk as an ethscription on Ethereum L1 - ~$0.03 ETH gas stipend for trading ### 3. Trade on L1 **Escrow Contract:** [REDACTED_SECRET] The escrow contract handles trustless trading using ESIP-2. #### List for Sale 1. Transfer your ethscription to the escrow contract 2. Call `depositAndList(bytes32 ethscriptionId, uint256 priceWei)` ```typescript import { encodeFunctionData } from 'viem'; // First, send ethscription to escrow (send tx to contract with ethscription data) // Then list it: const data = encodeFunctionData({ abi: ESCROW_ABI, functionName: 'depositAndList', args: [ethscriptionId, priceWei], }); await walletClient.sendTransaction({ to: [REDACTED_SECRET], data, }); ``` #### Buy a Listing ```typescript // Check listing const [active, seller, price] = await publicClient.readContract({ address: [REDACTED_SECRET], abi: ESCROW_ABI, functionName: 'getListing', args: [ethscriptionId], }); // Buy it await walletClient.writeContract({ address: [REDACTED_SECRET], abi: ESCROW_ABI, functionName: 'buy', args: [ethscriptionId], value: price, }); ``` #### Cancel Listing ```typescript await walletClient.writeContract({ address: [REDACTED_SECRET], abi: ESCROW_ABI, functionName: 'cancelAndWithdraw', args: [ethscriptionId], }); ``` ## API Reference ### GET /health Health check. ### GET /collection Returns collection info, mint stats, and agent instructions. ```json { "name": "ClawPhunks", "symbol": "CPHUNK", "totalSupply": 10000, "minted": 0, "available": 10000, "mintPrice": "1.99", "mintCurrency": "USDC", "chain": "ethereum", "escrowContract": [REDACTED_SECRET], "agentInstructions": { ... } } ``` ### POST /mint Mint a random phunk. Requires x402 payment ($1.99 USDC on Base). ## Escrow Contract ABI ```json [ { "name": "depositAndList", "type": "function", "inputs": [ { "name": "ethscriptionId", "type": "bytes32" }, { "name": "price", "type": "uint256" } ] }, { "name": "buy", "type": "function", "stateMutability": "payable", "inputs": [ { "name": "ethscriptionId", "type": "bytes32" } ] }, { "name": "cancelAndWithdraw", "type": "function", "inputs": [ { "name": "ethscriptionId", "type": "bytes32" } ] }, { "name": "getListing", "type": "function", "stateMutability": "view", "inputs": [ { "name": "ethscriptionId", "type": "bytes32" } ], "outputs": [ { "name": "active", "type": "bool" }, { "name": "seller", "type": "address" }, { "name": "price", "type": "uint256" } ] }, { "name": "updatePrice", "type": "function", "inputs": [ { "name": "ethscriptionId", "type": "bytes32" }, { "name": "newPrice", "type": "uint256" } ] } ] ``` ## Traits Each ClawPhunk has embedded traits in the ethscription metadata: ```json { "attributes": [ { "trait_type": "Type", "value": "Female" }, { "trait_type": "Accessory", "value": "Mohawk" }, { "trait_type": "Accessory", "value": "3D Glasses" }, { "trait_type": "Accessory", "value": "Earring" } ] } ``` ## Links - **Marketplace:** https://clawphunks.vercel.app - **Skills/Docs:** https://clawphunks.vercel.app/skills - **Mint API:** https://clawphunks.vercel.app - **Escrow Contract:** https://etherscan.io/address/0x3e67d49716e50a8b1c71b8dEa0e31755305733fd - **Collection:** https://ethscriptions.com/collections/0xb432d8c446afefb98651e05c8eee22a8617bd7a0b239dfab70e1fb3a02aa9e8a ## x402 Payment ClawPhunks uses the x402 protocol for payments: 1. Call `/mint` without payment → get `402 Payment Required` with payment details 2. Pay $1.99 USDC on Base 3. Retry with payment proof in header 4. Receive your phunk If using Coinbase AgentKit, x402 is handled automatically.

path: README.md
content: # ClawPhunks

10,000 left-facing pixel punks with OpenClaw red backgrounds. The first NFT collection for AI agents.

## Setup

```bash
npm install
```

## Generate Art

Downloads the phunks sprite sheet, slices into 10k PNGs with #C83232 backgrounds:

```bash
npm run generate-art
```

Output goes to `/output` directory.

## Run Server

```bash
cp .env.example .env
# Edit .env with your keys
npm run dev
```

## Deploy

```bash
vercel
```

## API

- `GET /collection` - Collection info
- `GET /marketplace` - Active listings
- `POST /mint` - Mint a phunk (x402)
- `POST /buy/:id` - Buy a listing (x402)
- `POST /list` - List for sale
- `POST /delist` - Remove listing

## ClawHub Skill

Copy `SKILL.md` to your ClawHub skills directory or publish to the registry.


path: STATUS.md
content: # ClawPhunks Status - March 4, 2026

## What Works

### Minting (x402)
- **API**: `POST https://clawphunks.vercel.app/mint` with `{"recipient": "0x..."}`
- **Payment**: $1.99 USDC on Base via x402 (EIP-3009 signature)
- **Self-hosted facilitator**: Executes USDC transfer on Base, pays gas
- **Output**: Ethscription on L1 + 0.00005 ETH gas stipend (~$0.12)

### Collection Display
- **Page**: https://chainhost.online/clawphunks
- **Item pages**: https://chainhost.online/clawphunks/[id]
- **Skills page**: https://chainhost.online/clawphunks/skills (complete mint + list code)
- **Data source**: Ethscriptions AppChain RPC (`mainnet.ethscriptions.com`)
- **Contract**: [REDACTED_SECRET]

### Images
- All 10k phunks facing LEFT (like CryptoPhunks) - FIXED
- Stored in Supabase as ESIP-6 data URIs with traits
- #9590 was right-facing before fix (minted before regeneration)

### Trading
- **Escrow Contract**: [REDACTED_SECRET] (L1)
- **Functions**:
  - `depositAndList(bytes32 ethscriptionId, uint256 priceWei)`
  - `buy(bytes32 ethscriptionId)` payable
  - `cancelAndWithdraw(bytes32 ethscriptionId)`
  - `getListing(bytes32 ethscriptionId)` view

## Minted Items
- **#0**: Collection creator token
- **#9590**: First mint, listed at 0.69 ETH
- **#5031**: Second test mint
- **Total on-chain**: 3

## MCP Server
- **NPM**: `clawphunks-mcp@1.2.0`
- **Tools**:
  - `get_collection` - Collection info and stats
  - `mint_phunk` - Mint with x402 (returns 402 with payment requirements)
  - `get_mint_code` - Complete TypeScript code for x402 minting
  - `get_rarity` - Rarity info and rare token IDs
  - `get_trading_instructions` - Complete mint + list code

## Key Addresses
- **Signer/Deployer**: [REDACTED_SECRET]
- **USDC on Base**: [REDACTED_SECRET]
- **Payment Recipient**: [REDACTED_SECRET]
- **Escrow Contract**: [REDACTED_SECRET]
- **Collection Contract**: [REDACTED_SECRET]

## Environment Variables (Vercel - clawphunks)
- `SIGNER_PRIVATE_KEY` - L1 minting wallet
- `ETHEREUM_RPC_URL` - L1 RPC
- `BASE_RPC_URL` - Base RPC (for facilitator)
- `SUPABASE_URL` / `SUPABASE_SERVICE_KEY` - Item database
- `PAYMENT_RECIPIENT` - Where USDC goes
- `FACILITATOR_URL` - Self-hosted at same domain
- `GAS_STIPEND_WEI` - `50000000000000` (0.00005 ETH)

## Files
- `/Users/jef/clawphunks/src/server.ts` - Main API
- `/Users/jef/clawphunks/src/facilitator.ts` - x402 facilitator
- `/Users/jef/clawphunks/src/mint.ts` - L1 minting
- `/Users/jef/clawphunks/src/db.ts` - Supabase
- `/Users/jef/clawphunks/mcp/src/index.ts` - MCP server
- `/Users/jef/chainhost/src/app/clawphunks/page.tsx` - Collection frontend
- `/Users/jef/chainhost/src/app/clawphunks/[id]/page.tsx` - Item detail page
- `/Users/jef/chainhost/src/app/clawphunks/skills/page.tsx` - Skills/docs page

## TODO
- [ ] Test browser mint end-to-end
- [ ] Create new test wallet (old one exposed)


path: integrations/agentkit/README.md
content: # ClawPhunks AgentKit Action

Mint and trade ClawPhunks NFTs with Coinbase AgentKit. x402 payments are handled automatically.

## Install

```bash
npm install @coinbase/agentkit
```

## Usage

```typescript
import { AgentKit } from '@coinbase/agentkit';
import { clawphunksActions } from './clawphunks_action';

const agent = new AgentKit({
  actions: [...clawphunksActions],
  // AgentKit handles x402 USDC payments on Base automatically
});

// Mint a phunk
await agent.execute('clawphunks_mint', {
  recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f...'
});

// Check collection
await agent.execute('clawphunks_collection', {});
```

## Actions

| Action | Description |
|--------|-------------|
| `clawphunks_mint` | Mint a random phunk ($1.99 USDC on Base) |
| `clawphunks_collection` | Get mint stats and rarity info |
| `clawphunks_skills` | Get full trading scripts for L1 |

## Payment

AgentKit's x402 middleware handles USDC payments on Base automatically. Ensure your agent wallet has sufficient USDC balance.


path: integrations/agentkit/clawphunks_action.ts
content: /**
 * ClawPhunks AgentKit Action
 *
 * Mint and trade ClawPhunks NFTs with Coinbase AgentKit.
 * x402 payments are handled automatically by AgentKit.
 *
 * npm install @coinbase/agentkit
 *
 * Usage:
 *   import { clawphunksMintAction, clawphunksCollectionAction } from './clawphunks_action';
 *
 *   const agent = new AgentKit({
 *     actions: [clawphunksMintAction, clawphunksCollectionAction]
 *   });
 */

import { z } from 'zod';
import { ActionDefinition } from '@coinbase/agentkit';

const MINT_URL = 'https://clawphunks.vercel.app/mint';
const COLLECTION_URL = 'https://clawphunks.vercel.app/collection';
const SKILLS_URL = 'https://chainhost.online/clawphunks/skills';

/**
 * Mint a ClawPhunk NFT
 *
 * Costs $1.99 USDC on Base via x402 (AgentKit handles payment automatically).
 * Returns ethscription on Ethereum L1 + gas stipend for trading.
 */
export const clawphunksMintAction: ActionDefinition = {
  name: 'clawphunks_mint',
  description: 'Mint a random ClawPhunk NFT. Costs $1.99 USDC on Base. Returns ethscription on Ethereum L1 plus gas stipend.',
  schema: z.object({
    recipient: z.string().describe('Ethereum address to receive the ClawPhunk'),
  }),
  handler: async ({ recipient }, { wallet }) => {
    // AgentKit's x402 middleware handles 402 responses automatically
    const response = await fetch(MINT_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ recipient }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Mint failed: ${error}`);
    }

    const result = await response.json();

    return {
      success: true,
      tokenId: result.tokenId,
      txHash: result.txHash,
      ethscriptionId: result.ethscriptionId,
      gasStipendWei: result.gasStipendWei,
      message: `Minted ClawPhunk #${result.tokenId} to ${recipient}`,
    };
  },
};

/**
 * Get ClawPhunks collection info
 */
export const clawphunksCollectionAction: ActionDefinition = {
  name: 'clawphunks_collection',
  description: 'Get ClawPhunks collection stats, rarity data, and mint availability.',
  schema: z.object({}),
  handler: async () => {
    const response = await fetch(COLLECTION_URL);

    if (!response.ok) {
      throw new Error(`Failed to fetch collection: ${response.status}`);
    }

    const data = await response.json();

    return {
      name: data.name,
      minted: data.minted,
      available: data.available,
      totalSupply: data.totalSupply,
      mintPrice: `${data.mintPrice} ${data.mintCurrency}`,
      escrowContract: data.escrowContract,
      rarity: {
        aliens: '9 (0.09%)',
        apes: '24 (0.24%)',
        zombies: '88 (0.88%)',
      },
    };
  },
};

/**
 * Get ClawPhunks trading scripts
 */
export const clawphunksSkillsAction: ActionDefinition = {
  name: 'clawphunks_skills',
  description: 'Get complete executable scripts for listing, buying, transferring, and rescuing ClawPhunks on L1.',
  schema: z.object({}),
  handler: async () => {
    const response = await fetch(SKILLS_URL);

    if (!response.ok) {
      throw new Error(`Failed to fetch skills: ${response.status}`);
    }

    return await response.json();
  },
};

/**
 * All ClawPhunks actions
 */
export const clawphunksActions = [
  clawphunksMintAction,
  clawphunksCollectionAction,
  clawphunksSkillsAction,
];

export default clawphunksActions;


path: integrations/langchain/README.md
content: # ClawPhunks LangChain Tool

Mint and trade ClawPhunks NFTs from any LangChain agent.

## Install

```bash
pip install langchain requests
```

## Usage

```python
from langchain.agents import initialize_agent, AgentType
from langchain.llms import OpenAI
from clawphunks_tool import get_clawphunks_tools

llm = OpenAI(temperature=0)
tools = get_clawphunks_tools()

agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# Check collection
agent.run("How many ClawPhunks are available?")

# Mint (requires x402 payment handling)
agent.run("Mint me a ClawPhunk to 0x742d35Cc6634C0532925a3b844Bc9e7595f...")
```

## Tools

| Tool | Description |
|------|-------------|
| `clawphunks_mint` | Mint a random phunk ($1.99 USDC) |
| `clawphunks_collection` | Get mint stats and rarity info |
| `clawphunks_skills` | Get full trading scripts |

## x402 Payment

Minting returns a 402 response with payment details. You need x402 middleware to handle payment automatically, or process the payment manually and retry.


path: integrations/langchain/clawphunks_tool.py
content: """
ClawPhunks LangChain Tool

Mint and trade ClawPhunks NFTs from any LangChain agent.

pip install langchain requests

Usage:
    from clawphunks_tool import ClawPhunksMintTool, ClawPhunksCollectionTool

    tools = [ClawPhunksMintTool(), ClawPhunksCollectionTool()]
    agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)
    agent.run("Mint me a ClawPhunk to 0x...")
"""

from typing import Optional, Type
from langchain.tools import BaseTool
from pydantic import BaseModel, Field
import requests


SKILLS_URL = "https://chainhost.online/clawphunks/skills"
MINT_URL = "https://clawphunks.vercel.app/mint"
COLLECTION_URL = "https://clawphunks.vercel.app/collection"


class MintInput(BaseModel):
    recipient: str = Field(description="Ethereum address to receive the ClawPhunk")


class ClawPhunksMintTool(BaseTool):
    """Tool for minting ClawPhunks NFTs."""

    name: str = "clawphunks_mint"
    description: str = (
        "Mint a random ClawPhunk NFT. Costs $1.99 USDC on Base via x402 protocol. "
        "Returns ethscription on Ethereum L1 plus gas stipend for trading. "
        "Input: Ethereum address to receive the phunk."
    )
    args_schema: Type[BaseModel] = MintInput

    def _run(self, recipient: str) -> str:
        """Mint a ClawPhunk to the specified address."""
        try:
            # First call gets 402 with payment requirements
            response = requests.post(
                MINT_URL,
                json={"recipient": recipient},
                headers={"Content-Type": "application/json"}
            )

            if response.status_code == 402:
                payment_info = response.json()
                return (
                    f"Payment required: $1.99 USDC on Base. "
                    f"Payment details: {payment_info}. "
                    f"Use x402 protocol to complete payment and retry."
                )

            if response.status_code == 200:
                result = response.json()
                return (
                    f"Minted ClawPhunk #{result.get('tokenId')} "
                    f"to {recipient}. "
                    f"TX: {result.get('txHash')}. "
                    f"Ethscription: {result.get('ethscriptionId')}. "
                    f"Gas stipend: {result.get('gasStipendWei')} wei."
                )

            return f"Error: {response.status_code} - {response.text}"

        except Exception as e:
            return f"Error minting: {str(e)}"

    async def _arun(self, recipient: str) -> str:
        """Async version."""
        return self._run(recipient)


class ClawPhunksCollectionTool(BaseTool):
    """Tool for getting ClawPhunks collection info."""

    name: str = "clawphunks_collection"
    description: str = (
        "Get ClawPhunks collection info including mint stats, rarity data, "
        "and available supply. No input required."
    )

    def _run(self, query: str = "") -> str:
        """Get collection info."""
        try:
            response = requests.get(COLLECTION_URL)
            if response.status_code == 200:
                data = response.json()
                return (
                    f"ClawPhunks: {data.get('minted', 0)}/{data.get('totalSupply', 10000)} minted. "
                    f"{data.get('available', 10000)} available. "
                    f"Price: {data.get('mintPrice', '1.99')} {data.get('mintCurrency', 'USDC')}. "
                    f"Rarity: 9 Aliens (0.09%), 24 Apes (0.24%), 88 Zombies (0.88%)."
                )
            return f"Error: {response.status_code}"
        except Exception as e:
            return f"Error: {str(e)}"

    async def _arun(self, query: str = "") -> str:
        return self._run(query)


class ClawPhunksSkillsTool(BaseTool):
    """Tool for getting full ClawPhunks trading scripts."""

    name: str = "clawphunks_skills"
    description: str = (
        "Get complete executable scripts for minting, listing, buying, "
        "transferring, and rescuing ClawPhunks. Returns Node.js/TypeScript code."
    )

    def _run(self, query: str = "") -> str:
        """Get skills/scripts."""
        try:
            response = requests.get(SKILLS_URL)
            if response.status_code == 200:
                return response.text
            return f"Error: {response.status_code}"
        except Exception as e:
            return f"Error: {str(e)}"

    async def _arun(self, query: str = "") -> str:
        return self._run(query)


# Convenience function
def get_clawphunks_tools():
    """Get all ClawPhunks tools for LangChain."""
    return [
        ClawPhunksMintTool(),
        ClawPhunksCollectionTool(),
        ClawPhunksSkillsTool(),
    ]


path: mcp/package-lock.json
content: {
  "name": "@clawphunks/mcp-server",
  "version": "1.2.1",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "@clawphunks/mcp-server",
      "version": "1.2.1",
      "dependencies": {
        "@modelcontextprotocol/sdk": "^1.0.0"
      },
      "bin": {
        "clawphunks-mcp": "dist/index.js"
      },
      "devDependencies": {
        "@types/node": "^20.11.0",
        "typescript": "^5.3.3"
      }
    },
    "node_modules/@hono/node-server": {
      "version": "1.19.10",
      "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.10.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18.14.1"
      },
      "peerDependencies": {
        "hono": "^4"
      }
    },
    "node_modules/@modelcontextprotocol/sdk": {
      "version": "1.27.1",
      "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@hono/node-server": "^1.19.9",
        "ajv": "^8.17.1",
        "ajv-formats": "^3.0.1",
        "content-type": "^1.0.5",
        "cors": "^2.8.5",
        "cross-spawn": "^7.0.5",
        "eventsource": "^3.0.2",
        "eventsource-parser": "^3.0.0",
        "express": "^5.2.1",
        "express-rate-limit": "^8.2.1",
        "hono": "^4.11.4",
        "jose": "^6.1.3",
        "json-schema-typed": "^8.0.2",
        "pkce-challenge": "^5.0.0",
        "raw-body": "^3.0.0",
        "zod": "^3.25 || ^4.0",
        "zod-to-json-schema": "^3.25.1"
      },
      "engines": {
        "node": ">=18"
      },
      "peerDependencies": {
        "@cfworker/json-schema": "^4.1.1",
        "zod": "^3.25 || ^4.0"
      },
      "peerDependenciesMeta": {
        "@cfworker/json-schema": {
          "optional": true
        },
        "zod": {
          "optional": false
        }
      }
    },
    "node_modules/@types/node": {
      "version": "20.19.35",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "undici-types": "~6.21.0"
      }
    },
    "node_modules/accepts": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mime-types": "^3.0.0",
        "negotiator": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/ajv": {
      "version": "8.18.0",
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "fast-deep-equal": "^3.1.3",
        "fast-uri": "^3.0.1",
        "json-schema-traverse": "^1.0.0",
        "require-from-string": "^2.0.2"
      },
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/epoberezkin"
      }
    },
    "node_modules/ajv-formats": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ajv": "^8.0.0"
      },
      "peerDependencies": {
        "ajv": "^8.0.0"
      },
      "peerDependenciesMeta": {
        "ajv": {
          "optional": true
        }
      }
    },
    "node_modules/body-parser": {
      "version": "2.2.2",
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "bytes": "^3.1.2",
        "content-type": "^1.0.5",
        "debug": "^4.4.3",
        "http-errors": "^2.0.0",
        "iconv-lite": "^0.7.0",
        "on-finished": "^2.4.1",
        "qs": "^6.14.1",
        "raw-body": "^3.0.1",
        "type-is": "^2.0.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/bytes": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/call-bound": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "get-intrinsic": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/content-disposition": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/content-type": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/cookie": {
      "version": "0.7.2",
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/cookie-signature": {
      "version": "1.2.2",
      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=6.6.0"
      }
    },
    "node_modules/cors": {
      "version": "2.8.6",
      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "object-assign": "^4",
        "vary": "^1"
      },
      "engines": {
        "node": ">= 0.10"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/cross-spawn": {
      "version": "7.0.6",
      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "path-key": "^3.1.0",
        "shebang-command": "^2.0.0",
        "which": "^2.0.1"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/debug": {
      "version": "4.4.3",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/depd": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/ee-first": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/encodeurl": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-object-atoms": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/escape-html": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/etag": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/eventsource": {
      "version": "3.0.7",
      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "eventsource-parser": "^3.0.1"
      },
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/eventsource-parser": {
      "version": "3.0.6",
      "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/express": {
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "accepts": "^2.0.0",
        "body-parser": "^2.2.1",
        "content-disposition": "^1.0.0",
        "content-type": "^1.0.5",
        "cookie": "^0.7.1",
        "cookie-signature": "^1.2.1",
        "debug": "^4.4.0",
        "depd": "^2.0.0",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "etag": "^1.8.1",
        "finalhandler": "^2.1.0",
        "fresh": "^2.0.0",
        "http-errors": "^2.0.0",
        "merge-descriptors": "^2.0.0",
        "mime-types": "^3.0.0",
        "on-finished": "^2.4.1",
        "once": "^1.4.0",
        "parseurl": "^1.3.3",
        "proxy-addr": "^2.0.7",
        "qs": "^6.14.0",
        "range-parser": "^1.2.1",
        "router": "^2.2.0",
        "send": "^1.1.0",
        "serve-static": "^2.2.0",
        "statuses": "^2.0.1",
        "type-is": "^2.0.1",
        "vary": "^1.1.2"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/express-rate-limit": {
      "version": "8.2.1",
      "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ip-address": "10.0.1"
      },
      "engines": {
        "node": ">= 16"
      },
      "funding": {
        "url": "https://github.com/sponsors/express-rate-limit"
      },
      "peerDependencies": {
        "express": ">= 4.11"
      }
    },
    "node_modules/fast-deep-equal": {
      "version": "3.1.3",
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/fast-uri": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/fastify"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/fastify"
        }
      ],
      "license": "BSD-3-Clause"
    },
    "node_modules/finalhandler": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.0",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "on-finished": "^2.4.1",
        "parseurl": "^1.3.3",
        "statuses": "^2.0.1"
      },
      "engines": {
        "node": ">= 18.0.0"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/forwarded": {
      "version": "0.2.0",
      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/fresh": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/function-bind": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-intrinsic": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-symbols": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/hasown": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/hono": {
      "version": "4.12.5",
      "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "peer": true,
      "engines": {
        "node": ">=16.9.0"
      }
    },
    "node_modules/http-errors": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "depd": "~2.0.0",
        "inherits": "~2.0.4",
        "setprototypeof": "~1.2.0",
        "statuses": "~2.0.2",
        "toidentifier": "~1.0.1"
      },
      "engines": {
        "node": ">= 0.8"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/iconv-lite": {
      "version": "0.7.2",
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "safer-buffer": ">= 2.1.2 < 3.0.0"
      },
      "engines": {
        "node": ">=0.10.0"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/ip-address": {
      "version": "10.0.1",
      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 12"
      }
    },
    "node_modules/ipaddr.js": {
      "version": "1.9.1",
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/is-promise": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/isexe": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/jose": {
      "version": "6.1.3",
      "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/panva"
      }
    },
    "node_modules/json-schema-traverse": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/json-schema-typed": {
      "version": "8.0.2",
      "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause"
    },
    "node_modules/math-intrinsics": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/media-typer": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/merge-descriptors": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/mime-db": {
      "version": "1.54.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mime-db": "^1.54.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/negotiator": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/object-assign": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/object-inspect": {
      "version": "1.13.4",
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/on-finished": {
      "version": "2.4.1",
      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ee-first": "1.1.1"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/once": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "wrappy": "1"
      }
    },
    "node_modules/parseurl": {
      "version": "1.3.3",
      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/path-key": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/path-to-regexp": {
      "version": "8.3.0",
      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/pkce-challenge": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=16.20.0"
      }
    },
    "node_modules/proxy-addr": {
      "version": "2.0.7",
      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "forwarded": "0.2.0",
        "ipaddr.js": "1.9.1"
      },
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/qs": {
      "version": "6.15.0",
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause",
      "dependencies": {
        "side-channel": "^1.1.0"
      },
      "engines": {
        "node": ">=0.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/range-parser": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/raw-body": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "bytes": "~3.1.2",
        "http-errors": "~2.0.1",
        "iconv-lite": "~0.7.0",
        "unpipe": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/require-from-string": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/router": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.0",
        "depd": "^2.0.0",
        "is-promise": "^4.0.0",
        "parseurl": "^1.3.3",
        "path-to-regexp": "^8.0.0"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/safer-buffer": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/send": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "debug": "^4.4.3",
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "etag": "^1.8.1",
        "fresh": "^2.0.0",
        "http-errors": "^2.0.1",
        "mime-types": "^3.0.2",
        "ms": "^2.1.3",
        "on-finished": "^2.4.1",
        "range-parser": "^1.2.1",
        "statuses": "^2.0.2"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/serve-static": {
      "version": "2.2.1",
      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "encodeurl": "^2.0.0",
        "escape-html": "^1.0.3",
        "parseurl": "^1.3.3",
        "send": "^1.2.0"
      },
      "engines": {
        "node": ">= 18"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/setprototypeof": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/shebang-command": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "shebang-regex": "^3.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/shebang-regex": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/side-channel": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "object-inspect": "^1.13.3",
        "side-channel-list": "^1.0.0",
        "side-channel-map": "^1.0.1",
        "side-channel-weakmap": "^1.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-list": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "object-inspect": "^1.13.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-map": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bound": "^1.0.2",
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.5",
        "object-inspect": "^1.13.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-weakmap": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bound": "^1.0.2",
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.5",
        "object-inspect": "^1.13.3",
        "side-channel-map": "^1.0.1"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/statuses": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/toidentifier": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.6"
      }
    },
    "node_modules/type-is": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "content-type": "^1.0.5",
        "media-typer": "^1.1.0",
        "mime-types": "^3.0.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/typescript": {
      "version": "5.9.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "Apache-2.0",
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/undici-types": {
      "version": "6.21.0",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/unpipe": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/vary": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/which": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "isexe": "^2.0.0"
      },
      "bin": {
        "node-which": "bin/node-which"
      },
      "engines": {
        "node": ">= 8"
      }
    },
    "node_modules/wrappy": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/zod": {
      "version": "4.3.6",
      "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "peer": true,
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
    },
    "node_modules/zod-to-json-schema": {
      "version": "3.25.1",
      "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "peerDependencies": {
        "zod": "^3.25 || ^4"
      }
    }
  }
}


path: mcp/package.json
content: {
  "name": "clawphunks-mcp",
  "version": "1.2.1",
  "mcpName": "io.github.jefdiesel/clawphunks",
  "description": "MCP server for ClawPhunks NFT minting and trading",
  "repository": {
    "type": "git",
    "url": "https://github.com/jefdiesel/clawphunks.git"
  },
  "type": "module",
  "main": "dist/index.js",
  "bin": {
    "clawphunks-mcp": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "keywords": [
    "mcp",
    "nft",
    "ethereum",
    "ethscription",
    "clawphunks",
    "x402"
  ],
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.11.0",
    "typescript": "^5.3.3"
  }
}


path: mcp/server.json
content: {
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
  "name": "io.github.jefdiesel/clawphunks",
  "description": "Mint and trade ClawPhunks NFTs - 10k pixel punks for AI agents. x402 payment, ethscriptions on L1.",
  "version": "1.0.1",
  "repository": {
    "url": "https://github.com/jefdiesel/clawphunks",
    "source": "github"
  },
  "packages": [
    {
      "registryType": "npm",
      "identifier": "clawphunks-mcp",
      "version": "1.1.0",
      "transport": {
        "type": "stdio"
      }
    }
  ],
  "tools": [
    {
      "name": "get_collection",
      "description": "Get ClawPhunks collection info including mint stats, rarity data, and trading instructions"
    },
    {
      "name": "mint_phunk",
      "description": "Mint a random ClawPhunk NFT ($1.99 USDC via x402). May get rare Alien (0.09%), Ape (0.24%), or Zombie (0.88%)"
    },
    {
      "name": "get_rarity",
      "description": "Get full rarity information including all rare token IDs for Aliens, Apes, and Zombies"
    },
    {
      "name": "get_trading_instructions",
      "description": "Get escrow contract ABI and trading instructions for L1 marketplace"
    }
  ]
}


path: mcp/src/index.ts
content: #!/usr/bin/env node
/**
 * ClawPhunks MCP Server
 *
 * Exposes ClawPhunks NFT minting and trading as MCP tools.
 */

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

const API_BASE = 'https://clawphunks.vercel.app';
const ESCROW_CONTRACT = [REDACTED_SECRET];
const USDC_BASE = [REDACTED_SECRET];

const server = new Server(
  {
    name: 'clawphunks',
    version: '1.1.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'get_collection',
        description: 'Get ClawPhunks collection info including mint stats, rarity data, and trading instructions. Call this first to understand the collection.',
        inputSchema: {
          type: 'object',
          properties: {},
          required: [],
        },
      },
      {
        name: 'mint_phunk',
        description: 'Mint a random ClawPhunk NFT. Costs $1.99 USDC on Base via x402 protocol. Returns ethscription on Ethereum L1 plus gas stipend for trading. You may get a rare Alien (0.09%), Ape (0.24%), or Zombie (0.88%)!',
        inputSchema: {
          type: 'object',
          properties: {
            recipient: {
              type: 'string',
              description: 'Ethereum address to receive the ClawPhunk (0x...)',
            },
          },
          required: ['recipient'],
        },
      },
      {
        name: 'get_mint_code',
        description: 'Get complete TypeScript code for minting a ClawPhunk with x402 payment. Copy and run this code.',
        inputSchema: {
          type: 'object',
          properties: {},
          required: [],
        },
      },
      {
        name: 'get_rarity',
        description: 'Get full rarity information for ClawPhunks including all rare token IDs. Use this to understand which phunks are valuable.',
        inputSchema: {
          type: 'object',
          properties: {},
          required: [],
        },
      },
      {
        name: 'get_trading_instructions',
        description: 'Get complete code for trading ClawPhunks on the L1 escrow contract. Includes mint+list script.',
        inputSchema: {
          type: 'object',
          properties: {},
          required: [],
        },
      },
    ],
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case 'get_collection': {
      const res = await fetch(`${API_BASE}/collection`);
      const data = await res.json();
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(data, null, 2),
          },
        ],
      };
    }

    case 'mint_phunk': {
      const recipient = (args as { recipient: string }).recipient;

      if (!recipient || !/^0x[0-9a-fA-F]{40}$/.test(recipient)) {
        return {
          content: [
            {
              type: 'text',
              text: 'Error: Invalid recipient address. Must be a valid Ethereum address (0x...).',
            },
          ],
          isError: true,
        };
      }

      const res = await fetch(`${API_BASE}/mint`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ recipient }),
      });

      if (res.status === 402) {
        const paymentInfo = await res.json();
        return {
          content: [
            {
              type: 'text',
              text: `Payment required: $1.99 USDC on Base via x402 protocol.\n\nPayment details:\n${JSON.stringify(paymentInfo, null, 2)}\n\nUse get_mint_code tool for complete working code, or if using Coinbase AgentKit, x402 is handled automatically.`,
            },
          ],
        };
      }

      const data = await res.json();

      if (!res.ok) {
        return {
          content: [
            {
              type: 'text',
              text: `Mint failed: ${data.error}`,
            },
          ],
          isError: true,
        };
      }

      // Check rarity
      const rareTypes = {
        aliens: [635, 2890, 3100, 3443, 5822, 5905, 6089, 7523, 7804],
        apes: [372, 1021, 2140, 2243, 2386, 2460, 2491, 2711, 2924, 4156, 4178, 4464, 5217, 5314, 5577, 5795, 6145, 6915, 6965, 7191, 8219, 8498, 9265, 9280],
        zombies: [117, 987, 1119, 1190, 1374, 1478, 1526, 1658, 1748, 1886, 1935, 2066, 2132, 2249, 2306, 2329, 2338, 2424, 2484, 2560, 2566, 2681, 2708, 2938, 2967, 3211, 3328, 3393, 3489, 3493, 3609, 3636, 3831, 4472, 4513, 4559, 4747, 4830, 4850, 4874, 5066, 5234, 5253, 5299, 5312, 5336, 5412, 5489, 5573, 5742, 5761, 5944, 6275, 6297, 6304, 6491, 6515, 6586, 6649, 6704, 6784, 7014, 7121, 7127, 7252, 7337, 7458, 7660, 7756, 7914, 8127, 8307, 8386, 8472, 8531, 8553, 8780, 8857, 8909, 8957, 9203, 9368, 9474, 9804, 9838, 9909, 9955, 9997],
      };

      let rarityNote = '';
      if (rareTypes.aliens.includes(data.tokenId)) {
        rarityNote = '\n\nLEGENDARY! You minted an ALIEN - only 9 exist (0.09%)! Extremely valuable!';
      } else if (rareTypes.apes.includes(data.tokenId)) {
        rarityNote = '\n\nRARE! You minted an APE - only 24 exist (0.24%)! Very valuable!';
      } else if (rareTypes.zombies.includes(data.tokenId)) {
        rarityNote = '\n\nUNCOMMON! You minted a ZOMBIE - only 88 exist (0.88%)!';
      }

      return {
        content: [
          {
            type: 'text',
            text: `Successfully minted ClawPhunk #${data.tokenId}!${rarityNote}\n\nTransaction: ${data.txHash}\nEthscription ID: ${data.ethscriptionId}\nViewer: ${data.viewerUrl}\nGas stipend: ${data.gasStipendWei} wei (~$0.12 for listing)\n\nTo list for sale, use get_trading_instructions tool.`,
          },
        ],
      };
    }

    case 'get_mint_code': {
      const code = `
// IMPORTANT: Wallet Setup
// 1. If you have an existing EVM wallet, use that private key
// 2. If generating a new wallet, IMMEDIATELY save the private key:
//    - Create .env file with: AGENT_PRIVATE_KEY=0x...
//    - Create .gitignore with: .env
//    - Do this BEFORE minting to avoid losing access to your NFT
// 3. Fund wallet with $2+ USDC on Base network

import { createWalletClient, http, keccak256, encodePacked } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import { generatePrivateKey } from 'viem/accounts'; // For new wallet
import * as fs from 'fs';

// Generate new wallet if needed (save this immediately!)
// const newKey = generatePrivateKey();
// fs.writeFileSync('.env', \`AGENT_PRIVATE_KEY=\${newKey}\\n\`);
// fs.writeFileSync('.gitignore', '.env\\n');

const MINT_API = 'https://clawphunks.vercel.app/mint';
const USDC_BASE = '${USDC_BASE}';

async function mintClawPhunk(privateKey, recipient) {
  const account = privateKeyToAccount(privateKey);
  const walletClient = createWalletClient({
    account,
    chain: base,
    transport: http('https://mainnet.base.org'),
  });

  // Step 1: Get payment requirements (402 response)
  const reqRes = await fetch(MINT_API, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ recipient }),
  });
  const paymentReqs = await reqRes.json();
  const accept = paymentReqs.accepts[0];

  // Step 2: Sign EIP-3009 TransferWithAuthorization
  const nonce = keccak256(encodePacked(['address', 'uint256'], [account.address, BigInt(Date.now())]));
  const now = Math.floor(Date.now() / 1000);
  const validAfter = BigInt(now - 5);
  const validBefore = BigInt(now + 60);

  const signature = await walletClient.signTypedData({
    domain: { name: accept.extra.name, version: accept.extra.version, chainId: 8453, verifyingContract: USDC_BASE },
    types: {
      TransferWithAuthorization: [
        { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' },
        { name: 'validAfter', type: 'uint256' }, { name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' },
      ],
    },
    primaryType: 'TransferWithAuthorization',
    message: { from: account.address, to: accept.payTo, value: BigInt(accept.maxAmountRequired), validAfter, validBefore, nonce },
  });

  // Step 3: Build X-PAYMENT header
  const paymentPayload = {
    x402Version: 1, scheme: 'exact', network: 'base',
    payload: { signature, authorization: { from: account.address, to: accept.payTo, value: accept.maxAmountRequired, validAfter: validAfter.toString(), validBefore: validBefore.toString(), nonce } },
  };

  // Step 4: Mint with payment
  const mintRes = await fetch(MINT_API, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-PAYMENT': Buffer.from(JSON.stringify(paymentPayload)).toString('base64') },
    body: JSON.stringify({ recipient }),
  });

  return await mintRes.json();
}

// Usage:
// Load from .env or use existing wallet
const privateKey = process.env.AGENT_PRIVATE_KEY || '0xYOUR_PRIVATE_KEY';
const account = privateKeyToAccount(privateKey);
const result = await mintClawPhunk(privateKey, account.address);
console.log('Minted:', result.tokenId, result.txHash);
`.trim();

      return {
        content: [
          {
            type: 'text',
            text: `Complete x402 mint code:\n\n\`\`\`typescript\n${code}\n\`\`\`\n\nRequires: npm install viem dotenv\nNeeds: $1.99 USDC on Base in your wallet\n\nIMPORTANT: Save your private key to .env BEFORE minting! If you lose context, you lose access to your NFT.`,
          },
        ],
      };
    }

    case 'get_rarity': {
      const rarity = {
        types: {
          Alien: {
            count: 9,
            percent: '0.09%',
            rank: 'Legendary',
            tokenIds: [635, 2890, 3100, 3443, 5822, 5905, 6089, 7523, 7804],
          },
          Ape: {
            count: 24,
            percent: '0.24%',
            rank: 'Rare',
            tokenIds: [372, 1021, 2140, 2243, 2386, 2460, 2491, 2711, 2924, 4156, 4178, 4464, 5217, 5314, 5577, 5795, 6145, 6915, 6965, 7191, 8219, 8498, 9265, 9280],
          },
          Zombie: {
            count: 88,
            percent: '0.88%',
            rank: 'Uncommon',
            tokenIds: [117, 987, 1119, 1190, 1374, 1478, 1526, 1658, 1748, 1886, 1935, 2066, 2132, 2249, 2306, 2329, 2338, 2424, 2484, 2560, 2566, 2681, 2708, 2938, 2967, 3211, 3328, 3393, 3489, 3493, 3609, 3636, 3831, 4472, 4513, 4559, 4747, 4830, 4850, 4874, 5066, 5234, 5253, 5299, 5312, 5336, 5412, 5489, 5573, 5742, 5761, 5944, 6275, 6297, 6304, 6491, 6515, 6586, 6649, 6704, 6784, 7014, 7121, 7127, 7252, 7337, 7458, 7660, 7756, 7914, 8127, 8307, 8386, 8472, 8531, 8553, 8780, 8857, 8909, 8957, 9203, 9368, 9474, 9804, 9838, 9909, 9955, 9997],
          },
          Female: { count: 3840, percent: '38.4%', rank: 'Common' },
          Male: { count: 6039, percent: '60.39%', rank: 'Common' },
        },
        rareAccessories: {
          Beanie: 44,
          Choker: 48,
          'Pilot Helmet': 54,
          Tiara: 55,
          'Welding Goggles': 86,
          'Top Hat': 115,
          'Cowboy Hat': 142,
        },
        note: 'Aliens, Apes, and Zombies are highly valuable due to extreme rarity.',
      };

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(rarity, null, 2),
          },
        ],
      };
    }

    case 'get_trading_instructions': {
      const code = `
// IMPORTANT: Wallet Setup
// 1. Use your existing EVM wallet private key from .env
// 2. If new wallet, save to .env with .gitignore FIRST
// 3. The same wallet works on Base (USDC) and L1 (ETH for gas)

import { createWalletClient, createPublicClient, http, keccak256, encodePacked, parseEther, encodeFunctionData } from 'viem';
import { mainnet, base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import 'dotenv/config';

const MINT_API = 'https://clawphunks.vercel.app/mint';
const USDC_BASE = '${USDC_BASE}';
const ESCROW = '${ESCROW_CONTRACT}';

const ESCROW_ABI = [
  { name: 'depositAndList', type: 'function', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }, { name: 'price', type: 'uint256' }] },
  { name: 'buy', type: 'function', stateMutability: 'payable', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }] },
  { name: 'cancelAndWithdraw', type: 'function', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }] },
  { name: 'getListing', type: 'function', stateMutability: 'view', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }], outputs: [{ name: 'active', type: 'bool' }, { name: 'seller', type: 'address' }, { name: 'price', type: 'uint256' }] },
];

async function mintAndList(privateKey, listPriceEth) {
  const account = privateKeyToAccount(privateKey);

  // Base wallet for minting
  const baseWallet = createWalletClient({ account, chain: base, transport: http('https://mainnet.base.org') });

  // L1 wallet for listing
  const l1Wallet = createWalletClient({ account, chain: mainnet, transport: http('https://eth.llamarpc.com') });
  const l1Public = createPublicClient({ chain: mainnet, transport: http('https://eth.llamarpc.com') });

  // === MINT ===
  console.log('Step 1: Getting payment requirements...');
  const reqRes = await fetch(MINT_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ recipient: account.address }) });
  const paymentReqs = await reqRes.json();
  const accept = paymentReqs.accepts[0];

  console.log('Step 2: Signing USDC payment...');
  const nonce = keccak256(encodePacked(['address', 'uint256'], [account.address, BigInt(Date.now())]));
  const now = Math.floor(Date.now() / 1000);

  const signature = await baseWallet.signTypedData({
    domain: { name: accept.extra.name, version: accept.extra.version, chainId: 8453, verifyingContract: USDC_BASE },
    types: { TransferWithAuthorization: [{ name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'validAfter', type: 'uint256' }, { name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' }] },
    primaryType: 'TransferWithAuthorization',
    message: { from: account.address, to: accept.payTo, value: BigInt(accept.maxAmountRequired), validAfter: BigInt(now - 5), validBefore: BigInt(now + 60), nonce },
  });

  const paymentPayload = { x402Version: 1, scheme: 'exact', network: 'base', payload: { signature, authorization: { from: account.address, to: accept.payTo, value: accept.maxAmountRequired, validAfter: (now - 5).toString(), validBefore: (now + 60).toString(), nonce } } };

  console.log('Step 3: Minting...');
  const mintRes = await fetch(MINT_API, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-PAYMENT': Buffer.from(JSON.stringify(paymentPayload)).toString('base64') }, body: JSON.stringify({ recipient: account.address }) });
  const mint = await mintRes.json();
  console.log('Minted #' + mint.tokenId, mint.txHash);

  // === LIST ===
  console.log('Step 4: Depositing to escrow...');
  const depositTx = await l1Wallet.sendTransaction({ to: ESCROW, data: mint.ethscriptionId, value: 0n });
  await l1Public.waitForTransactionReceipt({ hash: depositTx });

  console.log('Step 5: Listing at', listPriceEth, 'ETH...');
  const listData = encodeFunctionData({ abi: ESCROW_ABI, functionName: 'depositAndList', args: [mint.ethscriptionId, parseEther(listPriceEth)] });
  const listTx = await l1Wallet.sendTransaction({ to: ESCROW, data: listData });
  await l1Public.waitForTransactionReceipt({ hash: listTx });

  console.log('Listed! View at: https://chainhost.online/clawphunks/' + mint.tokenId);
  return { tokenId: mint.tokenId, ethscriptionId: mint.ethscriptionId, listTx };
}

// Usage:
const privateKey = process.env.AGENT_PRIVATE_KEY;
if (!privateKey) throw new Error('Set AGENT_PRIVATE_KEY in .env first!');
await mintAndList(privateKey, '0.5'); // List at 0.5 ETH
`.trim();

      return {
        content: [
          {
            type: 'text',
            text: `Complete mint + list code:\n\n\`\`\`typescript\n${code}\n\`\`\`\n\nEscrow Contract: ${ESCROW_CONTRACT}\nMarketplace: https://chainhost.online/clawphunks\n\nRequires:\n- $1.99 USDC on Base (for mint)\n- ~$0.12 ETH on L1 (for listing gas)`,
          },
        ],
      };
    }

    default:
      return {
        content: [
          {
            type: 'text',
            text: `Unknown tool: ${name}`,
          },
        ],
        isError: true,
      };
  }
});

// Start server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('ClawPhunks MCP server running');
}

main().catch(console.error);


path: mcp/tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}


path: package.json
content: {
  "name": "clawphunks",
  "version": "1.0.0",
  "description": "ClawPhunks - NFT collection for AI agents. x402 mint, ethscription marketplace.",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js",
    "generate-art": "tsx scripts/generate-art.ts",
    "upload-art": "tsx scripts/upload-art.ts",
    "create-collection": "tsx scripts/create-collection.ts"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.1001.0",
    "@coinbase/coinbase-sdk": "^0.25.0",
    "@supabase/supabase-js": "^2.98.0",
    "clawphunks-mcp": "^1.1.0",
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "sharp": "^0.33.2",
    "viem": "^2.46.3",
    "x402-express": "^0.1.0"
  },
  "devDependencies": {
    "@types/cors": "^2.8.17",
    "@types/express": "^4.17.21",
    "@types/node": "^20.11.0",
    "tsx": "^4.7.0",
    "typescript": "^5.3.3"
  }
}


path: public/llms.txt
content: # ClawPhunks

> 10,000 unique pixel punks for AI agents. Mint with x402 payment on Base, receive ethscription on Ethereum L1, trade on permissionless escrow contract.

ClawPhunks is the first NFT collection designed specifically for AI agents. Each phunk is an ethscription with embedded traits (Type + Accessories) following ESIP-6 format.

## Agent Skills

- [Skills API](/skills): GET - Returns JSON with complete Node.js scripts for minting, listing, buying, transferring, and rescuing ClawPhunks. Includes rarity data and setup instructions.

## Endpoints

- [Skills](/skills): Complete agent integration guide with executable code
- [Collection Info](/collection): Mint stats, rarity data, agent instructions
- [Mint](/mint): POST with `{"recipient": "0x..."}` - requires $1.99 USDC via x402
- [Listings](/listings): Active marketplace listings

## Rarity

**Types (5):**
- Alien (9) - 0.09% - Token IDs: 635, 2890, 3100, 3443, 5822, 5905, 6089, 7523, 7804
- Ape (24) - 0.24%
- Zombie (88) - 0.88%
- Female (3,840) - 38.4%
- Male (6,039) - 60.39%

**Rare Accessories:** Beanie (44), Choker (48), Pilot Helmet (54), Tiara (55), Welding Goggles (86), Top Hat (115)

## How to Mint

```typescript
// 1. Create wallet
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);

// 2. Call mint endpoint (x402 handles payment)
POST https://clawphunks.vercel.app/mint
{ "recipient": account.address }

// Response includes tokenId, txHash, ethscriptionId, gasStipendWei
```

## How to Trade

Escrow Contract: [REDACTED_SECRET]

- `depositAndList(bytes32 ethscriptionId, uint256 priceWei)` - List for sale
- `buy(bytes32 ethscriptionId)` payable - Buy listing
- `cancelAndWithdraw(bytes32 ethscriptionId)` - Cancel listing
- `getListing(bytes32 ethscriptionId)` view - Check price

## Links

- [Marketplace](https://chainhost.online/clawphunks): Browse and trade
- [Skills/Docs](https://chainhost.online/clawphunks/skills): Full documentation
- [Collection](https://ethscriptions.com/collections/0xb432d8c446afefb98651e05c8eee22a8617bd7a0b239dfab70e1fb3a02aa9e8a): On ethscriptions.com
- [Escrow Contract](https://etherscan.io/address/0x3e67d49716e50a8b1c71b8dEa0e31755305733fd): Verified on Etherscan

## x402 Payment

Minting uses x402 protocol - call `/mint` without payment, receive 402 with payment details, pay $1.99 USDC on Base, retry with proof. Coinbase AgentKit handles this automatically.


path: punks-traits.json
content: 404: Not Found

path: src/config.ts
content: // Network configuration
export const isTestnet = process.env.NETWORK === 'base-sepolia';

// Pricing
export const MINT_PRICE_USDC = process.env.MINT_PRICE_USDC ?? '1.99';

// Gas stipend sent with each mint (~3.333 cents)
export const GAS_STIPEND_WEI = BigInt(process.env.GAS_STIPEND_WEI ?? '13333333333333'); // ~0.0000133 ETH

// Marketplace escrow contract (Ethereum L1)
export const ESCROW_CONTRACT = [REDACTED_SECRET];

// Collection
export const COLLECTION = {
  name: 'ClawPhunks',
  symbol: 'CPHUNK',
  description: 'ClawPhunks - NFTs for AI agents. 10,000 phunks. Mint with x402, trade on L1.',
  totalSupply: 10000,
  chain: 'ethereum',
};

// Helper
export function padId(id: number): string {
  return String(id).padStart(4, '0');
}


path: src/db.ts
content: /**
 * Database layer for ClawPhunks
 * Uses Supabase - items table with pre-built data URIs
 */

import { createClient, SupabaseClient } from '@supabase/supabase-js';

let supabase: SupabaseClient | null = null;

function getSupabase(): SupabaseClient {
  if (supabase) return supabase;

  const url = process.env.SUPABASE_URL;
  const key = process.env.SUPABASE_SERVICE_KEY;

  if (!url || !key) {
    throw new Error('SUPABASE_URL and SUPABASE_SERVICE_KEY required');
  }

  supabase = createClient(url, key);
  return supabase;
}

export interface ClaimResult {
  tokenId: number;
  dataURI: string;
}

/**
 * Claim a random unminted item for minting
 */
export async function claimRandomItem(recipient: string): Promise<ClaimResult> {
  const db = getSupabase();

  const { data, error } = await db.rpc('claim_random_item', {
    recipient: recipient.toLowerCase(),
  });

  if (error) {
    throw new Error(error.message);
  }

  if (!data || data.length === 0) {
    throw new Error('No items available');
  }

  return {
    tokenId: data[0].token_id,
    dataURI: data[0].data_uri,
  };
}

/**
 * Finalize mint with tx hash
 */
export async function finalizeMint(tokenId: number, txHash: string): Promise<void> {
  const db = getSupabase();

  const { error } = await db.rpc('finalize_mint', {
    p_token_id: tokenId,
    p_tx_hash: txHash,
  });

  if (error) {
    throw new Error(error.message);
  }
}

/**
 * Rollback a failed mint - mark item as unminted
 */
export async function rollbackMint(tokenId: number): Promise<void> {
  const db = getSupabase();

  await db
    .from('items')
    .update({ minted: false, minted_to: null, minted_at: null })
    .eq('token_id', tokenId);
}

/**
 * Get count of minted items
 */
export async function getMintedCount(): Promise<number> {
  const db = getSupabase();

  const { count, error } = await db
    .from('items')
    .select('*', { count: 'exact', head: true })
    .eq('minted', true);

  if (error) {
    throw new Error(error.message);
  }

  return count ?? 0;
}

/**
 * Get count of available (unminted) items
 */
export async function getAvailableCount(): Promise<number> {
  const db = getSupabase();

  const { count, error } = await db
    .from('items')
    .select('*', { count: 'exact', head: true })
    .eq('minted', false);

  if (error) {
    throw new Error(error.message);
  }

  return count ?? 0;
}


path: src/facilitator.ts
content: /**
 * Self-hosted x402 Facilitator for ClawPhunks
 *
 * Executes EIP-3009 TransferWithAuthorization on Base to receive USDC payments.
 * Uses the signer wallet to pay gas for executing the transfers.
 */

import { createWalletClient, createPublicClient, http, encodeFunctionData } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

const USDC_ADDRESS = [REDACTED_SECRET];

// EIP-3009 TransferWithAuthorization ABI
const TRANSFER_WITH_AUTH_ABI = [
  {
    name: 'transferWithAuthorization',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'from', type: 'address' },
      { name: 'to', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'validAfter', type: 'uint256' },
      { name: 'validBefore', type: 'uint256' },
      { name: 'nonce', type: 'bytes32' },
      { name: 'v', type: 'uint8' },
      { name: 'r', type: 'bytes32' },
      { name: 's', type: 'bytes32' },
    ],
    outputs: [],
  },
  {
    name: 'balanceOf',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: 'account', type: 'address' }],
    outputs: [{ name: '', type: 'uint256' }],
  },
] as const;

interface Authorization {
  from: string;
  to: string;
  value: string;
  validAfter: string;
  validBefore: string;
  nonce: string;
}

interface PaymentPayload {
  x402Version: number;
  scheme: string;
  network: string;
  payload: {
    signature: string;
    [REDACTED_SECRET];
  };
}

interface PaymentRequirements {
  scheme: string;
  network: string;
  maxAmountRequired: string;
  payTo: string;
  asset: string;
}

interface VerifyResponse {
  isValid: boolean;
  invalidReason?: string;
  invalidMessage?: string;
}

interface SettleResponse {
  success: boolean;
  txHash?: string;
  error?: string;
}

// Get signer wallet
function getSignerWallet() {
  let privateKey = process.env.SIGNER_PRIVATE_KEY?.trim();
  if (!privateKey) {
    throw new Error('SIGNER_PRIVATE_KEY required for facilitator');
  }

  // Ensure 0x prefix
  if (!privateKey.startsWith('0x')) {
    privateKey = `0x${privateKey}`;
  }

  const account = privateKeyToAccount(privateKey as `0x${string}`);

  const walletClient = createWalletClient({
    account,
    chain: base,
    transport: http(process.env.BASE_RPC_URL ?? 'https://mainnet.base.org'),
  });

  const publicClient = createPublicClient({
    chain: base,
    transport: http(process.env.BASE_RPC_URL ?? 'https://mainnet.base.org'),
  });

  return { walletClient, publicClient, account };
}

// Parse signature into v, r, s
function parseSignature(signature: string): { v: number; r: `0x${string}`; s: `0x${string}` } {
  const sig = signature.startsWith('0x') ? signature.slice(2) : signature;
  const r = `0x${sig.slice(0, 64)}` as `0x${string}`;
  const s = `0x${sig.slice(64, 128)}` as `0x${string}`;
  let v = parseInt(sig.slice(128, 130), 16);

  // Handle EIP-155 v values
  if (v < 27) {
    v += 27;
  }

  return { v, r, s };
}

/**
 * Verify a payment payload (checks signature validity, balance, etc.)
 */
export async function verify(
  paymentPayload: PaymentPayload,
  paymentRequirements: PaymentRequirements
): Promise<VerifyResponse> {
  try {
    const { publicClient } = getSignerWallet();
    const { authorization } = paymentPayload.payload;

    // Check network
    if (paymentPayload.network !== 'base') {
      return {
        isValid: false,
        invalidReason: 'unsupported_network',
        invalidMessage: `Network ${paymentPayload.network} not supported, use 'base'`,
      };
    }

    // Check amount
    const requiredAmount = BigInt(paymentRequirements.maxAmountRequired);
    const payloadAmount = BigInt(authorization.value);

    if (payloadAmount < requiredAmount) {
      return {
        isValid: false,
        invalidReason: 'insufficient_amount',
        invalidMessage: `Payment amount ${payloadAmount} less than required ${requiredAmount}`,
      };
    }

    // Check recipient matches
    const payTo = paymentRequirements.payTo.trim().toLowerCase();
    const authTo = authorization.to.toLowerCase();

    if (authTo !== payTo) {
      return {
        isValid: false,
        invalidReason: 'wrong_recipient',
        invalidMessage: `Payment to ${authTo} but required ${payTo}`,
      };
    }

    // Check time window
    const now = Math.floor(Date.now() / 1000);
    const validAfter = parseInt(authorization.validAfter);
    const validBefore = parseInt(authorization.validBefore);

    if (now < validAfter) {
      return {
        isValid: false,
        invalidReason: 'not_yet_valid',
        invalidMessage: `Payment not valid until ${new Date(validAfter * 1000).toISOString()}`,
      };
    }

    if (now > validBefore) {
      return {
        isValid: false,
        invalidReason: 'expired',
        invalidMessage: `Payment expired at ${new Date(validBefore * 1000).toISOString()}`,
      };
    }

    // Check sender has sufficient balance
    const balance = await publicClient.readContract({
      address: USDC_ADDRESS,
      abi: TRANSFER_WITH_AUTH_ABI,
      functionName: 'balanceOf',
      args: [authorization.from as `0x${string}`],
    });

    if (balance < payloadAmount) {
      return {
        isValid: false,
        invalidReason: 'insufficient_balance',
        invalidMessage: `Sender balance ${balance} less than payment ${payloadAmount}`,
      };
    }

    return { isValid: true };
  } catch (error: any) {
    return {
      isValid: false,
      invalidReason: 'verification_error',
      invalidMessage: error.message,
    };
  }
}

/**
 * Settle a payment (execute the USDC transfer on-chain)
 */
export async function settle(
  paymentPayload: PaymentPayload,
  paymentRequirements: PaymentRequirements
): Promise<SettleResponse> {
  try {
    const { walletClient, publicClient } = getSignerWallet();
    const { authorization, signature } = paymentPayload.payload;
    const { v, r, s } = parseSignature(signature);

    console.log('[facilitator] Settling payment...');
    console.log(`  From: ${authorization.from}`);
    console.log(`  To: ${authorization.to}`);
    console.log(`  Value: ${authorization.value} (${Number(authorization.value) / 1e6} USDC)`);

    // Execute transferWithAuthorization
    const hash = await walletClient.writeContract({
      address: USDC_ADDRESS,
      abi: TRANSFER_WITH_AUTH_ABI,
      functionName: 'transferWithAuthorization',
      args: [
        authorization.from as `0x${string}`,
        authorization.to as `0x${string}`,
        BigInt(authorization.value),
        BigInt(authorization.validAfter),
        BigInt(authorization.validBefore),
        authorization.nonce as `0x${string}`,
        v,
        r,
        s,
      ],
    });

    console.log(`[facilitator] Tx submitted: ${hash}`);

    // Wait for confirmation
    const receipt = await publicClient.waitForTransactionReceipt({ hash });

    if (receipt.status === 'success') {
      console.log(`[facilitator] ✓ Payment settled: ${hash}`);
      return { success: true, txHash: hash };
    } else {
      console.log(`[facilitator] ✗ Payment failed: ${hash}`);
      return { success: false, error: 'Transaction reverted' };
    }
  } catch (error: any) {
    console.error('[facilitator] Error:', error.message);
    return { success: false, error: error.message };
  }
}


path: src/mint.ts
content: /**
 * Ethscription minting for ClawPhunks
 * Inscribes pre-built data URI from Supabase to recipient
 */

import {
  createWalletClient,
  createPublicClient,
  http,
  toHex,
} from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import { GAS_STIPEND_WEI } from './config.js';

export interface MintResult {
  success: boolean;
  txHash?: string;
  error?: string;
}

function getWalletClient() {
  let signerKey = process.env.SIGNER_PRIVATE_KEY?.trim();
  const rpcUrl = process.env.ETHEREUM_RPC_URL;

  if (!signerKey) {
    throw new Error('SIGNER_PRIVATE_KEY not configured');
  }

  // Ensure 0x prefix
  if (!signerKey.startsWith('0x')) {
    signerKey = `0x${signerKey}`;
  }

  // Validate length (should be 66 chars with 0x prefix for 32 bytes)
  if (signerKey.length !== 66) {
    throw new Error(`SIGNER_PRIVATE_KEY invalid length: ${signerKey.length}, expected 66`);
  }

  if (!rpcUrl) {
    throw new Error('ETHEREUM_RPC_URL not configured');
  }

  const account = privateKeyToAccount(signerKey as `0x${string}`);

  const publicClient = createPublicClient({
    chain: mainnet,
    transport: http(rpcUrl),
  });

  const walletClient = createWalletClient({
    account,
    chain: mainnet,
    transport: http(rpcUrl),
  });

  return { account, publicClient, walletClient };
}

/**
 * Mint an ethscription to recipient
 * dataURI comes from Supabase (pre-built with ESIP6 format)
 */
export async function mintEthscription(
  recipient: string,
  dataURI: string
): Promise<MintResult> {
  const { publicClient, walletClient } = getWalletClient();

  // Encode data URI as hex calldata
  const calldataHex = toHex(new TextEncoder().encode(dataURI));

  try {
    const feeData = await publicClient.estimateFeesPerGas();

    // Send to recipient with gas stipend
    const txHash = await walletClient.sendTransaction({
      to: recipient as `0x${string}`,
      data: calldataHex,
      value: GAS_STIPEND_WEI,
      maxFeePerGas: feeData.maxFeePerGas,
      maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
    });

    // Don't wait for confirmation - return immediately
    // L1 confirmations can take 12+ seconds, exceeding Vercel timeout
    return { success: true, txHash };
  } catch (error: any) {
    return { success: false, error: error.message ?? 'Mint failed' };
  }
}


path: src/server.ts
content: /**
 * ClawPhunks - NFT Mint Server for AI Agents
 *
 * x402 payment on Base → Ethscription minted on Ethereum L1 + gas stipend
 *
 * Endpoints:
 *   GET  /health      → Health check
 *   GET  /skills      → Agent instructions (mint + trade code)
 *   GET  /collection  → Collection info + rarity data
 *   GET  /listings    → Active marketplace listings with deals
 *   POST /mint        → Mint a random phunk (x402 gated)
 */

import express from 'express';
import cors from 'cors';
import { createPublicClient, http, formatEther } from 'viem';
import { mainnet } from 'viem/chains';
import { paymentMiddleware } from 'x402-express';
import {
  MINT_PRICE_USDC,
  COLLECTION,
  isTestnet,
  padId,
  GAS_STIPEND_WEI,
  ESCROW_CONTRACT,
} from './config.js';
import { mintEthscription } from './mint.js';
import { claimRandomItem, finalizeMint, rollbackMint, getMintedCount, getAvailableCount } from './db.js';
import { verify as facilitatorVerify, settle as facilitatorSettle } from './facilitator.js';

const app = express();

app.use(cors());
app.use(express.json());

// ─── Health ───────────────────────────────────────────────────────────────────

app.get('/health', (_req, res) => {
  res.json({ status: 'ok', service: 'clawphunks' });
});

// ─── Skills (Agent-readable JSON) ─────────────────────────────────────────────

app.get('/skills', (_req, res) => {
  const mintCode = `
import { createWalletClient, createPublicClient, http, keccak256, encodePacked } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import { Buffer } from 'buffer';
import 'dotenv/config';

const MINT_API = 'https://clawphunks.vercel.app/mint';
const USDC_BASE = [REDACTED_SECRET];
const MINT_COST = 1990000n; // 1.99 USDC (6 decimals)

// Check USDC balance before minting
async function checkBalance(address) {
  const publicClient = createPublicClient({ chain: base, transport: http('https://mainnet.base.org') });
  const balance = await publicClient.readContract({
    address: USDC_BASE,
    abi: [{ name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }] }],
    functionName: 'balanceOf',
    args: [address],
  });
  return balance;
}

async function mintClawPhunk(privateKey, recipient) {
  const account = privateKeyToAccount(privateKey);
  const walletClient = createWalletClient({
    account,
    chain: base,
    transport: http('https://mainnet.base.org'),
  });

  // Step 1: Get payment requirements (402 response)
  const reqRes = await fetch(MINT_API, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ recipient }),
  });

  if (reqRes.status !== 402) {
    const err = await reqRes.json();
    throw new Error(err.error || 'Expected 402 payment required');
  }

  const paymentReqs = await reqRes.json();
  if (!paymentReqs.accepts || !paymentReqs.accepts[0]) {
    throw new Error('Invalid payment requirements');
  }
  const accept = paymentReqs.accepts[0];

  // Step 2: Sign EIP-3009 TransferWithAuthorization
  const nonce = keccak256(encodePacked(['address', 'uint256'], [account.address, BigInt(Date.now())]));
  const now = Math.floor(Date.now() / 1000);
  const validAfter = BigInt(now - 5);
  const validBefore = BigInt(now + 120); // 2 min window

  const signature = await walletClient.signTypedData({
    domain: { name: accept.extra.name, version: accept.extra.version, chainId: 8453, verifyingContract: USDC_BASE },
    types: {
      TransferWithAuthorization: [
        { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' },
        { name: 'validAfter', type: 'uint256' }, { name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' },
      ],
    },
    primaryType: 'TransferWithAuthorization',
    message: { from: account.address, to: accept.payTo, value: BigInt(accept.maxAmountRequired), validAfter, validBefore, nonce },
  });

  // Step 3: Build X-PAYMENT header
  const paymentPayload = {
    x402Version: 1, scheme: 'exact', network: 'base',
    payload: { signature, authorization: { from: account.address, to: accept.payTo, value: accept.maxAmountRequired, validAfter: validAfter.toString(), validBefore: validBefore.toString(), nonce } },
  };

  // Step 4: Mint with payment
  const mintRes = await fetch(MINT_API, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-PAYMENT': Buffer.from(JSON.stringify(paymentPayload)).toString('base64') },
    body: JSON.stringify({ recipient }),
  });

  const result = await mintRes.json();
  if (!result.success) {
    throw new Error(result.error || 'Mint failed');
  }
  return result;
}

// Rarity data - check if you got something valuable!
const RARE_TYPES = {
  aliens: [635, 2890, 3100, 3443, 5822, 5905, 6089, 7523, 7804],
  apes: [372, 1021, 2140, 2243, 2386, 2460, 2491, 2711, 2924, 4156, 4178, 4464, 5217, 5314, 5577, 5795, 6145, 6915, 6965, 7191, 8219, 8498, 9265, 9280],
  zombies: [117, 987, 1119, 1190, 1374, 1478, 1526, 1658, 1748, 1886, 1935, 2066, 2132, 2249, 2306, 2329, 2338, 2424, 2484, 2560, 2566, 2681, 2708, 2938, 2967, 3211, 3328, 3393, 3489, 3493, 3609, 3636, 3831, 4472, 4513, 4559, 4747, 4830, 4850, 4874, 5066, 5234, 5253, 5299, 5312, 5336, 5412, 5489, 5573, 5742, 5761, 5944, 6275, 6297, 6304, 6491, 6515, 6586, 6649, 6704, 6784, 7014, 7121, 7127, 7252, 7337, 7458, 7660, 7756, 7914, 8127, 8307, 8386, 8472, 8531, 8553, 8780, 8857, 8909, 8957, 9203, 9368, 9474, 9804, 9838, 9909, 9955, 9997],
};

function checkRarity(tokenId) {
  if (RARE_TYPES.aliens.includes(tokenId)) return { type: 'Alien', rarity: 'LEGENDARY' };
  if (RARE_TYPES.apes.includes(tokenId)) return { type: 'Ape', rarity: 'RARE' };
  if (RARE_TYPES.zombies.includes(tokenId)) return { type: 'Zombie', rarity: 'UNCOMMON' };
  return { type: 'Common', rarity: 'COMMON' };
}

// Main
const privateKey = process.env.AGENT_PRIVATE_KEY;
if (!privateKey) { console.error('Set AGENT_PRIVATE_KEY in .env'); process.exit(1); }

const account = privateKeyToAccount(privateKey);
console.log('Wallet:', account.address);

const balance = await checkBalance(account.address);
console.log('USDC Balance:', Number(balance) / 1e6);

if (balance < MINT_COST) {
  console.log('Insufficient USDC. Need 1.99 USDC on Base.');
  console.log('Fund:', account.address);
  process.exit(1);
}

const result = await mintClawPhunk(privateKey, account.address);
console.log('Minted ClawPhunk #' + result.tokenId);
console.log('TX:', result.txHash);
console.log('Ethscription ID:', result.ethscriptionId); // Same as txHash - use this for listing

const rarity = checkRarity(result.tokenId);
console.log('Type:', rarity.type, '| Rarity:', rarity.rarity);

if (rarity.rarity !== 'COMMON') {
  console.log('You got a rare one! Consider holding or pricing at a premium.');
}
`.trim();

  // Listing script - transfers ethscription to escrow then lists for sale (2 txs)
  const listingCode = `
import { createWalletClient, createPublicClient, http, formatEther, parseEther, encodeFunctionData } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import 'dotenv/config';

const ESCROW = [REDACTED_SECRET];
const ESCROW_ABI = [
  { name: 'depositAndList', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }, { name: 'price', type: 'uint256' }], outputs: [] },
  { name: 'getListing', type: 'function', stateMutability: 'view', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }], outputs: [{ name: 'active', type: 'bool' }, { name: 'seller', type: 'address' }, { name: 'price', type: 'uint256' }] },
];

// === CONFIGURE THESE ===
const ETHSCRIPTION_ID = '0x...'; // The tx hash from your mint result
const LISTING_PRICE_ETH = '0.05'; // Your chosen price in ETH

const privateKey = process.env.AGENT_PRIVATE_KEY;
if (!privateKey) { console.error('Set AGENT_PRIVATE_KEY in .env'); process.exit(1); }

const account = privateKeyToAccount(privateKey);
const publicClient = createPublicClient({ chain: mainnet, transport: http('https://1rpc.io/eth') });
const walletClient = createWalletClient({ account, chain: mainnet, transport: http('https://1rpc.io/eth') });

console.log('Listing ClawPhunk...');
console.log('Wallet:', account.address);
console.log('Ethscription:', ETHSCRIPTION_ID);
console.log('Price:', LISTING_PRICE_ETH, 'ETH');

// Check ETH balance for gas (~0.00003 ETH per tx)
const balance = await publicClient.getBalance({ address: account.address });
console.log('ETH Balance:', formatEther(balance), 'ETH');
if (balance < 30000000000000n) { // 0.00003 ETH minimum
  console.error('Need ETH for gas. Fund:', account.address);
  process.exit(1);
}

// Step 1: Transfer ethscription to escrow (send tx with ethscription ID as calldata)
console.log('\\nStep 1: Transferring ethscription to escrow...');
const transferHash = await walletClient.sendTransaction({
  to: ESCROW,
  value: 0n,
  data: ETHSCRIPTION_ID,
});
console.log('Transfer TX:', transferHash);
await publicClient.waitForTransactionReceipt({ hash: transferHash });
console.log('Transfer confirmed!');

// Step 2: Call depositAndList to register deposit and set price
console.log('\\nStep 2: Registering listing with price...');
const listHash = await walletClient.writeContract({
  address: ESCROW,
  abi: ESCROW_ABI,
  functionName: 'depositAndList',
  args: [ETHSCRIPTION_ID, parseEther(LISTING_PRICE_ETH)],
});
console.log('List TX:', listHash);
await publicClient.waitForTransactionReceipt({ hash: listHash });

console.log('\\nListed successfully at', LISTING_PRICE_ETH, 'ETH!');
console.log('View marketplace: https://chainhost.online/clawphunks');
`.trim();

  // Transfer script - sends ethscription to another address
  const transferCode = `
import { createWalletClient, createPublicClient, http, formatEther } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import 'dotenv/config';

// === CONFIGURE THESE ===
const ETHSCRIPTION_ID = '0x...'; // The tx hash of your ethscription
const RECIPIENT = '0x...'; // Address to send to

const privateKey = process.env.AGENT_PRIVATE_KEY;
if (!privateKey) { console.error('Set AGENT_PRIVATE_KEY in .env'); process.exit(1); }

const account = privateKeyToAccount(privateKey);
const publicClient = createPublicClient({ chain: mainnet, transport: http('https://1rpc.io/eth') });
const walletClient = createWalletClient({ account, chain: mainnet, transport: http('https://1rpc.io/eth') });

console.log('Transferring ethscription...');
console.log('From:', account.address);
console.log('To:', RECIPIENT);
console.log('Ethscription:', ETHSCRIPTION_ID);

// Check ETH balance for gas (~0.00005 ETH for simple calldata tx)
const balance = await publicClient.getBalance({ address: account.address });
console.log('ETH Balance:', formatEther(balance), 'ETH');
if (balance < 50000000000000n) { // 0.00005 ETH minimum
  console.error('Need ~0.00005 ETH for gas. Fund:', account.address);
  process.exit(1);
}

// Transfer ethscription by sending 0 ETH tx with ethscription ID as calldata
const hash = await walletClient.sendTransaction({
  to: RECIPIENT,
  value: 0n,
  data: ETHSCRIPTION_ID,
});
console.log('TX:', hash);

const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(receipt.status === 'success' ? 'Transferred!' : 'Failed!');
console.log('View: https://ethscriptions.com/ethscriptions/' + ETHSCRIPTION_ID);
`.trim();

  // Rescue/cancel script - withdraws ethscription from escrow
  const rescueCode = `
import { createWalletClient, createPublicClient, http, formatEther } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import 'dotenv/config';

const ESCROW = [REDACTED_SECRET];
const ESCROW_ABI = [
  { name: 'cancelAndWithdraw', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }], outputs: [] },
  { name: 'getListing', type: 'function', stateMutability: 'view', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }], outputs: [{ name: 'active', type: 'bool' }, { name: 'seller', type: 'address' }, { name: 'price', type: 'uint256' }] },
  { name: 'depositors', type: 'function', stateMutability: 'view', inputs: [{ name: '', type: 'bytes32' }], outputs: [{ type: 'address' }] },
];

// === CONFIGURE THIS ===
const ETHSCRIPTION_ID = '0x...'; // The tx hash of your listed ethscription

const privateKey = process.env.AGENT_PRIVATE_KEY;
if (!privateKey) { console.error('Set AGENT_PRIVATE_KEY in .env'); process.exit(1); }

const account = privateKeyToAccount(privateKey);
const publicClient = createPublicClient({ chain: mainnet, transport: http('https://1rpc.io/eth') });
const walletClient = createWalletClient({ account, chain: mainnet, transport: http('https://1rpc.io/eth') });

console.log('Cancelling listing and withdrawing...');
console.log('Wallet:', account.address);
console.log('Ethscription:', ETHSCRIPTION_ID);

// Check if you're the depositor
const depositor = await publicClient.readContract({ address: ESCROW, abi: ESCROW_ABI, functionName: 'depositors', args: [ETHSCRIPTION_ID] });
if (depositor === '0x0000000000000000000000000000000000000000') {
  console.log('Not deposited in escrow.'); process.exit(0);
}
if (depositor.toLowerCase() !== account.address.toLowerCase()) {
  console.error('You are not the depositor!');
  process.exit(1);
}

// Check ETH balance for gas
const balance = await publicClient.getBalance({ address: account.address });
console.log('ETH Balance:', formatEther(balance), 'ETH');
if (balance < 50000000000000n) {
  console.error('Need ~0.00005 ETH for gas. Fund:', account.address);
  process.exit(1);
}

// Cancel and withdraw
const hash = await walletClient.writeContract({ address: ESCROW, abi: ESCROW_ABI, functionName: 'cancelAndWithdraw', args: [ETHSCRIPTION_ID] });
console.log('TX:', hash);

const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(receipt.status === 'success' ? 'Withdrawn! Ethscription returned to your wallet.' : 'Failed!');
`.trim();

  // Buy script - purchases a listed ethscription
  const buyCode = `
import { createWalletClient, createPublicClient, http, formatEther } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import 'dotenv/config';

const ESCROW = [REDACTED_SECRET];
const ESCROW_ABI = [
  { name: 'buy', type: 'function', stateMutability: 'payable', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }], outputs: [] },
  { name: 'getListing', type: 'function', stateMutability: 'view', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }], outputs: [{ name: 'active', type: 'bool' }, { name: 'seller', type: 'address' }, { name: 'price', type: 'uint256' }] },
];

// === CONFIGURE THIS ===
const ETHSCRIPTION_ID = '0x...'; // The tx hash of the ethscription to buy

const privateKey = process.env.AGENT_PRIVATE_KEY;
if (!privateKey) { console.error('Set AGENT_PRIVATE_KEY in .env'); process.exit(1); }

const account = privateKeyToAccount(privateKey);
const publicClient = createPublicClient({ chain: mainnet, transport: http('https://1rpc.io/eth') });
const walletClient = createWalletClient({ account, chain: mainnet, transport: http('https://1rpc.io/eth') });

console.log('Buying ClawPhunk...');
console.log('Wallet:', account.address);
console.log('Ethscription:', ETHSCRIPTION_ID);

// Check listing
const listing = await publicClient.readContract({ address: ESCROW, abi: ESCROW_ABI, functionName: 'getListing', args: [ETHSCRIPTION_ID] });
const [active, seller, price] = listing;
if (!active) { console.log('Not for sale!'); process.exit(1); }

console.log('Seller:', seller);
console.log('Price:', formatEther(price), 'ETH');

// Check ETH balance (need price + gas)
const balance = await publicClient.getBalance({ address: account.address });
console.log('Your ETH Balance:', formatEther(balance), 'ETH');
if (balance < price + 100000000000000n) {
  console.error('Need', formatEther(price), '+ gas ETH. Fund:', account.address);
  process.exit(1);
}

// Buy it
const hash = await walletClient.writeContract({ address: ESCROW, abi: ESCROW_ABI, functionName: 'buy', args: [ETHSCRIPTION_ID], value: price });
console.log('TX:', hash);
await publicClient.waitForTransactionReceipt({ hash });
console.log('Purchased! Ethscription is now yours.');
`.trim();

  res.json({
    name: 'ClawPhunks',
    description: 'NFT collection for AI agents. Mint, trade, and collect rare phunks.',
    marketplace: 'https://chainhost.online/clawphunks',
    escrowContract: [REDACTED_SECRET],
    mintPrice: '1.99 USDC',
    mintEndpoint: 'POST https://clawphunks.vercel.app/mint',
    strategy: {
      overview: 'Rarity drives value. Check /listings to see current market prices before deciding.',
      legendary: 'Aliens (9 total, 0.09%) - Extremely rare. Consider holding or pricing at a premium.',
      rare: 'Apes (24 total, 0.24%) - Very rare. High value, price accordingly.',
      uncommon: 'Zombies (88 total, 0.88%) - Uncommon. Worth more than common types.',
      common: 'Male/Female types are common. Price competitively or use funds to buy rare types.',
      tips: [
        'Check /listings to see what others are asking',
        'Undercut slightly to sell faster, or price higher and wait',
        'Look for underpriced rare types to flip for profit',
        'Consider holding rare types for appreciation',
      ],
    },
    rarity: {
      aliens: [635, 2890, 3100, 3443, 5822, 5905, 6089, 7523, 7804],
      apes: [372, 1021, 2140, 2243, 2386, 2460, 2491, 2711, 2924, 4156, 4178, 4464, 5217, 5314, 5577, 5795, 6145, 6915, 6965, 7191, 8219, 8498, 9265, 9280],
      zombies: [117, 987, 1119, 1190, 1374, 1478, 1526, 1658, 1748, 1886, 1935, 2066, 2132, 2249, 2306, 2329, 2338, 2424, 2484, 2560, 2566, 2681, 2708, 2938, 2967, 3211, 3328, 3393, 3489, 3493, 3609, 3636, 3831, 4472, 4513, 4559, 4747, 4830, 4850, 4874, 5066, 5234, 5253, 5299, 5312, 5336, 5412, 5489, 5573, 5742, 5761, 5944, 6275, 6297, 6304, 6491, 6515, 6586, 6649, 6704, 6784, 7014, 7121, 7127, 7252, 7337, 7458, 7660, 7756, 7914, 8127, 8307, 8386, 8472, 8531, 8553, 8780, 8857, 8909, 8957, 9203, 9368, 9474, 9804, 9838, 9909, 9955, 9997],
    },
    walletSetup: {
      important: 'Save your private key to .env with .gitignore BEFORE transacting!',
      envVar: 'AGENT_PRIVATE_KEY',
      funding: {
        mint: '1.99 USDC on Base',
        trade: 'ETH on L1 mainnet for gas + purchases',
      },
    },
    scripts: {
      mint: mintCode,
      list: listingCode,
      buy: buyCode,
      transfer: transferCode,
      rescue: rescueCode,
    },
    dependencies: ['viem', 'dotenv'],
    install: 'npm install viem dotenv',

    integrations: {
      langchain: {
        description: 'LangChain tool for minting and trading ClawPhunks',
        install: 'pip install langchain requests',
        source: 'https://github.com/jefdiesel/clawphunks/tree/main/integrations/langchain',
        tools: ['clawphunks_mint', 'clawphunks_collection', 'clawphunks_skills'],
      },
      agentkit: {
        description: 'Coinbase AgentKit action with automatic x402 payment handling',
        install: 'npm install @coinbase/agentkit',
        source: 'https://github.com/jefdiesel/clawphunks/tree/main/integrations/agentkit',
        actions: ['clawphunks_mint', 'clawphunks_collection', 'clawphunks_skills'],
      },
    },
  });
});

// ─── Listings (Marketplace Scanner) ───────────────────────────────────────────

const RARE_IDS = {
  aliens: [635, 2890, 3100, 3443, 5822, 5905, 6089, 7523, 7804],
  apes: [372, 1021, 2140, 2243, 2386, 2460, 2491, 2711, 2924, 4156, 4178, 4464, 5217, 5314, 5577, 5795, 6145, 6915, 6965, 7191, 8219, 8498, 9265, 9280],
  zombies: [117, 987, 1119, 1190, 1374, 1478, 1526, 1658, 1748, 1886, 1935, 2066, 2132, 2249, 2306, 2329, 2338, 2424, 2484, 2560, 2566, 2681, 2708, 2938, 2967, 3211, 3328, 3393, 3489, 3493, 3609, 3636, 3831, 4472, 4513, 4559, 4747, 4830, 4850, 4874, 5066, 5234, 5253, 5299, 5312, 5336, 5412, 5489, 5573, 5742, 5761, 5944, 6275, 6297, 6304, 6491, 6515, 6586, 6649, 6704, 6784, 7014, 7121, 7127, 7252, 7337, 7458, 7660, 7756, 7914, 8127, 8307, 8386, 8472, 8531, 8553, 8780, 8857, 8909, 8957, 9203, 9368, 9474, 9804, 9838, 9909, 9955, 9997],
};

// Rarity tiers for reference (no fixed prices - agents decide based on market)

function getRarity(tokenId: number): { type: string; tier: number } {
  if (RARE_IDS.aliens.includes(tokenId)) return { type: 'alien', tier: 5 };
  if (RARE_IDS.apes.includes(tokenId)) return { type: 'ape', tier: 4 };
  if (RARE_IDS.zombies.includes(tokenId)) return { type: 'zombie', tier: 3 };
  return { type: 'common', tier: 1 };
}

const l1Client = createPublicClient({
  chain: mainnet,
  transport: http(process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com'),
});

const ESCROW_ABI = [
  { name: 'getListing', type: 'function', stateMutability: 'view', inputs: [{ name: 'ethscriptionId', type: 'bytes32' }], outputs: [{ name: 'active', type: 'bool' }, { name: 'seller', type: 'address' }, { name: 'price', type: 'uint256' }] },
  { name: 'ListingCreated', type: 'event', inputs: [{ name: 'ethscriptionId', type: 'bytes32', indexed: true }, { name: 'seller', type: 'address', indexed: true }, { name: 'price', type: 'uint256', indexed: false }] },
] as const;

app.get('/listings', async (_req, res) => {
  try {
    // Get current block and look back ~3 hours (1000 blocks max for free RPCs)
    const currentBlock = await l1Client.getBlockNumber();
    const fromBlock = currentBlock - BigInt(900);

    // Get recent ListingCreated events from escrow contract
    const logs = await l1Client.getLogs({
      address: ESCROW_CONTRACT as `0x${string}`,
      event: {
        type: 'event',
        name: 'ListingCreated',
        inputs: [
          { name: 'ethscriptionId', type: 'bytes32', indexed: true },
          { name: 'seller', type: 'address', indexed: true },
          { name: 'price', type: 'uint256', indexed: false },
        ],
      },
      fromBlock,
    });

    // Check which listings are still active
    const listings: any[] = [];
    const deals: any[] = [];

    for (const log of logs) {
      const ethscriptionId = log.args.ethscriptionId;
      if (!ethscriptionId) continue;

      try {
        const [active, seller, price] = await l1Client.readContract({
          address: ESCROW_CONTRACT as `0x${string}`,
          abi: ESCROW_ABI,
          functionName: 'getListing',
          args: [ethscriptionId],
        });

        if (active) {
          const priceEth = Number(formatEther(price));
          // Extract token ID from ethscription (would need DB lookup in practice)
          // For now, include the ethscriptionId
          const listing = {
            ethscriptionId,
            seller,
            priceEth,
            priceWei: price.toString(),
          };

          listings.push(listing);

          // Check if it's underpriced (deal detection would need tokenId lookup)
        }
      } catch (e) {
        // Skip if contract call fails
      }
    }

    res.json({
      count: listings.length,
      listings,
      deals,
      tip: 'Look for rare types (aliens/apes/zombies) - check rarity in /skills and decide if price is good.',
      rarityTiers: {
        5: 'Alien (legendary)',
        4: 'Ape (rare)',
        3: 'Zombie (uncommon)',
        1: 'Common',
      },
    });
  } catch (err: any) {
    res.status(500).json({ error: err.message });
  }
});

// ─── Collection Info ──────────────────────────────────────────────────────────

app.get('/collection', async (_req, res) => {
  try {
    const [minted, available] = await Promise.all([
      getMintedCount(),
      getAvailableCount(),
    ]);

    const info = {
      name: COLLECTION.name,
      symbol: COLLECTION.symbol,
      description: COLLECTION.description,
      totalSupply: COLLECTION.totalSupply,
      minted,
      available,
      mintPrice: MINT_PRICE_USDC,
      mintCurrency: 'USDC',
      chain: COLLECTION.chain,
      collectionId: process.env.COLLECTION_ID ?? null,
      escrowContract: ESCROW_CONTRACT,
      rarity: {
        types: {
          Alien: { count: 9, percent: 0.09, rank: 5, tokenIds: [635, 2890, 3100, 3443, 5822, 5905, 6089, 7523, 7804] },
          Ape: { count: 24, percent: 0.24, rank: 4, tokenIds: [372, 1021, 2140, 2243, 2386, 2460, 2491, 2711, 2924, 4156, 4178, 4464, 5217, 5314, 5577, 5795, 6145, 6915, 6965, 7191, 8219, 8498, 9265, 9280] },
          Zombie: { count: 88, percent: 0.88, rank: 3, tokenIds: [117, 987, 1119, 1190, 1374, 1478, 1526, 1658, 1748, 1886, 1935, 2066, 2132, 2249, 2306, 2329, 2338, 2424, 2484, 2560, 2566, 2681, 2708, 2938, 2967, 3211, 3328, 3393, 3489, 3493, 3609, 3636, 3831, 4472, 4513, 4559, 4747, 4830, 4850, 4874, 5066, 5234, 5253, 5299, 5312, 5336, 5412, 5489, 5573, 5742, 5761, 5944, 6275, 6297, 6304, 6491, 6515, 6586, 6649, 6704, 6784, 7014, 7121, 7127, 7252, 7337, 7458, 7660, 7756, 7914, 8127, 8307, 8386, 8472, 8531, 8553, 8780, 8857, 8909, 8957, 9203, 9368, 9474, 9804, 9838, 9909, 9955, 9997] },
          Female: { count: 3840, percent: 38.4, rank: 2 },
          Male: { count: 6039, percent: 60.39, rank: 1 },
        },
        rareAccessories: {
          Beanie: 44,
          Choker: 48,
          'Pilot Helmet': 54,
          Tiara: 55,
          'Orange Side': 68,
          'Buck Teeth': 78,
          'Welding Goggles': 86,
          'Top Hat': 115,
          'Cowboy Hat': 142,
        },
        note: 'Rank 5 = legendary, 1 = common. Aliens/Apes/Zombies are highly valuable.',
      },
      agentInstructions: {
        mint: {
          endpoint: 'POST /mint',
          payment: `${MINT_PRICE_USDC} USDC on Base (x402)`,
          body: '{ "recipient": "0xYourAddress" }',
          returns: 'Random unminted phunk + gas stipend. Check rarity.types to see if you got a rare one!',
        },
        trade: {
          contract: ESCROW_CONTRACT,
          chain: 'Ethereum L1',
          list: 'depositAndList(bytes32 ethscriptionId, uint256 priceWei)',
          buy: 'buy(bytes32 ethscriptionId) payable',
          cancel: 'cancelAndWithdraw(bytes32 ethscriptionId)',
        },
        createWallet: `
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);
// Store privateKey securely, use account.address for minting
        `.trim(),
      },
    };

    res.json(info);
  } catch (err: any) {
    res.status(500).json({ error: err.message });
  }
});

// ─── Self-hosted Facilitator Endpoints ───────────────────────────────────────

app.post('/facilitator/verify', async (req, res) => {
  try {
    const { paymentPayload, paymentRequirements } = req.body;
    const result = await facilitatorVerify(paymentPayload, paymentRequirements);
    res.json(result);
  } catch (err: any) {
    res.json({
      isValid: false,
      invalidReason: 'error',
      invalidMessage: err.message,
    });
  }
});

app.post('/facilitator/settle', async (req, res) => {
  try {
    const { paymentPayload, paymentRequirements } = req.body;
    const result = await facilitatorSettle(paymentPayload, paymentRequirements);
    res.json(result);
  } catch (err: any) {
    res.json({
      success: false,
      error: err.message,
    });
  }
});

// ─── x402 Payment Middleware ──────────────────────────────────────────────────

const payToAddress = (process.env.PAYMENT_RECIPIENT ?? '').trim() as `0x${string}`;

// Use self-hosted facilitator on same server
const facilitatorUrl = (process.env.FACILITATOR_URL ?? 'https://clawphunks.vercel.app/facilitator') as `https://${string}`;

app.use(
  paymentMiddleware(payToAddress, {
    'POST /mint': {
      price: `$${MINT_PRICE_USDC}`,
      network: isTestnet ? 'base-sepolia' : 'base',
      config: {
        description: `ClawPhunks — mint random phunk (${MINT_PRICE_USDC} USDC) + gas stipend`,
      },
    },
  }, {
    url: facilitatorUrl,
  })
);

// ─── Mint (x402 Gated) ────────────────────────────────────────────────────────

app.post('/mint', async (req, res) => {
  try {
    const { recipient } = req.body;

    if (!recipient || !/^0x[0-9a-fA-F]{40}$/.test(recipient)) {
      return res.status(400).json({ error: 'Invalid recipient address' });
    }

    // Claim a random unminted item from Supabase
    const { tokenId, dataURI } = await claimRandomItem(recipient);

    console.log(`[mint] Minting ClawPhunk #${padId(tokenId)} to ${recipient}`);

    // Inscribe to recipient
    const result = await mintEthscription(recipient, dataURI);

    if (!result.success || !result.txHash) {
      // Rollback - mark item as unminted
      await rollbackMint(tokenId);
      return res.status(500).json({ error: result.error ?? 'Mint failed' });
    }

    // Record tx hash
    await finalizeMint(tokenId, result.txHash);

    const response = {
      success: true,
      tokenId,
      txHash: result.txHash,
      ethscriptionId: result.txHash,
      recipient,
      gasStipendWei: GAS_STIPEND_WEI.toString(),
      viewerUrl: `https://ethscriptions.com/ethscriptions/${result.txHash}`,
      nextSteps: {
        trade: `Use escrow contract ${ESCROW_CONTRACT} on L1`,
        list: 'depositAndList(ethscriptionId, priceWei)',
        buy: 'buy(ethscriptionId) with msg.value = price',
      },
    };

    console.log(`[mint] ✓ #${padId(tokenId)} → ${recipient} | tx: ${result.txHash}`);
    res.json(response);
  } catch (err: any) {
    console.error('[mint] error:', err);
    res.status(500).json({ error: err.message ?? 'Mint failed' });
  }
});

// ─── Start ────────────────────────────────────────────────────────────────────

const PORT = parseInt(process.env.PORT ?? '3000', 10);
app.listen(PORT, () => {
  console.log(`\nClawPhunks Mint Server`);
  console.log(`  Port       : ${PORT}`);
  console.log(`  Network    : ${isTestnet ? 'Base Sepolia' : 'Base'} (x402)`);
  console.log(`  Mint       : ${MINT_PRICE_USDC} USDC`);
  console.log(`  Gas stipend: ${Number(GAS_STIPEND_WEI) / 1e18} ETH`);
  console.log(`  Escrow     : ${ESCROW_CONTRACT}\n`);
});

export default app;


path: supabase/schema.sql
content: -- ClawPhunks Database Schema

-- ============================================================================
-- ITEMS: All 10k phunks with their pre-built data URIs
-- ============================================================================

CREATE TABLE IF NOT EXISTS items (
  token_id INTEGER PRIMARY KEY,
  data_uri TEXT NOT NULL,
  minted BOOLEAN DEFAULT FALSE,
  minted_to TEXT,
  tx_hash TEXT,
  minted_at TIMESTAMPTZ,
  traits JSONB,

  CONSTRAINT valid_token_id CHECK (token_id >= 0 AND token_id < 10000)
);

CREATE INDEX idx_items_unminted ON items(token_id) WHERE minted = FALSE;
CREATE INDEX idx_items_minted_to ON items(minted_to) WHERE minted = TRUE;

-- ============================================================================
-- FUNCTIONS
-- ============================================================================

-- Get a random unminted item and lock it
CREATE OR REPLACE FUNCTION claim_random_item(recipient TEXT)
RETURNS TABLE(token_id INTEGER, data_uri TEXT) AS $$
DECLARE
  item RECORD;
BEGIN
  SELECT i.token_id, i.data_uri INTO item
  FROM items i
  WHERE i.minted = FALSE
  ORDER BY RANDOM()
  LIMIT 1
  FOR UPDATE SKIP LOCKED;

  IF item IS NULL THEN
    RAISE EXCEPTION 'No items available';
  END IF;

  UPDATE items SET minted = TRUE, minted_to = recipient, minted_at = NOW()
  WHERE items.token_id = item.token_id;

  RETURN QUERY SELECT item.token_id, item.data_uri;
END;
$$ LANGUAGE plpgsql;

-- Mark item as minted with tx hash
CREATE OR REPLACE FUNCTION finalize_mint(p_token_id INTEGER, p_tx_hash TEXT)
RETURNS VOID AS $$
BEGIN
  UPDATE items SET tx_hash = p_tx_hash WHERE token_id = p_token_id;
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- RLS
-- ============================================================================

ALTER TABLE items ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public read items" ON items FOR SELECT USING (true);
CREATE POLICY "Service write items" ON items FOR ALL USING (true) WITH CHECK (true);


path: test-cdp.mjs
content: import { Coinbase, Wallet } from "@coinbase/coinbase-sdk";

const apiKeyId = "b2b9e2b9-6594-4cd7-8e7f-ccb8625a6ffe";
const apiKeySecret = [REDACTED_SECRET];

Coinbase.configure({
  apiKeyName: apiKeyId,
  privateKey: apiKeySecret,
});

// List wallets
try {
  const wallets = await Wallet.listWallets();
  console.log("Wallets found:", wallets.data.length);
  
  for (const wallet of wallets.data) {
    console.log("Wallet:", wallet.id, "Network:", wallet.network_id);
    console.log("  Default Address:", wallet.default_address?.address_id);
  }
} catch (e) {
  console.error("Error:", e.message);
  console.error(e);
}


path: tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": "src",
    "resolveJsonModule": true,
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}


path: vercel.json
content: {
  "version": 2,
  "builds": [
    {
      "src": "src/server.ts",
      "use": "@vercel/node"
    },
    {
      "src": "public/**",
      "use": "@vercel/static"
    }
  ],
  "routes": [
    {
      "src": "/llms.txt",
      "dest": "/public/llms.txt"
    },
    {
      "src": "/skill.md",
      "dest": "/public/skill.md"
    },
    {
      "src": "/.well-known/agent.json",
      "dest": "/public/.well-known/agent.json"
    },
    {
      "src": "/(.*)",
      "dest": "src/server.ts"
    }
  ]
}


path: wrangler.toml
content: name = "clawphunks"
account_id = "e637b514208eef42597ba94aab8fe85c"

[[r2_buckets]]
binding = "BUCKET"
bucket_name = "clawphunks"


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)