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