Deposit & Redeem
Intuition interprets the state of ‘who is saying what about what’ as the positions that users hold on Vaults. Each Atom has one respective vault, and each Triple has two respective vaults—one for the affirmative/positive of the statement of the Triple (representing ‘true’), and one for the negative of the Triple (representing ‘false’).
For example, if User A holds one share of the affirmative/positive vault of the Triple [Billy][is] [Trustworthy], User A may be interpreted as saying ‘I believe [Billy][is] [Trustworthy] is TRUE, with 1 share of conviction.
Deposits and Redeems
The Intuition protocol supports two distinct economic models for deposits and redeems: bonding curve mechanics and pro-rata mechanics. Most applications should use the bonding curve methods, which provide dynamic pricing based on supply and demand, creating compelling economic games around the data being created. The pro-rata methods use a simple proportional allocation model and is extremely conservative, useful for scenarios in which a developer solely wants to use Intuition for the functionality provided, rather than for offering their users exposure to the economic games possible with Intuition.
You can deposit into and redeem from both atoms and triples. Each type has its own dedicated methods:
- Atom methods:
depositAtom
,depositAtomCurve
,redeemAtom
,redeemAtomCurve
- Triple methods:
depositTriple
,depositTripleCurve
,redeemTriple
,redeemTripleCurve
The preview and vault state functions work for both atoms and triples using a generic id
parameter.
Bonding Curve Deposits and Redeems (Recommended)
Bonding curve mechanics use mathematical curves to determine the price of shares based on current supply. As more people deposit into an atom's vault, the share price increases along the curve. This creates natural price discovery and allows early depositors to benefit from later interest.
The protocol supports multiple bonding curve types, identified by a curveId
. All bonding curve methods end with the word "Curve" in their function names.
Understanding Curve Configuration
Before making deposits or redeems, you can retrieve the bonding curve configuration. It's suggested to pick a curve ID to use across your entire application, to simplify and streamline the experience for end users. If you want to display vertices of the curve itself in your application, you may wish to read values from the curve directly via RPC calls, but that is beyond the scope of this document. Most users will be more interested in price history, which is available via the GraphQL API. The most balanced curve is the Offset Progressive Curve 3,5e35, which is Curve ID 4. Curve IDs never change once they are added to the registry, so it's safe to define a hard-coded Curve ID as a const variable in your application.
// Get the bonding curve registry address and default curve ID
const [registryAddress, defaultCurveId] = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'bondingCurveConfig',
})
// Get information about a specific curve
const curveName = await publicClient.readContract({
address: registryAddress,
abi: BONDING_CURVE_REGISTRY_ABI,
functionName: 'getCurveName',
args: [curveId],
})
console.log(`Using curve: ${curveName} (ID: ${curveId})`)
Step 1: Get Current Vault State
Before making deposits or redeems, you'll want to understand the current state of the vault. The share price is always scaled by 1e18. Since this is just a read
, it can be done anywhere in your stack -- front end or back end.
const curveId = 4 // Most applications use curve ID 4
// Get vault totals (total assets and shares for the curve)
const [totalAssets, totalShares] = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'getCurveVaultState',
args: [atomId, curveId],
})
// Get user's current position in the vault
const [userShares, userAssets] = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'getVaultStateForUserCurve',
args: [atomId, curveId, userAddress as `0x${string}`],
})
// Get current share price
const sharePrice = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'currentSharePriceCurve',
args: [atomId, curveId],
})
console.log(`Vault has ${formatEther(totalAssets)} ETH and ${totalShares} shares`)
console.log(`User has ${userShares} shares worth ${formatEther(userAssets)} ETH`)
console.log(`Current share price: ${sharePrice} wei per share`)
Step 2: Preview Deposit or Redeem
Before executing a transaction, you can preview the expected outcome. The preview amount is based on the current state of the specified vault (atomId
+ curveId
), regardless of any particular users position. This can also be done anywhere in the stack.
// Preview a deposit: How many shares will I get for X ETH?
// Works for both atoms and triples - just use the appropriate ID
const ethAmount = parseEther('0.1') // 0.1 ETH
// For atoms:
const expectedAtomShares = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'previewDepositCurve',
args: [ethAmount, atomId, curveId],
})
// For triples:
const expectedTripleShares = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'previewDepositCurve',
args: [ethAmount, tripleId, curveId],
})
console.log(`Depositing ${formatEther(ethAmount)} ETH into atom will get you ${expectedAtomShares} shares`)
console.log(`Depositing ${formatEther(ethAmount)} ETH into triple will get you ${expectedTripleShares} shares`)
// Preview a redeem: How much ETH will I get for X shares?
// Works for both atoms and triples - just use the appropriate ID
const sharesToRedeem = 1000n // 1000 shares
// For atoms:
const expectedAtomEth = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'previewRedeemCurve',
args: [sharesToRedeem, atomId, curveId],
})
// For triples:
const expectedTripleEth = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'previewRedeemCurve',
args: [sharesToRedeem, tripleId, curveId],
})
console.log(`Redeeming ${sharesToRedeem} shares from atom will get you ${formatEther(expectedAtomEth)} ETH`)
console.log(`Redeeming ${sharesToRedeem} shares from triple will get you ${formatEther(expectedTripleEth)} ETH`)
Step 3: Execute Deposit
Deposits (as well as Redeems) allow you to specify a receiver
address. The receiver
is the wallet (or contract) that you actually want to receive the shares. Depositing (or redeeming) on behalf of another user requires their approval, which is explained later in this document. For now we will assume the sender
and recipient
are the same address.
Backend/Server Method:
There are a few reasons why you might want to do this:
- You may have designed an application which deposits or redeems on behalf of users, mitigating the need for them to confirm every transaction with their Ethereum wallet.
- You may wish to purchase a small amount of shares in Atoms or Triples -- especially if you expect them to be popular with users. Doing so will give you an early (low) share price, which you could potentially redeem at a later time once the share price increases from user activity.
- You may want to increase liquidity or share value in a given Atom or Triple for some other reason.
import { createPublicClient, createWalletClient, http, parseEther } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { config } from '../config.server'
import { MULTIVAULT_ABI } from '../lib/contracts'
const curveId = 4 // Most applications use curve ID 4
// For depositing into atoms
async function depositAtomCurve(atomId: bigint, ethAmount: bigint, receiverAddress: string) {
const account = privateKeyToAccount(config.privateKey as `0x${string}`)
const publicClient = createPublicClient({ chain: config.chain, transport: http() })
const walletClient = createWalletClient({ account, chain: config.chain, transport: http() })
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'depositAtomCurve',
args: [receiverAddress as `0x${string}`, atomId, curveId],
value: ethAmount,
})
return await walletClient.writeContract(request)
}
// For depositing into triples
async function depositTripleCurve(tripleId: bigint, ethAmount: bigint, receiverAddress: string) {
const account = privateKeyToAccount(config.privateKey as `0x${string}`)
const publicClient = createPublicClient({ chain: config.chain, transport: http() })
const walletClient = createWalletClient({ account, chain: config.chain, transport: http() })
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'depositTripleCurve',
args: [receiverAddress as `0x${string}`, tripleId, curveId],
value: ethAmount,
})
return await walletClient.writeContract(request)
}
// Usage examples
const atomId = 12345n
const tripleId = 67890n
const depositAmount = parseEther('0.1') // 0.1 ETH
const receiverAddress = '0x742d35Cc6634C0532925a3b8D4a02b1E34E6E5f8'
const atomHash = await depositAtomCurve(atomId, depositAmount, receiverAddress)
console.log('Atom deposit transaction:', atomHash)
const tripleHash = await depositTripleCurve(tripleId, depositAmount, receiverAddress)
console.log('Triple deposit transaction:', tripleHash)
Frontend/Wallet Method:
The reasons for implementing this are far more straightforward than doing it in the back-end. This is how you enable users to stake on atoms and triples that already exist.
import { encodeFunctionData, parseEther } from 'viem'
import { useDynamicContext } from '@dynamic-labs/sdk-react-core'
import { isEthereumWallet } from '@dynamic-labs/ethereum'
import { chainConfig } from '~/config.client'
import { MULTIVAULT_ABI } from '~/lib/contracts'
const curveId = 4 // Most applications use curve ID 4
// For depositing into atoms
async function depositAtomCurve(atomId: bigint, ethAmount: string) {
const { primaryWallet } = useDynamicContext()
if (!primaryWallet || !isEthereumWallet(primaryWallet)) return
const walletClient = await primaryWallet.getWalletClient()
const fnData = encodeFunctionData({
abi: MULTIVAULT_ABI,
functionName: 'depositAtomCurve',
args: [walletClient.account.address, atomId, curveId],
})
const txData = {
to: chainConfig.multivaultAddress,
data: fnData,
value: parseEther(ethAmount),
}
return await walletClient.sendTransaction(txData)
}
// For depositing into triples
async function depositTripleCurve(tripleId: bigint, ethAmount: string) {
const { primaryWallet } = useDynamicContext()
if (!primaryWallet || !isEthereumWallet(primaryWallet)) return
const walletClient = await primaryWallet.getWalletClient()
const fnData = encodeFunctionData({
abi: MULTIVAULT_ABI,
functionName: 'depositTripleCurve',
args: [walletClient.account.address, tripleId, curveId],
})
const txData = {
to: chainConfig.multivaultAddress,
data: fnData,
value: parseEther(ethAmount),
}
return await walletClient.sendTransaction(txData)
}
// Usage examples
const atomHash = await depositAtomCurve(12345n, '0.1') // Deposit 0.1 ETH to atom 12345
const tripleHash = await depositTripleCurve(67890n, '0.1') // Deposit 0.1 ETH to triple 67890
Step 4: Execute Redeem
Redeems work the same way as deposits, but in reverse. Your application may want to redeem on behalf of a user, or it may want to redeem its own shares in an Atom or Triple once a share price threshold is exceeded, or something else. Your imagination is the only limitation!
Backend/Server Method:
// For redeeming from atoms
async function redeemAtomCurve(atomId: bigint, shareAmount: bigint, receiverAddress: string) {
const account = privateKeyToAccount(config.privateKey as `0x${string}`)
const publicClient = createPublicClient({ chain: config.chain, transport: http() })
const walletClient = createWalletClient({ account, chain: config.chain, transport: http() })
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'redeemAtomCurve',
args: [shareAmount, receiverAddress as `0x${string}`, atomId, curveId],
value: BigInt(0), // No ETH sent for redeems
})
return await walletClient.writeContract(request)
}
// For redeeming from triples
async function redeemTripleCurve(tripleId: bigint, shareAmount: bigint, receiverAddress: string) {
const account = privateKeyToAccount(config.privateKey as `0x${string}`)
const publicClient = createPublicClient({ chain: config.chain, transport: http() })
const walletClient = createWalletClient({ account, chain: config.chain, transport: http() })
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'redeemTripleCurve',
args: [shareAmount, receiverAddress as `0x${string}`, tripleId, curveId],
value: BigInt(0), // No ETH sent for redeems
})
return await walletClient.writeContract(request)
}
// Usage examples
const sharesToRedeem = 1000n // Redeem 1000 shares
const atomHash = await redeemAtomCurve(atomId, sharesToRedeem, receiverAddress)
console.log('Atom redeem transaction:', atomHash)
const tripleHash = await redeemTripleCurve(tripleId, sharesToRedeem, receiverAddress)
console.log('Triple redeem transaction:', tripleHash)
Frontend/Wallet Method:
This is how you enable users to redeem their shares in an Atom.
// For redeeming from atoms
async function redeemAtomCurve(atomId: bigint, shareAmount: bigint) {
const { primaryWallet } = useDynamicContext()
if (!primaryWallet || !isEthereumWallet(primaryWallet)) return
const walletClient = await primaryWallet.getWalletClient()
const fnData = encodeFunctionData({
abi: MULTIVAULT_ABI,
functionName: 'redeemAtomCurve',
args: [shareAmount, walletClient.account.address, atomId, curveId],
})
const txData = {
to: chainConfig.multivaultAddress,
data: fnData,
value: BigInt(0), // No ETH for redeems
}
return await walletClient.sendTransaction(txData)
}
// For redeeming from triples
async function redeemTripleCurve(tripleId: bigint, shareAmount: bigint) {
const { primaryWallet } = useDynamicContext()
if (!primaryWallet || !isEthereumWallet(primaryWallet)) return
const walletClient = await primaryWallet.getWalletClient()
const fnData = encodeFunctionData({
abi: MULTIVAULT_ABI,
functionName: 'redeemTripleCurve',
args: [shareAmount, walletClient.account.address, tripleId, curveId],
})
const txData = {
to: chainConfig.multivaultAddress,
data: fnData,
value: BigInt(0), // No ETH for redeems
}
return await walletClient.sendTransaction(txData)
}
// Usage examples
const atomHash = await redeemAtomCurve(12345n, 1000n) // Redeem 1000 shares from atom 12345
const tripleHash = await redeemTripleCurve(67890n, 1000n) // Redeem 1000 shares from triple 67890
Pro-Rata Deposits and Redeems (Alternative)
Pro-rata mechanics use a simple proportional model where share price is calculated as totalAssets / totalShares
. This provides predictable pricing but lacks the dynamic price discovery of bonding curves.
Pro-rata methods use the same function names as bonding curve methods but without the "Curve" suffix. Most applications should use bonding curves, but pro-rata methods are available for specific use cases where predictable linear pricing is preferred.
Example Pro-Rata Usage
// Pro-rata deposit into atoms (linear pricing)
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'depositAtom', // Note: no "Curve" suffix
args: [receiverAddress as `0x${string}`, atomId], // Note: no curveId parameter
value: parseEther('0.1'),
})
// Pro-rata deposit into triples (linear pricing)
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'depositTriple', // Note: no "Curve" suffix
args: [receiverAddress as `0x${string}`, tripleId], // Note: no curveId parameter
value: parseEther('0.1'),
})
// Pro-rata redeem from atoms (linear pricing)
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'redeemAtom', // Note: no "Curve" suffix
args: [shareAmount, receiverAddress as `0x${string}`, atomId], // Note: no curveId parameter
value: BigInt(0),
})
// Pro-rata redeem from triples (linear pricing)
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'redeemTriple', // Note: no "Curve" suffix
args: [shareAmount, receiverAddress as `0x${string}`, tripleId], // Note: no curveId parameter
value: BigInt(0),
})
// Pro-rata preview functions (work for both atoms and triples)
const atomShares = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'previewDeposit', // Note: no "Curve" suffix
args: [ethAmount, atomId], // Note: no curveId parameter
})
const tripleShares = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'previewDeposit', // Note: no "Curve" suffix
args: [ethAmount, tripleId], // Note: no curveId parameter
})
const atomAssets = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'previewRedeem', // Note: no "Curve" suffix
args: [shareAmount, atomId], // Note: no curveId parameter
})
const tripleAssets = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'previewRedeem', // Note: no "Curve" suffix
args: [shareAmount, tripleId], // Note: no curveId parameter
})
The key differences for pro-rata methods are:
- Function names do not end with "Curve"
- No
curveId
parameter is required - Pricing follows a linear
totalAssets / totalShares
formula - No dynamic price discovery based on supply and demand
Batch Deposits and Redeems
The Intuition protocol provides efficient batch operations for depositing into and redeeming from multiple vaults in a single transaction. Unlike the individual deposit/redeem methods, the batch operations are generic and work with both atoms and triples using their term IDs.
Important Note: V2 of the contracts will significantly simplify the API by merging the separate atom/triple and curve/pro-rata pathways into unified methods. The current implementation maintains separate functions for backward compatibility with audited code.
Examples Note: We are only showing Frontend/Wallet examples here, because doing this in the back-end is nearly identical -- it just uses a back-end wallet instead, like the many back-end examples which are already given for other smart contract interactions. We wanted the examples to be easier to read.
Batch Deposits
Batch deposits allow you to deposit ETH into multiple vaults (atoms, triples, or a mix) in a single transaction. There are two variants:
batchDeposit
- Pro-Rata Deposits
Deposits into multiple vaults using the linear pro-rata pricing model:
// Frontend/Wallet example
async function batchDepositMultipleTerms(termIds: bigint[], amounts: string[]) {
const totalValue = amounts.reduce((sum, amount) => sum + parseEther(amount), 0n)
const fnData = encodeFunctionData({
abi: MULTIVAULT_ABI,
functionName: 'batchDeposit',
args: [walletClient.account.address, termIds, amounts.map((a) => parseEther(a))],
})
const txData = {
to: chainConfig.multivaultAddress,
data: fnData,
value: totalValue,
}
return await walletClient.sendTransaction(txData)
}
// Showing one Backend/Server example here for brevity
async function batchDepositCurveMultipleTerms(
termIds: bigint[],
curveIds: bigint[],
amounts: bigint[],
receiverAddress: string
) {
const account = privateKeyToAccount(config.privateKey as `0x${string}`)
const publicClient = createPublicClient({ chain: config.chain, transport: http() })
const walletClient = createWalletClient({ account, chain: config.chain, transport: http() })
const totalValue = amounts.reduce((sum, amount) => sum + amount, 0n)
const { request } = await publicClient.simulateContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'batchDepositCurve',
args: [receiverAddress as `0x${string}`, termIds, curveIds, amounts],
value: totalValue,
})
return await walletClient.writeContract(request)
}
batchDepositCurve
- Bonding Curve Deposits
Deposits into multiple bonding curve vaults in a single transaction:
// Frontend/Wallet example
async function batchDepositCurveMultipleTerms(termIds: bigint[], curveIds: bigint[], amounts: string[]) {
const totalValue = amounts.reduce((sum, amount) => sum + parseEther(amount), 0n)
const fnData = encodeFunctionData({
abi: MULTIVAULT_ABI,
functionName: 'batchDepositCurve',
args: [walletClient.account.address, termIds, curveIds, amounts.map((a) => parseEther(a))],
})
const txData = {
to: chainConfig.multivaultAddress,
data: fnData,
value: totalValue,
}
return await walletClient.sendTransaction(txData)
}
Batch Redeems
Batch redeems allow you to redeem shares from multiple vaults in a single transaction. They use a percentage-based approach, redeeming a specified percentage of your holdings in each vault. The percentage is in basis points, meaning 10000 = 100.00%, 7500 = 75.00%, 101 = 1.01%, etc.
batchRedeem
- Pro-Rata Redeems
Redeems from multiple pro-rata vaults using percentage of holdings:
// Frontend/Wallet example
async function batchRedeemMultipleTerms(percentage: number, termIds: bigint[]) {
const { primaryWallet } = useDynamicContext()
if (!primaryWallet || !isEthereumWallet(primaryWallet)) return
const walletClient = await primaryWallet.getWalletClient()
// Convert percentage to basis points (100% = 10000, 50% = 5000, etc.)
const percentageBps = BigInt(percentage * 100)
const fnData = encodeFunctionData({
abi: MULTIVAULT_ABI,
functionName: 'batchRedeem',
args: [percentageBps, walletClient.account.address, termIds],
})
const txData = {
to: chainConfig.multivaultAddress,
data: fnData,
value: 0n,
}
return await walletClient.sendTransaction(txData)
}
batchRedeemCurve
- Bonding Curve Redeems
Redeems from multiple bonding curve vaults using percentage of holdings, in basis points:
// Frontend/Wallet example
async function batchRedeemCurveMultipleTerms(percentage: number, termIds: bigint[], curveIds: bigint[]) {
const { primaryWallet } = useDynamicContext()
if (!primaryWallet || !isEthereumWallet(primaryWallet)) return
const walletClient = await primaryWallet.getWalletClient()
const percentageBps = BigInt(percentage * 100)
const fnData = encodeFunctionData({
abi: MULTIVAULT_ABI,
functionName: 'batchRedeemCurve',
args: [percentageBps, walletClient.account.address, termIds, curveIds],
})
const txData = {
to: chainConfig.multivaultAddress,
data: fnData,
value: 0n,
}
return await walletClient.sendTransaction(txData)
}
Key Benefits of Batch Operations
- Gas Efficiency: Process multiple operations in a single transaction
- Atomic Execution: All operations succeed or fail together
- Simplified UX: One transaction instead of multiple confirmations
- Flexible: Mix atoms and triples in the same batch
- Percentage-Based Redeems: Easy portfolio rebalancing with percentage withdrawals
Important Notes
- Array Lengths: For curve operations,
termIds
andcurveIds
arrays must be the same length - Minimum Deposits: Each individual deposit amount must meet the minimum deposit requirement
- Percentage Format: Percentages are in basis points (10000 = 100%, 5000 = 50%, 2500 = 25%)
- Generic Term IDs: Batch operations work with any term ID (atoms, triples, or counter-triples)
- V2 Simplification: Future contract versions will merge these pathways for a cleaner API
- Examples: Examples were provided for front end code, but look nearly identical on the back-end, like other back-end examples in this document. All smart contract interactions can be done in either the front-end or the back-end, depending on who you want to be executing the transaction.
See also:
- [Problem Internal Link]
- [Problem Internal Link]
- [Problem Internal Link]