Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.yieldo.xyz/llms.txt

Use this file to discover all available pages before exploring further.

The Deposit Router is an attribution-only, non-custodial pass-through. It pulls tokens from the caller, dispatches to the correct vault protocol (ERC-4626, Midas issuance vault, Veda teller, Lido queue, or custom adapter), forwards the minted shares to the user, and emits a Routed event for on-chain attribution. No fees are deducted — 100% of the user’s tokens reach the vault. Current version: V3.2.0 on all chains (Ethereum, Base, Arbitrum, Optimism, HyperEVM, Katana). Mainnet upgraded 2026-04-25 in tx 0x2f7370658006e87972488159bc97dfc5cfef8067039e5127c8060c384ecbce21.

What Changed in V3.2.0

  • depositForAvailable(...) — new entry point that pulls min(allowance, balance) from msg.sender instead of an explicit amount argument. Eliminates the calldata-amount-mismatch revert that bridge-fee underflows would otherwise cause: any 1-wei underdelivery from Across/Stargate would previously revert the whole composer call. Now the router sweeps whatever actually arrived.
  • ERC-4626 void-return fallback — vaults whose deposit() returns no value (some forks of OpenZeppelin’s older 4626 reference) are detected via vrd.length and fall back to a balanceOf delta to compute shares. Removes a class of “Adapter: shares not delivered” reverts on niche forks.
  • All V3.1.1 changes (open caller model, no whitelist) carried forward.

What Changed in V3.1.1

  • Removed the authorizedCallers gate on depositFor / depositRequestFor. Any address can now call with an arbitrary user argument, which is used purely for the Routed event and as the share recipient. This removes ops friction of whitelisting every new LiFi bridge-receiver contract (ReceiverAcrossV3/V4, ReceiverStargateV2, ReceiverChainflip, per-chain Executors, etc.) and keeps the composer flow working across bridge updates without a router upgrade.
  • Security note: funds are pulled from msg.sender (the caller must own the tokens or have been approved), so no one can spend a third party’s balance. The remaining risk is attribution spoofing — anyone can emit a Routed event with an unrelated user/partnerId. Attribution consumers (revenue share, referrer tiering) should treat Routed events as advisory and cross-check against on-chain share balances where strict correctness matters.
  • authorizedCallers storage slot and the admin functions (setAuthorizedCaller, setAuthorizedCallerBatch) are preserved for forward compatibility but are not consulted by the current implementation.

What Changed in V3.1.0

  • 8-arg depositFor with minSharesOut — on-chain slippage floor. The 7-arg form is kept as a compatibility shim (forwards with minSharesOut=0).
  • Shares returned explicitly_executeVaultCall now returns the exact shares minted, cross-checked against balanceOf(recipient) to detect silent failures in downstream adapters.
  • Routed event now includes shares — previously indexers had to read vault share balances to compute this.
  • Introduced authorizedCallers whitelist (removed again in V3.1.1 — see above).

Contract Addresses

All chains on V3.2.0 as of 2026-04-25. Same proxy address on Katana and HyperEVM is coincidental (CREATE2 on separate chains).
ChainChain IDProxy Address
Ethereum10x85f76c1685046Ea226E1148EE1ab81a8a15C385d
Base84530xF6B7723661d52E8533c77479d3cad534B4D147Aa
Arbitrum421610xC5700f4D8054BA982C39838D7C33442f54688bd2
Optimism100x7554937Aa95195D744A6c45E0fd7D4F95A2F8F72
Monad1430xCD8dfD627A3712C9a2B079398e0d524970D5E73F
HyperEVM9990xa682CD1c2Fd7c8545b401824096A600C2bD98F69
Katana7474740xa682CD1c2Fd7c8545b401824096A600C2bD98F69

How Deposits Work

Same-Chain Deposit

User approves tokens to router → User calls depositFor() → Router pulls tokens
  → Router dispatches to vault protocol → Shares sent to user → Routed event emitted
msg.sender == user. No whitelist check needed.

Cross-Chain Deposit (Two-Step)

Used for vault types that aren’t composable with LiFi’s Composer (Midas, Veda, Custom, IPOR, Lido):
Step 1 (source):  User sends LiFi bridge tx
                  → tokens arrive at user's wallet on destination chain
