1

I am trying to perform a swap using Jupiter aggregator API.

The code below is written based on the examples from Jupiter docs, some parts were taken from other sources.

It works, but the issue is that it's using legacy transactions standard. I need help in updating this code to the new versioned transactions standard.

I want to be able to add more than one swap in a single transaction, which you cannot do with legacy, but can with versioned.

Please don't judge too hard, I'm still a beginner.

I would highly appreciate if someone could directly show me how to resolve my issue, not just sharing docs on versioned transactions. I've read through them and still have difficulties.

import bs58 from "bs58";
import {
  Connection,
  Keypair,
  Transaction,
  PublicKey,
  SystemProgram,
} from "@solana/web3.js";
import got from "got";
import { Wallet } from "@project-serum/anchor";
import promiseRetry from "promise-retry";
import {
  ASSOCIATED_TOKEN_PROGRAM_ID,
  Token,
  TOKEN_PROGRAM_ID,
} from "@solana/spl-token";

// Private key is stored in .env
console.log({ dotenv });
dotenv.config();

// RPC Endpoint
const connection = new Connection("https://cosmopolitan-falling-model.solana-mainnet.discover.quiknode.pro/e60644c0da4aaad24292d9ffe56ed80a013dfe98/");
const wallet = new Wallet(
  Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY || ""))
);

const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const SOL_MINT = "So11111111111111111111111111111111111111112";

// wsol account
const createWSolAccount = async () => {
  const wsolAddress = await Token.getAssociatedTokenAddress(
    ASSOCIATED_TOKEN_PROGRAM_ID,
    TOKEN_PROGRAM_ID,
    new PublicKey(SOL_MINT),
    wallet.publicKey
  );

  const wsolAccount = await connection.getAccountInfo(wsolAddress);

  if (!wsolAccount) {
    const transaction = new Transaction({
      feePayer: wallet.publicKey,
    });
    const instructions = [];

    instructions.push(
      await Token.createAssociatedTokenAccountInstruction(
        ASSOCIATED_TOKEN_PROGRAM_ID,
        TOKEN_PROGRAM_ID,
        new PublicKey(SOL_MINT),
        wsolAddress,
        wallet.publicKey,
        wallet.publicKey
      )
    );

    // fund 1 sol to the account
    instructions.push(
      SystemProgram.transfer({
        fromPubkey: wallet.publicKey,
        toPubkey: wsolAddress,
        lamports: 200_000_000, // 0.2 sol
      })
    );

    instructions.push(
      // This is not exposed by the types, but indeed it exists
      Token.createSyncNativeInstruction(TOKEN_PROGRAM_ID, wsolAddress)
    );

    transaction.add(...instructions);
    transaction.recentBlockhash = await (
      await connection.getRecentBlockhash()
    ).blockhash;
    transaction.partialSign(wallet.payer);
    const result = await connection.sendTransaction(transaction, [
      wallet.payer,
    ]);
    console.log({ result });
  }

  return wsolAccount;
};

const getCoinQuote = (inputMint, outputMint, amount) =>
  got
    .get(
      `https://quote-api.jup.ag/v1/quote?outputMint=${outputMint}&inputMint=${inputMint}&amount=${amount}&slippage=0.2`
    )
    .json();

const getTransaction = (route) => {
  return got
    .post("https://quote-api.jup.ag/v1/swap", {
      json: {
        route: route,
        userPublicKey: wallet.publicKey.toString(),
        // to make sure it doesnt close the sol account
        wrapUnwrapSOL: false,
      },
    })
    .json();
};

const getConfirmTransaction = async (txid) => {
  const res = await promiseRetry(
    async (retry, attempt) => {
      let txResult = await connection.getTransaction(txid, {
        commitment: "confirmed",
      });

      if (!txResult) {
        const error = new Error("Transaction was not confirmed");
        error.txid = txid;

        retry(error);
        return;
      }
      return txResult;
    },
    {
      retries: 40,
      minTimeout: 500,
      maxTimeout: 1000,
    }
  );
  if (res.meta.err) {
    throw new Error("Transaction failed");
  }
  return txid;
};

// require wsol to start trading, this function create your wsol account and fund 1 SOL to it
await createWSolAccount();

const initial = 100_000_000;

// 0.1 SOL
const sol_to_usdc = await getCoinQuote(SOL_MINT, USDC_MINT, initial);
//console.log(sol_to_usdc);

//console.log(sol_to_usdc.data[0]);
  
await Promise.all(
      [sol_to_usdc.data[0]].map(async (route) => {
        const { setupTransaction, swapTransaction, cleanupTransaction } =
          await getTransaction(route);

        await Promise.all(
          [setupTransaction, swapTransaction, cleanupTransaction]
            .filter(Boolean)
            .map(async (serializedTransaction) => {
              // get transaction object from serialized transaction
              const transaction = Transaction.from(
                Buffer.from(serializedTransaction, "base64")
              );
              //console.log(transaction);
              
              // perform the swap
              // Transaction might failed or dropped
              const txid = await connection.sendTransaction(
                transaction,
                [wallet.payer],
                {
                  skipPreflight: true,
                }
              );
              try {
                await getConfirmTransaction(txid);
                console.log(`%cSuccess: https://solscan.io/tx/${txid}`, 'color: green');
              } catch (e) {
                console.log(`%cFailed: https://solscan.io/tx/${txid}`, 'color: red');
              }
            })
        );
      })
    );```

1 Answer 1

1

Currently, Solana transactions are limited to 1232 bytes. The main feature that versioned transactions (v0) enable is the use of Address Lookup Tables.

Effectively, "Address Lookup Tables" are accounts used to store addresses of other accounts, which can then be referenced in versioned transactions using a 1 byte index instead of the full 32 bytes per address. This enables the creation of more complex transactions (ex. the 3 hop swaps you sometimes see on Jupiter).

To achieve your goal, you'll need to build your transaction using lookup tables (although I believe 2 hop swaps on Jupiter are still possible using legacy transactions). Here are some resources that you may find helpful in case you haven't seen them.

https://station.jup.ag/docs/additional-topics/composing-with-versioned-transaction#what-are-versioned-transactions

https://www.soldev.app/course/versioned-transaction

1
  • Thank you John, this is really helpful. However this is too difficult for me to implement at my current programming level. I am willing to pay to someone who can help me rewrite my code. Hit me up if you're interested.
    – pleyosol
    Commented Jun 20, 2023 at 11:28

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.