Errors
Every failure is a named program error. Translate it into a sentence that tells the user what to do next.
Handling
Never hardcode a numeric error code. Anchor assigns them by declaration order, so adding one variant shifts everything after it, and that has already happened twice. Match on the name through parseOtcError(), which returns { code, name, message } or null when the failure was not ours.
import { parseOtcError } from '@otcdex/sdk';
try {
await sendAndConfirmTransaction(connection, tx, [wallet]);
} catch (err) {
// Never show the raw simulation log to a user.
const otc = parseOtcError(err);
toast.error(otc ? otc.message : 'Transaction failed');
// Worth re-quoting automatically on a moved price.
if (otc?.name === 'PriceBoundExceeded' || otc?.name === 'ProceedsBelowMinimum') refresh();
}Price bound errors are not bugs. They are the protection working: the order was re-priced between your quote and your transaction landing, and the program refused to execute at a worse price than you signed for. Re-quote and resubmit.
Reference
| Error | Code | Show the user |
|---|---|---|
| ClampedFillTooLarge | 6065 | Too large while the price is catching up to the market. Try a smaller amount. |
| PoolReservesEmpty | 6061 | This token's pool has no liquidity right now. |
| PoolPriceUnusable | 6062 | This token's pool price cannot be read right now. |
| PoolProgramNotAllowed | 6058 | This token's pool is not one we can price against. |
| PoolLayoutUnexpected | 6059 | This token's pool changed shape. Pricing is paused for it. |
| PoolVaultMismatch | 6060 | Wrong pool accounts for this order. Refresh and try again. |
| PoolMismatch | 6063 | This order prices against a different pool. Refresh and try again. |
| PriceUnchanged | 6067 | The price is already up to date. |
| NotOracleTracked | 6064 | This order has a fixed price, so there is nothing to re-anchor. |
| PricingKindImmutable | 6066 | An order cannot switch between fixed and market pricing. |
| TrackedOrderNotMatchable | 6068 | Market-priced orders are filled directly, not matched. |
| ZeroProceeds | 6069 | Too small : the fee would take all of it. Try a larger amount. |
| FloorRequired | 6070 | Set a floor price. Market-tracked orders will not fill below it. |
| FloorTooWide | 6071 | That floor is too far below the market. Raise it to within 25%. |
| CeilingRequired | 6072 | Set a ceiling price. Market-tracked bids will not pay above it. |
| CeilingTooWide | 6073 | That ceiling is too far above the market. Lower it to within 25%. |
| PriceBoundExceeded | 6037 | Price moved. Refresh and try again. |
| ProceedsBelowMinimum | 6020 | The bid dropped below your minimum. Refresh and try again. |
| FillBelowMinimum | 6030 | Below this order's minimum size. |
| FillExceedsRemaining | 6032 | Not enough left : someone bought first. |
| FillExceedsRemainingQuote | 6019 | Not enough left in this bid : someone sold first. |
| ListingExpired | 6011 | This order has expired. |
| ListingNotActive | 6010 | This order is no longer active. |
| BidNotActive | 6018 | This bid is no longer active. |
| BuyerNotAllowed | 6015 | This is a private listing for another wallet. |
| SellerNotAllowed | 6016 | This is a private bid for another wallet. |
| ProtocolPaused | 6007 | Trading is paused. |
| QuoteMintNotAllowed | 6039 | This payment asset is not accepted. |
| SelfFill | 6035 | You cannot fill your own listing. |
| SelfFillBid | 6021 | You cannot fill your own bid. |
| ZeroTokensReceived | 6023 | That amount is too small : the fee would consume all of it. |
| NotionalCapExceeded | 6033 | Order size is above the current cap. |
| UnsupportedMintExtension | 6045 | This token uses a feature the venue does not support. |
| QuoteMintHasTransferFee | 6047 | Payment assets with a transfer fee are not supported. |
Anything unrecognised should fall back to a generic message plus the transaction signature. Do not print a simulation log into the interface: it is unreadable to a user and often leaks account addresses they did not ask about. Keep it in console.debug.