Mezo UI integration
Integrate the September 23, 2026 MUSD vault into a Mezo wallet or frontend.
Use deployment mainnet-2026-09-23. This is the V2 vault, not the August pilot. All user transactions take place on Mezo mainnet, chain ID 31612, with BTC for gas. Users do not bridge funds or transact on Ethereum themselves.
The vault is live with real funds. Initial live end-to-end validation is still in progress; a funded deposit → request → claim test remains a release check for the Mezo integration.
Deployment addresses · Reference UI · Frontend source
1. Network and contracts
import { parseAbi, parseUnits } from 'viem';
const chainId = 31612;
const MUSD = '0xdD468A1DDc392dcdbEf6db6e34E89AA338F9F186';
const VAULT = '0x9171Cb787C3fEEbBC808D07CaB62B1d789CE14F8';
const QUEUE = '0xeBA277be5AEdd2Bb2F90f81f01ea58f1cc959A0a';
const NAV = '0x16270960d928B687A2dedC0235036CcbdaE03F8c';
const tokenAbi = parseAbi([
'function allowance(address owner, address spender) view returns (uint256)',
'function approve(address spender, uint256 amount) returns (bool)',
'function balanceOf(address account) view returns (uint256)',
]);
const vaultAbi = parseAbi([
'function asset() view returns (address)',
'function queue() view returns (address)',
'function nav() view returns (address)',
'function mode() view returns (uint8)',
'function balanceOf(address account) view returns (uint256)',
'function totalAssets() view returns (uint256)',
'function maxDeposit(address receiver) view returns (uint256)',
'function estimateDeposit(uint256 assets) view returns (uint256 shares, bool available)',
'function convertToAssets(uint256 shares) view returns (uint256)',
'function deposit(uint256 assets, address receiver) returns (uint256 shares)',
'function requestWithdrawal(uint256 shares) returns (uint256 batchId)',
'function cancelRedeem(uint64 batchId, uint256 shares)',
'function claimWithdrawal(uint64 batchId) returns (uint256 assets)',
'event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)',
'event RedeemRequest(address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 shares)',
]);
const queueAbi = parseAbi([
'struct BatchInfo { uint64 batchId; uint8 status; uint8 reason; uint64 openedAt; uint64 closedAt; uint64 eta; uint256 totalShares; uint256 closeNavRay; uint256 obligation; uint256 funded; uint256 claimed; }',
'function currentBatchId() view returns (uint64)',
'function batchInfo(uint64 batchId) view returns (BatchInfo)',
'function sharesOf(address controller, uint64 batchId) view returns (uint256)',
'function claimableAssets(address controller, uint64 batchId) view returns (uint256)',
'event Claimed(uint64 indexed batchId, address indexed controller, address receiver, uint256 assets)',
]);
const navAbi = parseAbi([
'function currentNAV() view returns (uint256)',
'function lastPostedAt() view returns (uint64)',
]);MUSD and dMUSD both use 18 decimals. currentNAV() is MUSD per dMUSD scaled by 1e27. Ethereum USDC and Mezo mUSDC use 6 decimals; they are not the deposit token.
Use the vault proxy above for user calls and as the approval spender, not its implementation. Send the approval transaction to MUSD. Check eth_chainId, asset(), queue() and nav() against this bundle before enabling transactions. The vault is also the dMUSD ERC-20 token.
Do not use a generic synchronous ERC-4626 widget unchanged. All four preview* methods and synchronous withdraw/redeem revert. maxWithdraw/maxRedeem return zero because instant exits are unsupported—not because withdrawal requests are disabled. Use the methods below. No Ditto API key or operator service is required for these flows.
2. Deposit MUSD
The examples assume a Mezo publicClient, a walletClient connected to the user's wallet, and the selected address account. Never use a server-held key to sign user transactions.
Read
mode()(0 = Active), MUSD balance, BTC balance,maxDeposit(account)andestimateDeposit(assets). The estimate'savailableflag only checks mode: it does not check balance, allowance or cap. Block submission on failed reads, zero shares or an amount above the deposit limit.Read
MUSD.allowance(account, VAULT). If insufficient, approve VAULT, not the queue or bridge. An exact-amount approval is sufficient. Wait for a successful approval receipt before depositing.Simulate
deposit(assets, account), then request the wallet transaction. Display success only after a successful receipt; the vault'sDepositevent gives the actual shares minted.
Deposits mint shares immediately at the last published NAV. They do not wait for placement or bridging. The estimate can change before inclusion; the deposit method has no minShares argument. Show the price and lastPostedAt() timestamp, not a promise of a fixed output.
3. Request, track and claim a withdrawal
Request
VAULT.requestWithdrawal(shares)
Amount is dMUSD, not MUSD. Locks the user's shares into a batch. No dMUSD approval is needed for this self-service method.
Identify
RedeemRequest in the request receipt
requestId is the batch ID. Filter logs by the vault address and the expected controller/owner. Do not use a pre-read currentBatchId() as proof: a batch may close before inclusion.
Track
QUEUE.batchInfo(batchId) and claimableAssets(account, batchId)
Poll after receipts and while processing. Keep the request visible after shares leave the wallet.
Cancel
VAULT.cancelRedeem(batchId, shares)
Only while status is Open. Check sharesOf(account, batchId) and simulate; cancellation can race batch close.
Claim
VAULT.claimWithdrawal(batchId)
Enable when claimableAssets > 0, including partial funding. Pays MUSD to the calling user. Claim is a separate wallet transaction.
Use the same simulate → wallet → successful-receipt pattern for each write. Refresh balances, batch status and claimable amount afterwards. An emergency partial claim can leave more payable later; do not delete the request merely because one claim succeeded. sharesOf is not a reliable remaining-entitlement view after claims: use claimableAssets and the vault's pendingRedeemRequest / claimableRedeemRequest views when needed.
To restore requests after reconnecting, index this vault's RedeemRequest events for the controller from Mezo block 12045502. Store the key as (chainId, vault, controller, batchId). Handle reorgs and RPC errors; an unreadable range is not an empty history. Current batch ID is only for displaying where the next request is expected to go.
BatchInfo.status
UI treatment
0 — None
No such batch
1 — Open
Queued; cancellation available
2–4 — Closed / Unwinding / InFlight
Processing; do not require every intermediate status to appear
5 — PartiallyFunded
Claim what claimableAssets permits; retain unpaid remainder
6 — Fulfilled
Settled; claim only if the user's claimableAssets is nonzero
Current off-chain policy is 100 MUSD queued or 24 hours after the batch opened, with no empty-batch close. These are operator settings, not immutable contract deadlines. Closing and funding still require execution. eta == 0 means unknown, not immediate. Buffer-funded settlement is faster than returning funds from Ethereum; do not show an unconditional completion time.
Modes: 0 Active, 1 Paused, 2 Deprecated. Disable deposits outside Active. V2 still accepts withdrawal requests and open-batch cancellation in modes 1 and 2, and funded claims remain callable. Pause can delay normal settlement; Deprecated is a permanent wind-down, not confiscation of claims. These V2 rules must not be applied blindly to the pilot.
4. Values and risk copy
Position value:
convertToAssets(balanceOf(account)), explicitly labelled at the last published NAV. Show pending withdrawals and claimable MUSD separately.TVL:
totalAssets()is NAV-marked, not cash available to withdraw. Pending obligations and queue balances must not be blindly added again.APY: show Spark Savings' current rate as the vault's estimated APY, as the reference UI does. Label it an estimate, not a guaranteed return: the vault's actual yield varies with rebalancing. Part of the MUSD stays on Mezo as a withdrawal buffer, new deposits move to Spark in batches, and bridge and swap costs show up in the share price.
Fees: no protocol deposit/withdrawal fee; bridge and swap costs are borne by the vault. Wallet gas is BTC on Mezo.
Risk: stale published prices, stablecoin exchange rates, contract/bridge/venue failures and operator delays can reduce value or delay exits. Deposits are public; there is no NAV-age gate.
Bridge display: show independently verified transfer status, not
TVL minus visible balances. Ditto Earn's recent-activity endpoint is a bounded UI feed, not a settlement oracle or an all-time pending balance API.
Before releasing the Mezo UI
Confirm the chain, four contract addresses and 18/27 decimal handling against this deployment.
Test approval rejection, failed RPC reads, reverted transactions and wallet/network changes.
Run deposit → request → batch funding → claim with a small agreed amount; separately check cancel while Open and a request racing close.
Test partial claim, paused/deprecated handling and reconnection in fixtures or on a fork—do not mutate production governance to test the UI.
Keep the August pilot as a separate product/address if supporting it. Its shares do not become V2 shares.
Reference selectors and tuple layout were checked against the deployed September 23 contracts and source on 2026-09-23. Full source: DMUSDVault, WithdrawalQueue. Repository access may be required.