Corvus Labs

Falcon errors & retries

Every Falcon rejection by transport: JSON-RPC codes and messages, raw HTTP statuses, QUIC SubmitError codes, UDP silence, and what to retry.

The same rejection looks completely different depending on how you submitted it.

TransportWhere the error appears
JSON-RPCerror object in the body, usually with HTTP 200
/binary, /plaintextHTTP status + a short plain-text body
QUICSubmitError from falcon-client, or a connection close with a reason string
Native UDPNowhere. Nothing is sent back for any reason

JSON-RPC errors

Falcon answers with HTTP 200 for almost every JSON-RPC error. Only two carry a matching status.

CodeHTTPMessageCauseFix
-32700200parse error: <detail>Body is not valid JSONSend a valid JSON object
-32600200invalid requestNot a POST, body is not a JSON object, or jsonrpc is missingPOST a JSON-RPC 2.0 object
-32600200unsupported jsonrpc versionjsonrpc is not "2.0"Set "jsonrpc":"2.0"
-32600200missing methodNo method fieldAdd sendTransaction or getVersion
-32600200request body too large (limit 3072 bytes)Body over 3 KiBShrink the envelope; the transaction itself is capped at 1,232 bytes
-32600200request body read timed outThe body never finished arrivingResend the same signed transaction
-32601200method not foundAny method other than the two supportedFalcon is not a full RPC; use your Solana RPC
-32602200See the parameter table belowThe request or the transaction is invalidFix and re-sign; do not retry identical bytes
-32603200server configuration error: tip accounts not setServer-side tip configurationReport on Discord
-32603200preflight check failed: RPC node returned an errorUpstream simulation RPC erroredRetry, or submit with skipPreflight: true
-32603200preflight check timed outSimulation exceeded its timeoutRetry, or submit with skipPreflight: true
-32000401unauthorizedapi-key missing, not a UUID, or not a live keyFix the key; do not retry
-32001429rate limit exceededOver your per-second submission allowanceSlow down, then resend the same bytes
-32002200Transaction simulation failed: <error>Preflight simulation returned an errorRead data, fix the transaction

-32602 messages

MessageMeaning
expected params array in request bodyparams missing or not an array
first parameter must be a base58 or base64 encoded transaction stringparams[0] is not a string
unsupported encodingencoding is neither base58 nor base64
transaction string is not valid base58Usually base64 sent without "encoding":"base64"
transaction string is not valid base64Malformed base64
transaction exceeds maximum size of 1232 bytesDecoded transaction over the cap
transaction has no valid signatureUnsigned, or a signature count of 0
could not deserialize transaction: verify encoding and formatNot a parseable Solana transaction, or extra trailing bytes
transaction signature count does not match required signaturesWith "data":{"expected":n,"actual":n}
transaction does not include required tipWith "data":{"minimumLamports":n}; see Tips

-32002 carries the whole simulation result in data, including err, logs and unitsConsumed.

Raw HTTP errors

/binary and /plaintext answer with a status and a short plain-text body. Several causes share a status, so the body is the only thing that separates them.

StatusBodyCauseRetry?
200base58 signatureAccepted and forwarded
400failed to read bodyConnection broke mid-bodyYes
400invalid utf8/plaintext body is not UTF-8 textNo
400invalid base64/plaintext body is not valid base64No
400transaction has no valid signatureUnsigned transactionNo
400could not deserialize transactionNot a parseable transactionNo
400transaction signature count does not match required signaturesMissing a signerNo
400transaction does not include required tipTip missing or below the minimumNo
401unauthorizedAPI key missing, malformed or revokedNo
405method not allowedUsed a verb other than POSTNo
408body read timed outBody did not arrive in timeYes
413request body too largeOver 1,232 bytes on /binary, or over 2,048 on /plaintextNo
413decoded transaction too large/plaintext base64 decodes to more than 1,232 bytesNo
429rate limitedOver your per-second allowanceYes, after backing off

No HTTP route reports a forwarding failure. Once validation passes you get 200 and a signature, whatever the forward does.

