Skip to content
Earn is live — trading opens when the launchpad does. No coins are listed yet.
Developer API · v1

Build on Kaching.
You hold the keys.

Read every market, quote off the real curve, check what a creator can claim — and get back ready-to-sign transactions to launch coins, trade them, and claim fees. No SDK, no key handover, no waiting list.

The rule this API is built around

Kaching never takes custody, and never holds your key.

Every write endpoint returns an unsigned transaction { to, data, value, chainId } — that you sign and broadcast with your own wallet, paying your own gas. We build the calldata; you decide whether it ever reaches a node.

  • No endpoint accepts a private key or a seed phrase. Not now, not with a flag, not for “convenience”. If a page claiming to be Kaching ever asks you for one, it is not Kaching.
  • No endpoint broadcasts on your behalf. There is no relay, no “submit this signed tx” route, and no server-side wallet that could front-run or censor you.
  • Launching is on your own expense. You sign createToken, so you pay the gas — and you are stamped as the coin's immutable creator and the only address that can ever claim its creator fees.
  • Claiming pays you directly. claimCreatorFees() pays the curve's immutable creator. Kaching could not intercept it even if it wanted to.
Contents — 8 sections
01

The chain this API serves

One deployment, one chain. Every response repeats it in meta.chain so a client that talks to several Kaching instances can never mix them up.

The launchpad has not opened yet

The website and this API deliberately disagree right now, and you would otherwise have no way to tell. While meta.chain.launchPhase reads "prelaunch" the public site renders no coins, prices, market caps or trades at all — this API keeps serving whatever the index holds. Coin counts and volumes read here are not evidence of an open public market. Branch on meta.chain.launchPhase, meta.chain.chainId and meta.chain.realMoney, which ride on every response below.

Network
Kaching Testnet (demo)
Chain id
31337
Instance
local
Real money
No
Contracts
Deployed
LaunchConfig fields
10

Factory: 0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf. Coins launched on earlier factory generations stay indexed and tradeable — each coin's curve is fixed at birth. GET /api/v1/status lists every factory this deployment reads.

02

Conventions

Base URL

All v1 routes live under /api/v1. The version is in the path; a breaking change ships as /api/v2, never as a silent edit to this one.

Auth — none required

Every endpoint works anonymously. An optional Authorization: Bearer <key> only raises your rate limit. A key that is sent but not recognised is a 401, never a silent downgrade — if your key stops working you will be told.

CORS

Access-Control-Allow-Origin: * on every v1 route, for GET and POST. Credentials are never allowed, because nothing here is authenticated by a cookie — so a wildcard origin can never be leveraged into an authenticated read.

Amounts are strings

Every wei / token amount is a decimal string of base units, never a JSON number. A JSON number loses precision past 253, and a rounded wei amount is a wrong transaction. Human-scale figures (ETH, 0–1 percentages) stay numbers and are named so.

Caching

Set deliberately per route and stated on each one below. Prices, claimable balances and transaction payloads are no-store — a cached quote is a wrong quote. Errors are never cached.

Pagination

limit + offset, with total and nextOffset in the response. nextOffset is null on the last page.

One envelope, always
// success
{
  "ok": true,
  "data": { /* endpoint-specific */ },
  "meta": {
    "apiVersion": "1",
    "chain": { "key": "testnet", "chainId": 46630, "realMoney": false, "contractsDeployed": true,
               "launchPhase": "prelaunch" },  // prelaunch = the public site shows no coins; this API still does
    "generatedAt": 1785000000000,
    "source": "live",              // live | stale | sample  (indexed payloads only)
    "sampleData": false,           // true ONLY when no factory is deployed
    "notice": "..."                // disclosure, when one applies
  }
}

// failure
{
  "ok": false,
  "error": { "code": "INVALID_REQUEST", "message": "`ethIn` must be greater than zero.", "field": "ethIn" },
  "meta": { /* same meta */ }
}

Always means always: a mistyped path under /api/v1 answers 404 NOT_FOUND in this envelope, for every HTTP method, rather than the site’s HTML 404 page — and a real path called with the wrong method answers 405 METHOD_NOT_ALLOWED in it too, with an Allow header, rather than an empty body. response.json() never has to parse <!DOCTYPE html>, and never gets nothing at all.

03

What this API will not do

These are behavioural guarantees, not aspirations. They are the reason a few responses are less convenient than they could be.

Never a fabricated number

A failed chain or index read returns UPSTREAM_UNAVAILABLE, never a 0. On /creator/{wallet}/fees an unreadable balance comes back as null with read: "failed", and the total is explicitly marked partial — a creator whose RPC blipped must never be told they have nothing to claim.

Never a silent zero-slippage trade

If you omit minOut and the live quote needed to derive it cannot be read, /tx/buy and /tx/sell fail rather than build an unprotected trade. An explicit "minOut": "0" is honoured — with a warning — because you said it out loud.

Never an unlimited approval by default

