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.Re-authenticate (AuthFlow.refresh()) 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. The SDK's ResilientStream auto-retries; 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()),
    },
}
go
import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

if err != nil {
    s, _ := status.FromError(err)
    switch s.Code() {
    case codes.Unauthenticated:
        _, _ = auth.Refresh(ctx)
    case codes.ResourceExhausted:
        time.Sleep(backoff)
    default:
        return err
    }
}
ts
import { ConnectError, Code } from "@connectrpc/connect";

try {
  await client.getBalance(req);
} catch (err) {
  if (err instanceof ConnectError) {
    if (err.code === Code.Unauthenticated) await auth.refresh();
    if (err.code === Code.ResourceExhausted) 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.

The SDKs' AuthFlow.refresh() 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.confirmed().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 */,
}

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 microsecond seed handles restarts.
OrderSequenceNotMonotonicSame as above for UpdateQuotingParams.Same fix.
OracleFairTooStalecurrent_slot - last_oracle_slot > order.staleness.Increase staleness or flush more often.
InsufficientBalanceThe two-balance trap.See How Sweetspot works.

Apache 2.0