Skip to main content

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:

  1. Smart Pools SDK installed and client initialized
  2. Sufficient balance of the token you want to deposit
  3. Approved token spending for the Single-Asset Deposit contract
  4. Pool has adequate liquidity for the internal swap

How Single-Asset Deposits Work

Single-asset deposits simplify the process by:

  1. Calculating optimal swap amount - Determines how much of your input token to swap
  2. Applying slippage protection - Sets price limits to prevent excessive slippage
  3. Simulating the swap - Previews expected outcomes before execution
  4. Estimating LP tokens - Calculates the final LP tokens to be received
  5. 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

  1. Preview First

    • Always preview deposits before execution
    • Check price impact and expected LP tokens
    • Validate that outcomes meet your expectations
  2. 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
  3. Price Impact

    • Monitor price impact percentage from preview
    • Large deposits may significantly impact pool price
    • Consider splitting large deposits across multiple transactions
  4. Gas Optimization

    • Single transaction combines swap and deposit
    • More gas-efficient than manual swap + deposit
    • Consider network congestion when setting gas limits
  5. Supported Networks

    • Polygon: Fully supported with deployed contracts
    • Ethereum, Arbitrum: Coming soon

Best Practices

  1. Pre-deposit Validation

    • Verify token balance and allowance
    • Check vault status and pool liquidity
    • Validate all addresses and parameters
  2. Risk Management

    • Use conservative slippage settings
    • Monitor price impact closely
    • Set minimum LP token thresholds
  3. Transaction Monitoring

    • Wait for transaction confirmation
    • Handle potential MEV and front-running
    • Implement proper retry logic for failed transactions
  4. 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