Skip to content

Commit 99b3412

Browse files
committed
Initial commit
0 parents  commit 99b3412

File tree

11 files changed

+1256
-0
lines changed

11 files changed

+1256
-0
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Soos3D
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Raydium SDK Swap Example
2+
3+
This project demonstrates how to perform a token swap on the Solana blockchain using Raydium's protocol. The example specifically illustrates swapping SOL (native Solana token) for USDC (a stablecoin).
4+
5+
## Features
6+
7+
- Utilizes the Raydium SDK for interacting with the Solana blockchain.
8+
- Supports both versioned and legacy transactions.
9+
- Allows simulation of swap transactions before execution.
10+
- Easy configuration for swap parameters through a dedicated config file.
11+
12+
## Prerequisites
13+
14+
Before you begin, ensure you have met the following requirements:
15+
16+
- Node.js installed (v14 or above recommended)
17+
- A Solana wallet with some SOL for testing the swap
18+
- An environment file (.env) with your RPC URL and WALLET_PRIVATE_KEY
19+
20+
## Environment variables
21+
22+
Add your RPC endoint and private key to a `.env` file:
23+
24+
```env
25+
RPC_URL=YOUR_RPC_URL
26+
WALLET_PRIVATE_KEY=YOUR_PRIVATE_KEY
27+
```
28+
29+
## Installation
30+
31+
Clone the repository locally and install the dependencies:
32+
33+
```bash
34+
git clone https://github.com/your-username/raydium-sdk-swap-example.git
35+
cd raydium-sdk-swap-example
36+
yarn
37+
```
38+
39+
## Usage
40+
41+
Edit the configuration in `src/swapConfig.ts` editing:
42+
43+
- Select if you want to send the transaction or only simulate
44+
- The amount to swap
45+
- The tokens to swap
46+
- The liquidity file to pull the pool info from
47+
48+
Then run:
49+
50+
```sh
51+
yarn swap
52+
```

mainnet.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "raydium-swap-example",
3+
"version": "1.0.0",
4+
"description": "An example to swap tokens on Solana using the Raydium SDK.",
5+
"main": "index.js",
6+
"scripts": {
7+
"swap": "ts-node ./src/index.ts"
8+
},
9+
"author": "DZ",
10+
"license": "MIT",
11+
"dependencies": {
12+
"@project-serum/anchor": "^0.26.0",
13+
"@raydium-io/raydium-sdk": "^1.3.1-beta.46",
14+
"@solana/spl-token": "^0.3.11",
15+
"bs58": "^5.0.0",
16+
"dotenv": "^16.4.5",
17+
"ts-node": "^10.9.2",
18+
"typescript": "^5.3.3"
19+
},
20+
"devDependencies": {
21+
"@types/node": "^18.11.13"
22+
}
23+
}

replit.nix

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{ pkgs }: {
2+
deps = [
3+
pkgs.yarn
4+
pkgs.esbuild
5+
pkgs.nodejs-18_x
6+
7+
pkgs.nodePackages.typescript
8+
pkgs.nodePackages.typescript-language-server
9+
];
10+
}

