@shogun-sdk/swap 0.0.2-test.24 → 0.0.2-test.26

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/dist/react.js CHANGED
@@ -1,90 +1,19 @@
1
- // src/react/useTokenList.ts
2
- import { useEffect, useMemo, useRef, useState } from "react";
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
3
4
 
4
- // src/core/token-list.ts
5
- import { getTokenList as intentsGetTokenList } from "@shogun-sdk/intents-sdk";
6
- async function getTokenList(params) {
7
- return intentsGetTokenList(params);
8
- }
5
+ // src/react/SwapProvider.tsx
6
+ import { createContext, useContext, useMemo } from "react";
9
7
 
10
- // src/react/useTokenList.ts
11
- var tokenCache = /* @__PURE__ */ new Map();
12
- function useTokenList(params) {
13
- const [data, setData] = useState(null);
14
- const [loading, setLoading] = useState(false);
15
- const [error, setError] = useState(null);
16
- const controllerRef = useRef(null);
17
- const debounceRef = useRef(null);
18
- const debounceMs = params.debounceMs ?? 250;
19
- const cacheKey = useMemo(() => {
20
- return JSON.stringify({
21
- q: params.q?.trim().toLowerCase() ?? "",
22
- networkId: params.networkId ?? "all",
23
- page: params.page ?? 1,
24
- limit: params.limit ?? 50
25
- });
26
- }, [params.q, params.networkId, params.page, params.limit]);
27
- async function fetchTokens(signal) {
28
- if (tokenCache.has(cacheKey)) {
29
- setData(tokenCache.get(cacheKey));
30
- setLoading(false);
31
- setError(null);
32
- return;
33
- }
34
- try {
35
- setLoading(true);
36
- const result = await getTokenList({ ...params, signal });
37
- tokenCache.set(cacheKey, result);
38
- setData(result);
39
- setError(null);
40
- } catch (err) {
41
- if (err.name !== "AbortError") {
42
- const e = err instanceof Error ? err : new Error("Unknown error while fetching tokens");
43
- setError(e);
44
- }
45
- } finally {
46
- setLoading(false);
47
- }
48
- }
49
- useEffect(() => {
50
- if (!params.q && !params.networkId) return;
51
- if (debounceRef.current) clearTimeout(debounceRef.current);
52
- if (controllerRef.current) controllerRef.current.abort();
53
- const controller = new AbortController();
54
- controllerRef.current = controller;
55
- debounceRef.current = setTimeout(() => {
56
- fetchTokens(controller.signal);
57
- }, debounceMs);
58
- return () => {
59
- controller.abort();
60
- if (debounceRef.current) clearTimeout(debounceRef.current);
61
- };
62
- }, [cacheKey, debounceMs]);
63
- return useMemo(
64
- () => ({
65
- /** Current fetched data (cached when possible) */
66
- data,
67
- /** Whether a request is in progress */
68
- loading,
69
- /** Error object if a request failed */
70
- error,
71
- /** Manually refetch the token list */
72
- refetch: () => fetchTokens(),
73
- /** Clear all cached token results (shared across hook instances) */
74
- clearCache: () => tokenCache.clear()
75
- }),
76
- [data, loading, error]
77
- );
78
- }
79
-
80
- // src/react/useExecuteOrder.ts
81
- import { useState as useState2, useCallback, useRef as useRef2, useEffect as useEffect2 } from "react";
8
+ // src/core/getQuote.ts
9
+ import { QuoteProvider } from "@shogun-sdk/intents-sdk";
10
+ import { parseUnits } from "viem";
82
11
 
83
- // src/core/executeOrder/execute.ts
84
- import { ChainID as ChainID3, isEvmChain as isEvmChain2 } from "@shogun-sdk/intents-sdk";
85
- import { BaseError } from "viem";
12
+ // src/core/execute/normalizeNative.ts
13
+ import { isEvmChain as isEvmChain2 } from "@shogun-sdk/intents-sdk";
86
14
 
87
15
  // src/utils/address.ts
