Add rpc url fallback to all gmx methods

This commit is contained in:
2025-08-17 01:18:12 +07:00
parent 09a217ca63
commit 0fb8178cea

View File

@@ -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<T>(
operation: (sdk: GmxSdk, retryCount: number) => Promise<T>,
sdk: GmxSdk,
retryCount: number = 0
): Promise<T> {
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,7 +349,8 @@ export const openGmxPositionImpl = async (
stopLossPrice?: number,
takeProfitPrice?: number
): Promise<string> => {
try {
return executeWithFallback(
async (sdk, retryCount) => {
// Get markets and tokens data from GMX SDK with cache
const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo();
@@ -360,10 +413,8 @@ export const openGmxPositionImpl = async (
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'}`);
}
}, sdk, 0
);
};
@@ -445,7 +496,8 @@ export const cancelGmxOrdersImpl = async (
sdk: GmxSdk,
ticker: string
): Promise<boolean> => {
try {
return executeWithFallback(
async (sdk, retryCount) => {
// Get markets and tokens data first
const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo();
@@ -478,10 +530,8 @@ export const cancelGmxOrdersImpl = async (
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'}`);
}
}, sdk, 0
);
};
@@ -580,7 +630,8 @@ export const closeGmxPositionImpl = async (
ticker: string,
direction: TradeDirection
): Promise<string> => {
try {
return executeWithFallback(
async (sdk, retryCount) => {
// Get markets and tokens data from GMX SDK with cache
const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo();
@@ -660,10 +711,8 @@ export const closeGmxPositionImpl = async (
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'}`);
}
}, sdk, 0
);
};
/**
@@ -712,7 +761,8 @@ export const getGmxTradeImpl = async (
sdk: GmxSdk,
ticker: string
): Promise<Trade[]> => {
return executeWithFallback(
async (sdk, retryCount) => {
const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk);
const orders = await sdk.orders.getOrders({
@@ -721,7 +771,6 @@ export const getGmxTradeImpl = async (
tokensData
});
// Filter orders for the specific ticker and transform them to trades
const trades = Object.values(orders.ordersInfoData)
.filter(order => {
@@ -742,7 +791,6 @@ export const getGmxTradeImpl = async (
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
@@ -790,7 +838,9 @@ export const getGmxTradeImpl = async (
});
return trades;
}
}, sdk, 0
);
};
/**
* Gets trades on GMX
@@ -832,7 +882,8 @@ export const getGmxPositionsImpl = async (
sdk: GmxSdk,
retryCount: number = 0
): Promise<Position[]> => {
try {
return executeWithFallback(
async (sdk, retryCount) => {
const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo();
const positionsInfo = await sdk.positions.getPositionsInfo({
@@ -954,41 +1005,8 @@ export const getGmxPositionsImpl = async (
});
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;
}
}, sdk, retryCount
);
};
/**
@@ -1086,7 +1104,8 @@ export const getGmxRebateStatsImpl = async (
rebateFactor: number;
discountFactor: number;
} | null> => {
try {
return executeWithFallback(
async (sdk, retryCount) => {
// Get the referral storage contract address
const referralStorageAddress = getContract(sdk.chainId, "ReferralStorage");
@@ -1203,10 +1222,8 @@ export const getGmxRebateStatsImpl = async (
rebateFactor: Number(totalRebateFactor),
discountFactor: Number(discountFactor)
};
} catch (error) {
console.error('Error getting GMX rebate stats:', error);
throw error;
}
}, sdk, 0
);
};
export async function getGmxRebateStats(
@@ -1253,7 +1270,8 @@ interface FundingFeesClaimData {
export const getClaimableFundingFeesImpl = async (
sdk: GmxSdk
): Promise<ClaimableFundingData> => {
try {
return executeWithFallback(
async (sdk, retryCount) => {
const { marketsInfoData, tokensData } = await getMarketsInfoWithCache(sdk);
if (!marketsInfoData || !tokensData) {
@@ -1341,11 +1359,8 @@ export const getClaimableFundingFeesImpl = async (
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'}`);
}
}, sdk, 0
);
};
/**
@@ -1524,7 +1539,8 @@ interface ClaimableUiFeeData {
export const getClaimableUiFeesImpl = async (
sdk: GmxSdk
): Promise<ClaimableUiFeeData> => {
try {
return executeWithFallback(
async (sdk, retryCount) => {
const { marketsInfoData } = await getMarketsInfoWithCache(sdk);
if (!marketsInfoData) {
@@ -1590,11 +1606,8 @@ export const getClaimableUiFeesImpl = async (
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'}`);
}
}, sdk, 0
);
};
/**
@@ -1733,7 +1746,8 @@ export const swapGmxTokensImpl = async (
triggerRatio?: number,
allowedSlippage: number = 0.5
): Promise<string> => {
try {
return executeWithFallback(
async (sdk, retryCount) => {
// Get markets and tokens data from GMX SDK with cache
const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk);
@@ -1818,15 +1832,12 @@ export const swapGmxTokensImpl = async (
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'}`);
}
}, sdk, 0
);
};
/**