Overview
Concrete Vaults are ERC-4626-compliant smart contracts that wrap underlying ERC20 assets (for example WBTC, USDC) into vault shares (ctAssets).
Each vault behaves as a yield wrapper: deposits are deployed into yield strategies such as money markets, liquidity provision, or restaking.
From an integration perspective, a user's deposit always results in ERC20 shares (ctAssets) that represent their claim on the vault's underlying and accrued yield.
Usage
Whether you are using vanilla JS/TS, React hooks, or Wagmi integration, you interact with the same underlying vault abstraction. The SDK is built on viem. If you are upgrading from 1.x, see Migrating from 1.x.
The Concrete SDK provides:
- Query vault details: decimals, symbols, total assets, balances.
- Approvals: approve the vault to spend the underlying ERC20.
- Deposits and redemptions: mint and redeem shares.
- Preview conversions: estimate output before sending a transaction.
- Multi-chain support: Ethereum, Arbitrum, Berachain, Katana, Corn, Morph.
| User action | SDK method | Result |
|---|---|---|
| Get vault details | vault.getVaultDetails() | Metadata: symbols, decimals, underlying address |
| Check underlying balance | (await vault.getUnderlyingErc20()).balanceOf(address) | User's underlying token balance |
| Approve deposit | (await vault.getUnderlyingErc20()).approve(vaultAddr, amount) | Allow vault to spend the underlying |
| Preview conversion | vault.previewConversion(amount) | Estimate shares minted or tokens redeemed |
| Deposit | vault.deposit(amount) | Underlying consumed, shares minted |
| Check share balance | vault.balanceOf(address) | User's ctAsset balance |
| Redeem shares | vault.redeem(amount) | Shares burned, underlying returned |
| Read live APY | vault.getApyDetails() | Live and historical APY, TVL, and history series |
| Read the deposit cap | vault.getDepositLimits() | Deposit cap and minimum deposit (V2) |
| Read the withdrawal cooldown | vault.getDepositLockWithFeeHook() | Cooldown period and locked shares (V2) |
| Forecast a withdrawal | vault.getWithdrawalForecast({ account }) | Estimated availability of Withdrawal Queue requests (V2) |
Vault = ERC20 + underlying ERC20
Every vault exposes two ERC20 layers while staying ERC-4626 compliant:
- Vault shares (ctAssets)
- The vault itself extends ERC20.
- You can call
balanceOf,approve,transfer, and other ERC20 methods on the shares.
- Underlying ERC20
- Exposed via
vault.getUnderlyingErc20(). - Full ERC20 instance:
approve,balanceOf,transfer.
- Exposed via
Example flow
A user approves the underlying ERC20, the vault consumes the underlying and mints ctAssets (shares), and the user can redeem the shares later.
Before writing code, you can build a mental model of the flow by trying it directly in the Concrete app:
- Earn page: deposit into a live vault and see shares minted.
- Earn docs: walkthrough of how vault deposits work at the user level.
The SDK expresses the same steps in code:
- Check underlying balance
const erc20 = await vault.getUnderlyingErc20();
const balance = await erc20.balanceOf(user);
console.log("Underlying balance:", balance.toString());
- Approve vault to spend underlying
const erc20 = await vault.getUnderlyingErc20();
await (await erc20.approve(vault.getAddress(), amount)).wait();
- Optionally preview deposit
const decimals = await vault.getUnderlyingDecimals();
const preview = await vault.previewConversion(1n * 10n ** BigInt(decimals));
console.log("Vault tokens (preview):", preview.vaultTokensReceiving);
- Deposit into vault
const tx = await vault.deposit(amount);
await tx.wait();
- Receive ctAssets (vault shares)
const shareBal = await vault.balanceOf(user);
console.log("Vault shares:", shareBal.toString());
- Redeem back into underlying
const redeemTx = await vault.redeem(shareBal);
await redeemTx.wait();