16
+ import { zeroAddress } from "viem";
88
17
  var NATIVE_TOKEN = {
89
18
  ETH: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
90
19
  SOL: "So11111111111111111111111111111111111111111",
@@ -95,13 +24,31 @@ var isNativeAddress = (tokenAddress) => {
95
24
  const normalizedTokenAddress = tokenAddress.toLowerCase();
96
25
  return !!tokenAddress && NATIVE_ADDRESSES.includes(normalizedTokenAddress);
97
26
  };
27
+ function normalizeEvmTokenAddress(address) {
28
+ const lower = address.toLowerCase();
29
+ return lower === NATIVE_TOKEN.ETH.toLowerCase() ? zeroAddress : address;
30
+ }
98
31
 
99
32
  // src/utils/chain.ts
100
- import { ChainID } from "@shogun-sdk/intents-sdk";
101
- var SOLANA_CHAIN_ID = ChainID.Solana;
102
- var SupportedChains = [
33
+ import { ChainID as BaseChainID, isEvmChain as isEvmChainIntent } from "@shogun-sdk/intents-sdk";
34
+ var SOLANA_CHAIN_ID = BaseChainID.Solana;
35
+ var CURRENT_SUPPORTED = [
36
+ BaseChainID.Solana,
37
+ BaseChainID.BSC,
38
+ BaseChainID.Base
39
+ ];
40
+ var ChainId = Object.entries(BaseChainID).reduce(
41
+ (acc, [key, value]) => {
42
+ if (typeof value === "number" && CURRENT_SUPPORTED.includes(value)) {
43
+ acc[key] = value;
44
+ }
45
+ return acc;
46
+ },
47
+ {}
48
+ );
49
+ var SupportedChainsInternal = [
103
50
  {
104
- id: ChainID.Arbitrum,
51
+ id: BaseChainID.Arbitrum,
105
52
  name: "Arbitrum",
106
53
  isEVM: true,
107
54
  wrapped: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -110,7 +57,7 @@ var SupportedChains = [
110
57
  tokenAddress: NATIVE_TOKEN.ETH
111
58
  },
112
59
  {
113
- id: ChainID.Optimism,
60
+ id: BaseChainID.Optimism,
114
61
  name: "Optimism",
115
62
  isEVM: true,
116
63
  wrapped: "0x4200000000000000000000000000000000000006",
@@ -119,7 +66,7 @@ var SupportedChains = [
119
66
  tokenAddress: NATIVE_TOKEN.ETH
120
67
  },
121
68
  {
122
- id: ChainID.Base,
69
+ id: BaseChainID.Base,
123
70
  name: "Base",
124
71
  isEVM: true,
125
72
  wrapped: "0x4200000000000000000000000000000000000006",
@@ -128,7 +75,7 @@ var SupportedChains = [
128
75
  tokenAddress: NATIVE_TOKEN.ETH
129
76
  },
130
77
  {
131
- id: ChainID.Hyperliquid,
78
+ id: BaseChainID.Hyperliquid,
132
79
  name: "Hyperliquid",
133
80
  isEVM: true,
134
81
  wrapped: "0x5555555555555555555555555555555555555555",
@@ -137,7 +84,7 @@ var SupportedChains = [
137
84
  tokenAddress: NATIVE_TOKEN.ETH
138
85
  },
139
86
  {
140
- id: ChainID.BSC,
87
+ id: BaseChainID.BSC,
141
88
  name: "BSC",
142
89
  isEVM: true,
143
90
  wrapped: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
@@ -155,6 +102,10 @@ var SupportedChains = [
155
102
  tokenAddress: NATIVE_TOKEN.SOL
156
103
  }
157
104
  ];
105
+ var SupportedChains = SupportedChainsInternal.filter(
106
+ (c) => CURRENT_SUPPORTED.includes(c.id)
107
+ );
108
+ var isEvmChain = isEvmChainIntent;
158
109
 
159
110
  // src/utils/viem.ts
160
111
  function isViemWalletClient(wallet) {
@@ -182,9 +133,197 @@ function serializeBigIntsToStrings(obj) {
182
133
  return obj;
183
134
  }
184
135
 
136
+ // src/core/execute/normalizeNative.ts
137
+ function normalizeNative(chainId, address) {
138
+ if (isEvmChain2(chainId) && isNativeAddress(address)) {
139
+ const chain = SupportedChains.find((c) => c.id === chainId);
140
+ if (!chain?.wrapped)
141
+ throw new Error(`Wrapped token not found for chainId ${chainId}`);
142
+ return chain.wrapped;
143
+ }
144
+ return address;
145
+ }
146
+
147
+ // src/core/getQuote.ts
148
+ async function getQuote(params) {
149
+ const amount = BigInt(params.amount);
150
+ if (!params.tokenIn?.address || !params.tokenOut?.address) {
151
+ throw new Error("Both tokenIn and tokenOut must include an address.");
152
+ }
153
+ if (!params.sourceChainId || !params.destChainId) {
154
+ throw new Error("Both sourceChainId and destChainId are required.");
155
+ }
156
+ if (amount <= 0n) {
157
+ throw new Error("Amount must be greater than 0.");
158
+ }
159
+ const normalizedTokenIn = normalizeNative(params.sourceChainId, params.tokenIn.address);
160
+ const data = await QuoteProvider.getQuote({
161
+ sourceChainId: params.sourceChainId,
162
+ destChainId: params.destChainId,
163
+ tokenIn: normalizedTokenIn,
164
+ tokenOut: params.tokenOut.address,
165
+ amount
166
+ });
167
+ const slippagePercent = Math.min(Math.max(params.slippage ?? 0.5, 0), 50);
168
+ let warning;
169
+ if (slippagePercent > 10) {
170
+ warning = `\u26A0\uFE0F High slippage tolerance (${slippagePercent.toFixed(2)}%) \u2014 price may vary significantly.`;
171
+ }
172
+ const estimatedAmountOut = BigInt(data.estimatedAmountOut);
173
+ const slippageBps = BigInt(Math.round(slippagePercent * 100));
174
+ const estimatedAmountOutAfterSlippage = estimatedAmountOut * (10000n - slippageBps) / 10000n;
175
+ const pricePerTokenOutInUsd = data.estimatedAmountOutUsd / Number(data.estimatedAmountOut);
176
+ const amountOutUsdAfterSlippage = Number(estimatedAmountOutAfterSlippage) * pricePerTokenOutInUsd;
177
+ const minStablecoinsAmountValue = BigInt(data.estimatedAmountInAsMinStablecoinAmount);
178
+ const minStablecoinsAmountAfterSlippage = minStablecoinsAmountValue * (10000n - slippageBps) / 10000n;
179
+ const pricePerInputToken = estimatedAmountOut * 10n ** BigInt(params.tokenIn.decimals ?? 18) / BigInt(params.amount);
180
+ return {
181
+ amountOut: estimatedAmountOutAfterSlippage,
182
+ amountOutUsd: amountOutUsdAfterSlippage,
183
+ amountInUsd: data.amountInUsd,
184
+ // Input USD stays the same
185
+ minStablecoinsAmount: minStablecoinsAmountAfterSlippage,
186
+ tokenIn: {
187
+ address: params.tokenIn.address,
188
+ decimals: params.tokenIn.decimals ?? 18,
189
+ chainId: params.sourceChainId
190
+ },
191
+ tokenOut: {
192
+ address: params.tokenOut.address,
193
+ decimals: params.tokenOut.decimals ?? 18,
194
+ chainId: params.destChainId
195
+ },
196
+ amountIn: BigInt(params.amount),
197
+ pricePerInputToken,
198
+ slippage: slippagePercent,
199
+ internal: {
200
+ ...data,
201
+ estimatedAmountOutReduced: estimatedAmountOutAfterSlippage,
202
+ estimatedAmountOutUsdReduced: amountOutUsdAfterSlippage
203
+ },
204
+ warning
205
+ };
206
+ }
207
+ function buildQuoteParams({
208
+ tokenIn,
209
+ tokenOut,
210
+ sourceChainId,
211
+ destChainId,
212
+ amount,
213
+ slippage
214
+ }) {
215
+ return {
216
+ tokenIn,
217
+ tokenOut,
218
+ sourceChainId,
219
+ destChainId,
220
+ amount: parseUnits(amount.toString(), tokenIn.decimals ?? 18).toString(),
221
+ slippage
222
+ };
223
+ }
224
+
225
+ // src/core/getBalances.ts
226
+ import { TOKEN_SEARCH_API_BASE_URL } from "@shogun-sdk/intents-sdk";
227
+ async function getBalances(params, options) {
228
+ const { addresses, cursorEvm, cursorSvm } = params;
229
+ const { signal } = options ?? {};
230
+ if (!addresses?.evm && !addresses?.svm) {
231
+ throw new Error("At least one address (EVM or SVM) must be provided.");
232
+ }
233
+ const payload = JSON.stringify({
234
+ addresses,
235
+ cursorEvm,
236
+ cursorSvm
237
+ });
238
+ const start = performance.now();
239
+ const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL}/tokens/balances`, {
240
+ method: "POST",
241
+ headers: {
242
+ accept: "application/json",
243
+ "Content-Type": "application/json"
244
+ },
245
+ body: payload,
246
+ signal
247
+ }).catch((err) => {
248
+ if (err.name === "AbortError") {
249
+ throw new Error("Balance request was cancelled.");
250
+ }
251
+ throw err;
252
+ });
253
+ if (!response.ok) {
254
+ const text = await response.text().catch(() => "");
255
+ throw new Error(`Failed to fetch balances: ${response.status} ${text}`);
256
+ }
257
+ const data = await response.json().catch(() => {
258
+ throw new Error("Invalid JSON response from balances API.");
259
+ });
260
+ const duration = (performance.now() - start).toFixed(1);
261
+ if (process.env.NODE_ENV !== "production") {
262
+ console.debug(`[Shogun SDK] Fetched balances in ${duration}ms`);
263
+ }
264
+ const evmItems = data.evm?.items ?? [];
265
+ const svmItems = data.svm?.items ?? [];
266
+ const combined = [...evmItems, ...svmItems];
267
+ const filtered = combined.filter(
268
+ (b) => CURRENT_SUPPORTED.includes(b.chainId)
269
+ );
270
+ return {
271
+ results: filtered,
272
+ nextCursorEvm: data.evm?.cursor ?? null,
273
+ nextCursorSvm: data.svm?.cursor ?? null
274
+ };
275
+ }
276
+
277
+ // src/core/token-list.ts
278
+ import { TOKEN_SEARCH_API_BASE_URL as TOKEN_SEARCH_API_BASE_URL2 } from "@shogun-sdk/intents-sdk";
279
+ async function getTokenList(params) {
280
+ const url = new URL(`${TOKEN_SEARCH_API_BASE_URL2}/tokens/search`);
281
+ if (params.q) url.searchParams.append("q", params.q);
282
+ if (params.networkId) url.searchParams.append("networkId", String(params.networkId));
283
+ if (params.page) url.searchParams.append("page", String(params.page));
284
+ if (params.limit) url.searchParams.append("limit", String(params.limit));
285
+ const res = await fetch(url.toString(), {
286
+ signal: params.signal
287
+ });
288
+ if (!res.ok) {
289
+ throw new Error(`Failed to fetch tokens: ${res.status} ${res.statusText}`);
290
+ }
291
+ const data = await res.json();
292
+ const filteredResults = data.results.filter(
293
+ (token) => CURRENT_SUPPORTED.includes(token.chainId)
294
+ );
295
+ return {
296
+ ...data,
297
+ results: filteredResults,
298
+ count: filteredResults.length
299
+ };
300
+ }
301
+
302
+ // src/core/token.ts
303
+ import { TOKEN_SEARCH_API_BASE_URL as TOKEN_SEARCH_API_BASE_URL3 } from "@shogun-sdk/intents-sdk";
304
+ async function getTokensData(addresses) {
305
+ if (!addresses?.length) return [];
306
+ const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL3}/tokens/tokens`, {
307
+ method: "POST",
308
+ headers: {
309
+ "Content-Type": "application/json",
310
+ accept: "*/*"
311
+ },
312
+ body: JSON.stringify({ addresses })
313
+ });
314
+ if (!response.ok) {
315
+ throw new Error(`Failed to fetch token data: ${response.statusText}`);
316
+ }
317
+ const data = await response.json();
318
+ const filtered = data.filter((t) => CURRENT_SUPPORTED.includes(Number(t.chainId)));
319
+ return filtered;
320
+ }
321
+
322
+ // src/core/execute/execute.ts
323
+ import { ChainID as ChainID2, isEvmChain as isEvmChain3 } from "@shogun-sdk/intents-sdk";
324
+ import "viem";
325
+
185
326
  // src/wallet-adapter/evm-wallet-adapter/adapter.ts
186
- import { utils as ethersUtils } from "ethers/lib/ethers.js";
187
- import { hexValue } from "ethers/lib/utils.js";
188
327
  import {
189
328
  custom,
190
329
  publicActions
@@ -274,26 +413,14 @@ var adaptViemWallet = (wallet) => {
274
413
  };
275
414
  };
276
415
 
277
- // src/core/executeOrder/handleEvmExecution.ts
416
+ // src/core/execute/handleEvmExecution.ts
278
417
  import {
279
418
  getEVMSingleChainOrderTypedData,
280
419
  getEVMCrossChainOrderTypedData
281
420
  } from "@shogun-sdk/intents-sdk";
282
421
  import { encodeFunctionData as encodeFunctionData2 } from "viem";
283
422
 
284
- // src/core/executeOrder/normalizeNative.ts
285
- import { isEvmChain } from "@shogun-sdk/intents-sdk";
286
- function normalizeNative(chainId, address) {
287
- if (isEvmChain(chainId) && isNativeAddress(address)) {
288
- const chain = SupportedChains.find((c) => c.id === chainId);
289
- if (!chain?.wrapped)
290
- throw new Error(`Wrapped token not found for chainId ${chainId}`);
291
- return chain.wrapped;
292
- }
293
- return address;
294
- }
295
-
296
- // src/core/executeOrder/stageMessages.ts
423
+ // src/core/execute/stageMessages.ts
297
424
  var DEFAULT_STAGE_MESSAGES = {
298
425
  processing: "Preparing transaction for execution",
299
426
  approving: "Approving token allowance",
@@ -302,20 +429,44 @@ var DEFAULT_STAGE_MESSAGES = {
302
429
  submitting: "Submitting transaction",
303
430
  initiated: "Transaction initiated.",
304
431
  success: "Transaction Executed successfully",
432
+ success_limit: "Limit order has been submitted successfully.",
305
433
  shogun_processing: "Shogun is processing your transaction",
306
434
  error: "Transaction failed during submission"
307
435
  };
308
436
 
309
- // src/core/executeOrder/buildOrder.ts
437
+ // src/core/execute/buildOrder.ts
310
438
  import { CrossChainOrder, SingleChainOrder } from "@shogun-sdk/intents-sdk";
439
+ import { formatUnits, parseUnits as parseUnits2 } from "viem";
440
+
441
+ // src/utils/order.ts
442
+ var OrderExecutionType = /* @__PURE__ */ ((OrderExecutionType2) => {
443
+ OrderExecutionType2["LIMIT"] = "limit";
444
+ OrderExecutionType2["MARKET"] = "market";
445
+ return OrderExecutionType2;
446
+ })(OrderExecutionType || {});
447
+
448
+ // src/core/execute/buildOrder.ts
311
449
  async function buildOrder({
312
450
  quote,
313
451
  accountAddress,
314
452
  destination,
315
453
  deadline,
316
- isSingleChain
454
+ isSingleChain,
455
+ orderType,
456
+ options
317
457
  }) {
318
458
  const { tokenIn, tokenOut } = quote;
459
+ let amountOutMin = BigInt(quote.internal.estimatedAmountOutReduced);
460
+ if (orderType === "limit" /* LIMIT */ && options && "executionPrice" in options) {
461
+ const executionPrice = Number(options.executionPrice);
462
+ if (Number.isFinite(executionPrice) && executionPrice > 0) {
463
+ const decimalsIn = tokenIn.decimals ?? 18;
464
+ const decimalsOut = tokenOut.decimals ?? 18;
465
+ const formattedAmountIn = Number(formatUnits(BigInt(quote.amountIn.toString()), decimalsIn));
466
+ const rawAmountOut = formattedAmountIn * executionPrice;
467
+ amountOutMin = parseUnits2(rawAmountOut.toString(), decimalsOut);
468
+ }
469
+ }
319
470
  if (isSingleChain) {
320
471
  return await SingleChainOrder.create({
321
472
  user: accountAddress,
@@ -323,7 +474,7 @@ async function buildOrder({
323
474
  tokenIn: tokenIn.address,
324
475
  tokenOut: tokenOut.address,
325
476
  amountIn: quote.amountIn,
326
- amountOutMin: quote.internal.estimatedAmountOutReduced,
477
+ amountOutMin,
327
478
  deadline,
328
479
  destinationAddress: destination
329
480
  });
@@ -337,7 +488,7 @@ async function buildOrder({
337
488
  destinationTokenAddress: tokenOut.address,
338
489
  destinationAddress: destination,
339
490
  deadline,
340
- destinationTokenMinAmount: quote.internal.estimatedAmountOutReduced,
491
+ destinationTokenMinAmount: amountOutMin,
341
492
  minStablecoinAmount: quote.minStablecoinsAmount
342
493
  });
343
494
  }
@@ -408,7 +559,7 @@ async function pollOrderStatus(address, orderId, options = {}) {
408
559
  });
409
560
  }
410
561
 
411
- // src/core/executeOrder/handleOrderPollingResult.ts
562
+ // src/core/execute/handleOrderPollingResult.ts
412
563
  async function handleOrderPollingResult({
413
564
  status,
414
565
  orderId,
@@ -446,7 +597,7 @@ async function handleOrderPollingResult({
446
597
  };
447
598
  }
448
599
 
449
- // src/core/executeOrder/ensurePermit2Allowance.ts
600
+ // src/core/execute/ensurePermit2Allowance.ts
450
601
  import { encodeFunctionData, erc20Abi, maxUint256 } from "viem";
451
602
  import { PERMIT2_ADDRESS } from "@shogun-sdk/intents-sdk";
452
603
  async function ensurePermit2Allowance({
@@ -491,7 +642,7 @@ async function ensurePermit2Allowance({
491
642
  );
492
643
  }
493
644
 
494
- // src/core/executeOrder/handleEvmExecution.ts
645
+ // src/core/execute/handleEvmExecution.ts
495
646
  async function handleEvmExecution({
496
647
  recipientAddress,
497
648
  quote,
@@ -499,15 +650,19 @@ async function handleEvmExecution({
499
650
  accountAddress,
500
651
  wallet,
501
652
  isSingleChain,
502
- deadline,
503
- update
653
+ update,
654
+ orderType,
655
+ options
504
656
  }) {
505
- const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
657
+ const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage] ?? "";
658
+ const deadline = options?.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
506
659
  await wallet.switchChain(chainId);
507
660
  const tokenIn = normalizeNative(chainId, quote.tokenIn.address);
661
+ quote.tokenOut.address = normalizeEvmTokenAddress(quote.tokenOut.address);
508
662
  const shouldWrapNative = isNativeAddress(quote.tokenIn.address);
509
663
  update("processing", shouldWrapNative ? `${messageFor("processing")} (wrapping native token)` : messageFor("processing"));
510
664
  if (shouldWrapNative) {
665
+ quote.tokenIn.address === tokenIn;
511
666
  await wallet.sendTransaction({
512
667
  to: tokenIn,
513
668
  data: encodeFunctionData2({
@@ -534,7 +689,9 @@ async function handleEvmExecution({
534
689
  accountAddress,
535
690
  destination,
536
691
  deadline,
537
- isSingleChain
692
+ isSingleChain,
693
+ orderType,
694
+ options
538
695
  });
539
696
  console.debug(`order`, order);
540
697
  update("processing", messageFor("signing"));
@@ -558,17 +715,28 @@ async function handleEvmExecution({
558
715
  update("initiated", messageFor("initiated"));
559
716
  const { intentId: orderId } = res.data;
560
717
  update("initiated", messageFor("shogun_processing"));
561
- const status = await pollOrderStatus(accountAddress, orderId);
562
- return await handleOrderPollingResult({
563
- status,
564
- orderId,
565
- chainId,
566
- update,
567
- messageFor
568
- });
718
+ if (orderType === "limit" /* LIMIT */) {
719
+ update("success", messageFor("success_limit"));
720
+ return {
721
+ status: true,
722
+ orderId,
723
+ chainId,
724
+ finalStatus: "OrderPlaced",
725
+ stage: "success"
726
+ };
727
+ } else {
728
+ const status = await pollOrderStatus(accountAddress, orderId);
729
+ return await handleOrderPollingResult({
730
+ status,
731
+ orderId,
732
+ chainId,
733
+ update,
734
+ messageFor
735
+ });
736
+ }
569
737
  }
570
738
 
571
- // src/core/executeOrder/handleSolanaExecution.ts
739
+ // src/core/execute/handleSolanaExecution.ts
572
740
  import {
573
741
  getSolanaSingleChainOrderInstructions,
574
742
  getSolanaCrossChainOrderInstructions
@@ -581,12 +749,14 @@ async function handleSolanaExecution({
581
749
  isSingleChain,
582
750
  update,
583
751
  accountAddress,
584
- deadline
752
+ orderType,
753
+ options
585
754
  }) {
586
755
  if (!wallet.rpcUrl) {
587
756
  throw new Error("Solana wallet is missing rpcUrl");
588
757
  }
589
- const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
758
+ const deadline = options?.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
759
+ const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage] ?? "";
590
760
  update("processing", messageFor("processing"));
591
761
  const destination = recipientAddress ?? accountAddress;
592
762
  const order = await buildOrder({
@@ -594,7 +764,9 @@ async function handleSolanaExecution({
594
764
  accountAddress,
595
765
  destination,
596
766
  deadline,
597
- isSingleChain
767
+ isSingleChain,
768
+ orderType,
769
+ options
598
770
  });
599
771
  const txData = await getSolanaOrderInstructions({
600
772
  order,
@@ -616,14 +788,25 @@ async function handleSolanaExecution({
616
788
  update("initiated", messageFor("initiated"));
617
789
  const { jwt, intentId: orderId } = response.data;
618
790
  update("initiated", messageFor("shogun_processing"));
619
- const status = await pollOrderStatus(jwt, orderId);
620
- return await handleOrderPollingResult({
621
- status,
622
- orderId,
623
- chainId: SOLANA_CHAIN_ID,
624
- update,
625
- messageFor
626
- });
791
+ if (orderType === "limit" /* LIMIT */) {
792
+ update("success", messageFor("success_limit"));
793
+ return {
794
+ status: true,
795
+ orderId,
796
+ chainId: SOLANA_CHAIN_ID,
797
+ finalStatus: "OrderPlaced",
798
+ stage: "success"
799
+ };
800
+ } else {
801
+ const status = await pollOrderStatus(jwt, orderId);
802
+ return await handleOrderPollingResult({
803
+ status,
804
+ orderId,
805
+ chainId: SOLANA_CHAIN_ID,
806
+ update,
807
+ messageFor
808
+ });
809
+ }
627
810
  }
628
811
  async function getSolanaOrderInstructions({
629
812
  order,
@@ -656,13 +839,14 @@ async function submitToAuctioneer({
656
839
  });
657
840
  }
658
841
 
659
- // src/core/executeOrder/execute.ts
842
+ // src/core/execute/execute.ts
660
843
  async function executeOrder({
661
844
  quote,
662
845
  accountAddress,
663
846
  recipientAddress,
664
847
  wallet,
665
848
  onStatus,
849
+ orderType = "market" /* MARKET */,
666
850
  options = {}
667
851
  }) {
668
852
  const isDev = process.env.NODE_ENV !== "production";
@@ -675,21 +859,31 @@ async function executeOrder({
675
859
  onStatus?.(stage, message ?? messageFor(stage));
676
860
  };
677
861
  try {
678
- const deadline = options.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
679
862
  log("Starting execution:", {
680
863
  accountAddress,
681
864
  recipientAddress,
682
- deadline,
683
865
  tokenIn: quote?.tokenIn,
684
866
  tokenOut: quote?.tokenOut
685
867
  });
686
868
  const adapter = normalizeWallet(wallet);
687
869
  if (!adapter) throw new Error("No wallet provided");
688
870
  const { tokenIn, tokenOut } = quote;
871
+ const srcChain = Number(tokenIn.chainId);
872
+ const destChain = Number(tokenOut.chainId);
873
+ if (!CURRENT_SUPPORTED.includes(srcChain) || !CURRENT_SUPPORTED.includes(destChain)) {
874
+ const unsupportedChains = [
875
+ !CURRENT_SUPPORTED.includes(srcChain) ? srcChain : null,
876
+ !CURRENT_SUPPORTED.includes(destChain) ? destChain : null
877
+ ].filter(Boolean).join(", ");
878
+ const errorMsg = `Unsupported chain(s): ${unsupportedChains}`;
879
+ update("error", errorMsg);
880
+ log("Error:", errorMsg);
881
+ throw new Error(errorMsg);
882
+ }
689
883
  const isSingleChain = tokenIn.chainId === tokenOut.chainId;
690
884
  const chainId = Number(tokenIn.chainId);
691
885
  update("processing");
692
- if (isEvmChain2(chainId)) {
886
+ if (isEvmChain3(chainId)) {
693
887
  log("Detected EVM chain:", chainId);
694
888
  const result = await handleEvmExecution({
695
889
  recipientAddress,
@@ -698,13 +892,14 @@ async function executeOrder({
698
892
  accountAddress,
699
893
  wallet: adapter,
700
894
  isSingleChain,
701
- deadline,
702
- update
895
+ update,
896
+ orderType,
897
+ options
703
898
  });
704
899
  log("EVM execution result:", result);
705
900
  return result;
706
901
  }
707
- if (chainId === ChainID3.Solana) {
902
+ if (chainId === ChainID2.Solana) {
708
903
  log("Detected Solana chain");
709
904
  const result = await handleSolanaExecution({
710
905
  recipientAddress,
@@ -712,8 +907,9 @@ async function executeOrder({
712
907
  accountAddress,
713
908
  wallet: adapter,
714
909
  isSingleChain,
715
- deadline,
716
- update
910
+ update,
911
+ orderType,
912
+ options
717
913
  });
718
914
  log("Solana execution result:", result);
719
915
  return result;
@@ -723,8 +919,13 @@ async function executeOrder({
723
919
  log("Error:", unsupported);
724
920
  return { status: false, message: unsupported, stage: "error" };
725
921
  } catch (error) {
726
- const message = error instanceof BaseError ? error.shortMessage : error instanceof Error ? error.message : String(error);
727
- log("Execution failed:", { message, error });
922
+ let message = "An unknown error occurred";
923
+ if (error && typeof error === "object") {
924
+ const err = error;
925
+ message = err.details ?? err.message ?? message;
926
+ } else if (typeof error === "string") {
927
+ message = error;
928
+ }
728
929
  update("error", message);
729
930
  return { status: false, message, stage: "error" };
730
931
  }
@@ -735,353 +936,497 @@ function normalizeWallet(wallet) {
735
936
  return wallet;
736
937
  }
737
938
 
738
- // src/react/useExecuteOrder.ts
739
- function useExecuteOrder() {
740
- const [status, setStatus] = useState2("processing");
741
- const [message, setMessage] = useState2(null);
742
- const [loading, setLoading] = useState2(false);
743
- const [data, setData] = useState2(null);
744
- const [error, setError] = useState2(null);
745
- const isMounted = useRef2(true);
939
+ // src/core/client.ts
940
+ var SwapSDK = class {
941
+ constructor(config) {
942
+ __publicField(this, "apiKey");
943
+ /**
944
+ * Fetches metadata for one or more tokens from the Shogun Token Search API.
945
+ *
946
+ * ---
947
+ * ### Overview
948
+ * `getTokensData` retrieves normalized token information — such as symbol, name,
949
+ * decimals, logo URI, and verified status — for a given list of token addresses.
950
+ *
951
+ * It supports both **EVM** and **SVM (Solana)** tokens, returning metadata from
952
+ * Shogun’s unified token registry.
953
+ *
954
+ * ---
955
+ * @example
956
+ * ```ts
957
+ * const tokens = await getTokensData([
958
+ * "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
959
+ * "So11111111111111111111111111111111111111112", // SOL
960
+ * ]);
961
+ *
962
+ * console.log(tokens);
963
+ * [
964
+ * { symbol: "USDC", name: "USD Coin", chainId: 1, decimals: 6, ... },
965
+ * { symbol: "SOL", name: "Solana", chainId: 101, decimals: 9, ... }
966
+ * ]
967
+ * ```
968
+ *
969
+ * @param addresses - An array of token addresses (EVM or SVM) to fetch metadata for.
970
+ * @returns A promise resolving to an array of {@link TokenInfo} objects.
971
+ *
972
+ * @throws Will throw an error if the network request fails or the API responds with a non-OK status.
973
+ */
974
+ __publicField(this, "getTokensData", getTokensData.bind(this));
975
+ if (!config.apiKey) {
976
+ throw new Error("SwapSDK: Missing API key");
977
+ }
978
+ this.apiKey = config.apiKey;
979
+ if (this.apiKey) void this.apiKey;
980
+ }
981
+ /**
982
+ * Retrieves a swap quote for the given input and output tokens.
983
+ *
984
+ * @param params - Quote parameters including source/destination tokens and amount.
985
+ * @returns A normalized `SwapQuoteResponse` containing output amount, route, and metadata.
986
+ */
987
+ async getQuote(params) {
988
+ return getQuote(params);
989
+ }
990
+ /**
991
+ * Fetches token balances for the specified user wallet(s).
992
+ *
993
+ * Supports both EVM and SVM (Solana) wallet addresses.
994
+ *
995
+ * @param params - Wallet address and optional chain filters.
996
+ * @param options - Optional abort signal for cancellation.
997
+ * @returns A unified balance response with per-chain token details.
998
+ */
999
+ async getBalances(params, options) {
1000
+ return getBalances(params, options);
1001
+ }
1002
+ /**
1003
+ * Retrieves a list of verified tokens based on search query or chain filter.
1004
+ *
1005
+ * @param params - Search parameters (query, chain ID, pagination options).
1006
+ * @returns Paginated `TokenSearchResponse` containing token metadata.
1007
+ */
1008
+ async getTokenList(params) {
1009
+ return getTokenList(params);
1010
+ }
1011
+ /**
1012
+ * Executes a prepared swap quote using the provided wallet and configuration.
1013
+ *
1014
+ * Handles:
1015
+ * - Token approval (if required)
1016
+ * - Transaction signing and broadcasting
1017
+ * - Confirmation polling and stage-based status updates
1018
+ *
1019
+ * Supports both:
1020
+ * - Market orders (with optional deadline)
1021
+ * - Limit orders (requires executionPrice and deadline)
1022
+ *
1023
+ * @param quote - The swap quote to execute, containing route and metadata.
1024
+ * @param accountAddress - The user's wallet address executing the swap.
1025
+ * @param recipientAddress - Optional recipient address for the output tokens (defaults to sender).
1026
+ * @param wallet - Adapted wallet instance (EVM/Solana) or a standard Viem `WalletClient`.
1027
+ * @param onStatus - Optional callback for receiving execution stage updates and messages.
1028
+ * @param orderType - Defines whether this is a market or limit order.
1029
+ * @param options - Execution parameters (different per order type).
1030
+ *
1031
+ * @returns A finalized execution result containing transaction hash, status, and any returned data.
1032
+ *
1033
+ * @example
1034
+ * ```ts
1035
+ * * Market order
1036
+ * const result = await sdk.executeTransaction({
1037
+ * quote,
1038
+ * accountAddress: "0x123...",
1039
+ * wallet,
1040
+ * orderType: OrderExecutionType.MARKET,
1041
+ * options: { deadline: 1800 },
1042
+ * onStatus: (stage, msg) => console.log(stage, msg),
1043
+ * });
1044
+ *
1045
+ * * Limit order
1046
+ * const result = await sdk.executeTransaction({
1047
+ * quote,
1048
+ * accountAddress: "0x123...",
1049
+ * wallet,
1050
+ * orderType: OrderExecutionType.LIMIT,
1051
+ * options: { executionPrice: "0.0021", deadline: 3600 },
1052
+ * });
1053
+ * ```
1054
+ */
1055
+ async executeTransaction({
1056
+ quote,
1057
+ accountAddress,
1058
+ recipientAddress,
1059
+ wallet,
1060
+ onStatus,
1061
+ orderType,
1062
+ options
1063
+ }) {
1064
+ return executeOrder({
1065
+ quote,
1066
+ wallet,
1067
+ accountAddress,
1068
+ recipientAddress,
1069
+ onStatus,
1070
+ orderType,
1071
+ options
1072
+ });
1073
+ }
1074
+ };
1075
+
1076
+ // src/react/SwapProvider.tsx
1077
+ import { jsx } from "react/jsx-runtime";
1078
+ var SwapContext = createContext(null);
1079
+ var SwapProvider = ({ config, children }) => {
1080
+ const sdk = useMemo(() => new SwapSDK(config), [config.apiKey]);
1081
+ return /* @__PURE__ */ jsx(SwapContext.Provider, { value: sdk, children });
1082
+ };
1083
+ var useSwap = () => {
1084
+ const ctx = useContext(SwapContext);
1085
+ if (!ctx) {
1086
+ throw new Error("useSwap must be used within <SwapProvider>");
1087
+ }
1088
+ return ctx;
1089
+ };
1090
+
1091
+ // src/react/useQuote.ts
1092
+ import { useCallback, useEffect, useMemo as useMemo2, useRef } from "react";
1093
+ import useSWR, { mutate } from "swr";
1094
+ var QUOTE_KEY = "quote-global";
1095
+ function isValidQuoteParams(p) {
1096
+ if (!p) return false;
1097
+ const { tokenIn, tokenOut, amount } = p;
1098
+ if (!tokenIn || !tokenOut || !amount) return false;
1099
+ const n = Number(amount);
1100
+ return Number.isFinite(n) && n > 0;
1101
+ }
1102
+ function useQuote(initialParams) {
1103
+ const sdk = useSwap();
1104
+ const abortRef = useRef(null);
1105
+ const paramsRef = globalThis.__QUOTE_PARAMS_REF__ ?? (globalThis.__QUOTE_PARAMS_REF__ = { current: initialParams ?? null });
1106
+ useEffect(() => {
1107
+ if (initialParams && JSON.stringify(paramsRef.current) !== JSON.stringify(initialParams)) {
1108
+ paramsRef.current = initialParams;
1109
+ void mutate(QUOTE_KEY);
1110
+ }
1111
+ }, [initialParams]);
1112
+ const fetcher = useCallback(async () => {
1113
+ const activeParams = paramsRef.current;
1114
+ if (!isValidQuoteParams(activeParams)) return null;
1115
+ if (abortRef.current) abortRef.current.abort();
1116
+ const controller = new AbortController();
1117
+ abortRef.current = controller;
1118
+ try {
1119
+ return await sdk.getQuote(activeParams);
1120
+ } catch (err) {
1121
+ if (err instanceof DOMException && err.name === "AbortError") return null;
1122
+ if (err instanceof Error) throw err;
1123
+ throw new Error(String(err));
1124
+ }
1125
+ }, [sdk]);
1126
+ const {
1127
+ data,
1128
+ error,
1129
+ isValidating: loading,
1130
+ mutate: mutateQuote
1131
+ } = useSWR(QUOTE_KEY, fetcher, {
1132
+ revalidateOnFocus: false,
1133
+ shouldRetryOnError: false,
1134
+ keepPreviousData: true
1135
+ });
1136
+ const refetch = useCallback(async () => {
1137
+ await mutateQuote();
1138
+ }, [mutateQuote]);
1139
+ const setParams = useCallback(
1140
+ (next) => {
1141
+ paramsRef.current = next;
1142
+ void mutate(QUOTE_KEY);
1143
+ },
1144
+ []
1145
+ );
1146
+ return useMemo2(
1147
+ () => ({
1148
+ data,
1149
+ loading,
1150
+ error: error instanceof Error ? error.message : null,
1151
+ refetch,
1152
+ setParams,
1153
+ get activeParams() {
1154
+ return paramsRef.current;
1155
+ }
1156
+ }),
1157
+ [data, loading, error, refetch, setParams]
1158
+ );
1159
+ }
1160
+
1161
+ // src/react/useExecuteTransaction.ts
1162
+ import { useState, useCallback as useCallback3 } from "react";
1163
+
1164
+ // src/react/useBalances.ts
1165
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2 } from "react";
1166
+ import useSWR2, { mutate as mutate2 } from "swr";
1167
+ var BALANCE_KEY = "balances-global";
1168
+ function useBalances(initialParams) {
1169
+ const sdk = useSwap();
1170
+ const abortRef = useRef2(null);
1171
+ const paramsRef = globalThis.__BALANCES_PARAMS_REF__ ?? (globalThis.__BALANCES_PARAMS_REF__ = { current: initialParams ?? null });
1172
+ const fetcher = useCallback2(async () => {
1173
+ const activeParams = paramsRef.current;
1174
+ if (!activeParams) return null;
1175
+ if (abortRef.current) abortRef.current.abort();
1176
+ const controller = new AbortController();
1177
+ abortRef.current = controller;
1178
+ try {
1179
+ return await sdk.getBalances(activeParams, { signal: controller.signal });
1180
+ } catch (err) {
1181
+ if (err instanceof DOMException && err.name === "AbortError") return null;
1182
+ if (err instanceof Error) throw err;
1183
+ throw new Error(String(err));
1184
+ }
1185
+ }, [sdk, paramsRef]);
1186
+ const {
1187
+ data,
1188
+ error,
1189
+ isValidating: loading,
1190
+ mutate: mutateBalances
1191
+ } = useSWR2(BALANCE_KEY, fetcher, {
1192
+ revalidateOnFocus: false,
1193
+ shouldRetryOnError: false,
1194
+ keepPreviousData: true
1195
+ });
1196
+ const refetch = useCallback2(async () => {
1197
+ await mutateBalances();
1198
+ }, [mutateBalances]);
1199
+ const setParams = useCallback2((next) => {
1200
+ paramsRef.current = next;
1201
+ void mutate2(BALANCE_KEY);
1202
+ }, [paramsRef]);
746
1203
  useEffect2(() => {
747
- return () => {
748
- isMounted.current = false;
749
- };
750
- }, []);
751
- const execute = useCallback(
1204
+ if (initialParams) {
1205
+ const prevParams = paramsRef.current;
1206
+ const paramsChanged = !prevParams || prevParams.addresses.svm !== initialParams.addresses.svm || prevParams.addresses.evm !== initialParams.addresses.evm;
1207
+ if (paramsChanged) {
1208
+ paramsRef.current = initialParams;
1209
+ void mutate2(BALANCE_KEY);
1210
+ }
1211
+ }
1212
+ }, [initialParams, paramsRef]);
1213
+ return useMemo3(
1214
+ () => ({
1215
+ data,
1216
+ loading,
1217
+ error: error instanceof Error ? error : null,
1218
+ refetch,
1219
+ setParams,
1220
+ get activeParams() {
1221
+ return paramsRef.current;
1222
+ }
1223
+ }),
1224
+ [data, loading, error, refetch, setParams, paramsRef]
1225
+ );
1226
+ }
1227
+
1228
+ // src/react/useExecuteTransaction.ts
1229
+ function useExecuteTransaction() {
1230
+ const sdk = useSwap();
1231
+ const [isLoading, setLoading] = useState(false);
1232
+ const [stage, setStage] = useState(null);
1233
+ const [message, setMessage] = useState(null);
1234
+ const [error, setError] = useState(null);
1235
+ const [result, setResult] = useState(null);
1236
+ const { refetch: refetchQuote } = useQuote();
1237
+ const { refetch: refetchBalances } = useBalances();
1238
+ const execute = useCallback3(
752
1239
  async ({
753
1240
  quote,
754
1241
  accountAddress,
755
1242
  recipientAddress,
756
1243
  wallet,
757
- deadline
1244
+ orderType,
1245
+ options
758
1246
  }) => {
759
- if (!quote || !wallet) {
760
- throw new Error("Quote and wallet are required for order execution.");
761
- }
762
1247
  setLoading(true);
763
1248
  setError(null);
764
- setData(null);
765
- setMessage(null);
1249
+ setResult(null);
1250
+ const onStatus = (s, msg) => {
1251
+ setStage(s);
1252
+ setMessage(msg ?? "");
1253
+ };
766
1254
  try {
767
- const effectiveDeadline = deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
768
- const onStatus = (stage, msg) => {
769
- if (!isMounted.current) return;
770
- setStatus(stage);
771
- if (msg) setMessage(msg);
772
- };
773
- const result = await executeOrder({
1255
+ const res = await sdk.executeTransaction({
774
1256
  quote,
1257
+ wallet,
775
1258
  accountAddress,
776
1259
  recipientAddress,
777
- wallet,
778
1260
  onStatus,
779
- options: { deadline: effectiveDeadline }
1261
+ orderType,
1262
+ options
780
1263
  });
781
- if (!isMounted.current) return result;
782
- setData(result);
783
- setStatus(result.stage);
784
- setMessage("Order executed successfully");
785
- return result;
786
- } catch (err) {
787
- const errorObj = err instanceof Error ? err : new Error(String(err));
788
- if (isMounted.current) {
789
- setError(errorObj);
790
- setStatus("error");
791
- setMessage(errorObj.message);
792
- setData({
793
- status: false,
794
- stage: "error",
795
- message: errorObj.message
1264
+ if (res && typeof res === "object" && "status" in res && "stage" in res && typeof res.stage === "string") {
1265
+ setResult({
1266
+ ...res,
1267
+ stage: res.stage
796
1268
  });
1269
+ } else {
1270
+ setResult(res);
797
1271
  }
798
- return {
799
- status: false,
800
- stage: "error",
801
- message: errorObj.message
802
- };
1272
+ return res;
1273
+ } catch (err) {
1274
+ const msg = err instanceof Error ? err.message : String(err);
1275
+ setError(msg);
1276
+ throw err;
803
1277
  } finally {
804
- if (isMounted.current) setLoading(false);
1278
+ await Promise.allSettled([refetchQuote(), refetchBalances()]);
1279
+ setLoading(false);
805
1280
  }
806
1281
  },
807
- []
1282
+ [sdk, refetchQuote, refetchBalances]
808
1283
  );
809
1284
  return {
810
- /** Executes the swap order. */
811
1285
  execute,
812
- /** Current execution stage. */
813
- status,
814
- /** Human-readable status message. */
1286
+ isLoading,
1287
+ stage,
815
1288
  message,
816
- /** Whether execution is ongoing. */
817
- loading,
818
- /** Raw SDK response data. */
819
- data,
820
- /** Captured error (if execution failed). */
821
- error
1289
+ error,
1290
+ result
822
1291
  };
823
1292
  }
824
1293
 
825
- // src/react/useQuote.ts
826
- import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
827
-
828
- // src/core/getQuote.ts
829
- import { QuoteProvider } from "@shogun-sdk/intents-sdk";
830
- import { parseUnits } from "viem";
831
- async function getQuote(params) {
832
- if (!params.tokenIn?.address || !params.tokenOut?.address) {
833
- throw new Error("Both tokenIn and tokenOut must include an address.");
834
- }
835
- if (!params.sourceChainId || !params.destChainId) {
836
- throw new Error("Both sourceChainId and destChainId are required.");
837
- }
838
- if (params.amount <= 0n) {
839
- throw new Error("Amount must be greater than 0.");
840
- }
841
- const normalizedTokenIn = normalizeNative(params.sourceChainId, params.tokenIn.address);
842
- const data = await QuoteProvider.getQuote({
843
- sourceChainId: params.sourceChainId,
844
- destChainId: params.destChainId,
845
- tokenIn: normalizedTokenIn,
846
- tokenOut: params.tokenOut.address,
847
- amount: params.amount
848
- });
849
- const slippagePercent = Math.min(Math.max(params.slippage ?? 0.5, 0), 50);
850
- let warning;
851
- if (slippagePercent > 10) {
852
- warning = `\u26A0\uFE0F High slippage tolerance (${slippagePercent.toFixed(2)}%) \u2014 price may vary significantly.`;
853
- }
854
- const estimatedAmountOut = BigInt(data.estimatedAmountOut);
855
- const slippageBps = BigInt(Math.round(slippagePercent * 100));
856
- const estimatedAmountOutAfterSlippage = estimatedAmountOut * (10000n - slippageBps) / 10000n;
857
- const pricePerTokenOutInUsd = data.estimatedAmountOutUsd / Number(data.estimatedAmountOut);
858
- const amountOutUsdAfterSlippage = Number(estimatedAmountOutAfterSlippage) * pricePerTokenOutInUsd;
859
- const minStablecoinsAmountValue = BigInt(data.estimatedAmountInAsMinStablecoinAmount);
860
- const minStablecoinsAmountAfterSlippage = minStablecoinsAmountValue * (10000n - slippageBps) / 10000n;
861
- const pricePerInputToken = estimatedAmountOut * 10n ** BigInt(params.tokenIn.decimals ?? 18) / BigInt(params.amount);
862
- return {
863
- amountOut: estimatedAmountOutAfterSlippage,
864
- amountOutUsd: amountOutUsdAfterSlippage,
865
- amountInUsd: data.amountInUsd,
866
- // Input USD stays the same
867
- minStablecoinsAmount: minStablecoinsAmountAfterSlippage,
868
- tokenIn: {
869
- address: params.tokenIn.address,
870
- decimals: params.tokenIn.decimals ?? 18,
871
- chainId: params.sourceChainId
872
- },
873
- tokenOut: {
874
- address: params.tokenOut.address,
875
- decimals: params.tokenOut.decimals ?? 18,
876
- chainId: params.destChainId
877
- },
878
- amountIn: params.amount,
879
- pricePerInputToken,
880
- slippage: slippagePercent,
881
- internal: {
882
- ...data,
883
- estimatedAmountOutReduced: estimatedAmountOutAfterSlippage,
884
- estimatedAmountOutUsdReduced: amountOutUsdAfterSlippage
1294
+ // src/react/useTokenList.ts
1295
+ import { useRef as useRef3, useState as useState2, useCallback as useCallback4 } from "react";
1296
+ function useTokenList() {
1297
+ const [tokens, setTokens] = useState2([]);
1298
+ const [loading, setLoading] = useState2(false);
1299
+ const [error, setError] = useState2(null);
1300
+ const [hasMore, setHasMore] = useState2(true);
1301
+ const [page, setPage] = useState2(1);
1302
+ const sdk = useSwap();
1303
+ const pageRef = useRef3(1);
1304
+ const hasMoreRef = useRef3(true);
1305
+ const lastQuery = useRef3({});
1306
+ const cache = useRef3(/* @__PURE__ */ new Map());
1307
+ const isLoadingRef = useRef3(false);
1308
+ pageRef.current = page;
1309
+ hasMoreRef.current = hasMore;
1310
+ const loadTokens = useCallback4(
1311
+ async (params) => {
1312
+ const { q, networkId, reset } = params;
1313
+ if (isLoadingRef.current && !reset) return;
1314
+ try {
1315
+ let currentPage = pageRef.current;
1316
+ if (reset) {
1317
+ currentPage = 1;
1318
+ setTokens([]);
1319
+ setPage(1);
1320
+ setHasMore(true);
1321
+ pageRef.current = 1;
1322
+ hasMoreRef.current = true;
1323
+ }
1324
+ if (!reset && !hasMoreRef.current) return;
1325
+ isLoadingRef.current = true;
1326
+ setLoading(true);
1327
+ setError(null);
1328
+ const cacheKey = JSON.stringify({
1329
+ q: q?.toLowerCase() ?? "",
1330
+ networkId: networkId ?? void 0,
1331
+ page: currentPage
1332
+ });
1333
+ if (cache.current.has(cacheKey)) {
1334
+ const cached = cache.current.get(cacheKey);
1335
+ setTokens((prev) => reset ? cached.results : [...prev, ...cached.results]);
1336
+ if (!reset && cached.results.length > 0) {
1337
+ const nextPage = currentPage + 1;
1338
+ setPage(nextPage);
1339
+ pageRef.current = nextPage;
1340
+ }
1341
+ isLoadingRef.current = false;
1342
+ setLoading(false);
1343
+ return;
1344
+ }
1345
+ const apiParams = {
1346
+ q,
1347
+ page: currentPage,
1348
+ limit: 20,
1349
+ ...networkId !== void 0 ? { networkId } : {}
1350
+ };
1351
+ const res = await sdk.getTokenList(apiParams);
1352
+ cache.current.set(cacheKey, res);
1353
+ setTokens((prev) => reset ? res.results : [...prev, ...res.results]);
1354
+ const isLastPage = res.results.length === 0 || res.results.length < 20 || res.count && currentPage * 20 >= res.count;
1355
+ setHasMore(!isLastPage);
1356
+ hasMoreRef.current = !isLastPage;
1357
+ if (!reset && !isLastPage) {
1358
+ const nextPage = currentPage + 1;
1359
+ setPage(nextPage);
1360
+ pageRef.current = nextPage;
1361
+ }
1362
+ lastQuery.current = { q, networkId };
1363
+ } catch (e) {
1364
+ console.error("useTokenList error:", e);
1365
+ setError("Failed to load tokens");
1366
+ } finally {
1367
+ setLoading(false);
1368
+ isLoadingRef.current = false;
1369
+ }
885
1370
  },
886
- warning
887
- };
888
- }
889
-
890
- // src/react/useQuote.ts
891
- function useQuote(params, options) {
892
- const [data, setData] = useState3(null);
893
- const [loading, setLoading] = useState3(false);
894
- const [error, setError] = useState3(null);
895
- const [warning, setWarning] = useState3(null);
896
- const debounceMs = options?.debounceMs ?? 250;
897
- const autoRefreshMs = options?.autoRefreshMs;
898
- const abortRef = useRef3(null);
899
- const debounceRef = useRef3(null);
900
- const mounted = useRef3(false);
901
- const paramsKey = useMemo2(
902
- () => params ? JSON.stringify(serializeBigIntsToStrings(params)) : null,
903
- [params]
1371
+ [sdk]
904
1372
  );
905
- useEffect3(() => {
906
- mounted.current = true;
907
- return () => {
908
- mounted.current = false;
909
- abortRef.current?.abort();
910
- if (debounceRef.current) clearTimeout(debounceRef.current);
911
- };
1373
+ const resetTokens = useCallback4(() => {
1374
+ setTokens([]);
1375
+ setPage(1);
1376
+ setHasMore(true);
1377
+ pageRef.current = 1;
1378
+ hasMoreRef.current = true;
1379
+ cache.current.clear();
1380
+ lastQuery.current = {};
912
1381
  }, []);
913
- const fetchQuote = useCallback2(async () => {
914
- if (!params) return;
915
- try {
916
- setLoading(true);
917
- setWarning(null);
918
- const result = await getQuote(params);
919
- const serializeResult = serializeBigIntsToStrings(result);
920
- if (!mounted.current) return;
921
- setData((prev) => {
922
- if (JSON.stringify(prev) === JSON.stringify(serializeResult)) return prev;
923
- return serializeResult;
924
- });
925
- setWarning(result.warning ?? null);
926
- setError(null);
927
- } catch (err) {
928
- if (err.name === "AbortError") return;
929
- console.error("[useQuote] fetch error:", err);
930
- if (mounted.current) setError(err instanceof Error ? err : new Error(String(err)));
931
- } finally {
932
- if (mounted.current) setLoading(false);
933
- }
934
- }, [paramsKey]);
935
- useEffect3(() => {
936
- if (!paramsKey) return;
937
- if (debounceRef.current) clearTimeout(debounceRef.current);
938
- debounceRef.current = setTimeout(() => {
939
- fetchQuote();
940
- }, debounceMs);
941
- return () => {
942
- if (debounceRef.current) clearTimeout(debounceRef.current);
943
- abortRef.current?.abort();
944
- };
945
- }, [paramsKey, debounceMs, fetchQuote]);
946
- useEffect3(() => {
947
- if (!autoRefreshMs || !paramsKey) return;
948
- const interval = setInterval(() => fetchQuote(), autoRefreshMs);
949
- return () => clearInterval(interval);
950
- }, [autoRefreshMs, paramsKey, fetchQuote]);
951
- return useMemo2(
952
- () => ({
953
- data,
954
- loading,
955
- error,
956
- warning,
957
- refetch: fetchQuote
958
- }),
959
- [data, loading, error, warning, fetchQuote]
960
- );
961
- }
962
-
963
- // src/react/useBalances.ts
964
- import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
965
-
966
- // src/core/getBalances.ts
967
- import { TOKEN_SEARCH_API_BASE_URL } from "@shogun-sdk/intents-sdk";
968
- async function getBalances(params, options) {
969
- const { addresses, cursorEvm, cursorSvm } = params;
970
- const { signal } = options ?? {};
971
- if (!addresses?.evm && !addresses?.svm) {
972
- throw new Error("At least one address (EVM or SVM) must be provided.");
973
- }
974
- const payload = JSON.stringify({
975
- addresses,
976
- cursorEvm,
977
- cursorSvm
978
- });
979
- const start = performance.now();
980
- const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL}/tokens/balances`, {
981
- method: "POST",
982
- headers: {
983
- accept: "application/json",
984
- "Content-Type": "application/json"
985
- },
986
- body: payload,
987
- signal
988
- }).catch((err) => {
989
- if (err.name === "AbortError") {
990
- throw new Error("Balance request was cancelled.");
991
- }
992
- throw err;
993
- });
994
- if (!response.ok) {
995
- const text = await response.text().catch(() => "");
996
- throw new Error(`Failed to fetch balances: ${response.status} ${text}`);
997
- }
998
- const data = await response.json().catch(() => {
999
- throw new Error("Invalid JSON response from balances API.");
1000
- });
1001
- const duration = (performance.now() - start).toFixed(1);
1002
- if (process.env.NODE_ENV !== "production") {
1003
- console.debug(`[Shogun SDK] Fetched balances in ${duration}ms`);
1004
- }
1005
- const evmItems = data.evm?.items ?? [];
1006
- const svmItems = data.svm?.items ?? [];
1007
- const combined = [...evmItems, ...svmItems];
1008
1382
  return {
1009
- results: combined,
1010
- nextCursorEvm: data.evm?.cursor ?? null,
1011
- nextCursorSvm: data.svm?.cursor ?? null
1383
+ tokens,
1384
+ loading,
1385
+ error,
1386
+ hasMore,
1387
+ page,
1388
+ lastQuery: lastQuery.current,
1389
+ loadTokens,
1390
+ resetTokens
1012
1391
  };
1013
1392
  }
1014
1393
 
1015
- // src/react/useBalances.ts
1016
- function useBalances(params) {
1017
- const [data, setData] = useState4(null);
1018
- const [loading, setLoading] = useState4(false);
1019
- const [error, setError] = useState4(null);
1020
- const abortRef = useRef4(null);
1021
- const stableParams = useMemo3(() => {
1022
- if (!params) return null;
1023
- const { addresses, cursorEvm, cursorSvm } = params;
1024
- return {
1025
- addresses: {
1026
- evm: addresses?.evm ?? void 0,
1027
- svm: addresses?.svm ?? void 0
1028
- },
1029
- cursorEvm,
1030
- cursorSvm
1031
- };
1032
- }, [params?.addresses?.evm, params?.addresses?.svm, params?.cursorEvm, params?.cursorSvm]);
1033
- const fetchBalances = useCallback3(async () => {
1034
- if (!stableParams) return;
1035
- if (abortRef.current) abortRef.current.abort();
1036
- const controller = new AbortController();
1037
- abortRef.current = controller;
1394
+ // src/react/useTokensData.ts
1395
+ import { useState as useState3, useEffect as useEffect3 } from "react";
1396
+ function useTokensData(addresses) {
1397
+ const sdk = useSwap();
1398
+ const [data, setData] = useState3([]);
1399
+ const [loading, setLoading] = useState3(false);
1400
+ const [error, setError] = useState3(null);
1401
+ useEffect3(() => {
1402
+ if (!addresses?.length) return;
1403
+ let isMounted = true;
1038
1404
  setLoading(true);
1039
1405
  setError(null);
1040
- try {
1041
- const result = await getBalances(stableParams, { signal: controller.signal });
1042
- setData((prev) => {
1043
- if (JSON.stringify(prev) === JSON.stringify(result)) return prev;
1044
- return result;
1045
- });
1046
- return result;
1047
- } catch (err) {
1048
- if (err.name === "AbortError") return;
1049
- const e = err instanceof Error ? err : new Error(String(err));
1050
- setError(e);
1051
- throw e;
1052
- } finally {
1053
- setLoading(false);
1054
- }
1055
- }, [stableParams]);
1056
- useEffect4(() => {
1057
- if (stableParams) fetchBalances().catch(() => {
1406
+ sdk.getTokensData(addresses).then((res) => {
1407
+ if (isMounted) setData(res);
1408
+ }).catch((e) => {
1409
+ if (isMounted) setError(e instanceof Error ? e : new Error(String(e)));
1410
+ }).finally(() => {
1411
+ if (isMounted) setLoading(false);
1058
1412
  });
1059
1413
  return () => {
1060
- if (abortRef.current) abortRef.current.abort();
1414
+ isMounted = false;
1061
1415
  };
1062
- }, [fetchBalances]);
1063
- return useMemo3(
1064
- () => ({
1065
- /** Latest fetched balance data */
1066
- data,
1067
- /** Whether the hook is currently fetching */
1068
- loading,
1069
- /** Error object if fetching failed */
1070
- error,
1071
- /** Manually trigger a refresh */
1072
- refetch: fetchBalances
1073
- }),
1074
- [data, loading, error, fetchBalances]
1075
- );
1416
+ }, [sdk, JSON.stringify(addresses)]);
1417
+ return { data, loading, error };
1076
1418
  }
1077
-
1078
- // src/react/index.ts
1079
- import { ChainID as ChainID4, isEvmChain as isEvmChain3 } from "@shogun-sdk/intents-sdk";
1080
1419
  export {
1081
- ChainID4 as ChainID,
1082
- isEvmChain3 as isEvmChain,
1420
+ ChainId,
1421
+ OrderExecutionType,
1422
+ SupportedChains,
1423
+ SwapProvider,
1424
+ buildQuoteParams,
1425
+ isEvmChain,
1083
1426
  useBalances,
1084
- useExecuteOrder,
1427
+ useExecuteTransaction,
1085
1428
  useQuote,
1086
- useTokenList
1429
+ useSwap,
1430
+ useTokenList,
1431
+ useTokensData
1087
1432
  };