@shogun-sdk/swap 0.0.2-test.2 → 0.0.2-test.21

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/index.js CHANGED
@@ -139,22 +139,25 @@ async function getQuote(params) {
139
139
  tokenOut: params.tokenOut.address,
140
140
  amount: params.amount
141
141
  });
142
- const inputSlippage = params.slippage ?? 5;
143
- const slippageDecimal = inputSlippage / 100;
144
- const slippage = Math.min(Math.max(slippageDecimal, 0), 0.5);
142
+ const slippagePercent = Math.min(Math.max(params.slippage ?? 0.5, 0), 50);
145
143
  let warning;
146
- if (slippage > 0.1) {
147
- warning = `\u26A0\uFE0F High slippage tolerance (${(slippage * 100).toFixed(2)}%) \u2014 price may vary significantly.`;
144
+ if (slippagePercent > 10) {
145
+ warning = `\u26A0\uFE0F High slippage tolerance (${slippagePercent.toFixed(2)}%) \u2014 price may vary significantly.`;
148
146
  }
149
- const estimatedAmountOut = BigInt(data.estimatedAmountOutReduced);
150
- const slippageBps = BigInt(Math.round(slippage * 1e4));
147
+ const estimatedAmountOut = BigInt(data.estimatedAmountOut);
148
+ const slippageBps = BigInt(Math.round(slippagePercent * 100));
151
149
  const estimatedAmountOutAfterSlippage = estimatedAmountOut * (10000n - slippageBps) / 10000n;
150
+ const pricePerTokenOutInUsd = data.estimatedAmountOutUsd / Number(data.estimatedAmountOut);
151
+ const amountOutUsdAfterSlippage = Number(estimatedAmountOutAfterSlippage) * pricePerTokenOutInUsd;
152
+ const minStablecoinsAmountValue = BigInt(data.estimatedAmountInAsMinStablecoinAmount);
153
+ const minStablecoinsAmountAfterSlippage = minStablecoinsAmountValue * (10000n - slippageBps) / 10000n;
152
154
  const pricePerInputToken = estimatedAmountOut * 10n ** BigInt(params.tokenIn.decimals ?? 18) / BigInt(params.amount);
153
155
  return {
154
- amountOut: estimatedAmountOut,
155
- amountOutUsd: data.estimatedAmountOutUsd,
156
+ amountOut: estimatedAmountOutAfterSlippage,
157
+ amountOutUsd: amountOutUsdAfterSlippage,
156
158
  amountInUsd: data.amountInUsd,
157
- minStablecoinsAmount: data.estimatedAmountInAsMinStablecoinAmount,
159
+ // Input USD stays the same
160
+ minStablecoinsAmount: minStablecoinsAmountAfterSlippage,
158
161
  tokenIn: {
159
162
  address: params.tokenIn.address,
160
163
  decimals: params.tokenIn.decimals ?? 18,
@@ -167,10 +170,11 @@ async function getQuote(params) {
167
170
  },
168
171
  amountIn: params.amount,
169
172
  pricePerInputToken,
170
- slippage,
173
+ slippage: slippagePercent,
171
174
  internal: {
172
175
  ...data,
173
- estimatedAmountOutReduced: estimatedAmountOutAfterSlippage
176
+ estimatedAmountOutReduced: estimatedAmountOutAfterSlippage,
177
+ estimatedAmountOutUsdReduced: amountOutUsdAfterSlippage
174
178
  },
175
179
  warning
176
180
  };
@@ -243,7 +247,7 @@ async function getBalances(params, options) {
243
247
  }
244
248
 
245
249
  // src/core/executeOrder/execute.ts
246
- import { ChainID as ChainID4, isEvmChain as isEvmChain2 } from "@shogun-sdk/intents-sdk";
250
+ import { ChainID as ChainID3, isEvmChain as isEvmChain2 } from "@shogun-sdk/intents-sdk";
247
251
  import { BaseError } from "viem";
248
252
 
249
253
  // src/wallet-adapter/svm-wallet-adapter/adapter.ts
@@ -256,7 +260,7 @@ var adaptSolanaWallet = (walletAddress, chainId, rpcUrl, signAndSendTransaction)
256
260
  const getChainId = async () => _chainId;
257
261
  const sendTransaction = async (transaction) => {
258
262
  const txHash = await signAndSendTransaction(transaction);
259
- console.log(`\u{1F6F0} Sent transaction: ${txHash}`);
263
+ console.debug(`\u{1F6F0} Sent transaction: ${txHash}`);
260
264
  const maxRetries = 20;
261
265
  const delayMs = 2e3;
262
266
  for (let attempt = 0; attempt < maxRetries; attempt++) {
@@ -350,15 +354,26 @@ var adaptViemWallet = (wallet) => {
350
354
  if (!isEVMTransaction(transaction)) {
351
355
  throw new Error("Expected EVMTransaction but got SolanaTransaction");
352
356
  }
353
- const tx = await wallet.sendTransaction({
354
- from: transaction.from,
355
- to: transaction.to,
356
- data: transaction.data,
357
- value: transaction.value,
358
- account: wallet.account?.address,
359
- chain: wallet.chain
360
- });
361
- return tx;
357
+ if (wallet.transport.type === "http") {
358
+ const request = await wallet.prepareTransactionRequest({
359
+ to: transaction.to,
360
+ data: transaction.data,
361
+ value: transaction.value,
362
+ chain: wallet.chain
363
+ });
364
+ const serializedTransaction = await wallet.signTransaction(request);
365
+ const tx = await wallet.sendRawTransaction({ serializedTransaction });
366
+ return tx;
367
+ } else {
368
+ const hash = await wallet.sendTransaction({
369
+ to: transaction.to,
370
+ data: transaction.data,
371
+ value: transaction.value,
372
+ chain: wallet.chain,
373
+ account: wallet.account
374
+ });
375
+ return hash;
376
+ }
362
377
  };
363
378
  const switchChain = async (chainId) => {
364
379
  try {
@@ -405,10 +420,12 @@ var DEFAULT_STAGE_MESSAGES = {
405
420
  processing: "Preparing transaction for execution",
406
421
  approving: "Approving token allowance",
407
422
  approved: "Token approved successfully",
408
- signing: "Signing order for submission",
409
- submitting: "Submitting order to Auctioneer",
410
- success: "Order executed successfully",
411
- error: "Order execution failed"
423
+ signing: "Signing transaction for submission",
424
+ submitting: "Submitting transaction",
425
+ initiated: "Transaction initiated.",
426
+ success: "Transaction Executed successfully",
427
+ shogun_processing: "Shogun is processing your transaction",
428
+ error: "Transaction failed during submission"
412
429
  };
413
430
 
414
431
  // src/core/executeOrder/buildOrder.ts
@@ -447,6 +464,110 @@ async function buildOrder({
447
464
  });
448
465
  }
449
466
 
467
+ // src/utils/pollOrderStatus.ts
468
+ import { AUCTIONEER_URL } from "@shogun-sdk/intents-sdk";
469
+ async function pollOrderStatus(address, orderId, options = {}) {
470
+ const { intervalMs = 2e3, timeoutMs = 3e5 } = options;
471
+ const startTime = Date.now();
472
+ const isDebug = process.env.NODE_ENV !== "production";
473
+ const isEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(address);
474
+ const isSuiAddress = /^0x[a-fA-F0-9]{64}$/.test(address);
475
+ const isSolanaAddress = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
476
+ let queryParam;
477
+ if (isEvmAddress) queryParam = `evmWallets=${address}`;
478
+ else if (isSuiAddress) queryParam = `suiWallets=${address}`;
479
+ else if (isSolanaAddress) queryParam = `solanaWallets=${address}`;
480
+ else throw new Error(`Unrecognized wallet address format: ${address}`);
481
+ const queryUrl = `${AUCTIONEER_URL}/user_intent?${queryParam}`;
482
+ return new Promise((resolve, reject) => {
483
+ const pollInterval = setInterval(async () => {
484
+ try {
485
+ if (Date.now() - startTime > timeoutMs) {
486
+ clearInterval(pollInterval);
487
+ return resolve("Timeout");
488
+ }
489
+ const res = await fetch(queryUrl, {
490
+ method: "GET",
491
+ headers: { "Content-Type": "application/json" }
492
+ });
493
+ if (!res.ok) {
494
+ clearInterval(pollInterval);
495
+ return reject(
496
+ new Error(`Failed to fetch orders: ${res.status} ${res.statusText}`)
497
+ );
498
+ }
499
+ const json = await res.json();
500
+ const data = json?.data ?? {};
501
+ const allOrders = [
502
+ ...data.crossChainDcaOrders ?? [],
503
+ ...data.crossChainLimitOrders ?? [],
504
+ ...data.singleChainDcaOrders ?? [],
505
+ ...data.singleChainLimitOrders ?? []
506
+ ];
507
+ const targetOrder = allOrders.find((o) => o.orderId === orderId);
508
+ if (!targetOrder) {
509
+ if (isDebug)
510
+ console.debug(`[pollOrderStatus] [${orderId}] Not found yet`);
511
+ return;
512
+ }
513
+ const { orderStatus } = targetOrder;
514
+ if (isDebug) {
515
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
516
+ console.debug(`targetOrder`, targetOrder);
517
+ console.debug(
518
+ `[pollOrderStatus] [${orderId}] status=${orderStatus} (elapsed ${elapsed}s)`
519
+ );
520
+ }
521
+ if (["Fulfilled", "Cancelled", "Outdated"].includes(orderStatus)) {
522
+ clearInterval(pollInterval);
523
+ return resolve(orderStatus);
524
+ }
525
+ } catch (error) {
526
+ clearInterval(pollInterval);
527
+ return reject(error);
528
+ }
529
+ }, intervalMs);
530
+ });
531
+ }
532
+
533
+ // src/core/executeOrder/handleOrderPollingResult.ts
534
+ async function handleOrderPollingResult({
535
+ status,
536
+ orderId,
537
+ chainId,
538
+ update,
539
+ messageFor
540
+ }) {
541
+ switch (status) {
542
+ case "Fulfilled":
543
+ update("success", messageFor("success"));
544
+ return {
545
+ status: true,
546
+ orderId,
547
+ chainId,
548
+ finalStatus: status,
549
+ stage: "success"
550
+ };
551
+ case "Cancelled":
552
+ update("error", "Order was cancelled before fulfillment");
553
+ break;
554
+ case "Timeout":
555
+ update("error", "Order polling timed out");
556
+ break;
557
+ case "NotFound":
558
+ default:
559
+ update("error", "Order not found");
560
+ break;
561
+ }
562
+ return {
563
+ status: false,
564
+ orderId,
565
+ chainId,
566
+ finalStatus: status,
567
+ stage: "error"
568
+ };
569
+ }
570
+
450
571
  // src/core/executeOrder/handleEvmExecution.ts
451
572
  async function handleEvmExecution({
452
573
  recipientAddress,
@@ -475,18 +596,18 @@ async function handleEvmExecution({
475
596
  from: accountAddress
476
597
  });
477
598
  }
478
- update("approving", messageFor("approving"));
599
+ update("processing", messageFor("approving"));
479
600
  await wallet.sendTransaction({
480
601
  to: tokenIn,
481
602
  data: encodeFunctionData({
482
603
  abi: erc20Abi,
483
604
  functionName: "approve",
484
- args: [PERMIT2_ADDRESS[chainId], BigInt(quote.amountIn)]
605
+ args: [PERMIT2_ADDRESS[chainId], quote.amountIn]
485
606
  }),
486
607
  value: 0n,
487
608
  from: accountAddress
488
609
  });
489
- update("approved", messageFor("approved"));
610
+ update("processing", messageFor("approved"));
490
611
  const destination = recipientAddress ?? accountAddress;
491
612
  const order = await buildOrder({
492
613
  quote,
@@ -495,24 +616,40 @@ async function handleEvmExecution({
495
616
  deadline,
496
617
  isSingleChain
497
618
  });
498
- update("signing", messageFor("signing"));
619
+ console.debug(`order`, order);
620
+ update("processing", messageFor("signing"));
499
621
  const { orderTypedData, nonce } = isSingleChain ? await getEVMSingleChainOrderTypedData(order) : await getEVMCrossChainOrderTypedData(order);
622
+ const typedData = serializeBigIntsToStrings(orderTypedData);
500
623
  if (!wallet.signTypedData) {
501
624
  throw new Error("Wallet does not support EIP-712 signing");
502
625
  }
503
- const signature = await wallet.signTypedData(serializeBigIntsToStrings(orderTypedData));
504
- update("submitting", messageFor("submitting"));
626
+ const signature = await wallet.signTypedData({
627
+ domain: typedData.domain,
628
+ types: typedData.types,
629
+ primaryType: typedData.primaryType,
630
+ value: typedData.message,
631
+ message: typedData.message
632
+ });
633
+ update("processing", messageFor("submitting"));
505
634
  const res = await order.sendToAuctioneer({ signature, nonce: nonce.toString() });
506
635
  if (!res.success) {
507
636
  throw new Error("Auctioneer submission failed");
508
637
  }
509
- update("success", messageFor("success"));
510
- return { status: true, txHash: res.data, chainId, stage: "success" };
638
+ update("initiated", messageFor("initiated"));
639
+ const { intentId: orderId } = res.data;
640
+ update("initiated", messageFor("shogun_processing"));
641
+ const status = await pollOrderStatus(accountAddress, orderId);
642
+ return await handleOrderPollingResult({
643
+ status,
644
+ orderId,
645
+ chainId,
646
+ update,
647
+ messageFor
648
+ });
511
649
  }
512
650
 
513
651
  // src/core/executeOrder/handleSolanaExecution.ts
514
652
  import {
515
- ChainID as ChainID3,
516
653
  getSolanaSingleChainOrderInstructions,
517
654
  getSolanaCrossChainOrderInstructions
518
655
  } from "@shogun-sdk/intents-sdk";
@@ -545,10 +682,9 @@ async function handleSolanaExecution({
545
682
  rpcUrl: wallet.rpcUrl
546
683
  });
547
684
  const transaction = VersionedTransaction2.deserialize(Uint8Array.from(txData.txBytes));
548
- update("signing", messageFor("signing"));
549
- console.log({ order });
550
- const txSignature = await wallet.sendTransaction(transaction);
551
- update("submitting", messageFor("submitting"));
685
+ update("processing", messageFor("signing"));
686
+ await wallet.sendTransaction(transaction);
687
+ update("processing", messageFor("submitting"));
552
688
  const response = await submitToAuctioneer({
553
689
  order,
554
690
  isSingleChain,
@@ -557,13 +693,17 @@ async function handleSolanaExecution({
557
693
  if (!response.success) {
558
694
  throw new Error("Auctioneer submission failed");
559
695
  }
560
- update("success", messageFor("success"));
561
- return {
562
- status: true,
563
- txHash: txSignature,
564
- chainId: ChainID3.Solana,
565
- stage: "success"
566
- };
696
+ update("initiated", messageFor("initiated"));
697
+ const { jwt, intentId: orderId } = response.data;
698
+ update("initiated", messageFor("shogun_processing"));
699
+ const status = await pollOrderStatus(jwt, orderId);
700
+ return await handleOrderPollingResult({
701
+ status,
702
+ orderId,
703
+ chainId: SOLANA_CHAIN_ID,
704
+ update,
705
+ messageFor
706
+ });
567
707
  }
568
708
  async function getSolanaOrderInstructions({
569
709
  order,
@@ -605,18 +745,33 @@ async function executeOrder({
605
745
  onStatus,
606
746
  options = {}
607
747
  }) {
608
- const deadline = options.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
748
+ const isDev = process.env.NODE_ENV !== "production";
749
+ const log = (...args) => {
750
+ if (isDev) console.debug("[OneShot::executeOrder]", ...args);
751
+ };
609
752
  const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
610
- const update = (stage, message) => onStatus?.(stage, message ?? messageFor(stage));
753
+ const update = (stage, message) => {
754
+ log("Stage:", stage, "| Message:", message ?? messageFor(stage));
755
+ onStatus?.(stage, message ?? messageFor(stage));
756
+ };
611
757
  try {
758
+ const deadline = options.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
759
+ log("Starting execution:", {
760
+ accountAddress,
761
+ recipientAddress,
762
+ deadline,
763
+ tokenIn: quote?.tokenIn,
764
+ tokenOut: quote?.tokenOut
765
+ });
612
766
  const adapter = normalizeWallet(wallet);
613
767
  if (!adapter) throw new Error("No wallet provided");
614
768
  const { tokenIn, tokenOut } = quote;
615
769
  const isSingleChain = tokenIn.chainId === tokenOut.chainId;
616
770
  const chainId = Number(tokenIn.chainId);
617
- update("processing", messageFor("processing"));
771
+ update("processing");
618
772
  if (isEvmChain2(chainId)) {
619
- return await handleEvmExecution({
773
+ log("Detected EVM chain:", chainId);
774
+ const result = await handleEvmExecution({
620
775
  recipientAddress,
621
776
  quote,
622
777
  chainId,
@@ -626,9 +781,12 @@ async function executeOrder({
626
781
  deadline,
627
782
  update
628
783
  });
784
+ log("EVM execution result:", result);
785
+ return result;
629
786
  }
630
- if (chainId === ChainID4.Solana) {
631
- return await handleSolanaExecution({
787
+ if (chainId === ChainID3.Solana) {
788
+ log("Detected Solana chain");
789
+ const result = await handleSolanaExecution({
632
790
  recipientAddress,
633
791
  quote,
634
792
  accountAddress,
@@ -637,11 +795,16 @@ async function executeOrder({
637
795
  deadline,
638
796
  update
639
797
  });
798
+ log("Solana execution result:", result);
799
+ return result;
640
800
  }
641
- update("error", "Unsupported chain");
642
- return { status: false, message: "Unsupported chain", stage: "error" };
801
+ const unsupported = `Unsupported chain: ${chainId}`;
802
+ update("error", unsupported);
803
+ log("Error:", unsupported);
804
+ return { status: false, message: unsupported, stage: "error" };
643
805
  } catch (error) {
644
806
  const message = error instanceof BaseError ? error.shortMessage : error instanceof Error ? error.message : String(error);
807
+ log("Execution failed:", { message, error });
645
808
  update("error", message);
646
809
  return { status: false, message, stage: "error" };
647
810
  }
@@ -822,77 +985,64 @@ function useQuote(params, options) {
822
985
  const autoRefreshMs = options?.autoRefreshMs;
823
986
  const abortRef = useRef3(null);
824
987
  const debounceRef = useRef3(null);
825
- const mountedRef = useRef3(true);
988
+ const mounted = useRef3(false);
989
+ const paramsKey = useMemo2(
990
+ () => params ? JSON.stringify(serializeBigIntsToStrings(params)) : null,
991
+ [params]
992
+ );
826
993
  useEffect3(() => {
994
+ mounted.current = true;
827
995
  return () => {
828
- mountedRef.current = false;
996
+ mounted.current = false;
829
997
  abortRef.current?.abort();
830
998
  if (debounceRef.current) clearTimeout(debounceRef.current);
831
999
  };
832
1000
  }, []);
833
- const fetchQuote = useCallback2(
834
- async (signal) => {
835
- if (!params) return;
836
- try {
837
- setLoading(true);
838
- setWarning(null);
839
- const result = await getQuote({ ...params, signal });
840
- if (!mountedRef.current) return;
841
- setData((prev) => {
842
- if (JSON.stringify(prev) === JSON.stringify(result)) return prev;
843
- return result;
844
- });
845
- setWarning(result.warning ?? null);
846
- setError(null);
847
- } catch (err) {
848
- if (err.name === "AbortError") return;
849
- const e = err instanceof Error ? err : new Error(String(err));
850
- if (mountedRef.current) setError(e);
851
- } finally {
852
- if (mountedRef.current) setLoading(false);
853
- }
854
- },
855
- [params]
856
- );
857
- useEffect3(() => {
1001
+ const fetchQuote = useCallback2(async () => {
858
1002
  if (!params) return;
1003
+ try {
1004
+ setLoading(true);
1005
+ setWarning(null);
1006
+ const result = await getQuote(params);
1007
+ const serializeResult = serializeBigIntsToStrings(result);
1008
+ if (!mounted.current) return;
1009
+ setData((prev) => {
1010
+ if (JSON.stringify(prev) === JSON.stringify(serializeResult)) return prev;
1011
+ return serializeResult;
1012
+ });
1013
+ setWarning(result.warning ?? null);
1014
+ setError(null);
1015
+ } catch (err) {
1016
+ if (err.name === "AbortError") return;
1017
+ console.error("[useQuote] fetch error:", err);
1018
+ if (mounted.current) setError(err instanceof Error ? err : new Error(String(err)));
1019
+ } finally {
1020
+ if (mounted.current) setLoading(false);
1021
+ }
1022
+ }, [paramsKey]);
1023
+ useEffect3(() => {
1024
+ if (!paramsKey) return;
859
1025
  if (debounceRef.current) clearTimeout(debounceRef.current);
860
- abortRef.current?.abort();
861
- const controller = new AbortController();
862
- abortRef.current = controller;
863
1026
  debounceRef.current = setTimeout(() => {
864
- fetchQuote(controller.signal);
1027
+ fetchQuote();
865
1028
  }, debounceMs);
866
1029
  return () => {
867
- controller.abort();
868
1030
  if (debounceRef.current) clearTimeout(debounceRef.current);
1031
+ abortRef.current?.abort();
869
1032
  };
870
- }, [
871
- params?.tokenIn?.address,
872
- params?.tokenOut?.address,
873
- params?.sourceChainId,
874
- params?.destChainId,
875
- params?.amount,
876
- debounceMs,
877
- fetchQuote
878
- ]);
1033
+ }, [paramsKey, debounceMs, fetchQuote]);
879
1034
  useEffect3(() => {
880
- if (!autoRefreshMs || !params) return;
1035
+ if (!autoRefreshMs || !paramsKey) return;
881
1036
  const interval = setInterval(() => fetchQuote(), autoRefreshMs);
882
1037
  return () => clearInterval(interval);
883
- }, [autoRefreshMs, params, fetchQuote]);
1038
+ }, [autoRefreshMs, paramsKey, fetchQuote]);
884
1039
  return useMemo2(
885
1040
  () => ({
886
- /** Latest quote data */
887
1041
  data,
888
- /** Whether a fetch is ongoing */
889
1042
  loading,
890
- /** Error (if any) */
891
1043
  error,
892
- /** Warning (e.g. high slippage alert) */
893
1044
  warning,
894
- /** Manual refetch */
895
- refetch: () => fetchQuote()
1045
+ refetch: fetchQuote
896
1046
  }),
897
1047
  [data, loading, error, warning, fetchQuote]
898
1048
  );