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 is an instance of one of seven classes. Test with instanceof, most specific first, and keep a final throw for errors the SDK does not own.
import {
ContractRevertError,
IndexerError,
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 SomniaMarketsError) {
// NotConfiguredError and anything else the SDK raised
} else {
throw e; // not ours: a wallet or an application bug
}
}
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. 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 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.