Skip to content

Errors

Every authenticated RPC can fail. Most failures fall into one of five codes; the SDKs surface the gRPC status verbatim plus an optional domain-specific reason string.

Codes you'll see

gRPC codeWhenRetry?
UNAUTHENTICATEDSession token missing / expired / revoked. Pubkey not registered as a quoting authority.Refresh the session and retry once.
INVALID_ARGUMENTBad pair name, malformed pubkey, out-of-range limit, unsupported interval.No — fix the input.
RESOURCE_EXHAUSTEDPer-IP rate-limit bucket empty.Yes, with exponential backoff. The bucket replenishes.
FAILED_PRECONDITIONHistorical query when the historical archive is disabled on this deployment.No — feature isn't enabled.
NOT_FOUNDPair not in catalog, maker_id has no balances.No — fix the input.
UNAVAILABLETransport drop. The server is restarting or unreachable.Yes, with backoff. ResilientStream auto-retries server streamsResilientStream backs chain-tip and tx-status helpers; retry generated streams in your loopstreamWithBackoff auto-retries server streams; for unary calls you decide the policy.
INTERNALA bug. Report it.Capped retry (1–3 attempts) before surfacing.

The SDKs don't retry by default. Pick your policy explicitly.

Reading errors

rust
use tonic::Code;

match client.get_balance(req).await {
    Ok(res) => /* ... */,
    Err(status) => match status.code() {
        Code::Unauthenticated => auth.refresh().await?,
        Code::ResourceExhausted => sleep_then_retry().await,
        _ => return Err(status.into()),
    },
}
python
import grpc

try:
    await client.list_markets()
except grpc.RpcError as exc:
    code = exc.code()
    if code == grpc.StatusCode.RESOURCE_EXHAUSTED:
        await sleep_then_retry()
    if code == grpc.StatusCode.UNAVAILABLE:
        await sleep_then_retry()
ts
import { GrpcError, GrpcCode } from "@superis-labs/flint-api-client";

try {
  await client.listPairs({});
} catch (err) {
  if (err instanceof GrpcError) {
    if (err.code === GrpcCode.ResourceExhausted) await sleep(backoff);
    if (err.code === GrpcCode.Unavailable) await sleep(backoff);
  }
}

Auth-flow specific failures

AuthService.Authenticate returns UNAUTHENTICATED for any of:

  • The pubkey has no outstanding nonce (you didn't call Challenge first).
  • The nonce expired (TTL exceeded between Challenge and Authenticate).
  • The signature doesn't cover b"SWEETSPOT-AUTH-V1:" || nonce.
  • The pubkey isn't registered as a quoting authority for any maker.

Refreshing the session always runs Challenge → sign → Authenticate from scratch, so you don't need to think about nonce expiry yourself.

On-chain failures (quoting)

When you submit a tx via the quoting layer, the on-chain program may revert. The SDK surfaces this through Receipt:

rust
match receipt.landed().await {
    Ok(()) => /* landed */,
    Err(CommitError::OnChain { reason }) => {
        // Reason is whatever the on-chain program returned.
        // Common: "OracleFairSequenceNotMonotonic", "InsufficientBalance".
    }
    Err(CommitError::Timeout) => /* didn't land in time */,
    Err(CommitError::Disconnected) => /* status stream dropped */,
}
python
from flint import CommitDisconnected, CommitTimeout, OnChainFailure

try:
    await receipt.landed()
except OnChainFailure as exc:
    reason = exc.reason
except CommitTimeout:
    pass
except CommitDisconnected:
    pass

The most common on-chain reverts and what to do about them:

ReasonCauseFix
OracleFairSequenceNotMonotonicTwo clients sharing a maker_id, or a clock-skew restart.Don't share maker_ids; the SDK's nanosecond seed handles restarts.
OrderSequenceNotMonotonicSame as above for UpdateQuotingParams.Same fix.
OracleFairTooStalecurrent_slot - last_oracle_slot > order.staleness.Increase staleness or flush more often.
InsufficientBalanceOne of the legs the matcher budgets against — your micro-book or the counterparty — has zero deposited inventory or zero soft_max_balance headroom.See Budgets per leg.

landed() returns the first on-chain failure and stops. When a single quote commit updates several markets, some can land while others revert. To get the full split — which of your updates landed and which failed — use receipt.settled() instead of landed(). It reports landed_intents and failed_intents as flat indices over the (market, kind) intents you submitted, without short-circuiting on the first failure; resolve an index to its market and operation with receipt.intents(). See Receipts.

Built on Solana