Single-Asset Deposits into Smart Pools
This guide explains how to programmatically deposit a single token into Smart Pools. Unlike traditional deposits that require both tokens in the correct ratio, single-asset deposits allow you to deposit only one token while the contract automatically handles the swap and balance.
Installation
First, install the Steer Protocol SDK:
npm install @steerprotocol/sdk
Client Setup
Initialize the Steer Protocol client:
import { SteerClient, AMMType } from '@steerprotocol/sdk';
import { createPublicClient, createWalletClient, http } from 'viem';
import { polygon } from 'viem/chains';
// Create Viem clients
const publicClient = createPublicClient({
chain: polygon,
transport: http()
});
const walletClient = createWalletClient({
chain: polygon,
transport: http()
});
// Initialize SteerClient
const steerClient = new SteerClient({
environment: 'production', // Use 'testnet' for testing
client: publicClient,
walletClient: walletClient
});
Prerequisites
Before making a single-asset deposit, ensure you have:
- Smart Pools SDK installed and client initialized
- Sufficient balance of the token you want to deposit
- Approved token spending for the Single-Asset Deposit contract
- Pool has adequate liquidity for the internal swap
How Single-Asset Deposits Work
Single-asset deposits simplify the process by:
- Calculating optimal swap amount - Determines how much of your input token to swap
- Applying slippage protection - Sets price limits to prevent excessive slippage
- Simulating the swap - Previews expected outcomes before execution
- Estimating LP tokens - Calculates the final LP tokens to be received
- Executing the deposit - Performs the swap and deposit in a single transaction
Implementation Steps
1. Preview Single-Asset Deposit
Always preview your deposit first to understand the expected outcomes:
// Prepare deposit parameters
const assets = parseEther('100'); // Amount of tokens to deposit
const userAddress = '0x1234567890123456789012345678901234567890';
const vaultAddress = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef';
const poolAddress = '0x9876543210987654321098765432109876543210';
// Preview the single-asset deposit
const preview = await steerClient.vaults.previewSingleAssetDeposit({
assets,
receiver: userAddress as `0x${string}`,
vault: vaultAddress as `0x${string}`,
isToken0: true, // Set to false if depositing token1
depositSlippagePercent: 5n, // 5% max slippage for deposit
swapSlippageBP: 500, // 5% max slippage for internal swap (in basis points)
ammType: AMMType.UniswapV3,
}, poolAddress as `0x${string}`);
if (!preview.success) {
throw new Error(`Preview failed: ${preview.error}`);
}
// Analyze preview results
const {
swapAmount,
currentSqrtPrice,
sqrtPriceLimitX96,
swapSimulation,
lpEstimation,
zeroForOne
} = preview.data;
console.log('Amount to be swapped:', swapAmount);
console.log('Expected LP tokens:', lpEstimation.lpTokens);
console.log('Price impact:', swapSimulation.priceSlippagePercent + '%');
console.log('Final token0 amount:', lpEstimation.finalAmount0);
console.log('Final token1 amount:', lpEstimation.finalAmount1);
2. Validate Price Impact
Check if the price impact is acceptable before proceeding:
const priceImpact = parseFloat(preview.data.swapSimulation.priceSlippagePercent);
if (priceImpact > 5.0) {
console.warn(`High price impact: ${priceImpact}%`);
// Consider reducing deposit amount or waiting for better conditions
throw new Error('Price impact too high');
}
// Check minimum LP tokens expected
const minExpectedLpTokens = parseEther('0.95'); // Your minimum threshold
if (preview.data.lpEstimation.lpTokens < minExpectedLpTokens) {
throw new Error('Expected LP tokens below minimum threshold');
}
3. Execute Single-Asset Deposit
If the preview looks good, execute the deposit:
// Execute the single-asset deposit
const result = await steerClient.vaults.singleAssetDeposit({
assets,
receiver: userAddress as `0x${string}`,
vault: vaultAddress as `0x${string}`,
isToken0: true,
depositSlippagePercent: 5n,
swapSlippageBP: 500,
ammType: AMMType.UniswapV3,
});
if (!result.success) {
throw new Error(`Deposit failed: ${result.error}`);
}
console.log('Transaction hash:', result.data);
// Wait for transaction confirmation
const receipt = await publicClient.waitForTransactionReceipt({
hash: result.data
});
console.log('Transaction confirmed:', receipt.status);
4. Alternative: Prepare Transaction for Custom Execution
For advanced use cases, you can prepare the transaction without executing it:
// Prepare transaction data without executing
const txData = await steerClient.vaults.prepareSingleAssetDepositTx({
assets,
receiver: userAddress as `0x${string}`,
vault: vaultAddress as `0x${string}`,
isToken0: true,
depositSlippagePercent: 5n,
swapSlippageBP: 500,
ammType: AMMType.UniswapV3,
});
if (!txData.success) {
throw new Error(`Failed to prepare transaction: ${txData.error}`);
}
// Use prepared transaction data for custom execution or batching
const {
address, // Contract address
functionName, // Function to call
abi, // Contract ABI
args // Function arguments
} = txData.data;
// Execute with custom parameters
const hash = await walletClient.writeContract({
address,
abi,
functionName,
args,
gas: 500000n, // Custom gas limit
});
Token Approval
Before executing the deposit, ensure the token is approved:
// Check current allowance
const tokenContract = {
address: isToken0 ? pool.token0 : pool.token1,
abi: [{
name: 'allowance',
type: 'function',
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' }
],
outputs: [{ type: 'uint256' }]
}, {
name: 'approve',
type: 'function',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ type: 'bool' }]
}]
};
// Contract address for single-asset deposits (Polygon)
const singleAssetDepositContract = '0x9903C4ED61A71d0d5C322097adfF752d6339F871';
const allowance = await publicClient.readContract({
...tokenContract,
functionName: 'allowance',
args: [userAddress, singleAssetDepositContract]
});
if (allowance < assets) {
// Approve tokens
await walletClient.writeContract({
...tokenContract,
functionName: 'approve',
args: [singleAssetDepositContract, assets]
});
}
Advanced Usage
Using Individual Core Functions
For more control, you can use the individual core functions:
Calculate Swap Amount
import { calculateSwapAmount } from '@steerprotocol/sdk';
const swapResult = await calculateSwapAmount(publicClient, {
depositAmount: assets,
isToken0: true,
vault: vaultAddress as `0x${string}`,
pool: poolAddress as `0x${string}`,
ammType: AMMType.UniswapV3,
singleAssetDepositContract: singleAssetDepositContract as `0x${string}`
});
if (swapResult.success) {
console.log('Optimal swap amount:', swapResult.data.swapAmount);
console.log('Current sqrt price:', swapResult.data.sqrtPrice);
}
Calculate Price Limits
import { calculateLimitPrice } from '@steerprotocol/sdk';
const limitResult = await calculateLimitPrice(publicClient, {
pool: poolAddress as `0x${string}`,
slippageBP: 500, // 5% slippage tolerance
zeroForOne: true, // Swap direction
ammType: AMMType.UniswapV3,
token0: token0Address as `0x${string}`,
token1: token1Address as `0x${string}`
});
if (limitResult.success) {
console.log('Price limit:', limitResult.data);
}
Simulate Swap with Slippage
import { simulateSwapWithSlippage } from '@steerprotocol/sdk';
const simulation = await simulateSwapWithSlippage(publicClient, {
poolAddress: poolAddress as `0x${string}`,
recipient: userAddress as `0x${string}`,
zeroForOne: true,
amountSpecified: swapAmount,
sqrtPriceLimitX96: priceLimit,
ammType: AMMType.UniswapV3,
tokenIn: token0Address as `0x${string}`,
tokenOut: token1Address as `0x${string}`,
fee: 3000
}, chainId);
if (simulation.success) {
console.log('Price slippage:', simulation.data.priceSlippagePercent + '%');
console.log('Current price:', simulation.data.currentPrice);
console.log('Price after swap:', simulation.data.priceAfter);
}
Estimate LP Tokens
import { estimateLpTokens } from '@steerprotocol/sdk';
const estimation = await estimateLpTokens(publicClient, {
vault: vaultAddress as `0x${string}`,
originalAssets: assets,
swapAmount: swapAmount,
swapResult: {
amount0: -swapAmount,
amount1: outputAmount
},
isToken0: true
});
if (estimation.success) {
console.log('Estimated LP tokens:', estimation.data.lpTokens);
console.log('Final amount0:', estimation.data.finalAmount0);
console.log('Final amount1:', estimation.data.finalAmount1);
}
Error Handling
async function handleSingleAssetDeposit() {
try {
// Validate parameters
steerClient.vaults.validateSingleAssetDepositParams({
assets,
receiver: userAddress as `0x${string}`,
vault: vaultAddress as `0x${string}`,
isToken0: true,
depositSlippagePercent: 5n,
swapSlippageBP: 500,
ammType: AMMType.UniswapV3,
});
// Check token balance
const balance = await publicClient.readContract({
address: tokenAddress,
abi: [{
name: 'balanceOf',
type: 'function',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ type: 'uint256' }]
}],
functionName: 'balanceOf',
args: [userAddress]
});
if (balance < assets) {
throw new Error('Insufficient token balance');
}
// Preview deposit
const preview = await steerClient.vaults.previewSingleAssetDeposit(params, poolAddress);
if (!preview.success) {
throw new Error(`Preview failed: ${preview.error}`);
}
// Check price impact
const priceImpact = parseFloat(preview.data.swapSimulation.priceSlippagePercent);
if (priceImpact > 10.0) {
throw new Error(`Price impact too high: ${priceImpact}%`);
}
// Execute deposit
const result = await steerClient.vaults.singleAssetDeposit(params);
if (!result.success) {
throw new Error(`Deposit failed: ${result.error}`);
}
return result.data; // Transaction hash
} catch (error) {
console.error('Single-asset deposit failed:', error);
// Handle specific error types
if (error.message.includes('insufficient liquidity')) {
console.error('Pool lacks sufficient liquidity for this deposit size');
} else if (error.message.includes('price impact')) {
console.error('Swap would cause excessive price movement');
} else if (error.message.includes('Invalid vault address')) {
console.error('Vault address is invalid or vault is inactive');
}
throw error;
}
}
Important Notes
Preview First
- Always preview deposits before execution
- Check price impact and expected LP tokens
- Validate that outcomes meet your expectations
Slippage Settings
depositSlippagePercent
: Protection for the final deposit (usually 1-5%)swapSlippageBP
: Protection for the internal swap (usually 1-5%)- Higher slippage = higher chance of success but worse execution
Price Impact
- Monitor price impact percentage from preview
- Large deposits may significantly impact pool price
- Consider splitting large deposits across multiple transactions
Gas Optimization
- Single transaction combines swap and deposit
- More gas-efficient than manual swap + deposit
- Consider network congestion when setting gas limits
Supported Networks
- Polygon: Fully supported with deployed contracts
- Ethereum, Arbitrum: Coming soon
Best Practices
Pre-deposit Validation
- Verify token balance and allowance
- Check vault status and pool liquidity
- Validate all addresses and parameters
Risk Management
- Use conservative slippage settings
- Monitor price impact closely
- Set minimum LP token thresholds
Transaction Monitoring
- Wait for transaction confirmation
- Handle potential MEV and front-running
- Implement proper retry logic for failed transactions
User Experience
- Show preview results to users before execution
- Display expected outcomes and risks
- Provide clear error messages
Supported AMM Types
Currently supported:
enum AMMType {
UniswapV3 = 0, // Fully supported
Algebra = 1, // Planned
AlgebraDirectional = 2, // Planned
AlgebraVE33 = 3, // Planned
AlgebraIntegral = 4 // Planned
}
Next Steps
- Learn about Regular Deposits for balanced token deposits
- Explore Withdrawing from Pools for exit strategies
- Check Best Practices for development guidelines