Skip to content

Bot API

Follow the Heat and trade it from your own bot. You get the live race, a webhook when a token enters it, and swap transactions that are ready to sign, in one call. Your bot signs with its own key and sends the transaction. TRNCH never holds your funds and never sees your keys. Snipers and trading bots welcome.

Get a key

Connect your wallet, open Profile and create a key in the API section. You can have 3 active keys. A key is shown once: copy it then. We only keep a fingerprint, so a lost key cannot be recovered, only revoked and replaced. A key works for the wallet that created it and for no other: swaps built with it always pay that wallet.

Authorization: Bearer trnch_bot_YOUR_KEY

GET /heat

The current Heat race, the same data as the site: token, rank, Heat score, price, liquidity, volume, price changes, and when the token entered the Heat with its entry price. Rugs and tokens with no price or liquidity are left out. With since (epoch milliseconds or an ISO date) you only receive tokens that entered the Heat after it. Pass the nextSince of each response to your next call.

curl -H "Authorization: Bearer trnch_bot_YOUR_KEY" \
  "https://trnch.fun/api/bot/v1/heat"

# Only what entered the Heat after the last call (pass back nextSince):
curl -H "Authorization: Bearer trnch_bot_YOUR_KEY" \
  "https://trnch.fun/api/bot/v1/heat?since=1760000000000"

POST /buy and POST /sell

One call to trade. We ask every active router, as the site does, keep the best route and build the transaction for the wallet of your key. /buy takes token and either amountUsd (paid in USDG) or amountIn in the smallest unit of tokenIn (USDG by default). /sell takes token and either percent (a share of the balance your wallet holds, read on-chain) or amount in token units, and pays you in USDG. slippageBps is optional (default 100, from 10 to 5000). If the best route cannot be built any more, the next router is tried once. The answer is { router, quote, approval, tx, chainId, from }: send approval first when it is not null, then tx, both signed by your bot. Same guards as /build: no receiver field, rugs refused (409), and the same limits. When swaps are paused on TRNCH, it answers 503.

curl -X POST "https://trnch.fun/api/bot/v1/buy" \
  -H "Authorization: Bearer trnch_bot_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "token": "0xTOKEN_ADDRESS", "amountUsd": 25, "slippageBps": 150 }'

# Or pay with another token: "amountIn" in its smallest unit, plus "tokenIn" (USDG by default)
#   { "token": "0xTOKEN_ADDRESS", "tokenIn": "0xTOKEN_YOU_PAY_WITH", "amountIn": "1000000000000000" }

# Response: { router, quote, approval, tx, chainId, from }
# quote: { amountOut, inUsd, outUsd, gasUsd, unjudged, feeTaken, routesAsked }
curl -X POST "https://trnch.fun/api/bot/v1/sell" \
  -H "Authorization: Bearer trnch_bot_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "token": "0xTOKEN_ADDRESS", "percent": 100, "slippageBps": 150 }'

# Or an exact amount, in token units: { "token": "0xTOKEN_ADDRESS", "amount": "1234.5" }

POST /quote

The best route for a swap, every router asked: input and output token, amountIn in the smallest unit of the input token, and slippage as a fraction (0.01 is 1%, default 1%). The answer lists each route with its output amount; keep the router of the one you want. Routes that need an intermediate signature are not offered.

curl -X POST "https://trnch.fun/api/bot/v1/quote" \
  -H "Authorization: Bearer trnch_bot_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tokenIn": "0xUSDG_ADDRESS",
    "tokenOut": "0xTOKEN_ADDRESS",
    "amountIn": "25000000",
    "decimalsIn": 6,
    "decimalsOut": 18,
    "slippage": 0.01
  }'

POST /build

A transaction ready to sign, for the wallet of your key and nobody else. The body is the quote body plus the router. There is no receiver or taker field: any extra field is refused. The route is quoted again and checked against our prices when you build, so sign it soon after. If the input token is an ERC-20, approval holds an approve for the exact amount, to send first unless your allowance already covers it; it is null for native ETH.

curl -X POST "https://trnch.fun/api/bot/v1/build" \
  -H "Authorization: Bearer trnch_bot_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "router": "0x",
    "tokenIn": "0xUSDG_ADDRESS",
    "tokenOut": "0xTOKEN_ADDRESS",
    "amountIn": "25000000",
    "decimalsIn": 6,
    "decimalsOut": 18,
    "slippage": 0.01
  }'

