@shogun-sdk/intents-sdk 1.2.5 → 1.2.6-test
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -19
- package/dist/esm/chains.js +3 -0
- package/dist/esm/chains.js.map +1 -1
- package/dist/esm/constants.js +9 -0
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/core/evm/chain-provider.js +2 -1
- package/dist/esm/core/evm/chain-provider.js.map +1 -1
- package/dist/esm/core/evm/cross-chain-limit-order.js +1 -1
- package/dist/esm/core/evm/cross-chain-limit-order.js.map +1 -1
- package/dist/esm/core/evm/permit2-signature-transfer.js +15 -0
- package/dist/esm/core/evm/permit2-signature-transfer.js.map +1 -0
- package/dist/esm/core/evm/single-chain-dca-order.js +1 -1
- package/dist/esm/core/evm/single-chain-dca-order.js.map +1 -1
- package/dist/esm/core/evm/single-chain-limit-order.js +1 -1
- package/dist/esm/core/evm/single-chain-limit-order.js.map +1 -1
- package/dist/esm/core/solana/dca/single-chain-dca-order.js +3 -2
- package/dist/esm/core/solana/dca/single-chain-dca-order.js.map +1 -1
- package/dist/esm/core/solana/dca/single-chain-limit-order.js +3 -2
- package/dist/esm/core/solana/dca/single-chain-limit-order.js.map +1 -1
- package/dist/esm/core/solana/utils.js +14 -4
- package/dist/esm/core/solana/utils.js.map +1 -1
- package/dist/esm/core/sui/single-chain-dca-order.js +2 -1
- package/dist/esm/core/sui/single-chain-dca-order.js.map +1 -1
- package/dist/esm/core/sui/single-chain-limit-order.js +2 -1
- package/dist/esm/core/sui/single-chain-limit-order.js.map +1 -1
- package/dist/esm/index.js +6 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/token-list.js +2 -0
- package/dist/esm/types/token-list.js.map +1 -0
- package/dist/esm/utils/defillama.js +1 -0
- package/dist/esm/utils/defillama.js.map +1 -1
- package/dist/esm/utils/quote/aggregator.js +158 -42
- package/dist/esm/utils/quote/aggregator.js.map +1 -1
- package/dist/esm/utils/quote/paraswap.js +1 -0
- package/dist/esm/utils/quote/paraswap.js.map +1 -1
- package/dist/esm/utils/quote/pumpfun/estimations.js +169 -0
- package/dist/esm/utils/quote/pumpfun/estimations.js.map +1 -0
- package/dist/esm/utils/quote/pumpfun/estimations_amm.js +130 -0
- package/dist/esm/utils/quote/pumpfun/estimations_amm.js.map +1 -0
- package/dist/esm/utils/quote/pumpfun/index.js +198 -0
- package/dist/esm/utils/quote/pumpfun/index.js.map +1 -0
- package/dist/esm/utils/quote/pumpfun/models.js +70 -0
- package/dist/esm/utils/quote/pumpfun/models.js.map +1 -0
- package/dist/esm/utils/quote/pumpfun/utils.js +269 -0
- package/dist/esm/utils/quote/pumpfun/utils.js.map +1 -0
- package/dist/esm/utils/quote/raydium.js +336 -0
- package/dist/esm/utils/quote/raydium.js.map +1 -0
- package/dist/esm/utils/quote/stablecoins-tokens.js +1 -0
- package/dist/esm/utils/quote/stablecoins-tokens.js.map +1 -1
- package/dist/esm/utils/quote/utils.js +8 -0
- package/dist/esm/utils/quote/utils.js.map +1 -0
- package/dist/esm/utils/tokens/index.js +37 -0
- package/dist/esm/utils/tokens/index.js.map +1 -0
- package/dist/types/chains.d.ts +4 -2
- package/dist/types/chains.d.ts.map +1 -1
- package/dist/types/constants.d.ts +2 -1
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/core/evm/chain-provider.d.ts.map +1 -1
- package/dist/types/core/evm/permit2-signature-transfer.d.ts +3 -0
- package/dist/types/core/evm/permit2-signature-transfer.d.ts.map +1 -0
- package/dist/types/core/solana/dca/single-chain-dca-order.d.ts +1 -2
- package/dist/types/core/solana/dca/single-chain-dca-order.d.ts.map +1 -1
- package/dist/types/core/solana/dca/single-chain-limit-order.d.ts +1 -2
- package/dist/types/core/solana/dca/single-chain-limit-order.d.ts.map +1 -1
- package/dist/types/core/solana/utils.d.ts +2 -0
- package/dist/types/core/solana/utils.d.ts.map +1 -1
- package/dist/types/core/sui/single-chain-dca-order.d.ts.map +1 -1
- package/dist/types/core/sui/single-chain-limit-order.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types/token-list.d.ts +30 -0
- package/dist/types/types/token-list.d.ts.map +1 -0
- package/dist/types/utils/defillama.d.ts +1 -0
- package/dist/types/utils/defillama.d.ts.map +1 -1
- package/dist/types/utils/quote/aggregator.d.ts +42 -5
- package/dist/types/utils/quote/aggregator.d.ts.map +1 -1
- package/dist/types/utils/quote/paraswap.d.ts.map +1 -1
- package/dist/types/utils/quote/pumpfun/estimations.d.ts +30 -0
- package/dist/types/utils/quote/pumpfun/estimations.d.ts.map +1 -0
- package/dist/types/utils/quote/pumpfun/estimations_amm.d.ts +49 -0
- package/dist/types/utils/quote/pumpfun/estimations_amm.d.ts.map +1 -0
- package/dist/types/utils/quote/pumpfun/index.d.ts +10 -0
- package/dist/types/utils/quote/pumpfun/index.d.ts.map +1 -0
- package/dist/types/utils/quote/pumpfun/models.d.ts +94 -0
- package/dist/types/utils/quote/pumpfun/models.d.ts.map +1 -0
- package/dist/types/utils/quote/pumpfun/utils.d.ts +50 -0
- package/dist/types/utils/quote/pumpfun/utils.d.ts.map +1 -0
- package/dist/types/utils/quote/raydium.d.ts +98 -0
- package/dist/types/utils/quote/raydium.d.ts.map +1 -0
- package/dist/types/utils/quote/stablecoins-tokens.d.ts.map +1 -1
- package/dist/types/utils/quote/utils.d.ts +5 -0
- package/dist/types/utils/quote/utils.d.ts.map +1 -0
- package/dist/types/utils/tokens/index.d.ts +20 -0
- package/dist/types/utils/tokens/index.d.ts.map +1 -0
- package/package.json +20 -18
- package/src/chains.ts +3 -0
- package/src/constants.ts +12 -2
- package/src/core/evm/chain-provider.ts +2 -1
- package/src/core/evm/cross-chain-limit-order.ts +1 -1
- package/src/core/evm/permit2-signature-transfer.ts +15 -0
- package/src/core/evm/single-chain-dca-order.ts +1 -1
- package/src/core/evm/single-chain-limit-order.ts +1 -1
- package/src/core/solana/dca/single-chain-dca-order.ts +3 -3
- package/src/core/solana/dca/single-chain-limit-order.ts +3 -2
- package/src/core/solana/utils.ts +24 -12
- package/src/core/sui/single-chain-dca-order.ts +2 -2
- package/src/core/sui/single-chain-limit-order.ts +2 -2
- package/src/index.ts +14 -4
- package/src/types/token-list.ts +35 -0
- package/src/utils/defillama.ts +1 -0
- package/src/utils/quote/aggregator.ts +210 -55
- package/src/utils/quote/paraswap.ts +1 -0
- package/src/utils/quote/pumpfun/estimations.ts +206 -0
- package/src/utils/quote/pumpfun/estimations_amm.ts +165 -0
- package/src/utils/quote/pumpfun/index.ts +266 -0
- package/src/utils/quote/pumpfun/models.ts +163 -0
- package/src/utils/quote/pumpfun/utils.ts +343 -0
- package/src/utils/quote/raydium.ts +506 -0
- package/src/utils/quote/stablecoins-tokens.ts +1 -0
- package/src/utils/quote/utils.ts +8 -0
- package/src/utils/tokens/index.ts +38 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { NATIVE_SOLANA_TOKEN_ADDRESS, WRAPPED_SOL_MINT_ADDRESS } from '../../constants.js';
|
|
2
|
+
import {
|
|
3
|
+
API_URLS,
|
|
4
|
+
getPdaLaunchpadPoolId,
|
|
5
|
+
Raydium,
|
|
6
|
+
LAUNCHPAD_PROGRAM,
|
|
7
|
+
PlatformConfig,
|
|
8
|
+
Curve,
|
|
9
|
+
type LaunchpadConfigInfo,
|
|
10
|
+
type LaunchpadPoolInfo,
|
|
11
|
+
} from '@raydium-io/raydium-sdk-v2';
|
|
12
|
+
import { type SimpleQuote } from './aggregator.js';
|
|
13
|
+
import { Connection, clusterApiUrl, PublicKey } from '@solana/web3.js'; // Used this package for raydium-sdk-v2 compatibility
|
|
14
|
+
import BN from 'bn.js';
|
|
15
|
+
import { Decimal } from 'decimal.js';
|
|
16
|
+
import { JupiterQuoteProvider, type JupiterQuoteParams } from './jupiter.js';
|
|
17
|
+
import { TWO_SECONDS_MS, TEN_MINUTES_MS } from './utils.js';
|
|
18
|
+
|
|
19
|
+
const launchpadPoolCache = new Map<
|
|
20
|
+
string,
|
|
21
|
+
{
|
|
22
|
+
data: [
|
|
23
|
+
LaunchpadPoolInfo & {
|
|
24
|
+
programId: PublicKey;
|
|
25
|
+
configInfo: LaunchpadConfigInfo;
|
|
26
|
+
},
|
|
27
|
+
PublicKey,
|
|
28
|
+
];
|
|
29
|
+
expiresAt: number;
|
|
30
|
+
}
|
|
31
|
+
>();
|
|
32
|
+
|
|
33
|
+
const launchpadPoolInfoCache = new Map<
|
|
34
|
+
string,
|
|
35
|
+
{
|
|
36
|
+
data: LaunchpadPoolInfo & {
|
|
37
|
+
programId: PublicKey;
|
|
38
|
+
configInfo: LaunchpadConfigInfo;
|
|
39
|
+
};
|
|
40
|
+
expiresAt: number;
|
|
41
|
+
}
|
|
42
|
+
>();
|
|
43
|
+
|
|
44
|
+
const platformConfigCache = new Map<
|
|
45
|
+
string,
|
|
46
|
+
{
|
|
47
|
+
data: ReturnType<typeof PlatformConfig.decode>;
|
|
48
|
+
expiresAt: number;
|
|
49
|
+
}
|
|
50
|
+
>();
|
|
51
|
+
|
|
52
|
+
const mintInfoCache = new Map<string, Awaited<ReturnType<Raydium['token']['getTokenInfo']>>>();
|
|
53
|
+
|
|
54
|
+
// Local implementation of compareAddresses function
|
|
55
|
+
const compareAddresses = (firstAddress?: string, secondAddress?: string): boolean => {
|
|
56
|
+
return !!firstAddress && !!secondAddress && firstAddress.toLowerCase() === secondAddress.toLowerCase();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const LAUNCHPAD_WHITELISTED_QUOTE_TOKENS = [
|
|
60
|
+
new PublicKey('So11111111111111111111111111111111111111112'), // WSOL
|
|
61
|
+
new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), // USDC
|
|
62
|
+
new PublicKey('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'), // USDT
|
|
63
|
+
new PublicKey('USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB'), // USD1
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
export type RaydiumPumpQuoteParams = {
|
|
67
|
+
inputMint: string;
|
|
68
|
+
outputMint: string;
|
|
69
|
+
amount: string;
|
|
70
|
+
slippageBps: number;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type RaydiumQuoteResponse = {
|
|
74
|
+
success: boolean;
|
|
75
|
+
data: {
|
|
76
|
+
inputMint: string;
|
|
77
|
+
outputMint: string;
|
|
78
|
+
inAmount: string;
|
|
79
|
+
outAmount: string;
|
|
80
|
+
otherAmountThreshold: string;
|
|
81
|
+
swapMode: string;
|
|
82
|
+
slippageBps: number;
|
|
83
|
+
platformFee: null;
|
|
84
|
+
priceImpactPct: string;
|
|
85
|
+
routePlan: Array<{
|
|
86
|
+
swapInfo: {
|
|
87
|
+
ammKey: string;
|
|
88
|
+
label: string;
|
|
89
|
+
inputMint: string;
|
|
90
|
+
outputMint: string;
|
|
91
|
+
notEnoughLiquidity: boolean;
|
|
92
|
+
minInAmount: string;
|
|
93
|
+
minOutAmount: string;
|
|
94
|
+
priceImpactPct: string;
|
|
95
|
+
lpFee: {
|
|
96
|
+
amount: string;
|
|
97
|
+
mint: string;
|
|
98
|
+
pct: number;
|
|
99
|
+
};
|
|
100
|
+
platformFee: {
|
|
101
|
+
amount: string;
|
|
102
|
+
mint: string;
|
|
103
|
+
pct: number;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
percent: number;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let raydium: Raydium | undefined;
|
|
112
|
+
export const initSdk = async (): Promise<Raydium> => {
|
|
113
|
+
if (raydium) return raydium;
|
|
114
|
+
raydium = await Raydium.load({
|
|
115
|
+
connection: new Connection(clusterApiUrl('mainnet-beta')),
|
|
116
|
+
disableFeatureCheck: true,
|
|
117
|
+
disableLoadToken: true,
|
|
118
|
+
});
|
|
119
|
+
return raydium;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Using API_URLS from Raydium SDK v2
|
|
123
|
+
|
|
124
|
+
export class RaydiumQuoteProvider {
|
|
125
|
+
/**
|
|
126
|
+
* Get priority fee recommendations from Raydium API
|
|
127
|
+
* @returns Priority fee data with different tiers (vh, h, m)
|
|
128
|
+
*/
|
|
129
|
+
public async getPriorityFee(): Promise<{
|
|
130
|
+
vh: number; // very high
|
|
131
|
+
h: number; // high
|
|
132
|
+
m: number; // medium
|
|
133
|
+
}> {
|
|
134
|
+
const url = new URL(API_URLS.PRIORITY_FEE, API_URLS.BASE_HOST);
|
|
135
|
+
const response = await fetch(url);
|
|
136
|
+
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(`Failed to fetch priority fee from Raydium API: ${response.status} ${response.statusText}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const data = await response.json();
|
|
142
|
+
|
|
143
|
+
if (!data.success) {
|
|
144
|
+
throw new Error('Raydium API returned unsuccessful response for priority fee');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return data.data.default;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public async getQuote(raydiumParams: RaydiumPumpQuoteParams): Promise<RaydiumQuoteResponse> {
|
|
151
|
+
const params = { ...raydiumParams };
|
|
152
|
+
|
|
153
|
+
// Handle native SOL token mapping for Solana
|
|
154
|
+
if (compareAddresses(params.inputMint, NATIVE_SOLANA_TOKEN_ADDRESS)) {
|
|
155
|
+
params.inputMint = WRAPPED_SOL_MINT_ADDRESS;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (compareAddresses(params.outputMint, NATIVE_SOLANA_TOKEN_ADDRESS)) {
|
|
159
|
+
params.outputMint = WRAPPED_SOL_MINT_ADDRESS;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const url = new URL('/compute/swap-base-in', API_URLS.SWAP_HOST);
|
|
163
|
+
url.searchParams.set('inputMint', params.inputMint);
|
|
164
|
+
url.searchParams.set('outputMint', params.outputMint);
|
|
165
|
+
url.searchParams.set('amount', params.amount);
|
|
166
|
+
url.searchParams.set('slippageBps', params.slippageBps.toString());
|
|
167
|
+
url.searchParams.set('txVersion', 'V0');
|
|
168
|
+
|
|
169
|
+
const response = await fetch(url);
|
|
170
|
+
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(`Failed to fetch quote from Raydium API: ${response.status} ${response.statusText}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const data: RaydiumQuoteResponse = await response.json();
|
|
176
|
+
|
|
177
|
+
if (!data.success) {
|
|
178
|
+
throw new Error('Raydium API returned unsuccessful response');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return data;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get transaction data for executing a swap
|
|
186
|
+
* @param params - Transaction parameters
|
|
187
|
+
* @returns Serialized transaction data
|
|
188
|
+
*/
|
|
189
|
+
public async getSwapTransaction(params: {
|
|
190
|
+
swapResponse: RaydiumQuoteResponse;
|
|
191
|
+
wallet: string;
|
|
192
|
+
txVersion: 'V0' | 'LEGACY';
|
|
193
|
+
wrapSol?: boolean;
|
|
194
|
+
unwrapSol?: boolean;
|
|
195
|
+
inputAccount?: string;
|
|
196
|
+
outputAccount?: string;
|
|
197
|
+
computeUnitPriceMicroLamports?: string;
|
|
198
|
+
}): Promise<{
|
|
199
|
+
id: string;
|
|
200
|
+
version: string;
|
|
201
|
+
success: boolean;
|
|
202
|
+
data: { transaction: string }[];
|
|
203
|
+
}> {
|
|
204
|
+
const url = new URL('/transaction/swap-base-in', API_URLS.SWAP_HOST);
|
|
205
|
+
const response = await fetch(url, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: {
|
|
208
|
+
'Content-Type': 'application/json',
|
|
209
|
+
},
|
|
210
|
+
body: JSON.stringify({
|
|
211
|
+
computeUnitPriceMicroLamports: params.computeUnitPriceMicroLamports || '0',
|
|
212
|
+
swapResponse: params.swapResponse,
|
|
213
|
+
txVersion: params.txVersion,
|
|
214
|
+
wallet: params.wallet,
|
|
215
|
+
wrapSol: params.wrapSol || false,
|
|
216
|
+
unwrapSol: params.unwrapSol || false,
|
|
217
|
+
inputAccount: params.inputAccount,
|
|
218
|
+
outputAccount: params.outputAccount,
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
throw new Error(`Failed to get swap transaction from Raydium API: ${response.status} ${response.statusText}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const data = await response.json();
|
|
227
|
+
|
|
228
|
+
if (!data.success) {
|
|
229
|
+
throw new Error('Raydium API returned unsuccessful response for swap transaction');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return data;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public async getQuotePrebonded(raydiumParams: RaydiumPumpQuoteParams): Promise<SimpleQuote> {
|
|
236
|
+
// First, get the Raydium quote token
|
|
237
|
+
let raydiumSdk = await initSdk();
|
|
238
|
+
let inputMintPk = new PublicKey(raydiumParams.inputMint);
|
|
239
|
+
let outputMintPk = new PublicKey(raydiumParams.outputMint);
|
|
240
|
+
|
|
241
|
+
// Check if either input or output mint is in the launchpad whitelisted tokens
|
|
242
|
+
let poolInfo, inputPoolInfo, outputPoolInfo, quoteToken, isInputPrebonded, inputQuoteToken, outputQuoteToken;
|
|
243
|
+
try {
|
|
244
|
+
[inputPoolInfo, inputQuoteToken] = await this.checkPreboundedTokensSwapLaunchpad(raydiumSdk, inputMintPk);
|
|
245
|
+
if (inputPoolInfo && inputQuoteToken) {
|
|
246
|
+
isInputPrebonded = true;
|
|
247
|
+
poolInfo = inputPoolInfo;
|
|
248
|
+
quoteToken = inputQuoteToken;
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {}
|
|
251
|
+
try {
|
|
252
|
+
[outputPoolInfo, outputQuoteToken] = await this.checkPreboundedTokensSwapLaunchpad(raydiumSdk, outputMintPk);
|
|
253
|
+
if (outputPoolInfo && outputQuoteToken) {
|
|
254
|
+
isInputPrebonded = false;
|
|
255
|
+
poolInfo = outputPoolInfo;
|
|
256
|
+
quoteToken = outputQuoteToken;
|
|
257
|
+
}
|
|
258
|
+
} catch (e) {}
|
|
259
|
+
|
|
260
|
+
if (inputPoolInfo && outputPoolInfo) {
|
|
261
|
+
// Both input and output are prebonded, which is not supported
|
|
262
|
+
throw new Error('Both input and output tokens are prebonded, which is not supported');
|
|
263
|
+
} else if (!quoteToken || !poolInfo) {
|
|
264
|
+
// Neither input nor output is prebonded, fallback to regular Raydium quote
|
|
265
|
+
throw new Error('Neither input nor output tokens are prebonded');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let quoteIsInput = quoteToken.equals(inputMintPk);
|
|
269
|
+
let quoteIsOutput = quoteToken.equals(outputMintPk);
|
|
270
|
+
|
|
271
|
+
if (isInputPrebonded) {
|
|
272
|
+
// We have to estimate prebonded first and then swap from quote token to output token (if needed)
|
|
273
|
+
let { expectedAmountOut, minimumAmountOut } = await this.estimatePrebondedToQuote(
|
|
274
|
+
raydiumSdk,
|
|
275
|
+
inputMintPk,
|
|
276
|
+
poolInfo,
|
|
277
|
+
'sell',
|
|
278
|
+
new BN(raydiumParams.amount),
|
|
279
|
+
new BN(raydiumParams.slippageBps), // Use full slippage for simplicity
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Then, if output token is not quote token, we have to swap from quote token to output token
|
|
283
|
+
if (!quoteIsOutput) {
|
|
284
|
+
let jupiterQuoteRequest: JupiterQuoteParams = {
|
|
285
|
+
amount: BigInt(expectedAmountOut.toString()),
|
|
286
|
+
tokenIn: quoteToken.toString(),
|
|
287
|
+
tokenOut: raydiumParams.outputMint,
|
|
288
|
+
swapMode: 'ExactIn',
|
|
289
|
+
slippageBps: raydiumParams.slippageBps, // Use full slippage for simplicity
|
|
290
|
+
};
|
|
291
|
+
const jupiterQuoter = new JupiterQuoteProvider();
|
|
292
|
+
const jupiterQuote = await jupiterQuoter.getQuote(jupiterQuoteRequest);
|
|
293
|
+
if (!jupiterQuote.quote) {
|
|
294
|
+
throw new Error('Jupiter quote failed');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
amountIn: BigInt(raydiumParams.amount),
|
|
299
|
+
amountOut: BigInt(jupiterQuote.quote.outAmount),
|
|
300
|
+
amountOutMin: BigInt(jupiterQuote.quote.otherAmountThreshold),
|
|
301
|
+
slippage: raydiumParams.slippageBps / 100,
|
|
302
|
+
};
|
|
303
|
+
} else {
|
|
304
|
+
return {
|
|
305
|
+
amountIn: BigInt(raydiumParams.amount),
|
|
306
|
+
amountOut: BigInt(expectedAmountOut.toString()),
|
|
307
|
+
amountOutMin: BigInt(minimumAmountOut.toString()),
|
|
308
|
+
slippage: raydiumParams.slippageBps / 100,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
// We have to swap from input token to quote token and then estimate prebonded
|
|
313
|
+
let amountOutFromInput = new BN(raydiumParams.amount);
|
|
314
|
+
if (!quoteIsInput) {
|
|
315
|
+
let jupiterQuoteRequest: JupiterQuoteParams = {
|
|
316
|
+
amount: BigInt(raydiumParams.amount),
|
|
317
|
+
tokenIn: raydiumParams.inputMint,
|
|
318
|
+
tokenOut: quoteToken.toString(),
|
|
319
|
+
swapMode: 'ExactIn',
|
|
320
|
+
slippageBps: raydiumParams.slippageBps, // Use full slippage for simplicity
|
|
321
|
+
};
|
|
322
|
+
const jupiterQuoter = new JupiterQuoteProvider();
|
|
323
|
+
const jupiterQuote = await jupiterQuoter.getQuote(jupiterQuoteRequest);
|
|
324
|
+
if (!jupiterQuote.quote) {
|
|
325
|
+
throw new Error('Jupiter quote failed');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Calculate minimum amount with slippage applied
|
|
329
|
+
amountOutFromInput = new BN(jupiterQuote.quote.outAmount);
|
|
330
|
+
}
|
|
331
|
+
// Now estimate prebonded
|
|
332
|
+
let { expectedAmountOut, minimumAmountOut } = await this.estimatePrebondedToQuote(
|
|
333
|
+
raydiumSdk,
|
|
334
|
+
outputMintPk,
|
|
335
|
+
poolInfo,
|
|
336
|
+
'buy',
|
|
337
|
+
amountOutFromInput,
|
|
338
|
+
new BN(raydiumParams.slippageBps),
|
|
339
|
+
);
|
|
340
|
+
return {
|
|
341
|
+
amountIn: BigInt(raydiumParams.amount),
|
|
342
|
+
amountOut: BigInt(expectedAmountOut.toString()),
|
|
343
|
+
amountOutMin: BigInt(minimumAmountOut.toString()),
|
|
344
|
+
slippage: raydiumParams.slippageBps / 100,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async checkPreboundedTokensSwapLaunchpad(
|
|
350
|
+
raydiumSdk: Raydium,
|
|
351
|
+
raydiumMint: PublicKey,
|
|
352
|
+
): Promise<
|
|
353
|
+
[
|
|
354
|
+
LaunchpadPoolInfo & {
|
|
355
|
+
programId: PublicKey;
|
|
356
|
+
configInfo: LaunchpadConfigInfo;
|
|
357
|
+
},
|
|
358
|
+
PublicKey,
|
|
359
|
+
]
|
|
360
|
+
> {
|
|
361
|
+
// Try cache first
|
|
362
|
+
const cacheKey = raydiumMint.toBase58();
|
|
363
|
+
const cached = launchpadPoolCache.get(cacheKey);
|
|
364
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
365
|
+
return cached.data;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Check if either input or output mint is in the launchpad whitelisted tokens
|
|
369
|
+
// Check for input mint
|
|
370
|
+
for (const quoteToken of LAUNCHPAD_WHITELISTED_QUOTE_TOKENS) {
|
|
371
|
+
try {
|
|
372
|
+
const poolId = getPdaLaunchpadPoolId(LAUNCHPAD_PROGRAM, raydiumMint, quoteToken).publicKey;
|
|
373
|
+
const poolCacheKey = poolId.toBase58();
|
|
374
|
+
const cachedPoolInfo = launchpadPoolInfoCache.get(poolCacheKey);
|
|
375
|
+
const poolInfo =
|
|
376
|
+
cachedPoolInfo && Date.now() < cachedPoolInfo.expiresAt
|
|
377
|
+
? cachedPoolInfo.data
|
|
378
|
+
: await raydiumSdk.launchpad.getRpcPoolInfo({ poolId });
|
|
379
|
+
|
|
380
|
+
launchpadPoolInfoCache.set(poolCacheKey, {
|
|
381
|
+
data: poolInfo,
|
|
382
|
+
expiresAt: Date.now() + TWO_SECONDS_MS,
|
|
383
|
+
});
|
|
384
|
+
// Cache positive result for a short period
|
|
385
|
+
launchpadPoolCache.set(cacheKey, {
|
|
386
|
+
data: [poolInfo, quoteToken],
|
|
387
|
+
expiresAt: Date.now() + TWO_SECONDS_MS,
|
|
388
|
+
});
|
|
389
|
+
return [poolInfo, quoteToken];
|
|
390
|
+
} catch (e) {
|
|
391
|
+
// Ignore and continue trying with next quote token
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
throw new Error('No launchpad pool found for the given token pair');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async estimatePrebondedToQuote(
|
|
398
|
+
raydiumSdk: Raydium,
|
|
399
|
+
raydiumMint: PublicKey,
|
|
400
|
+
poolInfo: LaunchpadPoolInfo & {
|
|
401
|
+
programId: PublicKey;
|
|
402
|
+
configInfo: LaunchpadConfigInfo;
|
|
403
|
+
},
|
|
404
|
+
trade_type: 'buy' | 'sell',
|
|
405
|
+
amount: BN,
|
|
406
|
+
slippage: BN,
|
|
407
|
+
): Promise<{ expectedAmountOut: BN; minimumAmountOut: BN }> {
|
|
408
|
+
const epochInfo = await raydiumSdk.connection.getEpochInfo();
|
|
409
|
+
const slot = await raydiumSdk.connection.getSlot();
|
|
410
|
+
const platformCacheKey = poolInfo.platformId.toBase58();
|
|
411
|
+
let platformInfoEntry = platformConfigCache.get(platformCacheKey);
|
|
412
|
+
if (!platformInfoEntry || Date.now() > platformInfoEntry.expiresAt) {
|
|
413
|
+
const accountInfo = await raydiumSdk.connection.getAccountInfo(poolInfo.platformId);
|
|
414
|
+
if (!accountInfo) {
|
|
415
|
+
throw new Error('Platform account not found');
|
|
416
|
+
}
|
|
417
|
+
platformInfoEntry = {
|
|
418
|
+
data: PlatformConfig.decode(accountInfo.data),
|
|
419
|
+
expiresAt: Date.now() + TEN_MINUTES_MS,
|
|
420
|
+
};
|
|
421
|
+
platformConfigCache.set(platformCacheKey, platformInfoEntry);
|
|
422
|
+
}
|
|
423
|
+
const platformInfo = platformInfoEntry.data;
|
|
424
|
+
|
|
425
|
+
const mintCacheKey = raydiumMint.toBase58();
|
|
426
|
+
let mintInfo = mintInfoCache.get(mintCacheKey);
|
|
427
|
+
if (!mintInfo) {
|
|
428
|
+
mintInfo = await raydiumSdk.token.getTokenInfo(raydiumMint);
|
|
429
|
+
mintInfoCache.set(mintCacheKey, mintInfo);
|
|
430
|
+
}
|
|
431
|
+
const shareFeeRate = new BN(0);
|
|
432
|
+
|
|
433
|
+
let expectedAmountOut;
|
|
434
|
+
let minimumAmountOut;
|
|
435
|
+
if (trade_type === 'buy') {
|
|
436
|
+
const res = Curve.buyExactIn({
|
|
437
|
+
poolInfo,
|
|
438
|
+
amountB: amount,
|
|
439
|
+
protocolFeeRate: poolInfo.configInfo.tradeFeeRate,
|
|
440
|
+
platformFeeRate: platformInfo.feeRate,
|
|
441
|
+
curveType: poolInfo.configInfo.curveType,
|
|
442
|
+
shareFeeRate,
|
|
443
|
+
creatorFeeRate: platformInfo.creatorFeeRate,
|
|
444
|
+
transferFeeConfigA: mintInfo.extensions.feeConfig
|
|
445
|
+
? {
|
|
446
|
+
transferFeeConfigAuthority: PublicKey.default,
|
|
447
|
+
withdrawWithheldAuthority: PublicKey.default,
|
|
448
|
+
withheldAmount: BigInt(0),
|
|
449
|
+
olderTransferFee: {
|
|
450
|
+
epoch: BigInt(mintInfo.extensions.feeConfig.olderTransferFee.epoch ?? epochInfo?.epoch ?? 0),
|
|
451
|
+
maximumFee: BigInt(mintInfo.extensions.feeConfig.olderTransferFee.maximumFee),
|
|
452
|
+
transferFeeBasisPoints: mintInfo.extensions.feeConfig.olderTransferFee.transferFeeBasisPoints,
|
|
453
|
+
},
|
|
454
|
+
newerTransferFee: {
|
|
455
|
+
epoch: BigInt(mintInfo.extensions.feeConfig.newerTransferFee.epoch ?? epochInfo?.epoch ?? 0),
|
|
456
|
+
maximumFee: BigInt(mintInfo.extensions.feeConfig.newerTransferFee.maximumFee),
|
|
457
|
+
transferFeeBasisPoints: mintInfo.extensions.feeConfig.newerTransferFee.transferFeeBasisPoints,
|
|
458
|
+
},
|
|
459
|
+
}
|
|
460
|
+
: undefined,
|
|
461
|
+
slot,
|
|
462
|
+
});
|
|
463
|
+
expectedAmountOut = new BN(res.amountA.amount.sub(res.amountA.fee ?? new BN(0)).toString());
|
|
464
|
+
minimumAmountOut = new BN(
|
|
465
|
+
new Decimal(res.amountA.amount.sub(res.amountA.fee ?? new BN(0)).toString())
|
|
466
|
+
.mul((10000 - slippage.toNumber()) / 10000)
|
|
467
|
+
.toFixed(0),
|
|
468
|
+
);
|
|
469
|
+
} else if (trade_type === 'sell') {
|
|
470
|
+
const res = Curve.sellExactIn({
|
|
471
|
+
poolInfo,
|
|
472
|
+
amountA: amount,
|
|
473
|
+
protocolFeeRate: poolInfo.configInfo.tradeFeeRate,
|
|
474
|
+
platformFeeRate: platformInfo.feeRate,
|
|
475
|
+
curveType: poolInfo.configInfo.curveType,
|
|
476
|
+
shareFeeRate,
|
|
477
|
+
creatorFeeRate: platformInfo.creatorFeeRate,
|
|
478
|
+
slot,
|
|
479
|
+
transferFeeConfigA: mintInfo.extensions.feeConfig
|
|
480
|
+
? {
|
|
481
|
+
transferFeeConfigAuthority: PublicKey.default,
|
|
482
|
+
withdrawWithheldAuthority: PublicKey.default,
|
|
483
|
+
withheldAmount: BigInt(0),
|
|
484
|
+
olderTransferFee: {
|
|
485
|
+
epoch: BigInt(mintInfo.extensions.feeConfig.olderTransferFee.epoch ?? epochInfo?.epoch ?? 0),
|
|
486
|
+
maximumFee: BigInt(mintInfo.extensions.feeConfig.olderTransferFee.maximumFee),
|
|
487
|
+
transferFeeBasisPoints: mintInfo.extensions.feeConfig.olderTransferFee.transferFeeBasisPoints,
|
|
488
|
+
},
|
|
489
|
+
newerTransferFee: {
|
|
490
|
+
epoch: BigInt(mintInfo.extensions.feeConfig.newerTransferFee.epoch ?? epochInfo?.epoch ?? 0),
|
|
491
|
+
maximumFee: BigInt(mintInfo.extensions.feeConfig.newerTransferFee.maximumFee),
|
|
492
|
+
transferFeeBasisPoints: mintInfo.extensions.feeConfig.newerTransferFee.transferFeeBasisPoints,
|
|
493
|
+
},
|
|
494
|
+
}
|
|
495
|
+
: undefined,
|
|
496
|
+
});
|
|
497
|
+
expectedAmountOut = new BN(res.amountB);
|
|
498
|
+
minimumAmountOut = new BN(
|
|
499
|
+
new Decimal(res.amountB.toString()).mul((10000 - slippage.toNumber()) / 10000).toFixed(0),
|
|
500
|
+
);
|
|
501
|
+
} else {
|
|
502
|
+
throw new Error('Invalid trade type. Must be "buy" or "sell".');
|
|
503
|
+
}
|
|
504
|
+
return { expectedAmountOut, minimumAmountOut };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
@@ -17,6 +17,7 @@ export const CROSS_CHAIN_TOKENS: Record<string, Record<ChainID, string>> = {
|
|
|
17
17
|
[ChainID.Hyperliquid]: '0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb', // USDT on Hyperliquid (No USDC on Hyperliquid)
|
|
18
18
|
[ChainID.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC', // USDC on Sui
|
|
19
19
|
[ChainID.Optimism]: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607', // USDC on Optimism
|
|
20
|
+
[ChainID.BSC]: '0x55d398326f99059ff775485246999027b3197955',
|
|
20
21
|
},
|
|
21
22
|
// Add more tokens as needed
|
|
22
23
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const TEN_MINUTES_MS = 10 * 60 * 1000;
|
|
2
|
+
export const TWO_SECONDS_MS = 2 * 1000;
|
|
3
|
+
export const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
4
|
+
|
|
5
|
+
export function applySlippage(amount: bigint, slippageBps = 0): bigint {
|
|
6
|
+
const multiplier = BigInt(10000 - slippageBps);
|
|
7
|
+
return (amount * multiplier) / 10000n;
|
|
8
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { TOKEN_SEARCH_API_BASE_URL } from '../../constants.js';
|
|
2
|
+
import type { TokenSearchParams, TokenSearchResponse } from '../../types/token-list.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* getTokenList
|
|
6
|
+
*
|
|
7
|
+
* High-level SDK function for token discovery.
|
|
8
|
+
* Supports cancellation via AbortController.
|
|
9
|
+
*
|
|
10
|
+
* Example:
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { getTokenList } from "@shogun/sdk";
|
|
13
|
+
*
|
|
14
|
+
* const controller = new AbortController();
|
|
15
|
+
* const res = await getTokenList({ q: "usdc", networkId: 8453, signal: controller.signal });
|
|
16
|
+
*
|
|
17
|
+
* To cancel:
|
|
18
|
+
* controller.abort();
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export async function getTokenList(params: TokenSearchParams): Promise<TokenSearchResponse> {
|
|
22
|
+
const url = new URL(`${TOKEN_SEARCH_API_BASE_URL}/tokens/search`);
|
|
23
|
+
|
|
24
|
+
if (params.q) url.searchParams.append('q', params.q);
|
|
25
|
+
if (params.networkId) url.searchParams.append('networkId', String(params.networkId));
|
|
26
|
+
if (params.page) url.searchParams.append('page', String(params.page));
|
|
27
|
+
if (params.limit) url.searchParams.append('limit', String(params.limit));
|
|
28
|
+
|
|
29
|
+
const res = await fetch(url.toString(), {
|
|
30
|
+
signal: params.signal,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`Failed to fetch tokens: ${res.status} ${res.statusText}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (await res.json()) as TokenSearchResponse;
|
|
38
|
+
}
|