Skip to content

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:

http
POST /v1/bundle
Content-Type: application/json
x-api-key: YOUR_API_KEY

{
  "transactions": [
    "AQAAAA...base64SignedTx1...",
    "AQAAAA...base64SignedTx2..."
  ]
}

Body fields:

FieldTypeDescription
transactionsstring[]1–5 base64 entries, each a fully-signed VersionedTransaction. Order is preserved on submission.

Response (success):

json
{
  "bundleId": "abc123…",
  "signatures": [
    "5Kx9abc…",
    "9YuLdef…"
  ],
  "status": "submitted",
  "submittedAt": 1712000000000
}

Response fields:

FieldDescription
bundleIdJito bundle identifier — usable against bundle-status surfaces. May be null if the block engine accepted but did not return one.
signaturesPer-transaction signatures (base58), in the same order as the input.
statusAlways "submitted" — does not wait for confirmation.
submittedAtUNIX millisecond timestamp at which submission completed.

Errors:

StatusDescription
400Body is not JSON, transactions missing/empty, more than 5 entries, or a tx is malformed / unsigned.
401Missing or invalid API key.
429Rate limit exceeded.
502Bundle was rejected by the block engine, or submission failed before the engine could ack.
503Bundle submission is unavailable on this deployment.

Tracking

Bundle landing is tracked via the per-transaction signatures, not the bundle ID. Use either:

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
3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT

Pick 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

typescript
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.