@xswap-link/sdk 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.global.js +92 -92
  3. package/dist/index.js +1799 -1725
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1752 -1681
  6. package/dist/index.mjs.map +1 -1
  7. package/localTheme.ts +1 -1
  8. package/package.json +1 -1
  9. package/src/assets/icons/ChainlinkMarkIcon.tsx +4 -5
  10. package/src/assets/icons/CheckCircleThinIcon.tsx +22 -0
  11. package/src/assets/icons/ChevronDownThinIcon.tsx +39 -0
  12. package/src/assets/icons/ChevronDownWideThinIcon.tsx +18 -0
  13. package/src/assets/icons/ChevronsUpDownThinIcon.tsx +21 -0
  14. package/src/assets/icons/CloseIcon.tsx +3 -2
  15. package/src/assets/icons/ErrorCircleThinIcon.tsx +22 -0
  16. package/src/assets/icons/LinkExternalThinIcon.tsx +19 -0
  17. package/src/assets/icons/LoadingRingIcon.tsx +22 -0
  18. package/src/assets/icons/ProgressRingThinIcon.tsx +29 -0
  19. package/src/assets/icons/SearchThinIcon.tsx +15 -0
  20. package/src/assets/icons/SignThinIcon.tsx +15 -0
  21. package/src/assets/icons/WalletCheckThinIcon.tsx +24 -0
  22. package/src/assets/icons/index.ts +11 -0
  23. package/src/components/PoweredBy/index.tsx +1 -1
  24. package/src/components/Swap/Header/index.tsx +2 -4
  25. package/src/components/Swap/HistoryView/ActivityCard/index.tsx +110 -115
  26. package/src/components/Swap/HistoryView/index.tsx +10 -7
  27. package/src/components/Swap/ReorderButton/ReorderButton.tsx +4 -4
  28. package/src/components/Swap/SettingsView/Delivery/index.tsx +19 -17
  29. package/src/components/Swap/SettingsView/InfiniteApproval/index.tsx +19 -17
  30. package/src/components/Swap/SettingsView/Slippage/index.tsx +55 -49
  31. package/src/components/Swap/SwapView/ConfirmationView/TokenTiles/index.tsx +69 -0
  32. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/Header/index.tsx +13 -25
  33. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/Steps/index.tsx +65 -113
  34. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +19 -23
  35. package/src/components/Swap/SwapView/ConfirmationView/TxResult/index.tsx +51 -74
  36. package/src/components/Swap/SwapView/FeesPanel/index.tsx +4 -4
  37. package/src/components/Swap/SwapView/SwapButton/index.tsx +0 -29
  38. package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +2 -2
  39. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/ChainPicker/index.tsx +3 -3
  40. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/NetworkFilter/index.tsx +17 -16
  41. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +91 -40
  42. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/index.tsx +4 -4
  43. package/src/components/Swap/SwapView/SwapPanel/UsdValue/index.tsx +2 -2
  44. package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +10 -16
  45. package/src/components/Swap/SwapView/SwapPanel/index.tsx +3 -3
  46. package/src/components/Swap/SwapView/WalletPicker/index.tsx +131 -139
  47. package/src/components/Swap/SwapView/index.tsx +11 -7
  48. package/src/components/Swap/WalletPickerView/index.tsx +65 -0
  49. package/src/components/Swap/index.tsx +23 -6
  50. package/src/components/ToggleButton/index.tsx +5 -7
  51. package/src/components/TokenLogo/index.tsx +1 -1
  52. package/src/components/TxModal/index.tsx +14 -4
  53. package/src/components/global.css +18 -1
  54. package/src/constants/index.ts +4 -0
  55. package/src/context/SwapProvider.tsx +106 -19
  56. package/src/utils/validation.ts +9 -4
  57. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/ArrowIcon.tsx +0 -13
  58. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/SwapPanel/index.tsx +0 -72
  59. package/src/components/Swap/SwapView/ConfirmationView/TxResult/TokenItem/index.tsx +0 -25
@@ -1,5 +1,7 @@
1
1
  import { TxConfigFormPayload } from "@src/components";
