Unagii Docs
  • Introduction
  • Unagii Vaults
    • Overview
    • Understanding Vaults
    • Strategies
    • Architecture
    • Smart Contracts
    • Security
    • F.A.Q
    • Vault Integration
  • Unagii Stake
    • Overview
    • Ethereum
      • Kyber
      • Skale
    • Tendermint
      • Band
      • Cosmos
      • Kava
      • Persistence
      • Terra
      • Agoric
      • Akash
      • Archway
      • Shentu
      • Kujira
      • Osmosis
      • Passage
      • Quicksilver
      • Celestia
      • dYdX
    • Others
      • Sui
      • Solana
    • Security
    • F.A.Q
    • Stake Integration
      • Kyber
      • Skale
      • Tendermint Ecosystem
      • Sui
      • Solana
    • Page
  • DeFi
    • What is DeFi?
    • What are the risks in DeFi?
    • What are Stablecoins?
      • How to earn yield on Stablecoins?
        • What are Lending Pools?
        • What are Liquidity Pools?
  • Unagii Account
Powered by GitBook
On this page
  • Key Functions
  • Deposit
  • Calculations
  • Withdrawal
  • Notes
  • Block Delay

Was this helpful?

  1. Unagii Vaults

Vault Integration

Technical documentation on how to integrate your app with Unagii V3 Vaults.

PreviousF.A.QNextOverview

Last updated 10 months ago

Was this helpful?

Unagii V3 Vaults implement the with some additional functions for safety checks not implemented in the standard ERC-4626 interface. We recommend developers use these when possible for protection against MEV attacks, e.g. by usingsafeRedeem() instead of redeem() if making a large withdrawal.

An example ERC-4626 interface implementation can be found on .

Users should ensure that they're interacting with the official Unagii V3 Vault contracts found at the .

Key Functions

Deposit

vault.safeDeposit(uint256 amount, address receiver, uint256 minimumShares) -> uint256 sharesMinted

Parameter
Type
Description

amount

uint256

deposit exact amount of asset (e.g. USDC) in exchange to mint vault shares (e.g. uUSDCv3)

receiver

address

address which receives and owns shares minted

minimumShares

uint256

minimum shares to mint, can be calculated off-chain via helper methods like vault.previewDeposit(amount)

If receiver is a smart contract, it must implement the ability to make withdrawals or funds will be stuck forever!

Do not calculate minimumShares on-chain in the same call as this still leaves the transaction open to MEV attacks!

Example Code

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

// variables
Vault vault = Vault(/* vault address */);
uint256 amount = /* amount of tokens be deposited */;
address receiver = /* address of receiver */;

// step 1: approve tokens
address token = address(vault.asset());
ERC20(token).approve(address(vault), amount);

// step 2: calculate minimum shares to be minted
// this can be done off-chain using methods like `vault.previewDeposit()`
// do not do this on-chain in the same transaction!
uint256 minimumShares = /* minimum shares to be minted */;

// step 2: deposit
vault.safeDeposit(amount, receiver, minimumShares);
import ethers from 'ethers';

// variables
const receiver: string = /* address of receiver */;
const signer = ethers.Signer = /* signer, (e.g. ethers.Wallet) */; 
const amount: number = /* amount of asset (e.g. USDC, WETH) to deposit */;
// or ethers.BigNumber for v5, bigint for v6

// ERC20 asset (e.g. USDC or WETH)
const assetAddress: string = /* address of asset */;
const assetAbi: ethers.ContractInterface = /* ERC20 abi */;
const asset = new ethers.Contract(assetAddress, assetAbi, signer);

const vaultAddress: string = /* address of vault contract */;
const vaultAbi: ethers.ContractInterface = /* vault abi */;
const vault = new ethers.Contract(vaultAddress, vaultAbi, signer);

// step 1: approve token spending
const approveTx = await asset.approve(vault.address, amount); 
const approveReceipt = await approveTx.wait();

// step 2: calculate minimum shares to be minted
const minimumShares = await vault.previewDeposit(amount);

// step 3: deposit
const depositTx = await vault.safeDeposit(amount, receiver, minimumShares);
const depositReceipt = await depositTx.wait();

Calculations

