Skip to main content

Errors

The DollarStore protocol uses custom errors for gas-efficient reverts with clear failure reasons.

Validation errors

ZeroAmount

error ZeroAmount();

The amount was 0, or it rounded down to zero normalized units. An asset with more than 6 decimals needs at least 10**(decimals - 6) native units to register.


ZeroAddress

error ZeroAddress();

An address parameter was the zero address.


DeadlineExpired

error DeadlineExpired(uint256 deadline, uint256 timestamp);

The deadline had already passed. Every state-changing user entrypoint takes one.


AssetNotListed

error AssetNotListed(address asset);

The asset is not listed. Check isAssetListed() before calling.


WrongPool

error WrongPool(address asset, uint16 poolId);

The asset does not belong to the pool you passed. An asset belongs to exactly one pool—read it with assetPoolId(). A hub asset is valid on a spoke deposit only when it is funding that spoke's DLRS side.


InvalidPool

error InvalidPool(uint16 poolId);

No pool exists with that id. Valid ids are 0 to poolCount() - 1.


Route errors

SameAsset

error SameAsset();

A swap was attempted with the same input and output asset.


InvalidRoute

error InvalidRoute(address offerAsset, address wantAsset);

The route is not supported on-chain. In practice this means spoke → spoke: route it as two legs through a hub asset instead.


TipNotEnabled

error TipNotEnabled();

A non-zero tip was passed to swap. Priority queues are a post-launch upgrade; pass 0.


Reserve and liquidity errors

InsufficientReserves

error InsufficientReserves(address asset, uint256 requested, uint256 available);

The pool's reserves cannot cover a withdrawal. Check getReserve() first, or withdraw a different asset that does have reserves.


InsufficientLiquidity

error InsufficientLiquidity(uint256 filled, uint256 requested);

swapExactInput could not fill the full amount instantly. This function never queues—it either fully executes or reverts.

For aggregators: check that getSwapQuote() covers the amount you need before calling. Note the quote can go stale within a block.


MinAmountNotMet

error MinAmountNotMet(uint256 filled, uint256 required);

The instantly-filled amount was below minAmountOut. Remember minAmountOut is in normalized 6dp units, not the output token's native decimals—in both swap and swapExactInput.


InsufficientReceiptShares

error InsufficientReceiptShares(uint256 requested, uint256 available);

The caller does not hold enough spoke receipt shares. Read the balance with getReceiptShares().


DepositTooSmall

error DepositTooSmall();

A spoke deposit was too small to mint a whole receipt share at the pool's current share price. Raised by the share math, not by the main interface. Deposit more, rather than donating the value to existing LPs.


FeeOnTransferNotSupported

error FeeOnTransferNotSupported(address asset);

The token delivered less than was pulled. Fee-on-transfer and other non-standard tokens are unsupported.


Queue errors

QueueFull

error QueueFull(address offerAsset, address wantAsset);

The directed queue has reached its maximum of 150 positions. Wait for positions to be filled, or call processQueue to push it along.


OrderTooSmall

error OrderTooSmall(uint256 provided, uint256 minimum);

The queued remainder is below the minimum order size for that queue's current depth. Check getMinimumOrderSize(). The minimum is waived only for the first position of an empty queue.


QueuePositionNotFound

error QueuePositionNotFound(uint256 positionId);

The position id does not exist—never created, or already filled or cancelled.


NotPositionOwner

error NotPositionOwner(uint256 positionId, address caller);

The caller tried to cancel a position they do not own.


Risk control errors

Inflows—deposits, swaps, and queue settlements—are gated on the offer asset being unpaused, on peg, and priced by a fresh oracle round. Exits are not.

DepositsPaused

error DepositsPaused(address asset);

The guardian has paused inflows of this asset. Withdrawals and cancellations still work.


PoolPaused

error PoolPaused(uint16 poolId);

The pool is paused. Also raised when a swap touches a paused spoke. Exits stay open.


Oracle errors

ErrorMeaning
NoPriceFeed(address asset)No feed configured for the asset
InvalidPrice(address asset)The oracle returned a non-positive price
StaleRound(address asset)The round was answered in an earlier round
PriceStale(address asset, uint256 updatedAt)The answer is older than maxStaleness()
PriceOutOfBounds(address asset, uint256 price, uint256 lower, uint256 upper)The price is outside the peg tolerance band

A depegged or stale asset can still be withdrawn—only inflows are blocked.


LaunchCapExceeded

error LaunchCapExceeded(uint16 poolId, uint256 attempted, uint256 cap);

The deposit would push the pool's exposure above its launch cap. Read the cap with getLaunchCap(); 0 means uncapped.


SpokeWindingDown

error SpokeWindingDown(uint16 poolId);

The spoke is winding down: new liquidity and risk-increasing spoke → hub swaps are blocked. LP exits, cancellations, and hub → spoke swaps stay live.


Governance errors

Raised by admin paths. Integrators do not hit these, but they are listed for completeness.

ErrorRaised when
OnlyGovernor() / OnlyGuardian() / OnlyUpgrader()Caller does not hold the role
OnlyPendingGovernor() / OnlyPendingGuardian() / OnlyPendingUpgrader()Only the nominee can accept a role transfer
AssetAlreadyListed(address asset)The asset is already listed in some pool
MaxPoolsReached()The poolId space is exhausted
PoolNotSpoke(uint16 poolId)A spoke-only operation was aimed at the hub
PoolNotEmpty(uint16 poolId)A spoke still holds reserves, shares, or queued depth and cannot be removed
InvalidTolerance() / InvalidStaleness()Risk parameter outside its allowed range
CapNotStricter()The guardian's new cap does not tighten the current one
ReservesNotDrifted(address asset)Nothing to sync—reserves match the balance
NoExcessTokens(address asset)Nothing unaccounted to rescue
EscrowImpaired(address asset) / AssetNotImpaired(address asset)Balance-impairment state does not match the path taken
NoEscrowToHaircut(address asset) / HaircutBudgetExceeded() / PoolNotPaused(uint16 poolId)Preconditions of the guardian escrow haircut
NotEnabled()The operation is not enabled in this version
UnsupportedDecimals(uint8 decimals)Asset decimals outside [6, 18], or a feed reporting more than 18

Token errors

NonTransferable

error NonTransferable();

DLRS is soulbound. Raised by transfer(), transferFrom() and approve() on the DLRS contract. Spoke receipt shares are likewise non-transferable, and are not an ERC-20 at all—they are tracked inside the protocol and read with getReceiptShares().


Handling errors

In Solidity

try dollarStore.swapExactInput(offerAsset, wantAsset, amount, minUnits, deadline)
returns (uint256 amountOut)
{
// Success
} catch (bytes memory reason) {
// Decode and handle specific errors
}

In viem

Custom errors are decoded automatically against the ABI:

import { ContractFunctionRevertedError } from 'viem'

try {
await publicClient.simulateContract({
address: dollarStore,
abi,
functionName: 'swapExactInput',
args: [offerAsset, wantAsset, amount, minUnits, deadline],
})
} catch (error) {
const revert = error.walk(e => e instanceof ContractFunctionRevertedError)
if (revert?.data?.errorName === 'InsufficientLiquidity') {
const [filled, requested] = revert.data.args
// Not enough instant liquidity — route elsewhere
}
}

Simulating before sending is the cheapest way to surface these: every validation above runs in the same call, so a successful simulation in the current block tells you the route, peg, pause and liquidity checks all pass.