@shogun-sdk/swap 0.0.2-test.4 → 0.0.2-test.41

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";
3
-
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
- }
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);
9
4
 
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
- }
5
+ // src/react/SwapProvider.tsx
6
+ import { createContext, useContext, useMemo } from "react";
79
7
 
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 ChainID4, 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,32 @@ 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
+ BaseChainID.MONAD
40
+ ];
41
+ var ChainId = Object.entries(BaseChainID).reduce(
42
+ (acc, [key, value]) => {
43
+ if (typeof value === "number" && CURRENT_SUPPORTED.includes(value)) {
44
+ acc[key] = value;
45
+ }
46
+ return acc;
47
+ },
48
+ {}
49
+ );
50
+ var SupportedChainsInternal = [
103
51
  {
104
- id: ChainID.Arbitrum,
52
+ id: BaseChainID.Arbitrum,
105
53
  name: "Arbitrum",
106
54
  isEVM: true,
107
55
  wrapped: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
@@ -110,7 +58,7 @@ var SupportedChains = [
110
58
  tokenAddress: NATIVE_TOKEN.ETH
111
59
  },
112
60
  {
113
- id: ChainID.Optimism,
61
+ id: BaseChainID.Optimism,
114
62
  name: "Optimism",
115
63
  isEVM: true,
116
64
  wrapped: "0x4200000000000000000000000000000000000006",
@@ -119,7 +67,7 @@ var SupportedChains = [
119
67
  tokenAddress: NATIVE_TOKEN.ETH
120
68
  },
121
69
  {
122
- id: ChainID.Base,
70
+ id: BaseChainID.Base,
123
71
  name: "Base",
124
72
  isEVM: true,
125
73
  wrapped: "0x4200000000000000000000000000000000000006",
@@ -128,7 +76,7 @@ var SupportedChains = [
128
76
  tokenAddress: NATIVE_TOKEN.ETH
129
77
  },
130
78
  {
131
- id: ChainID.Hyperliquid,
79
+ id: BaseChainID.Hyperliquid,
132
80
  name: "Hyperliquid",
133
81
  isEVM: true,
134
82
  wrapped: "0x5555555555555555555555555555555555555555",
@@ -137,7 +85,7 @@ var SupportedChains = [
137
85
  tokenAddress: NATIVE_TOKEN.ETH
138
86
  },
139
87
  {
140
- id: ChainID.BSC,
88
+ id: BaseChainID.BSC,
141
89
  name: "BSC",
142
90
  isEVM: true,
143
91
  wrapped: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
@@ -153,8 +101,21 @@ var SupportedChains = [
153
101
  symbol: "SOL",
154
102
  decimals: 9,
155
103
  tokenAddress: NATIVE_TOKEN.SOL
104
+ },
105
+ {
106
+ id: BaseChainID.MONAD,
107
+ name: "Monad",
108
+ isEVM: true,
109
+ wrapped: "0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A",
110
+ symbol: "MON",
111
+ decimals: 18,
112
+ tokenAddress: NATIVE_TOKEN.ETH
156
113
  }
157
114
  ];
115
+ var SupportedChains = SupportedChainsInternal.filter(
116
+ (c) => CURRENT_SUPPORTED.includes(c.id)
117
+ );
118
+ var isEvmChain = isEvmChainIntent;
158
119
 
159
120
  // src/utils/viem.ts
160
121
  function isViemWalletClient(wallet) {
@@ -182,11 +143,229 @@ function serializeBigIntsToStrings(obj) {
182
143
  return obj;
183
144
  }
184
145
 
146
+ // src/core/execute/normalizeNative.ts
147
+ function normalizeNative(chainId, address) {
148
+ if (isEvmChain2(chainId) && isNativeAddress(address)) {
149
+ const chain = SupportedChains.find((c) => c.id === chainId);
150
+ if (!chain?.wrapped)
151
+ throw new Error(`Wrapped token not found for chainId ${chainId}`);
152
+ return chain.wrapped;
153
+ }
154
+ return address;
155
+ }
156
+
157
+ // src/core/getQuote.ts
158
+ async function getQuote(params) {
159
+ const amount = BigInt(params.amount);
160
+ if (!params.tokenIn?.address || !params.tokenOut?.address) {
161
+ throw new Error("Both tokenIn and tokenOut must include an address.");
162
+ }
163
+ if (!params.sourceChainId || !params.destChainId) {
164
+ throw new Error("Both sourceChainId and destChainId are required.");
165
+ }
166
+ if (amount <= 0n) {
167
+ throw new Error("Amount must be greater than 0.");
168
+ }
169
+ const slippagePercent = Math.min(Math.max(params.slippage ?? 0.5, 0), 50);
170
+ const slippageBps = BigInt(Math.round(slippagePercent * 100));
171
+ const applyUserSlippage = (value) => value * (10000n - slippageBps) / 10000n;
172
+ let warning;
173
+ if (slippagePercent > 10) {
174
+ warning = `\u26A0\uFE0F High slippage tolerance (${slippagePercent.toFixed(2)}%) \u2014 price may vary significantly.`;
175
+ }
176
+ const normalizedTokenIn = normalizeNative(params.sourceChainId, params.tokenIn.address);
177
+ const isSingleChain = params.sourceChainId === params.destChainId;
178
+ let data;
179
+ let netFloor;
180
+ let minStablecoinsAmount;
181
+ if (isSingleChain) {
182
+ const { quote, netAmountOut } = await QuoteProvider.getSingleChainNetQuote({
183
+ chainId: params.sourceChainId,
184
+ amount,
185
+ tokenIn: normalizedTokenIn,
186
+ tokenOut: params.tokenOut.address
187
+ });
188
+ netFloor = netAmountOut;
189
+ minStablecoinsAmount = 0n;
190
+ data = {
191
+ amountInUsd: quote.amountInUsd,
192
+ estimatedAmountInAsMinStablecoinAmount: 0n,
193
+ estimatedAmountOut: quote.amountOut,
194
+ estimatedAmountOutUsd: quote.amountOutUsd,
195
+ // filled in below once the user slippage buffer is known
196
+ estimatedAmountOutReduced: netAmountOut,
197
+ estimatedAmountOutUsdReduced: quote.amountOutUsd,
198
+ tokenIn: { address: params.tokenIn.address, chainId: params.sourceChainId },
199
+ tokenOut: { address: params.tokenOut.address, chainId: params.destChainId }
200
+ };
201
+ } else {
202
+ const crossChainQuote = await QuoteProvider.getCrossChainNetQuote({
203
+ sourceChainId: params.sourceChainId,
204
+ destChainId: params.destChainId,
205
+ tokenIn: normalizedTokenIn,
206
+ tokenOut: params.tokenOut.address,
207
+ amount
208
+ });
209
+ data = crossChainQuote;
210
+ netFloor = BigInt(crossChainQuote.estimatedAmountOutReduced);
211
+ minStablecoinsAmount = applyUserSlippage(
212
+ BigInt(crossChainQuote.estimatedAmountInAsMinStablecoinAmount)
213
+ );
214
+ }
215
+ const amountOut = applyUserSlippage(netFloor);
216
+ const grossAmountOut = BigInt(data.estimatedAmountOut);
217
+ const pricePerTokenOutInUsd = grossAmountOut > 0n ? data.estimatedAmountOutUsd / Number(grossAmountOut) : 0;
218
+ const amountOutUsd = Number(amountOut) * pricePerTokenOutInUsd;
219
+ const pricePerInputToken = grossAmountOut * 10n ** BigInt(params.tokenIn.decimals ?? 18) / amount;
220
+ return {
221
+ amountOut,
222
+ amountOutUsd,
223
+ amountInUsd: data.amountInUsd,
224
+ minStablecoinsAmount,
225
+ tokenIn: {
226
+ address: params.tokenIn.address,
227
+ decimals: params.tokenIn.decimals ?? 18,
228
+ chainId: params.sourceChainId
229
+ },
230
+ tokenOut: {
231
+ address: params.tokenOut.address,
232
+ decimals: params.tokenOut.decimals ?? 18,
233
+ chainId: params.destChainId
234
+ },
235
+ amountIn: amount,
236
+ pricePerInputToken,
237
+ slippage: slippagePercent,
238
+ internal: {
239
+ ...data,
240
+ estimatedAmountOutReduced: amountOut,
241
+ estimatedAmountOutUsdReduced: amountOutUsd
242
+ },
243
+ warning
244
+ };
245
+ }
246
+ function buildQuoteParams({
247
+ tokenIn,
248
+ tokenOut,
249
+ sourceChainId,
250
+ destChainId,
251
+ amount,
252
+ slippage
253
+ }) {
254
+ return {
255
+ tokenIn,
256
+ tokenOut,
257
+ sourceChainId,
258
+ destChainId,
259
+ amount: parseUnits(amount.toString(), tokenIn.decimals ?? 18).toString(),
260
+ slippage
261
+ };
262
+ }
263
+
264
+ // src/core/getBalances.ts
265
+ import { TOKEN_SEARCH_API_BASE_URL } from "@shogun-sdk/intents-sdk";
266
+ async function getBalances(params, options) {
267
+ const { addresses, cursorEvm, cursorSvm } = params;
268
+ const { signal } = options ?? {};
269
+ if (!addresses?.evm && !addresses?.svm) {
270
+ throw new Error("At least one address (EVM or SVM) must be provided.");
271
+ }
272
+ const payload = JSON.stringify({
273
+ addresses,
274
+ cursorEvm,
275
+ cursorSvm
276
+ });
277
+ const start = performance.now();
278
+ const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL}/tokens/balances`, {
279
+ method: "POST",
280
+ headers: {
281
+ accept: "application/json",
282
+ "Content-Type": "application/json"
283
+ },
284
+ body: payload,
285
+ signal
286
+ }).catch((err) => {
287
+ if (err.name === "AbortError") {
288
+ throw new Error("Balance request was cancelled.");
289
+ }
290
+ throw err;
291
+ });
292
+ if (!response.ok) {
293
+ const text = await response.text().catch(() => "");
294
+ throw new Error(`Failed to fetch balances: ${response.status} ${text}`);
295
+ }
296
+ const data = await response.json().catch(() => {
297
+ throw new Error("Invalid JSON response from balances API.");
298
+ });
299
+ const duration = (performance.now() - start).toFixed(1);
300
+ if (process.env.NODE_ENV !== "production") {
301
+ console.debug(`[Shogun SDK] Fetched balances in ${duration}ms`);
302
+ }
303
+ const evmItems = data.evm?.items ?? [];
304
+ const svmItems = data.svm?.items ?? [];
305
+ const combined = [...evmItems, ...svmItems];
306
+ const filtered = combined.filter(
307
+ (b) => CURRENT_SUPPORTED.includes(b.chainId)
308
+ );
309
+ return {
310
+ results: filtered,
311
+ nextCursorEvm: data.evm?.cursor ?? null,
312
+ nextCursorSvm: data.svm?.cursor ?? null
313
+ };
314
+ }
315
+
316
+ // src/core/token-list.ts
317
+ import { TOKEN_SEARCH_API_BASE_URL as TOKEN_SEARCH_API_BASE_URL2 } from "@shogun-sdk/intents-sdk";
318
+ async function getTokenList(params) {
319
+ const url = new URL(`${TOKEN_SEARCH_API_BASE_URL2}/tokens/search`);
320
+ if (params.q) url.searchParams.append("q", params.q);
321
+ if (params.networkId) url.searchParams.append("networkId", String(params.networkId));
322
+ if (params.page) url.searchParams.append("page", String(params.page));
323
+ if (params.limit) url.searchParams.append("limit", String(params.limit));
324
+ const res = await fetch(url.toString(), {
325
+ signal: params.signal
326
+ });
327
+ if (!res.ok) {
328
+ throw new Error(`Failed to fetch tokens: ${res.status} ${res.statusText}`);
329
+ }
330
+ const data = await res.json();
331
+ const filteredResults = data.results.filter(
332
+ (token) => CURRENT_SUPPORTED.includes(token.chainId)
333
+ );
334
+ return {
335
+ ...data,
336
+ results: filteredResults,
337
+ count: filteredResults.length
338
+ };
339
+ }
340
+
341
+ // src/core/token.ts
342
+ import { TOKEN_SEARCH_API_BASE_URL as TOKEN_SEARCH_API_BASE_URL3 } from "@shogun-sdk/intents-sdk";
343
+ async function getTokensData(addresses) {
344
+ if (!addresses?.length) return [];
345
+ const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL3}/tokens/tokens`, {
346
+ method: "POST",
347
+ headers: {
348
+ "Content-Type": "application/json",
349
+ accept: "*/*"
350
+ },
351
+ body: JSON.stringify({ addresses })
352
+ });
353
+ if (!response.ok) {
354
+ throw new Error(`Failed to fetch token data: ${response.statusText}`);
355
+ }
356
+ const data = await response.json();
357
+ const filtered = data.filter((t) => CURRENT_SUPPORTED.includes(Number(t.chainId)));
358
+ return filtered;
359
+ }
360
+
361
+ // src/core/execute/execute.ts
362
+ import { ChainID as ChainID2, isEvmChain as isEvmChain3 } from "@shogun-sdk/intents-sdk";
363
+ import "viem";
364
+
185
365
  // src/wallet-adapter/evm-wallet-adapter/adapter.ts
186
- import "ethers/lib/ethers.js";
187
- import { hexValue } from "ethers/lib/utils.js";
188
366
  import {
189
- custom
367
+ custom,
368
+ publicActions
190
369
  } from "viem";
191
370
  function isEVMTransaction(tx) {
192
371
  return typeof tx.from === "string";
@@ -205,15 +384,26 @@ var adaptViemWallet = (wallet) => {
205
384
  if (!isEVMTransaction(transaction)) {
206
385
  throw new Error("Expected EVMTransaction but got SolanaTransaction");
207
386
  }
208
- const tx = await wallet.sendTransaction({
209
- from: transaction.from,
210
- to: transaction.to,
211
- data: transaction.data,
212
- value: transaction.value,
213
- account: wallet.account?.address,
214
- chain: wallet.chain
215
- });
216
- return tx;
387
+ if (wallet.transport.type === "http") {
388
+ const request = await wallet.prepareTransactionRequest({
389
+ to: transaction.to,
390
+ data: transaction.data,
391
+ value: transaction.value,
392
+ chain: wallet.chain
393
+ });
394
+ const serializedTransaction = await wallet.signTransaction(request);
395
+ const tx = await wallet.sendRawTransaction({ serializedTransaction });
396
+ return tx;
397
+ } else {
398
+ const hash = await wallet.sendTransaction({
399
+ to: transaction.to,
400
+ data: transaction.data,
401
+ value: transaction.value,
402
+ chain: wallet.chain,
403
+ account: wallet.account
404
+ });
405
+ return hash;
406
+ }
217
407
  };
218
408
  const switchChain = async (chainId) => {
219
409
  try {
@@ -236,6 +426,20 @@ var adaptViemWallet = (wallet) => {
236
426
  if (!addr) throw new Error("No address found");
237
427
  return addr;
238
428
  };
429
+ const readContract = async ({
430
+ address: address2,
431
+ abi,
432
+ functionName,
433
+ args = []
434
+ }) => {
435
+ const publicClient = wallet.extend(publicActions);
436
+ return await publicClient.readContract({
437
+ address: address2,
438
+ abi,
439
+ functionName,
440
+ args
441
+ });
442
+ };
239
443
  return {
240
444
  vmType: "EVM" /* EVM */,
241
445
  transport: custom(wallet.transport),
@@ -243,51 +447,65 @@ var adaptViemWallet = (wallet) => {
243
447
  address,
244
448
  sendTransaction,
245
449
  signTypedData,
246
- switchChain
450
+ switchChain,
451
+ readContract
247
452
  };
248
453
  };
249
454
 
250
- // src/core/executeOrder/handleEvmExecution.ts
455
+ // src/core/execute/handleEvmExecution.ts
251
456
  import {
252
457
  getEVMSingleChainOrderTypedData,
253
- getEVMCrossChainOrderTypedData,
254
- PERMIT2_ADDRESS
458
+ getEVMCrossChainOrderTypedData
255
459
  } from "@shogun-sdk/intents-sdk";
256
- import { encodeFunctionData, erc20Abi } from "viem";
257
-
258
- // src/core/executeOrder/normalizeNative.ts
259
- import { isEvmChain } from "@shogun-sdk/intents-sdk";
260
- function normalizeNative(chainId, address) {
261
- if (isEvmChain(chainId) && isNativeAddress(address)) {
262
- const chain = SupportedChains.find((c) => c.id === chainId);
263
- if (!chain?.wrapped)
264
- throw new Error(`Wrapped token not found for chainId ${chainId}`);
265
- return chain.wrapped;
266
- }
267
- return address;
268
- }
460
+ import { encodeFunctionData as encodeFunctionData2 } from "viem";
269
461
 
270
- // src/core/executeOrder/stageMessages.ts
462
+ // src/core/execute/stageMessages.ts
271
463
  var DEFAULT_STAGE_MESSAGES = {
272
464
  processing: "Preparing transaction for execution",
273
465
  approving: "Approving token allowance",
274
466
  approved: "Token approved successfully",
275
- signing: "Signing order for submission",
276
- submitting: "Submitting order to Auctioneer",
277
- success: "Order executed successfully",
278
- error: "Order execution failed"
467
+ signing: "Signing transaction for submission",
468
+ submitting: "Submitting transaction",
469
+ initiated: "Transaction initiated.",
470
+ success: "Transaction Executed successfully",
471
+ success_limit: "Limit order has been submitted successfully.",
472
+ shogun_processing: "Shogun is processing your transaction",
473
+ error: "Transaction failed during submission"
279
474
  };
280
475
 
281
- // src/core/executeOrder/buildOrder.ts
476
+ // src/core/execute/buildOrder.ts
282
477
  import { CrossChainOrder, SingleChainOrder } from "@shogun-sdk/intents-sdk";
478
+ import { formatUnits, parseUnits as parseUnits2 } from "viem";
479
+
480
+ // src/utils/order.ts
481
+ var OrderExecutionType = /* @__PURE__ */ ((OrderExecutionType2) => {
482
+ OrderExecutionType2["LIMIT"] = "limit";
483
+ OrderExecutionType2["MARKET"] = "market";
484
+ return OrderExecutionType2;
485
+ })(OrderExecutionType || {});
486
+
487
+ // src/core/execute/buildOrder.ts
283
488
  async function buildOrder({
284
489
  quote,
285
490
  accountAddress,
286
491
  destination,
287
492
  deadline,
288
- isSingleChain
493
+ isSingleChain,
494
+ orderType,
495
+ options
289
496
  }) {
290
497
  const { tokenIn, tokenOut } = quote;
498
+ let amountOutMin = BigInt(quote.internal.estimatedAmountOutReduced);
499
+ if (orderType === "limit" /* LIMIT */ && options && "executionPrice" in options) {
500
+ const executionPrice = Number(options.executionPrice);
501
+ if (Number.isFinite(executionPrice) && executionPrice > 0) {
502
+ const decimalsIn = tokenIn.decimals ?? 18;
503
+ const decimalsOut = tokenOut.decimals ?? 18;
504
+ const formattedAmountIn = Number(formatUnits(BigInt(quote.amountIn.toString()), decimalsIn));
505
+ const rawAmountOut = formattedAmountIn * executionPrice;
506
+ amountOutMin = parseUnits2(rawAmountOut.toString(), decimalsOut);
507
+ }
508
+ }
291
509
  if (isSingleChain) {
292
510
  return await SingleChainOrder.create({
293
511
  user: accountAddress,
@@ -295,7 +513,7 @@ async function buildOrder({
295
513
  tokenIn: tokenIn.address,
296
514
  tokenOut: tokenOut.address,
297
515
  amountIn: quote.amountIn,
298
- amountOutMin: quote.internal.estimatedAmountOutReduced,
516
+ amountOutMin,
299
517
  deadline,
300
518
  destinationAddress: destination
301
519
  });
@@ -309,12 +527,166 @@ async function buildOrder({
309
527
  destinationTokenAddress: tokenOut.address,
310
528
  destinationAddress: destination,
311
529
  deadline,
312
- destinationTokenMinAmount: quote.internal.estimatedAmountOutReduced,
530
+ destinationTokenMinAmount: amountOutMin,
313
531
  minStablecoinAmount: quote.minStablecoinsAmount
314
532
  });
315
533
  }
316
534
 
317
- // src/core/executeOrder/handleEvmExecution.ts
535
+ // src/utils/pollOrderStatus.ts
536
+ import { AUCTIONEER_URL } from "@shogun-sdk/intents-sdk";
537
+ async function pollOrderStatus(address, orderId, options = {}) {
538
+ const { intervalMs = 2e3, timeoutMs = 3e5, authToken } = options;
539
+ const startTime = Date.now();
540
+ const isDebug = process.env.NODE_ENV !== "production";
541
+ const isEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(address);
542
+ const isSuiAddress = /^0x[a-fA-F0-9]{64}$/.test(address);
543
+ const isSolanaAddress = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
544
+ let queryParam;
545
+ if (isEvmAddress) queryParam = `evmWallets=${address}`;
546
+ else if (isSuiAddress) queryParam = `suiWallets=${address}`;
547
+ else if (isSolanaAddress) queryParam = `solanaWallets=${address}`;
548
+ else throw new Error(`Unrecognized wallet address format: ${address}`);
549
+ const queryUrl = `${AUCTIONEER_URL}/user_intent?${queryParam}`;
550
+ return new Promise((resolve, reject) => {
551
+ const pollInterval = setInterval(async () => {
552
+ try {
553
+ if (Date.now() - startTime > timeoutMs) {
554
+ clearInterval(pollInterval);
555
+ return resolve("Timeout");
556
+ }
557
+ const res = await fetch(queryUrl, {
558
+ method: "GET",
559
+ headers: {
560
+ "Content-Type": "application/json",
561
+ // The /user_intent status endpoint requires the SIWE JWT; without it
562
+ // the request 401s after the order has already been submitted.
563
+ ...authToken ? { Authorization: `Bearer ${authToken}` } : {}
564
+ }
565
+ });
566
+ if (!res.ok) {
567
+ clearInterval(pollInterval);
568
+ return reject(
569
+ new Error(`Failed to fetch orders: ${res.status} ${res.statusText}`)
570
+ );
571
+ }
572
+ const json = await res.json();
573
+ const data = json?.data ?? {};
574
+ const allOrders = [
575
+ ...data.crossChainDcaOrders ?? [],
576
+ ...data.crossChainLimitOrders ?? [],
577
+ ...data.singleChainDcaOrders ?? [],
578
+ ...data.singleChainLimitOrders ?? []
579
+ ];
580
+ const targetOrder = allOrders.find((o) => o.orderId === orderId);
581
+ if (!targetOrder) {
582
+ if (isDebug)
583
+ console.debug(`[pollOrderStatus] [${orderId}] Not found yet`);
584
+ return;
585
+ }
586
+ const { orderStatus } = targetOrder;
587
+ if (isDebug) {
588
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
589
+ console.debug(`targetOrder`, targetOrder);
590
+ console.debug(
591
+ `[pollOrderStatus] [${orderId}] status=${orderStatus} (elapsed ${elapsed}s)`
592
+ );
593
+ }
594
+ if (["Fulfilled", "Cancelled", "Outdated"].includes(orderStatus)) {
595
+ clearInterval(pollInterval);
596
+ return resolve(orderStatus);
597
+ }
598
+ } catch (error) {
599
+ clearInterval(pollInterval);
600
+ return reject(error);
601
+ }
602
+ }, intervalMs);
603
+ });
604
+ }
605
+
606
+ // src/core/execute/handleOrderPollingResult.ts
607
+ async function handleOrderPollingResult({
608
+ status,
609
+ orderId,
610
+ chainId,
611
+ update,
612
+ messageFor
613
+ }) {
614
+ switch (status) {
615
+ case "Fulfilled":
616
+ update("success", messageFor("success"));
617
+ return {
618
+ status: true,
619
+ orderId,
620
+ chainId,
621
+ finalStatus: status,
622
+ stage: "success"
623
+ };
624
+ case "Cancelled":
625
+ update("error", "Order was cancelled before fulfillment");
626
+ break;
627
+ case "Timeout":
628
+ update("error", "Order polling timed out");
629
+ break;
630
+ case "NotFound":
631
+ default:
632
+ update("error", "Order not found");
633
+ break;
634
+ }
635
+ return {
636
+ status: false,
637
+ orderId,
638
+ chainId,
639
+ finalStatus: status,
640
+ stage: "error"
641
+ };
642
+ }
643
+
644
+ // src/core/execute/ensurePermit2Allowance.ts
645
+ import { encodeFunctionData, erc20Abi, maxUint256 } from "viem";
646
+ import { PERMIT2_ADDRESS } from "@shogun-sdk/intents-sdk";
647
+ async function ensurePermit2Allowance({
648
+ chainId,
649
+ tokenIn,
650
+ wallet,
651
+ accountAddress,
652
+ requiredAmount,
653
+ increaseByDelta = false
654
+ }) {
655
+ const spender = PERMIT2_ADDRESS[chainId];
656
+ let currentAllowance = 0n;
657
+ try {
658
+ if (!wallet.readContract) {
659
+ throw new Error("Wallet does not implement readContract()");
660
+ }
661
+ currentAllowance = await wallet.readContract({
662
+ address: tokenIn,
663
+ abi: erc20Abi,
664
+ functionName: "allowance",
665
+ args: [accountAddress, spender]
666
+ });
667
+ } catch (error) {
668
+ console.warn(`[Permit2] Failed to read allowance for ${tokenIn}`, error);
669
+ }
670
+ const approvalAmount = increaseByDelta ? currentAllowance + requiredAmount : maxUint256;
671
+ console.debug(
672
+ `[Permit2] Approving ${approvalAmount} for ${tokenIn} (current: ${currentAllowance}, required: ${requiredAmount})`
673
+ );
674
+ await wallet.sendTransaction({
675
+ to: tokenIn,
676
+ from: accountAddress,
677
+ data: encodeFunctionData({
678
+ abi: erc20Abi,
679
+ functionName: "approve",
680
+ args: [spender, approvalAmount]
681
+ }),
682
+ value: 0n
683
+ });
684
+ console.info(
685
+ `[Permit2] Approval transaction sent for ${tokenIn} on chain ${chainId}`
686
+ );
687
+ }
688
+
689
+ // src/core/execute/handleEvmExecution.ts
318
690
  async function handleEvmExecution({
319
691
  recipientAddress,
320
692
  quote,
@@ -322,18 +694,22 @@ async function handleEvmExecution({
322
694
  accountAddress,
323
695
  wallet,
324
696
  isSingleChain,
325
- deadline,
326
- update
697
+ update,
698
+ orderType,
699
+ options
327
700
  }) {
328
- const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
701
+ const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage] ?? "";
702
+ const deadline = options?.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
329
703
  await wallet.switchChain(chainId);
330
704
  const tokenIn = normalizeNative(chainId, quote.tokenIn.address);
705
+ quote.tokenOut.address = normalizeEvmTokenAddress(quote.tokenOut.address);
331
706
  const shouldWrapNative = isNativeAddress(quote.tokenIn.address);
332
707
  update("processing", shouldWrapNative ? `${messageFor("processing")} (wrapping native token)` : messageFor("processing"));
333
708
  if (shouldWrapNative) {
709
+ quote.tokenIn.address === tokenIn;
334
710
  await wallet.sendTransaction({
335
711
  to: tokenIn,
336
- data: encodeFunctionData({
712
+ data: encodeFunctionData2({
337
713
  abi: [{ type: "function", name: "deposit", stateMutability: "payable", inputs: [], outputs: [] }],
338
714
  functionName: "deposit",
339
715
  args: []
@@ -342,44 +718,70 @@ async function handleEvmExecution({
342
718
  from: accountAddress
343
719
  });
344
720
  }
345
- update("approving", messageFor("approving"));
346
- await wallet.sendTransaction({
347
- to: tokenIn,
348
- data: encodeFunctionData({
349
- abi: erc20Abi,
350
- functionName: "approve",
351
- args: [PERMIT2_ADDRESS[chainId], BigInt(quote.amountIn)]
352
- }),
353
- value: 0n,
354
- from: accountAddress
721
+ update("processing", messageFor("approving"));
722
+ await ensurePermit2Allowance({
723
+ chainId,
724
+ tokenIn,
725
+ wallet,
726
+ accountAddress,
727
+ requiredAmount: BigInt(quote.amountIn)
355
728
  });
356
- update("approved", messageFor("approved"));
729
+ update("processing", messageFor("approved"));
357
730
  const destination = recipientAddress ?? accountAddress;
358
731
  const order = await buildOrder({
359
732
  quote,
360
733
  accountAddress,
361
734
  destination,
362
735
  deadline,
363
- isSingleChain
736
+ isSingleChain,
737
+ orderType,
738
+ options
364
739
  });
365
- update("signing", messageFor("signing"));
740
+ console.debug(`order`, order);
741
+ update("processing", messageFor("signing"));
366
742
  const { orderTypedData, nonce } = isSingleChain ? await getEVMSingleChainOrderTypedData(order) : await getEVMCrossChainOrderTypedData(order);
743
+ const typedData = serializeBigIntsToStrings(orderTypedData);
367
744
  if (!wallet.signTypedData) {
368
745
  throw new Error("Wallet does not support EIP-712 signing");
369
746
  }
370
- const signature = await wallet.signTypedData(serializeBigIntsToStrings(orderTypedData));
371
- update("submitting", messageFor("submitting"));
747
+ const signature = await wallet.signTypedData({
748
+ domain: typedData.domain,
749
+ types: typedData.types,
750
+ primaryType: typedData.primaryType,
751
+ value: typedData.message,
752
+ message: typedData.message
753
+ });
754
+ update("processing", messageFor("submitting"));
372
755
  const res = await order.sendToAuctioneer({ signature, nonce: nonce.toString() });
373
756
  if (!res.success) {
374
757
  throw new Error("Auctioneer submission failed");
375
758
  }
376
- update("success", messageFor("success"));
377
- return { status: true, txHash: res.data, chainId, stage: "success" };
759
+ update("initiated", messageFor("initiated"));
760
+ const { intentId: orderId } = res.data;
761
+ update("initiated", messageFor("shogun_processing"));
762
+ if (orderType === "limit" /* LIMIT */) {
763
+ update("success", messageFor("success_limit"));
764
+ return {
765
+ status: true,
766
+ orderId,
767
+ chainId,
768
+ finalStatus: "OrderPlaced",
769
+ stage: "success"
770
+ };
771
+ } else {
772
+ const status = await pollOrderStatus(accountAddress, orderId, { authToken: options?.authToken });
773
+ return await handleOrderPollingResult({
774
+ status,
775
+ orderId,
776
+ chainId,
777
+ update,
778
+ messageFor
779
+ });
780
+ }
378
781
  }
379
782
 
380
- // src/core/executeOrder/handleSolanaExecution.ts
783
+ // src/core/execute/handleSolanaExecution.ts
381
784
  import {
382
- ChainID as ChainID3,
383
785
  getSolanaSingleChainOrderInstructions,
384
786
  getSolanaCrossChainOrderInstructions
385
787
  } from "@shogun-sdk/intents-sdk";
@@ -391,12 +793,14 @@ async function handleSolanaExecution({
391
793
  isSingleChain,
392
794
  update,
393
795
  accountAddress,
394
- deadline
796
+ orderType,
797
+ options
395
798
  }) {
396
799
  if (!wallet.rpcUrl) {
397
800
  throw new Error("Solana wallet is missing rpcUrl");
398
801
  }
399
- const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
802
+ const deadline = options?.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
803
+ const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage] ?? "";
400
804
  update("processing", messageFor("processing"));
401
805
  const destination = recipientAddress ?? accountAddress;
402
806
  const order = await buildOrder({
@@ -404,7 +808,9 @@ async function handleSolanaExecution({
404
808
  accountAddress,
405
809
  destination,
406
810
  deadline,
407
- isSingleChain
811
+ isSingleChain,
812
+ orderType,
813
+ options
408
814
  });
409
815
  const txData = await getSolanaOrderInstructions({
410
816
  order,
@@ -412,10 +818,9 @@ async function handleSolanaExecution({
412
818
  rpcUrl: wallet.rpcUrl
413
819
  });
414
820
  const transaction = VersionedTransaction.deserialize(Uint8Array.from(txData.txBytes));
415
- update("signing", messageFor("signing"));
416
- console.log({ order });
417
- const txSignature = await wallet.sendTransaction(transaction);
418
- update("submitting", messageFor("submitting"));
821
+ update("processing", messageFor("signing"));
822
+ await wallet.sendTransaction(transaction);
823
+ update("processing", messageFor("submitting"));
419
824
  const response = await submitToAuctioneer({
420
825
  order,
421
826
  isSingleChain,
@@ -424,13 +829,28 @@ async function handleSolanaExecution({
424
829
  if (!response.success) {
425
830
  throw new Error("Auctioneer submission failed");
426
831
  }
427
- update("success", messageFor("success"));
428
- return {
429
- status: true,
430
- txHash: txSignature,
431
- chainId: ChainID3.Solana,
432
- stage: "success"
433
- };
832
+ update("initiated", messageFor("initiated"));
833
+ const { intentId: orderId } = response.data;
834
+ update("initiated", messageFor("shogun_processing"));
835
+ if (orderType === "limit" /* LIMIT */) {
836
+ update("success", messageFor("success_limit"));
837
+ return {
838
+ status: true,
839
+ orderId,
840
+ chainId: SOLANA_CHAIN_ID,
841
+ finalStatus: "OrderPlaced",
842
+ stage: "success"
843
+ };
844
+ } else {
845
+ const status = await pollOrderStatus(accountAddress, orderId, { authToken: options?.authToken });
846
+ return await handleOrderPollingResult({
847
+ status,
848
+ orderId,
849
+ chainId: SOLANA_CHAIN_ID,
850
+ update,
851
+ messageFor
852
+ });
853
+ }
434
854
  }
435
855
  async function getSolanaOrderInstructions({
436
856
  order,
@@ -463,52 +883,93 @@ async function submitToAuctioneer({
463
883
  });
464
884
  }
465
885
 
466
- // src/core/executeOrder/execute.ts
886
+ // src/core/execute/execute.ts
467
887
  async function executeOrder({
468
888
  quote,
469
889
  accountAddress,
470
890
  recipientAddress,
471
891
  wallet,
472
892
  onStatus,
893
+ orderType = "market" /* MARKET */,
473
894
  options = {}
474
895
  }) {
475
- const deadline = options.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
896
+ const isDev = process.env.NODE_ENV !== "production";
897
+ const log = (...args) => {
898
+ if (isDev) console.debug("[OneShot::executeOrder]", ...args);
899
+ };
476
900
  const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
477
- const update = (stage, message) => onStatus?.(stage, message ?? messageFor(stage));
901
+ const update = (stage, message) => {
902
+ log("Stage:", stage, "| Message:", message ?? messageFor(stage));
903
+ onStatus?.(stage, message ?? messageFor(stage));
904
+ };
478
905
  try {
906
+ log("Starting execution:", {
907
+ accountAddress,
908
+ recipientAddress,
909
+ tokenIn: quote?.tokenIn,
910
+ tokenOut: quote?.tokenOut
911
+ });
479
912
  const adapter = normalizeWallet(wallet);
480
913
  if (!adapter) throw new Error("No wallet provided");
481
914
  const { tokenIn, tokenOut } = quote;
915
+ const srcChain = Number(tokenIn.chainId);
916
+ const destChain = Number(tokenOut.chainId);
917
+ if (!CURRENT_SUPPORTED.includes(srcChain) || !CURRENT_SUPPORTED.includes(destChain)) {
918
+ const unsupportedChains = [
919
+ !CURRENT_SUPPORTED.includes(srcChain) ? srcChain : null,
920
+ !CURRENT_SUPPORTED.includes(destChain) ? destChain : null
921
+ ].filter(Boolean).join(", ");
922
+ const errorMsg = `Unsupported chain(s): ${unsupportedChains}`;
923
+ update("error", errorMsg);
924
+ log("Error:", errorMsg);
925
+ throw new Error(errorMsg);
926
+ }
482
927
  const isSingleChain = tokenIn.chainId === tokenOut.chainId;
483
928
  const chainId = Number(tokenIn.chainId);
484
- update("processing", messageFor("processing"));
485
- if (isEvmChain2(chainId)) {
486
- return await handleEvmExecution({
929
+ update("processing");
930
+ if (isEvmChain3(chainId)) {
931
+ log("Detected EVM chain:", chainId);
932
+ const result = await handleEvmExecution({
487
933
  recipientAddress,
488
934
  quote,
489
935
  chainId,
490
936
  accountAddress,
491
937
  wallet: adapter,
492
938
  isSingleChain,
493
- deadline,
494
- update
939
+ update,
940
+ orderType,
941
+ options
495
942
  });
943
+ log("EVM execution result:", result);
944
+ return result;
496
945
  }
497
- if (chainId === ChainID4.Solana) {
498
- return await handleSolanaExecution({
946
+ if (chainId === ChainID2.Solana) {
947
+ log("Detected Solana chain");
948
+ const result = await handleSolanaExecution({
499
949
  recipientAddress,
500
950
  quote,
501
951
  accountAddress,
502
952
  wallet: adapter,
503
953
  isSingleChain,
504
- deadline,
505
- update
954
+ update,
955
+ orderType,
956
+ options
506
957
  });
958
+ log("Solana execution result:", result);
959
+ return result;
507
960
  }
508
- update("error", "Unsupported chain");
509
- return { status: false, message: "Unsupported chain", stage: "error" };
961
+ const unsupported = `Unsupported chain: ${chainId}`;
962
+ update("error", unsupported);
963
+ log("Error:", unsupported);
964
+ return { status: false, message: unsupported, stage: "error" };
510
965
  } catch (error) {
511
- const message = error instanceof BaseError ? error.shortMessage : error instanceof Error ? error.message : String(error);
966
+ let message = "An unknown error occurred";
967
+ if (error && typeof error === "object") {
968
+ const err = error;
969
+ message = err.details ?? err.message ?? message;
970
+ } else if (typeof error === "string") {
971
+ message = error;
972
+ }
512
973
  update("error", message);
513
974
  return { status: false, message, stage: "error" };
514
975
  }
@@ -519,348 +980,998 @@ function normalizeWallet(wallet) {
519
980
  return wallet;
520
981
  }
521
982
 
522
- // src/react/useExecuteOrder.ts
523
- function useExecuteOrder() {
524
- const [status, setStatus] = useState2("processing");
525
- const [message, setMessage] = useState2(null);
526
- const [loading, setLoading] = useState2(false);
527
- const [data, setData] = useState2(null);
528
- const [error, setError] = useState2(null);
529
- const isMounted = useRef2(true);
530
- useEffect2(() => {
531
- return () => {
532
- isMounted.current = false;
533
- };
534
- }, []);
535
- const execute = useCallback(
983
+ // src/core/orders/getOrders.ts
984
+ import { fetchUserOrders } from "@shogun-sdk/intents-sdk";
985
+ async function getOrders({
986
+ evmAddress,
987
+ solAddress
988
+ }) {
989
+ const wallets = [evmAddress, solAddress].filter(
990
+ (wallet) => Boolean(wallet)
991
+ );
992
+ if (wallets.length === 0) {
993
+ throw new Error("At least one wallet address (EVM, Solana) must be provided.");
994
+ }
995
+ const results = await Promise.all(
996
+ wallets.map((wallet) => fetchUserOrders({ wallet }))
997
+ );
998
+ return results.reduce(
999
+ (merged, orders) => {
1000
+ merged.singleChainLimitOrders.push(...orders.singleChainLimitOrders);
1001
+ merged.crossChainLimitOrders.push(...orders.crossChainLimitOrders);
1002
+ return merged;
1003
+ },
1004
+ { singleChainLimitOrders: [], crossChainLimitOrders: [] }
1005
+ );
1006
+ }
1007
+
1008
+ // src/core/orders/cancelOrder.ts
1009
+ import {
1010
+ cancelCrossChainOrderInstructionsAsBytes,
1011
+ cancelSingleChainOrderInstructionsAsBytes,
1012
+ ChainID as ChainID3,
1013
+ CROSS_CHAIN_GUARD_ADDRESSES,
1014
+ PERMIT2_ADDRESS as PERMIT2_ADDRESS2,
1015
+ SINGLE_CHAIN_GUARD_ADDRESSES
1016
+ } from "@shogun-sdk/intents-sdk";
1017
+ import {
1018
+ VersionedMessage,
1019
+ VersionedTransaction as VersionedTransaction2
1020
+ } from "@solana/web3.js";
1021
+ import { encodeFunctionData as encodeFunctionData3 } from "viem";
1022
+ var inFlightCancels = /* @__PURE__ */ new Map();
1023
+ function cancelIntentsOrder(args) {
1024
+ const srcChain = "srcChainId" in args.order ? args.order.srcChainId : args.order.chainId;
1025
+ const key = `${srcChain}:${args.order.orderId}`;
1026
+ const existing = inFlightCancels.get(key);
1027
+ if (existing) return existing;
1028
+ const pending = performCancel(args).finally(() => {
1029
+ inFlightCancels.delete(key);
1030
+ });
1031
+ inFlightCancels.set(key, pending);
1032
+ return pending;
1033
+ }
1034
+ async function performCancel({
1035
+ order,
1036
+ wallet,
1037
+ sol_rpc
1038
+ }) {
1039
+ const isCrossChain = "srcChainId" in order && "destChainId" in order && order.srcChainId !== order.destChainId;
1040
+ const srcChain = "srcChainId" in order ? order.srcChainId : order.chainId;
1041
+ const isSolanaOrder = srcChain === ChainID3.Solana;
1042
+ if (isSolanaOrder) {
1043
+ if (!isCrossChain) {
1044
+ const { versionedMessageBytes: versionedMessageBytes2 } = await cancelSingleChainOrderInstructionsAsBytes(
1045
+ order.orderId,
1046
+ order.user,
1047
+ { rpcUrl: sol_rpc }
1048
+ );
1049
+ const message2 = VersionedMessage.deserialize(
1050
+ versionedMessageBytes2
1051
+ );
1052
+ const tx2 = new VersionedTransaction2(message2);
1053
+ return await wallet.sendTransaction(tx2);
1054
+ }
1055
+ const { versionedMessageBytes } = await cancelCrossChainOrderInstructionsAsBytes(
1056
+ order.orderId,
1057
+ order.user,
1058
+ { rpcUrl: sol_rpc }
1059
+ );
1060
+ const message = VersionedMessage.deserialize(
1061
+ versionedMessageBytes
1062
+ );
1063
+ const tx = new VersionedTransaction2(message);
1064
+ return await wallet.sendTransaction(tx);
1065
+ }
1066
+ const chainId = srcChain;
1067
+ const { readContract } = wallet;
1068
+ if (!readContract) {
1069
+ throw new Error("Wallet does not support readContract");
1070
+ }
1071
+ const nonce = BigInt(order.nonce ?? "0");
1072
+ const nonceWordPos = nonce >> 8n;
1073
+ const nonceBitPos = nonce - nonceWordPos * 256n;
1074
+ if (isCrossChain) {
1075
+ const [orderData = [false, false], currentNonceBitmap = 0n] = await Promise.all([
1076
+ readContract({
1077
+ address: CROSS_CHAIN_GUARD_ADDRESSES[chainId],
1078
+ abi: [
1079
+ {
1080
+ inputs: [{ name: "orderId", type: "bytes32" }],
1081
+ name: "orderData",
1082
+ outputs: [
1083
+ { name: "initialized", type: "bool" },
1084
+ { name: "deactivated", type: "bool" }
1085
+ ],
1086
+ stateMutability: "view",
1087
+ type: "function"
1088
+ }
1089
+ ],
1090
+ functionName: "orderData",
1091
+ args: [order.orderId]
1092
+ }),
1093
+ readContract({
1094
+ address: PERMIT2_ADDRESS2[chainId],
1095
+ abi: [
1096
+ {
1097
+ inputs: [
1098
+ { name: "owner", type: "address" },
1099
+ { name: "wordPos", type: "uint256" }
1100
+ ],
1101
+ name: "nonceBitmap",
1102
+ outputs: [{ name: "", type: "uint256" }],
1103
+ stateMutability: "view",
1104
+ type: "function"
1105
+ }
1106
+ ],
1107
+ functionName: "nonceBitmap",
1108
+ args: [order.user, nonceWordPos]
1109
+ })
1110
+ ]);
1111
+ const [initialized, deactivated] = orderData;
1112
+ if (initialized) {
1113
+ if (deactivated) {
1114
+ throw new Error("Order is already deactivated");
1115
+ }
1116
+ return await wallet.sendTransaction({
1117
+ to: CROSS_CHAIN_GUARD_ADDRESSES[order.srcChainId],
1118
+ data: encodeFunctionData3({
1119
+ abi: [
1120
+ {
1121
+ inputs: [
1122
+ {
1123
+ components: [
1124
+ { name: "user", type: "address" },
1125
+ { name: "tokenIn", type: "address" },
1126
+ { name: "srcChainId", type: "uint256" },
1127
+ { name: "deadline", type: "uint256" },
1128
+ { name: "amountIn", type: "uint256" },
1129
+ { name: "minStablecoinsAmount", type: "uint256" },
1130
+ { name: "executionDetailsHash", type: "bytes32" },
1131
+ { name: "nonce", type: "uint256" }
1132
+ ],
1133
+ name: "orderInfo",
1134
+ type: "tuple"
1135
+ }
1136
+ ],
1137
+ name: "cancelOrder",
1138
+ outputs: [],
1139
+ stateMutability: "nonpayable",
1140
+ type: "function"
1141
+ }
1142
+ ],
1143
+ functionName: "cancelOrder",
1144
+ args: [
1145
+ {
1146
+ user: order.user,
1147
+ tokenIn: order.tokenIn,
1148
+ srcChainId: BigInt(order.srcChainId),
1149
+ deadline: BigInt(order.deadline),
1150
+ amountIn: BigInt(order.amountIn),
1151
+ minStablecoinsAmount: BigInt(order.minStablecoinsAmount),
1152
+ executionDetailsHash: order.executionDetailsHash,
1153
+ nonce
1154
+ }
1155
+ ]
1156
+ }),
1157
+ value: BigInt(0),
1158
+ from: order.user
1159
+ });
1160
+ } else {
1161
+ if ((currentNonceBitmap & 1n << nonceBitPos) !== 0n) {
1162
+ throw new Error("Nonce is already invalidated");
1163
+ }
1164
+ const mask2 = 1n << nonceBitPos;
1165
+ return await wallet.sendTransaction({
1166
+ to: PERMIT2_ADDRESS2[order.srcChainId],
1167
+ data: encodeFunctionData3({
1168
+ abi: [
1169
+ {
1170
+ inputs: [
1171
+ { name: "wordPos", type: "uint256" },
1172
+ { name: "mask", type: "uint256" }
1173
+ ],
1174
+ name: "invalidateUnorderedNonces",
1175
+ outputs: [],
1176
+ stateMutability: "nonpayable",
1177
+ type: "function"
1178
+ }
1179
+ ],
1180
+ functionName: "invalidateUnorderedNonces",
1181
+ args: [nonceWordPos, mask2]
1182
+ }),
1183
+ value: BigInt(0),
1184
+ from: order.user
1185
+ });
1186
+ }
1187
+ }
1188
+ const [wasManuallyInitialized, currentBitmap = 0n] = await Promise.all([
1189
+ readContract({
1190
+ address: SINGLE_CHAIN_GUARD_ADDRESSES[chainId],
1191
+ abi: [
1192
+ {
1193
+ inputs: [{ name: "orderHash", type: "bytes32" }],
1194
+ name: "orderManuallyInitialized",
1195
+ outputs: [{ name: "", type: "bool" }],
1196
+ stateMutability: "view",
1197
+ type: "function"
1198
+ }
1199
+ ],
1200
+ functionName: "orderManuallyInitialized",
1201
+ args: [order.orderId]
1202
+ }),
1203
+ readContract({
1204
+ address: PERMIT2_ADDRESS2[chainId],
1205
+ abi: [
1206
+ {
1207
+ inputs: [
1208
+ { name: "owner", type: "address" },
1209
+ { name: "wordPos", type: "uint256" }
1210
+ ],
1211
+ name: "nonceBitmap",
1212
+ outputs: [{ name: "", type: "uint256" }],
1213
+ stateMutability: "view",
1214
+ type: "function"
1215
+ }
1216
+ ],
1217
+ functionName: "nonceBitmap",
1218
+ args: [order.user, nonceWordPos]
1219
+ })
1220
+ ]);
1221
+ if (wasManuallyInitialized) {
1222
+ return await wallet.sendTransaction({
1223
+ to: SINGLE_CHAIN_GUARD_ADDRESSES[chainId],
1224
+ data: encodeFunctionData3({
1225
+ abi: [
1226
+ {
1227
+ inputs: [
1228
+ {
1229
+ name: "order",
1230
+ type: "tuple",
1231
+ components: [
1232
+ { name: "amountIn", type: "uint256" },
1233
+ { name: "tokenIn", type: "address" },
1234
+ { name: "deadline", type: "uint256" },
1235
+ { name: "nonce", type: "uint256" },
1236
+ { name: "encodedExternalCallData", type: "bytes" },
1237
+ {
1238
+ name: "extraTransfers",
1239
+ type: "tuple[]",
1240
+ components: [
1241
+ { name: "amount", type: "uint256" },
1242
+ { name: "receiver", type: "address" },
1243
+ { name: "token", type: "address" }
1244
+ ]
1245
+ },
1246
+ {
1247
+ name: "requestedOutput",
1248
+ type: "tuple",
1249
+ components: [
1250
+ { name: "amount", type: "uint256" },
1251
+ { name: "receiver", type: "address" },
1252
+ { name: "token", type: "address" }
1253
+ ]
1254
+ },
1255
+ { name: "user", type: "address" }
1256
+ ]
1257
+ }
1258
+ ],
1259
+ name: "cancelManuallyCreatedOrder",
1260
+ outputs: [],
1261
+ stateMutability: "nonpayable",
1262
+ type: "function"
1263
+ }
1264
+ ],
1265
+ functionName: "cancelManuallyCreatedOrder",
1266
+ args: [
1267
+ {
1268
+ amountIn: BigInt(order.amountIn),
1269
+ tokenIn: order.tokenIn,
1270
+ deadline: BigInt(order.deadline),
1271
+ nonce,
1272
+ encodedExternalCallData: "0x",
1273
+ extraTransfers: (order.extraTransfers || []).map((t) => ({
1274
+ amount: BigInt(t.amount),
1275
+ receiver: t.receiver,
1276
+ token: t.token
1277
+ })),
1278
+ requestedOutput: {
1279
+ amount: BigInt(order.amountOutMin),
1280
+ receiver: order.destinationAddress,
1281
+ token: order.tokenOut
1282
+ },
1283
+ user: order.user
1284
+ }
1285
+ ]
1286
+ }),
1287
+ value: 0n,
1288
+ from: order.user
1289
+ });
1290
+ }
1291
+ const mask = 1n << nonceBitPos;
1292
+ if ((currentBitmap & mask) !== 0n) {
1293
+ throw new Error("Nonce is already invalidated");
1294
+ }
1295
+ return await wallet.sendTransaction({
1296
+ to: PERMIT2_ADDRESS2[chainId],
1297
+ data: encodeFunctionData3({
1298
+ abi: [
1299
+ {
1300
+ inputs: [
1301
+ { name: "wordPos", type: "uint256" },
1302
+ { name: "mask", type: "uint256" }
1303
+ ],
1304
+ name: "invalidateUnorderedNonces",
1305
+ outputs: [],
1306
+ stateMutability: "nonpayable",
1307
+ type: "function"
1308
+ }
1309
+ ],
1310
+ functionName: "invalidateUnorderedNonces",
1311
+ args: [nonceWordPos, mask]
1312
+ }),
1313
+ value: 0n,
1314
+ from: order.user
1315
+ });
1316
+ }
1317
+
1318
+ // src/core/client.ts
1319
+ var SwapSDK = class {
1320
+ constructor(config) {
1321
+ __publicField(this, "apiKey");
1322
+ /**
1323
+ * Fetches metadata for one or more tokens from the Shogun Token Search API.
1324
+ *
1325
+ * ---
1326
+ * ### Overview
1327
+ * `getTokensData` retrieves normalized token information — such as symbol, name,
1328
+ * decimals, logo URI, and verified status — for a given list of token addresses.
1329
+ *
1330
+ * It supports both **EVM** and **SVM (Solana)** tokens, returning metadata from
1331
+ * Shogun’s unified token registry.
1332
+ *
1333
+ * ---
1334
+ * @example
1335
+ * ```ts
1336
+ * const tokens = await getTokensData([
1337
+ * "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
1338
+ * "So11111111111111111111111111111111111111112", // SOL
1339
+ * ]);
1340
+ *
1341
+ * console.log(tokens);
1342
+ * [
1343
+ * { symbol: "USDC", name: "USD Coin", chainId: 1, decimals: 6, ... },
1344
+ * { symbol: "SOL", name: "Solana", chainId: 101, decimals: 9, ... }
1345
+ * ]
1346
+ * ```
1347
+ *
1348
+ * @param addresses - An array of token addresses (EVM or SVM) to fetch metadata for.
1349
+ * @returns A promise resolving to an array of {@link TokenInfo} objects.
1350
+ *
1351
+ * @throws Will throw an error if the network request fails or the API responds with a non-OK status.
1352
+ */
1353
+ __publicField(this, "getTokensData", getTokensData.bind(this));
1354
+ if (!config.apiKey) {
1355
+ throw new Error("SwapSDK: Missing API key");
1356
+ }
1357
+ this.apiKey = config.apiKey;
1358
+ if (this.apiKey) void this.apiKey;
1359
+ }
1360
+ /**
1361
+ * Retrieves a swap quote for the given input and output tokens.
1362
+ *
1363
+ * @param params - Quote parameters including source/destination tokens and amount.
1364
+ * @returns A normalized `SwapQuoteResponse` containing output amount, route, and metadata.
1365
+ */
1366
+ async getQuote(params) {
1367
+ return getQuote(params);
1368
+ }
1369
+ /**
1370
+ * Fetches token balances for the specified user wallet(s).
1371
+ *
1372
+ * Supports both EVM and SVM (Solana) wallet addresses.
1373
+ *
1374
+ * @param params - Wallet address and optional chain filters.
1375
+ * @param options - Optional abort signal for cancellation.
1376
+ * @returns A unified balance response with per-chain token details.
1377
+ */
1378
+ async getBalances(params, options) {
1379
+ return getBalances(params, options);
1380
+ }
1381
+ /**
1382
+ * Retrieves a list of verified tokens based on search query or chain filter.
1383
+ *
1384
+ * @param params - Search parameters (query, chain ID, pagination options).
1385
+ * @returns Paginated `TokenSearchResponse` containing token metadata.
1386
+ */
1387
+ async getTokenList(params) {
1388
+ return getTokenList(params);
1389
+ }
1390
+ /**
1391
+ * Executes a prepared swap quote using the provided wallet and configuration.
1392
+ *
1393
+ * Handles:
1394
+ * - Token approval (if required)
1395
+ * - Transaction signing and broadcasting
1396
+ * - Confirmation polling and stage-based status updates
1397
+ *
1398
+ * Supports both:
1399
+ * - Market orders (with optional deadline)
1400
+ * - Limit orders (requires executionPrice and deadline)
1401
+ *
1402
+ * @param quote - The swap quote to execute, containing route and metadata.
1403
+ * @param accountAddress - The user's wallet address executing the swap.
1404
+ * @param recipientAddress - Optional recipient address for the output tokens (defaults to sender).
1405
+ * @param wallet - Adapted wallet instance (EVM/Solana) or a standard Viem `WalletClient`.
1406
+ * @param onStatus - Optional callback for receiving execution stage updates and messages.
1407
+ * @param orderType - Defines whether this is a market or limit order.
1408
+ * @param options - Execution parameters (different per order type).
1409
+ *
1410
+ * @returns A finalized execution result containing transaction hash, status, and any returned data.
1411
+ *
1412
+ * @example
1413
+ * ```ts
1414
+ * * Market order
1415
+ * const result = await sdk.executeTransaction({
1416
+ * quote,
1417
+ * accountAddress: "0x123...",
1418
+ * wallet,
1419
+ * orderType: OrderExecutionType.MARKET,
1420
+ * options: { deadline: 1800 },
1421
+ * onStatus: (stage, msg) => console.log(stage, msg),
1422
+ * });
1423
+ *
1424
+ * * Limit order
1425
+ * const result = await sdk.executeTransaction({
1426
+ * quote,
1427
+ * accountAddress: "0x123...",
1428
+ * wallet,
1429
+ * orderType: OrderExecutionType.LIMIT,
1430
+ * options: { executionPrice: "0.0021", deadline: 3600 },
1431
+ * });
1432
+ * ```
1433
+ */
1434
+ async executeTransaction({
1435
+ quote,
1436
+ accountAddress,
1437
+ recipientAddress,
1438
+ wallet,
1439
+ onStatus,
1440
+ orderType,
1441
+ options
1442
+ }) {
1443
+ return executeOrder({
1444
+ quote,
1445
+ wallet,
1446
+ accountAddress,
1447
+ recipientAddress,
1448
+ onStatus,
1449
+ orderType,
1450
+ options
1451
+ });
1452
+ }
1453
+ /**
1454
+ * Fetches all user orders (Market, Limit, Cross-chain) for connected wallets.
1455
+ *
1456
+ * ---
1457
+ * ### Overview
1458
+ * Retrieves both **single-chain** and **cross-chain** orders from the Shogun Intents API.
1459
+ * Works across EVM, Solana
1460
+ *
1461
+ * ---
1462
+ * @example
1463
+ * ```ts
1464
+ * const orders = await sdk.getOrders({
1465
+ * evmAddress: "0x123...",
1466
+ * solAddress: "9d12hF...abc",
1467
+ * });
1468
+ *
1469
+ * console.log(orders.singleChainLimitOrders);
1470
+ * ```
1471
+ *
1472
+ * @param params - Wallet addresses to fetch orders for (EVM, Solana).
1473
+ * @returns A structured {@link ApiUserOrders} object containing all user orders.
1474
+ */
1475
+ async getOrders(params) {
1476
+ return getOrders(params);
1477
+ }
1478
+ /**
1479
+ * Cancels an order (single-chain or cross-chain) on EVM or Solana.
1480
+ *
1481
+ * Internally routes to the correct guard contract or Solana program to
1482
+ * deactivate the order or invalidate its nonce.
1483
+ *
1484
+ * @param params.order - Order payload returned from the Intents API.
1485
+ * @param params.wallet - Adapted wallet used to submit the cancellation transaction.
1486
+ * @param params.solRpc - Solana RPC URL used for SVM cancellations.
1487
+ *
1488
+ * @returns A transaction signature or hash from the underlying wallet.
1489
+ */
1490
+ async cancelOrder(params) {
1491
+ return cancelIntentsOrder({
1492
+ order: params.order,
1493
+ wallet: params.wallet,
1494
+ sol_rpc: params.solRpc
1495
+ });
1496
+ }
1497
+ };
1498
+
1499
+ // src/react/SwapProvider.tsx
1500
+ import { jsx } from "react/jsx-runtime";
1501
+ var SwapContext = createContext(null);
1502
+ var SwapProvider = ({ config, children }) => {
1503
+ const sdk = useMemo(() => new SwapSDK(config), [config.apiKey]);
1504
+ return /* @__PURE__ */ jsx(SwapContext.Provider, { value: sdk, children });
1505
+ };
1506
+ var useSwap = () => {
1507
+ const ctx = useContext(SwapContext);
1508
+ if (!ctx) {
1509
+ throw new Error("useSwap must be used within <SwapProvider>");
1510
+ }
1511
+ return ctx;
1512
+ };
1513
+
1514
+ // src/react/useQuote.ts
1515
+ import { useCallback, useEffect, useMemo as useMemo2, useRef } from "react";
1516
+ import useSWR, { mutate } from "swr";
1517
+ var QUOTE_KEY = "quote-global";
1518
+ function isValidQuoteParams(p) {
1519
+ if (!p) return false;
1520
+ const { tokenIn, tokenOut, amount } = p;
1521
+ if (!tokenIn || !tokenOut || !amount) return false;
1522
+ const n = Number(amount);
1523
+ return Number.isFinite(n) && n > 0;
1524
+ }
1525
+ function useQuote(initialParams) {
1526
+ const sdk = useSwap();
1527
+ const abortRef = useRef(null);
1528
+ const paramsRef = globalThis.__QUOTE_PARAMS_REF__ ?? (globalThis.__QUOTE_PARAMS_REF__ = { current: initialParams ?? null });
1529
+ useEffect(() => {
1530
+ if (initialParams && JSON.stringify(paramsRef.current) !== JSON.stringify(initialParams)) {
1531
+ paramsRef.current = initialParams;
1532
+ void mutate(QUOTE_KEY);
1533
+ }
1534
+ }, [initialParams]);
1535
+ const fetcher = useCallback(async () => {
1536
+ const activeParams = paramsRef.current;
1537
+ if (!isValidQuoteParams(activeParams)) return null;
1538
+ if (abortRef.current) abortRef.current.abort();
1539
+ const controller = new AbortController();
1540
+ abortRef.current = controller;
1541
+ try {
1542
+ return await sdk.getQuote(activeParams);
1543
+ } catch (err) {
1544
+ if (err instanceof DOMException && err.name === "AbortError") return null;
1545
+ if (err instanceof Error) throw err;
1546
+ throw new Error(String(err));
1547
+ }
1548
+ }, [sdk]);
1549
+ const {
1550
+ data,
1551
+ error,
1552
+ isValidating: loading,
1553
+ mutate: mutateQuote
1554
+ } = useSWR(QUOTE_KEY, fetcher, {
1555
+ revalidateOnFocus: false,
1556
+ shouldRetryOnError: false,
1557
+ keepPreviousData: true
1558
+ });
1559
+ const refetch = useCallback(async () => {
1560
+ await mutateQuote();
1561
+ }, [mutateQuote]);
1562
+ const setParams = useCallback(
1563
+ (next) => {
1564
+ paramsRef.current = next;
1565
+ void mutate(QUOTE_KEY);
1566
+ },
1567
+ []
1568
+ );
1569
+ return useMemo2(
1570
+ () => ({
1571
+ data,
1572
+ loading,
1573
+ error: error instanceof Error ? error.message : null,
1574
+ refetch,
1575
+ setParams,
1576
+ get activeParams() {
1577
+ return paramsRef.current;
1578
+ }
1579
+ }),
1580
+ [data, loading, error, refetch, setParams]
1581
+ );
1582
+ }
1583
+
1584
+ // src/react/useExecuteTransaction.ts
1585
+ import { useState, useCallback as useCallback3 } from "react";
1586
+
1587
+ // src/react/useBalances.ts
1588
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2 } from "react";
1589
+ import useSWR2, { mutate as mutate2 } from "swr";
1590
+ var BALANCE_KEY = "balances-global";
1591
+ function useBalances(initialParams, options) {
1592
+ const sdk = useSwap();
1593
+ const abortRef = useRef2(null);
1594
+ const lastDataRef = useRef2(null);
1595
+ const paramsRef = globalThis.__BALANCES_PARAMS_REF__ ?? (globalThis.__BALANCES_PARAMS_REF__ = { current: initialParams ?? null });
1596
+ const fetcher = useCallback2(async () => {
1597
+ const activeParams = paramsRef.current;
1598
+ if (!activeParams) return null;
1599
+ if (abortRef.current) abortRef.current.abort();
1600
+ const controller = new AbortController();
1601
+ abortRef.current = controller;
1602
+ try {
1603
+ const balances = await sdk.getBalances(activeParams, { signal: controller.signal });
1604
+ lastDataRef.current = balances;
1605
+ return balances;
1606
+ } catch (err) {
1607
+ if (err instanceof DOMException && err.name === "AbortError") return lastDataRef.current;
1608
+ if (err instanceof Error) throw err;
1609
+ throw new Error(String(err));
1610
+ }
1611
+ }, [sdk, paramsRef]);
1612
+ const {
1613
+ data,
1614
+ error,
1615
+ // `isLoading` is only true on the initial fetch (no data yet), so background
1616
+ // refreshes on `refreshInterval` don't toggle it and flicker the UI.
1617
+ isLoading: loading,
1618
+ isValidating,
1619
+ mutate: mutateBalances
1620
+ } = useSWR2(BALANCE_KEY, fetcher, {
1621
+ revalidateOnFocus: false,
1622
+ shouldRetryOnError: false,
1623
+ keepPreviousData: true,
1624
+ // 0 (default) disables polling; a positive value re-fetches on that interval.
1625
+ refreshInterval: options?.refreshInterval ?? 0
1626
+ });
1627
+ const refetch = useCallback2(async () => {
1628
+ await mutateBalances();
1629
+ }, [mutateBalances]);
1630
+ const setParams = useCallback2((next) => {
1631
+ paramsRef.current = next;
1632
+ void mutate2(BALANCE_KEY);
1633
+ }, [paramsRef]);
1634
+ useEffect2(() => {
1635
+ if (initialParams) {
1636
+ const prevParams = paramsRef.current;
1637
+ const paramsChanged = !prevParams || prevParams.addresses.svm !== initialParams.addresses.svm || prevParams.addresses.evm !== initialParams.addresses.evm;
1638
+ if (paramsChanged) {
1639
+ paramsRef.current = initialParams;
1640
+ void mutate2(BALANCE_KEY);
1641
+ }
1642
+ }
1643
+ }, [initialParams, paramsRef]);
1644
+ return useMemo3(
1645
+ () => ({
1646
+ data,
1647
+ loading,
1648
+ // True during background revalidation (e.g. `refreshInterval` polls) while
1649
+ // previous `data` stays visible — use for a subtle refresh indicator.
1650
+ isValidating,
1651
+ error: error instanceof Error ? error : null,
1652
+ refetch,
1653
+ setParams,
1654
+ get activeParams() {
1655
+ return paramsRef.current;
1656
+ }
1657
+ }),
1658
+ [data, loading, isValidating, error, refetch, setParams, paramsRef]
1659
+ );
1660
+ }
1661
+
1662
+ // src/react/useExecuteTransaction.ts
1663
+ function useExecuteTransaction() {
1664
+ const sdk = useSwap();
1665
+ const [isLoading, setLoading] = useState(false);
1666
+ const [stage, setStage] = useState(null);
1667
+ const [message, setMessage] = useState(null);
1668
+ const [error, setError] = useState(null);
1669
+ const [result, setResult] = useState(null);
1670
+ const { refetch: refetchQuote } = useQuote();
1671
+ const { refetch: refetchBalances } = useBalances();
1672
+ const execute = useCallback3(
536
1673
  async ({
537
1674
  quote,
538
1675
  accountAddress,
539
1676
  recipientAddress,
540
1677
  wallet,
541
- deadline
1678
+ orderType,
1679
+ options
542
1680
  }) => {
543
- if (!quote || !wallet) {
544
- throw new Error("Quote and wallet are required for order execution.");
545
- }
546
1681
  setLoading(true);
547
1682
  setError(null);
548
- setData(null);
549
- setMessage(null);
1683
+ setResult(null);
1684
+ const onStatus = (s, msg) => {
1685
+ setStage(s);
1686
+ setMessage(msg ?? "");
1687
+ };
550
1688
  try {
551
- const effectiveDeadline = deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
552
- const onStatus = (stage, msg) => {
553
- if (!isMounted.current) return;
554
- setStatus(stage);
555
- if (msg) setMessage(msg);
556
- };
557
- const result = await executeOrder({
1689
+ const res = await sdk.executeTransaction({
558
1690
  quote,
1691
+ wallet,
559
1692
  accountAddress,
560
1693
  recipientAddress,
561
- wallet,
562
1694
  onStatus,
563
- options: { deadline: effectiveDeadline }
1695
+ orderType,
1696
+ options
564
1697
  });
565
- if (!isMounted.current) return result;
566
- setData(result);
567
- setStatus(result.stage);
568
- setMessage("Order executed successfully");
569
- return result;
570
- } catch (err) {
571
- const errorObj = err instanceof Error ? err : new Error(String(err));
572
- if (isMounted.current) {
573
- setError(errorObj);
574
- setStatus("error");
575
- setMessage(errorObj.message);
576
- setData({
577
- status: false,
578
- stage: "error",
579
- message: errorObj.message
1698
+ if (res && typeof res === "object" && "status" in res && "stage" in res && typeof res.stage === "string") {
1699
+ setResult({
1700
+ ...res,
1701
+ stage: res.stage
580
1702
  });
1703
+ } else {
1704
+ setResult(res);
581
1705
  }
582
- return {
583
- status: false,
584
- stage: "error",
585
- message: errorObj.message
586
- };
1706
+ return res;
1707
+ } catch (err) {
1708
+ const msg = err instanceof Error ? err.message : String(err);
1709
+ setError(msg);
1710
+ throw err;
587
1711
  } finally {
588
- if (isMounted.current) setLoading(false);
1712
+ await Promise.allSettled([refetchQuote(), refetchBalances()]);
1713
+ setLoading(false);
589
1714
  }
590
1715
  },
591
- []
1716
+ [sdk, refetchQuote, refetchBalances]
592
1717
  );
593
1718
  return {
594
- /** Executes the swap order. */
595
1719
  execute,
596
- /** Current execution stage. */
597
- status,
598
- /** Human-readable status message. */
1720
+ isLoading,
1721
+ stage,
599
1722
  message,
600
- /** Whether execution is ongoing. */
601
- loading,
602
- /** Raw SDK response data. */
603
- data,
604
- /** Captured error (if execution failed). */
605
- error
606
- };
607
- }
608
-
609
- // src/react/useQuote.ts
610
- import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
611
-
612
- // src/core/getQuote.ts
613
- import { QuoteProvider } from "@shogun-sdk/intents-sdk";
614
- import { parseUnits } from "viem";
615
- async function getQuote(params) {
616
- if (!params.tokenIn?.address || !params.tokenOut?.address) {
617
- throw new Error("Both tokenIn and tokenOut must include an address.");
618
- }
619
- if (!params.sourceChainId || !params.destChainId) {
620
- throw new Error("Both sourceChainId and destChainId are required.");
621
- }
622
- if (params.amount <= 0n) {
623
- throw new Error("Amount must be greater than 0.");
624
- }
625
- const normalizedTokenIn = normalizeNative(params.sourceChainId, params.tokenIn.address);
626
- const data = await QuoteProvider.getQuote({
627
- sourceChainId: params.sourceChainId,
628
- destChainId: params.destChainId,
629
- tokenIn: normalizedTokenIn,
630
- tokenOut: params.tokenOut.address,
631
- amount: params.amount
632
- });
633
- const inputSlippage = params.slippage ?? 5;
634
- const slippageDecimal = inputSlippage / 100;
635
- const slippage = Math.min(Math.max(slippageDecimal, 0), 0.5);
636
- let warning;
637
- if (slippage > 0.1) {
638
- warning = `\u26A0\uFE0F High slippage tolerance (${(slippage * 100).toFixed(2)}%) \u2014 price may vary significantly.`;
639
- }
640
- const estimatedAmountOut = BigInt(data.estimatedAmountOutReduced);
641
- const slippageBps = BigInt(Math.round(slippage * 1e4));
642
- const estimatedAmountOutAfterSlippage = estimatedAmountOut * (10000n - slippageBps) / 10000n;
643
- const pricePerInputToken = estimatedAmountOut * 10n ** BigInt(params.tokenIn.decimals ?? 18) / BigInt(params.amount);
644
- return {
645
- amountOut: estimatedAmountOut,
646
- amountOutUsd: data.estimatedAmountOutUsd,
647
- amountInUsd: data.amountInUsd,
648
- minStablecoinsAmount: data.estimatedAmountInAsMinStablecoinAmount,
649
- tokenIn: {
650
- address: params.tokenIn.address,
651
- decimals: params.tokenIn.decimals ?? 18,
652
- chainId: params.sourceChainId
653
- },
654
- tokenOut: {
655
- address: params.tokenOut.address,
656
- decimals: params.tokenOut.decimals ?? 18,
657
- chainId: params.destChainId
658
- },
659
- amountIn: params.amount,
660
- pricePerInputToken,
661
- slippage,
662
- internal: {
663
- ...data,
664
- estimatedAmountOutReduced: estimatedAmountOutAfterSlippage
665
- },
666
- warning
1723
+ error,
1724
+ result
667
1725
  };
668
1726
  }
669
1727
 
670
- // src/react/useQuote.ts
671
- function useQuote(params, options) {
672
- const [data, setData] = useState3(null);
673
- const [loading, setLoading] = useState3(false);
674
- const [error, setError] = useState3(null);
675
- const [warning, setWarning] = useState3(null);
676
- const debounceMs = options?.debounceMs ?? 250;
677
- const autoRefreshMs = options?.autoRefreshMs;
678
- const abortRef = useRef3(null);
679
- const debounceRef = useRef3(null);
680
- const mounted = useRef3(false);
681
- useEffect3(() => {
682
- mounted.current = true;
683
- return () => {
684
- mounted.current = false;
685
- abortRef.current?.abort();
686
- if (debounceRef.current) clearTimeout(debounceRef.current);
687
- };
688
- }, []);
689
- const fetchQuote = useCallback2(
690
- async () => {
691
- if (!params) return;
1728
+ // src/react/useTokenList.ts
1729
+ import { useRef as useRef3, useState as useState2, useCallback as useCallback4 } from "react";
1730
+ function useTokenList() {
1731
+ const [tokens, setTokens] = useState2([]);
1732
+ const [loading, setLoading] = useState2(false);
1733
+ const [error, setError] = useState2(null);
1734
+ const [hasMore, setHasMore] = useState2(true);
1735
+ const [page, setPage] = useState2(1);
1736
+ const sdk = useSwap();
1737
+ const pageRef = useRef3(1);
1738
+ const hasMoreRef = useRef3(true);
1739
+ const lastQuery = useRef3({});
1740
+ const cache = useRef3(/* @__PURE__ */ new Map());
1741
+ const isLoadingRef = useRef3(false);
1742
+ pageRef.current = page;
1743
+ hasMoreRef.current = hasMore;
1744
+ const loadTokens = useCallback4(
1745
+ async (params) => {
1746
+ const { q, networkId, reset } = params;
1747
+ if (isLoadingRef.current && !reset) return;
692
1748
  try {
1749
+ let currentPage = pageRef.current;
1750
+ if (reset) {
1751
+ currentPage = 1;
1752
+ setTokens([]);
1753
+ setPage(1);
1754
+ setHasMore(true);
1755
+ pageRef.current = 1;
1756
+ hasMoreRef.current = true;
1757
+ }
1758
+ if (!reset && !hasMoreRef.current) return;
1759
+ isLoadingRef.current = true;
693
1760
  setLoading(true);
694
- setWarning(null);
695
- const result = await getQuote(params);
696
- const serializeResult = serializeBigIntsToStrings(result);
697
- if (!mounted.current) return;
698
- setData((prev) => {
699
- if (JSON.stringify(prev) === JSON.stringify(serializeResult)) return prev;
700
- return serializeResult;
701
- });
702
- setWarning(result.warning ?? null);
703
1761
  setError(null);
704
- } catch (err) {
705
- if (err.name === "AbortError") return;
706
- console.error("[useQuote] fetch error:", err);
707
- if (mounted.current) setError(err instanceof Error ? err : new Error(String(err)));
1762
+ const cacheKey = JSON.stringify({
1763
+ q: q?.toLowerCase() ?? "",
1764
+ networkId: networkId ?? void 0,
1765
+ page: currentPage
1766
+ });
1767
+ if (cache.current.has(cacheKey)) {
1768
+ const cached = cache.current.get(cacheKey);
1769
+ setTokens((prev) => reset ? cached.results : [...prev, ...cached.results]);
1770
+ if (!reset && cached.results.length > 0) {
1771
+ const nextPage = currentPage + 1;
1772
+ setPage(nextPage);
1773
+ pageRef.current = nextPage;
1774
+ }
1775
+ isLoadingRef.current = false;
1776
+ setLoading(false);
1777
+ return;
1778
+ }
1779
+ const apiParams = {
1780
+ q,
1781
+ page: currentPage,
1782
+ limit: 20,
1783
+ ...networkId !== void 0 ? { networkId } : {}
1784
+ };
1785
+ const res = await sdk.getTokenList(apiParams);
1786
+ cache.current.set(cacheKey, res);
1787
+ setTokens((prev) => reset ? res.results : [...prev, ...res.results]);
1788
+ const isLastPage = res.results.length === 0 || res.results.length < 20 || res.count && currentPage * 20 >= res.count;
1789
+ setHasMore(!isLastPage);
1790
+ hasMoreRef.current = !isLastPage;
1791
+ if (!reset && !isLastPage) {
1792
+ const nextPage = currentPage + 1;
1793
+ setPage(nextPage);
1794
+ pageRef.current = nextPage;
1795
+ }
1796
+ lastQuery.current = { q, networkId };
1797
+ } catch (e) {
1798
+ console.error("useTokenList error:", e);
1799
+ setError("Failed to load tokens");
708
1800
  } finally {
709
- if (mounted.current) setLoading(false);
1801
+ setLoading(false);
1802
+ isLoadingRef.current = false;
710
1803
  }
711
1804
  },
712
- [params]
1805
+ [sdk]
713
1806
  );
1807
+ const resetTokens = useCallback4(() => {
1808
+ setTokens([]);
1809
+ setPage(1);
1810
+ setHasMore(true);
1811
+ pageRef.current = 1;
1812
+ hasMoreRef.current = true;
1813
+ cache.current.clear();
1814
+ lastQuery.current = {};
1815
+ }, []);
1816
+ return {
1817
+ tokens,
1818
+ loading,
1819
+ error,
1820
+ hasMore,
1821
+ page,
1822
+ lastQuery: lastQuery.current,
1823
+ loadTokens,
1824
+ resetTokens
1825
+ };
1826
+ }
1827
+
1828
+ // src/react/useTokensData.ts
1829
+ import { useState as useState3, useEffect as useEffect3 } from "react";
1830
+ function useTokensData(addresses) {
1831
+ const sdk = useSwap();
1832
+ const [data, setData] = useState3([]);
1833
+ const [loading, setLoading] = useState3(false);
1834
+ const [error, setError] = useState3(null);
714
1835
  useEffect3(() => {
715
- if (!params) return;
716
- if (debounceRef.current) clearTimeout(debounceRef.current);
717
- debounceRef.current = setTimeout(() => {
718
- fetchQuote();
719
- }, debounceMs);
1836
+ if (!addresses?.length) return;
1837
+ let isMounted = true;
1838
+ setLoading(true);
1839
+ setError(null);
1840
+ sdk.getTokensData(addresses).then((res) => {
1841
+ if (isMounted) setData(res);
1842
+ }).catch((e) => {
1843
+ if (isMounted) setError(e instanceof Error ? e : new Error(String(e)));
1844
+ }).finally(() => {
1845
+ if (isMounted) setLoading(false);
1846
+ });
720
1847
  return () => {
721
- if (debounceRef.current) clearTimeout(debounceRef.current);
722
- abortRef.current?.abort();
1848
+ isMounted = false;
723
1849
  };
724
- }, [params, debounceMs, fetchQuote]);
725
- useEffect3(() => {
726
- if (!autoRefreshMs || !params) return;
727
- const interval = setInterval(() => fetchQuote(), autoRefreshMs);
728
- return () => clearInterval(interval);
729
- }, [autoRefreshMs, params, fetchQuote]);
730
- return useMemo2(
731
- () => ({
732
- data,
733
- loading,
734
- error,
735
- warning,
736
- refetch: () => fetchQuote()
737
- }),
738
- [data, loading, error, warning, fetchQuote]
739
- );
1850
+ }, [sdk, JSON.stringify(addresses)]);
1851
+ return { data, loading, error };
740
1852
  }
741
1853
 
742
- // src/react/useBalances.ts
743
- import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
744
-
745
- // src/core/getBalances.ts
746
- import { TOKEN_SEARCH_API_BASE_URL } from "@shogun-sdk/intents-sdk";
747
- async function getBalances(params, options) {
748
- const { addresses, cursorEvm, cursorSvm } = params;
749
- const { signal } = options ?? {};
750
- if (!addresses?.evm && !addresses?.svm) {
751
- throw new Error("At least one address (EVM or SVM) must be provided.");
752
- }
753
- const payload = JSON.stringify({
754
- addresses,
755
- cursorEvm,
756
- cursorSvm
757
- });
758
- const start = performance.now();
759
- const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL}/tokens/balances`, {
760
- method: "POST",
761
- headers: {
762
- accept: "application/json",
763
- "Content-Type": "application/json"
764
- },
765
- body: payload,
766
- signal
767
- }).catch((err) => {
768
- if (err.name === "AbortError") {
769
- throw new Error("Balance request was cancelled.");
770
- }
771
- throw err;
772
- });
773
- if (!response.ok) {
774
- const text = await response.text().catch(() => "");
775
- throw new Error(`Failed to fetch balances: ${response.status} ${text}`);
776
- }
777
- const data = await response.json().catch(() => {
778
- throw new Error("Invalid JSON response from balances API.");
779
- });
780
- const duration = (performance.now() - start).toFixed(1);
781
- if (process.env.NODE_ENV !== "production") {
782
- console.debug(`[Shogun SDK] Fetched balances in ${duration}ms`);
783
- }
784
- const evmItems = data.evm?.items ?? [];
785
- const svmItems = data.svm?.items ?? [];
786
- const combined = [...evmItems, ...svmItems];
787
- return {
788
- results: combined,
789
- nextCursorEvm: data.evm?.cursor ?? null,
790
- nextCursorSvm: data.svm?.cursor ?? null
791
- };
1854
+ // src/react/useOrders.ts
1855
+ import { useCallback as useCallback5, useEffect as useEffect4, useMemo as useMemo4, useRef as useRef4 } from "react";
1856
+ import useSWR3, { mutate as mutate3 } from "swr";
1857
+ var ORDERS_KEY = "orders-global";
1858
+ function hasValidAddress(addrs) {
1859
+ if (!addrs) return false;
1860
+ return Boolean(addrs.evmAddress || addrs.solAddress || addrs.suiAddress);
792
1861
  }
793
-
794
- // src/react/useBalances.ts
795
- function useBalances(params) {
796
- const [data, setData] = useState4(null);
797
- const [loading, setLoading] = useState4(false);
798
- const [error, setError] = useState4(null);
1862
+ function useOrders(initialAddresses) {
1863
+ const sdk = useSwap();
799
1864
  const abortRef = useRef4(null);
800
- const stableParams = useMemo3(() => {
801
- if (!params) return null;
802
- const { addresses, cursorEvm, cursorSvm } = params;
803
- return {
804
- addresses: {
805
- evm: addresses?.evm ?? void 0,
806
- svm: addresses?.svm ?? void 0
807
- },
808
- cursorEvm,
809
- cursorSvm
810
- };
811
- }, [params?.addresses?.evm, params?.addresses?.svm, params?.cursorEvm, params?.cursorSvm]);
812
- const fetchBalances = useCallback3(async () => {
813
- if (!stableParams) return;
1865
+ const addrRef = globalThis.__ORDERS_ADDR_REF__ ?? (globalThis.__ORDERS_ADDR_REF__ = { current: initialAddresses ?? null });
1866
+ useEffect4(() => {
1867
+ if (initialAddresses && JSON.stringify(addrRef.current) !== JSON.stringify(initialAddresses)) {
1868
+ addrRef.current = initialAddresses;
1869
+ void mutate3(ORDERS_KEY);
1870
+ }
1871
+ }, [initialAddresses]);
1872
+ const fetcher = useCallback5(async () => {
1873
+ const active = addrRef.current;
1874
+ if (!hasValidAddress(active)) return null;
814
1875
  if (abortRef.current) abortRef.current.abort();
815
1876
  const controller = new AbortController();
816
1877
  abortRef.current = controller;
817
- setLoading(true);
818
- setError(null);
819
1878
  try {
820
- const result = await getBalances(stableParams, { signal: controller.signal });
821
- setData((prev) => {
822
- if (JSON.stringify(prev) === JSON.stringify(result)) return prev;
823
- return result;
824
- });
825
- return result;
1879
+ return await sdk.getOrders(active ?? {});
826
1880
  } catch (err) {
827
- if (err.name === "AbortError") return;
828
- const e = err instanceof Error ? err : new Error(String(err));
829
- setError(e);
830
- throw e;
831
- } finally {
832
- setLoading(false);
1881
+ if (err instanceof DOMException && err.name === "AbortError") return null;
1882
+ if (err instanceof Error) throw err;
1883
+ throw new Error(String(err));
833
1884
  }
834
- }, [stableParams]);
835
- useEffect4(() => {
836
- if (stableParams) fetchBalances().catch(() => {
837
- });
838
- return () => {
839
- if (abortRef.current) abortRef.current.abort();
840
- };
841
- }, [fetchBalances]);
842
- return useMemo3(
1885
+ }, [sdk]);
1886
+ const {
1887
+ data,
1888
+ error,
1889
+ isValidating: loading,
1890
+ mutate: mutateOrders
1891
+ } = useSWR3(ORDERS_KEY, fetcher, {
1892
+ revalidateOnFocus: false,
1893
+ revalidateOnReconnect: false,
1894
+ shouldRetryOnError: false,
1895
+ keepPreviousData: true
1896
+ });
1897
+ const refetch = useCallback5(async () => {
1898
+ await mutateOrders();
1899
+ }, [mutateOrders]);
1900
+ const setAddresses = useCallback5(
1901
+ (next) => {
1902
+ if (!next) return;
1903
+ addrRef.current = next;
1904
+ void mutate3(ORDERS_KEY);
1905
+ },
1906
+ []
1907
+ );
1908
+ return useMemo4(
843
1909
  () => ({
844
- /** Latest fetched balance data */
845
1910
  data,
846
- /** Whether the hook is currently fetching */
847
1911
  loading,
848
- /** Error object if fetching failed */
849
- error,
850
- /** Manually trigger a refresh */
851
- refetch: fetchBalances
1912
+ error: error instanceof Error ? error.message : null,
1913
+ refetch,
1914
+ setAddresses,
1915
+ get activeAddresses() {
1916
+ return addrRef.current;
1917
+ }
852
1918
  }),
853
- [data, loading, error, fetchBalances]
1919
+ [data, loading, error, refetch, setAddresses]
854
1920
  );
855
1921
  }
856
1922
 
857
- // src/react/index.ts
858
- import { ChainID as ChainID5, isEvmChain as isEvmChain3 } from "@shogun-sdk/intents-sdk";
1923
+ // src/react/useCancelOrder.ts
1924
+ import { useCallback as useCallback6, useState as useState4 } from "react";
1925
+ import { mutate as mutate4 } from "swr";
1926
+ function useCancelOrder() {
1927
+ const sdk = useSwap();
1928
+ const [isLoading, setLoading] = useState4(false);
1929
+ const [error, setError] = useState4(null);
1930
+ const [result, setResult] = useState4(null);
1931
+ const cancel = useCallback6(
1932
+ async (params) => {
1933
+ setLoading(true);
1934
+ setError(null);
1935
+ setResult(null);
1936
+ try {
1937
+ const tx = await sdk.cancelOrder({
1938
+ order: params.order,
1939
+ wallet: params.wallet,
1940
+ solRpc: params.solRpc
1941
+ });
1942
+ setResult(tx);
1943
+ void mutate4("orders-global");
1944
+ return tx;
1945
+ } catch (err) {
1946
+ const msg = err instanceof Error ? err.message : String(err);
1947
+ setError(msg);
1948
+ throw err;
1949
+ } finally {
1950
+ setLoading(false);
1951
+ }
1952
+ },
1953
+ [sdk]
1954
+ );
1955
+ return {
1956
+ cancel,
1957
+ isLoading,
1958
+ error,
1959
+ result
1960
+ };
1961
+ }
859
1962
  export {
860
- ChainID5 as ChainID,
861
- isEvmChain3 as isEvmChain,
1963
+ ChainId,
1964
+ OrderExecutionType,
1965
+ SupportedChains,
1966
+ SwapProvider,
1967
+ buildQuoteParams,
1968
+ isEvmChain,
862
1969
  useBalances,
863
- useExecuteOrder,
1970
+ useCancelOrder,
1971
+ useExecuteTransaction,
1972
+ useOrders,
864
1973
  useQuote,
865
- useTokenList
1974
+ useSwap,
1975
+ useTokenList,
1976
+ useTokensData
866
1977
  };