|
|
@@ -214,7 +214,9 @@ async function signAndExecuteSwap(
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * ExactOut swap via Jupiter Swap v1 API (quote → swap → sign → send)
|
|
|
+ * ExactOut swap via Jupiter Swap v1 API.
|
|
|
+ * If ExactOut is not supported for the pair (400), falls back to ExactIn
|
|
|
+ * by estimating the input amount from a reference quote + 15% buffer.
|
|
|
*/
|
|
|
async function signAndExecuteSwapV1(
|
|
|
inputMint: string,
|
|
|
@@ -227,14 +229,27 @@ async function signAndExecuteSwapV1(
|
|
|
const connection = (await import('../solana/connection')).getConnection()
|
|
|
|
|
|
try {
|
|
|
- // 1. Get quote
|
|
|
- const quoteResponse = await fetchSwapQuote(inputMint, outputMint, amount, slippageBps, swapMode)
|
|
|
- console.log(`[Swap] v1 ${swapMode} quote: in=${quoteResponse.inAmount}, out=${quoteResponse.outAmount}`)
|
|
|
+ let quoteResponse: Record<string, unknown>
|
|
|
|
|
|
- // 2. Get swap transaction
|
|
|
+ if (swapMode === 'ExactOut') {
|
|
|
+ try {
|
|
|
+ // Try ExactOut first
|
|
|
+ quoteResponse = await fetchSwapQuote(inputMint, outputMint, amount, slippageBps, 'ExactOut')
|
|
|
+ } catch {
|
|
|
+ // ExactOut not supported for this pair, fall back to ExactIn
|
|
|
+ console.log('[Swap] ExactOut not available, estimating ExactIn amount via reference quote')
|
|
|
+ quoteResponse = await estimateExactInQuote(inputMint, outputMint, amount, slippageBps)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ quoteResponse = await fetchSwapQuote(inputMint, outputMint, amount, slippageBps, swapMode)
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[Swap] v1 quote: in=${quoteResponse.inAmount}, out=${quoteResponse.outAmount}, mode=${quoteResponse.swapMode}`)
|
|
|
+
|
|
|
+ // Get swap transaction
|
|
|
const { swapTransaction } = await fetchSwapTransaction(quoteResponse, keypair.publicKey.toBase58())
|
|
|
|
|
|
- // 3. Sign and send
|
|
|
+ // Sign and send
|
|
|
const txBuf = Buffer.from(swapTransaction, 'base64')
|
|
|
const transaction = VersionedTransaction.deserialize(txBuf)
|
|
|
transaction.sign([keypair])
|
|
|
@@ -244,7 +259,7 @@ async function signAndExecuteSwapV1(
|
|
|
maxRetries: 3,
|
|
|
})
|
|
|
|
|
|
- // 4. Confirm
|
|
|
+ // Confirm
|
|
|
const latestBlockhash = await connection.getLatestBlockhash('confirmed')
|
|
|
await connection.confirmTransaction(
|
|
|
{ signature, ...latestBlockhash },
|
|
|
@@ -260,6 +275,42 @@ async function signAndExecuteSwapV1(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * ExactOut 不可用时,通过参考报价估算所需 ExactIn 输入量。
|
|
|
+ * 1. 用小额 ExactIn 报价获取汇率
|
|
|
+ * 2. 按汇率反推所需输入量,加 15% buffer
|
|
|
+ * 3. 用计算出的输入量获取正式 ExactIn 报价
|
|
|
+ */
|
|
|
+async function estimateExactInQuote(
|
|
|
+ inputMint: string,
|
|
|
+ outputMint: string,
|
|
|
+ desiredOutputAmount: string,
|
|
|
+ slippageBps: number,
|
|
|
+): Promise<Record<string, unknown>> {
|
|
|
+ // Use 10 USDC (10_000_000 raw) as reference to get the exchange rate.
|
|
|
+ // For non-USDC input tokens, use 10 units of whatever the input is (6-decimal assumed).
|
|
|
+ const refInputAmount = '10000000'
|
|
|
+ const refQuote = await fetchSwapQuote(inputMint, outputMint, refInputAmount, slippageBps, 'ExactIn')
|
|
|
+
|
|
|
+ const refIn = BigInt(refQuote.inAmount as string)
|
|
|
+ const refOut = BigInt(refQuote.outAmount as string)
|
|
|
+
|
|
|
+ if (refOut === 0n) {
|
|
|
+ throw new Error('Reference quote returned 0 output, cannot estimate rate')
|
|
|
+ }
|
|
|
+
|
|
|
+ // estimatedInput = desiredOutput * (refIn / refOut) * 1.15
|
|
|
+ const desiredOut = BigInt(desiredOutputAmount)
|
|
|
+ const estimatedInput = (desiredOut * refIn * 115n) / (refOut * 100n)
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ `[Swap] Rate estimate: ${refIn}→${refOut}, need ${desiredOut} out, estimated input ${estimatedInput} (+15% buffer)`,
|
|
|
+ )
|
|
|
+
|
|
|
+ // Get actual ExactIn quote with estimated input amount
|
|
|
+ return fetchSwapQuote(inputMint, outputMint, estimatedInput.toString(), slippageBps, 'ExactIn')
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 获取代币余额(raw amount)
|
|
|
*/
|