The approval /tx/sell hands back is sized to exactly that sale. There is no approve-max convenience: an unlimited allowance is a standing permission, and a wallet prompt for one on a single sale is undisclosed.

Never a grade it cannot support

With too few holders to judge AND no measured concentration, /safety withholds the distribution score (score: null, grade: "—", insufficientData: true) — grading an unknown “A+” would be worse than silence. Concentration that IS measured always counts, however small the sample: a two-holder coin with one wallet at 79% is graded, and graded badly, because that is evidence rather than an unknown. An X-Ray that could not complete is inconclusive, never safe.

Never sample data in disguise

Fixtures appear only on a chain with no factory deployed, and always carry meta.source: "sample" plus meta.sampleData: true. Quotes refuse to answer at all in that state.

Never calldata guaranteed to revert

Send from and each builder dry-runs the exact call against current chain state. A revert comes back as SIMULATION_REVERTED with the reason — before you pay gas to discover it. An unreachable node degrades to preflight.simulated: false and says so rather than pretending the check passed.

04

Two things to try first

Claim your creator fees

Read what is claimable, then sign the transaction it hands back. The ETH goes from the curve straight to your wallet — it never touches Kaching.

Claim creator fees
# 1. what can this wallet claim, and on which curves?
curl "https://<host>/api/v1/creator/0xYourWallet/fees?includeTx=true"

# 2. or build the claim for one curve
curl -X POST https://<host>/api/v1/tx/claim-fees \
  -H 'Content-Type: application/json' \
  -d '{"curve":"0xCurveAddress","from":"0xYourWallet"}'

# 3. sign it yourself — viem
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";   // YOUR key, on YOUR machine

const { data } = await (await fetch(url, { method: "POST", body })).json();
const tx = data.transaction;                            // { to, data, value, chainId }
const hash = await wallet.sendTransaction({
  to: tx.to,
  data: tx.data,
  value: BigInt(tx.value),
});

Launch a coin

You sign createToken, so you pay the gas and you own the coin. Fee rates and guards lock at launch and can never be changed — by you or by anyone.

Launch a coin
# 1. check what this chain's factory expects
curl https://<host>/api/v1/status | jq '.data.contracts.launchConfigFields'

# 2. build the launch
curl -X POST https://<host>/api/v1/tx/launch \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Example Coin",
    "symbol": "EXMPL",
    "devBuyWei": "50000000000000000",
    "from": "0xYourWallet",
    "config": { "guardBlocks": 2, "antiBundle": true, "creatorFeeBps": 50 }
  }'

# 3. sign + broadcast it yourself, then read the new token address from the
#    TokenCreated event in the receipt.

# 4. attach the off-chain image / description / socials
curl -X POST https://<host>/api/metadata \
  -H 'Content-Type: application/json' \
  -d '{"address":"0xNewToken","name":"Example Coin","symbol":"EXMPL","description":"...","image":"data:image/png;base64,..."}'
05

Endpoints

Meta

GET

/api/v1/status

Which chain this API serves, and whether it can answer.

Call this first. It tells you the chain id and whether that chain handles real money, whether a Kaching factory is deployed, whether the RPC and index are currently healthy, the fixed curve constants, and — importantly for launches — how many fields this chain's factory expects in the LaunchConfig tuple. A degraded index is reported in the body as index.up: false, not as a 5xx. chain.launchPhase is on every envelope this API returns. While it is prelaunch the public Kaching website deliberately renders NO coins, prices, market caps or trades — the launchpad has not opened to the public — even though this API keeps serving whatever the index holds. Do not read coin counts or volumes from this deployment as evidence of an open, public market until it reads live.

Auth
None
Rate limit
120/min · 600 keyed
Cache-Control
public, max-age=10, s-maxage=10, stale-while-revalidate=60

No parameters.

Request
curl https://kaching.fun/api/v1/status
Response (illustrative)
{
  "ok": true,
  "data": {
    "api": { "version": "1", "docs": "/docs", "openapi": "/api/v1/openapi.json" },
    "chain": {
      "key": "local",
      "chainId": 31337,
      "name": "Anvil (Local)",
      "realMoney": false,
      "contractsDeployed": true,
      "factory": "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf",
      "launchPhase": "prelaunch"
    },
    "rpc": { "up": true, "blockNumber": "412903" },
    "index": { "up": true, "coins": 176, "source": "live" },
    "contracts": { "deployed": true, "launchConfigFields": 10, "graduationVenue": "a locked Kaching AMM pair" },
    "curve": { "totalSupply": "1000000000000000000000000000", "saleSupply": "793100000000000000000000000", "platformFeeBps": 50, "maxTotalFeeBps": 1000 },
    "custody": { "model": "non-custodial" }
  },
  "meta": { "apiVersion": "1", "generatedAt": 1785000000000 }
}

Coins

GET

/api/v1/coins

List coins — paginated, filterable, sortable.

