@shogun-sdk/swap 0.0.2-test.25 → 0.0.2-test.27

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,88 +1,16 @@
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
88
16
  import { zeroAddress } from "viem";
@@ -102,11 +30,25 @@ function normalizeEvmTokenAddress(address) {
102
30
  }
103
31
 
104
32
  // src/utils/chain.ts
105
- import { ChainID } from "@shogun-sdk/intents-sdk";
106
- var SOLANA_CHAIN_ID = ChainID.Solana;
107
- 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 = [
108
50
  {
109
- id: ChainID.Arbitrum,
51
+ id: BaseChainID.Arbitrum,
110
52
  name: "Arbitrum",
111
53
  isEVM: true,
112
54
  wrapped: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -115,7 +57,7 @@ var SupportedChains = [
115
57
  tokenAddress: NATIVE_TOKEN.ETH
116
58
  },
117
59
  {
118
- id: ChainID.Optimism,
60
+ id: BaseChainID.Optimism,
119
61
  name: "Optimism",
120
62
  isEVM: true,
121
63
  wrapped: "0x4200000000000000000000000000000000000006",
@@ -124,7 +66,7 @@ var SupportedChains = [
124
66
  tokenAddress: NATIVE_TOKEN.ETH
125
67
  },
126
68
  {
127
- id: ChainID.Base,
69
+ id: BaseChainID.Base,
128
70
  name: "Base",
129
71
  isEVM: true,
130
72
  wrapped: "0x4200000000000000000000000000000000000006",
@@ -133,7 +75,7 @@ var SupportedChains = [
133
75
  tokenAddress: NATIVE_TOKEN.ETH
134
76
  },
135
77
  {
136
- id: ChainID.Hyperliquid,
78
+ id: BaseChainID.Hyperliquid,
137
79
  name: "Hyperliquid",
138
80
  isEVM: true,
139
81
  wrapped: "0x5555555555555555555555555555555555555555",
@@ -142,7 +84,7 @@ var SupportedChains = [
142
84
  tokenAddress: NATIVE_TOKEN.ETH
143
85
  },
144
86
  {
145
- id: ChainID.BSC,
87
+ id: BaseChainID.BSC,
146
88
  name: "BSC",
147
89
  isEVM: true,
148
90
  wrapped: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
@@ -160,6 +102,10 @@ var SupportedChains = [
160
102
  tokenAddress: NATIVE_TOKEN.SOL
161
103
  }
162
104
  ];
105
+ var SupportedChains = SupportedChainsInternal.filter(
106
+ (c) => CURRENT_SUPPORTED.includes(c.id)
107
+ );
108
+ var isEvmChain = isEvmChainIntent;
163
109
 
164
110
  // src/utils/viem.ts
165
111
  function isViemWalletClient(wallet) {
@@ -187,9 +133,197 @@ function serializeBigIntsToStrings(obj) {
187
133
  return obj;
188
134
  }
189
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
+
190
326
  // src/wallet-adapter/evm-wallet-adapter/adapter.ts
191
- import { utils as ethersUtils } from "ethers/lib/ethers.js";
192
- import { hexValue } from "ethers/lib/utils.js";
193
327
  import {
194
328
  custom,
195
329
  publicActions
@@ -279,26 +413,14 @@ var adaptViemWallet = (wallet) => {
279
413
  };
280
414
  };
281
415
 
282
- // src/core/executeOrder/handleEvmExecution.ts
416
+ // src/core/execute/handleEvmExecution.ts
283
417
  import {
284
418
  getEVMSingleChainOrderTypedData,
285
419
  getEVMCrossChainOrderTypedData
286
420
  } from "@shogun-sdk/intents-sdk";
287
421
  import { encodeFunctionData as encodeFunctionData2 } from "viem";
288
422
 
289
- // src/core/executeOrder/normalizeNative.ts
290
- import { isEvmChain } from "@shogun-sdk/intents-sdk";
291
- function normalizeNative(chainId, address) {
292
- if (isEvmChain(chainId) && isNativeAddress(address)) {
293
- const chain = SupportedChains.find((c) => c.id === chainId);
294
- if (!chain?.wrapped)
295
- throw new Error(`Wrapped token not found for chainId ${chainId}`);
296
- return chain.wrapped;
297
- }
298
- return address;
299
- }
300
-
301
- // src/core/executeOrder/stageMessages.ts
423
+ // src/core/execute/stageMessages.ts
302
424
  var DEFAULT_STAGE_MESSAGES = {
303
425
  processing: "Preparing transaction for execution",
304
426
  approving: "Approving token allowance",
@@ -307,20 +429,44 @@ var DEFAULT_STAGE_MESSAGES = {
307
429
  submitting: "Submitting transaction",
308
430
  initiated: "Transaction initiated.",
309
431
  success: "Transaction Executed successfully",
432
+ success_limit: "Limit order has been submitted successfully.",
310
433
  shogun_processing: "Shogun is processing your transaction",
311
434
  error: "Transaction failed during submission"
312
435
  };
313
436
 
314
- // src/core/executeOrder/buildOrder.ts
437
+ // src/core/execute/buildOrder.ts
315
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
316
449
  async function buildOrder({
317
450
  quote,
318
451
  accountAddress,
319
452
  destination,
320
453
  deadline,
321
- isSingleChain
454
+ isSingleChain,
455
+ orderType,
456
+ options
322
457
  }) {
323
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
+ }
324
470
  if (isSingleChain) {
325
471
  return await SingleChainOrder.create({
326
472
  user: accountAddress,
@@ -328,7 +474,7 @@ async function buildOrder({
328
474
  tokenIn: tokenIn.address,
329
475
  tokenOut: tokenOut.address,
330
476
  amountIn: quote.amountIn,
331
- amountOutMin: quote.internal.estimatedAmountOutReduced,
477
+ amountOutMin,
332
478
  deadline,
333
479
  destinationAddress: destination
334
480
  });
@@ -342,7 +488,7 @@ async function buildOrder({
342
488
  destinationTokenAddress: tokenOut.address,
343
489
  destinationAddress: destination,
344
490
  deadline,
345
- destinationTokenMinAmount: quote.internal.estimatedAmountOutReduced,
491
+ destinationTokenMinAmount: amountOutMin,
346
492
  minStablecoinAmount: quote.minStablecoinsAmount
347
493
  });
348
494
  }
@@ -413,7 +559,7 @@ async function pollOrderStatus(address, orderId, options = {}) {
413
559
  });
414
560
  }
415
561
 
416
- // src/core/executeOrder/handleOrderPollingResult.ts
562
+ // src/core/execute/handleOrderPollingResult.ts
417
563
  async function handleOrderPollingResult({
418
564
  status,
419
565
  orderId,
@@ -451,7 +597,7 @@ async function handleOrderPollingResult({
451
597
  };
452
598
  }
453
599
 
454
- // src/core/executeOrder/ensurePermit2Allowance.ts
600
+ // src/core/execute/ensurePermit2Allowance.ts
455
601
  import { encodeFunctionData, erc20Abi, maxUint256 } from "viem";
456
602
  import { PERMIT2_ADDRESS } from "@shogun-sdk/intents-sdk";
457
603
  async function ensurePermit2Allowance({
@@ -496,7 +642,7 @@ async function ensurePermit2Allowance({
496
642
  );
497
643
  }
498
644
 
499
- // src/core/executeOrder/handleEvmExecution.ts
645
+ // src/core/execute/handleEvmExecution.ts
500
646
  async function handleEvmExecution({
501
647
  recipientAddress,
502
648
  quote,
@@ -504,16 +650,19 @@ async function handleEvmExecution({
504
650
  accountAddress,
505
651
  wallet,
506
652
  isSingleChain,
507
- deadline,
508
- update
653
+ update,
654
+ orderType,
655
+ options
509
656
  }) {
510
- 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;
511
659
  await wallet.switchChain(chainId);
512
660
  const tokenIn = normalizeNative(chainId, quote.tokenIn.address);
513
661
  quote.tokenOut.address = normalizeEvmTokenAddress(quote.tokenOut.address);
514
662
  const shouldWrapNative = isNativeAddress(quote.tokenIn.address);
515
663
  update("processing", shouldWrapNative ? `${messageFor("processing")} (wrapping native token)` : messageFor("processing"));
516
664
  if (shouldWrapNative) {
665
+ quote.tokenIn.address === tokenIn;
517
666
  await wallet.sendTransaction({
518
667
  to: tokenIn,
519
668
  data: encodeFunctionData2({
@@ -540,7 +689,9 @@ async function handleEvmExecution({
540
689
  accountAddress,
541
690
  destination,
542
691
  deadline,
543
- isSingleChain
692
+ isSingleChain,
693
+ orderType,
694
+ options
544
695
  });
545
696
  console.debug(`order`, order);
546
697
  update("processing", messageFor("signing"));
@@ -564,17 +715,28 @@ async function handleEvmExecution({
564
715
  update("initiated", messageFor("initiated"));
565
716
  const { intentId: orderId } = res.data;
566
717
  update("initiated", messageFor("shogun_processing"));
567
- const status = await pollOrderStatus(accountAddress, orderId);
568
- return await handleOrderPollingResult({
569
- status,
570
- orderId,
571
- chainId,
572
- update,
573
- messageFor
574
- });
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
+ }
575
737
  }
576
738
 
577
- // src/core/executeOrder/handleSolanaExecution.ts
739
+ // src/core/execute/handleSolanaExecution.ts
578
740
  import {
579
741
  getSolanaSingleChainOrderInstructions,
580
742
  getSolanaCrossChainOrderInstructions
@@ -587,12 +749,14 @@ async function handleSolanaExecution({
587
749
  isSingleChain,
588
750
  update,
589
751
  accountAddress,
590
- deadline
752
+ orderType,
753
+ options
591
754
  }) {
592
755
  if (!wallet.rpcUrl) {
593
756
  throw new Error("Solana wallet is missing rpcUrl");
594
757
  }
595
- 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] ?? "";
596
760
  update("processing", messageFor("processing"));
597
761
  const destination = recipientAddress ?? accountAddress;
598
762
  const order = await buildOrder({
@@ -600,7 +764,9 @@ async function handleSolanaExecution({
600
764
  accountAddress,
601
765
  destination,
602
766
  deadline,
603
- isSingleChain
767
+ isSingleChain,
768
+ orderType,
769
+ options
604
770
  });
605
771
  const txData = await getSolanaOrderInstructions({
606
772
  order,
@@ -620,16 +786,27 @@ async function handleSolanaExecution({
620
786
  throw new Error("Auctioneer submission failed");
621
787
  }
622
788
  update("initiated", messageFor("initiated"));
623
- const { jwt, intentId: orderId } = response.data;
789
+ const { intentId: orderId } = response.data;
624
790
  update("initiated", messageFor("shogun_processing"));
625
- const status = await pollOrderStatus(jwt, orderId);
626
- return await handleOrderPollingResult({
627
- status,
628
- orderId,
629
- chainId: SOLANA_CHAIN_ID,
630
- update,
631
- messageFor
632
- });
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(accountAddress, orderId);
802
+ return await handleOrderPollingResult({
803
+ status,
804
+ orderId,
805
+ chainId: SOLANA_CHAIN_ID,
806
+ update,
807
+ messageFor
808
+ });
809
+ }
633
810
  }
634
811
  async function getSolanaOrderInstructions({
635
812
  order,
@@ -662,13 +839,14 @@ async function submitToAuctioneer({
662
839
  });
663
840
  }
664
841
 
665
- // src/core/executeOrder/execute.ts
842
+ // src/core/execute/execute.ts
666
843
  async function executeOrder({
667
844
  quote,
668
845
  accountAddress,
669
846
  recipientAddress,
670
847
  wallet,
671
848
  onStatus,
849
+ orderType = "market" /* MARKET */,
672
850
  options = {}
673
851
  }) {
674
852
  const isDev = process.env.NODE_ENV !== "production";
@@ -681,21 +859,31 @@ async function executeOrder({
681
859
  onStatus?.(stage, message ?? messageFor(stage));
682
860
  };
683
861
  try {
684
- const deadline = options.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
685
862
  log("Starting execution:", {
686
863
  accountAddress,
687
864
  recipientAddress,
688
- deadline,
689
865
  tokenIn: quote?.tokenIn,
690
866
  tokenOut: quote?.tokenOut
691
867
  });
692
868
  const adapter = normalizeWallet(wallet);
693
869
  if (!adapter) throw new Error("No wallet provided");
694
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
+ }
695
883
  const isSingleChain = tokenIn.chainId === tokenOut.chainId;
696
884
  const chainId = Number(tokenIn.chainId);
697
885
  update("processing");
698
- if (isEvmChain2(chainId)) {
886
+ if (isEvmChain3(chainId)) {
699
887
  log("Detected EVM chain:", chainId);
700
888
  const result = await handleEvmExecution({
701
889
  recipientAddress,
@@ -704,13 +892,14 @@ async function executeOrder({
704
892
  accountAddress,
705
893
  wallet: adapter,
706
894
  isSingleChain,
707
- deadline,
708
- update
895
+ update,
896
+ orderType,
897
+ options
709
898
  });
710
899
  log("EVM execution result:", result);
711
900
  return result;
712
901
  }
713
- if (chainId === ChainID3.Solana) {
902
+ if (chainId === ChainID2.Solana) {
714
903
  log("Detected Solana chain");
715
904
  const result = await handleSolanaExecution({
716
905
  recipientAddress,
@@ -718,8 +907,9 @@ async function executeOrder({
718
907
  accountAddress,
719
908
  wallet: adapter,
720
909
  isSingleChain,
721
- deadline,
722
- update
910
+ update,
911
+ orderType,
912
+ options
723
913
  });
724
914
  log("Solana execution result:", result);
725
915
  return result;
@@ -729,8 +919,13 @@ async function executeOrder({
729
919
  log("Error:", unsupported);
730
920
  return { status: false, message: unsupported, stage: "error" };
731
921
  } catch (error) {
732
- const message = error instanceof BaseError ? error.shortMessage : error instanceof Error ? error.message : String(error);
733
- 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
+ }
734
929
  update("error", message);
735
930
  return { status: false, message, stage: "error" };
736
931
  }
@@ -741,353 +936,605 @@ function normalizeWallet(wallet) {
741
936
  return wallet;
742
937
  }
743
938
 
744
- // src/react/useExecuteOrder.ts
745
- function useExecuteOrder() {
746
- const [status, setStatus] = useState2("processing");
747
- const [message, setMessage] = useState2(null);
748
- const [loading, setLoading] = useState2(false);
749
- const [data, setData] = useState2(null);
750
- const [error, setError] = useState2(null);
751
- const isMounted = useRef2(true);
939
+ // src/core/orders/getOrders.ts
940
+ import { fetchUserOrders } from "@shogun-sdk/intents-sdk";
941
+ async function getOrders({
942
+ evmAddress,
943
+ solAddress
944
+ }) {
945
+ if (!evmAddress && !solAddress) {
946
+ throw new Error("At least one wallet address (EVM, Solana) must be provided.");
947
+ }
948
+ const orders = await fetchUserOrders(evmAddress, solAddress);
949
+ return orders;
950
+ }
951
+
952
+ // src/core/client.ts
953
+ var SwapSDK = class {
954
+ constructor(config) {
955
+ __publicField(this, "apiKey");
956
+ /**
957
+ * Fetches metadata for one or more tokens from the Shogun Token Search API.
958
+ *
959
+ * ---
960
+ * ### Overview
961
+ * `getTokensData` retrieves normalized token information — such as symbol, name,
962
+ * decimals, logo URI, and verified status — for a given list of token addresses.
963
+ *
964
+ * It supports both **EVM** and **SVM (Solana)** tokens, returning metadata from
965
+ * Shogun’s unified token registry.
966
+ *
967
+ * ---
968
+ * @example
969
+ * ```ts
970
+ * const tokens = await getTokensData([
971
+ * "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
972
+ * "So11111111111111111111111111111111111111112", // SOL
973
+ * ]);
974
+ *
975
+ * console.log(tokens);
976
+ * [
977
+ * { symbol: "USDC", name: "USD Coin", chainId: 1, decimals: 6, ... },
978
+ * { symbol: "SOL", name: "Solana", chainId: 101, decimals: 9, ... }
979
+ * ]
980
+ * ```
981
+ *
982
+ * @param addresses - An array of token addresses (EVM or SVM) to fetch metadata for.
983
+ * @returns A promise resolving to an array of {@link TokenInfo} objects.
984
+ *
985
+ * @throws Will throw an error if the network request fails or the API responds with a non-OK status.
986
+ */
987
+ __publicField(this, "getTokensData", getTokensData.bind(this));
988
+ if (!config.apiKey) {
989
+ throw new Error("SwapSDK: Missing API key");
990
+ }
991
+ this.apiKey = config.apiKey;
992
+ if (this.apiKey) void this.apiKey;
993
+ }
994
+ /**
995
+ * Retrieves a swap quote for the given input and output tokens.
996
+ *
997
+ * @param params - Quote parameters including source/destination tokens and amount.
998
+ * @returns A normalized `SwapQuoteResponse` containing output amount, route, and metadata.
999
+ */
1000
+ async getQuote(params) {
1001
+ return getQuote(params);
1002
+ }
1003
+ /**
1004
+ * Fetches token balances for the specified user wallet(s).
1005
+ *
1006
+ * Supports both EVM and SVM (Solana) wallet addresses.
1007
+ *
1008
+ * @param params - Wallet address and optional chain filters.
1009
+ * @param options - Optional abort signal for cancellation.
1010
+ * @returns A unified balance response with per-chain token details.
1011
+ */
1012
+ async getBalances(params, options) {
1013
+ return getBalances(params, options);
1014
+ }
1015
+ /**
1016
+ * Retrieves a list of verified tokens based on search query or chain filter.
1017
+ *
1018
+ * @param params - Search parameters (query, chain ID, pagination options).
1019
+ * @returns Paginated `TokenSearchResponse` containing token metadata.
1020
+ */
1021
+ async getTokenList(params) {
1022
+ return getTokenList(params);
1023
+ }
1024
+ /**
1025
+ * Executes a prepared swap quote using the provided wallet and configuration.
1026
+ *
1027
+ * Handles:
1028
+ * - Token approval (if required)
1029
+ * - Transaction signing and broadcasting
1030
+ * - Confirmation polling and stage-based status updates
1031
+ *
1032
+ * Supports both:
1033
+ * - Market orders (with optional deadline)
1034
+ * - Limit orders (requires executionPrice and deadline)
1035
+ *
1036
+ * @param quote - The swap quote to execute, containing route and metadata.
1037
+ * @param accountAddress - The user's wallet address executing the swap.
1038
+ * @param recipientAddress - Optional recipient address for the output tokens (defaults to sender).
1039
+ * @param wallet - Adapted wallet instance (EVM/Solana) or a standard Viem `WalletClient`.
1040
+ * @param onStatus - Optional callback for receiving execution stage updates and messages.
1041
+ * @param orderType - Defines whether this is a market or limit order.
1042
+ * @param options - Execution parameters (different per order type).
1043
+ *
1044
+ * @returns A finalized execution result containing transaction hash, status, and any returned data.
1045
+ *
1046
+ * @example
1047
+ * ```ts
1048
+ * * Market order
1049
+ * const result = await sdk.executeTransaction({
1050
+ * quote,
1051
+ * accountAddress: "0x123...",
1052
+ * wallet,
1053
+ * orderType: OrderExecutionType.MARKET,
1054
+ * options: { deadline: 1800 },
1055
+ * onStatus: (stage, msg) => console.log(stage, msg),
1056
+ * });
1057
+ *
1058
+ * * Limit order
1059
+ * const result = await sdk.executeTransaction({
1060
+ * quote,
1061
+ * accountAddress: "0x123...",
1062
+ * wallet,
1063
+ * orderType: OrderExecutionType.LIMIT,
1064
+ * options: { executionPrice: "0.0021", deadline: 3600 },
1065
+ * });
1066
+ * ```
1067
+ */
1068
+ async executeTransaction({
1069
+ quote,
1070
+ accountAddress,
1071
+ recipientAddress,
1072
+ wallet,
1073
+ onStatus,
1074
+ orderType,
1075
+ options
1076
+ }) {
1077
+ return executeOrder({
1078
+ quote,
1079
+ wallet,
1080
+ accountAddress,
1081
+ recipientAddress,
1082
+ onStatus,
1083
+ orderType,
1084
+ options
1085
+ });
1086
+ }
1087
+ /**
1088
+ * Fetches all user orders (Market, Limit, Cross-chain) for connected wallets.
1089
+ *
1090
+ * ---
1091
+ * ### Overview
1092
+ * Retrieves both **single-chain** and **cross-chain** orders from the Shogun Intents API.
1093
+ * Works across EVM, Solana
1094
+ *
1095
+ * ---
1096
+ * @example
1097
+ * ```ts
1098
+ * const orders = await sdk.getOrders({
1099
+ * evmAddress: "0x123...",
1100
+ * solAddress: "9d12hF...abc",
1101
+ * });
1102
+ *
1103
+ * console.log(orders.singleChainLimitOrders);
1104
+ * ```
1105
+ *
1106
+ * @param params - Wallet addresses to fetch orders for (EVM, Solana).
1107
+ * @returns A structured {@link ApiUserOrders} object containing all user orders.
1108
+ */
1109
+ async getOrders(params) {
1110
+ return getOrders(params);
1111
+ }
1112
+ };
1113
+
1114
+ // src/react/SwapProvider.tsx
1115
+ import { jsx } from "react/jsx-runtime";
1116
+ var SwapContext = createContext(null);
1117
+ var SwapProvider = ({ config, children }) => {
1118
+ const sdk = useMemo(() => new SwapSDK(config), [config.apiKey]);
1119
+ return /* @__PURE__ */ jsx(SwapContext.Provider, { value: sdk, children });
1120
+ };
1121
+ var useSwap = () => {
1122
+ const ctx = useContext(SwapContext);
1123
+ if (!ctx) {
1124
+ throw new Error("useSwap must be used within <SwapProvider>");
1125
+ }
1126
+ return ctx;
1127
+ };
1128
+
1129
+ // src/react/useQuote.ts
1130
+ import { useCallback, useEffect, useMemo as useMemo2, useRef } from "react";
1131
+ import useSWR, { mutate } from "swr";
1132
+ var QUOTE_KEY = "quote-global";
1133
+ function isValidQuoteParams(p) {
1134
+ if (!p) return false;
1135
+ const { tokenIn, tokenOut, amount } = p;
1136
+ if (!tokenIn || !tokenOut || !amount) return false;
1137
+ const n = Number(amount);
1138
+ return Number.isFinite(n) && n > 0;
1139
+ }
1140
+ function useQuote(initialParams) {
1141
+ const sdk = useSwap();
1142
+ const abortRef = useRef(null);
1143
+ const paramsRef = globalThis.__QUOTE_PARAMS_REF__ ?? (globalThis.__QUOTE_PARAMS_REF__ = { current: initialParams ?? null });
1144
+ useEffect(() => {
1145
+ if (initialParams && JSON.stringify(paramsRef.current) !== JSON.stringify(initialParams)) {
1146
+ paramsRef.current = initialParams;
1147
+ void mutate(QUOTE_KEY);
1148
+ }
1149
+ }, [initialParams]);
1150
+ const fetcher = useCallback(async () => {
1151
+ const activeParams = paramsRef.current;
1152
+ if (!isValidQuoteParams(activeParams)) return null;
1153
+ if (abortRef.current) abortRef.current.abort();
1154
+ const controller = new AbortController();
1155
+ abortRef.current = controller;
1156
+ try {
1157
+ return await sdk.getQuote(activeParams);
1158
+ } catch (err) {
1159
+ if (err instanceof DOMException && err.name === "AbortError") return null;
1160
+ if (err instanceof Error) throw err;
1161
+ throw new Error(String(err));
1162
+ }
1163
+ }, [sdk]);
1164
+ const {
1165
+ data,
1166
+ error,
1167
+ isValidating: loading,
1168
+ mutate: mutateQuote
1169
+ } = useSWR(QUOTE_KEY, fetcher, {
1170
+ revalidateOnFocus: false,
1171
+ shouldRetryOnError: false,
1172
+ keepPreviousData: true
1173
+ });
1174
+ const refetch = useCallback(async () => {
1175
+ await mutateQuote();
1176
+ }, [mutateQuote]);
1177
+ const setParams = useCallback(
1178
+ (next) => {
1179
+ paramsRef.current = next;
1180
+ void mutate(QUOTE_KEY);
1181
+ },
1182
+ []
1183
+ );
1184
+ return useMemo2(
1185
+ () => ({
1186
+ data,
1187
+ loading,
1188
+ error: error instanceof Error ? error.message : null,
1189
+ refetch,
1190
+ setParams,
1191
+ get activeParams() {
1192
+ return paramsRef.current;
1193
+ }
1194
+ }),
1195
+ [data, loading, error, refetch, setParams]
1196
+ );
1197
+ }
1198
+
1199
+ // src/react/useExecuteTransaction.ts
1200
+ import { useState, useCallback as useCallback3 } from "react";
1201
+
1202
+ // src/react/useBalances.ts
1203
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2 } from "react";
1204
+ import useSWR2, { mutate as mutate2 } from "swr";
1205
+ var BALANCE_KEY = "balances-global";
1206
+ function useBalances(initialParams) {
1207
+ const sdk = useSwap();
1208
+ const abortRef = useRef2(null);
1209
+ const paramsRef = globalThis.__BALANCES_PARAMS_REF__ ?? (globalThis.__BALANCES_PARAMS_REF__ = { current: initialParams ?? null });
1210
+ const fetcher = useCallback2(async () => {
1211
+ const activeParams = paramsRef.current;
1212
+ if (!activeParams) return null;
1213
+ if (abortRef.current) abortRef.current.abort();
1214
+ const controller = new AbortController();
1215
+ abortRef.current = controller;
1216
+ try {
1217
+ return await sdk.getBalances(activeParams, { signal: controller.signal });
1218
+ } catch (err) {
1219
+ if (err instanceof DOMException && err.name === "AbortError") return null;
1220
+ if (err instanceof Error) throw err;
1221
+ throw new Error(String(err));
1222
+ }
1223
+ }, [sdk, paramsRef]);
1224
+ const {
1225
+ data,
1226
+ error,
1227
+ isValidating: loading,
1228
+ mutate: mutateBalances
1229
+ } = useSWR2(BALANCE_KEY, fetcher, {
1230
+ revalidateOnFocus: false,
1231
+ shouldRetryOnError: false,
1232
+ keepPreviousData: true
1233
+ });
1234
+ const refetch = useCallback2(async () => {
1235
+ await mutateBalances();
1236
+ }, [mutateBalances]);
1237
+ const setParams = useCallback2((next) => {
1238
+ paramsRef.current = next;
1239
+ void mutate2(BALANCE_KEY);
1240
+ }, [paramsRef]);
752
1241
  useEffect2(() => {
753
- return () => {
754
- isMounted.current = false;
755
- };
756
- }, []);
757
- const execute = useCallback(
1242
+ if (initialParams) {
1243
+ const prevParams = paramsRef.current;
1244
+ const paramsChanged = !prevParams || prevParams.addresses.svm !== initialParams.addresses.svm || prevParams.addresses.evm !== initialParams.addresses.evm;
1245
+ if (paramsChanged) {
1246
+ paramsRef.current = initialParams;
1247
+ void mutate2(BALANCE_KEY);
1248
+ }
1249
+ }
1250
+ }, [initialParams, paramsRef]);
1251
+ return useMemo3(
1252
+ () => ({
1253
+ data,
1254
+ loading,
1255
+ error: error instanceof Error ? error : null,
1256
+ refetch,
1257
+ setParams,
1258
+ get activeParams() {
1259
+ return paramsRef.current;
1260
+ }
1261
+ }),
1262
+ [data, loading, error, refetch, setParams, paramsRef]
1263
+ );
1264
+ }
1265
+
1266
+ // src/react/useExecuteTransaction.ts
1267
+ function useExecuteTransaction() {
1268
+ const sdk = useSwap();
1269
+ const [isLoading, setLoading] = useState(false);
1270
+ const [stage, setStage] = useState(null);
1271
+ const [message, setMessage] = useState(null);
1272
+ const [error, setError] = useState(null);
1273
+ const [result, setResult] = useState(null);
1274
+ const { refetch: refetchQuote } = useQuote();
1275
+ const { refetch: refetchBalances } = useBalances();
1276
+ const execute = useCallback3(
758
1277
  async ({
759
1278
  quote,
760
1279
  accountAddress,
761
1280
  recipientAddress,
762
1281
  wallet,
763
- deadline
1282
+ orderType,
1283
+ options
764
1284
  }) => {
765
- if (!quote || !wallet) {
766
- throw new Error("Quote and wallet are required for order execution.");
767
- }
768
1285
  setLoading(true);
769
1286
  setError(null);
770
- setData(null);
771
- setMessage(null);
1287
+ setResult(null);
1288
+ const onStatus = (s, msg) => {
1289
+ setStage(s);
1290
+ setMessage(msg ?? "");
1291
+ };
772
1292
  try {
773
- const effectiveDeadline = deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
774
- const onStatus = (stage, msg) => {
775
- if (!isMounted.current) return;
776
- setStatus(stage);
777
- if (msg) setMessage(msg);
778
- };
779
- const result = await executeOrder({
1293
+ const res = await sdk.executeTransaction({
780
1294
  quote,
1295
+ wallet,
781
1296
  accountAddress,
782
1297
  recipientAddress,
783
- wallet,
784
1298
  onStatus,
785
- options: { deadline: effectiveDeadline }
1299
+ orderType,
1300
+ options
786
1301
  });
787
- if (!isMounted.current) return result;
788
- setData(result);
789
- setStatus(result.stage);
790
- setMessage("Order executed successfully");
791
- return result;
792
- } catch (err) {
793
- const errorObj = err instanceof Error ? err : new Error(String(err));
794
- if (isMounted.current) {
795
- setError(errorObj);
796
- setStatus("error");
797
- setMessage(errorObj.message);
798
- setData({
799
- status: false,
800
- stage: "error",
801
- message: errorObj.message
1302
+ if (res && typeof res === "object" && "status" in res && "stage" in res && typeof res.stage === "string") {
1303
+ setResult({
1304
+ ...res,
1305
+ stage: res.stage
802
1306
  });
1307
+ } else {
1308
+ setResult(res);
803
1309
  }
804
- return {
805
- status: false,
806
- stage: "error",
807
- message: errorObj.message
808
- };
1310
+ return res;
1311
+ } catch (err) {
1312
+ const msg = err instanceof Error ? err.message : String(err);
1313
+ setError(msg);
1314
+ throw err;
809
1315
  } finally {
810
- if (isMounted.current) setLoading(false);
1316
+ await Promise.allSettled([refetchQuote(), refetchBalances()]);
1317
+ setLoading(false);
811
1318
  }
812
1319
  },
813
- []
1320
+ [sdk, refetchQuote, refetchBalances]
814
1321
  );
815
1322
  return {
816
- /** Executes the swap order. */
817
1323
  execute,
818
- /** Current execution stage. */
819
- status,
820
- /** Human-readable status message. */
1324
+ isLoading,
1325
+ stage,
821
1326
  message,
822
- /** Whether execution is ongoing. */
823
- loading,
824
- /** Raw SDK response data. */
825
- data,
826
- /** Captured error (if execution failed). */
827
- error
1327
+ error,
1328
+ result
828
1329
  };
829
1330
  }
830
1331
 
831
- // src/react/useQuote.ts
832
- import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
833
-
834
- // src/core/getQuote.ts
835
- import { QuoteProvider } from "@shogun-sdk/intents-sdk";
836
- import { parseUnits } from "viem";
837
- async function getQuote(params) {
838
- if (!params.tokenIn?.address || !params.tokenOut?.address) {
839
- throw new Error("Both tokenIn and tokenOut must include an address.");
840
- }
841
- if (!params.sourceChainId || !params.destChainId) {
842
- throw new Error("Both sourceChainId and destChainId are required.");
843
- }
844
- if (params.amount <= 0n) {
845
- throw new Error("Amount must be greater than 0.");
846
- }
847
- const normalizedTokenIn = normalizeNative(params.sourceChainId, params.tokenIn.address);
848
- const data = await QuoteProvider.getQuote({
849
- sourceChainId: params.sourceChainId,
850
- destChainId: params.destChainId,
851
- tokenIn: normalizedTokenIn,
852
- tokenOut: params.tokenOut.address,
853
- amount: params.amount
854
- });
855
- const slippagePercent = Math.min(Math.max(params.slippage ?? 0.5, 0), 50);
856
- let warning;
857
- if (slippagePercent > 10) {
858
- warning = `\u26A0\uFE0F High slippage tolerance (${slippagePercent.toFixed(2)}%) \u2014 price may vary significantly.`;
859
- }
860
- const estimatedAmountOut = BigInt(data.estimatedAmountOut);
861
- const slippageBps = BigInt(Math.round(slippagePercent * 100));
862
- const estimatedAmountOutAfterSlippage = estimatedAmountOut * (10000n - slippageBps) / 10000n;
863
- const pricePerTokenOutInUsd = data.estimatedAmountOutUsd / Number(data.estimatedAmountOut);
864
- const amountOutUsdAfterSlippage = Number(estimatedAmountOutAfterSlippage) * pricePerTokenOutInUsd;
865
- const minStablecoinsAmountValue = BigInt(data.estimatedAmountInAsMinStablecoinAmount);
866
- const minStablecoinsAmountAfterSlippage = minStablecoinsAmountValue * (10000n - slippageBps) / 10000n;
867
- const pricePerInputToken = estimatedAmountOut * 10n ** BigInt(params.tokenIn.decimals ?? 18) / BigInt(params.amount);
868
- return {
869
- amountOut: estimatedAmountOutAfterSlippage,
870
- amountOutUsd: amountOutUsdAfterSlippage,
871
- amountInUsd: data.amountInUsd,
872
- // Input USD stays the same
873
- minStablecoinsAmount: minStablecoinsAmountAfterSlippage,
874
- tokenIn: {
875
- address: params.tokenIn.address,
876
- decimals: params.tokenIn.decimals ?? 18,
877
- chainId: params.sourceChainId
878
- },
879
- tokenOut: {
880
- address: params.tokenOut.address,
881
- decimals: params.tokenOut.decimals ?? 18,
882
- chainId: params.destChainId
883
- },
884
- amountIn: params.amount,
885
- pricePerInputToken,
886
- slippage: slippagePercent,
887
- internal: {
888
- ...data,
889
- estimatedAmountOutReduced: estimatedAmountOutAfterSlippage,
890
- estimatedAmountOutUsdReduced: amountOutUsdAfterSlippage
1332
+ // src/react/useTokenList.ts
1333
+ import { useRef as useRef3, useState as useState2, useCallback as useCallback4 } from "react";
1334
+ function useTokenList() {
1335
+ const [tokens, setTokens] = useState2([]);
1336
+ const [loading, setLoading] = useState2(false);
1337
+ const [error, setError] = useState2(null);
1338
+ const [hasMore, setHasMore] = useState2(true);
1339
+ const [page, setPage] = useState2(1);
1340
+ const sdk = useSwap();
1341
+ const pageRef = useRef3(1);
1342
+ const hasMoreRef = useRef3(true);
1343
+ const lastQuery = useRef3({});
1344
+ const cache = useRef3(/* @__PURE__ */ new Map());
1345
+ const isLoadingRef = useRef3(false);
1346
+ pageRef.current = page;
1347
+ hasMoreRef.current = hasMore;
1348
+ const loadTokens = useCallback4(
1349
+ async (params) => {
1350
+ const { q, networkId, reset } = params;
1351
+ if (isLoadingRef.current && !reset) return;
1352
+ try {
1353
+ let currentPage = pageRef.current;
1354
+ if (reset) {
1355
+ currentPage = 1;
1356
+ setTokens([]);
1357
+ setPage(1);
1358
+ setHasMore(true);
1359
+ pageRef.current = 1;
1360
+ hasMoreRef.current = true;
1361
+ }
1362
+ if (!reset && !hasMoreRef.current) return;
1363
+ isLoadingRef.current = true;
1364
+ setLoading(true);
1365
+ setError(null);
1366
+ const cacheKey = JSON.stringify({
1367
+ q: q?.toLowerCase() ?? "",
1368
+ networkId: networkId ?? void 0,
1369
+ page: currentPage
1370
+ });
1371
+ if (cache.current.has(cacheKey)) {
1372
+ const cached = cache.current.get(cacheKey);
1373
+ setTokens((prev) => reset ? cached.results : [...prev, ...cached.results]);
1374
+ if (!reset && cached.results.length > 0) {
1375
+ const nextPage = currentPage + 1;
1376
+ setPage(nextPage);
1377
+ pageRef.current = nextPage;
1378
+ }
1379
+ isLoadingRef.current = false;
1380
+ setLoading(false);
1381
+ return;
1382
+ }
1383
+ const apiParams = {
1384
+ q,
1385
+ page: currentPage,
1386
+ limit: 20,
1387
+ ...networkId !== void 0 ? { networkId } : {}
1388
+ };
1389
+ const res = await sdk.getTokenList(apiParams);
1390
+ cache.current.set(cacheKey, res);
1391
+ setTokens((prev) => reset ? res.results : [...prev, ...res.results]);
1392
+ const isLastPage = res.results.length === 0 || res.results.length < 20 || res.count && currentPage * 20 >= res.count;
1393
+ setHasMore(!isLastPage);
1394
+ hasMoreRef.current = !isLastPage;
1395
+ if (!reset && !isLastPage) {
1396
+ const nextPage = currentPage + 1;
1397
+ setPage(nextPage);
1398
+ pageRef.current = nextPage;
1399
+ }
1400
+ lastQuery.current = { q, networkId };
1401
+ } catch (e) {
1402
+ console.error("useTokenList error:", e);
1403
+ setError("Failed to load tokens");
1404
+ } finally {
1405
+ setLoading(false);
1406
+ isLoadingRef.current = false;
1407
+ }
891
1408
  },
892
- warning
1409
+ [sdk]
1410
+ );
1411
+ const resetTokens = useCallback4(() => {
1412
+ setTokens([]);
1413
+ setPage(1);
1414
+ setHasMore(true);
1415
+ pageRef.current = 1;
1416
+ hasMoreRef.current = true;
1417
+ cache.current.clear();
1418
+ lastQuery.current = {};
1419
+ }, []);
1420
+ return {
1421
+ tokens,
1422
+ loading,
1423
+ error,
1424
+ hasMore,
1425
+ page,
1426
+ lastQuery: lastQuery.current,
1427
+ loadTokens,
1428
+ resetTokens
893
1429
  };
894
1430
  }
895
1431
 
896
- // src/react/useQuote.ts
897
- function useQuote(params, options) {
898
- const [data, setData] = useState3(null);
1432
+ // src/react/useTokensData.ts
1433
+ import { useState as useState3, useEffect as useEffect3 } from "react";
1434
+ function useTokensData(addresses) {
1435
+ const sdk = useSwap();
1436
+ const [data, setData] = useState3([]);
899
1437
  const [loading, setLoading] = useState3(false);
900
1438
  const [error, setError] = useState3(null);
901
- const [warning, setWarning] = useState3(null);
902
- const debounceMs = options?.debounceMs ?? 250;
903
- const autoRefreshMs = options?.autoRefreshMs;
904
- const abortRef = useRef3(null);
905
- const debounceRef = useRef3(null);
906
- const mounted = useRef3(false);
907
- const paramsKey = useMemo2(
908
- () => params ? JSON.stringify(serializeBigIntsToStrings(params)) : null,
909
- [params]
910
- );
911
- useEffect3(() => {
912
- mounted.current = true;
913
- return () => {
914
- mounted.current = false;
915
- abortRef.current?.abort();
916
- if (debounceRef.current) clearTimeout(debounceRef.current);
917
- };
918
- }, []);
919
- const fetchQuote = useCallback2(async () => {
920
- if (!params) return;
921
- try {
922
- setLoading(true);
923
- setWarning(null);
924
- const result = await getQuote(params);
925
- const serializeResult = serializeBigIntsToStrings(result);
926
- if (!mounted.current) return;
927
- setData((prev) => {
928
- if (JSON.stringify(prev) === JSON.stringify(serializeResult)) return prev;
929
- return serializeResult;
930
- });
931
- setWarning(result.warning ?? null);
932
- setError(null);
933
- } catch (err) {
934
- if (err.name === "AbortError") return;
935
- console.error("[useQuote] fetch error:", err);
936
- if (mounted.current) setError(err instanceof Error ? err : new Error(String(err)));
937
- } finally {
938
- if (mounted.current) setLoading(false);
939
- }
940
- }, [paramsKey]);
941
1439
  useEffect3(() => {
942
- if (!paramsKey) return;
943
- if (debounceRef.current) clearTimeout(debounceRef.current);
944
- debounceRef.current = setTimeout(() => {
945
- fetchQuote();
946
- }, debounceMs);
1440
+ if (!addresses?.length) return;
1441
+ let isMounted = true;
1442
+ setLoading(true);
1443
+ setError(null);
1444
+ sdk.getTokensData(addresses).then((res) => {
1445
+ if (isMounted) setData(res);
1446
+ }).catch((e) => {
1447
+ if (isMounted) setError(e instanceof Error ? e : new Error(String(e)));
1448
+ }).finally(() => {
1449
+ if (isMounted) setLoading(false);
1450
+ });
947
1451
  return () => {
948
- if (debounceRef.current) clearTimeout(debounceRef.current);
949
- abortRef.current?.abort();
1452
+ isMounted = false;
950
1453
  };
951
- }, [paramsKey, debounceMs, fetchQuote]);
952
- useEffect3(() => {
953
- if (!autoRefreshMs || !paramsKey) return;
954
- const interval = setInterval(() => fetchQuote(), autoRefreshMs);
955
- return () => clearInterval(interval);
956
- }, [autoRefreshMs, paramsKey, fetchQuote]);
957
- return useMemo2(
958
- () => ({
959
- data,
960
- loading,
961
- error,
962
- warning,
963
- refetch: fetchQuote
964
- }),
965
- [data, loading, error, warning, fetchQuote]
966
- );
1454
+ }, [sdk, JSON.stringify(addresses)]);
1455
+ return { data, loading, error };
967
1456
  }
968
1457
 
969
- // src/react/useBalances.ts
970
- import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
971
-
972
- // src/core/getBalances.ts
973
- import { TOKEN_SEARCH_API_BASE_URL } from "@shogun-sdk/intents-sdk";
974
- async function getBalances(params, options) {
975
- const { addresses, cursorEvm, cursorSvm } = params;
976
- const { signal } = options ?? {};
977
- if (!addresses?.evm && !addresses?.svm) {
978
- throw new Error("At least one address (EVM or SVM) must be provided.");
979
- }
980
- const payload = JSON.stringify({
981
- addresses,
982
- cursorEvm,
983
- cursorSvm
984
- });
985
- const start = performance.now();
986
- const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL}/tokens/balances`, {
987
- method: "POST",
988
- headers: {
989
- accept: "application/json",
990
- "Content-Type": "application/json"
991
- },
992
- body: payload,
993
- signal
994
- }).catch((err) => {
995
- if (err.name === "AbortError") {
996
- throw new Error("Balance request was cancelled.");
997
- }
998
- throw err;
999
- });
1000
- if (!response.ok) {
1001
- const text = await response.text().catch(() => "");
1002
- throw new Error(`Failed to fetch balances: ${response.status} ${text}`);
1003
- }
1004
- const data = await response.json().catch(() => {
1005
- throw new Error("Invalid JSON response from balances API.");
1006
- });
1007
- const duration = (performance.now() - start).toFixed(1);
1008
- if (process.env.NODE_ENV !== "production") {
1009
- console.debug(`[Shogun SDK] Fetched balances in ${duration}ms`);
1010
- }
1011
- const evmItems = data.evm?.items ?? [];
1012
- const svmItems = data.svm?.items ?? [];
1013
- const combined = [...evmItems, ...svmItems];
1014
- return {
1015
- results: combined,
1016
- nextCursorEvm: data.evm?.cursor ?? null,
1017
- nextCursorSvm: data.svm?.cursor ?? null
1018
- };
1458
+ // src/react/useOrders.ts
1459
+ import { useCallback as useCallback5, useEffect as useEffect4, useMemo as useMemo4, useRef as useRef4 } from "react";
1460
+ import useSWR3, { mutate as mutate3 } from "swr";
1461
+ var ORDERS_KEY = "orders-global";
1462
+ function hasValidAddress(addrs) {
1463
+ if (!addrs) return false;
1464
+ return Boolean(addrs.evmAddress || addrs.solAddress || addrs.suiAddress);
1019
1465
  }
1020
-
1021
- // src/react/useBalances.ts
1022
- function useBalances(params) {
1023
- const [data, setData] = useState4(null);
1024
- const [loading, setLoading] = useState4(false);
1025
- const [error, setError] = useState4(null);
1466
+ function useOrders(initialAddresses) {
1467
+ const sdk = useSwap();
1026
1468
  const abortRef = useRef4(null);
1027
- const stableParams = useMemo3(() => {
1028
- if (!params) return null;
1029
- const { addresses, cursorEvm, cursorSvm } = params;
1030
- return {
1031
- addresses: {
1032
- evm: addresses?.evm ?? void 0,
1033
- svm: addresses?.svm ?? void 0
1034
- },
1035
- cursorEvm,
1036
- cursorSvm
1037
- };
1038
- }, [params?.addresses?.evm, params?.addresses?.svm, params?.cursorEvm, params?.cursorSvm]);
1039
- const fetchBalances = useCallback3(async () => {
1040
- if (!stableParams) return;
1469
+ const addrRef = globalThis.__ORDERS_ADDR_REF__ ?? (globalThis.__ORDERS_ADDR_REF__ = { current: initialAddresses ?? null });
1470
+ useEffect4(() => {
1471
+ if (initialAddresses && JSON.stringify(addrRef.current) !== JSON.stringify(initialAddresses)) {
1472
+ addrRef.current = initialAddresses;
1473
+ void mutate3(ORDERS_KEY);
1474
+ }
1475
+ }, [initialAddresses]);
1476
+ const fetcher = useCallback5(async () => {
1477
+ const active = addrRef.current;
1478
+ if (!hasValidAddress(active)) return null;
1041
1479
  if (abortRef.current) abortRef.current.abort();
1042
1480
  const controller = new AbortController();
1043
1481
  abortRef.current = controller;
1044
- setLoading(true);
1045
- setError(null);
1046
1482
  try {
1047
- const result = await getBalances(stableParams, { signal: controller.signal });
1048
- setData((prev) => {
1049
- if (JSON.stringify(prev) === JSON.stringify(result)) return prev;
1050
- return result;
1051
- });
1052
- return result;
1483
+ return await sdk.getOrders(active ?? {});
1053
1484
  } catch (err) {
1054
- if (err.name === "AbortError") return;
1055
- const e = err instanceof Error ? err : new Error(String(err));
1056
- setError(e);
1057
- throw e;
1058
- } finally {
1059
- setLoading(false);
1485
+ if (err instanceof DOMException && err.name === "AbortError") return null;
1486
+ if (err instanceof Error) throw err;
1487
+ throw new Error(String(err));
1060
1488
  }
1061
- }, [stableParams]);
1062
- useEffect4(() => {
1063
- if (stableParams) fetchBalances().catch(() => {
1064
- });
1065
- return () => {
1066
- if (abortRef.current) abortRef.current.abort();
1067
- };
1068
- }, [fetchBalances]);
1069
- return useMemo3(
1489
+ }, [sdk]);
1490
+ const {
1491
+ data,
1492
+ error,
1493
+ isValidating: loading,
1494
+ mutate: mutateOrders
1495
+ } = useSWR3(ORDERS_KEY, fetcher, {
1496
+ revalidateOnFocus: false,
1497
+ revalidateOnReconnect: false,
1498
+ shouldRetryOnError: false,
1499
+ keepPreviousData: true
1500
+ });
1501
+ const refetch = useCallback5(async () => {
1502
+ await mutateOrders();
1503
+ }, [mutateOrders]);
1504
+ const setAddresses = useCallback5(
1505
+ (next) => {
1506
+ if (!next) return;
1507
+ addrRef.current = next;
1508
+ void mutate3(ORDERS_KEY);
1509
+ },
1510
+ []
1511
+ );
1512
+ return useMemo4(
1070
1513
  () => ({
1071
- /** Latest fetched balance data */
1072
1514
  data,
1073
- /** Whether the hook is currently fetching */
1074
1515
  loading,
1075
- /** Error object if fetching failed */
1076
- error,
1077
- /** Manually trigger a refresh */
1078
- refetch: fetchBalances
1516
+ error: error instanceof Error ? error.message : null,
1517
+ refetch,
1518
+ setAddresses,
1519
+ get activeAddresses() {
1520
+ return addrRef.current;
1521
+ }
1079
1522
  }),
1080
- [data, loading, error, fetchBalances]
1523
+ [data, loading, error, refetch, setAddresses]
1081
1524
  );
1082
1525
  }
1083
-
1084
- // src/react/index.ts
1085
- import { ChainID as ChainID4, isEvmChain as isEvmChain3 } from "@shogun-sdk/intents-sdk";
1086
1526
  export {
1087
- ChainID4 as ChainID,
1088
- isEvmChain3 as isEvmChain,
1527
+ ChainId,
1528
+ OrderExecutionType,
1529
+ SupportedChains,
1530
+ SwapProvider,
1531
+ buildQuoteParams,
1532
+ isEvmChain,
1089
1533
  useBalances,
1090
- useExecuteOrder,
1534
+ useExecuteTransaction,
1535
+ useOrders,
1091
1536
  useQuote,
1092
- useTokenList
1537
+ useSwap,
1538
+ useTokenList,
1539
+ useTokensData
1093
1540
  };