sendBundle
Submit one to four signed transactions as one bundle for delivery through block engines.
{
"jsonrpc": "2.0",
"id": 1,
"method": "sendBundle",
"params": [["SIGNED_TRANSACTION_1", "SIGNED_TRANSACTION_2"], { "encoding": "base64" }]
}Params
| Position | Type | Content |
|---|---|---|
| 0 | array of strings | One to four fully signed wire transactions, in execution order, encoded as specified by options.encoding. |
| 1 | object, optional | Options, below. |
Options
| Field | Type | Default | Meaning |
|---|---|---|---|
encoding | string | "base58" | "base58" or "base64", for every transaction of the bundle. |
tip | integer, lamports | none | One declared tip for the whole bundle. Used only when the bundle's own tips are below your minimum and your key may declare tips. |
Other fields, skipPreflight among them, are ignored: bundles are never simulated. Each transaction has the size limits of sendTransaction.
Result
The bundle id: 64 lowercase hex characters, with HTTP 200.
{"jsonrpc":"2.0","result":"BUNDLE_ID_HEX","id":1}The id is SHA-256 of the members' first signatures, concatenated in order as raw 64-byte values. It is not a block engine's status id. SWQoS has no bundle status method; confirm each signature through your Solana RPC provider.
A result acknowledges submission to at least one block engine, not landing.
Submission requirements
Each member must meet the transaction size limits. The bundle must satisfy the tip, control-account and rate rules. Bundles have no preflight simulation.
Errors
| Reason | HTTP | Code | When |
|---|---|---|---|
unauthorized | 401 | -32000 | The key is missing, unknown or revoked. |
invalid_request | 200 | see codes | The request is not a valid sendBundle call. |
bundle_size | 200 | -32602 | Fewer than 1 or more than 4 transactions. count and limit. |
malformed, too_large, unsigned, signature_count | 200 | -32602 | A transaction failed to decode or check. index is its position. |
bundle_control | 200 | -32602 | A control-account rule was broken. index and rule. |
payment_required | 402 | -32003 | The key is suspended until its overdue invoice is paid. |
missing_tip | 200 | -32602 | The tips are below your minimum, or below the engine minimum. minimum is the one that applies. |
credit_exhausted | 200 | -32602 | Credit is unavailable for this declared-tip submission. |
not_ready | 503 | -32603 | waiting_for is block_engine, blockhash, tip_vaults or declared_tips. |
rate_limited | 429 | -32001 | limit: bundles for bundle admission, or limit: transactions for your key's send rate. Wait for retry_after_ms when supplied. |
overloaded | 503 | -32603 | The region is temporarily busy. |
For not_ready with waiting_for: block_engine, wait and retry while the signed transactions remain valid. Do not submit members separately if they must execute together.
Examples
This example sends signed transaction files, in order, as one bundle with the client helper. Together the transactions must carry the tip.
import { readFile } from 'node:fs/promises'
import { call, SwqosError } from './swqos.ts'
const files = process.argv.slice(2)
const transactions = await Promise.all(
files.map(async (file) => (await readFile(file)).toString('base64')),
)
try {
const bundleId = await call<string>('sendBundle', [transactions, { encoding: 'base64' }])
console.log('accepted', bundleId)
} catch (error) {
if (!(error instanceof SwqosError) || !error.rejection) throw error
console.error(error.rejection.reason, error.rejection.message)
}The same id, computed locally from the wire bytes with @solana/kit:
import { createHash } from 'node:crypto'
import {
getBase58Encoder,
getSignatureFromTransaction,
getTransactionDecoder,
} from '@solana/kit'
export function bundleId(wireTransactions: Uint8Array[]): string {
const hash = createHash('sha256')
for (const wire of wireTransactions) {
const signature = getSignatureFromTransaction(getTransactionDecoder().decode(wire))
hash.update(Uint8Array.from(getBase58Encoder().encode(signature)))
}
return hash.digest('hex')
}