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 code | When | Retry? |
|---|---|---|
UNAUTHENTICATED | Session token missing / expired / revoked. Pubkey not registered as a quoting authority. | Re-authenticate (AuthFlow.refresh()) and retry once. |
INVALID_ARGUMENT | Bad pair name, malformed pubkey, out-of-range limit, unsupported interval. | No — fix the input. |
RESOURCE_EXHAUSTED | Per-IP rate-limit bucket empty. | Yes, with exponential backoff. The bucket replenishes. |
FAILED_PRECONDITION | Historical query when the historical archive is disabled on this deployment. | No — feature isn't enabled. |
NOT_FOUND | Pair not in catalog, maker_id has no balances. | No — fix the input. |
UNAVAILABLE | Transport drop. The server is restarting or unreachable. | Yes, with backoff. The SDK's ResilientStream auto-retries; for unary calls you decide the policy. |
INTERNAL | A 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
Challengefirst). - The nonce expired (TTL exceeded between
ChallengeandAuthenticate). - 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:
| Reason | Cause | Fix |
|---|---|---|
OracleFairSequenceNotMonotonic | Two clients sharing a maker_id, or a clock-skew restart. | Don't share maker_ids; the SDK's microsecond seed handles restarts. |
OrderSequenceNotMonotonic | Same as above for UpdateQuotingParams. | Same fix. |
OracleFairTooStale | current_slot - last_oracle_slot > order.staleness. | Increase staleness or flush more often. |
InsufficientBalance | The two-balance trap. | See How Sweetspot works. |