Skip to main content

Capabilities Overview

The DollarStore protocol consists of two contracts:

ContractPurpose
DollarStoreMain protocol logic—pools, deposits/withdrawals, directed swaps, queues, risk controls. A UUPS proxy: integrate against the proxy address, never the implementation
DLRSNon-transferable 6-decimal receipt token, minted 1:1 when a hub stablecoin is deposited. Immutable, not upgradeable

Pools​

Liquidity is organized into pools, identified by a uint16 poolId.

PoolidContents
Hub0The core stablecoins. Depositing mints DLRS 1:1
Spoke>= 1One additional stablecoin each, paired against a DLRS-side reserve funded with hub assets. Depositing mints non-transferable receipt shares

An asset belongs to exactly one pool. The governor lists hub assets with addHubAsset and creates spokes with createSpoke; both freeze the token's decimals at listing time. Listed assets are never unlisted individually — only a fully-drained spoke pool can be removed.

Routes​

RouteSupported
hub → hubyes
hub → spokeyes
spoke → hubyes
spoke → spokenot on the protocol — reverts InvalidRoute. Composed as two legs through a hub asset, and a peripheral router is planned to do that in one call

Units​

All protocol accounting is in normalized 6-decimal units. An asset with more decimals is scaled by 10**(decimals - 6); supported decimals are [6, 18].

  • amount parameters are in the asset's native units.
  • minAmountOut, reserves, queue depths and minimum order sizes are in normalized 6dp units.
  • Sub-unit dust is never pulled from the caller; it stays in their wallet.

Read assetDecimals(asset) and assetScalingFactor(asset) to convert.

Supported stablecoins​

The hub launches with USDC and USDS. Every other stablecoin joins as its own spoke.

Examples throughout these docs follow that layout: a hub-to-hub swap is USDC ↔ USDS, and a hub-to-spoke swap is USDC ↔ USDT.

The set is governed and grows over time, so resolve it at runtime rather than hardcoding:

uint256 pools = dollarStore.poolCount();
address[] memory hubAssets = dollarStore.getPoolAssets(0);
bool listed = dollarStore.isAssetListed(token);
uint16 pool = dollarStore.assetPoolId(token); // 0 = hub, >= 1 = that spoke

assetPoolId is what tells you which route you are on, and therefore which rules apply.

The protocol is not deployed yet — see Addresses.

Integration paths​

For aggregators​

Use getSwapQuote to check instant fillability, then swapExactInput to execute:

// Instantly fillable amount, in native units of the output token (0 if the route is unsupported,
// or if a same-direction queue already owns the liquidity)
uint256 quote = dollarStore.getSwapQuote(USDC, USDS, amountIn);

if (quote >= expectedOut) {
// All-or-nothing: fills fully or reverts. Never queues. Pays msg.sender.
uint256 out = dollarStore.swapExactInput(
USDC, // offerAsset
USDS, // wantAsset
amountIn, // amount, native units of USDC
minUnits, // minAmountOut, normalized 6dp floor
block.timestamp + 300 // deadline
);
}

Output goes to msg.sender — a router receives the tokens itself and forwards them.

For direct users​

Use swap, which fills what reserves allow and queues the rest:

(uint256 filled, uint256 queued) = dollarStore.swap(
USDC, // offerAsset
USDS, // wantAsset
amount, // native units of USDC
0, // minAmountOut: 0 = accept any instant fill and queue the remainder
0, // tip: must be 0 (reserved)
block.timestamp + 300 // deadline
);

Set minAmountOut to the full normalized amount to demand an instant fill or revert.

For liquidity providers​

deposit into the hub mints DLRS 1:1; withdraw burns it for any hub asset with reserves.

uint256 dlrs = dollarStore.deposit(0, USDC, amount, deadline);
uint256 out = dollarStore.withdraw(0, USDS, units, deadline);

Depositing into a spoke — either its own asset or a hub asset funding its DLRS side — mints receipt shares instead. Use redeemSpoke to exit proportionally across both sides of the pool.

Key invariants​

  1. 1:1 ratio: every swap and every deposit/withdrawal is 1:1 in normalized units. No fees, no curve.
  2. DLRS supply matches reserves: total DLRS supply equals the sum of hub reserves; queue escrow is never counted as a reserve.
  3. FIFO ordering: each directed (offerAsset → wantAsset) queue is strictly first-in-first-out, and a swapper only reaches reserves once the queue ahead of it is cleared.
  4. Exits stay open: withdraw, redeemSpoke and cancelQueue are not blocked by pause.
  5. All-or-nothing for routers: swapExactInput executes in full or reverts—no intermediate states.

Next steps​

  • Functions — Complete function reference
  • Events — Events for monitoring
  • Errors — Error conditions and handling