diff --git a/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/orders/orders.ts b/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/orders/orders.ts index a7577a9..ca3afe6 100644 --- a/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/orders/orders.ts +++ b/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/orders/orders.ts @@ -15,10 +15,10 @@ import {getOrderInfo, isOrderForPositionByData, isVisibleOrder} from "../../util import {createDecreaseOrderTxn} from "./transactions/createDecreaseOrderTxn.js"; import {createIncreaseOrderTxn} from "./transactions/createIncreaseOrderTxn.js"; import { - buildGetOrdersMulticall, - getExecutionFeeAmountForEntry, - matchByMarket, - parseGetOrdersResponse + buildGetOrdersMulticall, + getExecutionFeeAmountForEntry, + matchByMarket, + parseGetOrdersResponse } from "./utils.js"; import {Module} from "../base.js"; import {createSwapOrderTxn} from "./transactions/createSwapOrderTxn.js"; diff --git a/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/orders/transactions/createSwapOrderTxn.ts b/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/orders/transactions/createSwapOrderTxn.ts index 7341397..597127f 100644 --- a/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/orders/transactions/createSwapOrderTxn.ts +++ b/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/orders/transactions/createSwapOrderTxn.ts @@ -9,6 +9,7 @@ import {TokensData} from "../../../types/tokens.js"; import {isMarketOrderType} from "../../../utils/orders.js"; import {applySlippageToMinOut} from "../../../utils/trade/index.js"; +import {simulateExecuteOrder} from "../../../utils/simulateExecuteOrder.js"; import type {GmxSdk} from "../../.."; @@ -30,7 +31,14 @@ export async function createSwapOrderTxn(sdk: GmxSdk, p: SwapOrderParams) { const { encodedPayload, totalWntAmount } = await getParams(sdk, p); const { encodedPayload: simulationEncodedPayload, totalWntAmount: sumaltionTotalWntAmount } = await getParams(sdk, p); - + if (p.orderType !== OrderType.LimitSwap) { + await simulateExecuteOrder(sdk, { + primaryPriceOverrides: {}, + createMulticallPayload: simulationEncodedPayload, + value: sumaltionTotalWntAmount, + tokensData: p.tokensData, + }); + } await sdk.callContract( getContract(sdk.chainId, "ExchangeRouter"), @@ -68,7 +76,7 @@ async function getParams(sdk: GmxSdk, p: SwapOrderParams) { callbackContract: zeroAddress, market: zeroAddress, swapPath: p.swapPath, - uiFeeReceiver: sdk.config.settings?.uiFeeReceiverAccount ?? zeroAddress, + uiFeeReceiver: zeroAddress, }, numbers: { sizeDeltaUsd: 0n, diff --git a/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/utils/utils.ts b/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/utils/utils.ts index 8c7e981..deff996 100644 --- a/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/utils/utils.ts +++ b/src/Managing.Web3Proxy/src/generated/gmxsdk/modules/utils/utils.ts @@ -1,27 +1,27 @@ import {withRetry} from "viem"; import { - EXECUTION_FEE_CONFIG_V2, - GAS_PRICE_PREMIUM_MAP, - getChain, - MAX_PRIORITY_FEE_PER_GAS_MAP + EXECUTION_FEE_CONFIG_V2, + GAS_PRICE_PREMIUM_MAP, + getChain, + MAX_PRIORITY_FEE_PER_GAS_MAP } from "../../configs/chains.js"; import {getContract} from "../../configs/contracts.js"; import { - decreaseOrderGasLimitKey, - depositGasLimitKey, - ESTIMATED_GAS_FEE_BASE_AMOUNT_V2_1, - ESTIMATED_GAS_FEE_MULTIPLIER_FACTOR, - ESTIMATED_GAS_FEE_PER_ORACLE_PRICE, - GLV_DEPOSIT_GAS_LIMIT, - GLV_PER_MARKET_GAS_LIMIT, - GLV_WITHDRAWAL_GAS_LIMIT, - increaseOrderGasLimitKey, - shiftGasLimitKey, - singleSwapGasLimitKey, - swapOrderGasLimitKey, - uiFeeFactorKey, - withdrawalGasLimitKey, + decreaseOrderGasLimitKey, + depositGasLimitKey, + ESTIMATED_GAS_FEE_BASE_AMOUNT_V2_1, + ESTIMATED_GAS_FEE_MULTIPLIER_FACTOR, + ESTIMATED_GAS_FEE_PER_ORACLE_PRICE, + GLV_DEPOSIT_GAS_LIMIT, + GLV_PER_MARKET_GAS_LIMIT, + GLV_WITHDRAWAL_GAS_LIMIT, + increaseOrderGasLimitKey, + shiftGasLimitKey, + singleSwapGasLimitKey, + swapOrderGasLimitKey, + uiFeeFactorKey, + withdrawalGasLimitKey, } from "../../configs/dataStore.js"; import type {DecreasePositionAmounts, IncreasePositionAmounts, SwapAmounts, TradeFeesType} from "../../types/trade.js"; @@ -31,10 +31,10 @@ import {TokensData} from "../../types/tokens.js"; import {bigMath} from "../../utils/bigmath.js"; import {estimateOrderOraclePriceCount} from "../../utils/fees/estimateOraclePriceCount.js"; import { - estimateExecuteDecreaseOrderGasLimit, - estimateExecuteIncreaseOrderGasLimit, - estimateExecuteSwapOrderGasLimit, - getExecutionFee, + estimateExecuteDecreaseOrderGasLimit, + estimateExecuteIncreaseOrderGasLimit, + estimateExecuteSwapOrderGasLimit, + getExecutionFee, } from "../../utils/fees/executionFee.js"; import {getSwapCount} from "../../utils/trade/index.js"; diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index 79faaea..08e5d4c 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -19,31 +19,14 @@ 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, SwapParams} from '../../generated/gmxsdk/modules/orders/helpers.js'; -import { - basisPointsToFloat, - bigintToNumber, - numberToBigint, - PRECISION_DECIMALS -} from '../../generated/gmxsdk/utils/numbers.js'; +import {PositionIncreaseParams} from '../../generated/gmxsdk/modules/orders/helpers.js'; +import {bigintToNumber, numberToBigint, PRECISION_DECIMALS} from '../../generated/gmxsdk/utils/numbers.js'; import {DecreasePositionSwapType, OrderType, PositionOrderInfo} from '../../generated/gmxsdk/types/orders.js'; import {DecreasePositionAmounts} from '../../generated/gmxsdk/types/trade.js'; -import {decodeReferralCode, encodeReferralCode} from '../../generated/gmxsdk/utils/referrals.js'; +import {encodeReferralCode} from '../../generated/gmxsdk/utils/referrals.js'; import {formatUsd} from '../../generated/gmxsdk/utils/numbers/formatting.js'; import {calculateDisplayDecimals} from '../../generated/gmxsdk/utils/numbers/index.js'; import {handleError} from '../../utils/errorHandler.js'; -import {getContract} from '../../generated/gmxsdk/configs/contracts.js'; -import {CLAIMABLE_FUNDING_AMOUNT} from '../../generated/gmxsdk/configs/dataStore.js'; -import {Abi, zeroHash} from 'viem'; -import {hashDataMap, hashString} from '../../generated/gmxsdk/utils/hash.js'; -import {abis} from '../../generated/gmxsdk/abis/index.js'; - -import {ApolloClient} from '@apollo/client/core/ApolloClient.js' -import {InMemoryCache} from '@apollo/client/cache/inmemory/inMemoryCache.js' -import {gql} from 'graphql-tag' -import {HttpLink} from '@apollo/client/link/http/HttpLink.js' -import 'cross-fetch/dist/node-polyfill.js' // Required for Apollo Client in Node.js -import {approveContractImpl, getTokenAllowance} from './privy.js'; // Cache implementation for markets info data interface CacheEntry { @@ -88,28 +71,6 @@ async function getMarketsInfoWithCache(sdk: GmxSdk): Promise<{ marketsInfoData: return data as { marketsInfoData: MarketsInfoData; tokensData: TokensData }; } -const CLAIMABLE_UI_FEE_AMOUNT = hashString("CLAIMABLE_UI_FEE_AMOUNT"); -const UI_FEE_RECEIVER_ACCOUNT = "0xF9f04a745Db54B25bB8B345a1da74D4E3c38c8aB"; - -/** - * Creates a GraphQL client for the GMX synthetics subgraph - * @param chainId The chain ID to get the client for - * @returns Apollo GraphQL client - */ -function getSyntheticsGraphClient(chainId: number): ApolloClient | null { - // For now, we only support Arbitrum (chainId 42161) - if (chainId !== 42161) { - return null; - } - - const url = "https://subgraph.satsuma-prod.com/3b2ced13c8d9/gmx/synthetics-arbitrum-stats/api"; - - return new ApolloClient({ - link: new HttpLink({ uri: url }), - cache: new InMemoryCache(), - }); -} - /** * GMX Plugin * @@ -141,15 +102,6 @@ declare module 'fastify' { closeGmxPosition: typeof closeGmxPosition; getGmxTrade: typeof getGmxTrade; getGmxPositions: typeof getGmxPositions; - getGmxRebateStats: typeof getGmxRebateStats; - getClaimableFundingFees: typeof getClaimableFundingFees; - claimGmxFundingFees: typeof claimGmxFundingFees; - claimGmxPriceImpact: typeof claimGmxPriceImpact; - getGmxPriceImpactRebates: typeof getGmxPriceImpactRebates; - getClaimableUiFees: typeof getClaimableUiFees; - claimGmxUiFees: typeof claimGmxUiFees; - swapGmxTokens: typeof swapGmxTokens; - checkGmxTokenAllowances: typeof checkGmxTokenAllowances; } } @@ -170,45 +122,6 @@ const cancelOrdersSchema = z.object({ ticker: z.string().nonempty() }); -// Schema for claim funding fees request -const claimFundingFeesSchema = z.object({ - account: z.string().nonempty() -}); - -// Schema for claim price impact request -const claimPriceImpactSchema = z.object({ - account: z.string().nonempty() -}); - -// Schema for get price impact rebates request -const getPriceImpactRebatesSchema = z.object({ - account: z.string().nonempty() -}); - -// Schema for claim UI fees request -const claimUiFeesSchema = z.object({ - account: z.string().nonempty() -}); - -// Schema for get claimable UI fees request -const getClaimableUiFeesSchema = z.object({ - account: z.string().nonempty() -}); - -// Schema for swap-tokens request -const swapTokensSchema = z.object({ - account: z.string().nonempty(), - fromTicker: z.string().nonempty(), - toTicker: z.string().nonempty(), - amount: z.number().positive(), - orderType: z.enum(['market', 'limit']).default('market'), - triggerRatio: z.number().optional(), - allowedSlippage: z.number().min(0).max(100).default(0.5) -}).refine((data) => data.fromTicker !== data.toTicker, { - message: "From and to tickers must be different", - path: ["toTicker"] -}); - /** * Gets a GMX SDK client initialized for the given address * If a walletId is provided, it will be used with Privy for signing @@ -228,7 +141,7 @@ export async function getClientForAddress( subsquidUrl: "https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql", subgraphUrl: "https://subgraph.satsuma-prod.com/3b2ced13c8d9/gmx/synthetics-arbitrum-stats/api", settings: { - uiFeeReceiverAccount: UI_FEE_RECEIVER_ACCOUNT + uiFeeReceiverAccount: "0xF9f04a745Db54B25bB8B345a1da74D4E3c38c8aB" }, markets: { "0x4D3Eb91efd36C2b74181F34B111bc1E91a0d0cb4": { @@ -240,18 +153,6 @@ export async function getClientForAddress( "0x9e79146b3A022Af44E0708c6794F03Ef798381A5": { isListed: false, }, - "0x2523B89298908FEf4c5e5bd6F55F20926e22058f": { - isListed: false, - }, - "0x7c54D547FAD72f8AFbf6E5b04403A0168b654C6f": { - isListed: false, - }, - "0x39AC3C494950A4363D739201BA5A0861265C9ae5": { - isListed: false, - }, - "0x0e46941F9bfF8d0784BFfa3d0D7883CDb82D7aE7": { - isListed: false, - }, } }; @@ -554,8 +455,6 @@ export const closeGmxPositionImpl = async ( showPnlInLeverage: true }); - console.log(positionsInfo); - // Find the specific position to close const positionKey = Object.keys(positionsInfo).find(key => { const position = positionsInfo[key]; @@ -961,15 +860,6 @@ export default fp(async (fastify) => { fastify.decorateRequest('closeGmxPosition', closeGmxPosition) fastify.decorateRequest('getGmxTrade', getGmxTrade) fastify.decorateRequest('getGmxPositions', getGmxPositions) - fastify.decorateRequest('getGmxRebateStats', getGmxRebateStats) - fastify.decorateRequest('getClaimableFundingFees', getClaimableFundingFees) - fastify.decorateRequest('claimGmxFundingFees', claimGmxFundingFees) - fastify.decorateRequest('claimGmxPriceImpact', claimGmxPriceImpact) - fastify.decorateRequest('getGmxPriceImpactRebates', getGmxPriceImpactRebates) - fastify.decorateRequest('getClaimableUiFees', getClaimableUiFees) - fastify.decorateRequest('claimGmxUiFees', claimGmxUiFees) - fastify.decorateRequest('swapGmxTokens', swapGmxTokens) - fastify.decorateRequest('checkGmxTokenAllowances', checkGmxTokenAllowances) // Pre-populate and refresh the markets cache on startup fastify.addHook('onReady', async () => { @@ -982,1049 +872,4 @@ export default fp(async (fastify) => { } }); }) - -export const getGmxRebateStatsImpl = async ( - sdk: GmxSdk -): Promise<{ - totalRebateUsd: number; - discountUsd: number; - volume: number; - tier: number; - rebateFactor: number; - discountFactor: number; -} | null> => { - try { - // 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({ - 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 codeOwner = codeOwnerRes.data.referralStorage.codeOwner.returnValues[0]; - const tierId = affiliateTierRes.data.referralStorage.referrerTiers.returnValues[0]; - - // Get tier information - const tiersRes = await sdk.executeMulticall({ - referralStorage: { - contractAddress: referralStorageAddress, - abiId: "ReferralStorage", - calls: { - tiers: { - methodName: "tiers", - params: [tierId], - }, - }, - }, - }); - - const [totalRebate, discountShare] = tiersRes.data.referralStorage.tiers.returnValues ?? [0n, 0n]; - - // 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; - } - - 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) - }; - } catch (error) { - console.error('Error getting GMX rebate stats:', error); - throw error; - } -}; - -export async function getGmxRebateStats( - this: FastifyRequest, - reply: FastifyReply, - account: string -) { - try { - const sdk = await this.getClientForAddress(account); - const rebateStats = await getGmxRebateStatsImpl(sdk); - - return { - success: true, - rebateStats - }; - } catch (error) { - return handleError(this, reply, error, 'getGmxRebateStats'); - } -} - -/** - * Interface for claimable funding data per market - */ -interface ClaimableFundingData { - [marketAddress: string]: { - claimableFundingAmountLong: number; - claimableFundingAmountShort: number; - }; -} - -/** - * Interface for funding fees claim parameters - */ -interface FundingFeesClaimData { - marketAddresses: string[]; - tokenAddresses: string[]; -} - -/** - * Implementation function to get claimable funding fees - * @param sdk The GMX SDK client - * @returns Claimable funding data - */ -export const getClaimableFundingFeesImpl = 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 {}; - } - - // 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 result = await sdk.executeMulticall(multicallRequest); - - // Parse the response - return Object.entries(result.data).reduce((claimableFundingData, [marketAddress, callsResult]: [string, any]) => { - const market = marketsInfoData[marketAddress]; - - if (!market) { - return claimableFundingData; - } - - // Get market divisor for proper decimal conversion - const marketDivisor = 1; // You might need to implement getMarketDivisor function - - claimableFundingData[marketAddress] = { - claimableFundingAmountLong: Number(callsResult.claimableFundingAmountLong.returnValues[0]) / marketDivisor, - claimableFundingAmountShort: Number(callsResult.claimableFundingAmountShort.returnValues[0]) / marketDivisor, - }; - - 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'}`); - } -}; - -/** - * Implementation function to claim funding fees - * @param sdk The GMX SDK client - * @returns Transaction hash - */ -export const claimGmxFundingFeesImpl = async ( - sdk: GmxSdk -): Promise => { - try { - // First get claimable funding data to determine what to claim - const claimableFundingData = await getClaimableFundingFeesImpl(sdk); - - const marketAddresses: string[] = []; - const tokenAddresses: string[] = []; - - // Build arrays of markets and tokens that have claimable amounts - Object.entries(claimableFundingData).forEach(([marketAddress, data]) => { - if (data.claimableFundingAmountLong > 0) { - marketAddresses.push(marketAddress); - // Get the market info to find the long token address - const { marketsInfoData } = marketsCache.get(`markets_${sdk.chainId}`)?.data || {}; - if (marketsInfoData?.[marketAddress]) { - tokenAddresses.push(marketsInfoData[marketAddress].longToken.address); - } - } - - if (data.claimableFundingAmountShort > 0) { - marketAddresses.push(marketAddress); - // Get the market info to find the short token address - const { marketsInfoData } = marketsCache.get(`markets_${sdk.chainId}`)?.data || {}; - if (marketsInfoData?.[marketAddress]) { - tokenAddresses.push(marketsInfoData[marketAddress].shortToken.address); - } - } - }); - - if (marketAddresses.length === 0) { - throw new Error("No funding fees available to claim"); - } - - // Get the ExchangeRouter contract address - const exchangeRouterAddress = getContract(sdk.chainId, "ExchangeRouter"); - - console.log("marketAddresses", marketAddresses) - console.log("tokenAddresses", tokenAddresses) - console.log("account", sdk.account) - - // Execute the claim funding fees transaction using sdk.callContract - await sdk.callContract( - exchangeRouterAddress, - abis.ExchangeRouter as Abi, - "claimFundingFees", - [marketAddresses, tokenAddresses, sdk.account] - ); - - return "funding_fees_claimed"; // Return a success indicator - } catch (error) { - console.error('Error claiming funding fees:', error); - throw new Error(`Failed to claim funding fees: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -}; - -/** - * Gets claimable funding fees on GMX - * @param this The FastifyRequest instance - * @param reply The FastifyReply instance - * @param account The wallet address of the user - * @returns The response object with success status and claimable funding data - */ -export async function getClaimableFundingFees( - this: FastifyRequest, - reply: FastifyReply, - account: string -) { - try { - // Validate the request parameters - claimFundingFeesSchema.parse({ account }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const claimableFundingData = await getClaimableFundingFeesImpl(sdk); - - return { - success: true, - claimableFundingData - }; - } catch (error) { - return handleError(this, reply, error, 'gmx/get-claimable-funding-fees'); - } -} - -/** - * Claims funding fees on GMX - * @param this The FastifyRequest instance - * @param reply The FastifyReply instance - * @param account The wallet address of the user - * @returns The response object with success status and transaction hash - */ -export async function claimGmxFundingFees( - this: FastifyRequest, - reply: FastifyReply, - account: string -) { - try { - // Validate the request parameters - claimFundingFeesSchema.parse({ account }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const hash = await claimGmxFundingFeesImpl(sdk); - - return { - success: true, - hash - }; - } catch (error) { - return handleError(this, reply, error, 'gmx/claim-funding-fees'); - } -} - -/** - * Implementation function to claim price impact rebates - * @param sdk The GMX SDK client - * @returns Transaction hash - */ -export const claimGmxPriceImpactImpl = async ( - sdk: GmxSdk -): Promise => { - try { - // First, get the claimable position price impact fees using our GraphQL method - const rebatesInfo = await getGmxPriceImpactRebatesImpl(sdk); - - if (!rebatesInfo.claimablePositionPriceImpactFees || rebatesInfo.claimablePositionPriceImpactFees.length === 0) { - throw new Error("No price impact fees available to claim"); - } - - // Get the ExchangeRouter contract address - const exchangeRouterAddress = getContract(sdk.chainId, "ExchangeRouter"); - - // Build the arrays for the contract call - const markets: string[] = []; - const tokens: string[] = []; - const timeKeys: number[] = []; - - rebatesInfo.claimablePositionPriceImpactFees.forEach((fee) => { - markets.push(fee.marketAddress); - tokens.push(fee.tokenAddress); - timeKeys.push(fee.timeKey); - }); - - console.log("markets", markets); - console.log("tokens", tokens); - console.log("timeKeys", timeKeys); - console.log("account", sdk.account); - - // Execute the claim collateral transaction using sdk.callContract - await sdk.callContract( - exchangeRouterAddress, - abis.ExchangeRouter as Abi, - "claimCollateral", - [markets, tokens, timeKeys, sdk.account] - ); - - return "price_impact_claimed"; // Return a success indicator - } catch (error) { - console.error('Error claiming price impact rebates:', error); - throw new Error(`Failed to claim price impact rebates: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -}; - -/** - * Claims price impact rebates on GMX - * @param this The FastifyRequest instance - * @param reply The FastifyReply instance - * @param account The wallet address of the user - * @returns The response object with success status and transaction hash - */ -export async function claimGmxPriceImpact( - this: FastifyRequest, - reply: FastifyReply, - account: string -) { - try { - // Validate the request parameters - claimPriceImpactSchema.parse({ account }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const hash = await claimGmxPriceImpactImpl(sdk); - - return { - success: true, - hash - }; - } catch (error) { - return handleError(this, reply, error, 'gmx/claim-price-impact'); - } -} - -/** - * Interface for raw claimable collateral from subgraph - */ -interface RawClaimableCollateral { - id: string; - marketAddress: string; - tokenAddress: string; - timeKey: number; - value: string; - factor: string; - factorByTime: string; -} - -/** - * Interface for processed rebate info item - */ -interface RebateInfoItem { - factor: bigint; - value: bigint; - valueByFactor: bigint; - timeKey: number; - marketAddress: string; - tokenAddress: string; - id: string; -} - -/** - * Interface for rebates info result - */ -interface RebatesInfoResult { - accruedPositionPriceImpactFees: RebateInfoItem[]; - claimablePositionPriceImpactFees: RebateInfoItem[]; -} - -/** - * Implementation function to get price impact rebates - * @param sdk The GMX SDK client - * @returns Rebates info with accrued and claimable fees - */ -export const getGmxPriceImpactRebatesImpl = async ( - sdk: GmxSdk -): Promise => { - try { - // Get the GraphQL client for the current chain - const client = getSyntheticsGraphClient(sdk.chainId); - - if (!client) { - throw new Error(`Unsupported chain ID: ${sdk.chainId}`); - } - - // Build the GraphQL query to fetch claimable collaterals - const query = gql(`{ - claimableCollaterals( - where: { account: "${sdk.account.toLowerCase()}", claimed: false } - ) { - id - marketAddress - tokenAddress - timeKey - value - factor - factorByTime - } - }`); - - // Execute the GraphQL query - const { data } = await client.query({ - query, - fetchPolicy: "no-cache" - }); - - const rawClaimableCollaterals: RawClaimableCollateral[] = data.claimableCollaterals || []; - - const result: RebatesInfoResult = { - accruedPositionPriceImpactFees: [], - claimablePositionPriceImpactFees: [] - }; - - // Process the raw data similar to the frontend implementation - rawClaimableCollaterals.forEach((rawRebateInfo) => { - let factor = BigInt(rawRebateInfo.factor); - const factorByTime = BigInt(rawRebateInfo.factorByTime); - - // Use the higher factor - if (factorByTime > factor) { - factor = factorByTime; - } - - const value = BigInt(rawRebateInfo.value); - const expandDecimals30 = BigInt(10) ** BigInt(30); // equivalent to expandDecimals(1, 30) - const valueByFactor = (value * factor) / expandDecimals30; - - const rebateInfo: RebateInfoItem = { - factor, - value, - valueByFactor, - timeKey: Number(rawRebateInfo.timeKey), - marketAddress: rawRebateInfo.marketAddress.toLowerCase(), - tokenAddress: rawRebateInfo.tokenAddress.toLowerCase(), - id: rawRebateInfo.id, - }; - - // Skip items where factor > 0 but valueByFactor == 0 to avoid CollateralAlreadyClaimed error - if (factor > 0n && valueByFactor === 0n) { - console.log(`Skipping rebate ${rebateInfo.id} - factor too small`); - return; - } - - // Separate accrued vs claimable based on factor - if (rebateInfo.factor === 0n) { - result.accruedPositionPriceImpactFees.push(rebateInfo); - } else { - result.claimablePositionPriceImpactFees.push(rebateInfo); - } - }); - - console.log(`Found ${result.accruedPositionPriceImpactFees.length} accrued fees`); - console.log(`Found ${result.claimablePositionPriceImpactFees.length} claimable fees`); - - return result; - } catch (error) { - console.error('Error getting price impact rebates:', error); - throw new Error(`Failed to get price impact rebates: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -}; - -/** - * Gets price impact rebates on GMX - * @param this The FastifyRequest instance - * @param reply The FastifyReply instance - * @param account The wallet address of the user - * @returns The response object with success status and rebates data - */ -export async function getGmxPriceImpactRebates( - this: FastifyRequest, - reply: FastifyReply, - account: string -) { - try { - // Validate the request parameters - getPriceImpactRebatesSchema.parse({ account }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const rebatesInfo = await getGmxPriceImpactRebatesImpl(sdk); - - return { - success: true, - rebatesInfo - }; - } catch (error) { - return handleError(this, reply, error, 'gmx/get-price-impact-rebates'); - } -} - -/** - * Interface for claimable UI fee data per market - */ -interface ClaimableUiFeeData { - [marketAddress: string]: { - claimableUiFeeAmount: number; - }; -} - -/** - * Implementation function to get claimable UI fees - * @param sdk The GMX SDK client - * @returns Claimable UI fee data - */ -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 = UI_FEE_RECEIVER_ACCOUNT; - - // Build multicall request for all markets - const multicallRequest = marketAddresses.reduce((request, marketAddress) => { - const market = marketsInfoData[marketAddress]; - - if (!market) { - return request; - } - - 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; - }, {}); - - const result = await sdk.executeMulticall(multicallRequest); - - // 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), - }; - - 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'}`); - } -}; - -/** - * Gets claimable UI fees on GMX - * @param this The FastifyRequest instance - * @param reply The FastifyReply instance - * @param account The wallet address of the user - * @returns The response object with success status and claimable UI fee data - */ -export async function getClaimableUiFees( - this: FastifyRequest, - reply: FastifyReply, - account: string -) { - try { - // Validate the request parameters - getClaimableUiFeesSchema.parse({ account }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const claimableUiFeeData = await getClaimableUiFeesImpl(sdk); - - return { - success: true, - claimableUiFeeData - }; - } catch (error) { - return handleError(this, reply, error, 'gmx/get-claimable-ui-fees'); - } -} - -/** - * Implementation function to claim UI fees - * @param sdk The GMX SDK client - * @returns Transaction hash - */ -export const claimGmxUiFeesImpl = async ( - sdk: GmxSdk -): Promise => { - try { - // First get claimable UI fee data to determine what to claim - const claimableUiFeeData = await getClaimableUiFeesImpl(sdk); - - const marketAddresses: string[] = []; - const tokenAddresses: string[] = []; - - // Build arrays of markets and tokens that have claimable amounts - Object.entries(claimableUiFeeData).forEach(([marketAddress, data]) => { - const { marketsInfoData } = marketsCache.get(`markets_${sdk.chainId}`)?.data || {}; - - if (!marketsInfoData?.[marketAddress]) { - return; - } - - const marketInfo = marketsInfoData[marketAddress]; - - marketAddresses.push(marketAddress); - tokenAddresses.push(marketInfo.longToken.address); - }); - - - - // Get the ExchangeRouter contract address - const exchangeRouterAddress = getContract(sdk.chainId, "ExchangeRouter"); - - console.log("UI fees - marketAddresses"); - marketAddresses.forEach(marketAddress => { - console.log(marketAddress); - }); - console.log("UI fees - tokenAddresses"); - tokenAddresses.forEach(tokenAddress => { - console.log(tokenAddress); - }); - console.log("UI fees - receiver account", sdk.account); - - // Execute the claim UI fees transaction using sdk.callContract - - - return "ui_fees_claimed"; // Return a success indicator - } catch (error) { - console.error('Error claiming UI fees:', error); - throw new Error(`Failed to claim UI fees: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -}; - -/** - * Claims UI fees on GMX - * @param this The FastifyRequest instance - * @param reply The FastifyReply instance - * @param account The wallet address of the user - * @returns The response object with success status and transaction hash - */ -export async function claimGmxUiFees( - this: FastifyRequest, - reply: FastifyReply, - account: string -) { - try { - // Validate the request parameters - claimUiFeesSchema.parse({ account }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const hash = await claimGmxUiFeesImpl(sdk); - - return { - success: true, - hash - }; - } catch (error) { - return handleError(this, reply, error, 'gmx/claim-ui-fees'); - } -} - -/** - * Implementation function to swap tokens on GMX using the SDK's built-in swap method - * @param sdk The GMX SDK client - * @param fromTicker The ticker symbol of the token to swap from - * @param toTicker The ticker symbol of the token to swap to - * @param amount The amount to swap - * @param orderType The order type (market or limit) - * @param triggerRatio The trigger ratio for limit orders (optional) - * @param allowedSlippage The allowed slippage percentage (default 0.5%) - * @returns The transaction hash - */ -export const swapGmxTokensImpl = async ( - sdk: GmxSdk, - fromTicker: string, - toTicker: string, - amount: number, - orderType: 'market' | 'limit' = 'market', - triggerRatio?: number, - allowedSlippage: number = 0.5 -): Promise => { - try { - // 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"); - } - - // 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}`); - } - - // 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( - 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'}`); - } - - // 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 - - // 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); - - swapParams.marketsInfoData = marketsInfoData; - swapParams.tokensData = tokensData; - - - // 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'}`); - } -}; - -/** - * Swaps tokens on GMX - * @param this The FastifyRequest instance - * @param reply The FastifyReply instance - * @param account The wallet address of the user - * @param fromTicker The ticker symbol of the token to swap from - * @param toTicker The ticker symbol of the token to swap to - * @param amount The amount to swap - * @param orderType The order type (market or limit) - * @param triggerRatio The trigger ratio for limit orders (optional) - * @param allowedSlippage The allowed slippage percentage (default 0.5%) - * @returns The response object with success status and transaction hash - */ -export async function swapGmxTokens( - this: FastifyRequest, - reply: FastifyReply, - account: string, - fromTicker: string, - toTicker: string, - amount: number, - orderType: 'market' | 'limit' = 'market', - triggerRatio?: number, - allowedSlippage: number = 0.5 -) { - try { - // Validate the request parameters - swapTokensSchema.parse({ - account, - fromTicker, - toTicker, - amount, - orderType, - triggerRatio, - allowedSlippage - }); - - // Get client for the address - const sdk = await this.getClientForAddress(account); - - // Call the implementation function - const hash = await swapGmxTokensImpl( - sdk, - fromTicker, - toTicker, - amount, - orderType, - triggerRatio, - allowedSlippage - ); - - return { - success: true, - hash, - message: `Successfully processed ${fromTicker} to ${toTicker} swap. Any required token approvals were handled automatically.` - }; - } catch (error) { - // Handle approval-specific errors with better messaging - if (error instanceof Error && error.message.includes('Failed to handle token allowance')) { - reply.status(400); - return { - success: false, - error: error.message, - errorType: 'APPROVAL_FAILED', - suggestion: `Token approval failed. Please ensure you have sufficient funds and try again, or manually approve the ExchangeRouter contract to spend your ${fromTicker} tokens.` - }; - } - - return handleError(this, reply, error, 'gmx/swap-tokens'); - } -} - -/** - * Checks token allowances for GMX contracts - * @param account The wallet address - * @param tokenAddress The token contract address - * @param chainId The chain ID (defaults to Arbitrum) - * @returns Object with allowances for different GMX contracts - */ -export const checkGmxTokenAllowances = async ( - account: string, - tokenAddress: string, - chainId: number = ARBITRUM -): Promise<{ - exchangeRouter: bigint; - orderVault: bigint; - syntheticsRouter: bigint; -}> => { - try { - const exchangeRouterAddress = getContract(chainId, "ExchangeRouter"); - const orderVaultAddress = getContract(chainId, "OrderVault"); - const syntheticsRouterAddress = getContract(chainId, "SyntheticsRouter"); - - const [exchangeRouterAllowance, orderVaultAllowance, syntheticsRouterAllowance] = await Promise.all([ - getTokenAllowance(account, tokenAddress, exchangeRouterAddress), - getTokenAllowance(account, tokenAddress, orderVaultAddress), - getTokenAllowance(account, tokenAddress, syntheticsRouterAddress) - ]); - - return { - exchangeRouter: exchangeRouterAllowance, - orderVault: orderVaultAllowance, - syntheticsRouter: syntheticsRouterAllowance - }; - } catch (error) { - console.error('Error checking GMX token allowances:', error); - throw new Error(`Failed to check token allowances: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -};