Enhance SpotBot slippage handling and logging
- Increased slippage tolerance from 0.6% to 0.7% to account for gas reserves. - Improved logging to provide detailed information when adjusting position quantities due to slippage or when retaining original quantities. - Updated CloseSpotPositionCommandHandler to use the position's opened quantity instead of the entire wallet balance, ensuring gas fees are preserved. - Adjusted Web3ProxyService settings for retry attempts and operation timeouts to improve performance. - Enhanced swap token implementation to handle native tokens correctly and increased operation timeout for better reliability.
This commit is contained in:
@@ -287,7 +287,7 @@ public class SpotBot : TradingBotBase
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tolerance = positionQuantity * 0.006m; // 0.6% tolerance for slippage
|
var tolerance = positionQuantity * 0.007m; // 0.7% tolerance for slippage and gas reserve
|
||||||
var difference = Math.Abs(tokenBalance - positionQuantity);
|
var difference = Math.Abs(tokenBalance - positionQuantity);
|
||||||
|
|
||||||
if (difference > tolerance)
|
if (difference > tolerance)
|
||||||
@@ -307,8 +307,25 @@ public class SpotBot : TradingBotBase
|
|||||||
lastPosition.Status = PositionStatus.Filled;
|
lastPosition.Status = PositionStatus.Filled;
|
||||||
lastPosition.Open.SetStatus(TradeStatus.Filled);
|
lastPosition.Open.SetStatus(TradeStatus.Filled);
|
||||||
|
|
||||||
// Update quantity to match actual token balance
|
// Only update quantity if actual balance is less than position quantity (slippage loss)
|
||||||
lastPosition.Open.Quantity = tokenBalance;
|
// Don't update if balance is higher (likely includes gas reserve for ETH)
|
||||||
|
if (tokenBalance < positionQuantity)
|
||||||
|
{
|
||||||
|
await LogInformationAsync(
|
||||||
|
$"📉 Adjusting Position Quantity Due to Slippage\n" +
|
||||||
|
$"Original Quantity: `{positionQuantity:F5}`\n" +
|
||||||
|
$"Actual Balance: `{tokenBalance:F5}`\n" +
|
||||||
|
$"Difference: `{difference:F5}`");
|
||||||
|
lastPosition.Open.Quantity = tokenBalance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await LogInformationAsync(
|
||||||
|
$"ℹ️ Keeping Original Position Quantity\n" +
|
||||||
|
$"Position Quantity: `{positionQuantity:F5}`\n" +
|
||||||
|
$"Actual Balance: `{tokenBalance:F5}`\n" +
|
||||||
|
$"Not updating (balance includes gas reserve or is within tolerance)");
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate current PnL
|
// Calculate current PnL
|
||||||
var currentPrice = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(
|
var currentPrice = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(
|
||||||
@@ -402,7 +419,7 @@ public class SpotBot : TradingBotBase
|
|||||||
// If balance is greater, it could be orphaned tokens from previous positions
|
// If balance is greater, it could be orphaned tokens from previous positions
|
||||||
if (tokenBalanceAmount < positionQuantity)
|
if (tokenBalanceAmount < positionQuantity)
|
||||||
{
|
{
|
||||||
var tolerance = positionQuantity * 0.006m; // 0.6% tolerance to account for slippage
|
var tolerance = positionQuantity * 0.007m; // 0.7% tolerance to account for slippage and gas reserve
|
||||||
var difference = positionQuantity - tokenBalanceAmount;
|
var difference = positionQuantity - tokenBalanceAmount;
|
||||||
|
|
||||||
if (difference > tolerance)
|
if (difference > tolerance)
|
||||||
@@ -413,7 +430,7 @@ public class SpotBot : TradingBotBase
|
|||||||
$"Position Quantity: `{positionQuantity:F5}`\n" +
|
$"Position Quantity: `{positionQuantity:F5}`\n" +
|
||||||
$"Token Balance: `{tokenBalanceAmount:F5}`\n" +
|
$"Token Balance: `{tokenBalanceAmount:F5}`\n" +
|
||||||
$"Difference: `{difference:F5}`\n" +
|
$"Difference: `{difference:F5}`\n" +
|
||||||
$"Tolerance (0.6%): `{tolerance:F5}`\n" +
|
$"Tolerance (0.7%): `{tolerance:F5}`\n" +
|
||||||
$"Token balance is significantly lower than expected\n" +
|
$"Token balance is significantly lower than expected\n" +
|
||||||
$"Skipping position synchronization");
|
$"Skipping position synchronization");
|
||||||
return; // Skip processing if balance is too low
|
return; // Skip processing if balance is too low
|
||||||
@@ -446,23 +463,35 @@ public class SpotBot : TradingBotBase
|
|||||||
internalPosition.Open.SetStatus(TradeStatus.Filled);
|
internalPosition.Open.SetStatus(TradeStatus.Filled);
|
||||||
positionForSignal.Open.SetStatus(TradeStatus.Filled);
|
positionForSignal.Open.SetStatus(TradeStatus.Filled);
|
||||||
|
|
||||||
// Update quantity to match actual token balance
|
// Update quantity to match actual token balance only if difference is within acceptable tolerance
|
||||||
|
// For ETH, we need to be careful not to include gas reserve in the position quantity
|
||||||
var actualTokenBalance = tokenBalance.Amount;
|
var actualTokenBalance = tokenBalance.Amount;
|
||||||
var quantityTolerance = internalPosition.Open.Quantity * 0.006m; // 0.6% tolerance for slippage
|
var quantityTolerance = internalPosition.Open.Quantity * 0.007m; // 0.7% tolerance for slippage and gas reserve
|
||||||
var quantityDifference = Math.Abs(internalPosition.Open.Quantity - actualTokenBalance);
|
var quantityDifference = Math.Abs(internalPosition.Open.Quantity - actualTokenBalance);
|
||||||
|
|
||||||
if (quantityDifference > quantityTolerance)
|
// Only update if the actual balance is LESS than expected (slippage loss)
|
||||||
|
// Don't update if balance is higher (likely includes gas reserve or orphaned tokens)
|
||||||
|
if (actualTokenBalance < internalPosition.Open.Quantity && quantityDifference > quantityTolerance)
|
||||||
{
|
{
|
||||||
await LogDebugAsync(
|
await LogDebugAsync(
|
||||||
$"🔄 Token Balance Mismatch\n" +
|
$"🔄 Token Balance Lower Than Expected (Slippage)\n" +
|
||||||
$"Internal Quantity: `{internalPosition.Open.Quantity:F5}`\n" +
|
$"Internal Quantity: `{internalPosition.Open.Quantity:F5}`\n" +
|
||||||
$"Broker Balance: `{actualTokenBalance:F5}`\n" +
|
$"Broker Balance: `{actualTokenBalance:F5}`\n" +
|
||||||
$"Difference: `{quantityDifference:F5}`\n" +
|
$"Difference: `{quantityDifference:F5}`\n" +
|
||||||
$"Tolerance (0.6%): `{quantityTolerance:F5}`\n" +
|
$"Tolerance (0.7%): `{quantityTolerance:F5}`\n" +
|
||||||
$"Updating to match broker balance");
|
$"Updating to match actual balance");
|
||||||
internalPosition.Open.Quantity = actualTokenBalance;
|
internalPosition.Open.Quantity = actualTokenBalance;
|
||||||
positionForSignal.Open.Quantity = actualTokenBalance;
|
positionForSignal.Open.Quantity = actualTokenBalance;
|
||||||
}
|
}
|
||||||
|
else if (actualTokenBalance > internalPosition.Open.Quantity)
|
||||||
|
{
|
||||||
|
await LogDebugAsync(
|
||||||
|
$"ℹ️ Token Balance Higher Than Position Quantity\n" +
|
||||||
|
$"Internal Quantity: `{internalPosition.Open.Quantity:F5}`\n" +
|
||||||
|
$"Broker Balance: `{actualTokenBalance:F5}`\n" +
|
||||||
|
$"Difference: `{quantityDifference:F5}`\n" +
|
||||||
|
$"Keeping original position quantity (extra balance likely gas reserve or orphaned tokens)");
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate and update PnL based on current price
|
// Calculate and update PnL based on current price
|
||||||
var currentPrice = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(
|
var currentPrice = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(
|
||||||
|
|||||||
@@ -48,18 +48,13 @@ public class CloseSpotPositionCommandHandler(
|
|||||||
{
|
{
|
||||||
// For live trading, call SwapGmxTokensAsync
|
// For live trading, call SwapGmxTokensAsync
|
||||||
var account = await accountService.GetAccountById(request.AccountId);
|
var account = await accountService.GetAccountById(request.AccountId);
|
||||||
var tokenBalance = await exchangeService.GetBalance(account, request.Position.Ticker);
|
|
||||||
|
|
||||||
if (tokenBalance == null || tokenBalance.Amount <= 0)
|
// Use the position's opened quantity instead of the entire wallet balance
|
||||||
{
|
// This naturally leaves extra ETH for gas fees and avoids the need for explicit reservation
|
||||||
throw new InvalidOperationException(
|
amountToSwap = request.Position.Open.Quantity;
|
||||||
$"No available balance to close spot position for {request.Position.Ticker}");
|
|
||||||
}
|
|
||||||
|
|
||||||
amountToSwap = tokenBalance.Amount;
|
|
||||||
|
|
||||||
logger?.LogInformation(
|
logger?.LogInformation(
|
||||||
"Closing spot position: PositionId={PositionId}, Ticker={Ticker}, TokenBalance={TokenBalance}, Swapping to USDC",
|
"Closing spot position: PositionId={PositionId}, Ticker={Ticker}, PositionQuantity={PositionQuantity}, Swapping to USDC",
|
||||||
request.Position.Identifier, request.Position.Ticker, amountToSwap);
|
request.Position.Identifier, request.Position.Ticker, amountToSwap);
|
||||||
|
|
||||||
swapResult = await tradingService.SwapGmxTokensAsync(
|
swapResult = await tradingService.SwapGmxTokensAsync(
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Managing.Infrastructure.Evm.Services
|
|||||||
public class Web3ProxySettings
|
public class Web3ProxySettings
|
||||||
{
|
{
|
||||||
public string BaseUrl { get; set; } = "http://localhost:3000";
|
public string BaseUrl { get; set; } = "http://localhost:3000";
|
||||||
public int MaxRetryAttempts { get; set; } = 2;
|
public int MaxRetryAttempts { get; set; } = 1;
|
||||||
public int RetryDelayMs { get; set; } = 2500;
|
public int RetryDelayMs { get; set; } = 2500;
|
||||||
public int TimeoutSeconds { get; set; } = 30;
|
public int TimeoutSeconds { get; set; } = 30;
|
||||||
}
|
}
|
||||||
@@ -633,7 +633,7 @@ namespace Managing.Infrastructure.Evm.Services
|
|||||||
pageSize,
|
pageSize,
|
||||||
ticker,
|
ticker,
|
||||||
fromDateTime = fromDate?.ToString("O"), // ISO 8601 format
|
fromDateTime = fromDate?.ToString("O"), // ISO 8601 format
|
||||||
toDateTime = toDate?.ToString("O") // ISO 8601 format
|
toDateTime = toDate?.ToString("O") // ISO 8601 format
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await GetGmxServiceAsync<GetGmxPositionHistoryResponse>("/position-history", payload);
|
var response = await GetGmxServiceAsync<GetGmxPositionHistoryResponse>("/position-history", payload);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {z} from 'zod'
|
|||||||
import {GmxSdk} from '../../generated/gmxsdk/index.js'
|
import {GmxSdk} from '../../generated/gmxsdk/index.js'
|
||||||
|
|
||||||
import {arbitrum} from 'viem/chains';
|
import {arbitrum} from 'viem/chains';
|
||||||
import {getTokenBySymbol} from '../../generated/gmxsdk/configs/tokens.js';
|
import {getTokenBySymbol, NATIVE_TOKEN_ADDRESS} from '../../generated/gmxsdk/configs/tokens.js';
|
||||||
import {createSwapOrderTxn} from '../../generated/gmxsdk/modules/orders/transactions/createSwapOrderTxn.js';
|
import {createSwapOrderTxn} from '../../generated/gmxsdk/modules/orders/transactions/createSwapOrderTxn.js';
|
||||||
|
|
||||||
import {MarketInfo, MarketsInfoData} from '../../generated/gmxsdk/types/markets.js';
|
import {MarketInfo, MarketsInfoData} from '../../generated/gmxsdk/types/markets.js';
|
||||||
@@ -60,7 +60,7 @@ interface CacheEntry {
|
|||||||
const CACHE_TTL = 60 * 60 * 1000; // 60 minutes in milliseconds (increased from 30 minutes)
|
const CACHE_TTL = 60 * 60 * 1000; // 60 minutes in milliseconds (increased from 30 minutes)
|
||||||
const marketsCache = new Map<string, CacheEntry>();
|
const marketsCache = new Map<string, CacheEntry>();
|
||||||
const MAX_CACHE_SIZE = 20; // Increased cache size to allow more markets data
|
const MAX_CACHE_SIZE = 20; // Increased cache size to allow more markets data
|
||||||
const OPERATION_TIMEOUT = 30000; // 30 seconds timeout for operations
|
const OPERATION_TIMEOUT = 60000; // 60 seconds timeout for operations (increased to handle slower swaps)
|
||||||
|
|
||||||
const MEMORY_WARNING_THRESHOLD = 0.85; // Warn when memory usage exceeds 85%
|
const MEMORY_WARNING_THRESHOLD = 0.85; // Warn when memory usage exceeds 85%
|
||||||
const MEMORY_CLEAR_THRESHOLD = 0.95; // Clear cache when memory usage exceeds 95%
|
const MEMORY_CLEAR_THRESHOLD = 0.95; // Clear cache when memory usage exceeds 95%
|
||||||
@@ -342,7 +342,7 @@ export async function estimatePositionGasFee(
|
|||||||
// Fallback RPC configuration
|
// Fallback RPC configuration
|
||||||
const FALLBACK_RPC_URL = "https://radial-shy-cherry.arbitrum-mainnet.quiknode.pro/098e57e961b05b24bcde008c4ca02fff6fb13b51/";
|
const FALLBACK_RPC_URL = "https://radial-shy-cherry.arbitrum-mainnet.quiknode.pro/098e57e961b05b24bcde008c4ca02fff6fb13b51/";
|
||||||
const PRIMARY_RPC_URL = "https://arb1.arbitrum.io/rpc";
|
const PRIMARY_RPC_URL = "https://arb1.arbitrum.io/rpc";
|
||||||
const MAX_RETRY_ATTEMPTS = 2; // Only allow one retry to prevent infinite loops
|
const MAX_RETRY_ATTEMPTS = 1; // Only allow one retry (2 total attempts: initial + 1 retry)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2947,13 +2947,20 @@ export const swapGmxTokensImpl = async (
|
|||||||
const fromTokenData = getTokenDataFromTicker(fromTicker, tokensData);
|
const fromTokenData = getTokenDataFromTicker(fromTicker, tokensData);
|
||||||
const toTokenData = getTokenDataFromTicker(toTicker, tokensData);
|
const toTokenData = getTokenDataFromTicker(toTicker, tokensData);
|
||||||
|
|
||||||
|
// IMPORTANT: For native tokens (ETH), we must use NATIVE_TOKEN_ADDRESS (0x0000...)
|
||||||
|
// instead of the wrapped token address (WETH) for the swap to work correctly
|
||||||
|
const fromTokenAddress = fromTokenData?.isNative ? NATIVE_TOKEN_ADDRESS : fromTokenData?.address;
|
||||||
|
const toTokenAddress = toTokenData?.isNative ? NATIVE_TOKEN_ADDRESS : toTokenData?.address;
|
||||||
|
|
||||||
console.log(`🔄 Swap details:`, {
|
console.log(`🔄 Swap details:`, {
|
||||||
fromTicker,
|
fromTicker,
|
||||||
toTicker,
|
toTicker,
|
||||||
fromTokenAddress: fromTokenData?.address,
|
fromTokenAddress,
|
||||||
|
fromTokenAddressOriginal: fromTokenData?.address,
|
||||||
fromTokenIsNative: fromTokenData?.isNative,
|
fromTokenIsNative: fromTokenData?.isNative,
|
||||||
fromTokenIsSynthetic: fromTokenData?.isSynthetic,
|
fromTokenIsSynthetic: fromTokenData?.isSynthetic,
|
||||||
toTokenAddress: toTokenData?.address,
|
toTokenAddress,
|
||||||
|
toTokenAddressOriginal: toTokenData?.address,
|
||||||
toTokenIsNative: toTokenData?.isNative,
|
toTokenIsNative: toTokenData?.isNative,
|
||||||
toTokenIsSynthetic: toTokenData?.isSynthetic
|
toTokenIsSynthetic: toTokenData?.isSynthetic
|
||||||
});
|
});
|
||||||
@@ -2983,10 +2990,11 @@ export const swapGmxTokensImpl = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for zero addresses (but allow native tokens like ETH)
|
// Check for zero addresses (but allow native tokens like ETH)
|
||||||
if (fromTokenData.address === "0x0000000000000000000000000000000000000000" && !fromTokenData.isNative) {
|
// Native tokens will have NATIVE_TOKEN_ADDRESS (0x0000...) which is valid
|
||||||
|
if (fromTokenAddress === "0x0000000000000000000000000000000000000000" && !fromTokenData.isNative) {
|
||||||
throw new Error(`From token ${fromTicker} has zero address - token not found or invalid`);
|
throw new Error(`From token ${fromTicker} has zero address - token not found or invalid`);
|
||||||
}
|
}
|
||||||
if (toTokenData.address === "0x0000000000000000000000000000000000000000" && !toTokenData.isNative) {
|
if (toTokenAddress === "0x0000000000000000000000000000000000000000" && !toTokenData.isNative) {
|
||||||
throw new Error(`To token ${toTicker} has zero address - token not found or invalid`);
|
throw new Error(`To token ${toTicker} has zero address - token not found or invalid`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2997,6 +3005,7 @@ export const swapGmxTokensImpl = async (
|
|||||||
walletBalance = await sdk.publicClient.getBalance({ address: sdk.account as Address });
|
walletBalance = await sdk.publicClient.getBalance({ address: sdk.account as Address });
|
||||||
} else {
|
} else {
|
||||||
// For ERC20 tokens, get balance using multicall
|
// For ERC20 tokens, get balance using multicall
|
||||||
|
// Use the original token address (not NATIVE_TOKEN_ADDRESS) for ERC20 balance checks
|
||||||
const balanceResult = await sdk.executeMulticall({
|
const balanceResult = await sdk.executeMulticall({
|
||||||
token: {
|
token: {
|
||||||
contractAddress: fromTokenData.address,
|
contractAddress: fromTokenData.address,
|
||||||
@@ -3029,7 +3038,9 @@ export const swapGmxTokensImpl = async (
|
|||||||
|
|
||||||
console.log('💰 Wallet balance check:', {
|
console.log('💰 Wallet balance check:', {
|
||||||
fromTicker,
|
fromTicker,
|
||||||
fromTokenAddress: fromTokenData.address,
|
fromTokenAddress,
|
||||||
|
fromTokenAddressOriginal: fromTokenData.address,
|
||||||
|
isNative: fromTokenData.isNative,
|
||||||
requestedAmount: requestedAmountBigInt.toString(),
|
requestedAmount: requestedAmountBigInt.toString(),
|
||||||
walletBalance: walletBalance.toString(),
|
walletBalance: walletBalance.toString(),
|
||||||
requestedAmountFormatted: requestedAmountNumber.toFixed(6),
|
requestedAmountFormatted: requestedAmountNumber.toFixed(6),
|
||||||
@@ -3162,8 +3173,8 @@ export const swapGmxTokensImpl = async (
|
|||||||
try {
|
try {
|
||||||
const swapParams = {
|
const swapParams = {
|
||||||
fromAmount: verifiedFromTokenAmount,
|
fromAmount: verifiedFromTokenAmount,
|
||||||
fromTokenAddress: fromTokenData.address,
|
fromTokenAddress,
|
||||||
toTokenAddress: toTokenData.address,
|
toTokenAddress,
|
||||||
allowedSlippageBps: allowedSlippageBps,
|
allowedSlippageBps: allowedSlippageBps,
|
||||||
referralCodeForTxn: encodeReferralCode("kaigen_ai"),
|
referralCodeForTxn: encodeReferralCode("kaigen_ai"),
|
||||||
skipSimulation: true,
|
skipSimulation: true,
|
||||||
@@ -3173,6 +3184,10 @@ export const swapGmxTokensImpl = async (
|
|||||||
swapParams.tokensData = tokensData;
|
swapParams.tokensData = tokensData;
|
||||||
|
|
||||||
console.log('🔄 Attempting SDK swap with slippage:', {
|
console.log('🔄 Attempting SDK swap with slippage:', {
|
||||||
|
fromTokenAddress,
|
||||||
|
toTokenAddress,
|
||||||
|
fromIsNative: fromTokenData.isNative,
|
||||||
|
toIsNative: toTokenData.isNative,
|
||||||
allowedSlippageBps: allowedSlippageBps,
|
allowedSlippageBps: allowedSlippageBps,
|
||||||
slippagePercentage: slippagePercentage
|
slippagePercentage: slippagePercentage
|
||||||
});
|
});
|
||||||
@@ -3198,8 +3213,8 @@ export const swapGmxTokensImpl = async (
|
|||||||
// Find the proper swap path using createFindSwapPath (works for both synthetic and non-synthetic tokens)
|
// Find the proper swap path using createFindSwapPath (works for both synthetic and non-synthetic tokens)
|
||||||
const findSwapPath = createFindSwapPath({
|
const findSwapPath = createFindSwapPath({
|
||||||
chainId: sdk.chainId,
|
chainId: sdk.chainId,
|
||||||
fromTokenAddress: fromTokenData.address,
|
fromTokenAddress,
|
||||||
toTokenAddress: toTokenData.address,
|
toTokenAddress,
|
||||||
marketsInfoData,
|
marketsInfoData,
|
||||||
gasEstimationParams: {
|
gasEstimationParams: {
|
||||||
gasLimits,
|
gasLimits,
|
||||||
@@ -3233,7 +3248,7 @@ export const swapGmxTokensImpl = async (
|
|||||||
// No swap path found - this should not happen for valid token pairs
|
// No swap path found - this should not happen for valid token pairs
|
||||||
// (Synthetic tokens are already validated early, so we won't reach here for them)
|
// (Synthetic tokens are already validated early, so we won't reach here for them)
|
||||||
console.warn('⚠️ No swap path found, using direct path (may not work for all token pairs)');
|
console.warn('⚠️ No swap path found, using direct path (may not work for all token pairs)');
|
||||||
swapPath = [fromTokenData.address, toTokenData.address];
|
swapPath = [fromTokenAddress, toTokenAddress];
|
||||||
swapsCount = 1;
|
swapsCount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3297,10 +3312,20 @@ export const swapGmxTokensImpl = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('🚀 Creating swap order with:', {
|
||||||
|
fromTokenAddress,
|
||||||
|
fromIsNative: fromTokenData.isNative,
|
||||||
|
toTokenAddress,
|
||||||
|
toIsNative: toTokenData.isNative,
|
||||||
|
fromTokenAmount: verifiedFromTokenAmount.toString(),
|
||||||
|
executionFee: executionFee.feeTokenAmount.toString(),
|
||||||
|
swapPath
|
||||||
|
});
|
||||||
|
|
||||||
await createSwapOrderTxn(sdk, {
|
await createSwapOrderTxn(sdk, {
|
||||||
fromTokenAddress: fromTokenData.address,
|
fromTokenAddress,
|
||||||
fromTokenAmount: verifiedFromTokenAmount,
|
fromTokenAmount: verifiedFromTokenAmount,
|
||||||
toTokenAddress: toTokenData.address,
|
toTokenAddress,
|
||||||
swapPath: swapPath,
|
swapPath: swapPath,
|
||||||
orderType: OrderType.MarketSwap,
|
orderType: OrderType.MarketSwap,
|
||||||
minOutputAmount: minOutputAmount,
|
minOutputAmount: minOutputAmount,
|
||||||
|
|||||||
@@ -1,32 +1,34 @@
|
|||||||
import {describe, it} from 'node:test'
|
import {describe, it} from 'node:test'
|
||||||
import assert from 'node:assert'
|
import assert from 'node:assert'
|
||||||
import {getClientForAddress, swapGmxTokensImpl} from '../../src/plugins/custom/gmx.js'
|
import {getClientForAddress, swapGmxTokensImpl} from '../../src/plugins/custom/gmx.js'
|
||||||
import {Ticker} from '../../src/generated/ManagingApiTypes.js'
|
|
||||||
|
|
||||||
describe('swap tokens implementation', () => {
|
describe('swap tokens implementation', () => {
|
||||||
|
|
||||||
it('should swap SOL to USDC successfully', async () => {
|
it('should swap $4 worth of ETH to USDC successfully', async () => {
|
||||||
try {
|
try {
|
||||||
const testAccount = '0x932167388dD9aad41149b3cA23eBD489E2E2DD78'
|
const testAccount = '0x932167388dD9aad41149b3cA23eBD489E2E2DD78'
|
||||||
|
|
||||||
const sdk = await getClientForAddress(testAccount)
|
const sdk = await getClientForAddress(testAccount)
|
||||||
|
|
||||||
|
console.log('\n🔄 Starting ETH → USDC swap ($4 worth of ETH to USDC)...\n')
|
||||||
|
|
||||||
|
// Swap ~0.0012 ETH to get approximately $4 worth of USDC (at ~$3400 ETH price)
|
||||||
|
// 0.0012 ETH * $3,400 = ~$4.08
|
||||||
const result = await swapGmxTokensImpl(
|
const result = await swapGmxTokensImpl(
|
||||||
sdk,
|
sdk,
|
||||||
Ticker.USDC,
|
'ETH',
|
||||||
Ticker.RENDER,
|
'USDC',
|
||||||
5, // 5 USDC
|
0.0012, // ~0.0012 ETH should give us ~$4 worth of USDC
|
||||||
'market',
|
'market',
|
||||||
undefined,
|
undefined,
|
||||||
0.5
|
0.5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log('\n✅ ETH → USDC swap successful! Swapped ~$4 worth of ETH to USDC\n')
|
||||||
assert.strictEqual(typeof result, 'string')
|
assert.strictEqual(typeof result, 'string')
|
||||||
assert.strictEqual(result, 'swap_order_created')
|
assert.strictEqual(result, 'swap_order_created')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error', error)
|
console.log('\n❌ ETH → USDC swap failed:', error)
|
||||||
|
|
||||||
// Test that the error is related to actual execution rather than parameter validation
|
|
||||||
assert.fail(error.message)
|
assert.fail(error.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user