Trade through the API
Quote, build unsigned, sign on your side, submit, check. The API never holds a key.
What is live today
TREND runs on its own dev validator. The mainnet programs are not deployed: their ids are reserved and no launch exists there. Statement as of 2026-10-10.
Four steps, and the split between them is the point.
POST /v1/trades/quote price it
POST /v1/trades/build get UNSIGNED bytes
you sign, with your own key, wherever your key lives
POST /v1/transactions/submit land it
GET /v1/transactions/{sig} check itThe backend never holds a private key. build returns base64 and stops. It reads the quote from the server's own
store by id and does not accept a quote object back, so nothing between the price you were shown and the bytes you
sign can change the amounts.
1. Quote
{
"launch": "<launch address>",
"direction": "buy",
"amountIn": "250000000",
"mode": "exactInSol",
"slippageBps": 300,
"wallet": "<your public address>",
"preview": false
}launch must be an address here. Unlike the read routes, a ticker is not accepted.
What amountIn counts
| Direction | mode | amountIn is |
|---|---|---|
| buy | exactInSol | Lamports, and a spending ceiling |
| buy | exactOut | Launch tokens you want out |
| buy | exactIn | The protocol's internal quote unit |
| sell | exactIn | Launch tokens you give up |
| either, on a launch past the curve | any | Mode is ignored: a buy is lamports, a sell is launch tokens |
exactInSol is a ceiling, not a target. The server reads each pool's reserves and solves for the largest trade the
budget covers, so the trade cannot cost more than the number you named. It is the mode for "buy 1 SOL of this".
The fields that change the answer
slippageBpsis your tolerance, default 100. On a buy it sizes an input buffer and any excess stays in your wallet, so a loose setting costs dust. On a sell it sets the floor on what you receive, so a loose setting is a real loss. Do not use one number for both.wallet, when given, changes the answer: the quote reports which components the wallet already holds and routes only the shortfall. Without it you get a price and no route.preview: trueprices without storing a quote. ThequoteIdit returns cannot be built. Use it for a number shown while a person is still deciding, and leave it out when you mean to build.
A quote expires after 30 seconds. Read the shape you got back in the three quote shapes.
2. Build
{ "quoteId": "<from the quote>", "wallet": "<the wallet that signs>" }The answer holds transaction (base64 of an unsigned versioned transaction) and lastValidBlockHeight. The wallet in
the request is the fee payer. Balances are read again here, so the route can differ from the quote's copy, and the
build's route is the authoritative one.
A plan with several steps
Sometimes the answer carries a plan:
{
"transaction": "<the trade>",
"plan": [
{ "step": 1, "label": "Create your token accounts for this pair", "transaction": "<base64>", "kind": "setup" },
{ "step": 2, "label": "Buy", "transaction": "<base64>", "kind": "trade", "settles": true }
]
}Rules that have each broken a client:
- Sign and land the steps in order. A buy acquires components and then settles, so its detached swaps come before the trade. A sell settles first, so its swaps come after. Running them out of order or at once fails on an account that does not exist yet or a balance that is not there.
transactionis always the last entry ofplan. A client that ignoresplansigns the right transaction and then fails loudly rather than doing something subtly wrong.- Tell the person how many signatures there will be before the first one. The quote's
route.transactionssays it. - Exactly one step has
settles: true. That is the step thequoteIdbelongs on.
3. Sign
Deserialize each transaction, sign it with the wallet you named, and keep the bytes. The snippet signs with a throwaway key and stops before submitting. It is a real file, run against dev after each deploy of this site.
// Trade end to end up to the signature, with a throwaway key. Needs @solana/web3.js.
// It quotes for real (preview: false), asks for the unsigned transaction, signs it locally and stops:
// it never submits, so nothing is spent. Replace the throwaway key with your own to go further.
import { Keypair, VersionedTransaction } from '@solana/web3.js';
const API = process.env.TREND_API ?? 'https://api.dev.trend.fun';
const wallet = Keypair.generate();
async function call(path, init) {
const res = await fetch(API + path, init);
return { status: res.status, body: await res.json() };
}
const post = (path, body) =>
call(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
const { body: list } = await call('/v1/launches/trending');
const launch = list.launches.find((l) => l.kind === 'pairs' && l.status === 'activeBonding') ?? list.launches[0];
// Quote (preview false), so the server stores it and a build can find it by id.
const quoted = await post('/v1/trades/quote', {
launch: launch.address,
direction: 'buy',
amountIn: '250000000',
mode: 'exactInSol',
wallet: wallet.publicKey.toBase58(),
preview: false,
});
if (quoted.status !== 200) throw new Error(`quote refused: ${quoted.status} ${quoted.body.error}`);
const { quote } = quoted.body;
// Build: unsigned bytes. The wallet in the request is the fee payer.
const built = await post('/v1/trades/build', { quoteId: quote.quoteId, wallet: wallet.publicKey.toBase58() });
if (built.status !== 200) {
// A fresh key holds nothing, so a refusal here is expected. It is a sentence in `error`.
console.log('build refused:', built.status, built.body.error);
process.exit(0);
}
// Sign every step in order. `transaction` is always the last step.
const steps = built.body.plan ? built.body.plan.map((s) => s.transaction) : [built.body.transaction];
for (const b64 of steps) {
const tx = VersionedTransaction.deserialize(Buffer.from(b64, 'base64'));
tx.sign([wallet]);
console.log('signed a transaction of', tx.serialize().length, 'bytes');
}4. Submit and check
Send the base64 of the signed transaction to POST /v1/transactions/submit with the quoteId, or send it through your
own RPC. Then poll GET /v1/transactions/{signature}. Pass lastValidBlockHeight from the build so the route can say
expired when the chain has moved past it with no sign of your transaction.
A transaction the network rejected comes back 200 with status: "failed" and the reason in error, not a 5xx:
the API call worked and the transaction did not. Check status, not the HTTP code.
Everything above is described field by field in the API reference.