Bundle Submission
Submit an atomic multi-transaction bundle. All transactions in the bundle execute in order, or the entire bundle fails — there is no partial-fill state.
Requires API key.
Bundles are routed to Jito's block engine. Standard rules apply:
- Include a Jito tip transaction in the bundle if you want a realistic landing rate on contested slots.
- Bundles are capped at 5 transactions.
- Each transaction must be already signed.
POST /v1/bundle
Request:
POST /v1/bundle
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"transactions": [
"AQAAAA...base64SignedTx1...",
"AQAAAA...base64SignedTx2..."
]
}Body fields:
| Field | Type | Description |
|---|---|---|
transactions | string[] | 1–5 base64 entries, each a fully-signed VersionedTransaction. Order is preserved on submission. |
Response (success):
{
"bundleId": "abc123…",
"signatures": [
"5Kx9abc…",
"9YuLdef…"
],
"status": "submitted",
"submittedAt": 1712000000000
}Response fields:
| Field | Description |
|---|---|
bundleId | Jito bundle identifier — usable against bundle-status surfaces. May be null if the block engine accepted but did not return one. |
signatures | Per-transaction signatures (base58), in the same order as the input. |
status | Always "submitted" — does not wait for confirmation. |
submittedAt | UNIX millisecond timestamp at which submission completed. |
Errors:
| Status | Description |
|---|---|
400 | Body is not JSON, transactions missing/empty, more than 5 entries, or a tx is malformed / unsigned. |
401 | Missing or invalid API key. |
429 | Rate limit exceeded. |
502 | Bundle was rejected by the block engine, or submission failed before the engine could ack. |
503 | Bundle submission is unavailable on this deployment. |
Tracking
Bundle landing is tracked via the per-transaction signatures, not the bundle ID. Use either:
GET /v1/stream/tx?signature=…— SSE lifecycle stream per signature.GET /v1/tx/:signature— point-in-time status snapshot.
Atomic bundle semantics mean that if any signature lands, all do. A signature that doesn't land within ~10 seconds typically means the bundle was dropped (insufficient tip vs. competing bundles is the most common reason).
Including the Jito tip
A Jito bundle won't land on contested slots without a tip. The tip itself is just a regular SystemProgram.transfer to one of Jito's eight published tip accounts — include it as the first or last transaction in the bundle.
Tip accounts (Jito docs):
96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5
HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe
Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY
ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49
DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh
ADuUkR4vqLUMWXxW9gh6D6L8pivKeVBBjNs1FsGo3KrJ
DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL
3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jTPick one at random per bundle — Jito rotates which account is "live" with each leader, so randomising spreads load and avoids rejections.
Tip amount — start at 1,000–10,000 lamports for non-competitive flows. Contested arbitrage / sandwich-resistant trades typically tip 100k+ lamports. Jito publishes live tip-floor percentiles via their API; query and bid above the 50th percentile for a realistic landing rate.
Example
import {
Connection,
Keypair,
PublicKey,
SystemProgram,
TransactionMessage,
VersionedTransaction,
} from '@solana/web3.js';
// Jito tip accounts — rotate randomly per bundle.
const JITO_TIP_ACCOUNTS = [
'96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5',
'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe',
'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY',
'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49',
'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh',
'ADuUkR4vqLUMWXxW9gh6D6L8pivKeVBBjNs1FsGo3KrJ',
'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL',
'3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT',
];
async function buildTipTx(
conn: Connection,
payer: Keypair,
tipLamports: number,
): Promise<VersionedTransaction> {
const { blockhash } = await conn.getLatestBlockhash('confirmed');
const tipAccount = new PublicKey(
JITO_TIP_ACCOUNTS[Math.floor(Math.random() * JITO_TIP_ACCOUNTS.length)]!,
);
const message = new TransactionMessage({
payerKey: payer.publicKey,
recentBlockhash: blockhash,
instructions: [
SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: tipAccount,
lamports: tipLamports,
}),
],
}).compileToV0Message();
const tx = new VersionedTransaction(message);
tx.sign([payer]);
return tx;
}
// Build, sign each transaction locally — Venum never sees private keys.
const tipTx = await buildTipTx(conn, wallet, 50_000);
const swapTx = await buildSwapTx(conn, wallet); // your own builder, signed
const transactions = [tipTx, swapTx].map((t) =>
Buffer.from(t.serialize()).toString('base64'),
);
const res = await fetch('https://api.venum.dev/v1/bundle', {
method: 'POST',
headers: {
'X-API-Key': process.env.VENUM_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ transactions }),
});
const result = await res.json();
console.log('bundle:', result.bundleId);
console.log('sigs: ', result.signatures);When to use
| You want… | Use |
|---|---|
| Atomic multi-leg execution (all-or-nothing) | /v1/bundle |
| Single-tx submission with multi-channel fan-out | /v1/send |
| Quote-bound swap submission via Venum's build flow | /v1/swap |
Rate Limit
See Plans & Rate Limits for the current per-tier limits. Bundles count against the same send per-minute quota as /v1/send.
