API reference
Public read-only endpoints at https://api.samefirst.com. All responses are JSON; amounts are wei strings (1 BNB = 10¹⁸ wei) and times are Unix seconds.
Rate limits and caching
| Item | Rule |
|---|---|
| Most endpoints | 120 requests per minute per IP |
/api/flap*, /api/meta, /api/tx*, /api/bind/* | 30 per minute per IP (reads the chain, simulates a call or calls Flap) |
| Over the limit | 429 with retry_after_s in the body |
| Caching | Responses carry cache-control and x-cache: HIT/MISS; the same URL returns the same data within its cache window |
| Endpoint | Cache |
|---|---|
/api/launches, /api/launch/*, /api/recent | 3 s |
/api/me/*, /api/airdrops* | 5 s |
/api/stats, /api/profile/* | 10 s |
/api/dao, /api/hot | 15 s |
/api/name/*, /api/address/*, /api/search | 30 s |
/api/flap, /api/flap/estimate, /api/vanity, /api/timeline | 60 s |
/api/binding/* | 3 seconds |
/api/decode | 5 minutes |
/api/health, /api/meta, /api/admin/*, /api/tx*, /api/bind/* | not cached |
For anything more frequent, read the on-chain events directly (see Contracts).
Rounds
GET /api/launches
List rounds. By default open rounds come first, then full, then launched; each group is ordered by the sort.
| Param | Notes |
|---|---|
sort | progress (default, fill progress) · people · amount |
stage | Open · Full · Launched · Done · Refunding; all if omitted |
limit | default 50, max 200 |
Each item has: address, creator (the address that sent the transaction), creator_id (the Agent ID the issuer acts for: itself, or the ID it is bound to; null if none), creator_name, template_id (game template, 0 = Classic), binding (binding contract this round locked in), kind (TaxV6/TaxV6Vault/PlainV7), name, symbol, meta, pool, quote_token, slip_bps, fee_bps, buy_tax_bps, sell_tax_bps, min_per_id, max_per_id, target, raised, left_bnb, progress (0–1, fill progress), subscribers, stage, created_time, filled_at, launched_at, graduated_at (when it graduated on Flap and liquidity moved to the DEX), token, spent, tokens_bought, fee_charged, plus tax_split (tax distribution in bps: mkt creator, burn, div dividends, lp liquidity; null for a standard token) tax_to_dao (whether the whole creator share goes to the Samefirst DAO), flap_blocked (Flap refuses the round's name, symbol or both, so it can't launch even when full; null when fine or not checked yet) and refund_at (when such a full round can be refunded; otherwise null).
GET /api/launch/:addr
All fields for one round, plus its current subscribers (subscribers: who is the address that paid and receives, id the Agent ID it counts for, name that ID's name; with a bound wallet who ≠ id), exits and settlements, and stats:
| Field | Notes |
|---|---|
first_sub_time, exits, exited_people | first subscription time; number of exits and of people who exited |
distributed, distributed_at | people paid out at distribution, and when it finished |
graduated_at, dex_pool | graduation time and DEX pool |
holders | addresses holding the token after launch (replayed from its on-chain transfers) |
real_holders, real_share | how many of them hold an Agent ID, and their share of the supply (0–1) |
top10_share | share of the 10 largest holders, leaving out contracts (bonding curve, DEX pool, the round) unless the contract itself holds an Agent ID |
GET /api/launch/:addr/wall
Name wall: subscription events (subscribe, top up, exit). limit defaults to 100, max 500.
GET /api/me/:addr
addr may also be a name (with or without .ai); looking needs no wallet. An address's Agent ID, rounds it joined or created, its amounts, and the DAO airdrops it received (airdrops: one row per batch and token, with amount, delivered or not, transaction hash). Joined rounds also carry my_balance (current holding), my_since (when this holding began), my_received, my_refunded and my_exit_back; my_payee is the address that actually paid and receives in that round (the bound wallet when the ID joined through it). For an Agent ID, rounds its bound wallet joined for it are listed too. binding is the first three fields of /api/binding below. profile is as below.
GET /api/profile/:id
Name-card data; id is a name (with or without .ai) or an address. profile has joined (rounds joined), at_launch (rounds still in at launch), created, holding (launch tokens still held), hold_days (days the longest current holding has lasted), airdrops (airdrop batches received), early (among the first 50,000 claims) and pending_airdrops (airdrops being sent but not yet delivered, summed per token).
GET /api/hot?limit=10
Real-person leaderboard: open rounds by current subscribers, launched ones by holders with an Agent ID (real_people), most first, up to 50.
Wallet binding
GET /api/binding/:who
who is an address or a name. Returns:
| Field | Meaning |
|---|---|
contract | binding contract new rounds use |
wallet | when who is an Agent ID: its bound wallet (address, bound_at, tx), or null |
id | when who is a wallet: the Agent ID it acts for (address, name, bound_at, tx), or null |
next_bind_at, nonce | Agent ID: earliest time it may bind again (0 = now), current signature nonce |
wallet_free_at | wallet: earliest time it may be bound (0 = now) |
history | last 50 binding events: kind (bound / unbound / revoked), id_addr, wallet, detail (how it was unbound: wallet / id / id_signature / replaced), block_time, tx_hash |
Agent hand-off (order codes)
The Agentic Wallet only works through chat. These endpoints prepare and check a transaction or an authorization to sign, store it under a 6-character code (valid 30 minutes, fetched once) for the Agent to run with the Samefirst skill. They only ever produce calls to Samefirst round contracts or the binding contract, never token approvals or transfers.
POST /api/tx
{ "action": "subscribe", "launch": "0x…", "from": "0x…", "amount": "0.05" }| Field | Meaning |
|---|---|
action | subscribe (subscribe or top up) · exit · claim · refund · withdraw · withdraw_tokens |
launch | round contract |
from | the address that will send it (Agentic Wallet or bound wallet) |
amount | BNB to subscribe (or amount_wei); subscribe only |
lang | optional; en gives an English summary (default Chinese); /api/bind/request takes it too |
check | optional; true only checks and simulates: the same transaction comes back without an order code (no code / expires_at) |
Checks, in order: the round exists and is at the right stage; which Agent ID from acts for; whether that ID already joined this round from another address; the amount against the per-person limits and the room left; finally a simulation from from. On failure: 400 with {"error": "reason (Chinese)", "code": "ErrorCode"}. On success:
{ "kind": "tx", "code": "NHJQU3", "expires_at": 1793008991, "chain_id": 56, "action": "subscribe",
"launch": "0x…", "from": "0x…", "to": "0x…", "value": "50000000000000000", "value_bnb": "0.05",
"data": "0x8f449a05", "function": "subscribe()", "summary": "…" }POST /api/bind/request
{ "name": "alice", "wallet": "0x…" } // bind
{ "action": "unbind", "name": "alice" } // unbind the current walletChecks the name, cooldowns and whether the wallet is taken, and returns the EIP-712 authorization to sign: kind: "sign", code, signer (the Agent ID address), wallet, binding, deadline, digest (the contract's own hash, to check the signature against), summary, and typed_data (domain Samefirst Binding / 1 / 56 / binding contract, primaryType Bind or Unbind; ready for eth_signTypedData_v4).
GET /api/tx/:code
The Agent fetches either of the above with the code (verbatim, typed_data keys in field order). Once only: again returns 410 (CodeUsed); expired returns 410 (CodeExpired); unknown returns 404.
Verification and entry lists
Each round's issuer picks one or more verification methods, and meeting any one of them is enough to subscribe: identity sources registered in the factory (agent_id Binance Agent ID, babt BABT, holding token balance checked at subscription), the issuer's whitelist (whitelist, Agent IDs or ordinary addresses), or open to every address (open). In /api/launch/:addr and /api/launches, ways lists the round's methods (in the order the contract tries them), sources gives the identity-source details (the token and threshold for holdings), and open_mode says whether the round is open to every address; /api/launches?way=babt and the like filter by method. The identity settled on someone's first subscription (an Agent ID, or the address itself) never changes afterwards. The issuer can also keep their own blacklist; the platform also keeps a global blacklist and banned token names. The rules are enforced on-chain (SamefirstLists, SamefirstGate); these endpoints make them visible and usable. Blocking an Agent ID also blocks the wallet bound to it; people already in are unaffected.
GET /api/lists
The platform blacklist blocked (address, name, reason, time, tx), banned token names names, the issuers' removal limit max_unblocks and the appeal link appeal; enabled false means the lists aren't live yet. Public page: samefirst.com/lists.
GET /api/launch/:addr/eligible/:who
who is an address or an Agent ID name. Returns ok and the identity it joins as, id (an Agent ID or the address itself); when it can't join, code: 2 platform blacklist, 3 this round's blacklist, 4 meets none of the round's verification methods, 5 the round's whitelist isn't available, with the reason in text; when joining via the whitelist, proof is the whitelist proof to subscribe with. The answer comes straight from SamefirstGate.canEnter on-chain.
POST /api/whitelist
{"names":["alice","bob.ai"],"addresses":["0x…"]}, up to 50,000. Addresses holding an Agent ID count as that ID; any other address counts as itself. Returns the Merkle root (added to a round as a whitelist batch, at creation or later by the issuer), the valid count and what couldn't be used (invalid). Once a round on-chain uses the root, the full list is published on IPFS and its CID shows in that round's whitelist_cid and in the endpoint below.
GET /api/whitelist/:root
All addresses and names of a whitelist. Leaf = keccak256(bytes.concat(keccak256(abi.encode(address)))) (an Agent ID or an ordinary address), pairs hashed sorted (OpenZeppelin MerkleProof): anyone can rebuild the root from the IPFS copy.
POST /api/whitelist/snapshot · GET /api/whitelist/snapshot/:id
Holder snapshot: {"token":"0x…","min":"<smallest unit>","block":<block, 0 = latest>} starts a background job and returns an id; polling returns status (running / done / failed), progress, and when done the root, cid and count. The platform reads every holder at or above the threshold at that block (sorted by balance) and stores them as a whitelist batch, used just like the result of POST /api/whitelist. It needs the token's full transfer history, so tokens created too long ago return failed.
Subscribing to a whitelist round
Call subscribe(bytes32[] proof) (selector 0xe38c9a53) with the proof; a plain subscribe() is refused for an address that can only get in through the whitelist. POST /api/tx works out whether a proof is needed and adds it; when someone is barred it returns GloballyBlocked, BlockedInRound, NotEligible, NotWhitelisted or NoWhitelist; a round hidden by the platform returns Hidden. /api/launch/:addr also has: in launch, gated, whitelisted (exclusive round), open_to_all (the issuer lifted the whitelist), open_at (when subscriptions open; 0 = at creation), preparing (not open yet; /api/tx answers NotOpenYet) and max_unblocks; the whitelist batches whitelists (root, cid, count, time) and their total whitelist_count; the round blacklist round_blocks (who, name, blocked, unblocks, time); and the project info info (below). A new stage, Cancelled, is a round its issuer dropped while preparing.
Community, launch wallet and vaults
GET /api/launch/:addr/comments·POST /api/launch/:addr/comments: comments. Posting takes a wallet signature over a message starting with "Samefirst 评论" (message,signature); only the round's issuer, its subscribers and, after launch, token holders can post, one per minute, up to 500 characters.POST /api/comments/:id/reportreports a comment.- Rounds hidden by the platform:
hiddenis true in/api/launch/:addr; they don't appear in listings and take no new subscriptions. GET /api/launcher/:addr: this issuer's (counted per Agent ID if they have one) fixed launch addresslauncher, itsbalance, and the gas to prepay at creationprepay(wei; 0 when the balance is enough). For the first 10 minutes after a round fills, only this address can launch it.GET /api/vaults: the Flap vault templates to choose from (those featured in Flap's vault store, without the Split Vault), with category and names and descriptions in Chinese and English;GET /api/vault/:factory?quote=0x…: whether a factory can be used (allowed,reasons), whether it's officially audited (audited), and its description ofvaultData(schema).dex_pairin/api/launch/:addris the pair with the most liquidity on DexScreener after launch, used for the price chart.
Tax dividends
For rounds that give their tax to the DAO, the tax recipient on Flap is the round's own splitter contract (SamefirstSplitter, whose address is derived from the round's address and the DAO share). /api/launch/:addr has splitter (null if none), splitter_bps (DAO share, in basis points) and splitter_deployed. In /api/flap, samefirst has splitter_factory and split_dao_bps: the factory address and the default DAO share for new rounds.
GET /api/launch/:addr/dividends/:who
What who (the subscribing address) can claim now: alloc (tokens originally allocated), factor (holding factor, 1e18 = 100%), deployed, and per asset asset (the zero address for BNB), symbol, due (before the holding factor) and pay (after it). Before the splitter contract is deployed, this is estimated from the balance at its address.
GET /api/dividends/:who
who is an address or an Agent ID name: the rounds with a splitter contract it took part in, each as above, plus payee (the address that actually claims; subscriptions made by a bound wallet on its behalf are listed too).
Claiming
action: "dividend" in POST /api/tx: the transaction goes to the splitter contract and calls claim(address[]) (selector 0x318d9e5d) to claim every asset with a balance. Returns NotDeployed if the splitter contract isn't deployed yet, NotSubscriber if the address isn't a subscribing address, and NothingToDo if there's nothing to claim.
Tax to an X account
A tax token's tax can go to an X account (SamefirstXSplitter; its address follows from the round's address, the account's keccak256("x:" + numeric id), the account's share and the claim window). /api/flap's samefirst block has x_splitter_factory (null = not offered), x_window (claim window, seconds) and x_max_bps (8000).
GET /api/x/user?handle=foo: look an account up:id,handle,name,avatar,followers; 404 if none.POST /api/x/intent{launch, x_id, x_bps, window}: the create page registers the round's X splitter before sending the creation; returnssplitter(= the on-chainpredict). Once the round exists the indexer keeps only the one that really is its tax recipient.x_taxin/api/launch/:addr(null if none):handle,name,avatar,x_bps,window,deployed,deadline,status(waiting/claimed/expired: gone to the DAO),claimed_at,paid(per asset: paid to the accountwallet, to the DAOdao, forfeited to the DAOforfeit). The wallet itself is not public.- Claiming:
GET /api/x/login?back=<a page of the site>signs in with X and returns with#xs=<session>;GET /api/x/me(Authorization: Bearer <session>) lists the rounds that name the account and what waits;POST /api/x/claim{splitter, wallet}names the wallet, which the platform binds on chain with its claim key (bind; no gas or signature from the owner);POST /api/x/logout.
Project info
Community links, a contact and announcements the issuer shows on samefirst.com: off-chain, editable any time, every version kept and public.
POST /api/launch/:addr/info
{"message":"…","signature":"0x…"}: text the issuer (the creating address, or a wallet bound to the issuer's Agent ID) signs with personal_sign, carrying the content itself:
Samefirst 项目资料
首发:0x…
时间:<unix seconds>
{"links":[{"type":"telegram","url":"https://t.me/…"}],"contact":"…","note":"…"}type is telegram, discord, x, website or other; up to 10 http(s) links; contact up to 200 characters, note up to 1,000. A signature is good for 10 minutes and must be newer than the stored version; at most 10 edits an hour.
GET /api/launch/:addr/info/history
Every version, newest first, with the signed text and signature, so anyone can verify them.
info in /api/launch/:addr: links, contact, note, updated_at, versions, has_address (the text contains a wallet address); when the operators hid it, only hidden and hidden_reason.
Error codes
Contracts revert with error codes, which an Agent's preview may show only as hex.
| Endpoint | Meaning |
|---|---|
GET /api/decode?data=0x… | turns revert data (or a whole error message containing it) into words: code, selector, text (Chinese) |
GET /api/errors | every error code with its 4-byte selector and text |
Samefirst DAO
GET /api/dao
Total income (launch_fees, exit_fees, swept), recent flows, and the DAO wallet safe: its address and live BNB balance (bnb_wei).
totals: tax commission (commission_bnb), tax share (tax_share_bnb: BNB the rounds' splitters paid the DAO), donations (donations_bnb, donations count including token gifts), spending (spent_bnb, split into token buys spent_buy_bnb and other spent_other_bnb), airdrop_batches, airdrop_people.
ledger: the multisig's inflows and outflows, newest first. Each entry has dir (in / out), kind, time, tx, n (transfers) and amounts:
kind | Meaning |
|---|---|
launch_fee · exit_fee · sweep | launch fees, exit fees (merged per round and day), swept dust |
tax_commission | Flap's tax commission from a tax token (symbol = the source token; paid in a pool token it carries token_symbol and decimals) |
tax_share | The tax share a round's splitter paid the DAO, one line per round and day; via is subscribers (subscriber splitter) or x (X-account splitter); paid in a pool token it carries token_symbol and decimals. Details: /api/dao/detail?kind=tax_share&launch=…&day=… |
donation | BNB sent straight to the multisig |
token_in | tokens sent to the multisig (token_symbol, decimals) |
buy · spend | BNB sent by the multisig: to Flap or PancakeSwap counts as a token buy, anything else as spending (each MultiSend entry counted) |
airdrop · token_out | tokens sent by the multisig, one entry per transaction (tokens lists each token); entries matched to an airdrop batch carry batch_id and batch_name |
GET /api/airdrops
Published airdrop batches (sending, done, cancelled), newest first. Each has id, name, note, rule (all_ids / launches / custom), tokens (per token: token, symbol, decimals, total_wei, sent_wei), recipients, sent (people fully paid), transfers, transfers_sent, status (active / done / cancelled), created_at, published_at, done_at, snapshot_block.
GET /api/airdrops/:id
One batch and its recipients, paged. Params: page, size (max 500), q (address or name prefix), status (pending still owed something / sent fully paid). Each recipient has address, handle, status (pending / partial / sent) and items (one per token: pos, amount_wei, status, tx_hash, sent_at).
GET /api/airdrops/:id/csv
The full list as CSV, one row per recipient and token, with delivery status.
Flap parameters
GET /api/flap
The samefirst field holds the factory's current settings for new rounds: factory, binding, fee_bps, min_target / max_target (raise range, wei), min_per_id_floor, official_vaults_only, abandon_after (seconds a full round waits for its launch), paused, and templates (each with id, name, impl, status, ready_at; active ones carry creation_cost, what creating a round costs).
The rest are Flap's current parameters read from chain: Portal version, trading fee (bps), BNB curve (r, h, k, graduation reserve), and the pool tokens Flap lists (enabled = Flap has opened it). Any other token can be checked with GET /api/flap/custom?token=…&bnb=…: whether Flap admits it (with Flap's signed price for a custom one), the PancakeSwap path the platform would launch through, what that much BNB buys, and the price impact (impact_bps; above 500 the platform does not launch).
GET /api/flap/estimate?progress=58&pool=BNB
Estimate the raise needed to reach a curve progress after launch, on the BNB curve. progress is in (0, 100]; non-BNB pools include 20% headroom at 100%.
GET /api/flap/namecheck?name=MyCoin&symbol=ETH
Asks Flap's anti-scam module whether it would refuse this name and symbol (it refuses well-known coins like ETH, BTC, BNB, USDT). Returns {"name_ok": true, "symbol_ok": false}. A refused round fails at launch (BlacklistedNameOrSymbol), so check before creating one. Cached 10 minutes.
Logo upload
POST /api/meta
Used when creating a launch: uploads the token logo and its details to Flap, pinned on IPFS, and returns a cid. That cid is the token's meta field, which Flap and samefirst.com both read the logo and links from. Browsers can't call Flap's upload endpoint directly because of cross-origin rules, so our backend forwards it.
The request is multipart/form-data:
| Field | Required | Notes |
|---|---|---|
image | yes | Logo: PNG, JPEG, WebP, SVG or GIF, up to 3 MB (same as Flap); square, 512×512 or more, is best |
description | no | Token description, up to 256 characters (same as Flap) |
website | no | Website link |
twitter | no | X link |
telegram | no | Telegram link |
Success returns 200:
{ "cid": "bafkreictuhft3yl5icnswbh52nhth3sjjcgrk7d4qnxglhuqehwgfznw3q" }The cid points to a JSON document (description, website, twitter, telegram, image, …) whose image is the picture's own CID. You can view it through Flap's gateway, e.g. https://flap.mypinata.cloud/ipfs/<cid>.
| Status | Meaning |
|---|---|
400 | No image, image over 3 MB or not a supported format, description over 256 characters, or a malformed form |
429 | Rate limited (30 per minute per IP, shared with /api/flap*) |
502 | Flap's upload failed or returned no cid (the body's detail carries Flap's error) |
Example:
curl -X POST https://api.samefirst.com/api/meta \
-F "image=@logo.png;type=image/png" \
-F "description=One-line pitch" \
-F "website=https://example.com" \
-F "twitter=https://x.com/example"Uploads are public and permanent on IPFS and can't be deleted, so only upload the material meant for the token.
Other
The admin endpoints under /api/admin/* are for DAO administration only (wallet sign-in) and aren't listed here.
Agent ID
| Endpoint | Notes |
|---|---|
GET /api/stats | Total Agent IDs and growth |
GET /api/name/:handle | Look up by name (without .ai) |
GET /api/address/:addr | Look up by address |
GET /api/search?q= | Name prefix search, up to 20 |
GET /api/recent?limit= | Latest registrations, default 50, max 200 |
GET /api/timeline?bucket=3600&hours=72 | Registrations over time |
GET /api/vanity | Three-character names, lucky-number names, earliest names |
GET /api/health | Service status and cache stats |