Served from the same in-process index the website reads, so it adds no chain load and cannot drift from the site's own figures. NOTE: it does not follow that this list is what a visitor sees — while meta.chain.launchPhase is prelaunch the public site renders no coins, prices or trades at all, and this API is the only surface showing them. tab applies the site's own lane logic; sort overrides ordering; creator, graduated and q narrow the set. PRICE PROVENANCE: pricedFrom says where priceEth came from — curve on a GRADUATED coin is the frozen graduation price, not a market quote. pricedAtMs is the epoch-ms time of the pair read behind a pair price (null otherwise); the reserve cache keeps a pair's last SUCCESSFUL read rather than expiring it to a definitely-wrong number, so check meta.generatedAt - pricedAtMs before treating a pair price as live.

Auth
None
Rate limit
120/min · 600 keyed
Cache-Control
public, max-age=5, s-maxage=10, stale-while-revalidate=30
Parameters
NameType · inDescription
tabstringqueryApply one of the site's board lanes.one of: new | trending | graduating | graduated
sortstringqueryOrdering. Overrides the lane's own order. Defaults to newest-first when no tab is given.one of: new | old | volume | marketcap | holders | trending | progress
creatoraddressqueryOnly coins launched by this wallet.
graduatedbooleanquerytrue for graduated coins only, false for still-bonding only.
qstringqueryCase-insensitive substring match on name or symbol, or an exact token address.
limitintegerqueryPage size, 1–200.default: 25
offsetintegerqueryPage offset. Use nextOffset from the previous page.default: 0
Request
curl "https://kaching.fun/api/v1/coins?tab=trending&limit=2"
Response (illustrative)
{
  "ok": true,
  "data": {
    "coins": [
      {
        "address": "0x909DdB63ee1E1e0e4e0e0e0e0e0e0e0e0e0e0e0e",
        "curve": "0x2222222222222222222222222222222222222222",
        "name": "Example Coin",
        "symbol": "EXMPL",
        "creator": "0x1111111111111111111111111111111111111111",
        "createdAt": 1784900000,
        "imageUrl": "https://kaching.fun/api/token/0x909D.../image?v=1c9k3zqf",
        "priceEth": 0.0000000041,
        "pricedFrom": "curve",
        "pricedAtMs": null,
        "marketCapEth": 4.1,
        "progress": 0.214,
        "graduated": false,
        "pair": null,
        "holders": 38,
        "volume24hEth": 1.72,
        "reserves": { "eth": "1712000000000000000", "token": "902000000000000000000000000", "tokensSold": "170000000000000000000000000", "realEth": "312000000000000000" },
        "concentration": { "topHolderPct": 0.061, "creatorHeldPct": 0.02, "corneredFloat": false },
        "dev": { "coins": 4, "graduated": 1 }
      }
    ],
    "total": 41, "limit": 2, "offset": 0, "nextOffset": 2
  },
  "meta": { "apiVersion": "1", "source": "live", "generatedAt": 1785000000000 }
}
GET

/api/v1/coins/{address}

One coin, with its immutable launch and fee config.

{address} is the TOKEN address. The response's curve is what you trade against before graduation; pair is what you trade against after. launchConfig and feeConfig are the settings stamped into the coin at launch — they can never change.

Auth
None
Rate limit
120/min · 600 keyed
Cache-Control
public, max-age=5, s-maxage=10, stale-while-revalidate=30
Parameters
NameType · inDescription
addressrequiredaddresspathThe coin's ERC-20 token address.
Request
curl https://kaching.fun/api/v1/coins/0x909D...
Response (illustrative)
{
  "ok": true,
  "data": {
    "coin": {
      "address": "0x909D...", "symbol": "EXMPL", "graduated": false,
      "launchConfig": { "guardBlocks": 2, "maxBuyPerWalletGuard": "500000000000000000", "maxBuyPerBlockGuard": "0", "antiBundle": true, "snipeMaxBps": 500, "snipeWindowBlocks": 30 },
      "feeConfig": { "platformBps": 50, "creatorBps": 50, "burnBps": 0, "holdersBps": 100, "buybackBps": 0, "totalBps": 200 },
      "graduation": { "graduatedAt": null, "lpBurned": null, "ethLiquidity": null, "tokenLiquidity": null, "graduator": null },
      "pots": { "burnWei": "0", "holdersWei": "31200000000000000", "buybackWei": "0" }
    }
  },
  "meta": { "apiVersion": "1", "source": "live", "generatedAt": 1785000000000 }
}
GET

/api/v1/coins/{address}/holders

Holder distribution and concentration signals.

Balances are reconstructed from Trade logs, largest first. Every pct is a share of TOTAL supply, not of the circulating float — concentration.corneredFloat is the separate signal for one wallet owning most of what actually trades. circulating is the FLOAT: the sum of positive wallet balances, excluding the curve's unsold escrow and excluding burn sinks (which are reported separately as burned). So on a coin with a buyback, circulating is smaller than the supply sold, by exactly burned — divide by it only when you mean a share of the tradeable float, never for a share of supply.

