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 ใดๆ
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();
Loading the Jupiter instance
Jupiter instance กำลังสร้างด้วย configurations ที่ให้มา เรามีตัวเลือก parameters มากมายที่จะส่งไปให้ instanceได้ ลองอ่านเพิ่มเติมได้ ที่นี่
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 จะบอกเราว่า tokens สามารถ swap ด้วย input token ที่ให้มาได้หรือเปล่า ซึ่งจะมีแต่ token mint addresses และไม่มี metadata.
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();
หาเส้นทางสำหรับ Input และ Output token ที่ให้มา
methods computeRoutes
รับ input/output Mint address และคืนค่า routes ที่เป็นไปได้ทั้งหมดโดยเรียงจากราคาที่ดีที่สุดมาก่อน
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,
});
Execute the Token Swap
method exchange
จะถูกเรียกตรงนี้เพื่อสร้าง transaction สำหรับ route ที่ได้มา
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();
วิธีใช้ 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 ก็ได้เหมือนกันถ้าต้องการ
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>
);
};
ดึงข้อมูลรายการ Tokens
ดึงข้อมูลทุก tokens ที่สามารถ swap ด้วย jupiter สำหรับ network ใดๆ แล้วเก็บไว้ใน state
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 ที่เพิ่มเข้าไปเพื่อจะสามารถ swapกันเอง หรือดึงมาจาก user อื่นด้วยก็ได้
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")
);
การใช้ react hook useJupiter
useJupiter Hook จะรับ parameters เพื่อหา routes ที่ทั้ง InputMint และ OutputMint สามารถ swap ได้. เรียนรู้เกี่ยวเพิ่มเติมได้ ที่นี่
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;
ทำการ Swap
หลังจากให้ข้อมูลกับ useJupiter Hook หมดแล้ว เราจะสามารถใช้ jupiter instance เพื่อทำการ swap ได้โดยใช้ method exchange
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",
});
},
});
})()
วิธี 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 ได้ในกรณีนี้
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();
วิธีทำ Serialized Transaction เพื่อเอาไป Swap
POST API request จะไปตาม route ที่เราต้องการจะไป รวมถึง wallet address ของ user ที่กำหนดไว้ นอกจากนี้จะมี params ตัวเลือกอื่นๆ อีกเช่น wrapUnwrapSOL และ feeAccount เราสามารถเรียนรู้เพิ่มเติมได้ที่ offical docs ตรงนี้ link
(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",
}),
});
ทำการ Swap Transaction
Transaction จะถูกสร้างและ sign ด้วย user.
(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,
});