SumUSD
Integrations
Everything needed to integrate SumUSD: the deployed contract addresses, brand assets, and the exact on-chain calls to mint, redeem, and batch-redeem. SumUSD is a standard ERC-20 (18 decimals); the SumUSDEngine is the entrypoint for every mint and redeem. All amounts are in base units and every flavor is valued at par ($1 = 1 unit).
Contracts
Addresses are published per network on deployment and injected into the frontend via the env vars below. Networks: Ethereum mainnet · Base · Sepolia (testnet).
| Contract | Role | Address | Env var |
|---|---|---|---|
| SumUSDEngine | Mint / redeem entrypoint | 0x… TBA | NEXT_PUBLIC_ENGINE_ADDRESS |
| SumUSD | The ERC-20 token (18 decimals) | 0x… TBA | NEXT_PUBLIC_SUMUSD_ADDRESS |
| ImmutableTimelock | Governance owner (96h delay) | 0x… TBA | — (published on deploy) |
Collateral flavors
Flavor addresses are not configured or hardcoded. The accepted-collateral set is a governance-curated whitelist that changes through timelocked setCollateral / removeCollateral actions, so this app and any integration should discover it on-chain rather than pin a list. Read listedCollaterals() for the full set (up to 24), then per token configs(token) for its cached decimals and enabled / backing-excluded status and the ERC-20 symbol() for a label. That is exactly what this frontend does (see lib/useCollaterals.ts), so a listing change needs no redeploy.
Brand assets
SumUSD
Sigma mark · emerald
Emerald 500
#22c55eEmerald 700
#15803dForeground
oklch(0.23 0.012 155)The mark is a sigma (“sum”) in an emerald disc. Keep clear space around it, don't recolor or add effects, and place it on white or a light background. For a monochrome context, use solid Foreground rather than the gradient.
Contract calls
The public entrypoints, with 4-byte selectors. Writes go to the engine except approve, which is called on the collateral token. Redemptions need no approval — the engine burns the caller's SumUSD directly.
- approvewriteERC-20
0x095ea7b3approve(address spender, uint256 amount)
Approve the engine to pull the collateral before minting. Called on the collateral token, not the engine. Not needed for redemptions.
- depositwriteEngine
0x0efe6a8bdeposit(address collateral, uint256 amount, uint256 minSumUsdOut) → uint256 minted
Mint SumUSD 1:1 from an accepted collateral (normalized for decimals). minSumUsdOut is slippage protection (0 to skip). Reverts if the price is off-peg, system backing is under 99%, or the distress latch is set (MintDisabledInDistress, even while backing reads above par during recovery).
- redeemwriteEngine
0x2b83cccdredeem(address collateral, uint256 sumUsdAmount, uint256 minCollateralOut) → uint256 collateralOut
Burn SumUSD for one flavor at par minus the weight-tilt haircut and margin. minCollateralOut is in the flavor's decimals. No approval needed — the engine burns your SumUSD directly.
- redeemBatchwriteEngine
0xcbae8314redeemBatch(address[] collaterals, uint256[] sumUsdAmounts, uint256[] minOuts) → uint256[] collateralOuts
Redeem several flavors in one tx, each leg priced like an individual redeem on a single basket snapshot. The three arrays must be equal length and non-empty; any leg reverting reverts the whole call. Normal mode only.
- redeemMixwriteEngine
0x351a7689redeemMix(uint256 sumUsdAmount, uint256[] minOut) → uint256[] amounts
Distress exit, callable only while distressed(): burn SumUSD for a pro-rata slice of the entire basket, capped at $1 of backing per SumUSD (slices are scaled by supply/backing while backing is above 100%). Pass an empty minOut array to skip per-token slippage checks.
- previewDepositviewEngine
0xb8f82b26previewDeposit(address collateral, uint256 amount) → uint256
Quote the SumUSD a deposit would mint right now.
- previewRedeemviewEngine
0xcbe52ae3previewRedeem(address collateral, uint256 sumUsdAmount) → uint256
Quote the net collateral a single-flavor redeem returns (haircut and margin included). Matches the redeem payout.
- previewRedeemBatchviewEngine
0xa7b46ea9previewRedeemBatch(address[] collaterals, uint256[] sumUsdAmounts) → uint256[] nets
Quote each leg of a redeemBatch. Matches the batch payout leg-for-leg.
- previewRedeemMixviewEngine
0x0281a0edpreviewRedeemMix(uint256 sumUsdAmount) → (address[] tokens, uint256[] amounts)
Quote the pro-rata basket slice a distress exit would return, including the above-par cap.
- listedCollateralsviewEngine
0x0995431blistedCollaterals() → address[]
The full set of listed collateral flavors (enabled and frozen), up to 24. The entry point for discovering the basket on-chain instead of hardcoding it.
- configsviewEngine
0xfce89878configs(address token) → (bool enabled, uint8 decimals, uint16 redeemRateBps, address oracle, bool backingExcluded)
Per-flavor config: whether deposits are enabled, cached token decimals, base redeem rate, its oracle, and whether it is backing-excluded (siloed).
- systemCollateralizationRatioBpsviewEngine
0x30912786systemCollateralizationRatioBps() → uint256 ratioBps
System backing in bps (10000 = 100%). Returns ~uint256 max before the first deposit. Do NOT infer the regime from this: read distressed() instead.
- currentRedeemRateBpsviewEngine
0x272f0606currentRedeemRateBps(address collateral) → uint256
The MARGINAL weight-tilt redemption rate for a flavor in bps (pre-margin, zero size). A real trade pays less: the tilt is priced on the post-redemption basket.
- redeemRateBpsForviewEngine
0x6e18abf7redeemRateBpsFor(address collateral, uint256 sumUsdAmount) → uint256
The rate a given SIZE actually prices at (pre-margin) — this is what redeem applies. Use it, not currentRedeemRateBps, to quote a specific trade.
- distressedviewEngine
0x6d4ec725distressed() → bool
The distress LATCH. True ⇒ redeem/redeemBatch revert UseRedeemMix and holders exit via redeemMix. Latches instantly below 99% backing; clears only after backing holds ≥ 100.25% for 6h, so a recovered ratio does not mean redemption has reopened.
- distressClearsAtviewEngine
0xba72f13bdistressClearsAt() → uint256
Unix time at which distress clears if backing holds; 0 when not distressed or the countdown has not started.
- pokeDistresswriteEngine
0xe2cd1ff2pokeDistress()
Permissionless keeper hook: syncs the distress latch and advances the recovery countdown when the system is otherwise idle.
interface ISumUSDEngine {
// --- Mint ---
function deposit(address collateral, uint256 amount, uint256 minSumUsdOut)
external returns (uint256 minted);
// --- Redeem ---
function redeem(address collateral, uint256 sumUsdAmount, uint256 minCollateralOut)
external returns (uint256 collateralOut);
function redeemBatch(
address[] calldata collaterals,
uint256[] calldata sumUsdAmounts,
uint256[] calldata minOuts
) external returns (uint256[] memory collateralOuts);
// Distress-only pro-rata exit (while distressed(); slices capped at $1 of backing per SumUSD)
function redeemMix(uint256 sumUsdAmount, uint256[] calldata minOut)
external returns (uint256[] memory amounts);
// --- Quotes (view) ---
function previewDeposit(address collateral, uint256 amount) external view returns (uint256);
function previewRedeem(address collateral, uint256 sumUsdAmount) external view returns (uint256);
function previewRedeemBatch(address[] calldata collaterals, uint256[] calldata sumUsdAmounts)
external view returns (uint256[] memory nets);
function previewRedeemMix(uint256 sumUsdAmount)
external view returns (address[] memory tokens, uint256[] memory amounts);
// --- Status (view) ---
function systemCollateralizationRatioBps() external view returns (uint256 ratioBps);
function currentRedeemRateBps(address collateral) external view returns (uint256); // marginal
function redeemRateBpsFor(address collateral, uint256 sumUsdAmount) external view returns (uint256);
function distressed() external view returns (bool);
function distressClearsAt() external view returns (uint256);
function pokeDistress() external;
}import {parseUnits} from "viem";
import {ENGINE_ABI, ERC20_ABI, ENGINE_ADDRESS} from "@/lib/contracts";
// --- Mint: approve the collateral, then deposit 1:1 ---
const amount = parseUnits("1000", 6); // a 6-decimal flavor
await wallet.writeContract({
address: COLLATERAL, abi: ERC20_ABI, functionName: "approve",
args: [ENGINE_ADDRESS, amount],
});
await wallet.writeContract({
address: ENGINE_ADDRESS, abi: ENGINE_ABI, functionName: "deposit",
args: [COLLATERAL, amount, 0n], // minSumUsdOut = 0n to skip slippage check
});
// --- Redeem one flavor (SumUSD is 18 decimals; no approval needed) ---
const burn = parseUnits("1000", 18);
await wallet.writeContract({
address: ENGINE_ADDRESS, abi: ENGINE_ABI, functionName: "redeem",
args: [COLLATERAL, burn, 0n], // minCollateralOut in the flavor's decimals
});
// --- redeemBatch: split one redemption across flavors in a single tx ---
await wallet.writeContract({
address: ENGINE_ADDRESS, abi: ENGINE_ABI, functionName: "redeemBatch",
args: [
[FLAVOR_A, FLAVOR_C], // collaterals
[parseUnits("600", 18), parseUnits("400", 18)], // sumUSD burned per leg
[0n, 0n], // per-leg minOut
],
});
// --- Distress exit: when distressed() is true, redeem / redeemBatch revert
// (UseRedeemMix). Read the LATCH, not the ratio: it clears only after backing
// has held >= 100.25% for 6h, so a recovered ratio can still be gated. ---
await wallet.writeContract({
address: ENGINE_ADDRESS, abi: ENGINE_ABI, functionName: "redeemMix",
args: [burn, []], // empty minOut skips per-token slippage checks
});The full engine ABI the app uses lives in lib/contracts.ts; the protocol and mechanism are specified in the whitepaper.