Auth
None
Rate limit
120/min · 600 keyed
Cache-Control
public, max-age=15, s-maxage=30, stale-while-revalidate=60
Parameters
NameType · inDescription
addressrequiredaddresspathThe coin's token address.
limitintegerqueryPage size, 1–500.default: 100
offsetintegerqueryPage offset.default: 0
Request
curl "https://kaching.fun/api/v1/coins/0x909D.../holders?limit=3"
Response (illustrative)
{
  "ok": true,
  "data": {
    "holders": [
      { "address": "0x1111...", "balance": "20000000000000000000000000", "pct": 0.02, "isCreator": true }
    ],
    "count": 38, "total": 38, "limit": 3, "offset": 0, "nextOffset": 3,
    "circulating": "170000000000000000000000000",
    "burned": "0",
    "concentration": { "topPct": 0.061, "creatorPct": 0.02, "over5Count": 1, "corneredFloat": false, "warning": true }
  },
  "meta": { "apiVersion": "1", "source": "live", "generatedAt": 1785000000000 }
}
GET

/api/v1/coins/{address}/trades

Trade history, newest first.

Each row is a decoded on-chain event — the curve's Trade before graduation, the pair's Swap after — including the reserve state immediately after it, so you can replay price without indexing the chain yourself.

Auth
None
Rate limit
120/min · 600 keyed
Cache-Control
public, max-age=5, s-maxage=10, stale-while-revalidate=30
Parameters
NameType · inDescription
addressrequiredaddresspathThe coin's token address.
sidestringqueryFilter to one direction.one of: buy | sell
limitintegerqueryPage size, 1–500.default: 100
offsetintegerqueryPage offset.default: 0
Request
curl "https://kaching.fun/api/v1/coins/0x909D.../trades?side=buy&limit=1"
Response (illustrative)
{
  "ok": true,
  "data": {
    "trades": [
      {
        "trader": "0x3333...", "side": "buy",
        "ethAmount": "50000000000000000", "tokenAmount": "12800000000000000000000",
        "feeEth": "500000000000000",
        "reserveEthAfter": "1762000000000000000", "reserveTokenAfter": "889200000000000000000000000",
        "tokensSoldAfter": "182800000000000000000000000",
        "timestamp": 1784999100, "txHash": "0xabc..."
      }
    ],
    "total": 512, "limit": 1, "offset": 0, "nextOffset": 1
  },
  "meta": { "apiVersion": "1", "source": "live", "generatedAt": 1785000000000 }
}
GET

/api/v1/coins/{address}/safety

Distribution grade + on-chain Rug X-Ray facts.

Two separate things, deliberately not merged. distribution is a 0–100 grade derived from holder concentration — and it is WITHHELD (score: null, grade: "—") under five holders, because a one-wallet coin is the most concentrated state possible and grading it would be worse than saying nothing. xray is facts at a block, never a rating for tokens outside the Kaching factory. A scan that could not complete returns xray.available: false: inconclusive is never safe. Each entry in distribution.factors carries a basis, so you can tell WHY it is claimed before you render it as a tick. factory-registry means it follows from this address being a coin the Kaching factory launched (a fixed-supply, no-admin-key, no-transfer-tax contract by construction) — it is NOT the output of a bytecode scan, and it does not become false when xray.inconclusive is true. measured means it was computed from indexed holder balances. sample-fixture means this deployment has no factory and the figures are illustrative. If your UI shows factors as green ticks, show the basis with them.

Auth
None
Rate limit
30/min · 240 keyed
Cache-Control
public, max-age=30, s-maxage=60, stale-while-revalidate=120
Parameters
NameType · inDescription
addressrequiredaddresspathThe coin's token address.
Request
curl https://kaching.fun/api/v1/coins/0x909D.../safety
Response (illustrative)
{
  "ok": true,
  "data": {
    "address": "0x909D...",
    "distribution": {
      "score": 88, "grade": "A", "insufficientData": false, "headline": "Well distributed",
      "holders": 38, "topHolderPct": 0.061, "creatorHeldPct": 0.02, "corneredFloat": false,
      "factorsBasis": "factory-registry",
      "factorsBasisNote": "The fixed-supply / sells-always-open / no-admin-keys factors below hold because this address is a coin the Kaching factory launched...",
      "factors": [
        { "label": "Fixed supply — token has no mint function", "ok": true, "basis": "factory-registry" },
        { "label": "Sells always open — no honeypot or transfer tax", "ok": true, "basis": "factory-registry" },
        { "label": "No wallet holds more than 5% of supply", "ok": true, "basis": "measured" }
      ]
    },
    "xray": { "available": true, "report": { "mode": "curve-native", "isOurs": true, "blockNumber": "412903", "inconclusive": false, "facts": [] } },
    "inconclusive": false,
    "disclaimer": "This is a distribution grade plus on-chain facts at a block — not investment advice."
  },
  "meta": { "apiVersion": "1", "source": "live", "generatedAt": 1785000000000 }
}

