I want to use the Jupiter API to swap a given token to USDC in Typescript. How can I go about doing this?
1 Answer
First thing that needs to be done is fetching a quote. This can be done via the /v6/quote endpoint. Here we'll take an "inputMint" as a string for the token we want to swap from and use axios
to fetch the quote:
export async function getQuote(
inputMint: string,
amount: number,
slippage: number
) {
try {
let mint = inputMint.toString();
let amt = amount;
const url = `https://quote-api.jup.ag/v6/quote?inputMint=${mint}&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=${amt}&slippageBps=${slippage}&maxAccounts=54&onlyDirectRoutes=true`;
const res = await axios.get(url);
return res.data;
} catch (err) {
console.error(err);
}
}
Since I'll be adding instructions in addition to the swap, I want to limit the total instructions I'll receive in the next step. Therefore, I've set "onlyDirectRoutes" to true, and "maxAccounts" to 54. Additionally, slippage will be variable depending on the token I'm swapping from. Next step is to get the swap instructions:
// POST request to /v6/swap-instructions endpoint w/ the quote from previous step
const instructions = await (
await fetch("https://quote-api.jup.ag/v6/swap-instructions", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
// Quote from previous step
quoteResponse: quote,
// Signer's publicKey
userPublicKey: wallet.publicKey?.toString(),
// Variable compute limit == true
dynamicComputeUnitLimit: true,
}),
})
).json();
// If API returns an error code, throw an error
if (instructions.error) {
throw new Error("Failed to get swap instructions: " + instructions.error);
}
// All of the instructions you'll need to setup ATAs, add ComputeBudget
// Conduct the swap, wrap/unwrap SOL if needed, and create lookup tables
const {
tokenLedgerInstruction,
computeBudgetInstructions,
setupInstructions,
swapInstruction: swapInstructionPayload,
cleanupInstruction,
addressLookupTableAddresses,
} = instructions;
// Deserialize instruction to get necessary data
const deserializeInstruction = (instruction: any) => {
return new TransactionInstruction({
programId: new PublicKey(instruction.programId),
keys: instruction.accounts.map((key: any) => ({
pubkey: new PublicKey(key.pubkey),
isSigner: key.isSigner,
isWritable: key.isWritable,
})),
data: Buffer.from(instruction.data, "base64"),
});
};
// Create AddressLookupTableAccount[] type given an array of string publicKeys
const getAddressLookupTableAccounts = async (
keys: string[]
): Promise<AddressLookupTableAccount[]> => {
// Get account infos
const addressLookupTableAccountInfos =
await connection.getMultipleAccountsInfo(
keys.map((key) => new PublicKey(key))
);
// Create AddressLookupTableAccount from the account info of a given key in the "keys" array
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<AddressLookupTableAccount>());
};
const addressLookupTableAccounts: AddressLookupTableAccount[] = [];
// Create lookup table to be added in transaction message
addressLookupTableAccounts.push(
...(await getAddressLookupTableAccounts(addressLookupTableAddresses))
);
const blockhash = (await connection.getLatestBlockhash()).blockhash;
// Create a v0 message with necessary instructions, depending on the mint
const messageV0 =
inputMint == NATIVE_MINT.toString()
? new TransactionMessage({
payerKey: wallet.adapter.publicKey as PublicKey,
recentBlockhash: blockhash,
instructions: [
// Include creation of ATAs
...setupInstructions.map(deserializeInstruction),
deserializeInstruction(swapInstructionPayload),
// Wrap & Unwrap SOL if using the SOL mint
deserializeInstruction(cleanupInstruction),
],
// Compile to a versioned message, and add lookup table accounts
}).compileToV0Message(addressLookupTableAccounts)
: new TransactionMessage({
payerKey: wallet.adapter.publicKey as PublicKey,
recentBlockhash: blockhash,
instructions: [
deserializeInstruction(swapInstructionPayload),
],
// Compile to a versioned message, and add lookup table accounts
}).compileToV0Message(addressLookupTableAccounts);
const transaction = new VersionedTransaction(messageV0);
return transaction
From here, we're ready to sign & send the transaction. You can do this with wallet adapter using sendTransaction
& connection.confirmTransaction
:
const signature = await sendTransaction(transaction, connection);
console.log("Transaction Signature:", signature);
const block = await connection.getLatestBlockhash("confirmed");
console.log("Confirming...");
const result = await connection.confirmTransaction(
{
signature,
...block,
},
"confirmed"
);
const error = result.value.err;
if (error) {
throw Error(error.toString());
}