Jupiter

Jupiter คือ liquidity aggregator หลักของ Solana, สนับสนุน tokens มากมาย และมี route discovery ระหว่าง token pair ที่ดีที่สุดให้ด้วย

การติดตั้ง

@jup-ag/core คือ Core package เอาไว้ใช้ติดต่อกับ jupiter on-chain programs เพื่อทำการ swaps ระหว่าง token pairs ที่เป็นไปได้

yarn add @jup-ag/core
npm install @jup-ag/core

ดึงข้อมูลรายการ Token จาก Jupiter

ดึงข้อมูลทุก tokens ที่สามารถ swap ด้วย jupiter สำหรับ network ใดๆ

Press </> button to view full source
import { Jupiter, RouteInfo, TOKEN_LIST_URL } from "@jup-ag/core";
import { Connection, PublicKey } from "@solana/web3.js";

interface Token {
  chainId: number;
  address: string;
  symbol: string;
  name: string;
  decimals: number;
  logoURI: string;
  tags: string[];
}

(async () => {
  const ENV = "mainnet-beta";
  const tokens: Token[] = await (await fetch(TOKEN_LIST_URL[ENV])).json();
})();

Loading the Jupiter instance

Jupiter instance กำลังสร้างด้วย configurations ที่ให้มา เรามีตัวเลือก parameters มากมายที่จะส่งไปให้ instanceได้ ลองอ่านเพิ่มเติมได้ ที่นี่open in new window

Press </> button to view full source
import { Jupiter, RouteInfo, TOKEN_LIST_URL } from "@jup-ag/core";
import { Connection, PublicKey, Keypair } from "@solana/web3.js";

interface Token {
  chainId: number;
  address: string;
  symbol: string;
  name: string;
  decimals: number;
  logoURI: string;
  tags: string[];
}

(async () => {
  const ENV = "devnet";
  const tokens: Token[] = await (await fetch(TOKEN_LIST_URL[ENV])).json();

  const USER_KEYPAIR = Keypair.generate();
  const connection = new Connection("https://api.devnet.solana.com");

  const jupiter = await Jupiter.load({
    connection,
    cluster: ENV,
    user: USER_KEYPAIR, 
  });
})();

หาเส้นทาง RouteMap

RouteMap จะบอกเราว่า tokens สามารถ swap ด้วย input token ที่ให้มาได้หรือเปล่า ซึ่งจะมีแต่ token mint addresses และไม่มี metadata.

Press </> button to view full source
import { Jupiter, RouteInfo, TOKEN_LIST_URL } from "@jup-ag/core";
import { Connection, PublicKey, Keypair } from "@solana/web3.js";

interface Token {
  chainId: number;
  address: string;
  symbol: string;
  name: string;
  decimals: number;
  logoURI: string;
  tags: string[];
}

(async () => {
  const ENV = "devnet";
  const tokens: Token[] = await (await fetch(TOKEN_LIST_URL[ENV])).json();

  const USER_KEYPAIR = Keypair.generate();
  const connection = new Connection("https://api.devnet.solana.com");

  const jupiter = await Jupiter.load({
    connection,
    cluster: ENV,
    user: USER_KEYPAIR, 
  });

  const routeMap = jupiter.getRouteMap();
})();

หาเส้นทางสำหรับ Input และ Output token ที่ให้มา

methods computeRoutes รับ input/output Mint address และคืนค่า routes ที่เป็นไปได้ทั้งหมดโดยเรียงจากราคาที่ดีที่สุดมาก่อน

Press </> button to view full source
import { Jupiter, RouteInfo, TOKEN_LIST_URL } from "@jup-ag/core";
import { Connection, PublicKey, Keypair } from "@solana/web3.js";

interface Token {
  chainId: number;
  address: string;
  symbol: string;
  name: string;
  decimals: number;
  logoURI: string;
  tags: string[];
}

