Jupiter
JupiterはSolanaの主要な流動性アグリゲーターであり、幅広いトークンとあらゆるトークンペア間の最適なルート発見を提供します。
Installation
@jup-ag/core は、jupiterオンチェーンプログラムと対話し、2つの可能なトークンペア間のスワップを実行するために使用されるコアパッケージです。
yarn add @jup-ag/core
npm install @jup-ag/core
Jupiterからのトークンリストの取得
特定のネットワークでJupiterとSwapできるすべての可能なトークンが取得されます。
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();
})();
const ENV = "mainnet-beta";
const tokens: Token[] = await(await fetch(TOKEN_LIST_URL[ENV])).json();
Jupiterインスタンスの読込
Jupiter インスタンスは、提供された設定で作成されます。インスタンスが受け取るオプションのパラメータは多数あり、詳しくはこちらをご覧ください。
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 jupiter = await Jupiter.load({
  connection,
  cluster: ENV,
  user: USER_KEYPAIR,
});
RouteMapの取得
RouteMapは、特定の入力トークンに対してどのトークンを交換できるかを識別します。ルートマップにはトークンミントアドレスのみが含まれ、メタデータは含まれません。
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 routeMap = jupiter.getRouteMap();
指定された入力および出力トークンのルートを取得する
computeRoutesメソッドは、入力Mintアドレスと出力Mintアドレスを受け取り、すべての可能なルートを最安値の順に提供します。
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 routes = await jupiter.computeRoutes({
  inputMint: new PublicKey(inputToken), 
  outputMint: new PublicKey(outputToken), 
  inputAmount, 
  slippage, 
  forceFetch: false, 
});
トークンスワップを実行する
ここでexchangeメソッドが呼び出され、特定のルートのトランザクションが構築されます。
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(); 
  
})();
bestRoute = routes.routesInfos[0];
const { execute } = await jupiter.exchange({
  bestRoute,
});
const swapResult = await execute();
React アプリケーションで Jupiter を使用する方法
インストール
yarn add @jup-ag/react-hook
npm install @jup-ag/react-hook
プロバイダーの追加
React アプリ全体で useJupiter フックを使用するために、ここで JupiterProvider をセットアップしています。クラスター パラメーターは、さまざまなトークンを取得するためにmainnet-beta として設定されていますが、必要に応じてdevnetに変更することもできます
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;
const JupiterApp = ({ children }) => {
  const { connection } = useConnection();
  const wallet = useWallet();
  return (
    <JupiterProvider
      cluster="mainnet-beta"
      connection={connection}
      userPublicKey={wallet.publicKey || undefined}
    >
      {children}
    </JupiterProvider>
  );
};
トークンのリストを取得する
特定のネットワークでスワップできるすべての可能なトークンが取得され、状態に格納されます。
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;
const [tokens, setTokens] = useState<Token[]>([]);
useEffect(() => {
  fetch(TOKEN_LIST_URL[ENV])
    .then((response) => response.json())
    .then((result) => setTokens(result));
}, []);
Stateの設定
InputMintとOutputMintは、互いに交換したり、ユーザーから取得したりできるようにするために追加されるStateです。
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;
const [inputMint] = useState<PublicKey>(
  new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
);
const [outputMint] = useState<PublicKey>(
  new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")
);
useJupiter react hookの仕様
useJupiterフックは、必要なすべてのパラメーターを取得して、InputMint と OutputMintの両方のトークンを交換できるルートを見つけます。詳細についてはこちら
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;
const jupiter = useJupiter({
  amount: 1 * 10 ** 6, 
  inputMint,
  outputMint,
  slippage: 1, 
  debounceTime: 250, 
});
const {
  allTokenMints, 
  routeMap,
  exchange,
  refresh, 
  lastRefreshTimestamp, 
  loading, 
  routes, 
  error,
} = jupiter;
スワップの実行
すべてのデータを useJupiter フックに提供した後exchangeメソッドを使用して、jupiterインスタンスを使用してスワップを実行できます。
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;
(async() => {
  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",
      });
    },
  });
})()
Jupiter APIの使用方法
これは、 jupiter programとやり取りして、提供された2つのトークンを交換する最も簡単な方法です。
インストール
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
ルートマップの取得
この API は、jupiter API を使用してスワップできるすべての利用可能なトークンを取得します。すべての可能なトークン ルートのリストがここで取得されます。allInputMints には、ミント アドレスごとのすべての可能な入力トークンのリストが含まれ、swappableOutputForSol には、この場合にSOLにスワップできるすべての可能なトークンが含まれます。
const routeMap = await(
  await fetch("https://quote-api.jup.ag/v1/route-map")
).json();
const allInputMints = Object.keys(routeMap);
const swappableOutputForSol =
  routeMap["So11111111111111111111111111111111111111112"];
const routeMap = await(
  await fetch("https://quote-api.jup.ag/v1/route-map")
).json();
シリアル化されたトランザクションを取得してスワップを実行する
POST APIリクエストには、利用したいルートとユーザーのウォレットアドレスを指定します。このAPIには、wrapUnwrapSOLやfeeAccountなどのオプションパラメータを追加することができます。 feeAccountについて詳しくは、こちらの公式ドキュメントをご覧ください。
(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;
})()
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",
  }),
});
スワップトランザクションの実行
Transactionオブジェクトが作成され、ユーザーによって署名されます。
(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);
  
  }  
})()
const transaction = Transaction.from(
  Buffer.from(serializedTransaction, "base64")
);
const txid = await connection.sendTransaction(transaction, [wallet.payer], {
  skipPreflight: false,
});