Local Trade API
The non-custodial way to trade Solana memecoins through an API. Your wallet stays on your computer - we never see your private key. You submit the signed trade through our relay, or through your own RPC.
One API, four networks: the same requests, streams and keys work on Solana, BNB Chain, Robinhood Chain and Arc; a bot moves between them by changing the base URL. The Solana side runs in Frankfurt, Amsterdam and New York; pick yours with the region picker at the bottom of the sidebar, see Regions. The fastest routes are ⚡ Lightspeed for trades and Ultra Fast Stream for data.
Around the trade endpoint: the Ultra Fast Stream delivers every pump.fun trade before the block runs, the 24-hour replay means a disconnect loses nothing, and the same API runs in three regions.
One endpoint for a coin from any launchpad, wherever it trades right now:
No API key, no account. The API builds the transaction, your machine signs it, and the relay sends it. The only thing we ever see is your public key.
What you need
- A Solana wallet - its private key in base58. (Phantom: Settings → Show Secret Recovery Phrase → Show Private Key.)
- Some SOL in that wallet - enough for the trade plus a small amount of network fees.
- A way to submit. By default you post the signed transaction back to
/api/sendand we broadcast it through our own endpoint, so no RPC is needed. If you prefer your own Solana RPC (Helius, Triton, QuickNode or the public one), send with that instead. - Node.js or Python installed if you want to run the snippet below as-is.
Example
- JavaScript
- Python
import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";
const wallet = Keypair.fromSecretKey(bs58.decode("<your wallet private key, base58>"));
// 1. Ask shrine.trade to build an unsigned transaction.
const res = await fetch("https://sol.shrine.trade/api/local-trade", {
method: "POST",
headers: { "content-type": "application/json", "accept": "application/json" },
body: JSON.stringify({
action: "buy", // "buy" or "sell"
publicKey: wallet.publicKey.toBase58(),
mint: "<token mint>", // base58 token mint
amount: 0.01, // SOL on a buy; tokens or "100%" on a sell
slippage: 10, // percent
priorityFee: 0.0001, // SOL
}),
});
const { tx } = await res.json();
// 2. Sign locally - your private key never leaves this script.
const txObj = VersionedTransaction.deserialize(Buffer.from(tx, "base64"));
txObj.sign([wallet]);
// 3. Send it through the shrine.trade relay - no RPC needed. Waits for confirmation.
const sent = await (await fetch("https://sol.shrine.trade/api/send", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ signedTx: Buffer.from(txObj.serialize()).toString("base64") }),
})).json();
console.log(sent.status, sent.explorer); // "landed" https://solscan.io/tx/…
// Or, with your own RPC:
// const conn = new Connection("https://your-rpc");
// const sig = await conn.sendRawTransaction(txObj.serialize());
import base64, requests, base58
from solana.rpc.api import Client
from solders.keypair import Keypair
from solders.transaction import VersionedTransaction
wallet = Keypair.from_bytes(base58.b58decode(WALLET_SECRET))
# 1. Ask shrine.trade to build an unsigned transaction.
res = requests.post(
"https://sol.shrine.trade/api/local-trade",
headers={"accept": "application/json"},
json={
"action": "buy", # "buy" or "sell"
"publicKey": str(wallet.pubkey()),
"mint": "<token mint>", # base58 token mint
"amount": 0.01, # SOL on a buy; tokens or "100%" on a sell
"slippage": 10, # percent
"priorityFee": 0.0001, # SOL
},
)
tx_b64 = res.json()["tx"]
# 2. Sign locally - your private key never leaves this script.
raw = VersionedTransaction.from_bytes(base64.b64decode(tx_b64))
signed = VersionedTransaction(raw.message, [wallet])
# 3. Send it through the shrine.trade relay - no RPC needed. Waits for confirmation.
sent = requests.post("https://sol.shrine.trade/api/send",
json={"signedTx": base64.b64encode(bytes(signed)).decode()}).json()
print(sent["status"], sent["explorer"]) # landed https://solscan.io/tx/…
# Or, with your own RPC:
# sig = Client("https://your-rpc").send_raw_transaction(bytes(signed)).value
print(f"https://solscan.io/tx/{sig}")
How it works
- You ask shrine.trade for a trade. We build an unsigned transaction.
- You sign the transaction on your machine, with your own wallet.
- You submit the signed transaction: post it to
/api/sendand we broadcast it, or send it through your own RPC.
Sending
POST https://sol.shrine.trade/api/send takes one signed transaction and broadcasts it through our Solana endpoint, so the scripts need no RPC at all. It only forwards transactions this API built: every build is remembered for 15 minutes, and the signed transaction must match one of them unchanged. Anything assembled elsewhere, or edited after building, is refused with not_ours. Using it is optional; a signed transaction can always go through your own RPC instead.
| Field | ||
|---|---|---|
signedTx | base64 | The signed transaction: Buffer.from(tx.serialize()).toString("base64") in web3.js, base64.b64encode(bytes(signed)) with solders. |
wait | boolean, optional | Default true: wait for confirmation. false returns the signature as soon as the node accepts it. |
{ "signature": "5Kd…", "status": "landed", "slot": 372114820, "explorer": "https://solscan.io/tx/5Kd…" }
status is landed (confirmed), failed (executed but errored on-chain; error carries the program error) or pending (not waited for, or not confirmed within 60 seconds). A transaction the node refuses in preflight - not enough SOL, slippage exceeded, expired blockhash - comes back as a 400 with the node's reason.
Request fields
| Field | Type | Notes |
|---|---|---|
action | "buy" / "sell" | |
publicKey | string | Your wallet. It signs and pays fees. |
mint | string | Token mint. |
amount | number or string | What goes in on a buy: SOL, or the coin's quote (USDC, …) for a pump.fun coin quoted in something else. Tokens on a sell, or a share of what the wallet holds: "100%" sells everything, "50%" half. |
slippage | number | Percent. Default 5. |
priorityFee | number | SOL. Default about 0.0002. |
pool | string | Which launchpad to route through, see below. Omit to auto-route from the mint. |
poolAddress | string | Required for raydium_cpmm, raydium_amm_v4 and meteora_damm_v2 when no pool is cached for the mint; optional for meteora_dbc. |
jitoTip | number | SOL. Appends a transfer to a Jito tip account so the signed transaction can go straight to a block engine. |
settle | string | For a coin paired with a pump coin: "sol" (default) trades with SOL through one multi-hop swap, "quote" pays in the quote coin from the wallet and is paid in it. See Coins paired with a pump coin. |
Launchpads
Every launchpad and AMM the data stream covers is tradable through the same call. Leave pool out and the mint is looked up and routed to its active pool automatically, following migrations. Set it only to force a launchpad.
pool | Launchpad |
|---|---|
pumpfun (or pump) | pump.fun bonding curve |
pumpswap | PumpSwap, where pump.fun coins trade after graduation |
bonk, stonkfun | Raydium LaunchLab: letsbonk.fun, StonkFun and every other LaunchLab platform |
meteora_dbc (or bags, moonshot) | Meteora Dynamic Bonding Curve: Bags, Moonshot and the rest of the Meteora launchpad |
raydium_cpmm | Raydium CPMM, where LaunchLab coins trade after graduation |
raydium_amm_v4 | Raydium AMM v4 |
meteora_damm_v2 | Meteora DAMM v2, where DBC coins trade after graduation |
Quoted coins
A coin need not be priced in SOL. pump.fun coins can be priced in USDC or another token, StonkFun coins on LaunchLab in a tokenised stock, a pre-IPO stock or a currency. The trade endpoint reads the quote from the coin's curve or pool and follows it, whatever the launchpad: a buy's amount is then how much of the quote to spend, the wallet must already hold that much plus the fee, a sell pays out in the quote, and the 0.25% fee is taken in the quote too. The response says which asset with quoteMint and feeMint; feeLamports is in the fee asset's base units. Quotes that are Token-2022 mints with a transfer fee (the xStocks) are handled; SOL is needed only for the network fee. Nothing to set, and token-info shows a coin's quote up front.
Coins paired with a pump coin
A pump.fun coin can be paired with another pump coin, which is itself paired with SOL. Such a coin is bought and sold with SOL: amount on a buy is SOL, a sell pays out SOL, and the 0.25% fee is taken in SOL, as on any SOL-paired coin. The trade is one multi-hop instruction, SOL → quote coin → coin on a buy and the reverse on a sell, through the quote coin's bonding curve or, once it has migrated, its pool. The wallet never holds the quote coin, and slippage is checked once, on the final amount. The response carries route (the coins passed through, ["SOL", "QUOTE", "COIN"] on a buy), hops (each venue with its amounts and the fee it charged) and expectedOut / minAmountOut (the quote the transaction was built on, in the coin's base units on a buy and lamports on a sell). A hop through a bonding curve that would empty it continues into the pool the migration opens with, at that pool's price; the hop then says syntheticMigration: true. Pass settle: "quote" to pay in the quote coin from the wallet instead, as for any quoted coin. Mayhem-mode coins and cashback coins cannot be passed through: they take the quoted path.
Several operations in one atomic transaction, including a launch followed by buys from other wallets, go through local-actions. Transfers, burns, wrapping SOL and balance reads are on Wallet utilities.
meteora_dbc supports SOL-quoted pools. Slippage on DBC is measured against the spot price, so leave headroom on a large buy into a steep curve.
The fee is 0.25% of the SOL side of the trade, included in the transaction. See Fees.
Try it
No API key needed - this endpoint is keyless. Paste your wallet private key and a token mint and press Sign & send. The API only ever receives your public key and returns an unsigned transaction; your browser signs it and posts the signed bytes to /api/send, or to your own RPC if you enter one. The key never leaves this tab. This is a real trade on mainnet, so start with a small amount.
If something goes wrong
You'll get a response like { "error": "..." } with a short message. Usual causes: a wrong wallet address, a wrong token mint, or not enough SOL. From /api/send, a 400 quotes the node's preflight reason (slippage, balance, expired blockhash) and not_ours means the transaction was not built by this API, or was changed after building.