(async () => {
  const ENV = "devnet";
  const tokens: Token[] = await (await fetch(TOKEN_LIST_URL[ENV])).json();

  const USER_KEYPAIR = Keypair.generate();
  const connection = new Connection("https://api.devnet.solana.com");

  const jupiter = await Jupiter.load({
    connection,
    cluster: ENV,
    user: USER_KEYPAIR, 
  });

  const routeMap = jupiter.getRouteMap();

  const inputToken = "So11111111111111111111111111111111111111112";
  const outputToken = "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt";
  const inputAmount = 1; 
  const slippage = 1; 

  const routes = await jupiter.computeRoutes({
    inputMint: new PublicKey(inputToken), 
    outputMint: new PublicKey(outputToken), 
    inputAmount, 
    slippage, 
    forceFetch: false, 
  });
})();

Execute the Token Swap

method exchange จะถูกเรียกตรงนี้เพื่อสร้าง transaction สำหรับ route ที่ได้มา

Press </> button to view full source
import { Jupiter, RouteInfo, TOKEN_LIST_URL } from "@jup-ag/core";
import { Connection, PublicKey, Keypair } from "@solana/web3.js";

interface Token {
  chainId: number;
  address: string;
  symbol: string;
  name: string;
  decimals: number;
  logoURI: string;
  tags: string[];
}

(async () => {
  const ENV = "devnet";
  const tokens: Token[] = await (await fetch(TOKEN_LIST_URL[ENV])).json();

  const USER_KEYPAIR = Keypair.generate();
  const connection = new Connection("https://api.devnet.solana.com");

  const jupiter = await Jupiter.load({
    connection,
    cluster: ENV,
    user: USER_KEYPAIR, 
  });

  const routeMap = jupiter.getRouteMap();

  const inputToken = "So11111111111111111111111111111111111111112";
  const outputToken = "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt";
  const inputAmount = 1; 
  const slippage = 1; 

  const routes = await jupiter.computeRoutes({
    inputMint: new PublicKey(inputToken), 
    outputMint: new PublicKey(outputToken),
    inputAmount, 
    slippage,
    forceFetch: false, 
  });

  const { execute } = await jupiter.exchange({
    routeInfo: routes.routesInfos[0],
  });
  
  const swapResult: any = await execute(); 
  
})();

วิธีใช้ Jupiter กับ React Application

ติดตั้ง

yarn add @jup-ag/react-hook
npm install @jup-ag/react-hook

เพิ่ม Provider

เราจะติดตั้ง JupiterProvider ตรงนี้เพื่อจะใช้ useJupiter Hook ใน React App. โดยที่จะตั้ง parameter ให้ cluster เป็น mainnet-beta เพื่อจะได้ tokens ที่หลากหลาย แต่เราจะเปลี่ยนเป็น devnet ก็ได้เหมือนกันถ้าต้องการ

Press </> button to view full source
import {
  ConnectionProvider,
  WalletProvider,
  useConnection,
  useWallet,
} from "@solana/wallet-adapter-react";
import {
  getLedgerWallet,
  getPhantomWallet,
  getSlopeWallet,
  getSolflareWallet,
  getSolletExtensionWallet,
  getSolletWallet,
  getTorusWallet,
} from "@solana/wallet-adapter-wallets";

const JupiterApp = ({ children }) => {
  const { connection } = useConnection();
  const wallet = useWallet();

  return (
    <JupiterProvider
      cluster="mainnet-beta"
      connection={connection}
      userPublicKey={wallet.publicKey || undefined}
    >
      {children}
    </JupiterProvider>
  );
};

const App = ({ children }) => {
  const network = WalletAdapterNetwork.Devnet;
  const wallets = useMemo(
    () => [
      getPhantomWallet(),
      getSlopeWallet(),
      getSolflareWallet(),
      getTorusWallet(),
      getLedgerWallet(),
      getSolletWallet({ network }),
      getSolletExtensionWallet({ network }),
    ],
    [network]
  );
  const endpoint = "https://solana-api.projectserum.com";

  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider wallets={wallets} autoConnect>
        <JupiterApp>{children}</JupiterApp>
      </WalletProvider>
    </ConnectionProvider>
  );
};

export default App;