Step 2 (dest):    User approves → User calls depositFor() → Shares sent to user
Step 2 is a normal same-chain deposit from the user’s perspective.

Cross-Chain Deposit (Single-Step, Composer)

Used for Morpho and other standard ERC-4626 vaults. LiFi bridges + calls depositFor atomically on the destination:
User sends LiFi tx on source chain → LiFi Executor on destination receives tokens
  → Executor calls router.depositFor(..., user=<user>) → Shares sent to user
Because msg.sender is a LiFi bridge receiver (Executor, ReceiverAcrossV4, ReceiverStargateV2, etc.), the router must allow callers other than user. V3.1.1 accepts any msg.sender — only msg.sender’s own balance can be pulled.

depositForAvailable (V3.2.0+)

Cross-chain composer entry point — pulls min(allowance, balance) from msg.sender instead of an explicit amount. Used by the /v1/quote/build API for any composer route so the destination call cannot revert on bridge underdelivery.
function depositForAvailable(
    address vault,
    address asset,
    address user,
    bytes32 partnerId,
    uint8 partnerType,
    bool isERC4626,
    uint256 minAmount,        // floor — revert if pulled amount < this
    uint256 minSharesOut      // slippage floor on shares minted
) external;
Behavior:
uint256 allowed = IERC20(asset).allowance(msg.sender, address(this));
uint256 bal     = IERC20(asset).balanceOf(msg.sender);
uint256 amount  = allowed < bal ? allowed : bal;
require(amount >= minAmount && amount > 0, "Insufficient");
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
// dispatch via _executeVaultCall, emit Routed.
Why: LiFi’s bridge receivers (Across, Stargate, etc.) approve the router for the post-bridge amount and call this. If the bridge delivered 1 wei less than the LiFi quote estimated, the explicit-amount depositFor would revert at safeTransferFrom. depositForAvailable sweeps the actual balance and proceeds.

depositFor

Primary entry point for same-chain user-initiated deposits. Two overloaded forms are exposed for backward compatibility:
// 8-arg (preferred) — includes slippage floor
function depositFor(
    address vault,
    address asset,
    uint256 amount,
    address user,
    bytes32 partnerId,
    uint8 partnerType,
    bool isERC4626,
    uint256 minSharesOut
) public;

// 7-arg (compat) — forwards to the 8-arg form with minSharesOut = 0
function depositFor(
    address vault,
    address asset,
    uint256 amount,
    address user,
    bytes32 partnerId,
    uint8 partnerType,
    bool isERC4626
) external;
Access control inside both forms (V3.1.1):
require(user != address(0) && vault != address(0) && asset != address(0) && amount > 0, "Bad params");
// No caller gate — any msg.sender may call with any `user`.
msg.sender is still the token source (safeTransferFrom(msg.sender, ...)), so only the caller’s own balance can ever be spent.

depositRequestFor

For vaults with async deposit queues (Yearn-style requestDeposit):
function depositRequestFor(
    address vault,
    address asset,
    uint256 amount,
    address user,
    bytes32 partnerId,
    uint8 partnerType
) external;
Emits DepositRequestRouted(partnerId, partnerType, user, vault, asset, amount, requestId).

Events

event Routed(
    bytes32 indexed partnerId,
    uint8 partnerType,
    address indexed user,
    address indexed vault,
    address asset,
    uint256 amount,
    uint256 shares     // NEW in V3.1.0
);

event DepositRequestRouted(
    bytes32 indexed partnerId,
    uint8 partnerType,
    address indexed user,
    address indexed vault,
    address asset,
    uint256 amount,
    uint256 requestId
);

event VaultAdapterUpdated(address indexed vault, address indexed adapter);
event AuthorizedCallerUpdated(address indexed caller, bool authorized); // emitted by admin setter; no longer affects caller access in V3.1.1

Vault Dispatch Priority

