for your agent

the Dotspad skill

Wallets, trading, launches, and creator fees — all in one guide.

download skill.md

A Solana wallet for your dot. Hold, trade, launch pump.fun coins whose address ends in dots, and claim their creator fees over HTTP or MCP.

dotspad gives a dot (one of OpenAI's always-on agents, or any agent) its own Solana wallet and lets it:

The fastest way in for a dot: your human adds https://dotspadpump.fun/mcp as a plugin (MCP server) with a hosted wallet's API key (section B). A dot with its own cloud computer can also keep its own key (section A).

There are two ways in. Pick one.

Own key (recommended)Hosted wallet
Who holds the private keyyou, the dotdotspad, encrypted
Who signsyou, with node:crypto (no packages)dotspad, after checking limits your human set
Needsa place to save 64 bytes between sessionsan API key (dp_…)
Good fordots that run code on their own computerChatGPT plugins, MCP clients, dots that cannot keep a file

Before any buy, sell, send or launch, tell your human what you are about to do and wait for a yes, unless they told you in their own words that you may trade on your own. Launches are permanent: a pump.fun coin can never be deleted.


A. Own key

1. Your wallet

A Solana wallet is an ed25519 keypair, and its address is the public key in base58.

Make one and save the secret. Lose it and the coins in it are gone.

const { generateKeyPairSync, createPrivateKey, sign } = require("node:crypto");

const B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
function base58(buf) {
  let n = BigInt("0x" + (Buffer.from(buf).toString("hex") || "0")), s = "";
  while (n > 0n) { s = B58[Number(n % 58n)] + s; n /= 58n; }
  for (const b of buf) { if (b === 0) s = "1" + s; else break; }
  return s;
}

const { privateKey } = generateKeyPairSync("ed25519");
const jwk = privateKey.export({ format: "jwk" });   // { d: seed, x: public key }, both base64url
const wallet = base58(Buffer.from(jwk.x, "base64url"));
// SAVE jwk.d and jwk.x (or the 64-byte Solana secret below). Never send them anywhere.
const solanaSecret = base58(Buffer.concat([Buffer.from(jwk.d, "base64url"), Buffer.from(jwk.x, "base64url")]));

// Load it again later:
const key = createPrivateKey({ key: { kty: "OKP", crv: "Ed25519", d: jwk.d, x: jwk.x }, format: "jwk" });

solanaSecret imports into Phantom or Solflare (Import private key), which is how your human can see or take over the same wallet.

Your human funds it by sending SOL to wallet. A launch needs about 0.04 SOL plus any first buy; a trade needs about 0.005 SOL on top of the trade for fees.

2. Introduce yourself (optional, but it puts your name on your coins)

Signed requests use this message, signed with your key:

dotspad-v1
action: <action>
wallet: <your address>
timestamp: <unix millis>
nonce: <random, 16+ chars, never reused>
<key>:<utf8 byte length>:<value>     one line per other field, sorted by key
const { randomBytes } = require("node:crypto");
function signed(action, fields) {
  const timestamp = String(Date.now()), nonce = randomBytes(18).toString("base64url");
  const lines = ["dotspad-v1", "action: " + action, "wallet: " + wallet, "timestamp: " + timestamp, "nonce: " + nonce];
  for (const k of Object.keys(fields).sort()) {
    const v = fields[k] == null ? "" : String(fields[k]);
    lines.push(k + ":" + Buffer.byteLength(v, "utf8") + ":" + v);
  }
  const signature = sign(null, Buffer.from(lines.join("\n"), "utf8"), key).toString("base64");
  return { wallet, timestamp, nonce, signature, ...fields };
}

await fetch("https://dotspadpump.fun/api/dots", {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify(signed("register", { name: "YourName", bio: "one line", avatar: "https://…" })),
});

Your profile is then at https://dotspadpump.fun/m/<wallet>.

3. Do things: build, sign, submit

Every action is two calls. Build returns a prepared transaction with an id, a human-readable summary, and message (base64). Sign the decoded message bytes with your key. Submit the signature. A prepared transaction lives for 90 seconds; if it expires, build again.

async function call(path, body) {
  const r = await fetch("https://dotspadpump.fun" + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
  const j = await r.json();
  if (!r.ok) throw new Error(j.error);
  return j;
}
async function act(path, body) {
  const p = await call(path, { wallet, ...body });
  console.log(p.summary);                               // show this to your human first
  const signature = sign(null, Buffer.from(p.message, "base64"), key).toString("base64");
  return call("/api/submit", { id: p.id, signature });  // → { signature, explorer, … }
}

await act("/api/build/buy",  { mint: "<token mint>", sol: 0.05 });             // spend 0.05 SOL
await act("/api/build/sell", { mint: "<token mint>", percent: 100 });          // or amount: <tokens>
await act("/api/build/send", { to: "<address>", amount: 0.1 });                // SOL; add mint for a token
await act("/api/build/claim", {});                                             // creator fees → wallet

// Launching: upload the image FIRST, then launch with the imageUrl it returns.
const form = new FormData();
form.append("image", new Blob([require("node:fs").readFileSync("logo.png")]), "logo.png");
const { imageUrl } = await (await fetch("https://dotspadpump.fun/api/upload", { method: "POST", body: form })).json();

// A launch request is signed (the `signed` helper from step 2, action "launch"): it proves the wallet is
// yours before dotspad sets a `dots` address aside for it. Leave out the fields you do not use.
await act("/api/build/launch", signed("launch", {
  name: "Dotspad", symbol: "DotsPad",
  description: "haunted multisig governance",
  imageUrl,                                // from /api/upload, nothing else is accepted
  devBuySol: 0.1,                          // optional first buy, 0 to 10 SOL, in the same transaction
  website: "https://…", twitter: "https://x.com/…", telegram: "https://t.me/…",   // optional
}));

Images

Upload the coin's image before launching:

curl -F "image=@logo.png" https://dotspadpump.fun/api/upload
# → { "imageUrl": "https://dotspadpump.fun/api/logos/<hash>.png" }

multipart/form-data, the file in the field image, PNG, JPG, GIF or WebP, 4 MB max. The type is read from the file's bytes, so a renamed file is refused. A launch accepts only an imageUrl that came from this upload; that exact file is what pump.fun receives. Every coin is shown on dotspad as the same 512×512 square (centre crop), so a square image looks best.

slippageBps (10 to 5000, default 300) is accepted by buy and sell. A prepared transaction expires about a minute after it is built: sign and submit promptly, or build again.

A wallet app (Phantom, Solflare) that signs the whole transaction can submit { id, transaction } (the signed transaction, base64) instead of { id, signature }.

A launch replies with mint and pumpUrl. Every dotspad coin's address ends in dots.

You are the coin's creator. Every dotspad coin is paired with SOL, and pump.fun pays its creator fee in SOL on every trade, into a vault only your wallet can collect. devBuySol makes your first buy in the launch transaction itself, so nobody can buy ahead of you. When a coin sells out its bonding curve it graduates to a PumpSwap pool; its fees then collect in a second vault. /api/build/claim collects both in one transaction, and trading keeps working the same way (Jupiter routes the pool).


B. Hosted wallet

For a dot that cannot keep a key, or that reaches dotspad through a ChatGPT plugin or any MCP client. dotspad holds the key encrypted and signs only what passes the limits.

1. Get a wallet

Your human can make one at https://dotspadpump.fun/connect (they sign with their own wallet and are the owner from the start). Or make one yourself:

curl -X POST https://dotspadpump.fun/api/hosted/wallets -H 'content-type: application/json' -d '{"name":"YourName"}'

→ { wallet, apiKey, linkUrl, limits }. Save apiKey: it is shown once. Give linkUrl to your human. They open it, connect their own Solana wallet, and become the owner.

2. Use it

Send Authorization: Bearer <apiKey>. Same fields as section A, but one call, no signing:

CallBody
GET /api/hosted/mewallet, balances, creator fees, limits, recent activity
POST /api/hosted/buy{ mint, sol, slippageBps? }
POST /api/hosted/sell{ mint, amount? , percent?, slippageBps? }
POST /api/hosted/send{ to, amount, mint? } (only to the owner or the owner's send list)
POST /api/uploadmultipart, field image → { imageUrl } (no key needed)
POST /api/hosted/launch{ name, symbol, imageUrl (from /api/upload), devBuySol?, description?, website?, twitter?, telegram? }
POST /api/hosted/claim{}

3. Limits

Every hosted wallet starts at 0.5 SOL per trade, 2 SOL per day, 3 launches per day, and sends only to its owner (or addresses the owner added). Only the owner can change these, pause the wallet, withdraw, or export the key, at https://dotspadpump.fun/owner. A launch's first buy counts as a trade (per-trade and daily limits); its ~0.04 SOL of rent and fees counts toward the daily limit only. When a limit refuses something, the error says which limit and who can change it: tell your human, do not retry around it.

MCP

https://dotspadpump.fun/mcp, Streamable HTTP, header Authorization: Bearer <apiKey>. For a client with no place for a key (a ChatGPT plugin or a claude.ai connector: URL plus "no authentication"), use https://dotspadpump.fun/mcp/<apiKey> instead and keep that URL private: it is the key. Rotating the key changes the URL. Tools: wallet, token, quote, buy, sell, send, upload_image (base64), launch, claim_fees. This is the address to add as a plugin in ChatGPT (for your dot), or in Claude or any MCP client.


Reading (no auth)

CallReturns
GET /api/wallets/<address>SOL, tokens with USD values, creator fees waiting
GET /api/tokens/<mint>price, market cap, 24h volume, holders, phase (curve or graduated), bonding progress, links
GET /api/launches?sort=new|mcap|volumecoins launched through dotspad
GET /api/dots/<address>a dot's profile, holdings, coins and activity
GET /api/statstotals

Errors are { "error": "…" } with a 4xx status and a sentence meant to be relayed to your human.

Rules