Skip to content

Commit

Permalink
adds initial raydium support (#22)
Browse files Browse the repository at this point in the history
* adds initial raydium support

* fix lint
  • Loading branch information
0xNe0x1 authored Dec 25, 2024
1 parent baf4c2d commit 6c13403
Show file tree
Hide file tree
Showing 27 changed files with 4,682 additions and 408 deletions.
6 changes: 3 additions & 3 deletions dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/5.7.1/ethers.umd.min.js" type="application/javascript"></script>
<script crossorigin src="https://unpkg.com/decimal.js@10"></script>
<script crossorigin src="https://unpkg.com/@depay/solana-web3.js@1"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@depay/solana-web3.js@1/dist/umd/index.js"></script>
<script crossorigin src="https://unpkg.com/@depay/web3-blockchains@9"></script>
<script crossorigin src="https://unpkg.com/@depay/web3-client@10"></script>
<script crossorigin src="https://unpkg.com/@depay/web3-tokens@10"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@depay/web3-client@10/dist/umd/index.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@depay/web3-tokens@10/dist/umd/index.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@depay/walletconnect-v2@2"></script>
<script crossorigin src="https://unpkg.com/@depay/web3-wallets@17"></script>
<script src="tmp/index.dev.js"></script>
Expand Down
5 changes: 4 additions & 1 deletion dist/esm/index.evm.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,039 changes: 925 additions & 114 deletions dist/esm/index.js

Large diffs are not rendered by default.

967 changes: 889 additions & 78 deletions dist/esm/index.solana.js

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion dist/umd/index.evm.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,037 changes: 924 additions & 113 deletions dist/umd/index.js

Large diffs are not rendered by default.

965 changes: 888 additions & 77 deletions dist/umd/index.solana.js

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions examples/raydium.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## AMM v4 (Legacy)

### 1 Pool

```javascript
let route = await Web3Exchanges.raydium.route({
blockchain: 'solana',
tokenIn: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
tokenOut: 'HhJpBhRRn4g56VsyLuT8DL5Bv31HkXqsrahTTUCZeZg4',
amountOutMin: 0.01
})

let wallets = await Web3Wallets.getWallets()
let wallet = wallets[1]
let account = await wallet.account()
let transaction = await route.getTransaction({ account })

wallet.sendTransaction(transaction)
```

2 changes: 1 addition & 1 deletion package.evm.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@depay/web3-exchanges-evm",
"moduleName": "Web3Exchanges",
"version": "13.13.1",
"version": "13.15.0",
"description": "JavaScript library simplifying decentralized web3 exchange routing for multiple blockchains and exchanges.",
"main": "dist/umd/index.evm.js",
"module": "dist/esm/index.evm.js",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@depay/web3-exchanges",
"moduleName": "Web3Exchanges",
"version": "13.13.1",
"version": "13.15.0",
"description": "JavaScript library simplifying decentralized web3 exchange routing for multiple blockchains and exchanges.",
"main": "dist/umd/index.js",
"module": "dist/esm/index.js",
Expand Down
2 changes: 1 addition & 1 deletion package.solana.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@depay/web3-exchanges-solana",
"moduleName": "Web3Exchanges",
"version": "13.13.1",
"version": "13.15.0",
"description": "JavaScript library simplifying decentralized web3 exchange routing for multiple blockchains and exchanges.",
"main": "dist/umd/index.solana.js",
"module": "dist/esm/index.solana.js",
Expand Down
5 changes: 4 additions & 1 deletion src/classes/Exchange.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ const route = ({
let amounts // includes intermediary amounts for longer routes
try {
;({ amountIn, amountInMax, amountOut, amountOutMin, amounts } = await getAmounts({ exchange, blockchain, path, pools, tokenIn, tokenOut, amountIn, amountInMax, amountOut, amountOutMin }));
} catch { return resolve() }
} catch(e) {
console.log(e);
return resolve()
}
if([amountIn, amountInMax, amountOut, amountOutMin].every((amount)=>{ return amount == undefined })) { return resolve() }

if(exchange.slippage && slippage !== false) {
Expand Down
6 changes: 6 additions & 0 deletions src/exchanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,27 @@ exchanges.worldchain.forEach((exchange)=>{ exchanges.worldchain[exchange.name] =
/*#elif _SOLANA
import orca from './exchanges/orca'
import raydium from './exchanges/raydium'
const exchanges = [
orca(),
raydium(),
]
exchanges.forEach((exchange)=>{
exchanges[exchange.name] = exchange
})
exchanges.solana = [
orca('solana'),
raydium('solana'),
]
exchanges.solana.forEach((exchange)=>{ exchanges.solana[exchange.name] = exchange })
//#else */

import honeyswap from './exchanges/honeyswap'
import orca from './exchanges/orca'
import raydium from './exchanges/raydium'
import pancakeswap from './exchanges/pancakeswap'
import pancakeswap_v3 from './exchanges/pancakeswap_v3'
import quickswap from './exchanges/quickswap'
Expand All @@ -147,6 +151,7 @@ import wxdai from './exchanges/wxdai'

const exchanges = [
orca(),
raydium(),
uniswap_v3(),
pancakeswap_v3(),
uniswap_v2(),
Expand Down Expand Up @@ -193,6 +198,7 @@ exchanges.polygon.forEach((exchange)=>{ exchanges.polygon[exchange.name] = excha

exchanges.solana = [
orca('solana'),
raydium('solana'),
]
exchanges.solana.forEach((exchange)=>{ exchanges.solana[exchange.name] = exchange })

Expand Down
48 changes: 48 additions & 0 deletions src/exchanges/raydium.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Exchange from '../classes/Exchange'
import Raydium from '../platforms/solana/raydium'

const exchange = {

name: 'raydium',
label: 'Raydium',
logo: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjMzIiB2aWV3Qm94PSIwIDAgMjkgMzMiIHdpZHRoPSIyOSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGxpbmVhckdyYWRpZW50IGlkPSJhIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjI4LjMxNjgiIHgyPSItMS43MzMzNiIgeTE9IjguMTkxNjIiIHkyPSIyMC4yMDg2Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNjMjAwZmIiLz48c3RvcCBvZmZzZXQ9Ii40ODk2NTgiIHN0b3AtY29sb3I9IiMzNzcyZmYiLz48c3RvcCBvZmZzZXQ9Ii40ODk3NTgiIHN0b3AtY29sb3I9IiMzNzczZmUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM1YWM0YmUiLz48L2xpbmVhckdyYWRpZW50PjxnIGZpbGw9InVybCgjYSkiPjxwYXRoIGQ9Im0yNi44NjI1IDEyLjI4MXYxMS40MTA0bC0xMi42OTE2IDcuMzI2MS0xMi42OTg1OS03LjMyNjF2LTE0LjY1OTM3bDEyLjY5ODU5LTcuMzMzMjIgOS43NTQxIDUuNjM0NDEgMS40NzIzLS44NDk0MS0xMS4yMjY0LTYuNDgzODEtMTQuMTcwOSA4LjE4MjYydjE2LjM1ODE4bDE0LjE3MDkgOC4xODI2IDE0LjE3MS04LjE4MjZ2LTEzLjEwOTJ6Ii8+PHBhdGggZD0ibTEwLjYxNzYgMjMuNjk4NWgtMi4xMjM1M3YtNy4xMjA5aDcuMDc4NDNjLjY2OTctLjAwNzQgMS4zMDk1LS4yNzgyIDEuNzgxMS0uNzUzOC40NzE2LS40NzU1LjczNy0xLjExNzYuNzM4OC0xLjc4NzQuMDAzOC0uMzMxMS0uMDYwMS0uNjU5Ni0uMTg3OS0uOTY1MS0uMTI3OS0uMzA1Ni0uMzE2OC0uNTgxNy0uNTU1NC0uODExNS0uMjMwOC0uMjM3Mi0uNTA3MS0uNDI1My0uODEyNC0uNTUzLS4zMDUzLS4xMjc4LS42MzMzLS4xOTI1LS45NjQyLS4xOTAzaC03LjA3ODQzdi0yLjE2NTk1aDcuMDg1NDNjMS4yNDA1LjAwNzQzIDIuNDI4MS41MDM1MSAzLjMwNTMgMS4zODA2NS44NzcxLjg3NzIgMS4zNzMyIDIuMDY0OCAxLjM4MDYgMy4zMDUyLjAwNzYuOTQ5Ni0uMjgxOSAxLjg3NzctLjgyODEgMi42NTQ0LS41MDI3Ljc0MzItMS4yMTExIDEuMzIzNy0yLjAzODYgMS42NzA1LS44MTk0LjI1OTktMS42NzQ1LjM4ODktMi41MzQxLjM4MjNoLTQuMjQ3eiIvPjxwYXRoIGQ9Im0yMC4yMTU5IDIzLjUyMTVoLTIuNDc3NWwtMS45MTExLTMuMzMzOWMuNzU2MS0uMDQ2MyAxLjUwMTktLjE5ODggMi4yMTU1LS40NTN6Ii8+PHBhdGggZD0ibTI1LjM4MzEgOS45MDk3NSAxLjQ2NTIuODE0MDUgMS40NjUzLS44MTQwNXYtMS43MjAwNWwtMS40NjUzLS44NDk0MS0xLjQ2NTIuODQ5NDF6Ii8+PC9nPjwvc3ZnPg==',
protocol: 'raydium',

slippage: true,

blockchains: ['solana'],

solana: {

router_amm: {
address: '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8',
api: Raydium.AMM_LAYOUT,
},

router_cpamm: {
address: 'CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C',
api: Raydium.CPAMM_LAYOUT
},

router_clmm: {
address: 'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK',
api: Raydium.CLMM_LAYOUT
},
}
}

export default (scope)=>{

return new Exchange(

Object.assign(exchange, {
scope,

findPath: (args)=>Raydium.findPath({ ...args, exchange }),
pathExists: (args)=>Raydium.pathExists({ ...args, exchange }),
getAmounts: (args)=>Raydium.getAmounts({ ...args, exchange }),
getPrep: (args)=>{},
getTransaction: (args)=>Raydium.getTransaction({ ...args, exchange }),
})
)
}
12 changes: 0 additions & 12 deletions src/platforms/solana/orca.js

This file was deleted.

16 changes: 12 additions & 4 deletions src/platforms/solana/orca/amounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ let getAmountsOut = async ({ path, amountIn, amountInMax }) => {

let amounts = [ethers.BigNumber.from(amountIn || amountInMax)]

amounts.push(ethers.BigNumber.from((await getBestPair({ tokenIn: path[0], tokenOut: path[1], amountIn, amountInMax })).price))
let bestPair = await getBestPair({ tokenIn: path[0], tokenOut: path[1], amountIn, amountInMax })
if(!bestPair){ return }
amounts.push(ethers.BigNumber.from(bestPair.price))

if (path.length === 3) {
amounts.push(ethers.BigNumber.from((await getBestPair({ tokenIn: path[1], tokenOut: path[2], amountIn: amountIn ? amounts[1] : undefined, amountInMax: amountInMax ? amounts[1] : undefined })).price))
let bestPair = await getBestPair({ tokenIn: path[1], tokenOut: path[2], amountIn: amountIn ? amounts[1] : undefined, amountInMax: amountInMax ? amounts[1] : undefined })
if(!bestPair){ return }
amounts.push(ethers.BigNumber.from(bestPair.price))
}

if(amounts.length != path.length) { return }
Expand All @@ -22,10 +26,14 @@ let getAmountsIn = async({ path, amountOut, amountOutMin }) => {
path = path.slice().reverse()
let amounts = [ethers.BigNumber.from(amountOut || amountOutMin)]

amounts.push(ethers.BigNumber.from((await getBestPair({ tokenIn: path[1], tokenOut: path[0], amountOut, amountOutMin })).price))
let bestPair = await getBestPair({ tokenIn: path[1], tokenOut: path[0], amountOut, amountOutMin })
if(!bestPair){ return }
amounts.push(ethers.BigNumber.from(bestPair.price))

if (path.length === 3) {
amounts.push(ethers.BigNumber.from((await getBestPair({ tokenIn: path[2], tokenOut: path[1], amountOut: amountOut ? amounts[1] : undefined, amountOutMin: amountOutMin ? amounts[1] : undefined })).price))
let bestPair = await getBestPair({ tokenIn: path[2], tokenOut: path[1], amountOut: amountOut ? amounts[1] : undefined, amountOutMin: amountOutMin ? amounts[1] : undefined })
if(!bestPair){ return }
amounts.push(ethers.BigNumber.from(bestPair.price))
}

if(amounts.length != path.length) { return }
Expand Down
12 changes: 12 additions & 0 deletions src/platforms/solana/orca/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { findPath, pathExists } from './path'
import { getAmounts } from './amounts'
import { getTransaction } from './transaction'
import { WHIRLPOOL_LAYOUT } from './layouts'

export default {
findPath,
pathExists,
getAmounts,
getTransaction,
WHIRLPOOL_LAYOUT,
}
1 change: 0 additions & 1 deletion src/platforms/solana/orca/pairs.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ let getBestPair = async({ tokenIn, tokenOut, amountIn, amountInMax, amountOut, a
} else { // amount out
bestPair = getLowestPrice(pairs)
}

return bestPair
}

Expand Down
136 changes: 136 additions & 0 deletions src/platforms/solana/raydium/amm/pairs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*#if _EVM
/*#elif _SOLANA
import { request } from '@depay/web3-client-solana'
import { BN } from '@depay/solana-web3.js'
//#else */

import { request } from '@depay/web3-client'
import { BN } from '@depay/solana-web3.js'

//#endif

import Blockchains from '@depay/web3-blockchains'
import { AMM_LAYOUT } from '../layouts'

export const LIQUIDITY_FEES_NUMERATOR = new BN(25)
export const LIQUIDITY_FEES_DENOMINATOR = new BN(10000)

const BNDivCeil = (bn1, bn2)=> {
const { div, mod } = bn1.divmod(bn2)

if (mod.gt(new BN(0))) {
return div.add(new BN(1))
} else {
return div
}
}

const getPairs = (base, quote)=>{
return request(`solana://675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8/getProgramAccounts`, {
params: { filters: [
{ dataSize: AMM_LAYOUT.span },
{ memcmp: { offset: 0, bytes: [6,0,0] }}, // filters for status 6 (Swap)
{ memcmp: { offset: 400, bytes: base }},
{ memcmp: { offset: 432, bytes: quote }},
]},
api: AMM_LAYOUT,
cache: 86400, // 24h,
cacheKey: ['raydium', base.toString(), quote.toString()].join('-')
})
}

const getPairsWithPrice = async({ tokenIn, tokenOut, amountIn, amountInMax, amountOut, amountOutMin })=>{

let accounts = await getPairs(tokenIn, tokenOut)

if(accounts.length == 0) {
accounts = await getPairs(tokenOut, tokenIn)
}

const pairs = await Promise.all(accounts.map(async(account)=>{

const baseMint = account.data.baseMint.toString()
const quoteMint = account.data.quoteMint.toString()

const balances = await Promise.all([
request(`solana://${account.data.baseVault.toString()}/getTokenAccountBalance`, { cache: 5 }),
request(`solana://${account.data.quoteVault.toString()}/getTokenAccountBalance`, { cache: 5 })
])
const baseReserve = (balances[0]?.value?.amount ? new BN(balances[0]?.value?.amount) : new BN(0)).sub(account.data.baseNeedTakePnl)
const quoteReserve = (balances[1]?.value?.amount ? new BN(balances[1]?.value?.amount) : new BN(0)).sub(account.data.quoteNeedTakePnl)

if(baseMint === Blockchains.solana.wrapped.address) {
if(baseReserve.lt(new BN(50000000))) {
return // to little liquidity
}
} else if (quoteMint === Blockchains.solana.wrapped.address) {
if(quoteReserve.lt(new BN(50000000))) {
return // to little liquidity
}
} else if (Blockchains.solana.stables.usd.includes(baseMint)) {
if((parseFloat(baseReserve.toString()) / 10 ** account.data.baseDecimal.toNumber()) < 10000) {
return // to little liquidity
}
} else if (Blockchains.solana.stables.usd.includes(quoteMint)) {
if((parseFloat(quoteReserve.toString()) / 10 ** account.data.quoteDecimal.toNumber()) < 10000) {
return // to little liquidity
}
}

const reserves = [baseReserve, quoteReserve]

if(tokenOut === baseMint) {
reserves.reverse()
}

const [reserveIn, reserveOut] = reserves

let price
if(amountOut || amountOutMin) { // compute amountIn

const amountInRaw = new BN(0)
let amountOutRaw = new BN((amountOut || amountOutMin).toString())

if (amountOutRaw.gt(reserveOut)) {
amountOutRaw = reserveOut.sub(new BN(1))
}

const denominator = reserveOut.sub(amountOutRaw)
const amountInWithoutFee = reserveIn.mul(amountOutRaw).div(denominator)

price = amountInWithoutFee
.mul(LIQUIDITY_FEES_DENOMINATOR)
.div(LIQUIDITY_FEES_DENOMINATOR.sub(LIQUIDITY_FEES_NUMERATOR))
.toString()

} else { // compute amountOut

const amountOutRaw = new BN(0)
const amountInRaw = new BN((amountIn || amountInMin).toString())
const feeRaw = BNDivCeil(amountInRaw.mul(LIQUIDITY_FEES_NUMERATOR), LIQUIDITY_FEES_DENOMINATOR)

const amountInWithFee = amountInRaw.sub(feeRaw)
const denominator = reserveIn.add(amountInWithFee)

price = reserveOut.mul(amountInWithFee).div(denominator).toString()
}

return {
publicKey: account.pubkey.toString(),
baseMint,
quoteMint,
price,
data: account.data
}

}))

return pairs.filter(Boolean)
}

export {
getPairsWithPrice
}
Loading

0 comments on commit 6c13403

Please sign in to comment.