2
2
  import {
3
+ DEFAULT_BRIDGE_DESTINATION_CHAIN_ID,
4
+ DEFAULT_BRIDGE_SOURCE_CHAIN_ID,
3
5
  DEFAULT_DESTINATION_CHAIN_ID,
4
6
  DEFAULT_REQUEST_ABORT_MSG,
5
7
  DEFAULT_SOURCE_CHAIN_ID,
@@ -291,6 +293,19 @@ export const SwapProvider = ({
291
293
  const routeAbortController = useRef<AbortController | null>(null);
292
294
  const refreshSelectedTokenBalanceNonce = useRef(crypto.randomUUID());
293
295
  const prevWalletAddresses = useRef<string | undefined>(undefined);
296
+ // last wallet chain id seen by the follow-the-wallet effect below
297
+ const prevFollowedWalletChainId = useRef<string | undefined>(undefined);
298
+ // True between the bridgeUI-change reset and the re-init that follows it.
299
+ // Effects in the reset's commit still close over the pre-reset state (with
300
+ // the new bridgeUI), so acting on that mix would remap the old mode's route
301
+ // as the new mode's — this ref lets them bail out synchronously.
302
+ const initPending = useRef(false);
303
+ // True while a bridgeUI-change re-init is pending. The web-component
304
+ // wrapper delivers attribute updates one by one, so at re-init time the
305
+ // route config may still hold the previous mode's values (there is no way
306
+ // to tell them apart from intentional ones) — the re-init therefore
307
+ // ignores route config entirely and starts from the new mode's defaults.
308
+ const modeSwitchReinit = useRef(false);
294
309
 
295
310
  // =============================================================================
296
311
  // Memoized Values
@@ -691,6 +706,12 @@ export const SwapProvider = ({
691
706
  !srcToken ||
692
707
  !srcToken?.address
693
708
  ) {
709
+ // Nothing to read a balance for — drop the previous selection's
710
+ // balance (and invalidate any in-flight read) so the MAX button
711
+ // never acts on a stale value.
712
+ refreshSelectedTokenBalanceNonce.current = crypto.randomUUID();
713
+ setSrcTokenBalanceWei("");
714
+ setIsFetchingBalance(false);
694
715
  return;
695
716
  }
696
717
 
@@ -1077,9 +1098,23 @@ export const SwapProvider = ({
1077
1098
  // useEffects responsible for state updates and validations
1078
1099
  // =============================================================================
1079
1100
 
1080
- // sets the initial source chain based on the wallet's chain ID if not already initialized.
1101
+ // Follows a real in-session wallet network switch with the source chain.
1102
+ // Only a defined -> defined change counts: the chain id first appearing
1103
+ // during reconnect is ignored, since wagmi restores a persisted (possibly
1104
+ // stale) chain id before the connector reports the actual one. Bridge keeps
1105
+ // its fixed default route instead of following the wallet chain.
1081
1106
  useEffect(() => {
1082
- if (!walletChainId || !isInitialStateSet || integrationConfig?.srcChainId) {
1107
+ const prevWalletChainId = prevFollowedWalletChainId.current;
1108
+ prevFollowedWalletChainId.current = walletChainId;
1109
+
1110
+ if (
1111
+ !walletChainId ||
1112
+ !prevWalletChainId ||
1113
+ prevWalletChainId === walletChainId ||
1114
+ !isInitialStateSet ||
1115
+ integrationConfig?.srcChainId ||
1116
+ bridgeUI
1117
+ ) {
1083
1118
  return;
1084
1119
  }
1085
1120
  const initSrcChain = supportedChains.find(
@@ -1093,19 +1128,29 @@ export const SwapProvider = ({
1093
1128
  isInitialStateSet,
1094
1129
  supportedChains,
1095
1130
  integrationConfig?.srcChainId,
1131
+ bridgeUI,
1096
1132
  ]);
1097
1133
 
1098
- // resets source and destination tokens and chains when bridgeUI changes and initial state is not yet set.
1134
+ // resets the widget state when bridgeUI changes at runtime: back to the
1135
+ // main view (leaving Settings/History), cleared selection and amount, and
1136
+ // re-arms the init effect below so the new mode gets its default route
1137
+ // (config -> wallet chain -> mode defaults) applied again.
1099
1138
  useEffect(() => {
1100
1139
  if (!isInitialStateSet) {
1101
1140
  return;
1102
1141
  }
1103
1142
 
1143
+ initPending.current = true;
1144
+ modeSwitchReinit.current = true;
1145
+ setTab(init.tab);
1104
1146
  setActivePicker(null);
1147
+ setError(init.error);
1148
+ setSrcValue(init.srcValue);
1105
1149
  setSrcToken(undefined);
1106
1150
  setDstToken(undefined);
1107
1151
  setSrcChain(undefined);
1108
1152
  setDstChain(undefined);
1153
+ setIsInitialStateSet(false);
1109
1154
 
1110
1155
  // eslint-disable-next-line react-hooks/exhaustive-deps
1111
1156
  }, [bridgeUI]);
@@ -1121,21 +1166,51 @@ export const SwapProvider = ({
1121
1166
  return;
1122
1167
  }
1123
1168
 
1169
+ // During a bridgeUI-change re-init the route config may still hold the
1170
+ // previous mode's values (web-component attribute updates arrive one by
1171
+ // one), so it is ignored — the new mode starts from its own defaults.
1172
+ // Route config still seeds the very first init (deep links, integrator
1173
+ // presets).
1174
+ const ignoreRouteConfig = modeSwitchReinit.current;
1175
+ const cfgSrcChainId = ignoreRouteConfig
1176
+ ? undefined
1177
+ : integrationConfig?.srcChainId;
1178
+ const cfgDstChainId = ignoreRouteConfig
1179
+ ? undefined
1180
+ : integrationConfig?.dstChainId;
1181
+ const cfgSrcTokenAddr = ignoreRouteConfig
1182
+ ? undefined
1183
+ : integrationConfig?.srcTokenAddr;
1184
+ const cfgDstTokenAddr = ignoreRouteConfig
1185
+ ? undefined
1186
+ : integrationConfig?.dstTokenAddr;
1187
+
1124
1188
  // find chains
1189
+ // Default routes are fixed (swap: ETH -> ETH, bridge: ETH -> Base) and do
1190
+ // not follow the wallet's chain — on load wagmi can still report a stale,
1191
+ // persisted chain id, so it can't be trusted here. Integrator config
1192
+ // still takes precedence, and a real in-session network switch is
1193
+ // followed by the effect below.
1125
1194
  const searchedSrcChainId =
1126
- srcChain ||
1127
- integrationConfig?.srcChainId ||
1128
- (walletChainId ? `${walletChainId}` : undefined) ||
1129
- (bridgeUI ? undefined : DEFAULT_SOURCE_CHAIN_ID);
1195
+ srcChain?.chainId ||
1196
+ cfgSrcChainId ||
1197
+ (bridgeUI ? DEFAULT_BRIDGE_SOURCE_CHAIN_ID : DEFAULT_SOURCE_CHAIN_ID);
1130
1198
 
1131
1199
  const initSrcChain = supportedChains.find(
1132
1200
  ({ chainId }) => chainId === searchedSrcChainId,
1133
1201
  );
1134
1202
 
1203
+ // Bridge can't route within one chain, so when the source already sits on
1204
+ // the default bridge destination, fall back to the reversed default route.
1205
+ const bridgeDefaultDstChainId =
1206
+ initSrcChain?.chainId === DEFAULT_BRIDGE_DESTINATION_CHAIN_ID
1207
+ ? DEFAULT_BRIDGE_SOURCE_CHAIN_ID
1208
+ : DEFAULT_BRIDGE_DESTINATION_CHAIN_ID;
1209
+
1135
1210
  const searchedDstChainId =
1136
- dstChain ||
1137
- integrationConfig?.dstChainId ||
1138
- (bridgeUI ? undefined : DEFAULT_DESTINATION_CHAIN_ID);
1211
+ dstChain?.chainId ||
1212
+ cfgDstChainId ||
1213
+ (bridgeUI ? bridgeDefaultDstChainId : DEFAULT_DESTINATION_CHAIN_ID);
1139
1214
 
1140
1215
  const initDstChain = supportedChains.find(
1141
1216
  ({ chainId }) => chainId === searchedDstChainId,
@@ -1146,13 +1221,13 @@ export const SwapProvider = ({
1146
1221
  let initSrcToken: Token | undefined;
1147
1222
 
1148
1223
  const srcTokenAddress = resolveTokenAddress(
1149
- integrationConfig.srcTokenAddr,
1150
- integrationConfig?.srcChainId,
1224
+ cfgSrcTokenAddr,
1225
+ cfgSrcChainId,
1151
1226
  supportedChains,
1152
1227
  );
1153
1228
  const dstTokenAddress = resolveTokenAddress(
1154
- integrationConfig.dstTokenAddr,
1155
- integrationConfig?.dstChainId,
1229
+ cfgDstTokenAddr,
1230
+ cfgDstChainId,
1156
1231
  supportedChains,
1157
1232
  );
1158
1233
 
@@ -1182,6 +1257,8 @@ export const SwapProvider = ({
1182
1257
  setSrcToken(source.token);
1183
1258
  setDstToken(target.token);
1184
1259
  // Mark initial state as set
1260
+ modeSwitchReinit.current = false;
1261
+ initPending.current = false;
1185
1262
  setIsInitialStateSet(true);
1186
1263
  // eslint-disable-next-line react-hooks/exhaustive-deps
1187
1264
  }, [
@@ -1200,7 +1277,11 @@ export const SwapProvider = ({
1200
1277
  if (
1201
1278
  !bridgeTokensDictionary ||
1202
1279
  supportedChains.length === 0 ||
1203
- !isInitialStateSet
1280
+ !isInitialStateSet ||
1281
+ // A bridgeUI-change reset is in flight: this closure still holds the
1282
+ // previous mode's route, so mapping it against the new mode would
1283
+ // resurrect it. The re-init applies the new mode's defaults instead.
1284
+ initPending.current
1204
1285
  ) {
1205
1286
  return;
1206
1287
  }
@@ -1241,9 +1322,15 @@ export const SwapProvider = ({
1241
1322
  bridgeTokensDictionary,
1242
1323
  isInitialStateSet,
1243
1324
  ]);
1325
+ // Notify effects below also re-run whenever the integrator recreates the
1326
+ // callback props (inline arrows give them a new identity every render).
1327
+ // During a bridgeUI-change reset their closures still hold the previous
1328
+ // mode's route, and re-emitting it would feed it back to the integrator as
1329
+ // config — the initPending guard keeps them quiet until the re-init lands.
1330
+
1244
1331
  // notifies integration or global callback when the destination chain changes.
1245
1332
  useEffect(() => {
1246
- if (!isInitialStateSet) {
1333
+ if (!isInitialStateSet || initPending.current) {
1247
1334
  return;
1248
1335
  }
1249
1336
  if (integrationConfig.onDstChainChange) {
@@ -1255,7 +1342,7 @@ export const SwapProvider = ({
1255
1342
  }, [integrationConfig.onDstChainChange, dstChain]);
1256
1343
  // notifies integration or global callback when the destination token changes.
1257
1344
  useEffect(() => {
1258
- if (!isInitialStateSet) {
1345
+ if (!isInitialStateSet || initPending.current) {
1259
1346
  return;
1260
1347
  }
1261
1348
  const token = dstToken
@@ -1271,7 +1358,7 @@ export const SwapProvider = ({
1271
1358
  }, [integrationConfig.onDstTokenChange, dstToken]);
1272
1359
  // notifies integration or global callback when the source chain changes.
1273
1360
  useEffect(() => {
1274
- if (!isInitialStateSet) {
1361
+ if (!isInitialStateSet || initPending.current) {
1275
1362
  return;
1276
1363
  }
1277
1364
  if (integrationConfig.onSrcChainChange) {
@@ -1283,7 +1370,7 @@ export const SwapProvider = ({
1283
1370
  }, [integrationConfig.onSrcChainChange, srcChain]);
1284
1371
  // notifies integration or global callback when the source token changes.
1285
1372
  useEffect(() => {
1286
- if (!isInitialStateSet) {
1373
+ if (!isInitialStateSet || initPending.current) {
1287
1374
  return;
1288
1375
  }
1289
1376
  const token = srcToken
@@ -379,11 +379,16 @@ const mapToValidPathBridge = ({
379
379
  chain: newSourceChain,
380
380
  bridgeTokensDictionary,
381
381
  });
382
- if (defaultToken && newSourceChain && newTargetChain) {
383
- const isDefaultTokenBridgeable =
384
- !!bridgeTokensDictionary[newSourceChain.chainId]?.[
382
+ if (defaultToken && newSourceChain) {
383
+ const bridgeTargets =
384
+ bridgeTokensDictionary[newSourceChain.chainId]?.[
385
385
  defaultToken.address.toLowerCase()
386
- ]?.[newTargetChain.chainId];
386
+ ] || {};
387
+ // With no target chain picked yet the default still applies, as long as
388
+ // the token is bridgeable somewhere.
389
+ const isDefaultTokenBridgeable = newTargetChain
390
+ ? !!bridgeTargets[newTargetChain.chainId]
391
+ : Object.keys(bridgeTargets).length > 0;
387
392
 
388
393
  if (isDefaultTokenBridgeable) {
389
394
  newSourceToken = defaultToken;
@@ -1,13 +0,0 @@
1
- import { ArrowDownLongIcon } from "@src/assets/icons";
2
-
3
- export const ArrowIcon = () => {
4
- return (
5
- <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 p-1 border border-solid border-t_text_primary border-opacity-10 rounded-xl bg-t_bg_secondary">
6
- <div className="relative bg-gradient-to-r from-t_main_accent_light to-t_main_accent_dark w-8 h-8 rounded-xl">
7
- <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 fill-white">
8
- <ArrowDownLongIcon />
9
- </div>
10
- </div>
11
- </div>
12
- );
13
- };
@@ -1,72 +0,0 @@
1
- import { TokenLogo } from "@src/components";
2
- import { useSwapContext } from "@src/context";
3
- import { SwapPanelType } from "../../../index";
4
- import { useMemo } from "react";
5
- import { Ecosystem } from "@src/models";
6
-
7
- type Props = {
8
- type: SwapPanelType;
9
- };
10
- export const SwapPanel = ({ type }: Props) => {
11
- const {
12
- srcToken,
13
- srcChain,
14
- dstToken,
15
- dstChain,
16
- srcValue,
17
- srcValueUsd,
18
- dstValue,
19
- dstValueUsd,
20
- bridgeUI,
21
- } = useSwapContext();
22
-
23
- const token = useMemo(
24
- () => (type === "source" ? srcToken : dstToken),
25
- [dstToken, srcToken, type],
26
- );
27
-
28
- const chain = useMemo(
29
- () => (type === "source" ? srcChain : dstChain),
30
- [dstChain, srcChain, type],
31
- );
32
-
33
- const value = useMemo(() => {
34
- if (
35
- type === "destination" &&
36
- bridgeUI &&
37
- srcChain?.ecosystem === Ecosystem.SOLANA
38
- ) {
39
- return srcValue;
40
- }
41
- return type === "source" ? srcValue : dstValue;
42
- }, [type, bridgeUI, srcChain?.ecosystem, srcValue, dstValue]);
43
-
44
- const valueUsd = useMemo(() => {
45
- return type === "source" ? srcValueUsd : dstValueUsd;
46
- }, [dstValueUsd, srcValueUsd, type]);
47
-
48
- return (
49
- <div className="flex justify-between bg-gradient-to-r from-t_main_accent_light/20 to-t_main_accent_dark/20 p-5 rounded-xl">
50
- <div className="flex flex-col">
51
- <div className="opacity-60 text-sm mb-2">{`You ${
52
- type === "source" ? "pay" : "receive"
53
- }`}</div>
54
- <div className={`${type === "destination" && "text-t_text_secondary"}`}>
55
- {value}
56
- </div>
57
- {valueUsd && <div className="opacity-60">${valueUsd}</div>}
58
- </div>
59
- <div className="flex items-center gap-3">
60
- <div className="flex flex-col text-right">
61
- <div>{token?.symbol}</div>
62
- <div className="opacity-60">{chain?.displayName}</div>
63
- </div>
64
- <TokenLogo
65
- token={token}
66
- className="w-9 h-9"
67
- generatedLogoClassName="w-9 h-9"
68
- />
69
- </div>
70
- </div>
71
- );
72
- };
@@ -1,25 +0,0 @@
1
- import { TokenLogoWithChain } from "@src/components";
2
- import { Chain, Token } from "@src/models";
3
-
4
- type Props = {
5
- chain: Chain | undefined;
6
- token: Token | undefined;
7
- value: string;
8
- };
9
- export const TokenItem = ({ chain, token, value }: Props) => {
10
- return (
11
- <div className="flex items-center gap-2">
12
- <TokenLogoWithChain
13
- chain={chain}
14
- token={token}
15
- chainLogoClassName="w-5"
16
- tokenLogoClassName="w-9 h-9"
17
- generatedLogoClassName="w-9 h-9 text-[16px]"
18
- />
19
- <div className="flex flex-col w-min sm:w-auto">
20
- <div>{`${value} ${token?.symbol}`}</div>
21
- <div className="opacity-60">{chain?.displayName}</div>
22
- </div>
23
- </div>
24
- );
25
- };