Swap on Raydium DEX
Introduction
This tutorial will walk you through the steps of performing a swap on The Raydium DEX where the input swap token is wSOL and the output swap token is USDC.
This example code is based on the script RaydiumSwap.js. This script is designed to perform a token swap on the Raydium decentralized exchange (DEX) on the Solana blockchain. It uses the Raydium SDK to interact with the blockchain.
Raydium doesn't support Solana's Devnet environment to test, so we will be using Solana's Mainnet environment for the following tutorial.
How the script works
Let us breakdown the main components of the script RaydiumSwap.js
to get into more depth of the process of swapping tokens on Raydium DEX.
Initial Checks and Imports
const { ethers } = require('hardhat');
const web3 = require('@solana/web3.js');
const { getAssociatedTokenAddress, getAccount } = require('@solana/spl-token');
const { Liquidity } = require('@raydium-io/raydium-sdk');
const { config } = require('./config');
This code block imports necessary libraries like ethers
for interacting with Ethereum-based contracts, @solana/web3.js
for Solana interactions, and @raydium-io/raydium-sdk
for Raydium DEX functionality.
Swap configuration
const swapConfig = {
tokenAAmount: 0.0001, // Amount of WSOL to swap
TokenA: 'So11111111111111111111111111111111111111112', // WSOL address
TokenB: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC address
direction: 'in', // Direction of swap
liquidityFile: 'https://api.raydium.io/v2/sdk/liquidity/mainnet.json',
slippage: 5 // Slippage tolerance in percentage
};
This code block Defines the parameters for the swap, including the amount to swap, token addresses, and the liquidity pool configuration.
Setup and Initialization
const TestCallSolanaFactory = await ethers.getContractFactory('TestCallSolana');
let TestCallSolanaAddress = config.CALL_SOLANA_SAMPLE_CONTRACT_MAINNET;
let TestCallSolana;
if (ethers.isAddress(TestCallSolanaAddress)) {
TestCallSolana = TestCallSolanaFactory.attach(TestCallSolanaAddress);
} else {
TestCallSolana = await ethers.deployContract('TestCallSolana');
await TestCallSolana.waitForDeployment();
TestCallSolanaAddress = TestCallSolana.target;
console.log(`TestCallSolana deployed to ${TestCallSolana.target}`);
}
This block of code creates or attaches to the already deployed TestCallSolana
smart contract.
Retrieving Contract Public Key
const contractPublicKeyInBytes = await TestCallSolana.getNeonAddress(TestCallSolanaAddress);
const contractPublicKey = ethers.encodeBase58(contractPublicKeyInBytes);
console.log(contractPublicKey, 'contractPublicKey');
This code block retrieves the public key for the TestCallSolana
contract.
Finding Pool Information
const poolKeys = await config.raydiumHelper.findPoolInfoForTokens(
swapConfig.liquidityFile,
swapConfig.TokenA,
swapConfig.TokenB
);
if (!poolKeys) {
console.error('Pool info not found');
return 'Pool info not found';
} else {
console.log('Found pool info');
}
This code block fetches the necessary pool information for the tokens involved in the swap from Raydium's API.
Token Account Checks
const ataContractTokenA = await getAssociatedTokenAddress(
new web3.PublicKey(swapConfig.TokenA),
new web3.PublicKey(contractPublicKey),
true
);
const ataContractTokenAInfo = await connection.getAccountInfo(ataContractTokenA);
const ataContractTokenB = await getAssociatedTokenAddress(
new web3.PublicKey(swapConfig.TokenB),
new web3.PublicKey(contractPublicKey),
true
);
const ataContractTokenBInfo = await connection.getAccountInfo(ataContractTokenB);
if (!ataContractTokenAInfo || !ataContractTokenBInfo) {
if (!ataContractTokenAInfo) console.log('Account does not have initialized ATA for TokenA.');
if (!ataContractTokenBInfo) console.log('Account does not have initialized ATA for TokenB.');
return;
} else if (
Number((await getAccount(connection, ataContractTokenA)).amount) <
swapConfig.tokenAAmount * 10 ** amountIn.currency.decimals
) {
console.log('Not enough TokenA amount to proceed with the swap.');
return;
}
This block of code checks if the contract’s account has Associated Token Accounts (ATAs) for both WSOL and USDC, which are necessary for the swap. It also verifies if the account holds enough WSOL for the swap.
Creating and Executing the Swap Instruction
const ins = await Liquidity.makeSwapInstruction({
poolKeys: poolKeys,
userKeys: {
tokenAccountIn: ataContractTokenA,
tokenAccountOut: ataContractTokenB,
owner: new web3.PublicKey(contractPublicKey)
},
amountIn: amountIn.raw,
amountOut: minAmountOut.raw,
fixedSide: 'in'
});
console.log("Executing executeComposabilityMethod with Raydium's swap instruction ...");
solanaTx = new web3.Transaction();
solanaTx.add(ins.innerTransaction.instructions[0]);
[tx, receipt] = await config.utils.executeComposabilityMethod(
solanaTx.instructions[0],
0,
TestCallSolana,
undefined,
user1
);
console.log(tx, 'tx');
console.log(receipt.logs[0].args, 'receipt args');
This block of code creates a swap instruction using Raydium's Liquidity
module. The instruction is then added to a Solana transaction, which is then executed via a composability method.
How to run the script
To test the example script RaydiumSwap.js
, run this command in the terminal -
npx hardhat run scripts/TestCallSolana/RaydiumSwap.js --network neonmainnet
After running the above command successfully, you should get the output similar to this -
EPE8HRAM9d3N5xHeQnYzESVgKNSvbyP5JEfMAgXUHsS3 contractPublicKey
Found pool info
Executing executeComposabilityMethod with Raydium's swap instruction ...
ContractTransactionResponse {
provider: HardhatEthersProvider {
_hardhatProvider: LazyInitializationProviderAdapter {
_providerFactory: [AsyncFunction (anonymous)],
_emitter: [EventEmitter],
_initializingPromise: [Promise],
provider: [BackwardsCompatibilityProviderAdapter]
},
_networkName: 'neonmainnet',
_blockListeners: [],
_transactionHashListeners: Map(0) {},
_eventListeners: [],
_isHardhatNetworkCached: false,
_transactionHashPollingTimeout: undefined
},
blockNumber: null,
blockHash: null,
index: undefined,
hash: '0xd166fcd39bc6cd93a75d14d39694fbfe33641ba134c15966dc0b851641d28c86',
type: 0,
to: '0xEf7b3ed123d2c51c780F8684B0DD7c0b4bd89190',
from: '0x9CE2A03A7a258fB96d04Afb8Dd84b69A748B5959',
nonce: 340,
gasLimit: 300000n,
gasPrice: 598145095750n,
maxPriorityFeePerGas: null,
maxFeePerGas: null,
maxFeePerBlobGas: null,
data: '0x121f31514bd949c43602c33f207790ed16a3524ca1b9975cf121a2a90cffec7df8b68acd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001206ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d6e472e67a46ea6b4bd0bab9dfd35e2b4c72f1d6d59c2eab95c942573ad22f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014157b0580f31c5fce44a62582dbcf9d78ee75943a084a393b350368d2289930800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f92f390ff9609e8ad437bb8e4c1f1aa43ac05d24308cca77de8512c5509292d300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001abe43c7c1e21eaa6f97c8bd355e21bd1279674756c1c8e106c6e712ba116d97000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b870e12dd379891561d2e9fa8f26431834eb736f2f24fc2a2a4dff1fd5dca4df00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f2cbb9b760eddb185706303063ad33d7b57296ea02d4e0335e31ceafa4cc42dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010d0751a8282da61305fe299c37b998e58471db1135037310f8be1045a60af6ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ac4c3cefa9f19bf54c8dc0f5e4d1ceee5327d26482b29d2b13cbaa43447218d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000146527949e0a7a659f8aadc86bc53cc7c42469a17765a9bad62b1b05bc868b5ee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c9beb9b16d18a8273976ef89b7fde84aec9baaca0db173db8fda4ae0de478a34000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016b103231c975050cec8da6de40357c9bca60ef9e8f33165a255665652a82533b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a84bb6466246781d7a9adab8588ba86b2acce51358c844f5444e40640875fd5a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014c9d997d2ec43bdc0d236269cfb0d08391afd103fd8fbd63453ff58b6e23a92000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001aa5a31cb020bb002985ca929908171940fe635cb76e99ce4fb8805428bb8254a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000095b12c2855d4bce649b9fd852608e747f546f049d78a2beff1600295f3f3c772000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014f3c9bd3d0fd35e6dfb74137337d2f72e81721438d527b147e9a32949a59172a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c6d98c89611168a9961be2aa24bedbac9fd195d6337013a8a9cd74e3993040e400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001109a0860100000000008336000000000000000000000000000000000000000000',
value: 0n,
chainId: 245022934n,
signature: Signature { r: "0x210b95035a1228647cd21e686ff577b0861da3a900f90e7b349bd005dc9b2ea6", s: "0x0c0609fdfb647e8db94beb9f07a38b73ba7fcdd2e5ca040c9951abf61724622f", yParity: 0, networkV: 490045903 },
accessList: null,
blobVersionedHashes: null
} tx
Result(3) [
'0x',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x'
] receipt args
The full code of the script can be viewed here RaydiumSwap.js.