ดึงข้อมูลรายการ Tokens

ดึงข้อมูลทุก tokens ที่สามารถ swap ด้วย jupiter สำหรับ network ใดๆ แล้วเก็บไว้ใน state

Press </> button to view full source
import { TOKEN_LIST_URL } from "@jup-ag/core";

const JupiterApp = () => {
  const [tokens, setTokens] = useState<Token[]>([]);
  useEffect(() => {
    fetch(TOKEN_LIST_URL[ENV])
      .then((response) => response.json())
      .then((result) => setTokens(result));
  }, []);
};

export default JupiterApp;

ตั้งค่า State

InputMint และ OutputMint คือ state ที่เพิ่มเข้าไปเพื่อจะสามารถ swapกันเอง หรือดึงมาจาก user อื่นด้วยก็ได้

Press </> button to view full source
import { TOKEN_LIST_URL } from "@jup-ag/core";

const JupiterApp = () => {
  const [tokens, setTokens] = useState<Token[]>([]);
  const [inputMint] = useState<PublicKey>(
    new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
  );
  const [outputMint] = useState<PublicKey>(
    new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")
  );

  useEffect(() => {

    fetch(TOKEN_LIST_URL[ENV])
      .then((response) => response.json())
      .then((result) => setTokens(result));
  }, []);
};

export default JupiterApp;

การใช้ react hook useJupiter

useJupiter Hook จะรับ parameters เพื่อหา routes ที่ทั้ง InputMint และ OutputMint สามารถ swap ได้. เรียนรู้เกี่ยวเพิ่มเติมได้ ที่นี่open in new window

Press </> button to view full source
import { TOKEN_LIST_URL } from "@jup-ag/core";

const JupiterApp = () => {
  const [tokens, setTokens] = useState<Token[]>([]);
  const [inputMint] = useState<PublicKey>(
    new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
  );
  const [outputMint] = useState<PublicKey>(
    new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")
  );

  useEffect(() => {

    fetch(TOKEN_LIST_URL[ENV])
      .then((response) => response.json())
      .then((result) => setTokens(result));
  }, []);

  const jupiter = useJupiter({
    amount: 1 * 10 ** 6,
    inputMint,
    outputMint,
    slippage: 1, 
    debounceTime: 250, 
  });

  const {
    allTokenMints, 
    routeMap, 
    exchange,
    refresh,
    lastRefreshTimestamp, 
    loading,
    routes,
    error,
  } = jupiter;

  return (
    <>
      <div style={{ fontWeight: "600", fontSize: 16, marginTop: 24 }}>
        Hook example
      </div>
      <div>Number of tokens: {tokens.length}</div>
      <div>Number of input tokens {allTokenMints.length}</div>
      <div>Possible number of routes: {routes?.length}</div>
      <div>Best quote: {routes ? routes[0].outAmount : ""}</div>
    </>
  );
};

export default JupiterApp;

ทำการ Swap

หลังจากให้ข้อมูลกับ useJupiter Hook หมดแล้ว เราจะสามารถใช้ jupiter instance เพื่อทำการ swap ได้โดยใช้ method exchange

Press </> button to view full source
import { TOKEN_LIST_URL } from "@jup-ag/core";

