3

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 1

5

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());
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.