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 and four.meme token on BNB Chain, wherever it lives:
- On the Flap bonding curve - from launch until 800M tokens are sold.
- On the four.meme bonding curve - from launch until the raise (18 BNB, or the equivalent in USDT, USD1 or a stock) is complete.
- On PancakeSwap - after graduation, in the pool the launchpad migrated it to.
You send the token address; the API asks both launchpads whose it is, reads its state and routes accordingly, reporting protocol as FLAP or FOURMEME and the venue as flap_curve, fourmeme_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, any four.meme 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.
four.meme tokens
The same request trades a four.meme token; the response comes back with protocol: "FOURMEME" and the venue fourmeme_curve or pancakeswap. What is different about four.meme:
- Quote assets. A four.meme curve is priced in BNB, USDT, USD1 or one of Binance's tokenised stocks (BNCB, DJTB, QQQB and more). A buy is still in BNB: four.meme's own Helper swaps it into the quote asset inside the transaction. To pay with the asset itself, hold it and send
payWithQuote: true. - Sells pay out the quote asset. A token priced in BNB sells for BNB; one priced in USDT sells for USDT, and so on.
receivein the response says which. - four.meme's fees are in the quote. The 1% trading fee (0.001 BNB minimum), the opening anti-sniper fee that some launches charge for their first blocks, and a tax token's tax are all already taken out of
expectedOut.curveFeeBpsis the fee this trade pays right now, so an anti-sniper launch shows a high number that falls block by block; Token Info has the same figure ascurrentBuyFeeBpsfor polling. - Amounts are whole gwei. four.meme rejects token amounts that are not multiples of 1e9 base units; a sell amount is rounded down to one, which
amountInreflects. - Older tokens. four.meme tokens created before September 2024 (its V1 manager) can be read but not traded on their curve:
unsupported_version. Once graduated they trade on PancakeSwap like any other.
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 | Neither Flap nor four.meme knows 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 the launchpad can't swap BNB into. Hold it and set payWithQuote. |
unsupported_version | A four.meme V1 token (pre-September 2024) still on its curve. Not tradeable here until it graduates. |
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 and four.meme 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 or four.meme 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.protocol-FLAPorFOURMEME, whichever launchpad the token came from.status-Tradableon the curve,DEXafter graduation.venue-flap_curve,fourmeme_curveorpancakeswap. The request is the same in every case.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 the launchpad's fee on the curve (Flap's 1.25%; four.meme's 1%, or its current anti-sniper fee; 0 once graduated - PancakeSwap's LP fee applies instead),creatorTaxBpsthe token's tax on this side,shrineFeeBpsours (50 = 0.5%).