diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index ca5dc7b0..75f8f8da 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -56,6 +56,58 @@ const FALLBACK_RPC_URL = "https://radial-shy-cherry.arbitrum-mainnet.quiknode.pr const PRIMARY_RPC_URL = "https://arb1.arbitrum.io/rpc"; const MAX_RETRY_ATTEMPTS = 2; // Only allow one retry to prevent infinite loops +/** + * Generic fallback method for GMX operations that can retry with a fallback RPC + * @param operation The operation function to execute + * @param sdk The GMX SDK client + * @param retryCount Current retry attempt count + * @returns The result of the operation + */ +async function executeWithFallback( + operation: (sdk: GmxSdk, retryCount: number) => Promise, + sdk: GmxSdk, + retryCount: number = 0 +): Promise { + try { + console.log(`🔄 Executing operation with retry count: ${retryCount}, RPC: ${retryCount === 0 ? 'Primary' : 'Fallback'}`); + return await operation(sdk, retryCount); + } catch (error) { + // Check if this is a multicall timeout error and we haven't exceeded retry limit + const errorMessage = error instanceof Error ? error.message : String(error); + const isMulticallTimeout = errorMessage.toLowerCase().includes('multicall timeout') || + errorMessage.toLowerCase().includes('timeout') || + errorMessage.toLowerCase().includes('rpc error'); + + if (isMulticallTimeout && retryCount < MAX_RETRY_ATTEMPTS) { + console.log(`🔄 Multicall timeout detected (attempt ${retryCount + 1}/${MAX_RETRY_ATTEMPTS + 1}), retrying with fallback RPC...`); + + try { + // Create a new SDK client with the fallback RPC + const fallbackSdk = await createGmxClientWithRpc(sdk.account, FALLBACK_RPC_URL); + + console.log('✅ Fallback RPC client created, retrying operation...'); + + // Recursively call the same function with the fallback SDK and incremented retry count + const result = await executeWithFallback(operation, fallbackSdk, retryCount + 1); + + console.log('✅ Fallback RPC request successful'); + return result; + + } catch (fallbackError) { + console.error('❌ Fallback RPC also failed:', fallbackError); + // If fallback also fails, throw the original error + throw error; + } + } + + // If it's not a multicall timeout or we've exceeded retry limit, re-throw the error + if (isMulticallTimeout && retryCount >= MAX_RETRY_ATTEMPTS) { + console.error(`❌ Maximum retry attempts (${MAX_RETRY_ATTEMPTS}) exceeded. Both primary and fallback RPCs failed.`); + } + throw error; + } +} + /** * Gets markets info data from cache or fetches it from GMX SDK * @param sdk The GMX SDK client @@ -297,73 +349,72 @@ export const openGmxPositionImpl = async ( stopLossPrice?: number, takeProfitPrice?: number ): Promise => { - try { - // Get markets and tokens data from GMX SDK with cache - const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); + return executeWithFallback( + async (sdk, retryCount) => { + // Get markets and tokens data from GMX SDK with cache + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); - if (!marketsInfoData || !tokensData) { - throw new Error("No markets or tokens info data"); - } + if (!marketsInfoData || !tokensData) { + throw new Error("No markets or tokens info data"); + } - const marketInfo = getMarketInfoFromTicker(ticker, marketsInfoData); - const collateralToken = getTokenDataFromTicker("USDC", tokensData); // Using USDC as collateral + const marketInfo = getMarketInfoFromTicker(ticker, marketsInfoData); + const collateralToken = getTokenDataFromTicker("USDC", tokensData); // Using USDC as collateral - // For market orders, we need to get the current market price - let currentPrice = price; - if (!currentPrice) { - // Get current market price from market info - currentPrice = Number(marketInfo.indexToken.prices.minPrice) / 1e30; // Convert from 30 decimals - console.log(`No price provided, using current market price: ${currentPrice}`); - } + // For market orders, we need to get the current market price + let currentPrice = price; + if (!currentPrice) { + // Get current market price from market info + currentPrice = Number(marketInfo.indexToken.prices.minPrice) / 1e30; // Convert from 30 decimals + console.log(`No price provided, using current market price: ${currentPrice}`); + } - // Calculate the collateral amount in USDC (quantity * price) - const collateralAmount = BigInt(Math.floor((quantity || 0) * currentPrice * 1e6)); // USDC has 6 decimals + // Calculate the collateral amount in USDC (quantity * price) + const collateralAmount = BigInt(Math.floor((quantity || 0) * currentPrice * 1e6)); // USDC has 6 decimals - // Note: Wallet initialization should be done separately before opening positions - // This reduces position opening time and allows for better error handling + // Note: Wallet initialization should be done separately before opening positions + // This reduces position opening time and allows for better error handling - // Calculate leverage in basis points (1x = 10000) - const leverageBps = BigInt((leverage || 1) * 10000); - const limitPrice = price ? numberToBigint(price, 30) : undefined; + // Calculate leverage in basis points (1x = 10000) + const leverageBps = BigInt((leverage || 1) * 10000); + const limitPrice = price ? numberToBigint(price, 30) : undefined; - const params: PositionIncreaseParams = { - payAmount: collateralAmount, - marketAddress: marketInfo.marketTokenAddress, - payTokenAddress: collateralToken.address, - collateralTokenAddress: collateralToken.address, - allowedSlippageBps: 50, // 0.5% slippage - leverage: leverageBps, - skipSimulation: true, - referralCodeForTxn: encodeReferralCode("kaigen_ai"), - stopLossPrice: stopLossPrice ? numberToBigint(stopLossPrice, 30) : undefined, - takeProfitPrice: takeProfitPrice ? numberToBigint(takeProfitPrice, 30) : undefined, - acceptablePriceImpactBuffer: 150, - limitPrice: limitPrice, - }; + const params: PositionIncreaseParams = { + payAmount: collateralAmount, + marketAddress: marketInfo.marketTokenAddress, + payTokenAddress: collateralToken.address, + collateralTokenAddress: collateralToken.address, + allowedSlippageBps: 50, // 0.5% slippage + leverage: leverageBps, + skipSimulation: true, + referralCodeForTxn: encodeReferralCode("kaigen_ai"), + stopLossPrice: stopLossPrice ? numberToBigint(stopLossPrice, 30) : undefined, + takeProfitPrice: takeProfitPrice ? numberToBigint(takeProfitPrice, 30) : undefined, + acceptablePriceImpactBuffer: 150, + limitPrice: limitPrice, + }; - console.log('📋 Position params:', { - payAmount: collateralAmount.toString(), - marketAddress: marketInfo.marketTokenAddress, - payTokenAddress: collateralToken.address, - collateralTokenAddress: collateralToken.address, - leverage: leverageBps.toString(), - direction: direction - }); + console.log('📋 Position params:', { + payAmount: collateralAmount.toString(), + marketAddress: marketInfo.marketTokenAddress, + payTokenAddress: collateralToken.address, + collateralTokenAddress: collateralToken.address, + leverage: leverageBps.toString(), + direction: direction + }); - console.log('🚀 Executing position order...'); + console.log('🚀 Executing position order...'); - if (direction === TradeDirection.Long) { - await sdk.orders.long(params); - } else { - await sdk.orders.short(params); - } - console.log('✅ Position order executed successfully'); + if (direction === TradeDirection.Long) { + await sdk.orders.long(params); + } else { + await sdk.orders.short(params); + } + console.log('✅ Position order executed successfully'); - return ""; - } catch (error) { - console.error('Error opening GMX position:', error); - throw new Error(`Failed to open position: ${error instanceof Error ? error.message : 'Unknown error'}`); - } + return ""; + }, sdk, 0 + ); }; @@ -445,43 +496,42 @@ export const cancelGmxOrdersImpl = async ( sdk: GmxSdk, ticker: string ): Promise => { - try { - // Get markets and tokens data first - const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); + return executeWithFallback( + async (sdk, retryCount) => { + // Get markets and tokens data first + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); - if (!marketsInfoData || !tokensData) { - throw new Error("No markets or tokens info data"); - } + if (!marketsInfoData || !tokensData) { + throw new Error("No markets or tokens info data"); + } - // Get active orders for the account with required parameters - const ordersData = await sdk.orders.getOrders({ - account: sdk.account, - marketsInfoData, - tokensData - }); + // Get active orders for the account with required parameters + const ordersData = await sdk.orders.getOrders({ + account: sdk.account, + marketsInfoData, + tokensData + }); - // Extract order keys for the specified ticker - const orderKeys = Object.values(ordersData.ordersInfoData) - .filter(order => { - if ('marketInfo' in order) { - return order.marketInfo.indexToken.symbol.toUpperCase() === ticker.toUpperCase(); - } - return false; - }) - .map(order => order.key); + // Extract order keys for the specified ticker + const orderKeys = Object.values(ordersData.ordersInfoData) + .filter(order => { + if ('marketInfo' in order) { + return order.marketInfo.indexToken.symbol.toUpperCase() === ticker.toUpperCase(); + } + return false; + }) + .map(order => order.key); - if (orderKeys.length === 0) { - return true; // No orders to cancel - } + if (orderKeys.length === 0) { + return true; // No orders to cancel + } - // Cancel orders using the batch method - await sdk.orders.cancelOrders(orderKeys); + // Cancel orders using the batch method + await sdk.orders.cancelOrders(orderKeys); - return true; - } catch (error) { - console.error('Error canceling GMX orders:', error); - throw new Error(`Failed to cancel orders: ${error instanceof Error ? error.message : 'Unknown error'}`); - } + return true; + }, sdk, 0 + ); }; @@ -580,90 +630,89 @@ export const closeGmxPositionImpl = async ( ticker: string, direction: TradeDirection ): Promise => { - try { - // Get markets and tokens data from GMX SDK with cache - const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); + return executeWithFallback( + async (sdk, retryCount) => { + // Get markets and tokens data from GMX SDK with cache + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); - if (!marketsInfoData || !tokensData) { - throw new Error("No markets or tokens info data"); - } + if (!marketsInfoData || !tokensData) { + throw new Error("No markets or tokens info data"); + } - const marketInfo = getMarketInfoFromTicker(ticker, marketsInfoData); + const marketInfo = getMarketInfoFromTicker(ticker, marketsInfoData); - // Get the user's position - const positionsInfo = await sdk.positions.getPositionsInfo({ - marketsInfoData, - tokensData, - showPnlInLeverage: true - }); + // Get the user's position + const positionsInfo = await sdk.positions.getPositionsInfo({ + marketsInfoData, + tokensData, + showPnlInLeverage: true + }); - // Find the specific position to close - const positionKey = Object.keys(positionsInfo).find(key => { - const position = positionsInfo[key]; - return position.marketInfo.indexToken.symbol === ticker && position.isLong === (direction === TradeDirection.Long); - }); + // Find the specific position to close + const positionKey = Object.keys(positionsInfo).find(key => { + const position = positionsInfo[key]; + return position.marketInfo.indexToken.symbol === ticker && position.isLong === (direction === TradeDirection.Long); + }); - if (!positionKey) { - throw new Error(`No open ${direction} position found for ${ticker}`); - } + if (!positionKey) { + throw new Error(`No open ${direction} position found for ${ticker}`); + } - const position = positionsInfo[positionKey]; + const position = positionsInfo[positionKey]; - console.log(position); + console.log(position); - const decreaseAmounts: DecreasePositionAmounts = { - isFullClose: true, - sizeDeltaUsd: position.sizeInUsd, - sizeDeltaInTokens: position.sizeInTokens, - collateralDeltaUsd: position.remainingCollateralAmount, - collateralDeltaAmount: position.remainingCollateralAmount, - acceptablePriceDeltaBps: 30n, - recommendedAcceptablePriceDeltaBps: 0n, - estimatedPnl: 0n, - estimatedPnlPercentage: 0n, - realizedPnl: 0n, - realizedPnlPercentage: 0n, - positionFeeUsd: 0n, - uiFeeUsd: 0n, - swapUiFeeUsd: 0n, - feeDiscountUsd: 0n, - borrowingFeeUsd: 0n, - fundingFeeUsd: 0n, - swapProfitFeeUsd: 0n, - positionPriceImpactDeltaUsd: 0n, - priceImpactDiffUsd: 0n, - payedRemainingCollateralAmount: 0n, - payedOutputUsd: 0n, - payedRemainingCollateralUsd: 0n, - receiveTokenAmount: 0n, - receiveUsd: 0n, - decreaseSwapType: DecreasePositionSwapType.NoSwap, - indexPrice: 0n, - collateralPrice: 0n, - acceptablePrice: position.markPrice, - triggerOrderType: OrderType.MarketDecrease, - triggerPrice: position.markPrice, - } + const decreaseAmounts: DecreasePositionAmounts = { + isFullClose: true, + sizeDeltaUsd: position.sizeInUsd, + sizeDeltaInTokens: position.sizeInTokens, + collateralDeltaUsd: position.remainingCollateralAmount, + collateralDeltaAmount: position.remainingCollateralAmount, + acceptablePriceDeltaBps: 30n, + recommendedAcceptablePriceDeltaBps: 0n, + estimatedPnl: 0n, + estimatedPnlPercentage: 0n, + realizedPnl: 0n, + realizedPnlPercentage: 0n, + positionFeeUsd: 0n, + uiFeeUsd: 0n, + swapUiFeeUsd: 0n, + feeDiscountUsd: 0n, + borrowingFeeUsd: 0n, + fundingFeeUsd: 0n, + swapProfitFeeUsd: 0n, + positionPriceImpactDeltaUsd: 0n, + priceImpactDiffUsd: 0n, + payedRemainingCollateralAmount: 0n, + payedOutputUsd: 0n, + payedRemainingCollateralUsd: 0n, + receiveTokenAmount: 0n, + receiveUsd: 0n, + decreaseSwapType: DecreasePositionSwapType.NoSwap, + indexPrice: 0n, + collateralPrice: 0n, + acceptablePrice: position.markPrice, + triggerOrderType: OrderType.MarketDecrease, + triggerPrice: position.markPrice, + } - const params2 = { - marketInfo, - marketsInfoData, - tokensData, - isLong: direction === TradeDirection.Long, - allowedSlippage: 30, - decreaseAmounts, - collateralToken: position.marketInfo.shortToken, - referralCodeForTxn: encodeReferralCode("kaigen_ai"), - } + const params2 = { + marketInfo, + marketsInfoData, + tokensData, + isLong: direction === TradeDirection.Long, + allowedSlippage: 30, + decreaseAmounts, + collateralToken: position.marketInfo.shortToken, + referralCodeForTxn: encodeReferralCode("kaigen_ai"), + } - // Execute the close position order - await sdk.orders.createDecreaseOrder(params2); + // Execute the close position order + await sdk.orders.createDecreaseOrder(params2); - return "hash"; - } catch (error) { - console.error('Error closing GMX position:', error); - throw new Error(`Failed to close position: ${error instanceof Error ? error.message : 'Unknown error'}`); - } + return "hash"; + }, sdk, 0 + ); }; /** @@ -712,85 +761,86 @@ export const getGmxTradeImpl = async ( sdk: GmxSdk, ticker: string ): Promise => { + return executeWithFallback( + async (sdk, retryCount) => { + const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk); - const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk); - - const orders = await sdk.orders.getOrders({ - account: sdk.account, - marketsInfoData, - tokensData - }); - - - // Filter orders for the specific ticker and transform them to trades - const trades = Object.values(orders.ordersInfoData) - .filter(order => { - // Ensure marketInfo exists before accessing its properties - return 'marketInfo' in order && order.marketInfo.indexToken.symbol.toUpperCase() === ticker.toUpperCase(); - }) - .map(order => { - const positionOrder = order as PositionOrderInfo; - - const priceDecimals = calculateDisplayDecimals( - positionOrder.indexToken.prices?.minPrice, - undefined, - positionOrder.indexToken.visualMultiplier - ); - - // Default values for potentially undefined fields - const triggerPrice = order.contractTriggerPrice ?? 0n; - const sizeDelta = order.sizeDeltaUsd ?? 0n; - const initialCollateral = order.initialCollateralDeltaAmount ?? 0n; - - - // Calculate leverage directly from the division of bigint values - // and convert to standard leverage number (like 2 for 2x) - let leverage = 2; // Default to 2x leverage - - if (initialCollateral !== 0n) { - // Convert values to regular numbers but preserve precision - // Convert BigInt to Number before arithmetic operations - const size = Number(sizeDelta) / 1e30; // Convert from PRECISION_DECIMALS - const collateral = Number(initialCollateral) / 1e6; // USDC has 6 decimals - - if (collateral > 0) { - leverage = Math.round(size / collateral); - } - } - - // Price should be converted to the proper format (e.g. 0.0008631855060000001 to 8631855) - const displayPrice = formatUsd(positionOrder.triggerPrice, { - displayDecimals: priceDecimals, - visualMultiplier: positionOrder.indexToken?.visualMultiplier, + const orders = await sdk.orders.getOrders({ + account: sdk.account, + marketsInfoData, + tokensData }); - // Remove currency symbol and convert to number - const numericPrice = Number(displayPrice.replace(/[^0-9.]/g, '')); + // Filter orders for the specific ticker and transform them to trades + const trades = Object.values(orders.ordersInfoData) + .filter(order => { + // Ensure marketInfo exists before accessing its properties + return 'marketInfo' in order && order.marketInfo.indexToken.symbol.toUpperCase() === ticker.toUpperCase(); + }) + .map(order => { + const positionOrder = order as PositionOrderInfo; + + const priceDecimals = calculateDisplayDecimals( + positionOrder.indexToken.prices?.minPrice, + undefined, + positionOrder.indexToken.visualMultiplier + ); + + // Default values for potentially undefined fields + const triggerPrice = order.contractTriggerPrice ?? 0n; + const sizeDelta = order.sizeDeltaUsd ?? 0n; + const initialCollateral = order.initialCollateralDeltaAmount ?? 0n; + + // Calculate leverage directly from the division of bigint values + // and convert to standard leverage number (like 2 for 2x) + let leverage = 2; // Default to 2x leverage + + if (initialCollateral !== 0n) { + // Convert values to regular numbers but preserve precision + // Convert BigInt to Number before arithmetic operations + const size = Number(sizeDelta) / 1e30; // Convert from PRECISION_DECIMALS + const collateral = Number(initialCollateral) / 1e6; // USDC has 6 decimals + + if (collateral > 0) { + leverage = Math.round(size / collateral); + } + } + + // Price should be converted to the proper format (e.g. 0.0008631855060000001 to 8631855) + const displayPrice = formatUsd(positionOrder.triggerPrice, { + displayDecimals: priceDecimals, + visualMultiplier: positionOrder.indexToken?.visualMultiplier, + }); + + // Remove currency symbol and convert to number + const numericPrice = Number(displayPrice.replace(/[^0-9.]/g, '')); // Calculate size in tokens instead of USD based on the collateral and the trigger price - // Convert BigInt to Number before arithmetic operations - const sizeInTokens = Number(sizeDelta) / Number(triggerPrice); + // Convert BigInt to Number before arithmetic operations + const sizeInTokens = Number(sizeDelta) / Number(triggerPrice); - // Apply proper scaling for token amount - // This converts from the large bigint value to the actual token amount - const scaledTokenSize = sizeInTokens * 1e-30; + // Apply proper scaling for token amount + // This converts from the large bigint value to the actual token amount + const scaledTokenSize = sizeInTokens * 1e-30; - return { - id: order.key, - ticker: ticker, - direction: order.isLong ? TradeDirection.Short : TradeDirection.Long, - price: numericPrice, // Numeric price without currency symbol - quantity: scaledTokenSize, // Properly scaled size in tokens - leverage: leverage, // Fixed leverage value - status: TradeStatus.PendingOpen, // Assuming these are pending orders - tradeType: order.orderType == OrderType.LimitIncrease ? TradeType.Limit : TradeType.Market, - date: new Date(Number(order.updatedAtTime) * 1000), // Convert BigInt to Number first - exchangeOrderId: order.key - } as Trade; - }); + return { + id: order.key, + ticker: ticker, + direction: order.isLong ? TradeDirection.Short : TradeDirection.Long, + price: numericPrice, // Numeric price without currency symbol + quantity: scaledTokenSize, // Properly scaled size in tokens + leverage: leverage, // Fixed leverage value + status: TradeStatus.PendingOpen, // Assuming these are pending orders + tradeType: order.orderType == OrderType.LimitIncrease ? TradeType.Limit : TradeType.Market, + date: new Date(Number(order.updatedAtTime) * 1000), // Convert BigInt to Number first + exchangeOrderId: order.key + } as Trade; + }); - return trades; -} + return trades; + }, sdk, 0 + ); +}; /** * Gets trades on GMX @@ -832,163 +882,131 @@ export const getGmxPositionsImpl = async ( sdk: GmxSdk, retryCount: number = 0 ): Promise => { - try { - const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); + return executeWithFallback( + async (sdk, retryCount) => { + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); - const positionsInfo = await sdk.positions.getPositionsInfo({ - marketsInfoData, - tokensData, - showPnlInLeverage: true, - }); + const positionsInfo = await sdk.positions.getPositionsInfo({ + marketsInfoData, + tokensData, + showPnlInLeverage: true, + }); - const positions = Object.values(positionsInfo).map(async (pos) => { - // Fix leverage calculation to avoid exponential notation issues - let leverage = 1; // Default to 1x leverage + const positions = Object.values(positionsInfo).map(async (pos) => { + // Fix leverage calculation to avoid exponential notation issues + let leverage = 1; // Default to 1x leverage - if (pos.collateralAmount > 0n) { - // Manual calculation of leverage from raw values - // For positions, we already have the leverage value from GMX, but we want to ensure it's properly formatted - if (pos.sizeInUsd > 0n && pos.collateralAmount > 0n) { - // Use a simplified approach to calculate leverage directly - // Convert BigInt to Number before division to avoid mixing types - const sizeInUsdNumber = Number(pos.sizeInUsd); - const collateralAmountNumber = Number(pos.collateralAmount); - - if (collateralAmountNumber > 0) { - leverage = Math.round(sizeInUsdNumber / collateralAmountNumber); + if (pos.collateralAmount > 0n) { + // Manual calculation of leverage from raw values + // For positions, we already have the leverage value from GMX, but we want to ensure it's properly formatted + if (pos.sizeInUsd > 0n && pos.collateralAmount > 0n) { + // Use a simplified approach to calculate leverage directly + // Convert BigInt to Number before division to avoid mixing types + const sizeInUsdNumber = Number(pos.sizeInUsd); + const collateralAmountNumber = Number(pos.collateralAmount); + + if (collateralAmountNumber > 0) { + leverage = Math.round(sizeInUsdNumber / collateralAmountNumber); + } } } - } - const collateralDecimals = pos.isLong ? pos.marketInfo.shortToken.decimals : pos.marketInfo.longToken.decimals; + const collateralDecimals = pos.isLong ? pos.marketInfo.shortToken.decimals : pos.marketInfo.longToken.decimals; - const ticker = pos.marketInfo.indexToken.symbol; - // get SL and TP from the position - const trades = await getGmxTradeImpl(sdk, ticker); + const ticker = pos.marketInfo.indexToken.symbol; + // get SL and TP from the position + const trades = await getGmxTradeImpl(sdk, ticker); - // Calculate proper price display decimals - const priceDecimals = calculateDisplayDecimals( - pos.indexToken.prices?.minPrice, - undefined, - pos.indexToken.visualMultiplier - ); + // Calculate proper price display decimals + const priceDecimals = calculateDisplayDecimals( + pos.indexToken.prices?.minPrice, + undefined, + pos.indexToken.visualMultiplier + ); - // Format prices using the same helper as trades for consistency - const displayEntryPrice = formatUsd(pos.entryPrice, { - displayDecimals: priceDecimals, - visualMultiplier: pos.indexToken?.visualMultiplier, - }); - - const displayMarkPrice = formatUsd(pos.markPrice, { - displayDecimals: priceDecimals, - visualMultiplier: pos.indexToken?.visualMultiplier, - }); - - let displayLiquidationPrice = undefined; - if (pos.liquidationPrice) { - displayLiquidationPrice = formatUsd(pos.liquidationPrice, { + // Format prices using the same helper as trades for consistency + const displayEntryPrice = formatUsd(pos.entryPrice, { displayDecimals: priceDecimals, visualMultiplier: pos.indexToken?.visualMultiplier, }); - } - // Remove currency symbols and convert to numbers - const numericEntryPrice = Number(displayEntryPrice.replace(/[^0-9.]/g, '')); - const numericMarkPrice = Number(displayMarkPrice.replace(/[^0-9.]/g, '')); - let numericLiquidationPrice = undefined; - if (displayLiquidationPrice) { - numericLiquidationPrice = Number(displayLiquidationPrice.replace(/[^0-9.]/g, '')); - } + const displayMarkPrice = formatUsd(pos.markPrice, { + displayDecimals: priceDecimals, + visualMultiplier: pos.indexToken?.visualMultiplier, + }); - // Make sure we scale the token quantity properly - const tokenSize = bigintToNumber(pos.sizeInTokens, pos.marketInfo.indexToken.decimals); + let displayLiquidationPrice = undefined; + if (pos.liquidationPrice) { + displayLiquidationPrice = formatUsd(pos.liquidationPrice, { + displayDecimals: priceDecimals, + visualMultiplier: pos.indexToken?.visualMultiplier, + }); + } - let stopLoss: Trade | undefined; - let takeProfit: Trade | undefined; + // Remove currency symbols and convert to numbers + const numericEntryPrice = Number(displayEntryPrice.replace(/[^0-9.]/g, '')); + const numericMarkPrice = Number(displayMarkPrice.replace(/[^0-9.]/g, '')); + let numericLiquidationPrice = undefined; + if (displayLiquidationPrice) { + numericLiquidationPrice = Number(displayLiquidationPrice.replace(/[^0-9.]/g, '')); + } - if (pos.isLong) { - stopLoss = trades.find(t => t.price < numericEntryPrice); - takeProfit = trades.find(t => t.price > numericEntryPrice); - } else { - stopLoss = trades.find(t => t.price > numericEntryPrice); - takeProfit = trades.find(t => t.price < numericEntryPrice); - } + // Make sure we scale the token quantity properly + const tokenSize = bigintToNumber(pos.sizeInTokens, pos.marketInfo.indexToken.decimals); - let position = { - id: pos.key, - ticker: ticker, - direction: pos.isLong ? TradeDirection.Long : TradeDirection.Short, - price: numericEntryPrice, // Numeric entry price without currency symbol - quantity: tokenSize, // Position Size in tokens with correct decimals - leverage: leverage, // Fixed leverage value - status: PositionStatus.Filled, // Represents an open/active position - tradeType: TradeType.Market, // Placeholder - date: new Date(Number(pos.increasedAtTime) * 1000), // Position open time - convert BigInt to Number first - exchangeOrderId: pos.key, // Position key - pnl: bigintToNumber(pos.pnlAfterFees, PRECISION_DECIMALS), // PnL after fees (USD), assuming 30 decimals - collateral: bigintToNumber(pos.collateralAmount, collateralDecimals), // Collateral (in Collateral Token units) - markPrice: numericMarkPrice, // Numeric mark price without currency symbol - liquidationPrice: numericLiquidationPrice, // Numeric liquidation price without currency symbol - } as unknown as Position; // Use unknown assertion due to missing fields in target Position type + let stopLoss: Trade | undefined; + let takeProfit: Trade | undefined; - if (stopLoss) { - position.stopLoss = stopLoss; - } + if (pos.isLong) { + stopLoss = trades.find(t => t.price < numericEntryPrice); + takeProfit = trades.find(t => t.price > numericEntryPrice); + } else { + stopLoss = trades.find(t => t.price > numericEntryPrice); + takeProfit = trades.find(t => t.price < numericEntryPrice); + } - if (takeProfit) { - position.takeProfit1 = takeProfit; - } + let position = { + id: pos.key, + ticker: ticker, + direction: pos.isLong ? TradeDirection.Long : TradeDirection.Short, + price: numericEntryPrice, // Numeric entry price without currency symbol + quantity: tokenSize, // Position Size in tokens with correct decimals + leverage: leverage, // Fixed leverage value + status: PositionStatus.Filled, // Represents an open/active position + tradeType: TradeType.Market, // Placeholder + date: new Date(Number(pos.increasedAtTime) * 1000), // Position open time - convert BigInt to Number first + exchangeOrderId: pos.key, // Position key + pnl: bigintToNumber(pos.pnlAfterFees, PRECISION_DECIMALS), // PnL after fees (USD), assuming 30 decimals + collateral: bigintToNumber(pos.collateralAmount, collateralDecimals), // Collateral (in Collateral Token units) + markPrice: numericMarkPrice, // Numeric mark price without currency symbol + liquidationPrice: numericLiquidationPrice, // Numeric liquidation price without currency symbol + } as unknown as Position; // Use unknown assertion due to missing fields in target Position type - // build the open trade base on the current position object - const open = { - direction: pos.isLong ? TradeDirection.Long : TradeDirection.Short, - price: numericEntryPrice, - quantity: tokenSize, - leverage: leverage, - status: TradeStatus.PendingOpen, - } as Trade; + if (stopLoss) { + position.stopLoss = stopLoss; + } - position.open = open; + if (takeProfit) { + position.takeProfit1 = takeProfit; + } - return position; - }); + // build the open trade base on the current position object + const open = { + direction: pos.isLong ? TradeDirection.Long : TradeDirection.Short, + price: numericEntryPrice, + quantity: tokenSize, + leverage: leverage, + status: TradeStatus.PendingOpen, + } as Trade; - return Promise.all(positions); - } catch (error) { - // Check if this is a multicall timeout error and we haven't exceeded retry limit - const errorMessage = error instanceof Error ? error.message : String(error); - const isMulticallTimeout = errorMessage.toLowerCase().includes('multicall timeout') || - errorMessage.toLowerCase().includes('timeout') || - errorMessage.toLowerCase().includes('rpc error'); - - if (isMulticallTimeout && retryCount < MAX_RETRY_ATTEMPTS) { - console.log(`🔄 Multicall timeout detected (attempt ${retryCount + 1}/${MAX_RETRY_ATTEMPTS + 1}), retrying with fallback RPC...`); - - try { - // Create a new SDK client with the fallback RPC - const fallbackSdk = await createGmxClientWithRpc(sdk.account, FALLBACK_RPC_URL); - - console.log('✅ Fallback RPC client created, retrying positions request...'); - - // Recursively call the same function with the fallback SDK and incremented retry count - const result = await getGmxPositionsImpl(fallbackSdk, retryCount + 1); - - console.log('✅ Fallback RPC request successful'); - return result; - - } catch (fallbackError) { - console.error('❌ Fallback RPC also failed:', fallbackError); - // If fallback also fails, throw the original error - throw error; - } - } - - // If it's not a multicall timeout or we've exceeded retry limit, re-throw the error - if (isMulticallTimeout && retryCount >= MAX_RETRY_ATTEMPTS) { - console.error(`❌ Maximum retry attempts (${MAX_RETRY_ATTEMPTS}) exceeded. Both primary and fallback RPCs failed.`); - } - throw error; - } + position.open = open; + + return position; + }); + + return Promise.all(positions); + }, sdk, retryCount + ); }; /** @@ -1086,127 +1104,126 @@ export const getGmxRebateStatsImpl = async ( rebateFactor: number; discountFactor: number; } | null> => { - try { - // Get the referral storage contract address - - const referralStorageAddress = getContract(sdk.chainId, "ReferralStorage"); + return executeWithFallback( + async (sdk, retryCount) => { + // Get the referral storage contract address + + const referralStorageAddress = getContract(sdk.chainId, "ReferralStorage"); - // Get user referral code - const userRefCodeRes = await sdk.executeMulticall({ - referralStorage: { - contractAddress: referralStorageAddress, - abiId: "ReferralStorage", - calls: { - traderReferralCodes: { - methodName: "traderReferralCodes", - params: [sdk.account], - }, - }, - }, - }); - - console.log("traderReferralCodes", userRefCodeRes.data.referralStorage.traderReferralCodes.returnValues) - - const userReferralCode = userRefCodeRes.data.referralStorage.traderReferralCodes.returnValues[0]; - const userReferralCodeString = decodeReferralCode(userReferralCode); - - console.log("userReferralCodeAfterDecode", userReferralCodeString) - - // If no referral code, return default values - if (!userReferralCode || userReferralCode === zeroHash) { - return { - totalRebateUsd: 0, - discountUsd: 0, - volume: 0, - tier: 0, - rebateFactor: 0, - discountFactor: 0 - }; - } - - // Get code owner and affiliate tier - const [codeOwnerRes, affiliateTierRes] = await Promise.all([ - sdk.executeMulticall({ + // Get user referral code + const userRefCodeRes = await sdk.executeMulticall({ referralStorage: { contractAddress: referralStorageAddress, abiId: "ReferralStorage", calls: { - codeOwner: { - methodName: "codeOwners", - params: [userReferralCodeString], - }, - }, - }, - }), - sdk.executeMulticall({ - referralStorage: { - contractAddress: referralStorageAddress, - abiId: "ReferralStorage", - calls: { - referrerTiers: { - methodName: "referrerTiers", + traderReferralCodes: { + methodName: "traderReferralCodes", params: [sdk.account], }, }, }, - }) - ]); + }); - const codeOwner = codeOwnerRes.data.referralStorage.codeOwner.returnValues[0]; - const tierId = affiliateTierRes.data.referralStorage.referrerTiers.returnValues[0]; + console.log("traderReferralCodes", userRefCodeRes.data.referralStorage.traderReferralCodes.returnValues) - // Get tier information - const tiersRes = await sdk.executeMulticall({ - referralStorage: { - contractAddress: referralStorageAddress, - abiId: "ReferralStorage", - calls: { - tiers: { - methodName: "tiers", - params: [tierId], + const userReferralCode = userRefCodeRes.data.referralStorage.traderReferralCodes.returnValues[0]; + const userReferralCodeString = decodeReferralCode(userReferralCode); + + console.log("userReferralCodeAfterDecode", userReferralCodeString) + + // If no referral code, return default values + if (!userReferralCode || userReferralCode === zeroHash) { + return { + totalRebateUsd: 0, + discountUsd: 0, + volume: 0, + tier: 0, + rebateFactor: 0, + discountFactor: 0 + }; + } + + // Get code owner and affiliate tier + const [codeOwnerRes, affiliateTierRes] = await Promise.all([ + sdk.executeMulticall({ + referralStorage: { + contractAddress: referralStorageAddress, + abiId: "ReferralStorage", + calls: { + codeOwner: { + methodName: "codeOwners", + params: [userReferralCodeString], + }, + }, }, - }, - }, - }); + }), + sdk.executeMulticall({ + referralStorage: { + contractAddress: referralStorageAddress, + abiId: "ReferralStorage", + calls: { + referrerTiers: { + methodName: "referrerTiers", + params: [sdk.account], + }, + }, + }, + }) + ]); - const [totalRebate, discountShare] = tiersRes.data.referralStorage.tiers.returnValues ?? [0n, 0n]; + const codeOwner = codeOwnerRes.data.referralStorage.codeOwner.returnValues[0]; + const tierId = affiliateTierRes.data.referralStorage.referrerTiers.returnValues[0]; - // Get custom discount share if available - let customDiscountShare = 0n; - if (codeOwner) { - const customDiscountRes = await sdk.executeMulticall({ + // Get tier information + const tiersRes = await sdk.executeMulticall({ referralStorage: { contractAddress: referralStorageAddress, abiId: "ReferralStorage", calls: { - referrerDiscountShares: { - methodName: "referrerDiscountShares", - params: [codeOwner], + tiers: { + methodName: "tiers", + params: [tierId], }, }, }, }); - customDiscountShare = customDiscountRes.data.referralStorage.referrerDiscountShares.returnValues[0] || 0n; - } - const finalDiscountShare = customDiscountShare > 0n ? customDiscountShare : discountShare; + const [totalRebate, discountShare] = tiersRes.data.referralStorage.tiers.returnValues ?? [0n, 0n]; - // Convert bigint values to numbers for JSON serialization - const totalRebateFactor = basisPointsToFloat(totalRebate); - const discountFactor = basisPointsToFloat(finalDiscountShare); + // Get custom discount share if available + let customDiscountShare = 0n; + if (codeOwner) { + const customDiscountRes = await sdk.executeMulticall({ + referralStorage: { + contractAddress: referralStorageAddress, + abiId: "ReferralStorage", + calls: { + referrerDiscountShares: { + methodName: "referrerDiscountShares", + params: [codeOwner], + }, + }, + }, + }); + customDiscountShare = customDiscountRes.data.referralStorage.referrerDiscountShares.returnValues[0] || 0n; + } - return { - totalRebateUsd: Number(totalRebate) / 1e4, // Convert from basis points to decimal - discountUsd: Number(finalDiscountShare) / 1e4, // Convert from basis points to decimal - volume: 0, // Volume data would need to be fetched from different endpoint - tier: Number(tierId), - rebateFactor: Number(totalRebateFactor), - discountFactor: Number(discountFactor) - }; - } catch (error) { - console.error('Error getting GMX rebate stats:', error); - throw error; - } + const finalDiscountShare = customDiscountShare > 0n ? customDiscountShare : discountShare; + + // Convert bigint values to numbers for JSON serialization + const totalRebateFactor = basisPointsToFloat(totalRebate); + const discountFactor = basisPointsToFloat(finalDiscountShare); + + return { + totalRebateUsd: Number(totalRebate) / 1e4, // Convert from basis points to decimal + discountUsd: Number(finalDiscountShare) / 1e4, // Convert from basis points to decimal + volume: 0, // Volume data would need to be fetched from different endpoint + tier: Number(tierId), + rebateFactor: Number(totalRebateFactor), + discountFactor: Number(discountFactor) + }; + }, sdk, 0 + ); }; export async function getGmxRebateStats( @@ -1253,99 +1270,97 @@ interface FundingFeesClaimData { export const getClaimableFundingFeesImpl = async ( sdk: GmxSdk ): Promise => { - try { - const { marketsInfoData, tokensData } = await getMarketsInfoWithCache(sdk); - - if (!marketsInfoData || !tokensData) { - throw new Error("No markets info data or tokens data available"); - } - - const marketAddresses = Object.keys(marketsInfoData); - - if (marketAddresses.length === 0) { - return {}; - } - - // Build multicall request for all markets - const multicallRequest = marketAddresses.reduce((request, marketAddress) => { - const market = marketsInfoData[marketAddress]; + return executeWithFallback( + async (sdk, retryCount) => { + const { marketsInfoData, tokensData } = await getMarketsInfoWithCache(sdk); - if (!market) { + if (!marketsInfoData || !tokensData) { + throw new Error("No markets info data or tokens data available"); + } + + const marketAddresses = Object.keys(marketsInfoData); + + if (marketAddresses.length === 0) { + return {}; + } + + // Build multicall request for all markets + const multicallRequest = marketAddresses.reduce((request, marketAddress) => { + const market = marketsInfoData[marketAddress]; + + if (!market) { + return request; + } + + const keys = hashDataMap({ + claimableFundingAmountLong: [ + ["bytes32", "address", "address", "address"], + [CLAIMABLE_FUNDING_AMOUNT, marketAddress, market.longToken.address, sdk.account], + ], + claimableFundingAmountShort: [ + ["bytes32", "address", "address", "address"], + [CLAIMABLE_FUNDING_AMOUNT, marketAddress, market.shortToken.address, sdk.account], + ], + }); + + request[marketAddress] = { + contractAddress: getContract(sdk.chainId, "DataStore"), + abiId: "DataStore", + calls: { + claimableFundingAmountLong: { + methodName: "getUint", + params: [keys.claimableFundingAmountLong], + }, + claimableFundingAmountShort: { + methodName: "getUint", + params: [keys.claimableFundingAmountShort], + }, + }, + }; + return request; - } + }, {}); - const keys = hashDataMap({ - claimableFundingAmountLong: [ - ["bytes32", "address", "address", "address"], - [CLAIMABLE_FUNDING_AMOUNT, marketAddress, market.longToken.address, sdk.account], - ], - claimableFundingAmountShort: [ - ["bytes32", "address", "address", "address"], - [CLAIMABLE_FUNDING_AMOUNT, marketAddress, market.shortToken.address, sdk.account], - ], - }); + const result = await sdk.executeMulticall(multicallRequest); - request[marketAddress] = { - contractAddress: getContract(sdk.chainId, "DataStore"), - abiId: "DataStore", - calls: { - claimableFundingAmountLong: { - methodName: "getUint", - params: [keys.claimableFundingAmountLong], - }, - claimableFundingAmountShort: { - methodName: "getUint", - params: [keys.claimableFundingAmountShort], - }, - }, - }; + // Parse the response and convert to USD + return Object.entries(result.data).reduce((claimableFundingData, [marketAddress, callsResult]: [string, any]) => { + const market = marketsInfoData[marketAddress]; - return request; - }, {}); + if (!market) { + return claimableFundingData; + } - const result = await sdk.executeMulticall(multicallRequest); + // Get token data for price conversion + const longTokenData = tokensData[market.longToken.address]; + const shortTokenData = tokensData[market.shortToken.address]; - // Parse the response and convert to USD - return Object.entries(result.data).reduce((claimableFundingData, [marketAddress, callsResult]: [string, any]) => { - const market = marketsInfoData[marketAddress]; + if (!longTokenData || !shortTokenData) { + console.warn(`Missing token data for market ${marketAddress}`); + return claimableFundingData; + } + + // Convert from wei to token units and then to USD + const longAmount = Number(callsResult.claimableFundingAmountLong.returnValues[0]); + const shortAmount = Number(callsResult.claimableFundingAmountShort.returnValues[0]); + + // Convert from wei to token units using decimals + const longTokenUnits = longAmount / Math.pow(10, longTokenData.decimals); + const shortTokenUnits = shortAmount / Math.pow(10, shortTokenData.decimals); + + // Convert to USD using token prices + const longUsdValue = longTokenUnits * (Number(longTokenData.prices.minPrice) / Math.pow(10, 30)); // GMX prices are in 30 decimals + const shortUsdValue = shortTokenUnits * (Number(shortTokenData.prices.minPrice) / Math.pow(10, 30)); + + claimableFundingData[marketAddress] = { + claimableFundingAmountLong: longUsdValue, + claimableFundingAmountShort: shortUsdValue, + }; - if (!market) { return claimableFundingData; - } - - // Get token data for price conversion - const longTokenData = tokensData[market.longToken.address]; - const shortTokenData = tokensData[market.shortToken.address]; - - if (!longTokenData || !shortTokenData) { - console.warn(`Missing token data for market ${marketAddress}`); - return claimableFundingData; - } - - // Convert from wei to token units and then to USD - const longAmount = Number(callsResult.claimableFundingAmountLong.returnValues[0]); - const shortAmount = Number(callsResult.claimableFundingAmountShort.returnValues[0]); - - // Convert from wei to token units using decimals - const longTokenUnits = longAmount / Math.pow(10, longTokenData.decimals); - const shortTokenUnits = shortAmount / Math.pow(10, shortTokenData.decimals); - - // Convert to USD using token prices - const longUsdValue = longTokenUnits * (Number(longTokenData.prices.minPrice) / Math.pow(10, 30)); // GMX prices are in 30 decimals - const shortUsdValue = shortTokenUnits * (Number(shortTokenData.prices.minPrice) / Math.pow(10, 30)); - - claimableFundingData[marketAddress] = { - claimableFundingAmountLong: longUsdValue, - claimableFundingAmountShort: shortUsdValue, - }; - - return claimableFundingData; - }, {} as ClaimableFundingData); - - } catch (error) { - console.error('Error getting claimable funding fees:', error); - throw new Error(`Failed to get claimable funding fees: ${error instanceof Error ? error.message : 'Unknown error'}`); - } + }, {} as ClaimableFundingData); + }, sdk, 0 + ); }; /** @@ -1524,77 +1539,75 @@ interface ClaimableUiFeeData { export const getClaimableUiFeesImpl = async ( sdk: GmxSdk ): Promise => { - try { - const { marketsInfoData } = await getMarketsInfoWithCache(sdk); - - if (!marketsInfoData) { - throw new Error("No markets info data available"); - } - - const marketAddresses = Object.keys(marketsInfoData); - - if (marketAddresses.length === 0) { - return {}; - } - - // Get UI fee receiver from SDK config - const uiFeeReceiver = sdk.config.settings?.uiFeeReceiverAccount; - - // Build multicall request for all markets - const multicallRequest = marketAddresses.reduce((request, marketAddress) => { - const market = marketsInfoData[marketAddress]; + return executeWithFallback( + async (sdk, retryCount) => { + const { marketsInfoData } = await getMarketsInfoWithCache(sdk); - if (!market) { - return request; + if (!marketsInfoData) { + throw new Error("No markets info data available"); } - const CLAIMABLE_UI_FEE_AMOUNT = hashString("CLAIMABLE_UI_FEE_AMOUNT"); + const marketAddresses = Object.keys(marketsInfoData); + + if (marketAddresses.length === 0) { + return {}; + } - const keys = hashDataMap({ - claimableUiFeeAmount: [ - ["bytes32", "address", "address", "address"], - [CLAIMABLE_UI_FEE_AMOUNT, marketAddress, market.longToken.address, uiFeeReceiver], - ], - }); + // Get UI fee receiver from SDK config + const uiFeeReceiver = sdk.config.settings?.uiFeeReceiverAccount; - request[marketAddress] = { - contractAddress: getContract(sdk.chainId, "DataStore"), - abiId: "DataStore", - calls: { - claimableUiFeeAmount: { - methodName: "getUint", - params: [keys.claimableUiFeeAmount], + // Build multicall request for all markets + const multicallRequest = marketAddresses.reduce((request, marketAddress) => { + const market = marketsInfoData[marketAddress]; + + if (!market) { + return request; + } + + const CLAIMABLE_UI_FEE_AMOUNT = hashString("CLAIMABLE_UI_FEE_AMOUNT"); + + const keys = hashDataMap({ + claimableUiFeeAmount: [ + ["bytes32", "address", "address", "address"], + [CLAIMABLE_UI_FEE_AMOUNT, marketAddress, market.longToken.address, uiFeeReceiver], + ], + }); + + request[marketAddress] = { + contractAddress: getContract(sdk.chainId, "DataStore"), + abiId: "DataStore", + calls: { + claimableUiFeeAmount: { + methodName: "getUint", + params: [keys.claimableUiFeeAmount], + }, }, - }, - }; + }; - return request; - }, {}); + return request; + }, {}); - const result = await sdk.executeMulticall(multicallRequest); + const result = await sdk.executeMulticall(multicallRequest); - // Parse the response - return Object.entries(result.data).reduce((claimableUiFeeData, [marketAddress, callsResult]: [string, any]) => { - const market = marketsInfoData[marketAddress]; + // Parse the response + return Object.entries(result.data).reduce((claimableUiFeeData, [marketAddress, callsResult]: [string, any]) => { + const market = marketsInfoData[marketAddress]; + + if (!market) { + return claimableUiFeeData; + } + + // Convert from wei to token units (assuming 6 decimals for USDC-like tokens) + const tokenDecimals = market.longToken.decimals || 6; + + claimableUiFeeData[marketAddress] = { + claimableUiFeeAmount: Number(callsResult.claimableUiFeeAmount.returnValues[0]) / Math.pow(10, tokenDecimals), + }; - if (!market) { return claimableUiFeeData; - } - - // Convert from wei to token units (assuming 6 decimals for USDC-like tokens) - const tokenDecimals = market.longToken.decimals || 6; - - claimableUiFeeData[marketAddress] = { - claimableUiFeeAmount: Number(callsResult.claimableUiFeeAmount.returnValues[0]) / Math.pow(10, tokenDecimals), - }; - - return claimableUiFeeData; - }, {} as ClaimableUiFeeData); - - } catch (error) { - console.error('Error getting claimable UI fees:', error); - throw new Error(`Failed to get claimable UI fees: ${error instanceof Error ? error.message : 'Unknown error'}`); - } + }, {} as ClaimableUiFeeData); + }, sdk, 0 + ); }; /** @@ -1733,100 +1746,98 @@ export const swapGmxTokensImpl = async ( triggerRatio?: number, allowedSlippage: number = 0.5 ): Promise => { - try { - // Get markets and tokens data from GMX SDK with cache - const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk); + return executeWithFallback( + async (sdk, retryCount) => { + // Get markets and tokens data from GMX SDK with cache + const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk); - if (!marketsInfoData || !tokensData) { - throw new Error("No markets or tokens info data"); - } + if (!marketsInfoData || !tokensData) { + throw new Error("No markets or tokens info data"); + } - // Get token data for from and to tokens - const fromTokenData = getTokenDataFromTicker(fromTicker, tokensData); - const toTokenData = getTokenDataFromTicker(toTicker, tokensData); + // Get token data for from and to tokens + const fromTokenData = getTokenDataFromTicker(fromTicker, tokensData); + const toTokenData = getTokenDataFromTicker(toTicker, tokensData); - console.log("fromTokenData", fromTokenData); - if (!fromTokenData || !toTokenData) { - throw new Error(`Token data not found for ${fromTicker} or ${toTicker}`); - } + console.log("fromTokenData", fromTokenData); + if (!fromTokenData || !toTokenData) { + throw new Error(`Token data not found for ${fromTicker} or ${toTicker}`); + } - // Calculate the from token amount with proper decimals - const fromTokenAmount = BigInt(Math.floor(amount * Math.pow(10, fromTokenData.decimals))); + // Calculate the from token amount with proper decimals + const fromTokenAmount = BigInt(Math.floor(amount * Math.pow(10, fromTokenData.decimals))); - // Check and handle token allowance for ExchangeRouter contract - const syntheticsRouterRouterAddress = getContract(sdk.chainId, "SyntheticsRouter"); - - try { - const currentAllowance = await getTokenAllowance( - sdk.account, - fromTokenData.address, - syntheticsRouterRouterAddress - ); - - console.log(`Current allowance for ${fromTicker}:`, currentAllowance.toString()); - console.log(`Required amount:`, fromTokenAmount.toString()); - - if (currentAllowance < fromTokenAmount) { - console.log(`🔧 Insufficient allowance for ${fromTicker}. Auto-approving ExchangeRouter...`); - - // Calculate a reasonable approval amount (use the larger of required amount or 1000 tokens) - const tokenUnit = BigInt(Math.pow(10, fromTokenData.decimals)); - const minApprovalAmount = tokenUnit * 1000n; // 1000 tokens - const approvalAmount = fromTokenAmount > minApprovalAmount ? fromTokenAmount : minApprovalAmount; - - const approvalHash = await approveContractImpl( + // Check and handle token allowance for ExchangeRouter contract + const syntheticsRouterRouterAddress = getContract(sdk.chainId, "SyntheticsRouter"); + + try { + const currentAllowance = await getTokenAllowance( sdk.account, fromTokenData.address, - syntheticsRouterRouterAddress, - sdk.chainId, - approvalAmount + syntheticsRouterRouterAddress ); - - console.log(`✅ Token approval successful! Hash: ${approvalHash}`); - console.log(`📝 Approved ${approvalAmount.toString()} ${fromTicker} for ExchangeRouter`); - } else { - console.log(`✅ Sufficient allowance already exists for ${fromTicker}`); + + console.log(`Current allowance for ${fromTicker}:`, currentAllowance.toString()); + console.log(`Required amount:`, fromTokenAmount.toString()); + + if (currentAllowance < fromTokenAmount) { + console.log(`🔧 Insufficient allowance for ${fromTicker}. Auto-approving ExchangeRouter...`); + + // Calculate a reasonable approval amount (use the larger of required amount or 1000 tokens) + const tokenUnit = BigInt(Math.pow(10, fromTokenData.decimals)); + const minApprovalAmount = tokenUnit * 1000n; // 1000 tokens + const approvalAmount = fromTokenAmount > minApprovalAmount ? fromTokenAmount : minApprovalAmount; + + const approvalHash = await approveContractImpl( + sdk.account, + fromTokenData.address, + syntheticsRouterRouterAddress, + sdk.chainId, + approvalAmount + ); + + console.log(`✅ Token approval successful! Hash: ${approvalHash}`); + console.log(`📝 Approved ${approvalAmount.toString()} ${fromTicker} for ExchangeRouter`); + } else { + console.log(`✅ Sufficient allowance already exists for ${fromTicker}`); + } + } catch (allowanceError) { + console.warn('Could not check or approve token allowance:', allowanceError); + throw new Error(`Failed to handle token allowance: ${allowanceError instanceof Error ? allowanceError.message : 'Unknown error'}`); } - } catch (allowanceError) { - console.warn('Could not check or approve token allowance:', allowanceError); - throw new Error(`Failed to handle token allowance: ${allowanceError instanceof Error ? allowanceError.message : 'Unknown error'}`); - } - // Calculate trigger price for limit orders - let triggerPrice: bigint | undefined; - if (orderType === 'limit' && triggerRatio) { - // Convert trigger ratio to price format (30 decimals) - triggerPrice = BigInt(Math.floor(triggerRatio * Math.pow(10, 30))); - } + // Calculate trigger price for limit orders + let triggerPrice: bigint | undefined; + if (orderType === 'limit' && triggerRatio) { + // Convert trigger ratio to price format (30 decimals) + triggerPrice = BigInt(Math.floor(triggerRatio * Math.pow(10, 30))); + } - // Convert slippage percentage to basis points - const allowedSlippageBps = Math.floor(allowedSlippage * 100); // Convert percentage to basis points + // Convert slippage percentage to basis points + const allowedSlippageBps = Math.floor(allowedSlippage * 100); // Convert percentage to basis points - // Create SwapParams for the SDK - const swapParams = { - fromAmount: fromTokenAmount, - fromTokenAddress: fromTokenData.address, - toTokenAddress: toTokenData.address, - allowedSlippageBps: allowedSlippageBps, - referralCodeForTxn: encodeReferralCode("kaigen_ai"), - triggerPrice: triggerPrice, // Only defined for limit orders - skipSimulation: true, - } as SwapParams; + // Create SwapParams for the SDK + const swapParams = { + fromAmount: fromTokenAmount, + fromTokenAddress: fromTokenData.address, + toTokenAddress: toTokenData.address, + allowedSlippageBps: allowedSlippageBps, + referralCodeForTxn: encodeReferralCode("kaigen_ai"), + triggerPrice: triggerPrice, // Only defined for limit orders + skipSimulation: true, + } as SwapParams; - console.log("swapParams", swapParams); + console.log("swapParams", swapParams); - swapParams.marketsInfoData = marketsInfoData; - swapParams.tokensData = tokensData; + swapParams.marketsInfoData = marketsInfoData; + swapParams.tokensData = tokensData; + // Use the SDK's built-in swap method + await sdk.orders.swap(swapParams); - // Use the SDK's built-in swap method - await sdk.orders.swap(swapParams); - - return "swap_order_created"; - } catch (error) { - console.error('Error swapping GMX tokens:', error); - throw new Error(`Failed to swap tokens: ${error instanceof Error ? error.message : 'Unknown error'}`); - } + return "swap_order_created"; + }, sdk, 0 + ); }; /**