# Response: { router, amountOut, tx, chainId, from, approval }
# 1. if approval is not null and your allowance is lower, sign and send approval
# 2. sign and send tx with YOUR key

POST /confirm

After you send the transaction, tell us its hash. The swap is checked on-chain and its volume counts for your player like a swap made on the site: packs and stats. Nothing is counted for a hash that is not a swap built by your key for your wallet.

curl -X POST "https://trnch.fun/api/bot/v1/confirm" \
  -H "Authorization: Bearer trnch_bot_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "txHash": "0xYOUR_SWAP_TRANSACTION_HASH" }'

Webhook: the Heat comes to you

Instead of polling, give a https URL to each key in Profile (API section). When a token enters the Heat, the same event as the New in the Heat alert, TRNCH sends a JSON POST to it, within about a minute. Only public token data is sent, nothing about you. The signing secret (whsec_...) is shown once when you save the URL.

  • Check X-TRNCH-Signature: t=<unix seconds>,v1=<hex>: v1 is the HMAC-SHA256 of <t>.<raw body> with your secret. Reject a signature that does not match, or a t more than 5 minutes old.
  • Answer with a 2xx within 3 seconds. A network error, a 5xx or a 429 is retried at most 2 more times; other answers are not retried. Redirects are not followed.
  • After 5 failed deliveries in a row the webhook is switched off; save the URL again in your Profile to restart it. A revoked key loses its webhook.
  • The URL must be https on port 443, with a public domain name (no IP address). One token is sent once per webhook, and only tokens that entered the Heat after the webhook was saved.
POST https://your-bot.example.com/trnch
Content-Type: application/json
X-TRNCH-Signature: t=1760000000,v1=9f2c...e1

{
  "event": "heat.entry",
  "id": "0xtoken:1760000000000",
  "createdAt": 1760000003000,
  "token": {
    "token": "0x...", "symbol": "MEME", "name": "Meme", "rank": 4,
    "heatScore": 74, "momentum": 1.8, "priceUsd": 0.00042, "liquidityUsd": 31000,
    "volume1hUsd": 5400, "volume24hUsd": 5400, "priceChange5mPct": 3.1, "priceChange1hPct": null,
    "traders24h": 38, "status": "active",
    "enteredHeatAt": 1760000000000, "enteredHeatPriceUsd": 0.0004, "sinceHeatPct": 5
  }
}

Starter scripts

Read this before you run anything
  • This is a starting point, not a strategy. New pools are the riskiest tokens there are: many are rugs, honeypots or illiquid, and the Heat filters only remove the ones we can detect.
  • A bot that signs by itself can lose all the funds in its wallet in minutes. Use a dedicated wallet with a small balance, never your main wallet. Start with tiny amounts.
  • The private key stays on your machine: read it from an environment variable, never write it in the code, never commit it, never send it to anyone. TRNCH never asks for it.
  • Nothing here is financial advice. You are responsible for what your bot buys and sells.

Both scripts poll /heat, keep the tokens that pass a simple filter (minimum Heat score and liquidity, edit the constants), call /buy, sign the transaction locally with the private key read from BOT_PRIVATE_KEY, send it to the public RPC of Robinhood Chain (chain id 4663), then call /confirm. They send the approval first when there is one, and refuse a transaction built for another wallet.

TypeScript (viem)

