|
|
@@ -15,6 +15,7 @@ const STABLECOIN_MINTS = new Set([USDC_MINT, USDT_MINT, USD1_MINT])
|
|
|
const SKIP_SWAP_BACK_MINTS = new Set([USDC_MINT, USDT_MINT, SOL_MINT, USD1_MINT])
|
|
|
|
|
|
const ULTRA_API_URL = 'https://api.jup.ag/ultra/v1'
|
|
|
+const SWAP_API_URL = 'https://api.jup.ag/swap/v1'
|
|
|
|
|
|
interface UltraOrderResponse {
|
|
|
requestId: string
|
|
|
@@ -110,7 +111,58 @@ async function executeUltraSwap(
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 签名并执行 Ultra swap
|
|
|
+ * Jupiter Swap v1 API: GET /quote (supports ExactOut)
|
|
|
+ */
|
|
|
+async function fetchSwapQuote(
|
|
|
+ inputMint: string,
|
|
|
+ outputMint: string,
|
|
|
+ amount: string,
|
|
|
+ slippageBps: number,
|
|
|
+ swapMode: 'ExactIn' | 'ExactOut',
|
|
|
+): Promise<Record<string, unknown>> {
|
|
|
+ return ky
|
|
|
+ .get(`${SWAP_API_URL}/quote`, {
|
|
|
+ searchParams: {
|
|
|
+ inputMint,
|
|
|
+ outputMint,
|
|
|
+ amount,
|
|
|
+ slippageBps: String(slippageBps),
|
|
|
+ swapMode,
|
|
|
+ },
|
|
|
+ timeout: 30000,
|
|
|
+ headers: getJupiterHeaders(),
|
|
|
+ })
|
|
|
+ .json<Record<string, unknown>>()
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Jupiter Swap v1 API: POST /swap (returns unsigned transaction)
|
|
|
+ */
|
|
|
+async function fetchSwapTransaction(
|
|
|
+ quoteResponse: Record<string, unknown>,
|
|
|
+ userPublicKey: string,
|
|
|
+): Promise<{ swapTransaction: string; lastValidBlockHeight: number }> {
|
|
|
+ return ky
|
|
|
+ .post(`${SWAP_API_URL}/swap`, {
|
|
|
+ json: {
|
|
|
+ quoteResponse,
|
|
|
+ userPublicKey,
|
|
|
+ wrapAndUnwrapSol: true,
|
|
|
+ dynamicComputeUnitLimit: true,
|
|
|
+ },
|
|
|
+ timeout: 30000,
|
|
|
+ headers: {
|
|
|
+ ...getJupiterHeaders(),
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ })
|
|
|
+ .json<{ swapTransaction: string; lastValidBlockHeight: number }>()
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 签名并执行 swap
|
|
|
+ * - ExactIn: 使用 Jupiter Ultra API(更快)
|
|
|
+ * - ExactOut: 使用 Jupiter Swap v1 API(Ultra 不支持 ExactOut)
|
|
|
*/
|
|
|
async function signAndExecuteSwap(
|
|
|
inputMint: string,
|
|
|
@@ -122,6 +174,11 @@ async function signAndExecuteSwap(
|
|
|
const keypair = getKeypair()
|
|
|
|
|
|
try {
|
|
|
+ if (swapMode === 'ExactOut') {
|
|
|
+ return await signAndExecuteSwapV1(inputMint, outputMint, amount, slippageBps, swapMode)
|
|
|
+ }
|
|
|
+
|
|
|
+ // ExactIn: use Ultra API
|
|
|
const orderData = await fetchUltraOrder(inputMint, outputMint, amount, keypair.publicKey.toBase58(), slippageBps, swapMode)
|
|
|
|
|
|
if (!orderData.transaction) {
|
|
|
@@ -156,6 +213,53 @@ async function signAndExecuteSwap(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * ExactOut swap via Jupiter Swap v1 API (quote → swap → sign → send)
|
|
|
+ */
|
|
|
+async function signAndExecuteSwapV1(
|
|
|
+ inputMint: string,
|
|
|
+ outputMint: string,
|
|
|
+ amount: string,
|
|
|
+ slippageBps: number,
|
|
|
+ swapMode: 'ExactIn' | 'ExactOut',
|
|
|
+): Promise<{ success: boolean; txid?: string; error?: string }> {
|
|
|
+ const keypair = getKeypair()
|
|
|
+ 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}`)
|
|
|
+
|
|
|
+ // 2. Get swap transaction
|
|
|
+ const { swapTransaction } = await fetchSwapTransaction(quoteResponse, keypair.publicKey.toBase58())
|
|
|
+
|
|
|
+ // 3. Sign and send
|
|
|
+ const txBuf = Buffer.from(swapTransaction, 'base64')
|
|
|
+ const transaction = VersionedTransaction.deserialize(txBuf)
|
|
|
+ transaction.sign([keypair])
|
|
|
+
|
|
|
+ const signature = await connection.sendRawTransaction(transaction.serialize(), {
|
|
|
+ skipPreflight: true,
|
|
|
+ maxRetries: 3,
|
|
|
+ })
|
|
|
+
|
|
|
+ // 4. Confirm
|
|
|
+ const latestBlockhash = await connection.getLatestBlockhash('confirmed')
|
|
|
+ await connection.confirmTransaction(
|
|
|
+ { signature, ...latestBlockhash },
|
|
|
+ 'confirmed',
|
|
|
+ )
|
|
|
+
|
|
|
+ console.log(`[Swap] v1 Confirmed: ${signature}`)
|
|
|
+ return { success: true, txid: signature }
|
|
|
+ } catch (error) {
|
|
|
+ const errorMessage = error instanceof Error ? error.message : 'Unknown swap error'
|
|
|
+ console.error(`[Swap] v1 Failed:`, errorMessage)
|
|
|
+ return { success: false, error: errorMessage }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 获取代币余额(raw amount)
|
|
|
*/
|