Somnia Markets / Reference
Errors
This page lists the error classes the SDK throws, the fields each carries, and the contract error names the SDK decodes into ContractRevertError. For the decision logic that goes with them, see Handle errors and reverts.
Every SDK error extends SomniaMarketsError, sets name to its class name, prefixes its message with @somnia-chain/markets-sdk: , and carries the underlying failure in cause when it wraps one.
Classes
All classes are exported from the root entry.
FillHistoryRestartRequiredError is returned by getUserFillsPage when a continuation belongs to a replaced serving dataset or is a legacy cursor without a generation. Its readonly cursorGeneration and servingGeneration fields identify the mismatch; legacy cursors report "legacy" as cursorGeneration. Discard accumulated pages and restart from page one; retrying the old cursor cannot succeed. getUserFillHistory makes at most two attempts: its first read or refresh, then one full read when the first cannot stand. It throws this error when the dataset changes during that full read. Call it again.
FillHistoryUnavailableError means the serving indexer has not initialized FillDataset/current. Deploy and reindex a compatible producer. Retrying the same deployment cannot make the contract appear.
PerpPositionChangesUnavailableError means the serving indexer predates the PerpPositionChange table, so listPerpPositionChangesForFills has no position updates to pair fills with. Show each fill's own side until the indexer is reindexed by a compatible producer. Retrying the same deployment cannot make the table appear.
| Class | Fields | Meaning | Retrying the same call |
|---|---|---|---|
SomniaMarketsError |
none | Base class. | depends on the subclass |
InvalidInputError |
chainIds?: readonly number[] |
The call cannot proceed: unknown symbol or reference, unknown timeframe, a method used on the wrong market kind, an amount below one lot, a limit order without a price, a market order against an empty opposite side, a probability outside [0, 1], or a non-finite amount. Most cases are local validation. The empty-book market-order case follows a chain book read and depends on current liquidity. chainIds is set when a call names no chain on a multi-chain exchange (exchange.client, exchange.trader) and when createReactivity gets a wallet on another chain. |
fails again, except an empty-book market order may succeed after liquidity appears |
NotConfiguredError |
what: string |
The client lacks a URL or address the feature needs: indexerUrl, wsRpcUrl, priceFeed, or an entry of addresses. Thrown at the first call that needs it, never at construction — a client built without an indexer still serves every chain-backed capability (smart accounts, executors, on-chain reads) and refuses indexer reads and live watches by feature. |
fails again |
NotSupportedError |
feature: string |
The call is valid, but this SDK version does not support it yet, for example the "spot" and "overall" scopes of getAccountEquity. Thrown before any network request. |
fails again |
SignerRequiredError |
operation: string |
An authenticated method was called without a privateKey, account, walletClient, or executor. Authenticated methods include fetchBalance, fetchOpenOrders, fetchOrders, fetchMyTrades, fetchPositions, watchOrders, watchMyTrades, exchange.trader, and every write. |
fails again |
CollateralOnWrongChainError |
chainId, code, needed, held, shortfall, elsewhere, plan? |
The order's collateral sits on another configured chain | Bridge the shortfall, then retry |
BridgeLiquidityError |
token, from, to, router, amount, available |
A native bridge route (STT) pays out of the destination router's own balance, and that balance is below the amount. exchange.bridge.transfer, exchange.ensureFunds, and createOrder(…, { funding: "auto" }) read it first and send nothing. |
Retry after seeding, bridge at most available, or fund another way |
BridgeDeliveryTimeoutError |
messageId, bridgeHash, from, to, token, amount |
exchange.ensureFunds or createOrder(…, { funding: "auto" }) sent the bridge leg, but the destination Mailbox did not process its message within the delivery timeout. The funds are in flight. exchange.bridge.transfer resolves with delivered: false and the same messageId instead of throwing. |
Call delivered(messageId) on the destination Mailbox before you bridge again |
IndexerError |
operation: string |
An indexer request did not complete: endpoint down, bad URL, HTTP error, GraphQL error, or the 30-second request timeout. Never means "no such row". | may succeed |
IncompleteHistoryError |
operation, reason, pagesRead, maxFillPages? |
Portfolio analytics could not traverse all indexed fills: the caller's page budget ended or the cursor cycled. No partial PnL is returned. | Increase or omit the budget; investigate a cursor cycle. |
IncompletePortfolioError |
account, limit, nextOffset |
getClaimable observed more nonzero positions than its complete-read budget. No incomplete claim array is returned. |
Use getClaimablePage and follow its raw-position nextOffset. |
IndexerUnavailableError |
reason: IndexerRefusalReason, retryAfterMs: number, consecutiveFailures: number |
The SDK refused the read locally. reason says which gate refused: breaker-open (repeated endpoint failures — timeouts or HTTP 429/502/503/504 — opened the breaker), breaker-trial (the cooling period ended and another read is the trial), queue-full (this client already has maxQueued reads waiting), or live-reset (stopLive() reset live reads while it queued or backed off). For the first three, nothing reached the network; if a gate refuses a retry after an earlier attempt was sent, the read fails with that attempt's IndexerError instead. retryAfterMs is meaningful only for breaker-open and is 0 for the rest. A subclass of IndexerError, so existing instanceof IndexerError handling still catches it. |
Branch on reason: sleep retryAfterMs for breaker-open, retry shortly for breaker-trial, back off on your own schedule for queue-full, and retry live-reset on the same client |
AccountViewError |
reason: AccountViewErrorReason, chainId: number, operation: string |
An account-view read (client.accountOrders, client.accountOutcomePositions) got an answer the SDK cannot accept as one complete account. invalidResponse: the data breaks the account-view contract. scopeMismatch: the data belongs to another account, chain or dataset. snapshotUnstable: the account changed during every read attempt. It never means an empty account. A subclass of IndexerError, so existing instanceof IndexerError handling still catches it. |
snapshotUnstable can succeed at once. The other two succeed only after the indexer serves correct data |
FillHistoryUnavailableError |
operation: string |
The serving indexer has no initialized fill-dataset generation. | requires a compatible producer deployment and reindex |
PerpPositionChangesUnavailableError |
operation: string |
The serving indexer predates the PerpPositionChange table, so fills cannot be paired with their position updates. |
fails the same way until a compatible producer reindexes the indexer |
FillHistoryRestartRequiredError |
cursorGeneration: string, servingGeneration: string |
A fill-page cursor belongs to a different serving generation or predates generation binding. | requires a fresh first page without the old cursor |
AccountsIndexerUnavailableError |
operation: string, retryAfterMs: number | undefined, chainId?: number |
A smart-account indexed read (client.accounts.*, exchange.accounts.*) found the accounts indexer rate-limited, overloaded or unreachable. retryAfterMs is how long it asked to wait, when it said. A subclass of IndexerError. |
Wait retryAfterMs (or back off) and retry |
AccountFeedRestartRequiredError |
chainId: number |
A smart-account feed cursor (getActivityPage, getAccountsPage, getNetworkActivityPage, getFactoryHistoryPage) was issued before that chain's accounts index was rebuilt; continuing would skip or repeat rows. |
Discard the pages read; start the read again without a cursor |
IndexerWaitTimeoutError |
targetBlock: number, latestProcessedBlock: number | null, timeoutMs: number, waitedMs: number, polls: number |
The indexer did not reach the requested block before the wait deadline. The fields report the target, last observed processed block (or null), deadline, elapsed time and metadata polls; unlike IndexerError, the indexer answered but remained behind. |
Retry with a longer deadline or handle the lag before reading. |
RpcError |
operation: string |
A JSON-RPC or WebSocket request did not complete: connection refused, 4-second request timeout, dropped subscription, unsupported method. From a write, it can also mean that the node refused the transaction (nonce too low, insufficient funds). Only the local-signer and session-key executors guarantee that nothing was sent otherwise; see Handle errors. | a read may succeed; a write may succeed after you fix the refusal |
TransactionOutcomeUnknownError |
chainId: number, operation: string, hash? |
A write signed a transaction that may have reached the node, and the SDK did not observe its receipt: the sync send timed out or its socket dropped, the receipt wait failed, or a session send lost its answer. The transaction can still mine. hash is absent only for a session send. A subclass of RpcError, so existing instanceof RpcError handling still catches it. |
do not retry blindly: look up hash (or read the state the write changes) first, because a retry can execute the write twice |
ContractRevertError |
errorName?, args?, reason?, data?, address?, functionName? |
The chain rejected the call. Thrown on send-time rejection, pre-send simulation, a mined receipt with failed status, and eth_call reads. |
fails again unless state changes |
InvariantError |
none | The SDK contradicted an invariant that its own control flow had established. It indicates an SDK defect, not invalid caller input. | not applicable; report the SDK defect |
Errors from code the SDK calls but does not own can surface unchanged. This includes a walletClient supplied by the app and the client returned by getViemClient(). Debug sink failures are different: the SDK contains them so diagnostic code cannot break an operation.
ContractRevertError fields
| Field | Type | Present when |
|---|---|---|
errorName |
string |
The revert data matched a custom error in contractErrorsAbi. |
args |
readonly unknown[] |
errorName is set. Positional decoded arguments. |
reason |
string |
The revert carried a require or revert string. |
data |
string |
The node returned revert data. |
address |
string |
The reverting contract is known. |
functionName |
string |
The called function is known. |
A mined transaction with status: "reverted" carries no revert data. The SDK replays the call as eth_call at the receipt's block to recover errorName or reason. When the replay yields nothing, the error is still a ContractRevertError and its message ends with (no revert data recoverable).
decodeRevert(caught) is exported for callers who send transactions themselves. It walks the cause chain, decodes the first revert data it finds against contractErrorsAbi, and always returns a ContractRevertError. It does not check that the input is a revert: a transport error passed to it returns a ContractRevertError without errorName.
Absence versus failure
| Outcome | Indexer point read (get*) |
Indexer list read (list*) |
Chain read | Live-store read |
|---|---|---|---|---|
| Row exists | the row | the rows | the value | the value |
| No such row | null |
[] |
throws ContractRevertError or returns the contract's empty value |
[], null, or an empty book |
| Request failed | throws IndexerError |
throws IndexerError |
throws RpcError or ContractRevertError |
not applicable |
Contract error names
contractErrorsAbi, exported from the root entry, is the generated ABI of every custom error the protocol contracts declare. ContractRevertError.errorName is one of its names. The names the exchange API and the trader raise most often:
| Area | Error names |
|---|---|
| Order validation | InvalidPrice, InvalidQuantity, PriceNotAlignedToTickSize, QuantityNotAlignedToLotSize, QuantityBelowMinimum, PriceOutOfBounds, PriceTooLarge, TooManyRestingOrders |
| Market state | TradingNotActive, UseBinaryPlacement, MarketRestricted, MarketNotSettled, NotFinalized, StaleMarketId |
| Order lifecycle | OrderDoesNotExist, IncorrectSender (a cancel of an order the caller does not own, including an order already cancelled), OrderAlreadyExpired, ExpiredOrderMustBeCancelled, NotExpired, OrderExpiryBeyondMarket |
| Time in force | PostOnlyWouldCross, FillOrKillNotFillable, ImmediateOrCancelNoFill, SelfMatchCancelTaker |
| Amend and batch | AmendOldOrderGone, AmendReplacementRejected, EmptyOrderBatch, BatchTooLarge, LengthMismatch |
| Funds | InsufficientBalance, InsufficientVaultBalance, ExceedsWithdrawableBalance, ERC20InsufficientAllowance, ERC20InsufficientBalance, InvalidMsgValue, NativeAmountMismatch |
| Builder fees | BuilderNotApproved, BuilderFeeExceedsApproval, BuilderFeeExceedsCap, InvalidBuilder |
| Binary settlement | NothingToClaim, AlreadyClaimed, NothingOwed, AlreadyFinalized, OracleNotAnswered, QuestionNotFinal |
| Perps | NoOpenPosition, InsufficientMargin, InsufficientMarginForOrder, InsufficientMarginAfterWithdrawal, MaxPositionSizeExceeded, OpenInterestCapExceeded, InvalidLeverage, MarkPriceUnavailable, OraclePriceStale, NotLiquidatable, PerpPoolNotRegistered |
| Stop orders | InvalidTriggerPrice, LimitPriceIncompatibleWithTrigger, InsufficientSomiPayment, TriggerTooCloseToEma, RefundFailed |
| Operators and venues | OperatorDisabled, OperatorNotActive, UnknownOperator, UnknownVenue, VenuePolicyDenied, InsufficientPermission, Unauthorized, NotOwner |
| Test collateral | FaucetCapExceeded |
The full list is longer and changes with the contracts. Read it from contractErrorsAbi in the installed version.