const JupiterApp = () => {
  const [tokens, setTokens] = useState<Token[]>([]);
  const [inputMint] = useState<PublicKey>(
    new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
  );
  const [outputMint] = useState<PublicKey>(
    new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")
  );

  useEffect(() => {

    fetch(TOKEN_LIST_URL[ENV])
      .then((response) => response.json())
      .then((result) => setTokens(result));
  }, []);

  const jupiter = useJupiter({
    amount: 1 * 10 ** 6, 
    inputMint,
    outputMint,
    slippage: 1, 
    debounceTime: 250, 
  });

  const {
    allTokenMints,
    routeMap,
    exchange, 
    refresh,
    lastRefreshTimestamp,
    loading, 
    routes, 
    error,
  } = jupiter;

  const onClickSwapBestRoute = async () => {

    const bestRoute = routes[0];

    await exchange({
      wallet: {
        sendTransaction: wallet.sendTransaction,
        publicKey: wallet.publicKey,
        signAllTransactions: wallet.signAllTransactions,
        signTransaction: wallet.signTransaction,
      },
      route: bestRoute,
      confirmationWaiterFactory: async (txid) => {
        console.log("sending transaction");
        await connection.confirmTransaction(txid);
        console.log("confirmed transaction");

        return await connection.getTransaction(txid, {
          commitment: "confirmed",
        });
      },
    });

    console.log({ swapResult });

    if ("error" in swapResult) {
      console.log("Error:", swapResult.error);
    } else if ("txid" in swapResult) {
      console.log("Sucess:", swapResult.txid);
      console.log("Input:", swapResult.inputAmount);
      console.log("Output:", swapResult.outputAmount);
    }
  };

  return (
    <>
      <div style={{ fontWeight: "600", fontSize: 16, marginTop: 24 }}>
        Hook example
      </div>
      <div>Number of tokens: {tokens.length}</div>
      <div>Number of input tokens {allTokenMints.length}</div>
      <div>Possible number of routes: {routes?.length}</div>
      <div>Best quote: {routes ? routes[0].outAmount : ""}</div>
      <button type="button" onClick={onClickSwapBestRoute}>
        Swap best route
      </button>
    </>
  );
};

export default JupiterApp;

วิธี use Jupiter API

นี่คือวิธีที่ง่ายที่สุดที่จะใช้งาน jupiter programs เพื่อ swap คู่ tokens ใดๆ

การติดตั้ง

yarn i @solana/web3.js
yarn i cross-fetch
yarn i @project-serum/anchor
yarn i bs58
npm i @solana/web3.js
npm i cross-fetch
npm i @project-serum/anchor
npm i bs58

หา Route Map

API จะหา tokens ที่สามารถ swap ได้โดยใช้ the jupiter API. รานการของ token routes ที่เป็นไปได้จะเริ่ม fetch ตรงจุดนี้ และ allInputMints จะมีรายการของ Input Tokens mint address ที่เป็นไปได้ และ swappableOutputForSol จะมีรายการของทุก tokens ที่สามารถ swapp เป็น SOL ได้ในกรณีนี้

Press </> button to view full source
const routeMap = await(
  await fetch("https://quote-api.jup.ag/v1/route-map")
).json();


const allInputMints = Object.keys(routeMap);


const swappableOutputForSol =
  routeMap["So11111111111111111111111111111111111111112"];

วิธีทำ Serialized Transaction เพื่อเอาไป Swap

POST API request จะไปตาม route ที่เราต้องการจะไป รวมถึง wallet address ของ user ที่กำหนดไว้ นอกจากนี้จะมี params ตัวเลือกอื่นๆ อีกเช่น wrapUnwrapSOL และ feeAccount เราสามารถเรียนรู้เพิ่มเติมได้ที่ offical docs ตรงนี้ linkopen in new window

Press </> button to view full source
(async() => {
  const transactions = await(
     fetch("https://quote-api.jup.ag/v1/swap", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
     
        route: routes[0],
  
        userPublicKey: wallet.publicKey.toString(),
      
        wrapUnwrapSOL: true,
  
        feeAccount: "xxxx",
      }),
    })
  ).json();
  
  const { setupTransaction, swapTransaction, cleanupTransaction } = transactions;
})()

ทำการ Swap Transaction

Transaction จะถูกสร้างและ sign ด้วย user.

Press </> button to view full source

(async() => {
  for (let serializedTransaction of [
    setupTransaction,
    swapTransaction,
    cleanupTransaction,
  ].filter(Boolean)) {
   
    const transaction = Transaction.from(
      Buffer.from(serializedTransaction, "base64")
    );
  
    const txid = await connection.sendTransaction(transaction, [wallet.payer], {
      skipPreflight: false,
    });
    await connection.confirmTransaction(txid);
  
  }  
})()

แหล่งข้อมูลอื่น

Last Updated:
Contributors: Todsaporn Banjerdkit