src/RaydiumSwap.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { Connection, PublicKey, Keypair, Transaction, VersionedTransaction, TransactionMessage } from '@solana/web3.js'
2+
import {
3+
Liquidity,
4+
LiquidityPoolKeys,
5+
jsonInfo2PoolKeys,
6+
LiquidityPoolJsonInfo,
7+
TokenAccount,
8+
Token,
9+
TokenAmount,
10+
TOKEN_PROGRAM_ID,
11+
Percent,
12+
SPL_ACCOUNT_LAYOUT,
13+
} from '@raydium-io/raydium-sdk'
14+
import { Wallet } from '@project-serum/anchor'
15+
import bs58 from 'bs58'
16+
import 'dotenv/config';
17+
import { swapConfig } from './swapConfig'; // Import the configuration
18+
19+
20+
class RaydiumSwap {
21+
allPoolKeysJson: LiquidityPoolJsonInfo[]
22+
connection: Connection
23+
wallet: Wallet
24+
25+
constructor(RPC_URL: string, WALLET_PRIVATE_KEY: string) {
26+
this.connection = new Connection(process.env.RPC_URL
27+
, { commitment: 'confirmed' })
28+
this.wallet = new Wallet(Keypair.fromSecretKey(Uint8Array.from(bs58.decode(process.env.WALLET_PRIVATE_KEY))))//base58.decode(WALLET_PRIVATE_KEY)))
29+
}
30+
31+
async loadPoolKeys() {
32+
const liquidityJsonResp = await fetch(swapConfig.liquidityFile); //https://pastebin.com/raw/CHPMmnDh
33+
if (!liquidityJsonResp.ok) return []
34+
const liquidityJson = (await liquidityJsonResp.json()) as { official: any; unOfficial: any }
35+
const allPoolKeysJson = [...(liquidityJson?.official ?? []), ...(liquidityJson?.unOfficial ?? [])]
36+
37+
this.allPoolKeysJson = allPoolKeysJson
38+
}
39+
40+
findPoolInfoForTokens(mintA: string, mintB: string) {
41+
const poolData = this.allPoolKeysJson.find(
42+
(i) => (i.baseMint === mintA && i.quoteMint === mintB) || (i.baseMint === mintB && i.quoteMint === mintA)
43+
)
44+
45+
if (!poolData) return null
46+
47+
return jsonInfo2PoolKeys(poolData) as LiquidityPoolKeys
48+
}
49+
50+
async getOwnerTokenAccounts() {
51+
const walletTokenAccount = await this.connection.getTokenAccountsByOwner(this.wallet.publicKey, {
52+
programId: TOKEN_PROGRAM_ID,
53+
})
54+
55+
return walletTokenAccount.value.map((i) => ({
56+
pubkey: i.pubkey,
57+
programId: i.account.owner,
58+
accountInfo: SPL_ACCOUNT_LAYOUT.decode(i.account.data),
59+
}))
60+
}
61+
62+
async getSwapTransaction(
63+
toToken: string,
64+
// fromToken: string,
65+
amount: number,
66+
poolKeys: LiquidityPoolKeys,
67+
maxLamports: number = 100000,
68+
useVersionedTransaction = true,
69+
fixedSide: 'in' | 'out' = 'in'
70+
): Promise<Transaction | VersionedTransaction> {
71+
const directionIn = poolKeys.quoteMint.toString() == toToken
72+
const { minAmountOut, amountIn } = await this.calcAmountOut(poolKeys, amount, directionIn)
73+
console.log({ minAmountOut, amountIn });
74+
const userTokenAccounts = await this.getOwnerTokenAccounts()
75+
const swapTransaction = await Liquidity.makeSwapInstructionSimple({
76+
connection: this.connection,
77+
makeTxVersion: useVersionedTransaction ? 0 : 1,
78+
poolKeys: {
79+
...poolKeys,
80+
},
81+
userKeys: {
82+
tokenAccounts: userTokenAccounts,
83+
owner: this.wallet.publicKey,
84+
},
85+
amountIn: amountIn,
86+
amountOut: minAmountOut,
87+
fixedSide: fixedSide,
88+
config: {
89+
bypassAssociatedCheck: false,
90+
},
91+
computeBudgetConfig: {
92+
microLamports: maxLamports,
93+
},
94+
})
95+
96+
const recentBlockhashForSwap = await this.connection.getLatestBlockhash()
97+
const instructions = swapTransaction.innerTransactions[0].instructions.filter(Boolean)
98+
99+
if (useVersionedTransaction) {
100+
const versionedTransaction = new VersionedTransaction(
101+
new TransactionMessage({
102+
payerKey: this.wallet.publicKey,
103+
recentBlockhash: recentBlockhashForSwap.blockhash,
104+
instructions: instructions,
105+
}).compileToV0Message()
106+
)
107+
108+
versionedTransaction.sign([this.wallet.payer])
109+
110+
return versionedTransaction
111+
}
112+
113+
const legacyTransaction = new Transaction({
114+
blockhash: recentBlockhashForSwap.blockhash,
115+
lastValidBlockHeight: recentBlockhashForSwap.lastValidBlockHeight,
116+
feePayer: this.wallet.publicKey,
117+
})
118+
119+
legacyTransaction.add(...instructions)
120+
121+
return legacyTransaction
122+
}
123+
124+
async sendLegacyTransaction(tx: Transaction) {
125+
const txid = await this.connection.sendTransaction(tx, [this.wallet.payer], {
126+
skipPreflight: true,
127+
maxRetries: 2,
128+
})
129+
130+
return txid
131+
}
132+
133+
async sendVersionedTransaction(tx: VersionedTransaction) {
134+
const txid = await this.connection.sendTransaction(tx, {
135+
skipPreflight: true,
136+
maxRetries: 2,
137+
})
138+
139+
return txid
140+
}
141+
142+
async simulateLegacyTransaction(tx: Transaction) {
143+
const txid = await this.connection.simulateTransaction(tx, [this.wallet.payer])
144+
145+
return txid
146+
}
147+
148+
async simulateVersionedTransaction(tx: VersionedTransaction) {
149+
const txid = await this.connection.simulateTransaction(tx)
150+
151+
return txid
152+
}
153+
154+
getTokenAccountByOwnerAndMint(mint: PublicKey) {
155+
return {
156+
programId: TOKEN_PROGRAM_ID,
157+
pubkey: PublicKey.default,
158+
accountInfo: {
159+
mint: mint,
160+
amount: 0,
161+
},
162+
} as unknown as TokenAccount
163+
}
164+
165+
async calcAmountOut(poolKeys: LiquidityPoolKeys, rawAmountIn: number, swapInDirection: boolean) {
166+
const poolInfo = await Liquidity.fetchInfo({ connection: this.connection, poolKeys })
167+
168+
let currencyInMint = poolKeys.baseMint
169+
let currencyInDecimals = poolInfo.baseDecimals
170+
let currencyOutMint = poolKeys.quoteMint
171+
let currencyOutDecimals = poolInfo.quoteDecimals
172+
173+
if (!swapInDirection) {
174+
currencyInMint = poolKeys.quoteMint
175+
currencyInDecimals = poolInfo.quoteDecimals
176+
currencyOutMint = poolKeys.baseMint
177+
currencyOutDecimals = poolInfo.baseDecimals
178+
}
179+
180+
const currencyIn = new Token(TOKEN_PROGRAM_ID, currencyInMint, currencyInDecimals)
181+
const amountIn = new TokenAmount(currencyIn, rawAmountIn, false)
182+
const currencyOut = new Token(TOKEN_PROGRAM_ID, currencyOutMint, currencyOutDecimals)
183+
const slippage = new Percent(5, 100) // 5% slippage
184+
185+
const { amountOut, minAmountOut, currentPrice, executionPrice, priceImpact, fee } = Liquidity.computeAmountOut({
186+
poolKeys,
187+
poolInfo,
188+
amountIn,
189+
currencyOut,
190+
slippage,
191+
})
192+
193+
return {
194+
amountIn,
195+
amountOut,
196+
minAmountOut,
197+
currentPrice,
198+
executionPrice,
199+
priceImpact,
200+
fee,
201+
}
202+
}
203+
}
204+
205+
export default RaydiumSwap