The vault itself is also an ERC20 token contract, ownership of which represents shares of the underlying asset (e.g. uUSDCv3 to USDC).

vault.balanceOf(address owner) -> uint256(shares)

  • Get the amount of vault shares (e.g. uUSDCv3) owned by owner address

vault.previewRedeem(uint256 shares) -> uint256(assets)

  • Calculates how many underlying assets (e.g. USDC) would be received when burning a corresponding amount of vault shares (e.g. uUSDCv3)

  • This assumes an ideal scenario with zero slippage from e.g. swap and withdrawal fees

Withdrawal

vault.safeRedeem(uint256 shares, address receiver, address owner, uint256 minimumAssets) -> uint256 assetsReceived

Parameter
Type
Description

shares

uint256

burn exact amount of vault shares (e.g. uUSDCv3) in exchange for underlying assets (e.g. USDC)

receiver

address

address which receives the withdrawn underlying assets

owner

address

address whose shares are being burnt. In most cases this will be the same as receiver, but can differ to support more complex smart contract integrations

minimumAssets

uint256

minimum amount of underlying asset (e.g. USDC) to receive, accounting for factors like slippage from swap and withdrawal fees of underlying strategies

vault.safeWithdraw(uint256 assets, address receiver, address owner, uint256 maximumShares) -> uint256 sharesBurnt

Parameter
Type
Description

assets

uint256

withdraw exact amount of underlying assets (e.g. USDC) by burning vault shares (e.g. uUSDCv3)

receiver

address

address which receives the withdrawn underlying assets

owner

address

address whose shares are being burnt. In most cases this will be the same as receiver, but can differ to support more complex smart contract integrations

maximumShares

uint256

maximum amount of vault shares (e.g. uUSDCv3) to burn, accounting for factors like slippage from swap and withdrawal fees of underlying strategies

If owner is not the same as the address making the withdrawal, ERC20 approval needs to be obtained first via vault.approve(address spender, uint256 shares)

Do not calculate minimumAssets or maximumShares on-chain in the same call as this still leaves the transaction open to MEV attacks!

Example Code

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

// variables
Vault vault = Vault(/* vault address */);
address receiver = /* address of receiver */;
address owner = /* address of owner */;

// step 0 (optional): approve another address redeeming your shares
address spender = /* address you authorize to spend your shares */;
uint256 sharesToApprove = /* how much shares the spender can redeem */;
// called by owner
vault.approve(spender, sharesToApprove);

// step 1: get current share balance
uint256 shares = vault.balanceOf(owner);

// step 2: calculate assets to be received
// EXAMPLE ONLY! do not do this on-chain in the same transaction!
uint256 minimumAssets = (vault.previewRedeem(shares) * 995) / 1000; // 0.5% slippage 

// step 2: withdraw
// called by owner OR spender
uint256 assets = vault.safeRedeem(shares, receiver, owner, minimumAssets);
import ethers from 'ethers';

// variables
const receiver: string = /* address of receiver */;
const owner: string = /* address of owner */;
const signer = ethers.Signer = /* signer, this should be owner OR spender */; 

const vaultAddress: string = /* address of vault contract */;
const vaultAbi: ethers.ContractInterface = /* vault abi */;
const vault = new ethers.Contract(vaultAddress, vaultAbi, signer);

// step 0 (optional): approve another address redeeming your shares
const spender: string = /* address you authorize to spend your shares */;
const sharesToApprove: number = /* how much shares the spender can redeem */;
// or ethers.BigNumber for v5, bigint for v6

const approveTx = await vault.approve(spender, sharesToApprove); 
const approveReceipt = await approveTx.wait();

// step 1: get current share balance
const shares = await vault.balanceOf(owner);

// step 2: calculate assets to be received, // 0.5% slippage
const minimumAssets = ((await vault.previewRedeem(shares)) * 995) / 1000; 

// step 3: withdraw
const redeemTx = await vault.safeRedeem(shares, receiver, owner, minimumAssets);
const redeemReceipt = await redeemTx.wait();

Notes

Block Delay

Our Vaults implement a which prevents the same address from making multiple deposit, withdrawal or share transfers within the same block. This is for defense against smart contract exploits like flash loan attacks.

BlockDelay
ERC-4626 Tokenized Vault Standard
OpenZeppelin
following addresses