Skip to content
StablepadStablepad
0105 · Public API

API

JSONNo API keyBase units

Everything the site does, you can do with a Keypair. All endpoints are JSON, live under https://stablepad.one/api, need no API key, and return amounts in base units (6 decimals) as strings. Transactions come back as base64: sign them, send them yourself.

Your keys never leave your machine. The server only builds unsigned bytes.

01 · Model

Model

A meme dollar is an SPL token with a fixed supply of 1,000,000 (6 decimals), no mint or freeze authority, and one Meteora DLMM pool against a stablecoin where the whole supply sits in bin 0 at price 1.00. The position belongs to a program vault: the program has no instruction that removes liquidity from it, so only trades move the backing (buyers pay in, sellers get $1.00 back, any time).

  • Every meme dollar is backed 1:1 by the stablecoin people paid for it. Sell it back for $1.00 any time — the backing stays locked in the pool, so no creator and no team, not even Stablepad, can run off with it.
  • Sold out? Then there are no more at $1. The $1 floor stays forever because the pool keeps its stablecoin. Hard floor, no upside cap after sellout.
  • 0.5% per swap (after Meteora's protocol share), split 50/50 between the creator and Stablepad. Stablepad's half buys back and burns $STABLEPAD.
02 · Conventions

Conventions

Rate limit
POST /api/launch/build: 10 requests per minute per IP by default (429 with Retry-After). The other write endpoints have their own per-IP limits (listed on each entry below); reads are not limited but please poll at ≥ 5 s.
Errors
4xx/5xx with { "error": { "code": "…", "message": "…" } }
Amounts
Strings of base units. "1000000" = 1.000000. Inputs like amount and devBuy are decimal token/quote units ("10").
Files
/i/<mint>.png token image (512×512 max), /m/<mint>.json metadata. Both immutable, cached one year.
03 · Endpoints

Endpoints

POST/api/launch/build

Builds the launch transactions for a new meme dollar. Generates the mint keypair server-side and keeps it in memory for 15 minutes so you can retry.

Rate limit
429 rate_limited — more than RATE_LIMIT_BUILD_PER_MIN builds per minute per IP
Request
{
  "name": "Dollar Doge",            // ≤ 32 chars
  "symbol": "DOGE",                 // ≤ 10 chars, A–Z 0–9
  "image": "data:image/png;base64,…", // PNG/JPEG/WebP ≤ 1 MB; omit when "uri" is set
  "uri": "https://…/metadata.json", // optional: host your own metadata instead of an image
  "description": "…",               // optional, ≤ 500 chars
  "twitter": "https://x.com/…",     // optional
  "telegram": "https://t.me/…",     // optional
  "website": "https://…",           // optional
  "quoteToken": "USDC",             // USDC | USDT | USD1 (default USDC)
  "payer": "<your wallet pubkey>",
  "devBuy": "25",                   // optional, quote units to buy in the same flow
  "mint": "<mint from a previous build>" // optional: resume a launch after a blockhash expiry
}
Response
{
  "transactions": ["<base64>", "<base64>", "<base64>?"], // sign & send in order
  "steps": [{ "label": "Create the token", "description": "…" }, …],
  "sizes": [678, 993, 724],                                // bytes, each ≤ 1232
  "mint": "…", "pool": "…", "vault": "…", "position": "…", "quoteMint": "…",
  "lastValidBlockHeight": 123456789,
  "estimatedNetworkCostLamports": "188496130",            // rent + fees, as a string (≈ 0.19 SOL)
  "estimatedNetworkCostSol": "0.18849613",
  "resumed": { "mint": false, "pool": false, "binArray": false, "vault": false, "devBuy": false },
  "requirements": { "quoteToken": "USDC", "quoteBalance": "any non-zero amount", "sol": "about 0.19 SOL" }
}

The first transaction is already partially signed by the mint keypair; add the payer signature and send. When uri is omitted the image is converted to a 512×512 PNG at /i/<mint>.png and the metadata JSON is served from /m/<mint>.json. If a transaction expires, call the endpoint again with the same body plus mint: only the transactions that have not landed are returned. Before you build: the payer wallet must hold a non-zero balance of the chosen quote token (any amount, it is not spent — Meteora requires the pool creator to hold both tokens of a new pool) and about 0.19 SOL for rent and fees (the exact figure comes back as estimatedNetworkCostLamports).

Errors
CodeStatus
validationa field is missing or malformed400
no_quote_balancethe payer wallet holds none of the chosen quote token (Meteora's token-launch proof)400
insufficient_solthe payer wallet holds less SOL than the rent and fees of the remaining steps (error carries required and balance)400
mint_existsresuming a mint that exists with a different configuration409
forbiddenresuming with a different payer403
resume_expiredthe mint keypair left memory (15 minutes)410
rate_limitedmore than RATE_LIMIT_BUILD_PER_MIN builds per minute per IP429
chain_not_configuredthe server has no RPC or quote mints for this cluster503

POST/api/launch/confirm

Records the launch once the vault exists on-chain and returns the token row.

Rate limit
429 rate_limited — more than 20 confirms per minute per IP
Request
{ "mint": "…", "signatures": ["…", "…"] }
Response
{ "ok": true, "token": { "mint": "…", "name": "…", "symbol": "…", "pool": "…", "vault": "…", … } }

Only vaults whose quote mint is USDC, USDT or USD1 are recorded. Metadata hosted elsewhere is fetched server-side with the same field rules as /api/launch/build (public http(s) hosts only, 64 KB max). The last signature is stored as the launch transaction only when it is a confirmed transaction that touched the mint or the vault, and only the first time the token is recorded.

Errors
CodeStatus
vault_missingthe vault account does not exist yet409
unsupported_quotethe vault's quote mint is not one of the configured stablecoins400
rate_limitedmore than 20 confirms per minute per IP429

GET/api/tokens?sort=newest|volume|sold|backing&q=&limit=&cursor=

Lists meme dollars. Amounts are base units (6 decimals) as strings.

Response
{
  "tokens": [{ "mint": "…", "name": "…", "symbol": "…", "imageUrl": "…", "quoteSymbol": "USDC",
               "sold": "123000000", "backing": "123000000", "volume24h": "0", "holders": 3, "launchedAt": "2026-09-23T10:00:00.000Z" }],
  "nextCursor": "<mint>" | null
}

GET/api/tokens/{mint}

One token with the live peg state read from the pool and the unclaimed fees.

Response
{
  "token": { …TokenCard, "description", "pool", "vault", "position", "creator", "feesTotal", "claimable", "volumeTotal", … },
  "peg": { "sold": "…", "remaining": "…", "backing": "…", "backingRatio": 1, "activeId": 0, "binStep": 1, "feeBps": 50, "positionFeesOwed": "…" } | null,
  "claimable": { "quote": "…", "x": "0" } | null,
  "live": true
}
Errors
CodeStatus
not_foundunknown mint404

GET/api/tokens/{mint}/swaps?limit=50

Latest swaps indexed for the pool.

Response
{ "swaps": [{ "sig": "…", "slot": "…", "ts": "…", "side": "buy" | "sell", "amountToken": "…", "amountQuote": "…", "fee": "…", "trader": "…" }] }

GET/api/tokens/{mint}/holders?limit=50

Largest holders (the pool reserve is excluded).

Response
{ "holders": [{ "owner": "…", "amount": "…" }], "total": 42 }

POST/api/swap/build

Builds a buy or sell through the Meteora DLMM pool. amount is in meme dollars (token units) for both sides.

Rate limit
429 rate_limited — more than 60 builds per minute per IP
Request
{ "mint": "…", "side": "buy" | "sell", "amount": "10", "user": "<pubkey>", "slippageBps": 50 }
Response
{
  "transaction": "<base64>",
  "quote": { "side": "buy", "amountIn": "10050252", "amountOut": "10000000", "minAmountOut": "10000000", "maxAmountIn": "10100503",
             "fee": "40202", "protocolFee": "10050", "priceImpact": 0 }
}

Buys are exact-out: buying 10.000000 costs 10.050252 quote (0.5% on the input, rounded up) and the transaction reverts above maxAmountIn. Sells are exact-in: selling 10.000000 returns 9.950000 quote (0.5% on the output), reverting below minAmountOut. fee is the LP share after Meteora's protocol share (protocolFee).

Errors
CodeStatus
NOT_ENOUGH_REMAININGthe buy exceeds what is left at $1.00; the error carries available (base units)400
NOT_ENOUGH_BACKINGthe sell exceeds the stablecoin in bin 0 (nobody has bought enough yet); carries available400
NO_VAULTthe mint has no Stablepad vault on this cluster404
AMOUNT_ZEROamount is zero or above the supply400
not_foundthe mint is not indexed by this site404
unsupported_quotethe token's vault is not backed by USDC, USDT or USD1400
rate_limitedmore than 60 builds per minute per IP429

POST/api/swap/quote

The same quote as /api/swap/build without a transaction: use it for previews while the user types.

Request
{ "mint": "…", "side": "buy" | "sell", "amount": "10", "slippageBps": 50 }
Response
{ "quote": { "side": "buy", "amountIn": "10050252", "amountOut": "10000000", "minAmountOut": "10000000", "maxAmountIn": "10100503", "fee": "40202", "protocolFee": "10050", "priceImpact": 0 } }

Same error codes as /api/swap/build. For a buy the amount the wallet may debit at most is maxAmountIn; for a sell the least it receives is minAmountOut. Limited to 120 quotes per minute per IP.

POST/api/claim/build

Builds the permissionless claim_fees transaction. The caller pays the fee; the proceeds go to the creator and the treasury, 50/50.

Rate limit
429 rate_limited — more than 30 builds per minute per IP
Request
{ "mint": "…", "caller": "<pubkey>" }
Response
{ "transaction": "<base64>" }
Errors
CodeStatus
not_foundthe mint is not indexed by this site404
rate_limitedmore than 30 builds per minute per IP429

POST/api/tx/confirm

Waits up to 30 s for a signature you just sent to confirm and re-indexes that token so the dashboard updates within seconds.

Rate limit
429 rate_limited — more than 30 confirms per minute per IP
Request
{ "signature": "<base58 signature>", "mint": "…" }
Response
{ "ok": true, "status": "confirmed", "token": { … } }
Errors
CodeStatus
validationsignature or mint is not base58400
tx_failedthe transaction landed with an error400
confirm_timeoutthe signature did not confirm within 30 s (the worker still picks the swap up)504
rate_limitedmore than 30 confirms per minute per IP429

GET/api/stablepad

$STABLEPAD mint (null before launch), treasury wallet, queued quote balance and the buyback-and-burn history.

Response
{ "mint": "…" | null, "treasury": "…", "queuedQuote": "…" | null, "queuedBreakdown": [{ "symbol": "USDC", "amount": "…" }], "events": [{ "id": 1, "ts": "…", "kind": "received|swap_to_sol|buy|burn", "sig": "…", "quoteAmount": "…", "solLamports": "…", "stablepadAmount": "…", "note": "" }] }

GET/api/program

Peg program id and whether its upgrade authority is None (cached 5 minutes; a failed read is retried after 15 s and flagged unavailable).

Response
{ "programId": "…", "programData": "…", "deployed": true, "upgradeAuthority": null | "<pubkey>", "unavailable": false }

GET/api/health

Liveness: the database decides ok; the RPC probe is informational and gives up after 2.5 s.

Response
{ "ok": true, "db": true, "rpc": true, "slot": 123, "cluster": "mainnet-beta", "time": "…" }
04 · Example

Node.js example: launch with a Keypair

Builds, signs and sends the launch transactions in order, retries after a blockhash expiry by resuming with the same mint, then records the launch. Run with KEYPAIR=~/.config/solana/id.json node launch.mjs.

launch.mjs
// launch.mjs — launches a meme dollar from a Keypair and buys the first 25 with the dev buy.
// npm i @solana/web3.js
import fs from "node:fs";
import { Connection, Keypair, VersionedTransaction, Transaction } from "@solana/web3.js";

const API = process.env.STABLEPAD_API ?? "https://stablepad.fun";
const RPC = process.env.RPC_URL ?? "https://api.mainnet-beta.solana.com";
const payer = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(process.env.KEYPAIR, "utf8"))));
const connection = new Connection(RPC, "confirmed");

