Claude Code Skill

This page contains a self-contained Claude Code skill file for launching tokens via the Blowfish Agent API. Copy the content below into a .md file and load it into your OpenClaw agent.

Usage

  1. Copy the skill content below into a file (e.g., blowfish-launch.md)

  2. Add it to your agent's skill directory

  3. The agent can then authenticate with the Blowfish API and launch tokens programmatically

Skill file

# Blowfish Token Launch Skill

You are an AI agent that can launch tokens on Solana using the Blowfish Agent API.

## API Base URL

`https://api-blowfish.neuko.ai`

## Authentication

The API uses wallet-based challenge-response auth. You need a Solana keypair.

### Auth flow

1. **Get challenge**: `POST /api/auth/challenge` with `{ "wallet": "<base58-address>" }`
   - Returns `{ "nonce": "<nonce>" }`
   - Nonce expires in 5 minutes

2. **Sign and verify**: Sign the message `Sign this message to authenticate: <nonce>` with ed25519, base58-encode the signature, then `POST /api/auth/verify` with:
   ```json
   {
     "wallet": "<base58-address>",
     "nonce": "<nonce>",
     "signature": "<base58-signature>"
   }
   ```
   - Returns `{ "token": "<jwt>" }`
   - JWT expires in 15 minutes

3. **Use JWT**: Include `Authorization: Bearer <jwt>` on all subsequent requests.

### TypeScript auth example

```typescript
import { Keypair } from "@solana/web3.js";
import nacl from "tweetnacl";
import bs58 from "bs58";

async function authenticate(keypair: Keypair): Promise<string> {
  const wallet = keypair.publicKey.toBase58();
  const base = "https://api-blowfish.neuko.ai";

  const { nonce } = await fetch(`${base}/api/auth/challenge`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ wallet }),
  }).then((r) => r.json());

  const message = `Sign this message to authenticate: ${nonce}`;
  const sig = nacl.sign.detached(new TextEncoder().encode(message), keypair.secretKey);

  const { token } = await fetch(`${base}/api/auth/verify`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ wallet, nonce, signature: bs58.encode(sig) }),
  }).then((r) => r.json());

  return token;
}
```

## Endpoints

### Launch a token

`POST /api/v1/tokens/launch`

**Auth required.** Launches a new token on Solana via Meteora DBC. This is asynchronous -- returns an event ID for polling.

Request body:

| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `name` | string | yes | 1-255 chars |
| `ticker` | string | yes | 2-10 chars, uppercase alphanumeric (`^[A-Z0-9]+$`) |
| `description` | string | no | max 1000 chars |
| `imageUrl` | string | no | max 255 chars, valid URL |

Response:

```json
{
  "success": true,
  "message": "We will notify you when your token is deployed",
  "eventId": "uuid-v4"
}
```

Errors: `400` validation failed, `409` ticker exists, `401` auth error.

**Rate limit:** 1 launch per agent per day (UTC).

### Check launch status

`GET /api/v1/tokens/launch/status/:eventId`

**Auth required.** Poll this endpoint after launching.

Response:

```json
{
  "status": "pending | success | failed | rate_limited",
  "processedAt": "ISO-8601 or null"
}
```

Poll every 5-10 seconds. Stop when status is `success` or `failed`.

### List your tokens

`GET /api/v1/tokens/`

**Auth required.** Returns all tokens launched by the authenticated agent.

Response:

```json
{
  "tokens": [
    {
      "poolAddress": "...",
      "tokenMint": "...",
      "ticker": "MCT",
      "tokenName": "My Cool Token",
      "isClaimed": false,
      "claimedAt": null,
      "claimedToAddress": null,
      "deployedAt": "ISO-8601"
    }
  ]
}
```

### Get a specific token

`GET /api/v1/tokens/:mintAddress`

**Auth required.** Returns a single token by its Solana mint address.

Response: Same shape as a single item in the tokens list above.

### Get claimable fees

`GET /api/v1/tokens/claims`

**Auth required.** Returns fee data for all tokens launched by the authenticated agent.

Response:

```json
{
  "claims": [
    {
      "tokenMint": "...",
      "poolAddress": "...",
      "ticker": "MCT",
      "dbcClaimableFees": 0.25,
      "dbcTotalFees": 1.0,
      "dbcClaimedFees": 0.75,
      "lpClaimableFees": 0.1,
      "lpTotalFees": 0.3,
      "lpClaimedFees": 0.2,
      "isMigrated": true
    }
  ]
}
```

### Claim fees for a token

`POST /api/v1/tokens/claims/:mintAddress`

**Auth required.** Two-step process: get unsigned tx, sign it, submit it back.

**Step 1** -- POST without body to get unsigned transaction:

```json
{
  "success": true,
  "transaction": "base64-encoded-unsigned-transaction"
}
```

**Step 2** -- Sign the transaction with your agent's private key (ed25519).

**Step 3** -- POST with signed transaction:

```json
{ "signedTransaction": "base64-encoded-signed-transaction" }
```

Response:

```json
{
  "success": true,
  "transactionHash": "...",
  "claimedSOL": 0.35
}
```

### Health check

`GET /health`

**No auth required.**

Response:

```json
{
  "ok": true,
  "db": "connected",
  "timestamp": "ISO-8601"
}
```

## Error handling

All errors follow this pattern:

```json
{
  "error": "Human-readable error message"
}
```

Common errors:

| Status | Error | Resolution |
|--------|-------|------------|
| 400 | Validation failed | Check request body against schema |
| 401 | Missing or invalid authorization header | Re-authenticate |
| 401 | Invalid or expired token | JWT expired, get a new one |
| 404 | Launch not found / Token not found | Check event ID or mint address |
| 409 | Ticker already exists | Choose a different ticker |

## Workflow

When asked to launch a token:

1. Authenticate using the wallet keypair
2. Call `POST /api/v1/tokens/launch` with token details
3. Poll `GET /api/v1/tokens/launch/status/:eventId` every 5 seconds
4. Report the final status to the user
5. On success, use `GET /api/v1/tokens/:mintAddress` to get full token details

Integration notes

  • The skill assumes the agent has access to a Solana keypair for signing

  • The agent should handle JWT expiry gracefully by re-authenticating when it receives a 401 response

  • The daily rate limit (1 launch per agent per day) resets at UTC midnight

Last updated