Skip to main content

Multi-action transactions

One transaction, several things in it: buy on one launchpad and sell on another, launch a coin and buy it, buy and move the tokens somewhere else, claim creator fees and burn. Everything in the list lands together or not at all.

POST https://sol.shrine.trade/api/local-actions

{
"publicKey": "<fee payer, and the wallet for actions without their own>",
"actions": [
{ "action": "buy", "mint": "<mint>", "amount": 0.05, "slippage": 15 },
{ "action": "transfer", "to": "<other wallet>", "mint": "<mint>", "amount": "50%" },
{ "action": "burn", "mint": "<mint>", "amount": "100%" }
],
"priorityFee": 0.0005,
"jitoTip": 0
}

The answer is one unsigned transaction plus the list of keys that must sign it:

{
"tx": "<base64 unsigned transaction>",
"blockhash": "…",
"signers": ["<fee payer>", "<other signing wallets or mint keypairs, in order>"],
"feeLamports": 125000,
"sizeBytes": 1088,
"actions": [
{ "action": "buy", "wallet": "…", "mint": "…", "amount": 0.05, "feeLamports": 125000 },
{ "action": "transfer", "wallet": "…", "mint": "…", "amount": 12345.6, "feeLamports": 0 },
{ "action": "burn", "wallet": "…", "mint": "…", "amount": 12345.6, "feeLamports": 0 }
]
}

Sign with every key in signers and send it through your own RPC, exactly like a single trade.

Actions

Every action takes an optional publicKey. Without one it runs as the top-level wallet; with one, that wallet signs too and appears in signers.

actionFieldsNotes
buymint, amount (SOL, or the coin's quote), slippage, pool, poolAddressAny launchpad the trade endpoint routes. 0.25% fee.
sellmint, amount (tokens or "100%"), slippage, pool, poolAddress0.25% fee.
createmint, mintRef, name, symbol, uri, initialBuy, slippage, creatorFeeAddress, quoteMint and the V2 flagsA pump.fun launch, see create-token. The mint keypair signs.
transferto, amount, mint (omit for SOL)Creates the recipient's token account if needed. Free.
burnmint, amountBurning all of it closes the account and refunds its rent. Free.
claimCreatorFeesSame as claim-creator-fees. Free.
wrapamount (SOL)SOL into the wallet's WSOL account. Free.
unwrapCloses the WSOL account, every lamport comes back. Free.

amount on a sell, transfer or burn may be a number of tokens or a percentage of what the wallet holds. "100%", "50%" and "all" are all understood.

Launch and buy in one go

A coin created in the transaction has no address yet when you write the request, so give the create a mintRef and refer to it as "$name" afterwards. Generate the mint keypair yourself and pass its public key as mint; it signs the transaction along with the wallet.

{
"publicKey": "<dev wallet>",
"actions": [
{ "action": "create", "mintRef": "coin", "mint": "<mint keypair public key>", "name": "My Coin", "symbol": "COIN", "uri": "https://…/metadata.json", "initialBuy": 0.5 },
{ "action": "buy", "mint": "$coin", "amount": 0.2, "slippage": 30 }
]
}

Buys on a coin born in the same transaction are quoted from the curve's launch constants, moved along by the SOL already put in before them, so give them a generous slippage.

How much fits

Solana caps a transaction at 1232 bytes. With the shared lookup table that is about four swaps on one launchpad, fewer across launchpads, and a launch with a dev buy and one more buy. When a set does not fit, the answer is a 400 naming the size; split it across a bundle, where mintRef still resolves from one transaction to the next.

Copy this

import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";

const wallet = Keypair.fromSecretKey(bs58.decode(process.env.SECRET));
const conn = new Connection(process.env.RPC_URL, "confirmed");

const res = await fetch("https://sol.shrine.trade/api/local-actions", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
publicKey: wallet.publicKey.toBase58(),
actions: [
{ action: "buy", mint: "<mint>", amount: 0.01, slippage: 15 },
{ action: "transfer", to: "<friend>", mint: "<mint>", amount: "50%" },
],
}),
});
const { tx, signers } = await res.json();

const txObj = VersionedTransaction.deserialize(Buffer.from(tx, "base64"));
txObj.sign([wallet]); // every key listed in `signers`
console.log(await conn.sendRawTransaction(txObj.serialize()));

Notes

  • Atomic. One instruction failing reverts the whole transaction: nothing partial ever lands.
  • Priority fee is one number for the whole transaction. The compute budget is sized from the actions inside.
  • Fees. 0.25% on each buy and sell, taken in SOL inside the same transaction and merged into one transfer per wallet. Everything else is free.
  • Keyless and unsigned, like every trade endpoint. No key, no account; nothing is signed or sent by us.