How to handle errors and reverts
This guide shows you how to branch on the errors the SDK throws so a bot or a UI reacts correctly to a bad input, an outage, and a rejected transaction. It assumes you write try/catch in TypeScript. The complete list of classes and contract error names is in Errors.
Branch on the class, not the message
Every SDK error has a concrete class. Test with instanceof, most specific first, and keep a final throw for errors the SDK does not own. InvariantError is the class for an SDK defect; report it and rethrow it instead of using it for control flow.
import {
ContractRevertError,
IndexerError,
IndexerWaitTimeoutError,
InvariantError,
InvalidInputError,
RpcError,
SignerRequiredError,
SomniaMarketsError,
} from "@somnia-chain/markets-sdk";
try {
await exchange.createOrder("SOMI/USDso", "limit", "buy", 10, 0.11);
} catch (e) {
if (e instanceof ContractRevertError) {
// the chain rejected it: read e.errorName
} else if (e instanceof InvalidInputError) {
// the call is wrong: fix the arguments, do not retry
} else if (e instanceof SignerRequiredError) {
// configure a privateKey, account, or walletClient
} else if (e instanceof RpcError || e instanceof IndexerError) {
// the request did not complete: retry with backoff, or degrade
} else if (e instanceof InvariantError) {
console.error(e); // report the SDK defect
throw e;
} else if (e instanceof SomniaMarketsError) {
// NotConfiguredError and anything else the SDK raised
} else {
throw e; // not ours: a wallet or an application bug
}
}
IndexerWaitTimeoutError comes from the indexer wait, not from the write. Wrap
waitForIndexedBlock when you need to branch on that deadline:
const targetBlock = 1_234_567n; // the confirmed transaction's block
try {
await exchange.client.waitForIndexedBlock(exchange.client.config.chain.id, targetBlock);
} catch (e) {
if (e instanceof IndexerWaitTimeoutError) {
// the indexer answered but is still behind e.targetBlock
} else if (e instanceof IndexerError) {
// the metadata read did not complete: retry with backoff, or degrade
} else {
throw e;
}
}
Message text is not stable. errorName, operation, and what are.
Decide from a revert
ContractRevertError.errorName is the contract's own error name when the revert data matched a known error. Branch on it with a fallback arm; errorName is undefined for a bare require string or unknown data.
import { ContractRevertError } from "@somnia-chain/markets-sdk";
try {
await exchange.trader.placeOrder(params);
} catch (e) {
if (!(e instanceof ContractRevertError)) throw e;
switch (e.errorName) {
case "ExpiredOrderMustBeCancelled": {
// An expired maker sits in the way. Sweep the expired orders, then retry.
const expired = await exchange.client.listSweepableOrders({ pool: params.pool });
await exchange.trader.cancelExpiredOrders({ pool: params.pool, orderIds: expired.map((o) => o.orderId) });
break;
}
case "InsufficientBalance":
case "ERC20InsufficientBalance":
// top up, then retry
break;
case "PostOnlyWouldCross":
// re-quote one tick away
break;
case undefined:
console.error("unrecognised revert", e.reason ?? e.data);
break;
default:
throw e;
}
}
e.args holds the decoded arguments in order. IncorrectSender(sender, expected), for example, is what a cancel of an already-cancelled order returns; expected is the zero address because the order no longer exists.
Retry only what can succeed
Retry RpcError and IndexerError with backoff; the request never reached a conclusion. An IndexerWaitTimeoutError means the indexer answered but did not reach targetBlock before the deadline: wait longer, or read the required state from chain. Retry a ContractRevertError only after the state that caused it changes; the same call reverts the same way. Never retry InvalidInputError, NotConfiguredError, or SignerRequiredError; fix the code or the configuration. A CollateralOnWrongChainError means the collateral is on another configured chain: bridge the shortfall — the error carries a plan when a route exists — and retry once it lands.
A write that throws RpcError after the transaction was sent may still have landed. Before re-sending, read your open orders at chain head with exchange.client.getOwnOpenOrdersOnchain(pool, owner); see Detect when an order fills.
Degrade an indexer read
An indexer failure is never "no rows". Catch IndexerError deliberately when a page should render with stale or empty data rather than fail.
import { IndexerError } from "@somnia-chain/markets-sdk";
const trades = await exchange
.fetchTrades("SOMI/USDso", undefined, 50)
.catch((e) => (e instanceof IndexerError ? [] : Promise.reject(e)));
Do not apply the same pattern to a chain read: a chain read throws only on real failure, and an empty fallback would hide it.
Tell cancellation apart from failure
When you pass signal in the configuration and abort it, in-flight indexer reads re-throw your abort reason, not an IndexerError. Check e.name === "AbortError" before the SDK classes.
Decode a revert you sent yourself
If you build a transaction with the exported ABIs and send it through your own client, decodeRevert(caught) turns the failure into a ContractRevertError with the same errorName decoding. It always returns an error and never throws. Call it only on failures you know are reverts; a transport error passed to it comes back without errorName.
import { decodeRevert } from "@somnia-chain/markets-sdk";
try {
await wallet.writeContract(request);
} catch (caught) {
const revert = decodeRevert(caught);
console.error(revert.errorName ?? revert.reason);
}
See the failure in context
Pass a debug sink to see which span failed and with what arguments. See Debug what the SDK is doing.