Upfront notes
- UTY vault mint (USDC → UTY) and redeem (UTY → USDC) run only on Base. The underlying USDC custody lives here; there is no cross-chain route to USDC. To exit UTY from a spoke, bridge UTY to Base first (see Bridge operations), then redeem.
- Every deposit and redemption request requires a prior ERC-20
approveon the underlying token granting the vault allowance. - Bonding periods are configurable vault state, not protocol constants. Call
getBondingPeriod()on each vault as the authoritative source; at the time of writing, yUTY returns0(claims available the same block as the request) and UTY returns7 days. Integrators building bonding-aware logic should read the function, not hard-code the value.
Deposit USDC into the UTY vault
UTY is USDC-backed at a strict 1:1 peg. You deposit USDC; the vault mints UTY 1:1; USDC is swept to the custodian wallet by the vault’s extension logic.1
Approve USDC
USDC.approve(UTY_VAULT, amount) on Base.2
Deposit
UTY.deposit(assets, receiver) — assets is the USDC amount; receiver is the address that receives the minted UTY.Common failure. If the deposit amount underflows the vault’s internal accounting, the call reverts with
FeeExceedsAmount(fee, amount). See Gotchas.Deposit UTY into the yUTY vault
yUTY is an ERC-4626-style yield-bearing vault over UTY. The exchange rate is not 1:1 — the mainnet yUTY vault has received donations, sototalAssets() != totalSupply(). Use convertToAssets(shares) and convertToShares(assets) to convert accurately; don’t assume a 1:1 relationship.
1
Approve UTY
UTY.approve(YUTY_VAULT, amount) on Base.2
Deposit
yUTY.deposit(assets, receiver) — assets is the UTY amount; receiver receives the yUTY shares.Withdraw from yUTY
yUTY uses the ERC-7540 async request-and-claim pattern. The bonding period is currently0, so the claim is available in the same block as the request — but the call sequence is still two steps.
1
Request
yUTY.requestRedeem(shares, controller, owner) — burns shares, creates a WithdrawalRequest, and returns a requestId. controller is the address authorized to claim; owner must be msg.sender or an approved operator.2
Claim (same block, since bondingPeriod == 0)
yUTY.redeemById(requestId, receiver) — transfers the underlying UTY to receiver. Call this in the next transaction once the request confirms, or in the same transaction if your integration batches both calls. To drain multiple matured requests in a single call, see Hub claim paths for the redeem and withdraw alternatives.Common failure. If
msg.sender isn’t the controller and isn’t approved via setOperator on the hub, the claim reverts with InvalidRequest() — an overloaded error that also surfaces when the request doesn’t exist or was already claimed. See Gotchas for the debugging recipe.Withdraw from UTY
UTY redemption (UTY → USDC) runs through the same ERC-7540 pattern but the bonding period is 7 days. The spoke doesn’t gate the claim client-side — if you claim before the bonding period elapses, the hub rejects (with a different revert name per claim path; see the Common-failure note below). Checkrequest.unlockTime against block.timestamp before claiming.
1
Approve UTY
UTY.approve(UTY_VAULT, amount) on Base (required even for redemption because the vault transfers shares internally).2
Request
UTY.requestRedeem(shares, controller, owner) — burns UTY shares, creates a WithdrawalRequest with unlockTime = block.timestamp + getBondingPeriod().3
Wait for bonding
7 days at current configuration. Read
getBondingPeriod() for the authoritative value.4
Claim
UTY.redeemById(requestId, receiver) — transfers USDC to receiver. To drain multiple matured requests in a single call, see Hub claim paths for the redeem and withdraw alternatives.Common failure. Premature claim revert names differ per path:
redeemByIdreverts withBondingPeriodNotOver().redeemreverts withERC4626ExceededMaxRedeem(controller, shares, maxShares)becausemaxRedeem(controller)returns0during bonding.withdrawreverts withERC4626ExceededMaxWithdraw(controller, assets, maxAssets)becausemaxWithdraw(controller)returns0during bonding.
request.unlockTime against block.timestamp before claiming on any path. See Gotchas.Hub claim paths
The hub vault exposes three claim functions. All three require the bonding period to have elapsed and apply the same controller-or-operator authorization rules described above. They differ in how they select which request or requests to consume.redeemandwithdrawskip non-matured requests in the loop, so a single call cleanly handles a queue that mixes matured and bonding requests.- All three paths are hub-only. None are exposed on the spoke
UTYVaultInterface— see Spoke claim constraints. - Partners doing per-request accounting (gauges, vault-of-vaults, anything that tracks individual request IDs) should use
redeemById. It is the only path with a stable caller-controlled request identity. Iteration order across multiple matured requests inredeemandwithdrawis implementation-defined and must not be relied on for accounting.
Function reference
UTY hub vault
yUTY hub vault
The WithdrawalRequest struct
Event reference
This section is the canonical home for the cross-chain event correlation recipe. The Bridge operations and Spoke chain operations pages link here rather than re-derive it.Hub vault (UTYAsyncVaultV1, on Base)
Hub composer (UTYVaultComposer, on Base)
Spoke VaultInterface (UTYVaultInterface, on each spoke — included here for correlation)
The spoke emits no per-request event. Partner indexers watching for the on-spoke handle on a cross-chain request use GasTankDebited’s guid field as the correlation key.
Correlation recipe
For a cross-chain flow initiated on a spoke, match the spoke’sGasTankDebited.guid to the corresponding hub event:
- Withdrawal request (spoke → hub): spoke
GasTankDebited.guid→ hub vaultRedeemRequest(theguidappears in the hub composer’sRedeemRequestedevent, which is emitted immediately before the vault’sRedeemRequest). - Deposit return-hop (hub → spoke): watch the hub composer’s
GasTankDebited.guidas the signal that the deposit’s return message has been paid for by the protocol’s hub gas tank. - Claim return-hop (hub → spoke): watch the spoke
VaultInterface’sGasTankDebited.guidfor the claim-phase return trip.
https://layerzeroscan.com/tx/<txhash> — the message may be queued pending a gas-tank refill. See Gotchas: InsufficientFunds.
Why donate() reverts on UTY but works on yUTY
The UTY vault overrides
donate() to revert. UTY maintains a strict 1:1 peg with USDC — a donation would inflate the share price and break the peg. The yUTY vault allows donate(); that’s the mechanism by which yield is distributed to yUTY holders (the per-share price increases).