diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index 12eeb8b..34a2916 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -1,35 +1,42 @@ import fp from 'fastify-plugin' -import { FastifyReply, FastifyRequest } from 'fastify' -import { z } from 'zod' -import { GmxSdk } from '../../generated/gmxsdk/index.js' +import {FastifyReply, FastifyRequest} from 'fastify' +import {z} from 'zod' +import {GmxSdk} from '../../generated/gmxsdk/index.js' -import { arbitrum } from 'viem/chains'; -import { getTokenBySymbol } from '../../generated/gmxsdk/configs/tokens.js'; -import { Trade, TradeDirection, TradeStatus, TradeType, Position, PositionStatus } from '../../generated/ManagingApiTypes.js'; -import { MarketInfo, MarketsInfoData } from '../../generated/gmxsdk/types/markets.js'; -import { MarketConfig, MARKETS } from '../../generated/gmxsdk/configs/markets.js' -import { ARBITRUM } from '../../generated/gmxsdk/configs/chains.js' -import { TokenData, TokensData } from '../../generated/gmxsdk/types/tokens.js'; -import { getByKey } from '../../generated/gmxsdk/utils/objects.js'; -import { GmxSdkConfig } from '../../generated/gmxsdk/types/sdk.js'; -import { PositionIncreaseParams } from '../../generated/gmxsdk/modules/orders/helpers.js'; -import { numberToBigint, PRECISION_DECIMALS } from '../../generated/gmxsdk/utils/numbers.js'; -import { DecreasePositionSwapType, PositionOrderInfo } from '../../generated/gmxsdk/types/orders.js'; -import { OrderType } from '../../generated/gmxsdk/types/orders.js'; -import { DecreasePositionAmounts } from '../../generated/gmxsdk/types/trade.js'; -import { encodeReferralCode } from '../../generated/gmxsdk/utils/referrals.js'; -import { convertToUsd } from '../../generated/gmxsdk/utils/tokens.js'; -import { toNumber } from 'ethers'; -import { bigintToNumber } from '../../generated/gmxsdk/utils/numbers.js'; -import { formatUsd } from '../../generated/gmxsdk/utils/numbers/formatting.js'; -import { calculateDisplayDecimals } from '../../generated/gmxsdk/utils/numbers/index.js'; +import {arbitrum} from 'viem/chains'; +import {getTokenBySymbol} from '../../generated/gmxsdk/configs/tokens.js'; +import { + Trade, + TradeDirection, + TradeStatus, + TradeType, + Position, + PositionStatus +} from '../../generated/ManagingApiTypes.js'; +import {MarketInfo, MarketsInfoData} from '../../generated/gmxsdk/types/markets.js'; +import {MarketConfig, MARKETS} from '../../generated/gmxsdk/configs/markets.js' +import {ARBITRUM} from '../../generated/gmxsdk/configs/chains.js' +import {TokenData, TokensData} from '../../generated/gmxsdk/types/tokens.js'; +import {getByKey} from '../../generated/gmxsdk/utils/objects.js'; +import {GmxSdkConfig} from '../../generated/gmxsdk/types/sdk.js'; +import {PositionIncreaseParams} from '../../generated/gmxsdk/modules/orders/helpers.js'; +import {numberToBigint, PRECISION_DECIMALS} from '../../generated/gmxsdk/utils/numbers.js'; +import {DecreasePositionSwapType, PositionOrderInfo} from '../../generated/gmxsdk/types/orders.js'; +import {OrderType} from '../../generated/gmxsdk/types/orders.js'; +import {DecreasePositionAmounts} from '../../generated/gmxsdk/types/trade.js'; +import {encodeReferralCode} from '../../generated/gmxsdk/utils/referrals.js'; +import {convertToUsd} from '../../generated/gmxsdk/utils/tokens.js'; +import {toNumber} from 'ethers'; +import {bigintToNumber} from '../../generated/gmxsdk/utils/numbers.js'; +import {formatUsd} from '../../generated/gmxsdk/utils/numbers/formatting.js'; +import {calculateDisplayDecimals} from '../../generated/gmxsdk/utils/numbers/index.js'; /** * GMX Plugin - * + * * This plugin adds functionality for interacting with the GMX trading platform, * including opening positions, checking positions, and more. - * + * * Position Opening Process: * 1. Client sends a request to /open-position with: * - account: The user's wallet address @@ -39,7 +46,7 @@ import { calculateDisplayDecimals } from '../../generated/gmxsdk/utils/numbers/i * - price: The price limit (optional for market orders) * - quantity: The quantity to trade * - leverage: The leverage multiplier (e.g., 2x, 10x) - * + * * 2. The server handles the request by: * - Validating the input parameters * - Getting market and token data from GMX SDK @@ -48,31 +55,31 @@ import { calculateDisplayDecimals } from '../../generated/gmxsdk/utils/numbers/i */ declare module 'fastify' { - export interface FastifyRequest { - openGmxPosition: typeof openGmxPosition; - getClientForAddress: typeof getClientForAddress; - cancelGmxOrders: typeof cancelGmxOrders; - closeGmxPosition: typeof closeGmxPosition; - getGmxTrade: typeof getGmxTrade; - getGmxPositions: typeof getGmxPositions; - } + export interface FastifyRequest { + openGmxPosition: typeof openGmxPosition; + getClientForAddress: typeof getClientForAddress; + cancelGmxOrders: typeof cancelGmxOrders; + closeGmxPosition: typeof closeGmxPosition; + getGmxTrade: typeof getGmxTrade; + getGmxPositions: typeof getGmxPositions; + } } // Schema for open-position request const openPositionSchema = z.object({ - account: z.string().nonempty(), - tradeType: z.enum(['market', 'limit']), - ticker: z.string().nonempty(), - direction: z.enum(Object.keys(TradeDirection) as [string, ...string[]]), - price: z.number().positive().optional(), - quantity: z.number().positive(), - leverage: z.number().positive() + account: z.string().nonempty(), + tradeType: z.enum(['market', 'limit']), + ticker: z.string().nonempty(), + direction: z.enum(Object.keys(TradeDirection) as [string, ...string[]]), + price: z.number().positive().optional(), + quantity: z.number().positive(), + leverage: z.number().positive() }); // Schema for cancel-orders request const cancelOrdersSchema = z.object({ - account: z.string().nonempty(), - ticker: z.string().nonempty() + account: z.string().nonempty(), + ticker: z.string().nonempty() }); /** @@ -83,26 +90,26 @@ const cancelOrdersSchema = z.object({ * @returns An initialized GMX SDK client */ export function getClientForAddress( - account: string, + account: string, ): GmxSdk { - try { - // Create SDK instance - const arbitrumSdkConfig: GmxSdkConfig = { - chainId: arbitrum.id, - account: account, - oracleUrl: "https://arbitrum-api.gmxinfra.io", - rpcUrl: "https://arb1.arbitrum.io/rpc", - subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:live/api/graphql", - subgraphUrl: "https://subgraph.satsuma-prod.com/3b2ced13c8d9/gmx/synthetics-arbitrum-stats/api", - }; - - const sdk = new GmxSdk(arbitrumSdkConfig) + try { + // Create SDK instance + const arbitrumSdkConfig: GmxSdkConfig = { + chainId: arbitrum.id, + account: account, + oracleUrl: "https://arbitrum-api.gmxinfra.io", + rpcUrl: "https://arb1.arbitrum.io/rpc", + subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:live/api/graphql", + subgraphUrl: "https://subgraph.satsuma-prod.com/3b2ced13c8d9/gmx/synthetics-arbitrum-stats/api", + }; - return sdk; - } catch (error) { - console.error(`Error getting GMX client: ${error instanceof Error ? error.message : 'Unknown error'}`); - throw error; - } + const sdk = new GmxSdk(arbitrumSdkConfig) + + return sdk; + } catch (error) { + console.error(`Error getting GMX client: ${error instanceof Error ? error.message : 'Unknown error'}`); + throw error; + } } @@ -120,66 +127,66 @@ export function getClientForAddress( * @returns The transaction hash */ export const openGmxPositionImpl = async ( - sdk: GmxSdk, - ticker: string, - direction: TradeDirection, - quantity: number, - leverage: number, - price?: number, - stopLossPrice?: number, - takeProfitPrice?: number + sdk: GmxSdk, + ticker: string, + direction: TradeDirection, + quantity: number, + leverage: number, + price?: number, + stopLossPrice?: number, + takeProfitPrice?: number ): Promise => { - try { - // Get markets and tokens data from GMX SDK - const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo(); + try { + // Get markets and tokens data from GMX SDK + 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"); + } + + console.log('price', price) + console.log('stopLossPrice', stopLossPrice) + console.log('takeProfitPrice', takeProfitPrice) + + const marketInfo = getMarketInfoFromTicker(ticker, marketsInfoData); + const collateralToken = getTokenDataFromTicker("USDC", tokensData); // Using USDC as collateral + console.log('collateralToken', collateralToken) + + // Calculate the collateral amount in USDC (quantity * price) + const collateralAmount = BigInt(Math.floor((quantity || 0) * (price || 0) * 1e6)); // USDC has 6 decimals + console.log('collateralAmount', collateralAmount); + + // 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: 100, // 1% slippage + leverage: leverageBps, + skipSimulation: true, + limitPrice: limitPrice, + referralCodeForTxn: encodeReferralCode("kaigen_ai"), + stopLossPrice: stopLossPrice ? numberToBigint(stopLossPrice, 30) : undefined, + takeProfitPrice: takeProfitPrice ? numberToBigint(takeProfitPrice, 30) : undefined + }; + + console.log('params', params) + + if (direction === TradeDirection.Long) { + await sdk.orders.long(params); + } else { + await sdk.orders.short(params); + } + + return ""; + } catch (error) { + console.error('Error opening GMX position:', error); + throw new Error(`Failed to open position: ${error instanceof Error ? error.message : 'Unknown error'}`); } - - console.log('price', price) - console.log('stopLossPrice', stopLossPrice) - console.log('takeProfitPrice', takeProfitPrice) - - const marketInfo = getMarketInfoFromTicker(ticker, marketsInfoData); - const collateralToken = getTokenDataFromTicker("USDC", tokensData); // Using USDC as collateral - console.log('collateralToken', collateralToken) - - // Calculate the collateral amount in USDC (quantity * price) - const collateralAmount = BigInt(Math.floor((quantity || 0) * (price || 0) * 1e6)); // USDC has 6 decimals - console.log('collateralAmount', collateralAmount); - - // 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: 100, // 1% slippage - leverage: leverageBps, - skipSimulation: true, - limitPrice: null, - referralCodeForTxn: encodeReferralCode("kaigen_ai"), - stopLossPrice: stopLossPrice ? numberToBigint(stopLossPrice, 30) : undefined, - takeProfitPrice: takeProfitPrice ? numberToBigint(takeProfitPrice, 30) : undefined - }; - - console.log('params', params) - - if (direction === TradeDirection.Long) { - await sdk.orders.long(params); - } else { - await sdk.orders.short(params); - } - - return ""; - } catch (error) { - console.error('Error opening GMX position:', error); - throw new Error(`Failed to open position: ${error instanceof Error ? error.message : 'Unknown error'}`); - } }; @@ -211,51 +218,51 @@ export async function openGmxPosition( leverage?: number, stopLossPrice?: number, takeProfitPrice?: number - ) { +) { try { - // Validate the request parameters - openPositionSchema.parse({ - account, - tradeType, - ticker, - direction, - price, - quantity, - leverage, - stopLossPrice, - takeProfitPrice - }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const hash = await openGmxPositionImpl( - sdk, - ticker, - direction, - quantity, - leverage, - price, - stopLossPrice, - takeProfitPrice - ); - - return { - success: true, - hash - }; + // Validate the request parameters + openPositionSchema.parse({ + account, + tradeType, + ticker, + direction, + price, + quantity, + leverage, + stopLossPrice, + takeProfitPrice + }); + + // Get client for the address + const sdk = await this.getClientForAddress(account); + + // Call the implementation function + const hash = await openGmxPositionImpl( + sdk, + ticker, + direction, + quantity, + leverage, + price, + stopLossPrice, + takeProfitPrice + ); + + return { + success: true, + hash + }; } catch (error) { - this.log.error(error); - - // Return appropriate error response - reply.status(error instanceof z.ZodError ? 400 : 500); - return { - success: false, - error: error instanceof Error ? error.message : 'An unknown error occurred' - }; + this.log.error(error); + + // Return appropriate error response + reply.status(error instanceof z.ZodError ? 400 : 500); + return { + success: false, + error: error instanceof Error ? error.message : 'An unknown error occurred' + }; } - } +} /** * Implementation function to cancel all GMX orders for a ticker @@ -265,50 +272,50 @@ export async function openGmxPosition( * @returns Whether the cancellation was successful */ export const cancelGmxOrdersImpl = async ( - sdk: GmxSdk, - ticker: string + sdk: GmxSdk, + ticker: string ): Promise => { - try { - // Get markets and tokens data first - const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo(); + try { + // 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"); - } - - // Get active orders for the account with required parameters - const ordersData = await sdk.orders.getOrders({ - account: sdk.account, - marketsInfoData, - tokensData - }); - - console.log('ordersData', ordersData) - - // 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(); + if (!marketsInfoData || !tokensData) { + throw new Error("No markets or tokens info data"); } - return false; - }) - .map(order => order.key); - - if (orderKeys.length === 0) { - return true; // No orders to cancel - } - console.log('orderKeys', 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'}`); - } + // Get active orders for the account with required parameters + const ordersData = await sdk.orders.getOrders({ + account: sdk.account, + marketsInfoData, + tokensData + }); + + console.log('ordersData', ordersData) + + // 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 + } + + console.log('orderKeys', 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'}`); + } }; @@ -322,41 +329,41 @@ export const cancelGmxOrdersImpl = async ( * @returns The response object with success status */ export async function cancelGmxOrders( - this: FastifyRequest, - reply: FastifyReply, - account: string, - ticker: string, + this: FastifyRequest, + reply: FastifyReply, + account: string, + ticker: string, ) { - try { - // Validate the request parameters - cancelOrdersSchema.parse({ - account, - ticker - }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const success = await cancelGmxOrdersImpl(sdk, ticker); - - return { - success - }; - } catch (error) { - this.log.error(error); - - // Return appropriate error response - reply.status(error instanceof z.ZodError ? 400 : 500); - return { - success: false, - error: error instanceof Error ? error.message : 'An unknown error occurred' - }; - } + try { + // Validate the request parameters + cancelOrdersSchema.parse({ + account, + ticker + }); + + // Get client for the address + const sdk = await this.getClientForAddress(account); + + // Call the implementation function + const success = await cancelGmxOrdersImpl(sdk, ticker); + + return { + success + }; + } catch (error) { + this.log.error(error); + + // Return appropriate error response + reply.status(error instanceof z.ZodError ? 400 : 500); + return { + success: false, + error: error instanceof Error ? error.message : 'An unknown error occurred' + }; + } } function getMarketInfoFromTicker(ticker: string, marketsInfoData: MarketsInfoData): MarketInfo { - const token = getTokenBySymbol(arbitrum.id, ticker); + const token = getTokenBySymbol(arbitrum.id, ticker); const marketInfo = getMarketByIndexToken(token.address); const marketInfoData = marketsInfoData[marketInfo.marketTokenAddress]; if (!marketInfo) { @@ -376,19 +383,19 @@ export function getTokenDataFromTicker(ticker: string, tokensData: TokensData): * @returns The market configuration if found, undefined otherwise */ export function getMarketByIndexToken(indexTokenAddress: string): MarketConfig | undefined { - // Convert the input address to lowercase for case-insensitive comparison - const normalizedIndexToken = indexTokenAddress.toLowerCase() - - // Get the markets for Arbitrum chain - const arbitrumMarkets = MARKETS[ARBITRUM] - - // Find the market where the indexTokenAddress matches - const marketKey = Object.keys(arbitrumMarkets).find(key => { - const market = arbitrumMarkets[key] - return market.indexTokenAddress.toLowerCase() === normalizedIndexToken - }) - - return marketKey ? arbitrumMarkets[marketKey] : undefined + // Convert the input address to lowercase for case-insensitive comparison + const normalizedIndexToken = indexTokenAddress.toLowerCase() + + // Get the markets for Arbitrum chain + const arbitrumMarkets = MARKETS[ARBITRUM] + + // Find the market where the indexTokenAddress matches + const marketKey = Object.keys(arbitrumMarkets).find(key => { + const market = arbitrumMarkets[key] + return market.indexTokenAddress.toLowerCase() === normalizedIndexToken + }) + + return marketKey ? arbitrumMarkets[marketKey] : undefined } /** @@ -399,96 +406,96 @@ export function getMarketByIndexToken(indexTokenAddress: string): MarketConfig | * @returns The transaction hash */ export const closeGmxPositionImpl = async ( - sdk: GmxSdk, - ticker: string, - direction: TradeDirection + sdk: GmxSdk, + ticker: string, + direction: TradeDirection ): Promise => { - try { - // Get markets and tokens data from GMX SDK - const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo(); + try { + // Get markets and tokens data from GMX SDK + 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); + + // Get the user's position + const positionsInfo = await sdk.positions.getPositionsInfo({ + marketsInfoData, + tokensData, + showPnlInLeverage: true + }); + + console.log('positionsInfo', positionsInfo) + console.log('direction', direction) + // 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.Short); + }); + + if (!positionKey) { + throw new Error(`No open ${direction} position found for ${ticker}`); + } + + const position = positionsInfo[positionKey]; + + 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.LimitDecrease, + triggerPrice: position.markPrice, + } + + //console.log('params', params) + + const params2 = { + marketInfo, + marketsInfoData, + tokensData, + isLong: direction === TradeDirection.Short, + allowedSlippage: 30, + decreaseAmounts, + collateralToken: position.marketInfo.shortToken, + referralCodeForTxn: encodeReferralCode("kaigen_ai"), + } + + // 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'}`); } - - const marketInfo = getMarketInfoFromTicker(ticker, marketsInfoData); - - // Get the user's position - const positionsInfo = await sdk.positions.getPositionsInfo({ - marketsInfoData, - tokensData, - showPnlInLeverage: true - }); - - console.log('positionsInfo', positionsInfo) - console.log('direction', direction) - // 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.Short); - }); - - if (!positionKey) { - throw new Error(`No open ${direction} position found for ${ticker}`); - } - - const position = positionsInfo[positionKey]; - - 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.LimitDecrease, - triggerPrice: position.markPrice, - } - - //console.log('params', params) - - const params2 = { - marketInfo, - marketsInfoData, - tokensData, - isLong: direction === TradeDirection.Short, - allowedSlippage: 30, - decreaseAmounts, - collateralToken: position.marketInfo.shortToken, - referralCodeForTxn: encodeReferralCode("kaigen_ai"), - } - - // 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'}`); - } }; /** @@ -501,37 +508,37 @@ export const closeGmxPositionImpl = async ( * @returns The response object with success status and transaction hash */ export async function closeGmxPosition( - this: FastifyRequest, - reply: FastifyReply, - account: string, - ticker: string, - direction: TradeDirection + this: FastifyRequest, + reply: FastifyReply, + account: string, + ticker: string, + direction: TradeDirection ) { - try { - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const hash = await closeGmxPositionImpl( - sdk, - ticker, - direction - ); - - return { - success: true, - hash - }; - } catch (error) { - this.log.error(error); - - // Return appropriate error response - reply.status(500); - return { - success: false, - error: error instanceof Error ? error.message : 'An unknown error occurred' - }; - } + try { + // Get client for the address + const sdk = await this.getClientForAddress(account); + + // Call the implementation function + const hash = await closeGmxPositionImpl( + sdk, + ticker, + direction + ); + + return { + success: true, + hash + }; + } catch (error) { + this.log.error(error); + + // Return appropriate error response + reply.status(500); + return { + success: false, + error: error instanceof Error ? error.message : 'An unknown error occurred' + }; + } } /** @@ -541,86 +548,86 @@ export async function closeGmxPosition( * @returns Array of trades */ export const getGmxTradeImpl = async ( - sdk: GmxSdk, - ticker: string + sdk: GmxSdk, + ticker: string ): Promise => { - const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo(); + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); - 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 - 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); - console.log('Calculated leverage:', leverage, 'from size:', size, 'collateral:', 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 - 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; - - 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), - exchangeOrderId: order.key - } as Trade; + const orders = await sdk.orders.getOrders({ + account: sdk.account, + marketsInfoData, + tokensData }); - return trades; + + // 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 + 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); + console.log('Calculated leverage:', leverage, 'from size:', size, 'collateral:', 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 + 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; + + 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), + exchangeOrderId: order.key + } as Trade; + }); + + return trades; } /** @@ -632,32 +639,32 @@ export const getGmxTradeImpl = async ( * @returns The response object with success status and trades array */ export async function getGmxTrade( - this: FastifyRequest, - reply: FastifyReply, - account: string, - ticker: string + this: FastifyRequest, + reply: FastifyReply, + account: string, + ticker: string ) { - try { - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const trades = await getGmxTradeImpl(sdk, ticker); - - return { - success: true, - trades - }; - } catch (error) { - this.log.error(error); - - // Return appropriate error response - reply.status(500); - return { - success: false, - error: error instanceof Error ? error.message : 'An unknown error occurred' - }; - } + try { + // Get client for the address + const sdk = await this.getClientForAddress(account); + + // Call the implementation function + const trades = await getGmxTradeImpl(sdk, ticker); + + return { + success: true, + trades + }; + } catch (error) { + this.log.error(error); + + // Return appropriate error response + reply.status(500); + return { + success: false, + error: error instanceof Error ? error.message : 'An unknown error occurred' + }; + } } /** @@ -666,124 +673,124 @@ export async function getGmxTrade( * @returns Array of positions */ export const getGmxPositionsImpl = async ( - sdk: GmxSdk + sdk: GmxSdk ): Promise => { - const { marketsInfoData, tokensData } = await sdk.markets.getMarketsInfo(); + const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo(); - 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 = 2; // Default to 2x 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 - leverage = Math.round(Number(pos.sizeInUsd) / Number(pos.collateralAmount)); - } - } - - 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); - - // 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 positionsInfo = await sdk.positions.getPositionsInfo({ + marketsInfoData, + tokensData, + showPnlInLeverage: true, }); - const displayMarkPrice = formatUsd(pos.markPrice, { - displayDecimals: priceDecimals, - visualMultiplier: pos.indexToken?.visualMultiplier, + const positions = Object.values(positionsInfo).map(async (pos) => { + // Fix leverage calculation to avoid exponential notation issues + let leverage = 2; // Default to 2x 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 + leverage = Math.round(Number(pos.sizeInUsd) / Number(pos.collateralAmount)); + } + } + + 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); + + // 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, { + 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, '')); + } + + // Make sure we scale the token quantity properly + const tokenSize = bigintToNumber(pos.sizeInTokens, pos.marketInfo.indexToken.decimals); + + let stopLoss: Trade | undefined; + let takeProfit: Trade | undefined; + + 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); + } + + 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 + 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 + + if (stopLoss) { + position.stopLoss = stopLoss; + } + + if (takeProfit) { + position.takeProfit1 = takeProfit; + } + + // 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; + + position.open = open; + + return position; }); - - let displayLiquidationPrice = undefined; - if (pos.liquidationPrice) { - displayLiquidationPrice = formatUsd(pos.liquidationPrice, { - 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, '')); - } - - // Make sure we scale the token quantity properly - const tokenSize = bigintToNumber(pos.sizeInTokens, pos.marketInfo.indexToken.decimals); - - let stopLoss: Trade | undefined; - let takeProfit: Trade | undefined; - - 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); - } - - 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 - 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 - - if (stopLoss) { - position.stopLoss = stopLoss; - } - - if (takeProfit) { - position.takeProfit1 = takeProfit; - } - - // 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; - - position.open = open; - - return position; - }); - return Promise.all(positions); + return Promise.all(positions); }; /** @@ -794,25 +801,25 @@ export const getGmxPositionsImpl = async ( * @returns The response object with success status and positions array */ export async function getGmxPositions( - this: FastifyRequest, - reply: FastifyReply, - account: string + this: FastifyRequest, + reply: FastifyReply, + account: string ) { - try { - const sdk = await this.getClientForAddress(account); - const positions = await getGmxPositionsImpl(sdk); - return { - success: true, - positions, - }; - } catch (error) { - this.log.error(error); - reply.status(500); - return { - success: false, - error: error instanceof Error ? error.message : 'An unknown error occurred', - }; - } + try { + const sdk = await this.getClientForAddress(account); + const positions = await getGmxPositionsImpl(sdk); + return { + success: true, + positions, + }; + } catch (error) { + this.log.error(error); + reply.status(500); + return { + success: false, + error: error instanceof Error ? error.message : 'An unknown error occurred', + }; + } } /** @@ -829,5 +836,5 @@ export default fp(async (fastify) => { fastify.decorateRequest('closeGmxPosition', closeGmxPosition) fastify.decorateRequest('getGmxTrade', getGmxTrade) fastify.decorateRequest('getGmxPositions', getGmxPositions) - }) +})