> For the complete documentation index, see [llms.txt](https://docs.dittonetwork.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.dittonetwork.io/ditto-earn/ditto-musd-savings-vault/integration.md).

# 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](https://docs.dittonetwork.io/ditto-earn/ditto-musd-savings-vault/deployments) · [Reference UI](https://earn.dittonetwork.io/vaults/musd-savings-v2) · [Frontend source](https://github.com/dittonetwork/delta-neutral-vault-frontend)

## 1. Network and contracts

```ts
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.

```ts
const assets = parseUnits('10', 18); // example, not a minimum
// After the balance, mode, cap and allowance checks above:
const { request } = await publicClient.simulateContract({
  account, address: VAULT, abi: vaultAbi,
  functionName: 'deposit', args: [assets, account],
});
const hash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== 'success') throw new Error('Deposit reverted');
```

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

### NAV publication and UI refresh

Since **October 13, 2026**, V2 publishes NAV when a non-empty withdrawal batch closes, or **48 hours after the last successful publication**, whichever triggers the next update. A successful batch-close update, standalone post or guardian override resets the clock. This is an off-chain operator policy, not an on-chain freshness guarantee; the August pilot is unchanged.

The aggregator builds the full NAV calculation when a price update is needed, not on every idle loop. Publication still requires the normal finality, bridge-accounting, price/liquidity and operator-quorum checks. Failed checks or unavailable RPCs delay publication and trigger retries/monitoring; the last published price remains in use.

**No contract address, ABI or transaction-flow changes are needed for this cadence update.** Keep reading `currentNAV()` and `lastPostedAt()` from the existing NAVConsumer. Refresh those reads, `totalAssets()` and `convertToAssets(balanceOf(account))` through the UI's normal polling and after relevant receipts. Do not wait only for a batch-close event: standalone NAV updates also change the share price, TVL and position value. Do not calculate or post NAV in the frontend, or substitute an animated estimate for the published price.

Suggested FAQ copy: "The share price updates when a withdrawal batch closes, or 48 hours after the last publication. Safety checks or network issues may delay an update. Deposits use the last published price."

The 48-hour interval is not a withdrawal-settlement deadline or the independent liveness heartbeat. Withdrawal batch triggers remain 100 MUSD or 24 hours after opening; funding and claim follow separately.

### Display values

* **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.
* Verify that a standalone NAV update refreshes the displayed share price, TVL and position value without a withdrawal-batch event. Use a fixture or fork; do not send a production transaction just 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](https://github.com/dittonetwork/mezo-stable-yield/blob/291fe1903ee32f96f76818e87ba053710ab7df49/src/DMUSDVault.sol), [WithdrawalQueue](https://github.com/dittonetwork/mezo-stable-yield/blob/291fe1903ee32f96f76818e87ba053710ab7df49/src/WithdrawalQueue.sol). Repository access may be required.