Pricing

GET

/api/v1/quote

A price off the real curve (or the real pair).

Read live from the chain every time — there is no local fallback. The trading UI can fall back to the same constant-product maths because it labels the result an estimate on screen; an API response has no label a caller is forced to read, so a quote we could not get from the chain is an error, never a number. minOut is ready to paste into /tx/buy or /tx/sell — EXCEPT when it is **null**, which is what you get when the venue quotes zero output (tradable: false). Do not substitute 0 there: /tx/buy and /tx/sell honour an explicit minOut of 0 and would build a completely unprotected trade. feeBps is **null** on the few older curves that publish neither a decodable feeConfig() nor totalFeeBps() — it is never a default, so treat null as unknown and derive the real rate from feeEthWei / amountIn if you need one.

Auth
None
Rate limit
30/min · 240 keyed
Cache-Control
no-store
Parameters
NameType · inDescription
coinrequiredaddressqueryThe coin's token address.
siderequiredstringqueryDirection of the trade.one of: buy | sell
amountrequireduint256queryInput amount in BASE UNITS as a decimal string: ETH wei on a buy, token base units (18 dp) on a sell.
slippageBpsintegerqueryTolerance used to compute minOut, in basis points. Max 5000.default: 100
Request
curl "https://kaching.fun/api/v1/quote?coin=0x909D...&side=buy&amount=50000000000000000&slippageBps=100"
Response (illustrative)
{
  "ok": true,
  "data": {
    "coin": "0x909D...", "symbol": "EXMPL", "side": "buy", "graduated": false,
    "venue": "0x2222...", "quoteSource": "curve", "feeBps": 200,
    "amountIn": "50000000000000000", "amountOut": "12800000000000000000000",
    "amountInFormatted": "0.05", "amountOutFormatted": "12800",
    "feeEthWei": "1000000000000000",
    "tradable": true,
    "slippageBps": 100, "minOut": "12672000000000000000000",
    "priceImpactBps": 284,
    "reserves": { "eth": "1712000000000000000", "token": "902000000000000000000000000" },
    "quotedAt": 1785000000000
  },
  "meta": { "apiVersion": "1", "source": "live", "generatedAt": 1785000000000 }
}

Creator

GET

/api/v1/creator/{wallet}/fees

Creator fees this wallet can claim right now.

The coin list comes from the index; every claimable balance is re-read from the chain per curve. A read that fails comes back as claimableWei: null, read: "failed" and bumps totals.unreadCoins — never as a zero. While totals.partial is true, treat the total as a FLOOR. Pass ?includeTx=true to get the unsigned claim transaction inlined for every coin with a positive balance.

Auth
None
Rate limit
30/min · 240 keyed
Cache-Control
no-store
Parameters
NameType · inDescription
walletrequiredaddresspathThe creator wallet.
includeTxbooleanquerytrue to inline the unsigned claim transaction per coin with a positive balance.
Request
curl "https://kaching.fun/api/v1/creator/0x1111.../fees?includeTx=true"
Response (illustrative)
{
  "ok": true,
  "data": {
    "wallet": "0x1111...",
    "coins": [
      {
        "coin": "0x909D...", "curve": "0x2222...", "name": "Example Coin", "symbol": "EXMPL",
        "graduated": false, "createdAt": 1784900000,
        "claimableWei": "31200000000000000", "claimableEth": 0.0312, "read": "ok",
        "claimTx": { "chainId": 31337, "to": "0x2222...", "data": "0x351fee46", "value": "0", "from": "0x1111...", "function": "claimCreatorFees()" }
      }
    ],
    "totals": { "coins": 4, "claimableWei": "31200000000000000", "claimableEth": 0.0312, "unreadCoins": 0, "partial": false },
    "truncated": false,
    "howToClaim": { "endpoint": "POST /api/v1/tx/claim-fees" },
    "custody": "Kaching never takes custody..."
  },
  "meta": { "apiVersion": "1", "source": "live", "generatedAt": 1785000000000 }
}

Transactions

POST

/api/v1/tx/claim-fees

unsigned tx — you sign

Unsigned transaction: claim a coin's creator fees.

claimCreatorFees() pays the curve's IMMUTABLE creator and only the creator may call it — so Kaching could not claim on your behalf even if it wanted to. If you send from and it is provably not the creator, you get a named error instead of a wasted gas estimate.

Auth
None
Rate limit
60/min · 300 keyed
Cache-Control
no-store
Parameters
NameType · inDescription
curverequiredaddressbodyThe coin's bonding-curve address (from coin.curve).
fromaddressbodyThe address that will sign. Optional and informational — it is never used to sign anything. Supplying it enables the preflight simulation and (for sells) the allowance check.
Request
curl -X POST https://kaching.fun/api/v1/tx/claim-fees \
  -H 'Content-Type: application/json' \
  -d '{"curve":"0x2222...","from":"0x1111..."}'
