@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.
Files changed (121) hide show
  1. package/README.md +3 -19
  2. package/dist/esm/chains.js +3 -0
  3. package/dist/esm/chains.js.map +1 -1
  4. package/dist/esm/constants.js +9 -0
  5. package/dist/esm/constants.js.map +1 -1
  6. package/dist/esm/core/evm/chain-provider.js +2 -1
  7. package/dist/esm/core/evm/chain-provider.js.map +1 -1
  8. package/dist/esm/core/evm/cross-chain-limit-order.js +1 -1
  9. package/dist/esm/core/evm/cross-chain-limit-order.js.map +1 -1
  10. package/dist/esm/core/evm/permit2-signature-transfer.js +15 -0
  11. package/dist/esm/core/evm/permit2-signature-transfer.js.map +1 -0
  12. package/dist/esm/core/evm/single-chain-dca-order.js +1 -1
  13. package/dist/esm/core/evm/single-chain-dca-order.js.map +1 -1
  14. package/dist/esm/core/evm/single-chain-limit-order.js +1 -1
  15. package/dist/esm/core/evm/single-chain-limit-order.js.map +1 -1
  16. package/dist/esm/core/solana/dca/single-chain-dca-order.js +3 -2
  17. package/dist/esm/core/solana/dca/single-chain-dca-order.js.map +1 -1
  18. package/dist/esm/core/solana/dca/single-chain-limit-order.js +3 -2
  19. package/dist/esm/core/solana/dca/single-chain-limit-order.js.map +1 -1
  20. package/dist/esm/core/solana/utils.js +14 -4
  21. package/dist/esm/core/solana/utils.js.map +1 -1
  22. package/dist/esm/core/sui/single-chain-dca-order.js +2 -1
  23. package/dist/esm/core/sui/single-chain-dca-order.js.map +1 -1
  24. package/dist/esm/core/sui/single-chain-limit-order.js +2 -1
  25. package/dist/esm/core/sui/single-chain-limit-order.js.map +1 -1
  26. package/dist/esm/index.js +6 -3
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/types/token-list.js +2 -0
  29. package/dist/esm/types/token-list.js.map +1 -0
  30. package/dist/esm/utils/defillama.js +1 -0
  31. package/dist/esm/utils/defillama.js.map +1 -1
  32. package/dist/esm/utils/quote/aggregator.js +158 -42
  33. package/dist/esm/utils/quote/aggregator.js.map +1 -1
  34. package/dist/esm/utils/quote/paraswap.js +1 -0
  35. package/dist/esm/utils/quote/paraswap.js.map +1 -1
  36. package/dist/esm/utils/quote/pumpfun/estimations.js +169 -0
  37. package/dist/esm/utils/quote/pumpfun/estimations.js.map +1 -0
  38. package/dist/esm/utils/quote/pumpfun/estimations_amm.js +130 -0
  39. package/dist/esm/utils/quote/pumpfun/estimations_amm.js.map +1 -0
  40. package/dist/esm/utils/quote/pumpfun/index.js +198 -0
  41. package/dist/esm/utils/quote/pumpfun/index.js.map +1 -0
  42. package/dist/esm/utils/quote/pumpfun/models.js +70 -0
  43. package/dist/esm/utils/quote/pumpfun/models.js.map +1 -0
  44. package/dist/esm/utils/quote/pumpfun/utils.js +269 -0
  45. package/dist/esm/utils/quote/pumpfun/utils.js.map +1 -0
  46. package/dist/esm/utils/quote/raydium.js +336 -0
  47. package/dist/esm/utils/quote/raydium.js.map +1 -0
  48. package/dist/esm/utils/quote/stablecoins-tokens.js +1 -0
  49. package/dist/esm/utils/quote/stablecoins-tokens.js.map +1 -1
  50. package/dist/esm/utils/quote/utils.js +8 -0
  51. package/dist/esm/utils/quote/utils.js.map +1 -0
  52. package/dist/esm/utils/tokens/index.js +37 -0
  53. package/dist/esm/utils/tokens/index.js.map +1 -0
  54. package/dist/types/chains.d.ts +4 -2
  55. package/dist/types/chains.d.ts.map +1 -1
  56. package/dist/types/constants.d.ts +2 -1
  57. package/dist/types/constants.d.ts.map +1 -1
  58. package/dist/types/core/evm/chain-provider.d.ts.map +1 -1
  59. package/dist/types/core/evm/permit2-signature-transfer.d.ts +3 -0
  60. package/dist/types/core/evm/permit2-signature-transfer.d.ts.map +1 -0
  61. package/dist/types/core/solana/dca/single-chain-dca-order.d.ts +1 -2
  62. package/dist/types/core/solana/dca/single-chain-dca-order.d.ts.map +1 -1
  63. package/dist/types/core/solana/dca/single-chain-limit-order.d.ts +1 -2
  64. package/dist/types/core/solana/dca/single-chain-limit-order.d.ts.map +1 -1
  65. package/dist/types/core/solana/utils.d.ts +2 -0
  66. package/dist/types/core/solana/utils.d.ts.map +1 -1
  67. package/dist/types/core/sui/single-chain-dca-order.d.ts.map +1 -1
  68. package/dist/types/core/sui/single-chain-limit-order.d.ts.map +1 -1
  69. package/dist/types/index.d.ts +5 -3
  70. package/dist/types/index.d.ts.map +1 -1
  71. package/dist/types/types/token-list.d.ts +30 -0
  72. package/dist/types/types/token-list.d.ts.map +1 -0
  73. package/dist/types/utils/defillama.d.ts +1 -0
  74. package/dist/types/utils/defillama.d.ts.map +1 -1
  75. package/dist/types/utils/quote/aggregator.d.ts +42 -5
  76. package/dist/types/utils/quote/aggregator.d.ts.map +1 -1
  77. package/dist/types/utils/quote/paraswap.d.ts.map +1 -1
  78. package/dist/types/utils/quote/pumpfun/estimations.d.ts +30 -0
  79. package/dist/types/utils/quote/pumpfun/estimations.d.ts.map +1 -0
  80. package/dist/types/utils/quote/pumpfun/estimations_amm.d.ts +49 -0
  81. package/dist/types/utils/quote/pumpfun/estimations_amm.d.ts.map +1 -0
  82. package/dist/types/utils/quote/pumpfun/index.d.ts +10 -0
  83. package/dist/types/utils/quote/pumpfun/index.d.ts.map +1 -0
  84. package/dist/types/utils/quote/pumpfun/models.d.ts +94 -0
  85. package/dist/types/utils/quote/pumpfun/models.d.ts.map +1 -0
  86. package/dist/types/utils/quote/pumpfun/utils.d.ts +50 -0
  87. package/dist/types/utils/quote/pumpfun/utils.d.ts.map +1 -0
  88. package/dist/types/utils/quote/raydium.d.ts +98 -0
  89. package/dist/types/utils/quote/raydium.d.ts.map +1 -0
  90. package/dist/types/utils/quote/stablecoins-tokens.d.ts.map +1 -1
  91. package/dist/types/utils/quote/utils.d.ts +5 -0
  92. package/dist/types/utils/quote/utils.d.ts.map +1 -0
  93. package/dist/types/utils/tokens/index.d.ts +20 -0
  94. package/dist/types/utils/tokens/index.d.ts.map +1 -0
  95. package/package.json +20 -18
  96. package/src/chains.ts +3 -0
  97. package/src/constants.ts +12 -2
  98. package/src/core/evm/chain-provider.ts +2 -1
  99. package/src/core/evm/cross-chain-limit-order.ts +1 -1
  100. package/src/core/evm/permit2-signature-transfer.ts +15 -0
  101. package/src/core/evm/single-chain-dca-order.ts +1 -1
  102. package/src/core/evm/single-chain-limit-order.ts +1 -1
  103. package/src/core/solana/dca/single-chain-dca-order.ts +3 -3
  104. package/src/core/solana/dca/single-chain-limit-order.ts +3 -2
  105. package/src/core/solana/utils.ts +24 -12
  106. package/src/core/sui/single-chain-dca-order.ts +2 -2
  107. package/src/core/sui/single-chain-limit-order.ts +2 -2
  108. package/src/index.ts +14 -4
  109. package/src/types/token-list.ts +35 -0
  110. package/src/utils/defillama.ts +1 -0
  111. package/src/utils/quote/aggregator.ts +210 -55
  112. package/src/utils/quote/paraswap.ts +1 -0
  113. package/src/utils/quote/pumpfun/estimations.ts +206 -0
  114. package/src/utils/quote/pumpfun/estimations_amm.ts +165 -0
  115. package/src/utils/quote/pumpfun/index.ts +266 -0
  116. package/src/utils/quote/pumpfun/models.ts +163 -0
  117. package/src/utils/quote/pumpfun/utils.ts +343 -0
  118. package/src/utils/quote/raydium.ts +506 -0
  119. package/src/utils/quote/stablecoins-tokens.ts +1 -0
  120. package/src/utils/quote/utils.ts +8 -0
  121. 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
+ }