161 lines
4.8 KiB
JavaScript
161 lines
4.8 KiB
JavaScript
import {
|
||
AddressLookupTableAccount,
|
||
PublicKey,
|
||
TransactionInstruction
|
||
} from "@solana/web3.js"
|
||
import {
|
||
DEFAULT_OPTIONS,
|
||
JUP_API,
|
||
JUP_REFERRAL_ADDRESS,
|
||
TOKENS
|
||
} from "../../constants"
|
||
|
||
import { getMint } from "@solana/spl-token"
|
||
import { sendTx } from "../../utils/send_tx"
|
||
|
||
/**
|
||
* Swap tokens using Jupiter Exchange
|
||
* @param agent SolanaAgentKit instance
|
||
* @param outputMint Target token mint address
|
||
* @param inputAmount Amount to swap (in token decimals)
|
||
* @param inputMint Source token mint address (defaults to USDC)
|
||
* @param slippageBps Slippage tolerance in basis points (default: 300 = 3%)
|
||
* @returns Transaction signature
|
||
*/
|
||
|
||
export async function trade(
|
||
agent,
|
||
outputMint,
|
||
inputAmount,
|
||
inputMint = TOKENS.USDC,
|
||
slippageBps = DEFAULT_OPTIONS.SLIPPAGE_BPS
|
||
) {
|
||
try {
|
||
// Check if input token is native SOL
|
||
const isNativeSol = inputMint.equals(TOKENS.SOL)
|
||
|
||
// For native SOL, we use LAMPORTS_PER_SOL, otherwise fetch mint info
|
||
const inputDecimals = isNativeSol
|
||
? 9 // SOL always has 9 decimals
|
||
: (await getMint(agent.connection, inputMint)).decimals
|
||
|
||
// Calculate the correct amount based on actual decimals
|
||
const scaledAmount = inputAmount * Math.pow(10, inputDecimals)
|
||
|
||
const quoteResponse = await (
|
||
await fetch(
|
||
`${JUP_API}/quote?` +
|
||
`inputMint=${
|
||
isNativeSol ? TOKENS.SOL.toString() : inputMint.toString()
|
||
}` +
|
||
`&outputMint=${outputMint.toString()}` +
|
||
`&amount=${scaledAmount}` +
|
||
`&slippageBps=${slippageBps}` +
|
||
`&onlyDirectRoutes=true` +
|
||
`&maxAccounts=40` +
|
||
`${
|
||
agent.config.JUPITER_FEE_BPS
|
||
? `&platformFeeBps=${agent.config.JUPITER_FEE_BPS}`
|
||
: ""
|
||
}`
|
||
)
|
||
).json()
|
||
|
||
// Get serialized transaction
|
||
let feeAccount
|
||
if (agent.config.JUPITER_REFERRAL_ACCOUNT) {
|
||
;[feeAccount] = PublicKey.findProgramAddressSync(
|
||
[
|
||
Buffer.from("referral_ata"),
|
||
new PublicKey(agent.config.JUPITER_REFERRAL_ACCOUNT).toBuffer(),
|
||
TOKENS.SOL.toBuffer()
|
||
],
|
||
new PublicKey(JUP_REFERRAL_ADDRESS)
|
||
)
|
||
}
|
||
|
||
const instructions = await (
|
||
await fetch("https://quote-api.jup.ag/v6/swap-instructions", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
body: JSON.stringify({
|
||
// quoteResponse from /quote or /swap api
|
||
quoteResponse,
|
||
userPublicKey: agent.wallet.publicKey.toBase58()
|
||
// other Jupiter request fields if needed
|
||
})
|
||
})
|
||
).json()
|
||
// Deserialize transaction
|
||
if (instructions.error) {
|
||
throw new Error("Failed to get swap instructions: " + instructions.error)
|
||
}
|
||
|
||
const {
|
||
tokenLedgerInstruction, // If using `useTokenLedger = true`
|
||
computeBudgetInstructions, // Jupiter’s default compute budget instructions (we will NOT use these)
|
||
setupInstructions, // Setup ATAs if needed
|
||
swapInstruction: swapInstructionPayload,
|
||
cleanupInstruction, // Unwrap SOL, if you used wrapAndUnwrapSol
|
||
addressLookupTableAddresses
|
||
} = instructions
|
||
|
||
const deserializeInstruction = instruction => {
|
||
return new TransactionInstruction({
|
||
programId: new PublicKey(instruction.programId),
|
||
keys: instruction.accounts.map(key => ({
|
||
pubkey: new PublicKey(key.pubkey),
|
||
isSigner: key.isSigner,
|
||
isWritable: key.isWritable
|
||
})),
|
||
data: Buffer.from(instruction.data, "base64")
|
||
})
|
||
}
|
||
|
||
const getAddressLookupTableAccounts = async keys => {
|
||
const addressLookupTableAccountInfos = await agent.connection.getMultipleAccountsInfo(
|
||
keys.map(key => new PublicKey(key))
|
||
)
|
||
|
||
return addressLookupTableAccountInfos.reduce(
|
||
(acc, accountInfo, index) => {
|
||
const addressLookupTableAddress = keys[index]
|
||
if (accountInfo) {
|
||
const addressLookupTableAccount = new AddressLookupTableAccount({
|
||
key: new PublicKey(addressLookupTableAddress),
|
||
state: AddressLookupTableAccount.deserialize(accountInfo.data)
|
||
})
|
||
acc.push(addressLookupTableAccount)
|
||
}
|
||
|
||
return acc
|
||
},
|
||
new Array()
|
||
)
|
||
}
|
||
|
||
const addressLookupTableAccounts = []
|
||
|
||
addressLookupTableAccounts.push(
|
||
...(await getAddressLookupTableAccounts(addressLookupTableAddresses))
|
||
)
|
||
|
||
const signature = await sendTx(
|
||
agent,
|
||
[
|
||
...setupInstructions.map(deserializeInstruction),
|
||
deserializeInstruction(swapInstructionPayload),
|
||
deserializeInstruction(cleanupInstruction)
|
||
],
|
||
undefined,
|
||
addressLookupTableAccounts
|
||
)
|
||
|
||
return signature
|
||
} catch (error) {
|
||
throw new Error(`Swap failed: ${error.message}`)
|
||
}
|
||
}
|