Response (illustrative)
{
  "ok": true,
  "data": {
    "transaction": {
      "chainId": 31337,
      "to": "0x2222...",
      "data": "0x351fee46",
      "value": "0",
      "from": "0x1111...",
      "function": "claimCreatorFees()",
      "summary": "Claim 0.0312 ETH of accrued creator fees from this curve to the creator's wallet."
    },
    "preflight": { "simulated": true, "ok": true, "reason": null },
    "custody": "self",
    "curve": "0x2222...", "creator": "0x1111...",
    "accruedWei": "31200000000000000", "accruedEth": 0.0312
  },
  "meta": { "apiVersion": "1", "generatedAt": 1785000000000 }
}
POST

/api/v1/tx/launch

unsigned tx — you sign

Unsigned transaction: launch a coin.

You sign it, so you pay the gas and you are stamped as the coin's immutable creator and creator-fee recipient. Every fee rate and guard in config locks at launch and can never be changed afterwards. The LaunchConfig tuple grew 8 → 10 fields when the snipe-tax shipped, which changed the function selector — this endpoint encodes for whatever the active chain's factory actually expects (see contracts.launchConfigFields on /api/v1/status) and refuses a snipe-tax on an older factory rather than returning calldata that is guaranteed to revert. Image, description and socials are off-chain: POST them to /api/metadata once the launch confirms — that first write is unauthenticated and first-writer-wins, so post it immediately; if you lose the race or your POST fails, a write signed by the coin's on-chain creator replaces the stored details at any time.

Auth
None
Rate limit
60/min · 300 keyed
Cache-Control
no-store
Parameters
NameType · inDescription
namerequiredstringbodyToken name, up to 128 characters.
symbolrequiredstringbodyToken symbol, up to 32 characters.
metadataURIstringbodyOptional on-chain metadata URI stamped into the token. Up to 512 characters.default: ""
minTokensOutuint256bodySlippage floor for the same-transaction dev-buy, in token base units.default: 0
devBuyWeiuint256bodyETH (wei) to spend on an opening buy in the same transaction. Becomes the transaction's value.default: 0
configobjectbodyLaunch protections + fee composition. All fields optional and default to 0/false. Keys: guardBlocks (uint32), maxBuyPerWalletGuard (uint256 wei), maxBuyPerBlockGuard (uint256 wei), antiBundle (bool), creatorFeeBps, burnFeeBps, holdersFeeBps, buybackFeeBps (uint16 each), snipeMaxBps (≤2000), snipeWindowBlocks (≤100). The 50 bps platform fee plus your four legs must total ≤ 1000 bps. snipeMaxBps and snipeWindowBlocks must both be zero or both non-zero.
fromaddressbodyThe address that will sign. Optional and informational — it is never used to sign anything. Supplying it enables the preflight simulation and (for sells) the allowance check.
Request
curl -X POST https://kaching.fun/api/v1/tx/launch \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Example Coin",
    "symbol": "EXMPL",
    "devBuyWei": "50000000000000000",
    "minTokensOut": "0",
    "from": "0x1111...",
    "config": { "guardBlocks": 2, "antiBundle": true, "creatorFeeBps": 50, "holdersFeeBps": 100 }
  }'
Response (illustrative)
{
  "ok": true,
  "data": {
    "transaction": {
      "chainId": 31337,
      "to": "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf",
      "data": "0x...",
      "value": "50000000000000000",
      "from": "0x1111...",
      "function": "createToken(string,string,string,uint256,(uint32,uint256,uint256,bool,uint16,uint16,uint16,uint16,uint16,uint32))",
      "summary": "Launch \"Example Coin\" ($EXMPL) with a fixed 1,000,000,000 supply on a fresh bonding curve, including a 0.05 ETH opening dev-buy."
    },
    "preflight": { "simulated": true, "ok": true, "reason": null },
    "custody": "self",
    "launchConfigFields": 10,
    "fees": { "platformBps": 50, "creatorBps": 50, "burnBps": 0, "holdersBps": 100, "buybackBps": 0, "totalBps": 200, "capBps": 1000 }
  },
  "meta": { "apiVersion": "1", "generatedAt": 1785000000000 }
}
POST

/api/v1/tx/buy

unsigned tx — you sign

Unsigned transaction: buy a coin.

Targets the right venue automatically — the bonding curve before graduation, the AMM pair after. Slippage is mandatory in substance: pass a minOut floor, or a slippageBps and let the endpoint derive the floor from a live on-chain quote. If that quote cannot be read the request FAILS rather than silently building an unprotected trade with minOut = 0. An explicit "minOut": "0" is honoured, with a warning, because you said it out loud. venue.source names where the ROUTING decision came from — live from a current index read, stale when the background revalidate is failing, sample on a chain with no factory. It is scoped to the venue on purpose: the quote, minOut and expectedOut in the same payload are read from the chain at request time and are never stale. If you refuse to sign against a stale routing decision, branch on this.