_executeVaultCall picks the deposit path in this order:
PriorityMappingCall emitted
1vaultAdapters[vault]IVaultAdapter(adapter).deposit(...)
2midasVaults[vault]midasIssuance.depositInstant(asset, amt18, 0, 0)
3vedaTellers[vault]teller.deposit(asset, amount, 0)
4lidoDepositQueues[vault][asset]queue.deposit(uint224(amount), address(0), [])
5isERC4626 == truevault.deposit(amount, recipient)
6fallbackvault.syncDeposit(amount, recipient, address(0))
Every path checks shares > 0 and cross-verifies balanceOf(recipient) deltas to fail closed.

Vault Adapter Pattern

New vault protocols can be added without a router upgrade. Deploy an adapter implementing IVaultAdapter:
interface IVaultAdapter {
    function deposit(
        address vault,
        address asset,
        uint256 amount,
        address recipient
    ) external returns (uint256 shares);
}
The router transfers amount of asset to the adapter and calls adapter.deposit(...). The adapter must (a) return the actual share count and (b) deliver those shares to recipient. Retained assets on the adapter revert the deposit. Register an adapter:
router.setVaultAdapter(vaultAddress, adapterAddress);      // single
router.setVaultAdapterBatch(vaults[], adapters[]);          // batch

Caller Model (V3.1.1)

V3.1.1 removes the caller whitelist. Any address may call depositFor(..., user=<anyone>). The admin setters (setAuthorizedCaller, setAuthorizedCallerBatch) and the authorizedCallers mapping still exist in storage but are not consulted by the current implementation — they remain in place so the whitelist can be re-enabled in a future release without a storage-layout migration. Why the gate was removed: LiFi routes composer deposits through several different bridge-specific receivers (ReceiverAcrossV3, ReceiverAcrossV4, ReceiverStargateV2, ReceiverChainflip, plus per-chain Executors), and each new bridge LiFi supports would otherwise require a whitelist update on every router proxy. V3.1.0’s Whitelist model couldn’t keep up. Trade-off: anyone can emit a Routed event with a spoofed user / partnerId. Funds are safe (safeTransferFrom pulls from msg.sender only), but attribution consumers should treat Routed events as informational and verify with on-chain share balances when exact attribution matters.

Admin Functions

Owner-only:
FunctionPurpose
setVaultAdapter(vault, adapter)Register a vault protocol integration
setVaultAdapterBatch(vaults, adapters)Batch variant
setMidasVault(token, iv)Map a Midas share token to its issuance vault
setVedaTeller(vault, teller)Map a Veda BoringVault to its teller
setLidoDepositQueue(vault, asset, q)Map a Lido vault to its SyncDepositQueue
setAuthorizedCaller(caller, ok)Whitelist a composer caller
setVaultAllowed(vault, ok)Optional vault-level whitelist
setVaultWhitelistEnabled(bool)Enable/disable the vault allowlist
pause() / unpause()Circuit breaker
rescueERC20(token, to, amt)Recover any stuck ERC-20 (should never be needed)
withdrawETH()Withdraw any native ETH that landed on the contract
transferOwnership(newOwner)Step 1 of two-step ownership transfer
acceptOwnership()Step 2 — pending owner accepts

Security Properties

  • No custody — contract never holds a positive balance between transactions. Any stuck balance is rescuable by owner.
  • No fees — 100% of the deposited amount is forwarded to the vault. Attribution is event-only.
  • Access-controlled user spoofing — non-authorized callers can only pass user == msg.sender.
  • Reentrancy protected — all state-mutating deposit paths use the OpenZeppelin ReentrancyGuard.
  • Optional vault allowlistvaultWhitelistEnabled can gate which vaults can be deposited into. Off by default.
  • Two-step ownership — prevents accidental transfer to dead addresses.
  • UUPS upgradeable — owner-controlled, storage layout preserved across all upgrades via _deprecated_ padding.

API Integration Notes

The Yieldo API (/v1/quote/build) returns pre-encoded depositFor calldata using the 7-arg form for maximum backward compat during the rollout window. After all integrators have migrated, the API will switch to the 8-arg form with a computed minSharesOut for real slippage protection. Users never sign an intent or typed-data message for deposits anymore — they only sign the approval (for ERC-20) and the deposit transaction itself. Withdrawals are handled by the vault protocols directly (ERC-4626 redeem / withdraw) and are outside the router’s scope.