QUIC rejections

Server rejections reach you as a SubmitError. On the wire that's two bytes: 0x01, then the code.

CodeSubmitErrorMeaningRetry?
0x00Accepted
0x01RateLimitedOver your per-second allowanceYes, after backing off
0x02UnsignedTransaction has no valid signatureNo
0x03MissingTipTip missing or below the minimumNo
0x04DeserializeFailedBytes are not a parseable transactionNo
0x05TooLargeOver 1,232 bytesNo
0x06ForwardFailedDefined in falcon-client, but the server does not currently return it; once validation passes the stream ack is always Accepted, whatever the forward doesYes, same bytes
0x07SignatureCountMismatchSignature count ≠ required signersNo
0x08UnauthorizedKey revoked or no longer authorizedNo
otherUnknown(code)Newer server rejectionLog the code and ask on Discord
use falcon_client::SubmitError;

match client.send_transaction(&transaction).await {
    Ok(()) => {}
    Err(error) => match error.downcast_ref::<SubmitError>() {
        Some(SubmitError::RateLimited) => {
            // Back off, then resubmit the same signed bytes.
        }
        Some(rejection) => {
            // Permanent: fix the transaction.
            eprintln!("falcon rejected the transaction: {rejection}");
        }
        None => {
            // Transport-level: timeout, MTU, connection lost.
            eprintln!("falcon transport error: {error}");
        }
    },
}

An Err with no SubmitError inside is a transport problem rather than a rejection, and by the time you see it the SDK has already reconnected and retried once.

Connection-level failures

When the server closes the QUIC connection, it gives you a readable reason:

Reason stringCauseFix
Missing API key in client certificate CN. Support: discord.gg/corvus-labsThe client certificate carried no UUIDUse falcon-client, or set the certificate CN to your key
Invalid or revoked API key. Support: discord.gg/corvus-labsKey is not liveCheck the key on Discord
Connection limit reached. Close unused connections or upgrade. Support: discord.gg/corvus-labsToo many concurrent connections for this keyReuse one long-lived client instead of connecting per submission

Individual streams also get reset when one connection has too many in flight at once; cap your concurrent sends rather than opening more connections.

Native UDP

Native UDP has no error surface at all. Bad frame, bad key, oversized transaction, missing tip, forward failure: every one is the same silence. The full drop list is on Native UDP.

Retry decision table

ConditionRetry?How
401 / -32000 / SubmitError::UnauthorizedNoFix the key
Encoding, size, signature or tip rejectionNoFix the transaction and re-sign; identical bytes fail identically
-32002 preflight simulation failedNoRead the simulation logs and fix the transaction
429 / -32001 / SubmitError::RateLimitedYesSlow to your allowance, then resend the same bytes
408, -32600 request body read timed out, -32603 preflight errorsYesResend the same signed bytes
SubmitError::ForwardFailed, connection lost, socket errorYesResend the same signed bytes
Accepted but not landingYesResend the same bytes while the blockhash is valid
Blockhash expiredNoRebuild and re-sign; this is a new signature

Stop on blockhash expiry rather than on an attempt count.

Duplicates

Falcon keeps a signature-keyed cache of recent submissions and short-circuits a duplicate instead of forwarding it again. That only takes effect after a submission of that signature has been delivered, so a copy arriving while the first is still in flight is forwarded too. That's why resending the same signed bytes to chase a landing is expected behaviour. Delivery still isn't exactly-once in either direction.

Acceptance is not landing

Falcon's answer never depends on the outcome of the forward, on any transport.

SignalWhat it proves
JSON-RPC result / HTTP 200 + signatureValidation passed and the forward was dispatched
QUIC stream Ok(())The server accepted it, or the datagram was already queued and the ack was lost
QUIC datagram-mode Ok(())A packet was queued on your machine
UDP send() returning without errorYour kernel accepted the datagram
getSignatureStatuses showing the signatureIt landed

Record both sides. Telemetry has the fields to keep.

On this page