For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

  1. Read mode() (0 = Active), MUSD balance, BTC balance, maxDeposit(account) and estimateDeposit(assets). The estimate's available flag 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.

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

  3. Simulate deposit(assets, account), then request the wallet transaction. Display success only after a successful receipt; the vault's Deposit event 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

Step
User call / read
Behaviour

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.