Fix futures open positions
This commit is contained in:
@@ -1,23 +1,28 @@
|
|||||||
import { Address } from "viem";
|
import {Address} from "viem";
|
||||||
|
|
||||||
import { getWrappedToken } from "../../configs/tokens.js";
|
import {getWrappedToken} from "../../configs/tokens.js";
|
||||||
import { MarketFilterLongShortItemData } from "../../modules/trades/trades.js";
|
import {MarketFilterLongShortItemData} from "../../modules/trades/trades.js";
|
||||||
import { MarketInfo, MarketsInfoData } from "../../types/markets.js";
|
import {MarketInfo, MarketsInfoData} from "../../types/markets.js";
|
||||||
import { OrdersData, OrdersInfoData, OrderType, PositionOrderInfo } from "../../types/orders.js";
|
import {OrdersData, OrdersInfoData, OrderType, PositionOrderInfo} from "../../types/orders.js";
|
||||||
import { SidecarLimitOrderEntryValid, SidecarSlTpOrderEntryValid } from "../../types/sidecarOrders.js";
|
import {SidecarLimitOrderEntryValid, SidecarSlTpOrderEntryValid} from "../../types/sidecarOrders.js";
|
||||||
import { TokenData, TokensData } from "../../types/tokens.js";
|
import {TokenData, TokensData} from "../../types/tokens.js";
|
||||||
import { DecreasePositionAmounts, IncreasePositionAmounts, SwapAmounts } from "../../types/trade.js";
|
import {DecreasePositionAmounts, IncreasePositionAmounts, SwapAmounts} from "../../types/trade.js";
|
||||||
import { getByKey } from "../../utils/objects.js";
|
import {getByKey} from "../../utils/objects.js";
|
||||||
import { getOrderInfo, isOrderForPositionByData, isVisibleOrder } from "../../utils/orders.js";
|
import {getOrderInfo, isOrderForPositionByData, isVisibleOrder} from "../../utils/orders.js";
|
||||||
|
|
||||||
import { createDecreaseOrderTxn } from "./transactions/createDecreaseOrderTxn.js";
|
import {createDecreaseOrderTxn} from "./transactions/createDecreaseOrderTxn.js";
|
||||||
import { createIncreaseOrderTxn } from "./transactions/createIncreaseOrderTxn.js";
|
import {createIncreaseOrderTxn} from "./transactions/createIncreaseOrderTxn.js";
|
||||||
import { buildGetOrdersMulticall, getExecutionFeeAmountForEntry, matchByMarket, parseGetOrdersResponse } from "./utils.js";
|
import {
|
||||||
import { Module } from "../base.js";
|
buildGetOrdersMulticall,
|
||||||
import { PositionIncreaseParams, SwapParams, increaseOrderHelper, swap } from "./helpers.js";
|
getExecutionFeeAmountForEntry,
|
||||||
import { cancelOrdersTxn } from "./transactions/cancelOrdersTxn.js";
|
matchByMarket,
|
||||||
import { createSwapOrderTxn } from "./transactions/createSwapOrderTxn.js";
|
parseGetOrdersResponse
|
||||||
import { createWrapOrUnwrapTxn, WrapOrUnwrapParams } from "./transactions/createWrapOrUnwrapTxn.js";
|
} from "./utils.js";
|
||||||
|
import {Module} from "../base.js";
|
||||||
|
import {increaseOrderHelper, PositionIncreaseParams, swap, SwapParams} from "./helpers.js";
|
||||||
|
import {cancelOrdersTxn} from "./transactions/cancelOrdersTxn.js";
|
||||||
|
import {createSwapOrderTxn} from "./transactions/createSwapOrderTxn.js";
|
||||||
|
import {createWrapOrUnwrapTxn, WrapOrUnwrapParams} from "./transactions/createWrapOrUnwrapTxn.js";
|
||||||
|
|
||||||
export class Orders extends Module {
|
export class Orders extends Module {
|
||||||
async getOrders({
|
async getOrders({
|
||||||
@@ -245,7 +250,7 @@ export class Orders extends Module {
|
|||||||
referralCode: referralCodeForTxn,
|
referralCode: referralCodeForTxn,
|
||||||
indexToken: marketInfo.indexToken,
|
indexToken: marketInfo.indexToken,
|
||||||
tokensData,
|
tokensData,
|
||||||
skipSimulation: skipSimulation || isLimit,
|
skipSimulation: true,
|
||||||
},
|
},
|
||||||
createDecreaseOrderParams: createSltpEntries?.map((entry, i) => {
|
createDecreaseOrderParams: createSltpEntries?.map((entry, i) => {
|
||||||
return {
|
return {
|
||||||
@@ -262,7 +267,7 @@ export class Orders extends Module {
|
|||||||
executionFee: getExecutionFeeAmountForEntry(this.sdk, entry, gasLimits, tokensData, gasPrice) ?? 0n,
|
executionFee: getExecutionFeeAmountForEntry(this.sdk, entry, gasLimits, tokensData, gasPrice) ?? 0n,
|
||||||
tokensData,
|
tokensData,
|
||||||
txnType: entry.txnType!,
|
txnType: entry.txnType!,
|
||||||
skipSimulation: isLimit,
|
skipSimulation: true,
|
||||||
autoCancel: i < autoCancelOrdersLimit,
|
autoCancel: i < autoCancelOrdersLimit,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import {getChainName, getPrivyClient} from '../../../plugins/custom/privy.js';
|
|||||||
import type {GmxSdk} from "../index.js";
|
import type {GmxSdk} from "../index.js";
|
||||||
import {bigMath} from "./bigmath.js";
|
import {bigMath} from "./bigmath.js";
|
||||||
import {
|
import {
|
||||||
GAS_PRICE_BUFFER_MAP,
|
GAS_PRICE_BUFFER_MAP,
|
||||||
GAS_PRICE_PREMIUM_MAP,
|
GAS_PRICE_PREMIUM_MAP,
|
||||||
getViemChain,
|
getViemChain,
|
||||||
MAX_FEE_PER_GAS_MAP,
|
MAX_FEE_PER_GAS_MAP,
|
||||||
MAX_PRIORITY_FEE_PER_GAS_MAP
|
MAX_PRIORITY_FEE_PER_GAS_MAP
|
||||||
} from "../configs/chains.js";
|
} from "../configs/chains.js";
|
||||||
import {BASIS_POINTS_DIVISOR_BIGINT} from "../configs/factors.js";
|
import {BASIS_POINTS_DIVISOR_BIGINT} from "../configs/factors.js";
|
||||||
|
|
||||||
@@ -214,12 +214,12 @@ export async function callContract(
|
|||||||
console.log('Address', sdk.config.account)
|
console.log('Address', sdk.config.account)
|
||||||
console.log('Method', method)
|
console.log('Method', method)
|
||||||
console.log('Params', params)
|
console.log('Params', params)
|
||||||
|
|
||||||
const response = await privy.walletApi.ethereum.sendTransaction(param as any);
|
const response = await privy.walletApi.ethereum.sendTransaction(param as any);
|
||||||
|
|
||||||
return response.hash;
|
return response.hash;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Transaction failed:", error);
|
console.error("Transaction failed:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ import {PositionIncreaseParams, SwapParams} from '../../generated/gmxsdk/modules
|
|||||||
import {
|
import {
|
||||||
basisPointsToFloat,
|
basisPointsToFloat,
|
||||||
bigintToNumber,
|
bigintToNumber,
|
||||||
|
calculateDisplayDecimals,
|
||||||
|
formatUsd,
|
||||||
numberToBigint,
|
numberToBigint,
|
||||||
PRECISION_DECIMALS
|
PRECISION_DECIMALS
|
||||||
} from '../../generated/gmxsdk/utils/numbers.js';
|
} from '../../generated/gmxsdk/utils/numbers.js';
|
||||||
import {DecreasePositionSwapType, OrderType, PositionOrderInfo} from '../../generated/gmxsdk/types/orders.js';
|
import {DecreasePositionSwapType, OrderType, PositionOrderInfo} from '../../generated/gmxsdk/types/orders.js';
|
||||||
import {DecreasePositionAmounts} from '../../generated/gmxsdk/types/trade.js';
|
import {DecreasePositionAmounts} from '../../generated/gmxsdk/types/trade.js';
|
||||||
import {decodeReferralCode, encodeReferralCode} from '../../generated/gmxsdk/utils/referrals.js';
|
import {decodeReferralCode, encodeReferralCode} from '../../generated/gmxsdk/utils/referrals.js';
|
||||||
import {formatUsd} from '../../generated/gmxsdk/utils/numbers.js';
|
|
||||||
import {calculateDisplayDecimals} from '../../generated/gmxsdk/utils/numbers.js';
|
|
||||||
import {handleError} from '../../utils/errorHandler.js';
|
import {handleError} from '../../utils/errorHandler.js';
|
||||||
import {Abi, formatEther, parseEther, zeroHash} from 'viem';
|
import {Abi, formatEther, parseEther, zeroHash} from 'viem';
|
||||||
import {CLAIMABLE_FUNDING_AMOUNT} from '../../generated/gmxsdk/configs/dataStore.js';
|
import {CLAIMABLE_FUNDING_AMOUNT} from '../../generated/gmxsdk/configs/dataStore.js';
|
||||||
@@ -114,8 +114,12 @@ export async function checkGasFeeBalance(
|
|||||||
const estimatedGasFeeEth = Number(formatEther(estimatedGasFee));
|
const estimatedGasFeeEth = Number(formatEther(estimatedGasFee));
|
||||||
const estimatedGasFeeUsd = estimatedGasFeeEth * ethPrice;
|
const estimatedGasFeeUsd = estimatedGasFeeEth * ethPrice;
|
||||||
|
|
||||||
// Check if gas fee exceeds maximum allowed (1 USDC)
|
// Check if:
|
||||||
const hasSufficientBalance = estimatedGasFeeUsd <= MAX_GAS_FEE_USD;
|
// 1. Wallet has enough ETH balance to cover gas
|
||||||
|
// 2. Gas fee is under the maximum allowed USD threshold
|
||||||
|
const hasEnoughEth = ethBalance >= estimatedGasFee;
|
||||||
|
const isUnderMaxFee = estimatedGasFeeUsd <= MAX_GAS_FEE_USD;
|
||||||
|
const hasSufficientBalance = hasEnoughEth && isUnderMaxFee;
|
||||||
|
|
||||||
console.log(`⛽ Gas fee check:`, {
|
console.log(`⛽ Gas fee check:`, {
|
||||||
ethBalance: ethBalanceFormatted,
|
ethBalance: ethBalanceFormatted,
|
||||||
@@ -123,16 +127,27 @@ export async function checkGasFeeBalance(
|
|||||||
estimatedGasFeeUsd: estimatedGasFeeUsd.toFixed(2),
|
estimatedGasFeeUsd: estimatedGasFeeUsd.toFixed(2),
|
||||||
ethPrice: ethPrice.toFixed(2),
|
ethPrice: ethPrice.toFixed(2),
|
||||||
maxAllowedUsd: MAX_GAS_FEE_USD,
|
maxAllowedUsd: MAX_GAS_FEE_USD,
|
||||||
|
hasEnoughEth,
|
||||||
|
isUnderMaxFee,
|
||||||
hasSufficientBalance
|
hasSufficientBalance
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Generate appropriate error message
|
||||||
|
let errorMessage: string | undefined;
|
||||||
|
if (!hasSufficientBalance) {
|
||||||
|
if (!hasEnoughEth) {
|
||||||
|
errorMessage = `Insufficient ETH balance for gas: need ${estimatedGasFeeEth.toFixed(6)} ETH (~$${estimatedGasFeeUsd.toFixed(2)}), but only have ${ethBalanceFormatted} ETH. Please add more ETH to your wallet.`;
|
||||||
|
} else if (!isUnderMaxFee) {
|
||||||
|
errorMessage = `Gas fee too high: $${estimatedGasFeeUsd.toFixed(2)} exceeds maximum of $${MAX_GAS_FEE_USD}. Please wait for lower gas fees.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasSufficientBalance,
|
hasSufficientBalance,
|
||||||
ethBalance: ethBalanceFormatted,
|
ethBalance: ethBalanceFormatted,
|
||||||
estimatedGasFeeUsd,
|
estimatedGasFeeUsd,
|
||||||
ethPrice,
|
ethPrice,
|
||||||
errorMessage: hasSufficientBalance ? undefined :
|
errorMessage
|
||||||
`Gas fee too high: $${estimatedGasFeeUsd.toFixed(2)} exceeds maximum of $${MAX_GAS_FEE_USD}. Please wait for lower gas fees or add more ETH.`
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -505,12 +520,20 @@ async function approveTokenForContract(
|
|||||||
try {
|
try {
|
||||||
const contractAddress = getContract(sdk.chainId, contractName as ContractName);
|
const contractAddress = getContract(sdk.chainId, contractName as ContractName);
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:523',message:'Allowance check start',data:{ticker:fromTicker,contractName,contractAddress,tokenAddress:fromTokenData.address,requiredAmount:fromTokenAmount.toString(),account:sdk.account},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
const currentAllowance = await getTokenAllowance(
|
const currentAllowance = await getTokenAllowance(
|
||||||
sdk.account,
|
sdk.account,
|
||||||
fromTokenData.address,
|
fromTokenData.address,
|
||||||
contractAddress
|
contractAddress
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:530',message:'Allowance check result',data:{ticker:fromTicker,contractName,currentAllowance:currentAllowance.toString(),requiredAmount:fromTokenAmount.toString(),isSufficient:currentAllowance>=fromTokenAmount},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
console.log(`Current allowance for ${fromTicker}:`, currentAllowance);
|
console.log(`Current allowance for ${fromTicker}:`, currentAllowance);
|
||||||
console.log(`Required amount:`, fromTokenAmount);
|
console.log(`Required amount:`, fromTokenAmount);
|
||||||
|
|
||||||
@@ -519,20 +542,88 @@ async function approveTokenForContract(
|
|||||||
|
|
||||||
// Approve maximum amount (2^256 - 1) to avoid future approval transactions
|
// Approve maximum amount (2^256 - 1) to avoid future approval transactions
|
||||||
const maxApprovalAmount = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
const maxApprovalAmount = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:537',message:'Approval transaction start',data:{ticker:fromTicker,contractName,contractAddress,approvalAmount:maxApprovalAmount.toString()},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
const approvalHash = await approveContractImpl(
|
const approvalHash = await approveContractImpl(
|
||||||
sdk.account,
|
sdk.account,
|
||||||
fromTokenData.address,
|
fromTokenData.address,
|
||||||
contractAddress,
|
contractAddress,
|
||||||
sdk.chainId,
|
sdk.chainId,
|
||||||
maxApprovalAmount
|
maxApprovalAmount,
|
||||||
|
true // waitForConfirmation = true (already default, but being explicit)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:545',message:'Approval transaction hash received',data:{ticker:fromTicker,contractName,approvalHash},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
console.log(`✅ Token approval successful! Hash: ${approvalHash}`);
|
console.log(`✅ Token approval successful! Hash: ${approvalHash}`);
|
||||||
console.log(`📝 Approved maximum amount ${fromTicker} for ${contractName}`);
|
console.log(`📝 Approved maximum amount ${fromTicker} for ${contractName}`);
|
||||||
|
console.log(`🔍 DEBUG: Contract ${contractName} address: ${contractAddress}`);
|
||||||
|
console.log(`🔍 DEBUG: Token ${fromTicker} address: ${fromTokenData.address}`);
|
||||||
|
console.log(`🔍 DEBUG: Account: ${sdk.account}`);
|
||||||
|
|
||||||
|
// Verify the approval transaction was actually mined by checking the receipt
|
||||||
|
try {
|
||||||
|
const receipt = await sdk.publicClient.getTransactionReceipt({ hash: approvalHash as `0x${string}` });
|
||||||
|
console.log(`✅ Approval transaction confirmed in block ${receipt.blockNumber}`);
|
||||||
|
console.log(`🔍 DEBUG: Approval transaction status: ${receipt.status === 'success' ? 'success' : 'failed'}`);
|
||||||
|
|
||||||
|
if (receipt.status !== 'success') {
|
||||||
|
throw new Error(`Approval transaction failed: ${approvalHash}`);
|
||||||
|
}
|
||||||
|
} catch (receiptError) {
|
||||||
|
console.warn(`⚠️ Could not verify approval transaction receipt: ${receiptError}`);
|
||||||
|
// Continue anyway as approveContractImpl should have already waited
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait additional time for state to propagate across RPC nodes
|
||||||
|
console.log(`⏳ Waiting 5 seconds for state to propagate across RPC nodes...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
|
||||||
|
// Verify allowance multiple times to ensure state has propagated
|
||||||
|
let postApprovalAllowance = await getTokenAllowance(
|
||||||
|
sdk.account,
|
||||||
|
fromTokenData.address,
|
||||||
|
contractAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`🔍 DEBUG: Post-approval allowance (first read): ${postApprovalAllowance.toString()}`);
|
||||||
|
|
||||||
|
// If still insufficient, wait more and retry with fresh RPC call
|
||||||
|
if (postApprovalAllowance < fromTokenAmount) {
|
||||||
|
console.log(`⏳ Allowance still insufficient, waiting 5 more seconds and retrying...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
postApprovalAllowance = await getTokenAllowance(
|
||||||
|
sdk.account,
|
||||||
|
fromTokenData.address,
|
||||||
|
contractAddress
|
||||||
|
);
|
||||||
|
console.log(`🔍 DEBUG: Post-approval allowance (second read): ${postApprovalAllowance.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:575',message:'Post-approval allowance check',data:{ticker:fromTicker,contractName,postApprovalAllowance:postApprovalAllowance.toString(),requiredAmount:fromTokenAmount.toString(),isSufficient:postApprovalAllowance>=fromTokenAmount},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
console.log(`🔍 DEBUG: Final post-approval allowance: ${postApprovalAllowance.toString()}`);
|
||||||
|
console.log(`🔍 DEBUG: Required amount: ${fromTokenAmount.toString()}`);
|
||||||
|
console.log(`🔍 DEBUG: Is sufficient: ${postApprovalAllowance >= fromTokenAmount}`);
|
||||||
|
|
||||||
|
if (postApprovalAllowance < fromTokenAmount) {
|
||||||
|
console.error(`❌ CRITICAL: Approval failed! Allowance ${postApprovalAllowance.toString()} is less than required ${fromTokenAmount.toString()}`);
|
||||||
|
throw new Error(`Token approval failed: allowance ${postApprovalAllowance.toString()} is less than required ${fromTokenAmount.toString()}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`✅ Sufficient allowance already exists for ${fromTicker}`);
|
console.log(`✅ Sufficient allowance already exists for ${fromTicker}`);
|
||||||
}
|
}
|
||||||
} catch (allowanceError) {
|
} catch (allowanceError) {
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:550',message:'Allowance approval error',data:{ticker:fromTicker,contractName,error:allowanceError instanceof Error ? allowanceError.message : 'Unknown error'},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'E'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
console.warn('Could not check or approve token allowance:', 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'}`);
|
throw new Error(`Failed to handle token allowance: ${allowanceError instanceof Error ? allowanceError.message : 'Unknown error'}`);
|
||||||
}
|
}
|
||||||
@@ -736,8 +827,58 @@ export const openGmxPositionImpl = async (
|
|||||||
console.log(`No price provided, using current market price: ${currentPrice}`);
|
console.log(`No price provided, using current market price: ${currentPrice}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the collateral amount in USDC (quantity * price)
|
// Calculate the collateral amount in USDC
|
||||||
const collateralAmount = BigInt(Math.floor((quantity || 0) * currentPrice * 1e6)); // USDC has 6 decimals
|
// quantity represents position size in BTC
|
||||||
|
// With leverage, collateral = (position size in USD) / leverage
|
||||||
|
const positionSizeUsd = (quantity || 0) * currentPrice;
|
||||||
|
const collateralAmountUsd = positionSizeUsd / (leverage || 1);
|
||||||
|
const collateralAmount = BigInt(Math.floor(collateralAmountUsd * 1e6)); // USDC has 6 decimals
|
||||||
|
|
||||||
|
console.log('💰 Collateral calculation:', {
|
||||||
|
quantity,
|
||||||
|
currentPrice,
|
||||||
|
leverage,
|
||||||
|
positionSizeUsd,
|
||||||
|
collateralAmountUsd,
|
||||||
|
collateralAmountRaw: collateralAmountUsd * 1e6,
|
||||||
|
collateralAmount: collateralAmount.toString(),
|
||||||
|
collateralAmountUSDC: Number(collateralAmount) / 1e6
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check USDC balance before proceeding
|
||||||
|
// Use SDK's executeMulticall to get balance (more reliable)
|
||||||
|
const balanceResult = await sdk.executeMulticall({
|
||||||
|
token: {
|
||||||
|
contractAddress: collateralToken.address,
|
||||||
|
abiId: "ERC20",
|
||||||
|
calls: {
|
||||||
|
balanceOf: {
|
||||||
|
methodName: "balanceOf",
|
||||||
|
params: [sdk.account]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const usdcBalance = balanceResult.data.token.balanceOf.returnValues[0] as bigint;
|
||||||
|
|
||||||
|
console.log('💵 USDC Balance check:', {
|
||||||
|
walletAddress: sdk.account,
|
||||||
|
usdcBalance: usdcBalance.toString(),
|
||||||
|
usdcBalanceFormatted: Number(usdcBalance) / 1e6,
|
||||||
|
requiredAmount: collateralAmount.toString(),
|
||||||
|
requiredAmountFormatted: Number(collateralAmount) / 1e6,
|
||||||
|
hasSufficientBalance: usdcBalance >= collateralAmount
|
||||||
|
});
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:835',message:'USDC balance check',data:{usdcBalance:usdcBalance.toString(),requiredAmount:collateralAmount.toString(),hasSufficientBalance:usdcBalance>=collateralAmount,quantity,currentPrice},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'F'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
if (usdcBalance < collateralAmount) {
|
||||||
|
const errorMsg = `Insufficient USDC balance: need ${Number(collateralAmount) / 1e6} USDC, but only have ${Number(usdcBalance) / 1e6} USDC`;
|
||||||
|
console.error(`❌ ${errorMsg}`);
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
// Note: Wallet initialization should be done separately before opening positions
|
// Note: Wallet initialization should be done separately before opening positions
|
||||||
// This reduces position opening time and allows for better error handling
|
// This reduces position opening time and allows for better error handling
|
||||||
@@ -801,6 +942,54 @@ export const openGmxPositionImpl = async (
|
|||||||
|
|
||||||
console.log('✅ Token allowances verified, proceeding with position opening...');
|
console.log('✅ Token allowances verified, proceeding with position opening...');
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
const exchangeRouterAddress = getContract(sdk.chainId, "ExchangeRouter");
|
||||||
|
const orderVaultAddress = getContract(sdk.chainId, "OrderVault");
|
||||||
|
const syntheticsRouterAddress = getContract(sdk.chainId, "SyntheticsRouter");
|
||||||
|
|
||||||
|
console.log(`🔍 DEBUG: ExchangeRouter address: ${exchangeRouterAddress}`);
|
||||||
|
console.log(`🔍 DEBUG: OrderVault address: ${orderVaultAddress}`);
|
||||||
|
console.log(`🔍 DEBUG: SyntheticsRouter address: ${syntheticsRouterAddress}`);
|
||||||
|
console.log(`🔍 DEBUG: USDC token address: ${collateralToken.address}`);
|
||||||
|
console.log(`🔍 DEBUG: Required collateral amount: ${collateralAmount.toString()}`);
|
||||||
|
|
||||||
|
// Force a fresh state read by waiting and checking multiple times
|
||||||
|
console.log(`⏳ Waiting 3 seconds for state to propagate...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
|
||||||
|
// Check allowance using getTokenAllowance (which uses multicall, but we'll verify with multiple reads)
|
||||||
|
console.log(`🔍 Reading ExchangeRouter allowance...`);
|
||||||
|
const finalExchangeRouterAllowance = await getTokenAllowance(sdk.account, collateralToken.address, exchangeRouterAddress);
|
||||||
|
|
||||||
|
console.log(`🔍 Reading OrderVault allowance...`);
|
||||||
|
const finalOrderVaultAllowance = await getTokenAllowance(sdk.account, collateralToken.address, orderVaultAddress);
|
||||||
|
|
||||||
|
console.log(`🔍 Reading SyntheticsRouter allowance...`);
|
||||||
|
const finalSyntheticsRouterAllowance = await getTokenAllowance(sdk.account, collateralToken.address, syntheticsRouterAddress);
|
||||||
|
|
||||||
|
// Double-check ExchangeRouter with a second read to catch any caching issues
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
const secondExchangeRouterAllowance = await getTokenAllowance(sdk.account, collateralToken.address, exchangeRouterAddress);
|
||||||
|
console.log(`🔍 Second ExchangeRouter allowance read: ${secondExchangeRouterAllowance.toString()}`);
|
||||||
|
if (finalExchangeRouterAllowance !== secondExchangeRouterAllowance) {
|
||||||
|
console.warn(`⚠️ Allowance values differ between reads! First: ${finalExchangeRouterAllowance.toString()}, Second: ${secondExchangeRouterAllowance.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🔍 DEBUG: ExchangeRouter allowance (direct read): ${finalExchangeRouterAllowance.toString()}`);
|
||||||
|
console.log(`🔍 DEBUG: OrderVault allowance (direct read): ${finalOrderVaultAllowance.toString()}`);
|
||||||
|
console.log(`🔍 DEBUG: SyntheticsRouter allowance (direct read): ${finalSyntheticsRouterAllowance.toString()}`);
|
||||||
|
console.log(`🔍 DEBUG: ExchangeRouter sufficient: ${finalExchangeRouterAllowance >= collateralAmount}`);
|
||||||
|
console.log(`🔍 DEBUG: OrderVault sufficient: ${finalOrderVaultAllowance >= collateralAmount}`);
|
||||||
|
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:850',message:'Pre-order allowance verification (direct read)',data:{exchangeRouterAddress,orderVaultAddress,syntheticsRouterAddress,exchangeRouterAllowance:finalExchangeRouterAllowance.toString(),orderVaultAllowance:finalOrderVaultAllowance.toString(),syntheticsRouterAllowance:finalSyntheticsRouterAllowance.toString(),requiredAmount:collateralAmount.toString()},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// Double-check ExchangeRouter allowance is sufficient before proceeding
|
||||||
|
if (finalExchangeRouterAllowance < collateralAmount) {
|
||||||
|
console.error(`❌ CRITICAL: ExchangeRouter allowance ${finalExchangeRouterAllowance.toString()} is insufficient for required ${collateralAmount.toString()}`);
|
||||||
|
throw new Error(`Insufficient ExchangeRouter allowance: ${finalExchangeRouterAllowance.toString()} < ${collateralAmount.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Check gas fees before opening position
|
// Check gas fees before opening position
|
||||||
console.log('⛽ Checking gas fees before opening position...');
|
console.log('⛽ Checking gas fees before opening position...');
|
||||||
const estimatedGasFee = await estimatePositionGasFee(sdk);
|
const estimatedGasFee = await estimatePositionGasFee(sdk);
|
||||||
@@ -814,11 +1003,35 @@ export const openGmxPositionImpl = async (
|
|||||||
|
|
||||||
console.log('🚀 Executing position order...');
|
console.log('🚀 Executing position order...');
|
||||||
|
|
||||||
|
// Final allowance check RIGHT before transaction execution
|
||||||
|
// Also check the current block number to ensure we're reading from the latest state
|
||||||
|
const currentBlock = await sdk.publicClient.getBlockNumber();
|
||||||
|
console.log(`🔍 DEBUG: Current block number: ${currentBlock.toString()}`);
|
||||||
|
|
||||||
|
const finalCheckAllowance = await getTokenAllowance(sdk.account, collateralToken.address, exchangeRouterAddress);
|
||||||
|
console.log(`🔍 DEBUG: Final allowance check RIGHT before order execution: ${finalCheckAllowance.toString()}`);
|
||||||
|
console.log(`🔍 DEBUG: Required amount: ${collateralAmount.toString()}`);
|
||||||
|
console.log(`🔍 DEBUG: Is sufficient: ${finalCheckAllowance >= collateralAmount}`);
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:850',message:'Final allowance check before execution',data:{blockNumber:currentBlock.toString(),finalAllowance:finalCheckAllowance.toString(),requiredAmount:collateralAmount.toString(),isSufficient:finalCheckAllowance>=collateralAmount},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'E'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
if (finalCheckAllowance < collateralAmount) {
|
||||||
|
console.error(`❌ CRITICAL: ExchangeRouter allowance ${finalCheckAllowance.toString()} is insufficient RIGHT before execution!`);
|
||||||
|
throw new Error(`Insufficient ExchangeRouter allowance at execution time: ${finalCheckAllowance.toString()} < ${collateralAmount.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (direction === TradeDirection.Long) {
|
if (direction === TradeDirection.Long) {
|
||||||
await sdk.orders.long(params);
|
await sdk.orders.long(params);
|
||||||
} else {
|
} else {
|
||||||
await sdk.orders.short(params);
|
await sdk.orders.short(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7242/ingest/556ee8d6-75a9-41e6-9311-6e6215b38a77',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'gmx.ts:837',message:'Order execution completed',data:{direction},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'D'})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
console.log('✅ Position order executed successfully');
|
console.log('✅ Position order executed successfully');
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {Ticker} from '../../src/generated/ManagingApiTypes'
|
|||||||
|
|
||||||
test('GMX Orders Closing', async (t) => {
|
test('GMX Orders Closing', async (t) => {
|
||||||
await t.test('should close all orders for BTC', async () => {
|
await t.test('should close all orders for BTC', async () => {
|
||||||
const sdk = await getClientForAddress('0x0b4A132cb6ed8fa66953bf61a53D0B91DaCaAd78')
|
const sdk = await getClientForAddress('0x932167388dD9aad41149b3cA23eBD489E2E2DD78')
|
||||||
|
|
||||||
const result = await cancelGmxOrdersImpl(
|
const result = await cancelGmxOrdersImpl(
|
||||||
sdk,
|
sdk,
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import {Ticker, TradeDirection} from '../../src/generated/ManagingApiTypes'
|
|||||||
|
|
||||||
test('GMX Position Opening', async (t) => {
|
test('GMX Position Opening', async (t) => {
|
||||||
await t.test('should open a long position for BTC', async () => {
|
await t.test('should open a long position for BTC', async () => {
|
||||||
const sdk = await getClientForAddress('0x0b4A132cb6ed8fa66953bf61a53D0B91DaCaAd78')
|
const sdk = await getClientForAddress('0x932167388dD9aad41149b3cA23eBD489E2E2DD78')
|
||||||
|
|
||||||
const result = await openGmxPositionImpl(
|
const result = await openGmxPositionImpl(
|
||||||
sdk,
|
sdk,
|
||||||
Ticker.BTC,
|
Ticker.BTC,
|
||||||
TradeDirection.Long,
|
TradeDirection.Long,
|
||||||
0.00009129924572776991,
|
0.00012, // ~5.3 USDC collateral with 2x leverage (fits available balance of 5.69 USDC)
|
||||||
2,
|
2,
|
||||||
111350,
|
87856,
|
||||||
110000,
|
undefined, // No stop-loss
|
||||||
114000
|
undefined // No take-profit
|
||||||
)
|
)
|
||||||
console.log('Position opening result:', result)
|
console.log('Position opening result:', result)
|
||||||
assert.ok(result, 'Position opening result should be defined')
|
assert.ok(result, 'Position opening result should be defined')
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ describe('swap tokens implementation', () => {
|
|||||||
console.log('Account', sdk.account)
|
console.log('Account', sdk.account)
|
||||||
const result = await swapGmxTokensImpl(
|
const result = await swapGmxTokensImpl(
|
||||||
sdk,
|
sdk,
|
||||||
Ticker.BTC,
|
|
||||||
Ticker.USDC,
|
Ticker.USDC,
|
||||||
0.00006893,
|
Ticker.SOL,
|
||||||
|
3,
|
||||||
'market',
|
'market',
|
||||||
undefined,
|
undefined,
|
||||||
0.5
|
0.5
|
||||||
|
|||||||
Reference in New Issue
Block a user