src/index.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import RaydiumSwap from './RaydiumSwap';
2+
import { Transaction, VersionedTransaction } from '@solana/web3.js';
3+
import { swapConfig } from './swapConfig'; // Import the configuration
4+
5+
/**
6+
* Performs a token swap on the Raydium protocol.
7+
* Depending on the configuration, it can execute the swap or simulate it.
8+
*/
9+
const swap = async () => {
10+
/**
11+
* The RaydiumSwap instance for handling swaps.
12+
*/
13+
const raydiumSwap = new RaydiumSwap(process.env.RPC_URL, process.env.WALLET_PRIVATE_KEY);
14+
console.log(`Raydium swap initialized`);
15+
16+
/**
17+
* Load pool keys from the Raydium API to enable finding pool information.
18+
*/
19+
await raydiumSwap.loadPoolKeys();
20+
console.log(`Loaded pool keys`);
21+
22+
/**
23+
* Find pool information for the given token pair.
24+
*/
25+
const poolInfo = raydiumSwap.findPoolInfoForTokens(swapConfig.tokenAAddress, swapConfig.tokenBAddress);
26+
console.log('Found pool info');
27+
28+
/**
29+
* Prepare the swap transaction with the given parameters.
30+
*/
31+
const tx = await raydiumSwap.getSwapTransaction(
32+
swapConfig.tokenBAddress,
33+
swapConfig.tokenAAmount,
34+
poolInfo,
35+
swapConfig.maxLamports, // Max amount of lamports
36+
swapConfig.useVersionedTransaction,
37+
swapConfig.direction
38+
);
39+
40+
/**
41+
* Depending on the configuration, execute or simulate the swap.
42+
*/
43+
if (swapConfig.executeSwap) {
44+
/**
45+
* Send the transaction to the network and log the transaction ID.
46+
*/
47+
const txid = swapConfig.useVersionedTransaction
48+
? await raydiumSwap.sendVersionedTransaction(tx as VersionedTransaction)
49+
: await raydiumSwap.sendLegacyTransaction(tx as Transaction);
50+
51+
console.log(`https://solscan.io/tx/${txid}`);
52+
} else {
53+
/**
54+
* Simulate the transaction and log the result.
55+
*/
56+
const simRes = swapConfig.useVersionedTransaction
57+
? await raydiumSwap.simulateVersionedTransaction(tx as VersionedTransaction)
58+
: await raydiumSwap.simulateLegacyTransaction(tx as Transaction);
59+
60+
console.log(simRes);
61+
}
62+
};
63+
64+
swap();

src/swapConfig.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const swapConfig = {
2+
executeSwap: true, // Send tx when true, simulate tx when false
3+
useVersionedTransaction: true,
4+
tokenAAmount: 0.01, // Swap 0.01 SOL for USDT in this example
5+
tokenAAddress: 'So11111111111111111111111111111111111111112', // Token to swap for the other
6+
tokenBAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
7+
LPpoolAddress: '8HoQnePLqPj4M7PUDzfw8e3Ymdwgc7NLGnaTUapubyvu',
8+
maxLamports: 1000000, // Max lamports allowed for fees
9+
direction: 'in' as 'in' | 'out', // Swap direction: 'in' or 'out'
10+
liquidityFile: 'https://api.raydium.io/v2/sdk/liquidity/mainnet.json'
11+
};
12+

0 commit comments

Comments
 (0)