CLRUSD and the ESA vault
Deploying and running CLRUSD: the savings token, issued one-for-one against USDC by an isolated
ESADepositVault whose reserve is kept apart from the AssurancePool. How the token and the vault
behave, including the redemption lock, is in Savings. Where this fits in
the full deployment order is Deploying the contracts.
On this page
Contracts
ClearUSDUpgradeable21_deploy_ClearUSDUpgradeableBurnMintERC20UUPS, 6 decimals. Carries the redemption lock (through an encumbrance source) and seize (LIQUIDATOR_ROLE)ClearUSD12_deploy_ClearUSDESADepositVault13_deploy_ESADepositVaultdepositWithAuthorization takes gasless EIP-3009 USDC deposits from a relayerCLRUSDTokenPool15_deploy_CLRUSDTokenPoolDeploying on a new network
Each command takes --network <name>; the npm scripts take the network after --.
1. The token.
npx hardhat run deploy/21_deploy_ClearUSDUpgradeable.ts --network base-sepoliaReads CLRUSD_ADMIN (the deployer if unset), CLRUSD_MAX_SUPPLY and CLRUSD_ADMIN_DELAY (two days
if unset). Nothing is pre-minted: supply arrives only through the vault. The admin delay is
scheduled at deployment, so a later handover of the admin role waits out the delay. If a vault is
already recorded, the script grants it mint and burn.
2. The vault.
npm run deploy:esa-vault -- base-sepoliaThe vault backs, in order of preference: VAULT_CLRUSD, the recorded ClearUSDUpgradeable, the
recorded ClearUSD, then CLRUSD_ADDRESS. The script:
- deploys the vault proxy, with
CLRUSD_ADMINholdingDEFAULT_ADMIN_ROLE,OPERATOR_ROLEandPAUSER_ROLE - grants
OPERATOR_ROLEto the savings relayer (SAVINGS_RELAYER_ADDRESS_<chainId>, elseSAVINGS_RELAYER_ADDRESS), and warns if neither is set - grants the vault
MINTER_ROLEandBURNER_ROLEon the token - accepts
ESA_VAULT_DEPOSIT_TOKENas a deposit token, or USDC by default on Base, Base Sepolia, Ethereum and Sepolia - records the vault as
ESADepositVault, or asVAULT_DEPLOYMENT_NAMEso a replacement can be recorded without overwriting the vault it replaces
3. The redemption lock. No script sets it. The token's admin calls
ClearUSDUpgradeable.setEncumbranceSource with the CollateralRegistry (deployed by script 20),
which reports how much of each holder's CLRUSD stands behind drawn credit. Until it is set, nothing
is locked.
4. The liquidator. After script 22 deploys the Liquidator,
scripts/upgrade_liquidator_savings.ts upgrades it and grants it LIQUIDATOR_ROLE on the token, so
savings-backed credit can be settled from a member's savings. It is idempotent.
5. Registry and cross-chain.
npm run deploy:clrusd-register -- base-sepolia # 14: registers CLRUSD in the TokenRegistry
npm run deploy:clrusd-pool -- base-sepolia # 15: the CCIP pool (CCIP_ROUTER, CCIP_RMN_PROXY)
npm run clrusd:ccip:configure -- base-sepolia # token admin and the remote lane
npm run clrusd:validate -- base-sepolia # roles, accepted token, registry entryThese four read the recorded ClearUSD first, and use CLRUSD_ADDRESS only when no ClearUSD
is recorded. On a network that has only ClearUSDUpgradeable, set CLRUSD_ADDRESS to it. On a
network that has both, they act on the original ClearUSD; registering, bridging or validating
ClearUSDUpgradeable there is not available through these scripts yet.
Moving an existing vault to the new token
A vault backing the original ClearUSD moves in two steps, in this order:
- 1.Redeem every outstanding
ClearUSD. - 2.Re-point: the vault's admin calls
ESADepositVault.setClrusd(<ClearUSDUpgradeable>). It refuses while any of the old token is outstanding.
scripts/verify_vault_parity.ts checks that a replacement vault holds the same roles and settings
as the one it replaces.
Environment variables
CLRUSD_ADMIN12, 13, 21CLRUSD_MAX_SUPPLY12, 21CLRUSD_ADMIN_DELAY21CLRUSD_PREMINT, CLRUSD_USE_CREATE2, CLRUSD_CREATE2_SALT, CLRUSD_CREATE2_FACTORY_ADDRESS12VAULT_CLRUSD, VAULT_DEPLOYMENT_NAME13CLRUSD_ADDRESS13, 14, 15, configure_ccip, validate_setup, when nothing is recordedESA_VAULT_ADDRESSvalidate_setup, repair_vault, when no vault is recordedESA_VAULT_DEPOSIT_TOKEN13, validate_setup, repair_vaultSAVINGS_RELAYER_ADDRESS(_<chainId>)13TOKEN_REGISTRY_ADDRESS, CLRUSD_REMOTE_CHAIN_ID, CLRUSD_REMOTE_ADDRESS14CCIP_ROUTER, CCIP_RMN_PROXY15CCIP_TOKEN_ADMIN_REGISTRY, CCIP_REGISTRY_MODULE_OWNER_CUSTOM, CCIP_REMOTE_CHAIN_SELECTOR, CCIP_REMOTE_TOKEN_ADDRESS, CCIP_REMOTE_POOL_ADDRESSconfigure_ccipCLRUSD_TOKEN_POOL_ADDRESS15, configure_ccip, validate_setupnpm run clrusd:repair-vault -- <network> re-grants the vault's mint and burn roles if they are
missing.
Emergency controls
ESADepositVault.pause()PAUSER_ROLE on the vaultMINTER_ROLE on the token from the vault and the CCIP poolDEFAULT_ADMIN_ROLEBURNER_ROLE on the tokenDEFAULT_ADMIN_ROLELIQUIDATOR_ROLE on the tokenDEFAULT_ADMIN_ROLEsetEncumbranceSource(address(0)), for example if the source breaksDEFAULT_ADMIN_ROLEUPGRADER_ROLE on the token. The vault upgrades under its DEFAULT_ADMIN_ROLEDEFAULT_ADMIN_ROLETo resume: re-grant roles only after the incident is reviewed and the state reconciled, then
unpause() the vault.
Solvency checks
For each accepted deposit token:
- 1.The vault's balance covers expected redemptions.
- 2.CLRUSD on the home chain reconciles against the vault's reserve. The API's hourly reconciler compares the vault's USDC with CLRUSD in circulation (Lithic → Reconciliation).
- 3.In-flight CCIP transfers are reconciled before large redemptions.
- 4.The
TokenRegistryentry and chain mappings for CLRUSD are current.