const image = "data:image/png;base64," + fs.readFileSync("./doge.png").toString("base64");

async function post(path, body) {
  const res = await fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
  const json = await res.json();
  if (!res.ok) throw new Error(`${path} → ${json.error.code}: ${json.error.message}`);
  return json;
}

function sign(bytes) {
  try {
    const tx = VersionedTransaction.deserialize(bytes);
    tx.sign([payer]);
    return { raw: tx.serialize(), blockhash: tx.message.recentBlockhash };
  } catch {
    const tx = Transaction.from(bytes);
    tx.partialSign(payer);
    return { raw: tx.serialize(), blockhash: tx.recentBlockhash };
  }
}

const form = {
  name: "Dollar Doge",
  symbol: "DOGE",
  description: "One doge, one dollar.",
  quoteToken: "USDC",
  payer: payer.publicKey.toBase58(),
  devBuy: "25",
};

let build = await post("/api/launch/build", { ...form, image });
console.log("mint", build.mint, "cost ≈", Number(build.estimatedNetworkCostLamports) / 1e9, "SOL");

const signatures = [];
let rebuilds = 0;
while (build.transactions.length > 0) {
  const step = build.steps[0];
  const { raw, blockhash } = sign(Buffer.from(build.transactions[0], "base64"));
  try {
    const sig = await connection.sendRawTransaction(raw, { maxRetries: 3 });
    const res = await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight: build.lastValidBlockHeight }, "confirmed");
    if (res.value.err) throw new Error(`${step.label} failed: ${JSON.stringify(res.value.err)}`);
    console.log("✓", step.label, sig);
    signatures.push(sig);
    build = { ...build, transactions: build.transactions.slice(1), steps: build.steps.slice(1) };
  } catch (e) {
    if (!/expired|block height exceeded|Blockhash not found/i.test(String(e)) || rebuilds++ >= 3) throw e;
    console.log("blockhash expired, rebuilding remaining steps");
    build = await post("/api/launch/build", { ...form, mint: build.mint }); // returns only what has not landed
  }
}

const { token } = await post("/api/launch/confirm", { mint: build.mint, signatures });
console.log("live at", `${API}/t/${token.mint}`);

Then swap

swap.mjs
// buy 10 meme dollars, then tell the site to re-index
const { transaction } = await post("/api/swap/build", { mint, side: "buy", amount: "10", user: payer.publicKey.toBase58() });
const { raw, blockhash } = sign(Buffer.from(transaction, "base64"));
const { lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
const signature = await connection.sendRawTransaction(raw);
await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, "confirmed");
await post("/api/tx/confirm", { signature, mint });
05 · On-chain

On-chain

You do not need the API at all: the program stablepad_peg has two instructions, init_vault and claim_fees, and every pool is a standard Meteora DLMM pool that any DLMM client can swap through. The $STABLEPAD page shows the treasury and its burn history.