// Heat sniper, starter. Node 20+ :  npm i viem tsx  &&  npx tsx bot.ts
// TRNCH_API_KEY = your API key (Profile > API). BOT_PRIVATE_KEY = the key of a DEDICATED wallet.
// The private key signs on this machine only. It is never sent anywhere.
import { createPublicClient, createWalletClient, defineChain, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const API = "https://trnch.fun/api/bot/v1";
const MIN_SCORE = 70;          // minimum Heat score
const MIN_LIQUIDITY_USD = 20000;
const BUY_USD = 5;             // dollars of USDG per buy: keep it small
const SLIPPAGE_BPS = 150;      // 1.5%
const POLL_MS = 5000;          // 12 calls a minute, the limit is 60

const chain = defineChain({
  id: 4663,
  name: "Robinhood Chain",
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
  rpcUrls: { default: { http: ["https://rpc.mainnet.chain.robinhood.com"] } },
});
const account = privateKeyToAccount(process.env.BOT_PRIVATE_KEY as `0x${string}`);
const rpc = createPublicClient({ chain, transport: http() });
const wallet = createWalletClient({ account, chain, transport: http() });

async function api(path: string, body?: unknown) {
  const res = await fetch(API + path, {
    method: body ? "POST" : "GET",
    headers: { Authorization: "Bearer " + process.env.TRNCH_API_KEY, "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
  const json = await res.json();
  if (!res.ok) throw new Error(path + " " + res.status + " " + JSON.stringify(json));
  return json;
}

// Your filter. Return true to buy.
function wanted(t: { heatScore: number; liquidityUsd: number; status: string }) {
  return t.status === "active" && t.heatScore >= MIN_SCORE && t.liquidityUsd >= MIN_LIQUIDITY_USD;
}

async function buy(token: string) {
  const r = await api("/buy", { token, amountUsd: BUY_USD, slippageBps: SLIPPAGE_BPS });
  // The transaction is built for the wallet of your API key. Refuse anything else.
  if (r.from.toLowerCase() !== account.address.toLowerCase()) throw new Error("API key wallet is not the signer");
  if (r.approval) {
    const approveHash = await wallet.sendTransaction({ to: r.approval.to, data: r.approval.data, value: 0n });
    await rpc.waitForTransactionReceipt({ hash: approveHash });
  }
  const hash = await wallet.sendTransaction({ to: r.tx.to, data: r.tx.data, value: BigInt(r.tx.value) }); // signed locally
  console.log("buy", token, "via", r.router, hash);
  await api("/confirm", { txHash: hash });
}

let since = Date.now();
for (;;) {
  try {
    const feed = await api("/heat?since=" + since);
    since = feed.nextSince;
    for (const t of feed.tokens) if (wanted(t)) await buy(t.token).catch((e) => console.error(t.symbol, e.message));
  } catch (e) {
    console.error((e as Error).message);
  }
  await new Promise((resolve) => setTimeout(resolve, POLL_MS));
}

Python (web3.py)

# Heat sniper, starter. Python 3.10+ :  pip install web3 requests  &&  python bot.py
# TRNCH_API_KEY = your API key (Profile > API). BOT_PRIVATE_KEY = the key of a DEDICATED wallet.
# The private key signs on this machine only. It is never sent anywhere.
import os, time, requests
from web3 import Web3

API = "https://trnch.fun/api/bot/v1"
MIN_SCORE = 70            # minimum Heat score
MIN_LIQUIDITY_USD = 20000
BUY_USD = 5               # dollars of USDG per buy: keep it small
SLIPPAGE_BPS = 150        # 1.5%
POLL_S = 5                # 12 calls a minute, the limit is 60
CHAIN_ID = 4663   # Robinhood Chain
RPC_URL = "https://rpc.mainnet.chain.robinhood.com"

w3 = Web3(Web3.HTTPProvider(RPC_URL))
account = w3.eth.account.from_key(os.environ["BOT_PRIVATE_KEY"])
headers = {"Authorization": "Bearer " + os.environ["TRNCH_API_KEY"]}

def api(path, body=None):
    res = requests.request("POST" if body else "GET", API + path, json=body, headers=headers, timeout=30)
    if not res.ok:
        raise RuntimeError(f"{path} {res.status_code} {res.text}")
    return res.json()

def send(tx):
    """Sign locally and send. The private key never leaves this process."""
    call = {"from": account.address, "to": Web3.to_checksum_address(tx["to"]), "data": tx["data"], "value": int(tx.get("value", "0"))}
    call["gas"] = int(w3.eth.estimate_gas(call) * 1.2)
    call["nonce"] = w3.eth.get_transaction_count(account.address, "pending")
    call["gasPrice"] = w3.eth.gas_price
    call["chainId"] = CHAIN_ID
    signed = account.sign_transaction(call)
    raw = getattr(signed, "raw_transaction", None) or signed.rawTransaction  # web3.py 7 / 6
    tx_hash = w3.eth.send_raw_transaction(raw)
    w3.eth.wait_for_transaction_receipt(tx_hash)
    return tx_hash.hex()

# Your filter. Return True to buy.
def wanted(t):
    return t["status"] == "active" and t["heatScore"] >= MIN_SCORE and t["liquidityUsd"] >= MIN_LIQUIDITY_USD

def buy(token):
    r = api("/buy", {"token": token, "amountUsd": BUY_USD, "slippageBps": SLIPPAGE_BPS})
    # The transaction is built for the wallet of your API key. Refuse anything else.
    if r["from"].lower() != account.address.lower():
        raise RuntimeError("API key wallet is not the signer")
    if r["approval"]:
        send(r["approval"])
    tx_hash = send(r["tx"])
    print("buy", token, "via", r["router"], tx_hash)
    api("/confirm", {"txHash": tx_hash if tx_hash.startswith("0x") else "0x" + tx_hash})

since = int(time.time() * 1000)
while True:
    try:
        feed = api(f"/heat?since={since}")
        since = feed["nextSince"]
        for t in feed["tokens"]:
            if wanted(t):
                try:
                    buy(t["token"])
                except Exception as e:
                    print(t["symbol"], e)
    except Exception as e:
        print(e)
    time.sleep(POLL_S)

Receive the webhook instead of polling

Same bot, fed by the webhook: verify the signature, then run your filter and buy on the token you receive.

// Webhook receiver, starter. Node 20+ :  npx tsx hook.ts
// TRNCH_WEBHOOK_SECRET = the whsec_... secret shown once when you saved the URL (Profile > API).
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const SECRET = process.env.TRNCH_WEBHOOK_SECRET!;

// Header: X-TRNCH-Signature: t=<unix seconds>,v1=<hex hmac sha256 of "<t>.<raw body>">
function verify(header: string | undefined, body: string): boolean {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "");
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
  const expected = createHmac("sha256", SECRET).update(m[1] + "." + body).digest();
  return timingSafeEqual(expected, Buffer.from(m[2], "hex"));
}

createServer((req, res) => {
  let body = "";
  req.on("data", (chunk) => (body += chunk));
  req.on("end", () => {
    if (!verify(req.headers["x-trnch-signature"] as string | undefined, body)) return void res.writeHead(401).end();
    res.writeHead(200).end(); // answer fast (within 3 s), work after
    const { token } = JSON.parse(body); // { event: "heat.entry", id, createdAt, token: { token, symbol, heatScore, liquidityUsd, ... } }
    console.log("entered the Heat:", token.symbol, token.heatScore, token.liquidityUsd);
    // call your wanted(token) filter and buy(token.token) from bot.ts here
  });
}).listen(8080);
# Webhook receiver, starter. Python 3.10+ :  python hook.py   (standard library only)
# TRNCH_WEBHOOK_SECRET = the whsec_... secret shown once when you saved the URL (Profile > API).
import hashlib, hmac, json, os, re, time
from http.server import BaseHTTPRequestHandler, HTTPServer

SECRET = os.environ["TRNCH_WEBHOOK_SECRET"].encode()

# Header: X-TRNCH-Signature: t=<unix seconds>,v1=<hex hmac sha256 of "<t>.<raw body>">
def verify(header, body: bytes) -> bool:
    m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
    if not m or abs(time.time() - int(m.group(1))) > 300:
        return False
    expected = hmac.new(SECRET, m.group(1).encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, m.group(2))

class Hook(BaseHTTPRequestHandler):
    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("content-length", 0)))
        if not verify(self.headers.get("x-trnch-signature"), body):
            self.send_response(401); self.end_headers(); return
        self.send_response(200); self.end_headers()  # answer fast (within 3 s), work after
        token = json.loads(body)["token"]  # { token, symbol, heatScore, liquidityUsd, ... }
        print("entered the Heat:", token["symbol"], token["heatScore"], token["liquidityUsd"])
        # call your wanted(token) filter and buy(token["token"]) from bot.py here

HTTPServer(("0.0.0.0", 8080), Hook).serve_forever()

Limits and fee

  • 60 calls per minute per key, all routes together; 30 of them can be quotes, builds, buys or sells. Above that you get 429 with a retry-after header.
  • When swaps are paused on TRNCH, quote, build, buy and sell answer 503. The feed stays open.
  • Tokens flagged as rugs are refused with 409. A token with no route gets a 404 at build time.
  • TRNCH takes a 1% fee on swaps built through the API. It is taken inside the route you sign, so the amounts we return are net of it.
  • Errors are JSON: an error field and the matching HTTP status (400, 401, 404, 409, 429, 503).

Fees go to the TRNCH treasury, a multisig Safe on Robinhood Chain, the only address that collects them: 0xe1F2aBa69cfD0C6973d037426D4391FE64b2Fc2e

Your bot signs

TRNCH never signs for you, never holds your funds and never sees a private key. The API gives you data and transactions; your bot signs them with its own key and sends them to the chain. Never share a wallet key or a seed phrase with anyone, TRNCH included. If an API key leaks, revoke it in your Profile: it stops working at once.