Auth
None
Rate limit
60/min · 300 keyed
Cache-Control
no-store
Parameters
NameType · inDescription
coinrequiredaddressbodyThe coin's token address.
ethInrequireduint256bodyETH to spend, in wei, as a decimal string.
minOutuint256bodyMinimum tokens out, base units. Omit to have it derived from a live quote and slippageBps.
slippageBpsintegerbodyUsed only when minOut is omitted. Max 5000.default: 100
fromaddressbodyThe address that will sign. Optional and informational — it is never used to sign anything. Supplying it enables the preflight simulation and (for sells) the allowance check.
Request
curl -X POST https://kaching.fun/api/v1/tx/buy \
  -H 'Content-Type: application/json' \
  -d '{"coin":"0x909D...","ethIn":"50000000000000000","slippageBps":100,"from":"0x3333..."}'
Response (illustrative)
{
  "ok": true,
  "data": {
    "transaction": {
      "chainId": 31337, "to": "0x2222...", "data": "0xd96a094a...", "value": "50000000000000000",
      "from": "0x3333...", "function": "buy(uint256 minTokensOut) payable",
      "summary": "Spend 0.05 ETH buying $EXMPL on its bonding curve, reverting unless at least 12672 tokens come back."
    },
    "preflight": { "simulated": true, "ok": true, "reason": null },
    "custody": "self",
    "venue": { "address": "0x2222...", "kind": "bonding-curve", "feeBps": 200, "source": "live" },
    "minOut": "12672000000000000000000", "minOutSource": "quoted",
    "expectedOut": "12800000000000000000000", "slippageBps": 100
  },
  "meta": { "apiVersion": "1", "generatedAt": 1785000000000 }
}
POST

/api/v1/tx/sell

unsigned tx — you sign

Unsigned transaction: sell a coin (plus the approval it needs).

Selling is two transactions: an ERC-20 approval so the venue can pull your tokens, then the sell. Send from and this endpoint reads your current allowance and returns the approval transaction too, sized to EXACTLY this sale — never unlimited, because a wallet prompt for 'unlimited' on a one-off sale is an undisclosed standing permission. Broadcast the approval, wait for its receipt, then broadcast the sell. venue.source names where the routing decision came from (live | stale | sample) — approving and selling against a venue resolved from a stale index is how a post-graduation sell ends up pointed at a dead curve.

Auth
None
Rate limit
60/min · 300 keyed
Cache-Control
no-store
Parameters
NameType · inDescription
coinrequiredaddressbodyThe coin's token address.
tokenInrequireduint256bodyTokens to sell, in base units (18 dp), as a decimal string.
minOutuint256bodyMinimum ETH out, in wei. Omit to have it derived from a live quote and slippageBps.
slippageBpsintegerbodyUsed only when minOut is omitted. Max 5000.default: 100
fromaddressbodyThe address that will sign. Optional and informational — it is never used to sign anything. Supplying it enables the preflight simulation and (for sells) the allowance check.
Request
curl -X POST https://kaching.fun/api/v1/tx/sell \
  -H 'Content-Type: application/json' \
  -d '{"coin":"0x909D...","tokenIn":"12800000000000000000000","slippageBps":100,"from":"0x3333..."}'
Response (illustrative)
{
  "ok": true,
  "data": {
    "transaction": {
      "chainId": 31337, "to": "0x2222...", "data": "0xd79875eb...", "value": "0",
      "from": "0x3333...", "function": "sell(uint256 tokenIn, uint256 minEthOut)"
    },
    "preflight": { "simulated": false, "ok": null, "reason": "..." },
    "custody": "self",
    "venue": { "address": "0x2222...", "kind": "bonding-curve", "feeBps": 200, "source": "live" },
    "minOut": "48510000000000000", "minOutSource": "quoted",
    "approval": {
      "required": true, "spender": "0x2222...", "allowanceWei": "0",
      "transaction": { "chainId": 31337, "to": "0x909D...", "data": "0x095ea7b3...", "value": "0", "function": "approve(address spender, uint256 amount)" }
    }
  },
  "meta": { "apiVersion": "1", "generatedAt": 1785000000000 }
}
POST

/api/v1/tx/approve

unsigned tx — you sign

Unsigned transaction: the ERC-20 approval a sell needs.

/tx/sell returns this inline when it can see you need one; this endpoint exists for callers managing the approval step separately. spender defaults to the coin's CURRENT venue — approving the old one is the most common reason a sell fails after graduation. There is no approve-max convenience: pass the amount you mean. venueSource names where the venue this defaults to was resolved from (live | stale | sample), so you can refuse to grant an allowance to a spender chosen from a stale index.

