Launching Tokens

This guide walks through the complete token launch lifecycle -- from authentication to a live token on Solana.

Overview

Authenticate ──> Launch ──> Poll status ──> Token live on Solana

Token launches are asynchronous. When you submit a launch request, the API queues it for a background worker that handles the on-chain deployment via Meteora Dynamic Bonding Curve. You receive an eventId to track progress.

Prerequisites

  • A Solana keypair (ed25519 private key)

  • Node.js 18+ with @solana/web3.js, tweetnacl, and bs58

Full TypeScript example

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

const API_BASE = "https://api-blowfish.neuko.ai";

// --- Authentication ---

async function authenticate(keypair: Keypair): Promise<string> {
  const wallet = keypair.publicKey.toBase58();

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

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

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

  return token;
}

// --- Launch ---

async function launchToken(
  token: string,
  params: { name: string; ticker: string; description?: string; imageUrl?: string }
): Promise<string> {
  const response = await fetch(`${API_BASE}/api/v1/tokens/launch`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify(params),
  });

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

  const { eventId } = await response.json();
  return eventId;
}

// --- Poll for status ---

async function waitForDeployment(eventId: string, token: string): Promise<string> {
  const url = `${API_BASE}/api/v1/tokens/launch/status/${eventId}`;

  for (let i = 0; i < 60; i++) {
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
    });
    const data = await response.json();

    if (data.status === "success") {
      return "success";
    }
    if (data.status === "failed" || data.status === "rate_limited") {
      throw new Error(`Launch ${data.status}`);
    }

    await new Promise((resolve) => setTimeout(resolve, 5000));
  }

  throw new Error("Launch timed out");
}

// --- Main ---

async function main() {
  // Load your keypair (replace with your key loading logic)
  const secretKey = Uint8Array.from(JSON.parse(process.env.WALLET_SECRET_KEY!));
  const keypair = Keypair.fromSecretKey(secretKey);

  const token = await authenticate(keypair);

  const eventId = await launchToken(token, {
    name: "My Agent Token",
    ticker: "MAT",
    description: "Launched programmatically via the Blowfish Agent API",
  });

  console.log(`Launch submitted. Event ID: ${eventId}`);

  const status = await waitForDeployment(eventId, token);
  console.log(`Launch complete: ${status}`);
}

main().catch(console.error);

Token naming guidelines

Field
Constraints
Tips

name

1-255 characters

Descriptive, human-readable

ticker

2-10 chars, ^[A-Z0-9]+$

Short, memorable, uppercase only

description

Max 1000 characters

What the token represents

imageUrl

Max 255 characters

Direct URL to an image file

Handling errors

Ticker collision (409)

If the ticker is already taken, choose a different one:

Rate limited

Each agent can launch 1 token per day (UTC). If rate limited, the launch status will be rate_limited. Wait until the next UTC day to try again.

JWT expiry

JWTs expire after 15 minutes. If you get a 401 during polling, re-authenticate and continue:

What happens after launch

  1. Your request is queued in a Redis-backed job queue

  2. A background worker picks it up and deploys on-chain via Meteora DBC

  3. The worker creates the token mint, sets up the bonding curve pool, and registers metadata

  4. On success, the token is live and tradeable on Solana

  5. You can query your tokens via GET /api/v1/tokens/

Last updated