Falcon submission methods
Falcon JSON-RPC, /binary, /plaintext and QUIC reference: request shapes, size limits, options and exactly what each success response proves.
Every route takes one signed Solana transaction per request. Which one you want depends on what your client is already holding, and on whether you need an answer back.
| Route | Request body | Body limit | Success | Preflight available |
|---|---|---|---|---|
JSON-RPC sendTransaction | JSON-RPC envelope, transaction as base58 or base64 string | 3,072 bytes | JSON result = base58 signature | Yes |
POST /binary | Raw serialized transaction bytes | 1,232 bytes | 200, text/plain base58 signature | No |
POST /plaintext | Base64 transaction text | 2,048 bytes | 200, text/plain base58 signature | No |
QUIC (falcon-client) | Serialized transaction on a stream or datagram | 1,232 bytes | Ok(()) | No |
| Falcon native UDP | API key prefix + raw transaction bytes | 1,232 bytes of transaction | Nothing | No |
The decoded transaction is capped at 1,232 bytes everywhere; the larger JSON-RPC and /plaintext limits cover the envelope and the base64 expansion on top of it.
Acceptance is not confirmation
Falcon answers the moment the transaction validates and the forward is dispatched. No transport reports a forwarding failure: the outcome is discarded and you get the signature either way. Confirm it on a normal Solana RPC.
Authenticate
| Transport | How the UUID is carried |
|---|---|
| All HTTP routes | ?api-key=YOUR_API_KEY_UUID query parameter |
| QUIC | The SDK puts the UUID in the CN of a self-signed client certificate at connect |
| Native UDP | The first 16 bytes (or 36 ASCII characters) of every datagram |
http://fra.falcon.wtf/binary?api-key=00000000-0000-0000-0000-000000000000Anything that stores a full URL stores your key with it; Telemetry covers what not to record.
JSON-RPC
POST to the region root. There are two methods.
| Method | Returns |
|---|---|
sendTransaction | Base58 signature string |
getVersion | {"solana-core":"falcon/<version>","feature-set":null} |
Anything else comes back as -32601 method not found. There are no batch arrays; send a single request object.
sendTransaction parameters
{
"jsonrpc": "2.0",
"id": 1,
"method": "sendTransaction",
"params": [
"SIGNED_TRANSACTION_BASE64",
{ "encoding": "base64", "skipPreflight": true }
]
}| Option | Accepted values | Default | Notes |
|---|---|---|---|
encoding | base58, base64 | base58 | Any other value → -32602 unsupported encoding |
skipPreflight | true, false | true | false makes Falcon simulate before forwarding, which costs latency |
preflightCommitment | processed, confirmed, finalized | unset | Only used when skipPreflight is false |
minContextSlot | non-negative integer | unset | Only used when skipPreflight is false |
maxRetries | — | — | Accepted and ignored; Falcon does not re-drive submissions for you |
Send a base64 string without "encoding":"base64" and you get -32602 transaction string is not valid base58.
Preflight
Set skipPreflight: false and Falcon simulates the transaction, signature verification included, against an upstream RPC before forwarding. A short server-side timeout bounds the wait.
| Preflight outcome | Response |
|---|---|
| Simulation returns an error | -32002, message Transaction simulation failed: <error>, data = the full simulation result including logs and unitsConsumed |
| Upstream RPC errors | -32603 preflight check failed: RPC node returned an error |
| Simulation exceeds the timeout | -32603 preflight check timed out |
None of those forward the transaction.
Reading responses
{"jsonrpc":"2.0","result":"5Yv...signature...","id":1}JSON-RPC errors come back with HTTP 200 and an error object. Two of them do carry a matching status: 401 with -32000 unauthorized, and 429 with -32001 rate limit exceeded. The full table is on Falcon errors & retries.
HTTP /binary
For when you're already holding serialized transaction bytes. No envelope, no length prefix, no key in the body.
curl -sS -X POST "http://fra.falcon.wtf/binary?api-key=${FALCON_API_KEY}" \
-H 'Content-Type: application/octet-stream' \
--data-binary @transaction.bin| Property | Value |
|---|---|
| Body | Raw serialized transaction |
| Maximum body | 1,232 bytes; a larger body is 413 request body too large |
| Success | 200, text/plain, base58 signature |
| Rejections | 400 with the reason as plain text, e.g. transaction does not include required tip |
HTTP /plaintext
Base64 transaction text, no JSON envelope. Whitespace around it is trimmed for you.
curl -sS -X POST "http://fra.falcon.wtf/plaintext?api-key=${FALCON_API_KEY}" \
-H 'Content-Type: text/plain' \
--data "${TX_BASE64}"| Property | Value |
|---|---|
| Body | Base64 text |
| Maximum body | 2,048 bytes → 413 request body too large |
| Maximum decoded transaction | 1,232 bytes → 413 decoded transaction too large |
| Invalid base64 | 400 invalid base64 |
| Non-UTF-8 body | 400 invalid utf8 |
| Success | 200, text/plain, base58 signature |
Both raw routes check size, signatures and tip; neither runs preflight.
QUIC with falcon-client
The published Rust SDK handles the persistent connection, serialization, authentication and one reconnect-and-retry for you.
[dependencies]
falcon-client = "0.1"
uuid = "1"use {falcon_client::FalconClient, uuid::Uuid};
let api_key = Uuid::parse_str("YOUR_API_KEY_UUID")?;
let mut client = FalconClient::connect("fra.falcon.wtf:5000", api_key).await?;
client.send_transaction(&transaction).await?;Other entry points, if you need them:
| Call | Use for |
|---|---|
send_transaction(&VersionedTransaction) | The normal path; the SDK serializes for you |
send_transaction_payload(&[u8]) / send_transaction_bytes(Bytes) | Transactions you already serialized |
connect_with_bind(addr, key, local_addr) | Pinning a fixed local UDP port for firewall rules |
set_send_timeout(Duration) | Bounding the stream-ack wait (default 100 ms, stream mode only) |
is_connected() | Liveness checks; the SDK reconnects on send failure anyway |
Transport modes
TransportMode::Stream queues the transaction as a QUIC datagram and sends it on a bidirectional stream that comes back with a server ack.
A server rejection on the stream reaches you as a SubmitError. But if the datagram was queued and the stream path then fails without a server rejection (a timeout, a stream error), you still get Ok(()). So read Ok(()) here as "the server accepted it, or it's on the wire and the ack got lost".
TransportMode::Datagram sends one fire-and-forget QUIC datagram: no stream, no ack, no server error.
use falcon_client::TransportMode;
client.set_transport_mode(TransportMode::Datagram);
client.send_transaction(&transaction).await?;Here Ok(()) means only that the packet was queued on your machine. The SDK does check the payload against the path MTU first and errors locally if it won't fit.
Switching modes takes effect on your next send, with no reconnect.
Delivery semantics
HTTP and QUIC stream mode acknowledge acceptance. QUIC datagram mode and native UDP acknowledge nothing.
Falcon keeps a signature-keyed cache of recent submissions and suppresses a duplicate, but only once that signature has already been delivered. That's why resending the same signed bytes while the blockhash is still valid is still the right way to chase a landing.
Don't re-sign to retry, though. A new signature is a different transaction, and it can land independently of the first.
Related
Falcon tips & transaction requirements
Exactly what Falcon accepts as a tip on a Solana transaction: instruction shape, allowed accounts, the 1,000,000 lamport minimum and the 1,232-byte cap.
Falcon native UDP
The exact Falcon UDP datagram frame for Solana transactions, every silent drop reason, and how to retry when nothing ever answers.