Errors
Failures come from two places:
- The API returns a standard gRPC status — a code plus a human-readable message. The SDKs surface it verbatim.
- The margin program rejects a transaction with a custom program
error code (
custom program error: 0x…). You see these from Solana RPC, fromSponsorshipService.SponsorTransaction, or inTxService.SubscribeTxStatusfailure events.
Generated unary service calls need an explicit retry policy. Managed SDK
components retry selected failures: authentication can refresh a session, streams
can reconnect, and QuotingCore can resend the same signed transaction.
See Rust SDK. Do not build a replacement transaction merely
because a submission timed out.
gRPC status codes
| gRPC code | When | Retry? |
|---|---|---|
INVALID_ARGUMENT | A required id is missing (trader_id is required, perp_id is required), a list is too long (at most 100 ids, book depth ≤ 100), a bad page token or time range, an unsupported candle interval, a malformed or under-signed transaction. | No — fix the request. |
NOT_FOUND | Unknown trader account, perp, or collateral market. | No. |
UNAUTHENTICATED | Missing, malformed, expired, or revoked session token. On Authenticate: no outstanding nonce, a bad signature, or a key that controls no trader account. | Re-run the auth handshake once, then retry. |
PERMISSION_DENIED | Valid session but wrong scope: the requested trader_id isn't controlled by the key, the session key isn't a required signer of a SubmitTx transaction, an organization without perps access, a trader with no organization calling GetSdkCredentials or PageTheTeam, or a RenameAccount signer that doesn't control the account. | No. |
RESOURCE_EXHAUSTED | Rate limit hit, the server is at capacity, a simulation pool is busy, the transaction queue is full, or the sponsor refused for capacity. Also ends SubscribeAccountsAtRisk when the client falls behind. | Yes, with exponential backoff. |
FAILED_PRECONDITION | A user or maker history RPC (GetActivity, GetAccountHistory, GetInterestHistory, GetFills, ListRecentQuotes, …) on a deployment without the history archive, or a risk view whose backing feature isn't enabled. | No — the feature isn't available on this deployment. |
UNAVAILABLE | Transport drop; the node is restarting; margin state hasn't loaded or is stale; sponsorship or simulation isn't configured; an upstream read failed. | Yes, with backoff. |
DATA_LOSS | A live stream (SubscribeActivity, SubscribeFills, SubscribeCandles) fell behind the server's buffer and was closed. | Reconnect, then reconcile with the matching unary RPC. |
DEADLINE_EXCEEDED | Your deadline, or the server's own request deadline, elapsed. | Yes, for idempotent reads. |
UNIMPLEMENTED | The RPC is declared in the schema but not served yet (see the API overview), or an unsupported option such as mark/index candles. | No. |
INTERNAL | A server bug. | Capped retry (1–3 attempts), then report it. |
The message string is for humans and may change; branch on the code.
Session-specific failures
Margin maker sessions last 6 hours and challenge nonces 30 seconds.
AuthFlow in both SDKs refreshes ahead of expiry and runs
Challenge → sign → Authenticate from scratch, so nonce expiry is not
your concern. Sessions are not guaranteed to survive a server restart
or a move to another node, though: a previously good token can start
returning UNAUTHENTICATED. Treat that as a cue to re-authenticate
once with auth.refresh() — on the AuthFlow in either SDK. The
Rust maker Client's managed streams and session heartbeat already
do this on UNAUTHENTICATED.
Reading errors
use tonic::Code;
match account.get_account(req).await {
Ok(res) => { /* ... */ }
Err(status) => match status.code() {
Code::ResourceExhausted | Code::Unavailable => backoff_and_retry().await,
Code::NotFound => { /* no such trader account */ }
_ => return Err(status.into()),
},
}import { GrpcCode, statusCode } from "@superis-labs/flint-margin-user-client";
try {
await account.getAccount({ traderId: { id: 42n } });
} catch (err) {
switch (statusCode(err)) {
case GrpcCode.ResourceExhausted:
case GrpcCode.Unavailable:
await sleep(backoff);
break;
case GrpcCode.NotFound:
// no such trader account
break;
default:
throw err;
}
}In Rust, generated service clients return tonic::Status; the SDK's
own helpers (Client::connect, AuthFlow) return
flint_client_core::CoreError, re-exported as each crate's Error.
In TypeScript, every failure is a GrpcError with a numeric code;
asGrpcError, statusCode, statusMessage, and responseMetadata
narrow an unknown error safely.
Transaction outcomes
TxService.SubmitTx returns once the server has accepted the
transaction — not when it lands. Follow it on SubscribeTxStatus:
| Event | Meaning |
|---|---|
ack | Accepted for submission. Not confirmation. |
landed | Observed at processed commitment with no error. |
failed | Reverted after landing, or timed out before landing. reason carries the cause, including program errors. |
Save the signature before dispatch. Require RPC status at confirmed or finalized
commitment with err: null before reporting success. If the outcome is unknown,
follow transaction recovery.
SponsorshipService.SponsorTransaction waits for confirmation and
returns the signature; a transaction the sponsor won't sign or that
fails comes back as INVALID_ARGUMENT with the reason.
Program error codes
The margin program's custom errors, as numbered in the IDL (decimal / hex). The program also logs the failing source location and, for some codes, the subject (trader, asset, or perp id) — read the transaction logs for the detail the code alone can't carry.
Both on-chain clients ship the enum: FlintError in
flint_margin_onchain::errors (Rust, FromPrimitive), and
FLINT_ERROR__* constants with isFlintError(...) /
getFlintErrorMessage(...) in @superis-labs/flint-idl.
General
| Code | Name | Meaning |
|---|---|---|
1 / 0x1 | MathError | Arithmetic overflow, underflow, or division by zero inside the program. Usually an extreme amount. |
2 / 0x2 | Generic | A rejection whose reason is only in the program log. Read the logs. |
8 / 0x8 | InvalidClock | The program couldn't read a usable clock. |
15 / 0xF | OperationDisabled | The operation is paused for this market or globally. The log names the operation. |
23 / 0x17 | UnsupportedMintExtension | The mint uses a Token-2022 extension outside the allowlist. |
62 / 0x3E | PositionDoesntExist | A balance move on an asset the account holds no position in. |
Margin and collateral
| Code | Name | Meaning |
|---|---|---|
3 / 0x3 | HealthCheckFailed | An account touched by the instruction would fail its margin requirement afterwards. For a perp order this can be the taker or one of the makers; the log names the trader id. |
6 / 0x6 | UtilisationThrottled | The pool's utilisation is at or above the throttle for this borrow or redeem. |
7 / 0x7 | UtilisationOverflow | The action would leave the pool with borrows ≥ deposits. |
11 / 0xB | RedeemBelowMinimum | The redeem returned less than the caller's minimum. |
12 / 0xC | DepositLimitExceeded | The market's deposit cap would be exceeded. |
13 / 0xD | BorrowLimitExceeded | The market's borrow cap would be exceeded. |
14 / 0xE | FlowCapExceeded | The market's net-flow limiter would be exceeded for this window. |
28 / 0x1C | SpotPositionLimitExceeded | The account would hold more collateral positions than allowed. |
48 / 0x30 | MarketBankrupt | The market's borrow/lend pool is bankrupt (deposit price is zero). |
63 / 0x3F | InsufficientIsolatedDeposit | A withdraw or lend larger than the position's idle (unlent) balance. |
64 / 0x40 | InsufficientDepositToRepay | A repay larger than the position's idle balance plus its own lent deposit. |
78 / 0x4E | SpotProductNotCached | The asset isn't in the program's product cache yet; a keeper sync must list it first. |
87 / 0x57 | AccountLeverageExceeded | Initial margin clears, but the account's own leverage cap (setAccountLeverageCap) does not. |
GetActionLimits and SimulateAction predict most of these before you
sign — the limit's constraint names which one binds.
Perp orders
| Code | Name | Meaning |
|---|---|---|
50 / 0x32 | PerpOrderLotsExceedPositionWidth | The order's lot count is too large for a position (it must fit in a signed 32-bit integer). |
51 / 0x33 | PerpOrderAverageBeyondLimit | The average-price bound is worse than the single-fill limit: above it for a buy, or below it for a sell. |
52 / 0x34 | PerpOrderFilledBelowMinimum | The order filled less than its min_fill_lots. |
53 / 0x35 | PerpOrderUnfilled | An immediate-or-cancel order crossed nothing inside its limit. |
54 / 0x36 | LinearPerpPositionLimitExceeded | The account would hold more perp positions than allowed. |
55 / 0x37 | LinearPerpOpenInterestCapExceeded | The fill would push the contract past its open-interest cap. |
56 / 0x38 | LinearPerpMarkExpired | The contract's cached mark is past its validity window; retry after the next cache sync. |
57 / 0x39 | PerpOrderNothingToReduce | A reduce-only order on a perp the taker is flat on, or already holds in the order's direction. |
58 / 0x3A | PerpOrderReducibleBelowMinimum | A reduce-only order whose position is smaller than its minimum fill. |
76 / 0x4C | PerpOrderSelfMatch | The taker passed itself as a maker. |
77 / 0x4D | PerpOrderMakersUnordered | Maker accounts must be passed strictly ascending by trader id, without repeats. |
79 / 0x4F | LinearPerpNotCached | The contract isn't in the program's perp cache yet. |
88 / 0x58 | PerpOrderWouldCross | A post-only order would cross the makers passed in. |
89 / 0x59 | PerpOrderNotFound | No resting order of the trader's at that side and price. |
90 / 0x5A | PerpOpenOrderLimitExceeded | The trader is at the ceiling on resting orders on this contract. |
91 / 0x5B | LinearPerpPositionSizeExceeded | The position would pass its size cap, counting resting orders as filled. |
92 / 0x5C | PerpOrderExpired | A Gtd expiry, or a modified resting-order expiry, is at or before the current slot. |
94 / 0x5E | PerpOrderMinimumUnreachable | The Ioc kind's min_fill_lots exceeds the order's lots. |
OrderService.PreviewOrder simulates an order against the current
book and margin before you sign it.
Order-book upkeep
The permissionless prunePerpOrders and forceCancelPerpOrders
instructions, usually run by keepers.
| Code | Name | Meaning |
|---|---|---|
93 / 0x5D | PerpNothingToPrune | No dead order among the traders passed to prunePerpOrders. |
95 / 0x5F | PerpForceCancelMarginMet | forceCancelPerpOrders on a trader that meets initial margin. |
96 / 0x60 | PerpForceCancelNothingReserved | forceCancelPerpOrders on a book whose orders reserve no margin. |
PnL and funding settlement
| Code | Name | Meaning |
|---|---|---|
59 / 0x3B | NoRealisedPnlToSettle | Nothing is pending on the position. |
60 / 0x3C | PnlSettlePoolExhausted | A gain to settle, but the pool holds less than one tick. |
61 / 0x3D | LinearPerpPnlCurrencyMismatch | The market passed isn't the contract's PnL currency. |
75 / 0x4B | SettlementExceedsPendingTicks | The settlement exceeds, or points the other way from, what's pending. |
80 / 0x50 | SettlementBelowMinimum | Less is settleable than the caller's min_settle_ticks. |
Liquidation, backstop, and loss socialization
Mostly relevant to keepers.
| Code | Name | Meaning |
|---|---|---|
4 / 0x4 | HealthyAccountCantBeLiquidated | The account meets maintenance margin. |
5 / 0x5 | LiquidationDidNotDecreaseDeficit | The liquidation wouldn't improve the account's margin deficit. |
16 / 0x10 | LiquidationCapLeavesDustBorrow | The capped repay would leave a dust-sized borrow. |
17 / 0x11 | SelfPositionTransfer | Liquidator and liquidatee are the same account. |
18 / 0x12 | AccountNotDistressed | Loss socialization on an account that isn't distressed. |
65 / 0x41 | NoDebtToSocialize | The account has no debt in that asset. |
66 / 0x42 | SocializedAccountHoldsCollateral | The account still holds collateral above the dust line. |
67 / 0x43 | LiquidationPairMismatch | The debt or collateral asset passed differs from the plan. |
68 / 0x44 | LiquidationTransferredNothing | The liquidation would move nothing. |
69 / 0x45 | LiquidationExchangeUnfair | The liquidatee would give up more value than the bonus allows. |
70 / 0x46 | LiquidationCollateralBelowMinimum | Collateral received is below the liquidator's min_collateral_received. |
71 / 0x47 | LiquidationDebtAboveMaximum | Debt taken on exceeds the liquidator's max_borrow_transfer. |
72 / 0x48 | BackstopGivesDebt | The backstop would leave the liquidatee owing more. |
73 / 0x49 | BackstopAssetNotHeld | The backstop names an asset the liquidatee holds no position in. |
74 / 0x4A | BackstopExchangeUnfair | The backstopper takes more value than it gives at fair prices. |
82 / 0x52 | PerpLiquidationNothingToClose | The liquidatee has no position on the contract. |
83 / 0x53 | PerpLiquidationMakersIncomplete | An enabled quoting slot wasn't passed as a remaining account. |
84 / 0x54 | BackstopPerpNotHeld | The backstop names a perp the liquidatee doesn't hold. |
85 / 0x55 | BackstopTakeNothing | A backstop with a zero percent. |
86 / 0x56 | BackstopLeftoverBelowMinimum | A partial backstop would leave the liquidatee under $500 of equity. |
Delegates and passkeys
| Code | Name | Meaning |
|---|---|---|
29 / 0x1D | InvalidDelegateScope | An empty delegate grant (reserved to mean revoked). |
30 / 0x1E | Secp256r1InstructionMalformed | The secp256r1 verification instruction is malformed. |
31 / 0x1F | PasskeyNotTopLevel | A passkey-authorized instruction must be top level, not a CPI. |
32 / 0x20 | PasskeyPrecompileMissing | The secp256r1 verification instruction must immediately precede the order. |
33 / 0x21 | PasskeyIntrospectionFailed | The program couldn't read the instructions sysvar. |
34 / 0x22 | PasskeyTooManyAccounts | The passkey intent binds more accounts than allowed. |
35 / 0x23 | PasskeyCredentialMismatch | The assertion's credential isn't the delegate's. |
36 / 0x24 | PasskeyRpIdMismatch | The relying-party id doesn't match the registered one. |
37 / 0x25 | PasskeyUserNotVerified | The authenticator didn't report user verification. |
38 / 0x26 | PasskeyClientDataMismatch | clientDataJSON doesn't hash to what the authenticator signed, or doesn't carry the expected type, challenge, or origin. |
39 / 0x27 | PasskeyIntentExpired | The signed intent's validity window has passed. |
40 / 0x28 | PasskeyIntentLifetimeTooLong | The signed intent's validity window is too long. |
41 / 0x29 | PasskeyNonceMismatch | The intent's nonce isn't the delegate's next nonce. |
42 / 0x2A | PasskeyNotAuthorized | The delegate isn't a passkey, or its TRADE grant is revoked or expired. |
43 / 0x2B | PasskeyCredentialInvalid | The passkey credential is malformed (credential id or origin length out of bounds). |
44 / 0x2C | UnknownDelegateCredential | The delegate credential's tag names no known kind. |
45 / 0x2D | PasskeyDomainInvalid | The configured challenge domain is empty or not printable ASCII. |
Admin, keeper, and protocol operations
You won't see these from trading or quoting flows.
| Code | Name | Meaning |
|---|---|---|
9 / 0x9 | FlashloanDisabled | Flash loans are disabled. |
10 / 0xA | IllegalFlashloan | The flash-loan start/end pair is malformed. |
19 / 0x13 | InvalidFeeDestination | Fee collection to an account other than the configured destination. |
20 / 0x14 | NothingToCollect | No fees to collect. |
21 / 0x15 | UpdateNotYetEffective | A proposed market update applied before its delay elapsed. |
22 / 0x16 | UpdateExpired | A proposed market update applied after it expired. |
24 / 0x18 | VaultAlreadyInitialized | The market vault already exists. |
25 / 0x19 | SharePoolNotEmpty | A share pool reset while it still holds shares. |
26 / 0x1A | LendingRateFeeTooHigh | The lending-rate fee is out of range. |
27 / 0x1B | TargetUtilisationOutOfRange | The target utilisation is out of range. |
46 / 0x2E | OracleIngestConfiguration | Invalid oracle ingest configuration. |
47 / 0x2F | OracleTierConfiguration | Invalid oracle tier configuration. |
49 / 0x31 | OracleRevisionAlreadyPublished | That oracle revision was already published. |
81 / 0x51 | FeeCollectionBelowMinimum | Less pool-backed fee accrual than the caller's min_collect_ticks. |