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.
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(process.env.WALLET_SECRET));
// 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. |
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 pump.fun coins
pump.fun coins can be priced in USDC or another token instead of SOL. The API detects the coin's quote from its curve: a buy's amount is then in that quote, a sell pays out in it, and the 0.25% fee is taken in it too. The response says which with quoteMint and feeMint; feeLamports is in the fee asset's base units. No field to set, and token-info shows a coin's quote up front.
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.