SumUSD

SumUSD

Integrations

Open app →

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).

01

Contracts

Addresses are published per network on deployment and injected into the frontend via the env vars below. Networks: Ethereum mainnet · Base · Sepolia (testnet).

ContractRoleAddressEnv var
SumUSDEngineMint / redeem entrypoint0x… TBANEXT_PUBLIC_ENGINE_ADDRESS
SumUSDThe ERC-20 token (18 decimals)0x… TBANEXT_PUBLIC_SUMUSD_ADDRESS
ImmutableTimelockGovernance 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.

02

Brand assets

Emerald 500

#22c55e

Emerald 700

#15803d

Foreground

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.

03

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-200x095ea7b3

    approve(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.

  • depositwriteEngine0x0efe6a8b

    deposit(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).

  • redeemwriteEngine0x2b83cccd

    redeem(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.

  • redeemBatchwriteEngine0xcbae8314

    redeemBatch(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.

  • redeemMixwriteEngine0x351a7689

    redeemMix(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.

  • previewDepositviewEngine0xb8f82b26

    previewDeposit(address collateral, uint256 amount) → uint256

    Quote the SumUSD a deposit would mint right now.

  • previewRedeemviewEngine0xcbe52ae3

    previewRedeem(address collateral, uint256 sumUsdAmount) → uint256

    Quote the net collateral a single-flavor redeem returns (haircut and margin included). Matches the redeem payout.

  • previewRedeemBatchviewEngine0xa7b46ea9

    previewRedeemBatch(address[] collaterals, uint256[] sumUsdAmounts) → uint256[] nets

    Quote each leg of a redeemBatch. Matches the batch payout leg-for-leg.

  • previewRedeemMixviewEngine0x0281a0ed

    previewRedeemMix(uint256 sumUsdAmount) → (address[] tokens, uint256[] amounts)

    Quote the pro-rata basket slice a distress exit would return, including the above-par cap.

  • listedCollateralsviewEngine0x0995431b

    listedCollaterals() → 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.

  • configsviewEngine0xfce89878

    configs(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).

  • systemCollateralizationRatioBpsviewEngine0x30912786

    systemCollateralizationRatioBps() → 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.

  • currentRedeemRateBpsviewEngine0x272f0606

    currentRedeemRateBps(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.

  • redeemRateBpsForviewEngine0x6e18abf7

    redeemRateBpsFor(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.

  • distressedviewEngine0x6d4ec725

    distressed() → 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.

  • distressClearsAtviewEngine0xba72f13b

    distressClearsAt() → uint256

    Unix time at which distress clears if backing holds; 0 when not distressed or the countdown has not started.

  • pokeDistresswriteEngine0xe2cd1ff2

    pokeDistress()

    Permissionless keeper hook: syncs the distress latch and advances the recovery countdown when the system is otherwise idle.

ISumUSDEngine.sol — canonical interface
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;
}
viem / wagmi — mint, redeem, redeemBatch, redeemMix
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.