Buy & Sell
One request, one unsigned transaction back, signed and sent by your own script. The snippet below is complete: set the key, the token and the amount, and run it.

One endpoint for every Flap token on BNB Chain, wherever it lives:
- On the bonding curve - from launch until 800M tokens are sold.
- On PancakeSwap - after graduation, in the pool Flap migrated it to.
You send the token address; the API reads its state from Flap's Portal and routes accordingly, reporting the venue as flap_curve or pancakeswap. Tax tokens need no special handling - their tax is already inside the quote.
What you need
- Node.js or Python.
- A wallet private key with some BNB on BNB Chain.
- A token address. Take one from any Flap token page or the live launches feed.
Example
- JavaScript
- Python
const { Wallet } = require("ethers");
// ─── change these three ───────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const TOKEN = "0xTHE_TOKEN_ADDRESS";
const AMOUNT = "0.01"; // to buy: BNB to spend. To sell: "1000" tokens, or "100%"
const ACTION = "buy"; // "buy" or "sell"
const SLIPPAGE = 5; // %, how far the price may move against you
// ──────────────────────────────────────────────────────
async function main() {
const wallet = new Wallet(PRIVATE_KEY); // signs only; no node needed
const res = await fetch("https://api.shrine.trade/bnb/api/local-trade", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: ACTION,
token: TOKEN,
amount: AMOUNT,
slippage: SLIPPAGE,
from: wallet.address,
}),
});
const data = await res.json();
if (data.error) throw new Error(`${data.error}: ${data.message}`);
console.log("you should get about", data.quote.expectedOutFormatted,
ACTION === "buy" ? "tokens" : data.receive);
// Sign locally and hand the signed bytes to the API to broadcast - the key
// never leaves this script, and you need no RPC of your own.
for (const tx of data.txs) {
const signed = await wallet.signTransaction({
to: tx.to, data: tx.data, value: BigInt(tx.value), gasLimit: BigInt(tx.gas),
maxFeePerGas: BigInt(tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas),
nonce: tx.nonce, chainId: tx.chainId, type: 2,
});
const sent = await (await fetch("https://api.shrine.trade/bnb/api/send", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ signedTx: signed }),
})).json();
if (sent.error) throw new Error(`${sent.error}: ${sent.message}`);
if (sent.status !== "landed") throw new Error(`${tx.description} ${sent.status}: ${sent.explorer}`);
console.log(tx.description + ": " + sent.explorer);
}
if (ACTION === "buy") {
console.log(`bought ${data.name} (${data.symbol}) ${data.token} for ${data.amountInFormatted} ${data.payWith}`);
} else {
console.log(`sold ${data.amountInFormatted} ${data.name} (${data.symbol}) ${data.token} for ${data.quote.expectedOutFormatted} ${data.receive}`);
}
}
main();
Save it as trade.js, then:
npm install ethers
node trade.js
import requests
from eth_account import Account
# ─── change these three ───────────────────────────────
PRIVATE_KEY = "0xYOUR_PRIVATE_KEY"
TOKEN = "0xTHE_TOKEN_ADDRESS"
AMOUNT = "0.01" # to buy: BNB to spend. To sell: "1000" tokens, or "100%"
ACTION = "buy" # "buy" or "sell"
SLIPPAGE = 5 # %, how far the price may move against you
# ──────────────────────────────────────────────────────
account = Account.from_key(PRIVATE_KEY) # signs only; no node needed
res = requests.post(
"https://api.shrine.trade/bnb/api/local-trade",
json={"action": ACTION, "token": TOKEN, "amount": AMOUNT, "slippage": SLIPPAGE, "from": account.address},
)
data = res.json()
if "error" in data:
raise SystemExit(f"{data['error']}: {data['message']}")
unit = "tokens" if ACTION == "buy" else data["receive"]
print("you should get about", data["quote"]["expectedOutFormatted"], unit)
for tx in data["txs"]:
signed = Account.sign_transaction(
{
"to": tx["to"],
"data": tx["data"],
"value": int(tx["value"]),
"gas": tx["gas"],
"maxFeePerGas": int(tx["maxFeePerGas"]),
"maxPriorityFeePerGas": int(tx["maxPriorityFeePerGas"]),
"nonce": tx["nonce"],
"chainId": tx["chainId"],
},
account.key,
)
sent = requests.post("https://api.shrine.trade/bnb/api/send", json={"signedTx": "0x" + signed.raw_transaction.hex().removeprefix("0x")}).json()
if "error" in sent:
raise SystemExit(f"{sent['error']}: {sent['message']}")
if sent["status"] != "landed":
raise SystemExit(f"{tx['description']} {sent['status']}: {sent['explorer']}")
print(tx["description"] + ":", sent["explorer"])
if ACTION == "buy":
print(f"bought {data['name']} ({data['symbol']}) {data['token']} for {data['amountInFormatted']} {data['payWith']}")
else:
print(f"sold {data['amountInFormatted']} {data['name']} ({data['symbol']}) {data['token']} for {data['quote']['expectedOutFormatted']} {data['receive']}")
Save it as trade.py, then:
pip install eth-account requests
python trade.py
Whatever the API returns, the script sends in sequence. A buy is a single transaction. The first sell of a given token is two: a one-time approval, then the sell itself.
Trades are front-run protected: the on-chain minOut guarantees the fill is never worse than your slippage allows, and the signed transaction is broadcast through our own endpoint via /api/send, not a public node, so you need no RPC of your own.
Some Flap tokens cap how much one wallet may buy on the curve. Your quote already reflects your remaining allowance; a buy past it is filled up to the cap and the rest of your BNB is refunded in the same transaction. Token Info shows the cap and what you have left.
Selling
Switch ACTION to "sell". For the amount, give either a token count or a percentage of the wallet's balance:
AMOUNT | Sells |
|---|---|
"1000" | 1000 tokens |
"50%" | half your balance |
"100%" | everything |
Proceeds arrive in BNB, whatever the token is priced in, whenever Flap can convert on the way out. receive in the response says which asset you end up with.
Tax tokens
Most Flap launches are tax tokens: the creator takes a percentage of every buy and sell. The tax is applied inside the quote, so expectedOut is what you actually receive - on the curve it is an extra fee on the trade, after graduation it is taken from the tokens as they move through the pool. creatorTaxBps in the response is the rate on your side of the trade. Nothing changes in the script.
Tokens priced in USD1, stocks or other assets
Flap lets a launch be priced in USD1, a tokenised stock (NVDAB, TSLAB, SPYB, XAUT and more - see Supported quote assets), or another Flap token instead of BNB. Nothing changes in the script: AMOUNT on a buy is always BNB, and Flap swaps it into the quote asset inside the transaction. quoteAsset says what the token is priced in and payWith what left your wallet.
Where Flap cannot swap on the way in (quoteAssetRequired), buy with the asset itself: hold it and send payWithQuote: true with amount in that asset. Selling such a token pays out the quote asset; send receiveQuote: true to insist on it for any token.
If it doesn't work
| Error | What to do |
|---|---|
insufficient_funds | Add BNB to the wallet - the amount plus a little for gas. |
insufficient_balance | The sell is bigger than the balance. "100%" always fits. |
token_not_found | Flap's Portal has never heard of this address. Make sure it is the token contract, not the PancakeSwap pool. |
slippage_exceeded | The price ran away between quote and send. Retry, with a higher SLIPPAGE on a busy token. |
buy_quota_exhausted | This wallet has bought all it may of this token on the curve. |
quote_asset_required | The token is priced in an asset Flap can't swap BNB into. Hold it and set payWithQuote. |
Full list on Errors.
Sending
POST https://api.shrine.trade/bnb/api/send takes one signed transaction and broadcasts it through our BNB Chain endpoint, so the scripts need no RPC at all. It only forwards transactions this API built - Flap trades, launches, approvals and claims - and refuses anything else with not_ours.
| Field | ||
|---|---|---|
signedTx | 0x-hex | The signed transaction, as wallet.signTransaction returns it. |
wait | boolean, optional | Default true: wait for the receipt. false returns the hash as soon as the node accepts it. |
{ "hash": "0x…", "status": "landed", "blockNumber": 120162431, "gasUsed": 514058, "explorer": "https://bscscan.com/tx/0x…" }
status is landed, reverted, or pending (not waited for, or not mined within 45 seconds - stuck on nonce or gas).
Request and response
POST https://api.shrine.trade/bnb/api/local-trade
Request
| Field | ||
|---|---|---|
action | "buy" or "sell" | |
token | address | The Flap token. |
amount | decimal string | On a buy, the BNB to spend regardless of the token's quote asset. On a sell, a token count such as "1000", or a percentage of the balance such as "50%" or "100%". |
from | address | Your wallet. |
slippage | number, optional | In percent. 5 when omitted. |
payWithQuote | boolean, optional | Buy only, tokens priced in an ERC-20: pay with that asset; amount is then in it. |
receiveQuote | boolean, optional | Sell only, tokens priced in an ERC-20: receive that asset instead of BNB. |
Response - a real one, a buy of a tax token for 0.001 BNB on its curve:
{
"action": "buy",
"protocol": "FLAP",
"token": "0x2f1952E00F6BA7655993120B8c41E5ab511e7777",
"name": "星际巨舰",
"symbol": "星际巨舰",
"status": "Tradable",
"venue": "flap_curve",
"amountIn": "1000000000000000",
"amountInFormatted": "0.001",
"quoteAsset": {
"address": "0x0000000000000000000000000000000000000000",
"symbol": "BNB",
"decimals": 18,
"isNative": true
},
"payWith": "BNB",
"receive": "TOKEN",
"quote": {
"expectedOut": "160442982903129676642962",
"expectedOutFormatted": "160442.982903129676642962",
"minOut": "152420833757973192810813",
"curveFeeBps": 125,
"creatorTaxBps": 1000,
"shrineFeeBps": 50,
"slippagePct": 5.0
},
"txs": [
{
"to": "0x799924b88883cD9436C8B0dfc51660147C8Bcd7C",
"data": "0x…",
"value": "1000000000000000",
"gas": 521904,
"maxFeePerGas": "62500000",
"maxPriorityFeePerGas": "50000000",
"nonce": 5,
"chainId": 56,
"type": 2,
"description": "buy"
}
]
}
txs- the transactions to sign and broadcast, in this order. The three wei-denominated fields (value,maxFeePerGas,maxPriorityFeePerGas) are decimal strings so they survive JSON; everything else is a plain number.descriptionlabels each:buy,sell, or a one-timeapprove/approve_quote.name/symbol- read from the token contract, for logging what was traded.status- Flap's:Tradableon the curve,DEXafter graduation.venue-flap_curveorpancakeswap. The request is the same in both cases.quoteAsset- what the token is priced in: BNB, or an ERC-20.payWith/receive- what actually left or reached your wallet (BNB, the quote symbol, orTOKEN).amountIn- what you spend, base units: wei of BNB on a buy, tokens on a sell.quote.expectedOut- the amount you should end up with once Flap's fee, the token's tax and ours are all taken.minOutis that figure less your slippage, and it is the floor the contract enforces. Both are base units; use the*Formattedtwins for display.quotefees -curveFeeBpsis Flap's fee on the curve (0 once graduated - PancakeSwap's LP fee applies instead),creatorTaxBpsthe token's tax on this side,shrineFeeBpsours (50 = 0.5%).