Raydium is the first decentralized automated market maker (AMM) on the Solana blockchain, which is an excellent layer-one blockchain, much faster and cheaper than Ethereum.
Isn’t it cool if we can build something like the swap feature in Raydium?
Final output: https://abbylow.github.io/raydium-test/
Source code is available here: https://github.com/abbylow/raydium-test
Step 1: Install Solana Wallet Adapter and Build a Frontend App
There are many wallets available on Solana blockchain. To handle the wallet connection, it is recommended to use Solana wallet adapter. You may follow the guideline to install the dependencies.
In the same directory, you can build a simple frontend app in the same directory. There are some starter code provided by Solana wallet adapter. Alternatively, you may build your own React App without Create-React-App following this guide.
Setup the wallet connection in the App.tsx.
// You can add or remove the wallet options hereconst wallets = useMemo(
() => [
new PhantomWalletAdapter(),
new GlowWalletAdapter(),
new SlopeWalletAdapter(),
new SolflareWalletAdapter({ network }),
new TorusWalletAdapter(),
],
[network]
);
You might notice that in my App.tsx, I use mainnet as the network which means we gonna use the real money to test the feature. However, there is no Raydium smart contract found in devnet or testnet.
If we want to make this swap feature works in devnet or testnet, we will have to deploy and initiate the liquidity pool in the network by ourselves. Luckily, Solana charges very little for each transaction so it is still affordable to test it on mainnet.
Some tips that might be useful:
- If you meet any issue to install
@solana/web3.js
, check the Node version that you are using. - After successfully install the packages, you might face some issues when running the app. Read the error messages and try to polyfill them.
In webpack.config.js, you can add the fallback as follow:
resolve: { fallback: { "crypto": require.resolve("crypto-browserify"), "stream": require.resolve("stream-browserify"), "os": require.resolve("os-browserify/browser"), }}
Step 2: Build a UI with the library of your choice.
In my project, I reused the UI from my previous ETH Swap project which utilises Bootstrap v5 and built in React. The main UI code is in this file.
Feel free to modify the look and feel of the swap project as you like! The most important thing is you have fun when building this.
Step 3: Get the token balances
It is different to get the balance of SOL and the other tokens (eg: RAY).
- To get the balance of SOL:
import { useConnection, useWallet } from '@solana/wallet-adapter-react';import { LAMPORTS_PER_SOL } from '@solana/web3.js';
const { publicKey } = useWallet();const { connection } = useConnection();const [solBalance, setSolBalance] = useState<number>(0);// ...if (publicKey !== null) {
const balance = await connection.getBalance(publicKey);
setSolBalance(balance / LAMPORTS_PER_SOL);
}
2. To get the balance of RAY token (or any other tokens), we have to use getTokenAccountsByOwner to retrieve all the token accounts of this user. Then we can find the RAY token account by RAY token address (You can find the token address in Solscan).
With RAY token account, we can getTokenAccountBalance to get the balance of the token.
import { useConnection, useWallet } from '@solana/wallet-adapter-react';import { PublicKey } from '@solana/web3.js';import { getTokenAccountsByOwner } from '../utils';
const { publicKey } = useWallet();const { connection } = useConnection();const RAY_TOKEN_MINT = '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R';const [rayBalance, setRayBalance] = useState<number>(0);// ...if (publicKey !== null) { // get all token accounts const tokenAccs = await getTokenAccountsByOwner(connection, publicKey as PublicKey); let rayTokenAddress: PublicKey; tokenAccs.filter(acc => acc.accountInfo.mint.toBase58() === RAY_TOKEN_MINT).map(async (acc) => { rayTokenAddress = acc.pubkey; const accBalance = await connection.getTokenAccountBalance(rayTokenAddress); const rayBal = accBalance.value.uiAmount || 0; setRayBalance(rayBal); });}
Let’s take a tea break 🍵 here and resume it in Part 2.