Auth
None
Rate limit
60/min · 300 keyed
Cache-Control
no-store
Parameters
NameType · inDescription
coinrequiredaddressbodyThe coin's token address.
amountrequireduint256bodyAllowance to grant, in token base units.
spenderaddressbodyDefaults to the coin's current trading venue (curve, or pair once graduated).
fromaddressbodyThe address that will sign. Optional and informational — it is never used to sign anything. Supplying it enables the preflight simulation and (for sells) the allowance check.
Request
curl -X POST https://kaching.fun/api/v1/tx/approve \
  -H 'Content-Type: application/json' \
  -d '{"coin":"0x909D...","amount":"12800000000000000000000","from":"0x3333..."}'
Response (illustrative)
{
  "ok": true,
  "data": {
    "transaction": { "chainId": 31337, "to": "0x909D...", "data": "0x095ea7b3...", "value": "0", "function": "approve(address spender, uint256 amount)" },
    "preflight": { "simulated": true, "ok": true, "reason": null },
    "custody": "self",
    "spender": "0x2222...", "spenderIsCurrentVenue": true, "venueSource": "live",
    "amount": "12800000000000000000000", "currentAllowanceWei": "0"
  },
  "meta": { "apiVersion": "1", "generatedAt": 1785000000000 }
}
06

Error codes

A closed set. Branch on error.code, never on the message text — messages are written for humans and will change.

CodeHTTPMeaning
INVALID_REQUEST400A parameter or body field is missing, malformed, or out of range. The field key names the offender.
INVALID_ADDRESS400An address argument is not a valid EVM address.
NOT_FOUND404No such endpoint on this deployment, or no coin/wallet record exists at that address. Every path under /api/v1 answers in this envelope, including a mistyped one.
NOT_DEPLOYED503This chain has no Kaching factory deployed, so the request cannot be answered from contracts.
UNAUTHORIZED401An Authorization header was sent but the API key is not recognised. Drop the header to use the anonymous tier.
RATE_LIMITED429Too many requests. Back off and retry after the Retry-After header.
OVERLOADED503This endpoint is at its server-wide capacity. This is NOT a chain or index failure and nothing is wrong with your request — back off and retry after the Retry-After header.
UPSTREAM_UNAVAILABLE503A chain or index read failed. You get this instead of a zero — an unread number is never reported as 0. Transient by definition: it always carries a Retry-After.
SIMULATION_REVERTED422The transaction was built, but a preflight simulation against current chain state reverted. Signing it would burn gas for nothing.
UNSUPPORTED409Well-formed, but not possible on this deployment (e.g. a snipe-tax launch on a factory generation that predates it).
METHOD_NOT_ALLOWED405Wrong HTTP method for this endpoint. The response carries an Allow header naming the one method the path serves, and it arrives in this envelope on every v1 path — never as an empty body.
INTERNAL500An unexpected server error. Nothing was signed and nothing was broadcast, so there is nothing to reconcile.
07

Rate limits

Per-minute sliding windows, per IP (or per key), plus a server-wide backstop so one caller cannot starve everyone else. Exceeding your own window returns 429 RATE_LIMITED with a Retry-After header. Hitting the shared backstop returns 503 OVERLOADED — a separate code on purpose, because it is not your fault and it is not a chain outage, and the two call for the same action from you: back off.

Index reads

120/min · 600 keyed

Coin lists, coin detail, holders, trades.

Chain reads

30/min · 240 keyed

Quotes, creator fees, safety scans — each hits the chain.

Transaction builders

60/min · 300 keyed

Launch, buy, sell, approve, claim-fees.

Documents

60/min · 300 keyed

The OpenAPI spec.

The keyed ceiling is built but no keys are issued on this deployment yet — an Authorization header is rejected rather than upgraded, so the anonymous tier is the real limit today. Said plainly because a documented tier you cannot obtain is worse than one that does not exist. A key will raise the ceiling and nothing else: every endpoint is public and every write is still an unsigned transaction you sign yourself, so there are no extra powers to grant.

08

Deliberately not here

A broadcast / relay endpoint

Accepting a signed transaction to submit for you would make Kaching a chokepoint that can censor, delay, or reorder your trade. Broadcast it yourself, to any node you trust.

Anything that takes a key

No private key, no mnemonic, no keystore upload, no “managed wallet”. There is no configuration that turns this on.

Gas estimates

We do not return a gasLimit. Your node estimates against the state it will actually broadcast into; a number from our node would be a guess dressed as a fact. The preflight tells you whether the call succeeds, which is the part you cannot easily check yourself.

A price oracle

Quotes are point-in-time reads of a specific venue, not an oracle, and Kaching is not a price feed. Re-quote immediately before you sign.

Kaching is an independent project. It is not affiliated with, endorsed by, or operated by Robinhood Markets, Inc.; “Robinhood Chain” refers to the public blockchain network. This documentation describes protocol and API mechanics, not investment advice. Example responses on this page are illustrative — the live endpoints are the source of truth. Transactions returned by this API are unsigned: what you sign, you own.