Raw HTTP
Send raw transaction and bundle bytes over HTTP with /binary and /plaintext, and the payload envelope they share.
The raw routes take a transaction's wire bytes instead of a JSON-RPC request. They serve the same hosts, ports and authentication as JSON-RPC.
| Route | Key | Body | Success |
|---|---|---|---|
POST /binary | yes | A payload, raw bytes | 200, text/plain |
POST /plaintext | yes | A payload, base64 text | 200, text/plain |
GET /health | no | none | Health check |
GET /tip_floor | yes | none | Tip floor |
POST /binary
Send one payload: a signed transaction or an envelope with a declared tip or bundle. The maximum body is 16,403 bytes, received within 2 seconds.
| Payload | Success body |
|---|---|
| One transaction | Its first signature, base58 |
| A bundle | The bundle id, 64 hex characters |
There is no encoding option or preflight simulation. The same tip, rate and transaction rules apply as for JSON-RPC.
POST /plaintext
The body is the same payload in standard base64 with padding, on one line. Whitespace before and after it is ignored. The body may be up to 21,888 bytes, and the decoded payload up to 16,403 bytes. Responses are those of /binary.
Payload envelope
Every raw channel carries this payload: /binary, /plaintext, QUIC datagrams and streams, and the agent's IPC records.
A payload whose first byte is not 0x00 is one signed transaction in wire format.
A payload that starts with 0x00 is an envelope:
| Offset | Size | Field |
|---|---|---|
| 0 | 1 | 0x00 |
| 1 | 1 | Flags: 0x01 declared tip present, 0x02 bundle body. No other bits may be set. |
| 2 | 8 | Declared tip in lamports, u64 little endian. Present only when flags include 0x01. |
| 2 or 10 | rest | The body. |
Without the 0x02 flag, the body is one transaction. With it, the body is:
| Size | Field |
|---|---|
| 1 | Transaction count, 1 to 4 |
| 2 | Length of the next transaction, u16 little endian, 1 to 4,096 |
| length | The transaction's wire bytes |
The length and transaction pair repeats once per transaction, and no bytes may follow the last one. The largest payload, a declared-tip bundle of four 4,096-byte transactions, is 16,403 bytes.
| Payload | Bytes |
|---|---|
| One transaction | TX |
| One transaction, declared tip of 1,000,000 lamports | 00 01 40 42 0f 00 00 00 00 00 TX |
| Bundle of two | 00 02 02 L1 L1 TX1 L2 L2 TX2 |
| Bundle of two, declared tip of 1,000,000 lamports | 00 03 40 42 0f 00 00 00 00 00 02 L1 L1 TX1 L2 L2 TX2 |
Here L1 L1 is the little-endian length of TX1. An envelope that breaks these rules is refused as malformed with problem invalid_envelope, or as bundle_size when the count is 0 or above 4.
Responses
A rejection is a JSON object with the HTTP status below: the reason, its fields, message, retryable and docs, as in the JSON-RPC error.data.
{
"reason": "missing_tip",
"minimum": 1000000,
"found": 123,
"message": "tip too small: the tip transfers add up to 123 lamports, the minimum is 1,000,000; raise the transfer to one of the tip accounts to at least 1,000,000 lamports",
"retryable": false,
"docs": "https://docs.zerocopy.sh/errors#missing_tip"
}| HTTP | Reasons |
|---|---|
| 400 | malformed, unsigned, signature_count, missing_tip, credit_exhausted, bundle_size, bundle_control, invalid_request not listed below |
| 401 | unauthorized |
| 402 | payment_required |
| 404 | invalid_request problem not_found; unsupported from the agent |
| 408 | invalid_request problem body_timeout |
| 413 | too_large |
| 429 | rate_limited |
| 503 | not_ready, overloaded, forward_failed, preflight_unavailable, internal |
A GET to any path other than /health and /tip_floor, and any method other than GET and POST, is refused with 404 and problem not_found. A POST to any path other than the raw routes is JSON-RPC.
Examples
curl -sS "https://sol.zerocopy.sh/binary" \
-H "x-api-key: $SWQOS_API_KEY" \
-H 'content-type: application/octet-stream' \
--data-binary @signed-tx.binbase64 -w0 signed-tx.bin | curl -sS "https://sol.zerocopy.sh/plaintext" \
-H "x-api-key: $SWQOS_API_KEY" \
--data-binary @-base64 -w0 is GNU coreutils; on macOS, base64 -i signed-tx.bin writes one line.
This helper builds envelopes. It needs no dependencies.
export function transactionPayload(
transaction: Uint8Array,
declaredTip?: bigint,
): Uint8Array<ArrayBuffer> {
if (declaredTip === undefined) return concat([transaction])
return concat([Uint8Array.of(0x00, 0x01), u64(declaredTip), transaction])
}
export function bundlePayload(
transactions: Uint8Array[],
declaredTip?: bigint,
): Uint8Array<ArrayBuffer> {
if (transactions.length < 1 || transactions.length > 4) {
throw new RangeError('a bundle holds 1 to 4 transactions')
}
const parts: Uint8Array[] = [Uint8Array.of(0x00, declaredTip === undefined ? 0x02 : 0x03)]
if (declaredTip !== undefined) parts.push(u64(declaredTip))
parts.push(Uint8Array.of(transactions.length))
for (const transaction of transactions) parts.push(u16(transaction.length), transaction)
return concat(parts)
}
function u64(value: bigint): Uint8Array<ArrayBuffer> {
const bytes = new Uint8Array(8)
new DataView(bytes.buffer).setBigUint64(0, value, true)
return bytes
}
function u16(value: number): Uint8Array<ArrayBuffer> {
const bytes = new Uint8Array(2)
new DataView(bytes.buffer).setUint16(0, value, true)
return bytes
}
function concat(parts: Uint8Array[]): Uint8Array<ArrayBuffer> {
const out = new Uint8Array(parts.reduce((size, part) => size + part.length, 0))
let offset = 0
for (const part of parts) {
out.set(part, offset)
offset += part.length
}
return out
}import { readFile } from 'node:fs/promises'
import { bundlePayload } from './payload.ts'
import { required, url } from './swqos.ts'
// Signed transaction files, in bundle order.
const transactions = await Promise.all(
process.argv.slice(2).map(async (file) => new Uint8Array(await readFile(file))),
)
const response = await fetch(`${url}/binary`, {
method: 'POST',
headers: { 'x-api-key': required('SWQOS_API_KEY') },
body: bundlePayload(transactions),
})
const body = await response.text()
console.log(response.ok ? `bundle ${body}` : `refused ${response.status}: ${body}`)send-binary.ts uses the url and required exports of the client helper.