thirdweb 5.106.1 → 5.107.1-nightly-d53f8a25b4978a9f92f494f50955765817e31bad-20250920000329

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 (146) hide show
  1. package/dist/cjs/bridge/Token.js +4 -1
  2. package/dist/cjs/bridge/Token.js.map +1 -1
  3. package/dist/cjs/exports/x402.js +8 -0
  4. package/dist/cjs/exports/x402.js.map +1 -0
  5. package/dist/cjs/react/core/design-system/index.js +1 -0
  6. package/dist/cjs/react/core/design-system/index.js.map +1 -1
  7. package/dist/cjs/react/web/ui/Bridge/BuyWidget.js +1 -1
  8. package/dist/cjs/react/web/ui/Bridge/CheckoutWidget.js +1 -1
  9. package/dist/cjs/react/web/ui/Bridge/TransactionWidget.js +1 -1
  10. package/dist/cjs/react/web/ui/Bridge/swap-widget/SearchInput.js +1 -1
  11. package/dist/cjs/react/web/ui/Bridge/swap-widget/SearchInput.js.map +1 -1
  12. package/dist/cjs/react/web/ui/Bridge/swap-widget/SwapWidget.js +44 -28
  13. package/dist/cjs/react/web/ui/Bridge/swap-widget/SwapWidget.js.map +1 -1
  14. package/dist/cjs/react/web/ui/Bridge/swap-widget/select-chain.js +9 -7
  15. package/dist/cjs/react/web/ui/Bridge/swap-widget/select-chain.js.map +1 -1
  16. package/dist/cjs/react/web/ui/Bridge/swap-widget/select-token-ui.js +82 -46
  17. package/dist/cjs/react/web/ui/Bridge/swap-widget/select-token-ui.js.map +1 -1
  18. package/dist/cjs/react/web/ui/Bridge/swap-widget/swap-ui.js +215 -80
  19. package/dist/cjs/react/web/ui/Bridge/swap-widget/swap-ui.js.map +1 -1
  20. package/dist/cjs/react/web/ui/Bridge/swap-widget/utils.js +6 -0
  21. package/dist/cjs/react/web/ui/Bridge/swap-widget/utils.js.map +1 -1
  22. package/dist/cjs/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.js +1 -1
  23. package/dist/cjs/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.js.map +1 -1
  24. package/dist/cjs/react/web/ui/components/buttons.js +5 -2
  25. package/dist/cjs/react/web/ui/components/buttons.js.map +1 -1
  26. package/dist/cjs/react/web/ui/components/formElements.js +4 -1
  27. package/dist/cjs/react/web/ui/components/formElements.js.map +1 -1
  28. package/dist/cjs/react/web/ui/hooks/useisMobile.js +19 -0
  29. package/dist/cjs/react/web/ui/hooks/useisMobile.js.map +1 -0
  30. package/dist/cjs/stories/Bridge/Swap/SelectChain.stories.js +16 -6
  31. package/dist/cjs/stories/Bridge/Swap/SelectChain.stories.js.map +1 -1
  32. package/dist/cjs/stories/Bridge/Swap/SwapWidget.stories.js +6 -11
  33. package/dist/cjs/stories/Bridge/Swap/SwapWidget.stories.js.map +1 -1
  34. package/dist/cjs/version.js +1 -1
  35. package/dist/cjs/version.js.map +1 -1
  36. package/dist/cjs/x402/facilitator.js +76 -0
  37. package/dist/cjs/x402/facilitator.js.map +1 -0
  38. package/dist/cjs/x402/fetchWithPayment.js +121 -0
  39. package/dist/cjs/x402/fetchWithPayment.js.map +1 -0
  40. package/dist/esm/bridge/Token.js +4 -1
  41. package/dist/esm/bridge/Token.js.map +1 -1
  42. package/dist/esm/exports/x402.js +3 -0
  43. package/dist/esm/exports/x402.js.map +1 -0
  44. package/dist/esm/react/core/design-system/index.js +1 -0
  45. package/dist/esm/react/core/design-system/index.js.map +1 -1
  46. package/dist/esm/react/web/ui/Bridge/BuyWidget.js +1 -1
  47. package/dist/esm/react/web/ui/Bridge/CheckoutWidget.js +1 -1
  48. package/dist/esm/react/web/ui/Bridge/TransactionWidget.js +1 -1
  49. package/dist/esm/react/web/ui/Bridge/swap-widget/SearchInput.js +1 -1
  50. package/dist/esm/react/web/ui/Bridge/swap-widget/SearchInput.js.map +1 -1
  51. package/dist/esm/react/web/ui/Bridge/swap-widget/SwapWidget.js +44 -28
  52. package/dist/esm/react/web/ui/Bridge/swap-widget/SwapWidget.js.map +1 -1
  53. package/dist/esm/react/web/ui/Bridge/swap-widget/select-chain.js +11 -9
  54. package/dist/esm/react/web/ui/Bridge/swap-widget/select-chain.js.map +1 -1
  55. package/dist/esm/react/web/ui/Bridge/swap-widget/select-token-ui.js +83 -47
  56. package/dist/esm/react/web/ui/Bridge/swap-widget/select-token-ui.js.map +1 -1
  57. package/dist/esm/react/web/ui/Bridge/swap-widget/swap-ui.js +218 -83
  58. package/dist/esm/react/web/ui/Bridge/swap-widget/swap-ui.js.map +1 -1
  59. package/dist/esm/react/web/ui/Bridge/swap-widget/utils.js +5 -0
  60. package/dist/esm/react/web/ui/Bridge/swap-widget/utils.js.map +1 -1
  61. package/dist/esm/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.js +2 -2
  62. package/dist/esm/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.js.map +1 -1
  63. package/dist/esm/react/web/ui/components/buttons.js +5 -2
  64. package/dist/esm/react/web/ui/components/buttons.js.map +1 -1
  65. package/dist/esm/react/web/ui/components/formElements.js +5 -2
  66. package/dist/esm/react/web/ui/components/formElements.js.map +1 -1
  67. package/dist/esm/react/web/ui/hooks/useisMobile.js +16 -0
  68. package/dist/esm/react/web/ui/hooks/useisMobile.js.map +1 -0
  69. package/dist/esm/stories/Bridge/Swap/SelectChain.stories.js +12 -4
  70. package/dist/esm/stories/Bridge/Swap/SelectChain.stories.js.map +1 -1
  71. package/dist/esm/stories/Bridge/Swap/SwapWidget.stories.js +7 -12
  72. package/dist/esm/stories/Bridge/Swap/SwapWidget.stories.js.map +1 -1
  73. package/dist/esm/version.js +1 -1
  74. package/dist/esm/version.js.map +1 -1
  75. package/dist/esm/x402/facilitator.js +73 -0
  76. package/dist/esm/x402/facilitator.js.map +1 -0
  77. package/dist/esm/x402/fetchWithPayment.js +118 -0
  78. package/dist/esm/x402/fetchWithPayment.js.map +1 -0
  79. package/dist/types/bridge/Token.d.ts +2 -0
  80. package/dist/types/bridge/Token.d.ts.map +1 -1
  81. package/dist/types/bridge/types/Token.d.ts +2 -0
  82. package/dist/types/bridge/types/Token.d.ts.map +1 -1
  83. package/dist/types/exports/x402.d.ts +3 -0
  84. package/dist/types/exports/x402.d.ts.map +1 -0
  85. package/dist/types/react/core/design-system/index.d.ts +1 -0
  86. package/dist/types/react/core/design-system/index.d.ts.map +1 -1
  87. package/dist/types/react/web/ui/Bridge/BuyWidget.d.ts +1 -1
  88. package/dist/types/react/web/ui/Bridge/CheckoutWidget.d.ts +1 -1
  89. package/dist/types/react/web/ui/Bridge/TransactionWidget.d.ts +1 -1
  90. package/dist/types/react/web/ui/Bridge/swap-widget/SearchInput.d.ts.map +1 -1
  91. package/dist/types/react/web/ui/Bridge/swap-widget/SwapWidget.d.ts +5 -0
  92. package/dist/types/react/web/ui/Bridge/swap-widget/SwapWidget.d.ts.map +1 -1
  93. package/dist/types/react/web/ui/Bridge/swap-widget/select-chain.d.ts +1 -0
  94. package/dist/types/react/web/ui/Bridge/swap-widget/select-chain.d.ts.map +1 -1
  95. package/dist/types/react/web/ui/Bridge/swap-widget/select-token-ui.d.ts.map +1 -1
  96. package/dist/types/react/web/ui/Bridge/swap-widget/swap-ui.d.ts +2 -1
  97. package/dist/types/react/web/ui/Bridge/swap-widget/swap-ui.d.ts.map +1 -1
  98. package/dist/types/react/web/ui/Bridge/swap-widget/utils.d.ts +1 -0
  99. package/dist/types/react/web/ui/Bridge/swap-widget/utils.d.ts.map +1 -1
  100. package/dist/types/react/web/ui/components/basic.d.ts +1 -1
  101. package/dist/types/react/web/ui/components/basic.d.ts.map +1 -1
  102. package/dist/types/react/web/ui/components/buttons.d.ts +1 -0
  103. package/dist/types/react/web/ui/components/buttons.d.ts.map +1 -1
  104. package/dist/types/react/web/ui/components/formElements.d.ts.map +1 -1
  105. package/dist/types/react/web/ui/hooks/useisMobile.d.ts +2 -0
  106. package/dist/types/react/web/ui/hooks/useisMobile.d.ts.map +1 -0
  107. package/dist/types/stories/Bridge/Swap/SelectChain.stories.d.ts +4 -2
  108. package/dist/types/stories/Bridge/Swap/SelectChain.stories.d.ts.map +1 -1
  109. package/dist/types/stories/Bridge/Swap/SwapWidget.stories.d.ts.map +1 -1
  110. package/dist/types/version.d.ts +1 -1
  111. package/dist/types/version.d.ts.map +1 -1
  112. package/dist/types/x402/facilitator.d.ts +48 -0
  113. package/dist/types/x402/facilitator.d.ts.map +1 -0
  114. package/dist/types/x402/fetchWithPayment.d.ts +43 -0
  115. package/dist/types/x402/fetchWithPayment.d.ts.map +1 -0
  116. package/package.json +10 -1
  117. package/src/bridge/Token.ts +6 -0
  118. package/src/bridge/types/Token.ts +2 -0
  119. package/src/exports/x402.ts +5 -0
  120. package/src/react/core/design-system/index.ts +1 -0
  121. package/src/react/web/ui/Bridge/BuyWidget.tsx +1 -1
  122. package/src/react/web/ui/Bridge/CheckoutWidget.tsx +1 -1
  123. package/src/react/web/ui/Bridge/TransactionWidget.tsx +1 -1
  124. package/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx +1 -0
  125. package/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx +62 -45
  126. package/src/react/web/ui/Bridge/swap-widget/select-chain.tsx +25 -12
  127. package/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx +302 -197
  128. package/src/react/web/ui/Bridge/swap-widget/swap-ui.tsx +496 -233
  129. package/src/react/web/ui/Bridge/swap-widget/utils.ts +6 -0
  130. package/src/react/web/ui/ConnectWallet/icons/ArrowUpDownIcon.tsx +10 -10
  131. package/src/react/web/ui/components/basic.tsx +1 -1
  132. package/src/react/web/ui/components/buttons.tsx +6 -2
  133. package/src/react/web/ui/components/formElements.tsx +5 -1
  134. package/src/react/web/ui/hooks/useisMobile.ts +21 -0
  135. package/src/stories/Bridge/Swap/SelectChain.stories.tsx +40 -2
  136. package/src/stories/Bridge/Swap/SwapWidget.stories.tsx +18 -13
  137. package/src/version.ts +1 -1
  138. package/src/x402/facilitator.ts +87 -0
  139. package/src/x402/fetchWithPayment.ts +173 -0
  140. package/dist/cjs/react/web/ui/Bridge/swap-widget/common.js +0 -17
  141. package/dist/cjs/react/web/ui/Bridge/swap-widget/common.js.map +0 -1
  142. package/dist/esm/react/web/ui/Bridge/swap-widget/common.js +0 -14
  143. package/dist/esm/react/web/ui/Bridge/swap-widget/common.js.map +0 -1
  144. package/dist/types/react/web/ui/Bridge/swap-widget/common.d.ts +0 -9
  145. package/dist/types/react/web/ui/Bridge/swap-widget/common.d.ts.map +0 -1
  146. package/src/react/web/ui/Bridge/swap-widget/common.tsx +0 -35
@@ -1,9 +1,5 @@
1
1
  import styled from "@emotion/styled";
2
- import {
3
- ChevronDownIcon,
4
- ChevronRightIcon,
5
- DiscIcon,
6
- } from "@radix-ui/react-icons";
2
+ import { ChevronDownIcon } from "@radix-ui/react-icons";
7
3
  import { useQuery } from "@tanstack/react-query";
8
4
  import { useState } from "react";
9
5
  import type { prepare as BuyPrepare } from "../../../../../bridge/Buy.js";
@@ -15,12 +11,10 @@ import { defineChain } from "../../../../../chains/utils.js";
15
11
  import type { ThirdwebClient } from "../../../../../client/client.js";
16
12
  import { NATIVE_TOKEN_ADDRESS } from "../../../../../constants/addresses.js";
17
13
  import { getToken } from "../../../../../pay/convert/get-token.js";
18
- import {
19
- getFiatSymbol,
20
- type SupportedFiatCurrency,
21
- } from "../../../../../pay/convert/type.js";
22
- import { getAddress } from "../../../../../utils/address.js";
14
+ import type { SupportedFiatCurrency } from "../../../../../pay/convert/type.js";
15
+ import { getAddress, shortenAddress } from "../../../../../utils/address.js";
23
16
  import { toTokens, toUnits } from "../../../../../utils/units.js";
17
+ import { AccountProvider } from "../../../../core/account/provider.js";
24
18
  import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js";
25
19
  import {
26
20
  fontSize,
@@ -31,11 +25,16 @@ import {
31
25
  } from "../../../../core/design-system/index.js";
32
26
  import { useWalletBalance } from "../../../../core/hooks/others/useWalletBalance.js";
33
27
  import type { BridgePrepareRequest } from "../../../../core/hooks/useBridgePrepare.js";
28
+ import { WalletProvider } from "../../../../core/wallet/provider.js";
34
29
  import { ConnectButton } from "../../ConnectWallet/ConnectButton.js";
30
+ import { DetailsModal } from "../../ConnectWallet/Details.js";
35
31
  import { ArrowUpDownIcon } from "../../ConnectWallet/icons/ArrowUpDownIcon.js";
36
- import { WalletDotIcon } from "../../ConnectWallet/icons/WalletDotIcon.js";
32
+ import connectLocaleEn from "../../ConnectWallet/locale/en.js";
37
33
  import { PoweredByThirdweb } from "../../ConnectWallet/PoweredByTW.js";
38
- import { formatTokenAmount } from "../../ConnectWallet/screens/formatTokenBalance.js";
34
+ import {
35
+ formatCurrencyAmount,
36
+ formatTokenAmount,
37
+ } from "../../ConnectWallet/screens/formatTokenBalance.js";
39
38
  import { Container } from "../../components/basic.js";
40
39
  import { Button } from "../../components/buttons.js";
41
40
  import { Input } from "../../components/formElements.js";
@@ -43,8 +42,13 @@ import { Img } from "../../components/Img.js";
43
42
  import { Modal } from "../../components/Modal.js";
44
43
  import { Skeleton } from "../../components/Skeleton.js";
45
44
  import { Spacer } from "../../components/Spacer.js";
45
+ import { Spinner } from "../../components/Spinner.js";
46
46
  import { Text } from "../../components/text.js";
47
- import { DecimalRenderer } from "./common.js";
47
+ import { useIsMobile } from "../../hooks/useisMobile.js";
48
+ import { AccountAvatar } from "../../prebuilt/Account/avatar.js";
49
+ import { AccountBlobbie } from "../../prebuilt/Account/blobbie.js";
50
+ import { AccountName } from "../../prebuilt/Account/name.js";
51
+ import { WalletIcon } from "../../prebuilt/Wallet/icon.js";
48
52
  import { SelectToken } from "./select-token-ui.js";
49
53
  import type {
50
54
  ActiveWalletInfo,
@@ -82,6 +86,7 @@ type SwapUIProps = {
82
86
  type: "buy" | "sell";
83
87
  amount: string;
84
88
  }) => void;
89
+ onDisconnect: (() => void) | undefined;
85
90
  };
86
91
 
87
92
  function useTokenPrice(options: {
@@ -110,9 +115,16 @@ function useTokenPrice(options: {
110
115
  * @internal
111
116
  */
112
117
  export function SwapUI(props: SwapUIProps) {
113
- const [modalState, setModalState] = useState<
114
- "select-buy-token" | "select-sell-token" | undefined
115
- >(undefined);
118
+ const [modalState, setModalState] = useState<{
119
+ screen: "select-buy-token" | "select-sell-token";
120
+ isOpen: boolean;
121
+ }>({
122
+ screen: "select-buy-token",
123
+ isOpen: false,
124
+ });
125
+
126
+ const [detailsModalOpen, setDetailsModalOpen] = useState(false);
127
+ const isMobile = useIsMobile();
116
128
 
117
129
  // Token Prices ----------------------------------------------------------------------------
118
130
  const buyTokenQuery = useTokenPrice({
@@ -193,14 +205,19 @@ export function SwapUI(props: SwapUIProps) {
193
205
  );
194
206
 
195
207
  // ----------------------------------------------------------------------------
208
+ const disableContinue =
209
+ !preparedResultQuery.data ||
210
+ preparedResultQuery.isFetching ||
211
+ notEnoughBalance;
196
212
 
197
213
  return (
198
214
  <Container p="md">
199
215
  <Modal
216
+ hide={false}
200
217
  className="tw-modal__swap-widget"
201
- size="compact"
218
+ size={isMobile ? "compact" : "wide"}
202
219
  title="Select Token"
203
- open={!!modalState}
220
+ open={modalState.isOpen}
204
221
  crossContainerStyles={{
205
222
  right: spacing.md,
206
223
  top: spacing["md+"],
@@ -208,19 +225,30 @@ export function SwapUI(props: SwapUIProps) {
208
225
  }}
209
226
  setOpen={(v) => {
210
227
  if (!v) {
211
- setModalState(undefined);
228
+ setModalState((v) => ({
229
+ ...v,
230
+ isOpen: false,
231
+ }));
212
232
  }
213
233
  }}
214
234
  >
215
- {modalState === "select-buy-token" && (
235
+ {modalState.screen === "select-buy-token" && (
216
236
  <SelectToken
217
237
  activeWalletInfo={props.activeWalletInfo}
218
- onBack={() => setModalState(undefined)}
238
+ onBack={() =>
239
+ setModalState((v) => ({
240
+ ...v,
241
+ isOpen: false,
242
+ }))
243
+ }
219
244
  client={props.client}
220
245
  selectedToken={props.buyToken}
221
246
  setSelectedToken={(token) => {
222
247
  props.setBuyToken(token);
223
- setModalState(undefined);
248
+ setModalState((v) => ({
249
+ ...v,
250
+ isOpen: false,
251
+ }));
224
252
  // if buy token is same as sell token, unset sell token
225
253
  if (
226
254
  props.sellToken &&
@@ -234,14 +262,22 @@ export function SwapUI(props: SwapUIProps) {
234
262
  />
235
263
  )}
236
264
 
237
- {modalState === "select-sell-token" && (
265
+ {modalState.screen === "select-sell-token" && (
238
266
  <SelectToken
239
- onBack={() => setModalState(undefined)}
267
+ onBack={() =>
268
+ setModalState((v) => ({
269
+ ...v,
270
+ isOpen: false,
271
+ }))
272
+ }
240
273
  client={props.client}
241
274
  selectedToken={props.sellToken}
242
275
  setSelectedToken={(token) => {
243
276
  props.setSellToken(token);
244
- setModalState(undefined);
277
+ setModalState((v) => ({
278
+ ...v,
279
+ isOpen: false,
280
+ }));
245
281
  // if sell token is same as buy token, unset buy token
246
282
  if (
247
283
  props.buyToken &&
@@ -257,10 +293,35 @@ export function SwapUI(props: SwapUIProps) {
257
293
  )}
258
294
  </Modal>
259
295
 
296
+ {detailsModalOpen && (
297
+ <DetailsModal
298
+ client={props.client}
299
+ locale={connectLocaleEn}
300
+ detailsModal={undefined}
301
+ theme={props.theme}
302
+ closeModal={() => {
303
+ setDetailsModalOpen(false);
304
+ }}
305
+ onDisconnect={() => {
306
+ props.onDisconnect?.();
307
+ }}
308
+ chains={[]}
309
+ connectOptions={props.connectOptions}
310
+ />
311
+ )}
312
+
260
313
  {/* Sell */}
261
314
  <TokenSection
315
+ onMaxClick={() => {
316
+ if (sellTokenBalanceQuery.data) {
317
+ props.setAmountSelection({
318
+ type: "sell",
319
+ amount: sellTokenBalanceQuery.data.displayValue,
320
+ });
321
+ }
322
+ }}
323
+ activeWalletInfo={props.activeWalletInfo}
262
324
  isConnected={!!props.activeWalletInfo}
263
- notEnoughBalance={notEnoughBalance}
264
325
  balance={{
265
326
  data: sellTokenBalanceQuery.data?.value,
266
327
  isFetching: sellTokenBalanceQuery.isFetching,
@@ -269,7 +330,7 @@ export function SwapUI(props: SwapUIProps) {
269
330
  data: sellTokenAmount,
270
331
  isFetching: isSellAmountFetching,
271
332
  }}
272
- label="Sell"
333
+ type="sell"
273
334
  setAmount={(value) => {
274
335
  props.setAmountSelection({ type: "sell", amount: value });
275
336
  }}
@@ -283,7 +344,15 @@ export function SwapUI(props: SwapUIProps) {
283
344
  }
284
345
  client={props.client}
285
346
  currency={props.currency}
286
- onSelectToken={() => setModalState("select-sell-token")}
347
+ onSelectToken={() =>
348
+ setModalState({
349
+ screen: "select-sell-token",
350
+ isOpen: true,
351
+ })
352
+ }
353
+ onWalletClick={() => {
354
+ setDetailsModalOpen(true);
355
+ }}
287
356
  />
288
357
 
289
358
  {/* Switch */}
@@ -302,8 +371,12 @@ export function SwapUI(props: SwapUIProps) {
302
371
 
303
372
  {/* Buy */}
304
373
  <TokenSection
374
+ onMaxClick={undefined}
375
+ onWalletClick={() => {
376
+ setDetailsModalOpen(true);
377
+ }}
378
+ activeWalletInfo={props.activeWalletInfo}
305
379
  isConnected={!!props.activeWalletInfo}
306
- notEnoughBalance={false}
307
380
  balance={{
308
381
  data: buyTokenBalanceQuery.data?.value,
309
382
  isFetching: buyTokenBalanceQuery.isFetching,
@@ -312,7 +385,7 @@ export function SwapUI(props: SwapUIProps) {
312
385
  data: buyTokenAmount,
313
386
  isFetching: isBuyAmountFetching,
314
387
  }}
315
- label="Buy"
388
+ type="buy"
316
389
  selectedToken={
317
390
  props.buyToken
318
391
  ? {
@@ -326,7 +399,12 @@ export function SwapUI(props: SwapUIProps) {
326
399
  }}
327
400
  client={props.client}
328
401
  currency={props.currency}
329
- onSelectToken={() => setModalState("select-buy-token")}
402
+ onSelectToken={() =>
403
+ setModalState({
404
+ screen: "select-buy-token",
405
+ isOpen: true,
406
+ })
407
+ }
330
408
  />
331
409
 
332
410
  {/* error message */}
@@ -342,7 +420,7 @@ export function SwapUI(props: SwapUIProps) {
342
420
  Failed to get a quote
343
421
  </Text>
344
422
  ) : (
345
- <Spacer y="lg" />
423
+ <Spacer y="md" />
346
424
  )}
347
425
 
348
426
  {/* Button */}
@@ -361,11 +439,7 @@ export function SwapUI(props: SwapUIProps) {
361
439
  />
362
440
  ) : (
363
441
  <Button
364
- disabled={
365
- !preparedResultQuery.data ||
366
- preparedResultQuery.isFetching ||
367
- notEnoughBalance
368
- }
442
+ disabled={disableContinue}
369
443
  fullWidth
370
444
  onClick={() => {
371
445
  if (
@@ -385,14 +459,20 @@ export function SwapUI(props: SwapUIProps) {
385
459
  });
386
460
  }
387
461
  }}
462
+ gap="xs"
388
463
  style={{
389
464
  fontSize: fontSize.md,
390
- padding: `${spacing.sm} ${spacing.md}`,
391
- borderRadius: radius.lg,
465
+ borderRadius: radius.full,
466
+ opacity: disableContinue ? 0.5 : 1,
392
467
  }}
393
468
  variant="primary"
394
469
  >
395
- Swap
470
+ {preparedResultQuery.isFetching && <Spinner size="sm" />}
471
+ {preparedResultQuery.isFetching
472
+ ? "Fetching Quote"
473
+ : notEnoughBalance
474
+ ? "Insufficient Balance"
475
+ : "Swap"}
396
476
  </Button>
397
477
  )}
398
478
 
@@ -599,10 +679,12 @@ function DecimalInput(props: {
599
679
  style={{
600
680
  border: "none",
601
681
  boxShadow: "none",
602
- fontSize: fontSize.xxl,
682
+ fontSize: fontSize.xl,
603
683
  fontWeight: 500,
604
684
  paddingInline: 0,
605
685
  paddingBlock: 0,
686
+ letterSpacing: "-0.025em",
687
+ height: "30px",
606
688
  }}
607
689
  type="text"
608
690
  value={props.value}
@@ -612,13 +694,13 @@ function DecimalInput(props: {
612
694
  }
613
695
 
614
696
  function TokenSection(props: {
615
- label: string;
616
- notEnoughBalance: boolean;
697
+ type: "buy" | "sell";
617
698
  amount: {
618
699
  data: string;
619
700
  isFetching: boolean;
620
701
  };
621
702
  setAmount: (amount: string) => void;
703
+ activeWalletInfo: ActiveWalletInfo | undefined;
622
704
  selectedToken:
623
705
  | {
624
706
  data: TokenWithPrices | undefined;
@@ -633,7 +715,10 @@ function TokenSection(props: {
633
715
  data: bigint | undefined;
634
716
  isFetching: boolean;
635
717
  };
718
+ onWalletClick: () => void;
719
+ onMaxClick: (() => void) | undefined;
636
720
  }) {
721
+ const theme = useCustomTheme();
637
722
  const chainQuery = useBridgeChains(props.client);
638
723
  const chain = chainQuery.data?.find(
639
724
  (chain) => chain.chainId === props.selectedToken?.data?.chainId,
@@ -648,140 +733,191 @@ function TokenSection(props: {
648
733
 
649
734
  return (
650
735
  <Container
651
- p="sm"
736
+ style={{
737
+ borderRadius: radius.xl,
738
+ borderWidth: 1,
739
+ borderStyle: "solid",
740
+ position: "relative",
741
+ overflow: "hidden",
742
+ }}
652
743
  borderColor="borderColor"
653
- bg="tertiaryBg"
654
- style={{ borderRadius: radius.lg, borderWidth: 1, borderStyle: "solid" }}
655
744
  >
656
- {/* row1 : label */}
657
- <Text size="md" color="primaryText" weight={500}>
658
- {props.label}
659
- </Text>
745
+ {/* make the background semi-transparent */}
746
+ <Container
747
+ bg="tertiaryBg"
748
+ style={{
749
+ position: "absolute",
750
+ inset: 0,
751
+ opacity: 0.5,
752
+ zIndex: 0,
753
+ }}
754
+ />
660
755
 
661
- {/* row2 : amount and select token */}
662
- <div
756
+ <Container
663
757
  style={{
664
- display: "flex",
665
- alignItems: "center",
666
- justifyContent: "space-between",
667
- gap: spacing.sm,
668
- paddingBottom: spacing.sm,
669
- paddingTop: spacing.sm,
758
+ position: "relative",
759
+ zIndex: 1,
670
760
  }}
671
761
  >
672
- {props.amount.isFetching ? (
673
- <Skeleton
674
- height={fontSize.xxl}
675
- width="140px"
676
- style={{
677
- borderRadius: radius.lg,
678
- }}
679
- />
680
- ) : (
681
- <DecimalInput value={props.amount.data} setValue={props.setAmount} />
682
- )}
762
+ {/* row1 : label */}
763
+ <Container
764
+ px="md"
765
+ py="sm"
766
+ relative
767
+ style={{
768
+ display: "flex",
769
+ justifyContent: "space-between",
770
+ alignItems: "center",
771
+ }}
772
+ >
773
+ <Container flex="row" center="y" gap="3xs" color="secondaryText">
774
+ <Text
775
+ size="xs"
776
+ color="primaryText"
777
+ style={{
778
+ letterSpacing: "0.07em",
779
+ }}
780
+ >
781
+ {props.type === "buy" ? "BUY" : "SELL"}
782
+ </Text>
783
+ </Container>
784
+ {props.activeWalletInfo && (
785
+ <WalletButton
786
+ variant="ghost-solid"
787
+ style={{
788
+ paddingInline: spacing.xxs,
789
+ paddingBlock: "2px",
790
+ }}
791
+ onClick={props.onWalletClick}
792
+ >
793
+ <ActiveWalletDetails
794
+ activeWalletInfo={props.activeWalletInfo}
795
+ client={props.client}
796
+ />
797
+ </WalletButton>
798
+ )}
799
+ </Container>
683
800
 
684
- {!props.selectedToken ? (
685
- <Button
686
- variant="accent"
687
- style={{
688
- borderRadius: radius.full,
689
- gap: spacing.xxs,
690
- paddingBlock: spacing.sm,
691
- paddingInline: spacing.sm,
692
- }}
693
- onClick={props.onSelectToken}
694
- >
695
- {" "}
696
- Select Token
697
- <ChevronDownIcon width={iconSize.sm} height={iconSize.sm} />
698
- </Button>
699
- ) : (
801
+ <Container
802
+ bg="tertiaryBg"
803
+ style={{
804
+ position: "relative",
805
+ overflow: "hidden",
806
+ borderRadius: radius.xl,
807
+ borderTop: `1px solid ${theme.colors.borderColor}`,
808
+ }}
809
+ >
700
810
  <SelectedTokenButton
701
811
  selectedToken={props.selectedToken}
702
812
  client={props.client}
703
813
  onSelectToken={props.onSelectToken}
704
814
  chain={chain}
705
815
  />
706
- )}
707
- </div>
708
816
 
709
- {/* row3 : fiat value/error and balance */}
710
- <div
711
- style={{
712
- display: "flex",
713
- alignItems: "center",
714
- gap: "4px",
715
- justifyContent: "space-between",
716
- }}
717
- >
718
- {/* Exceeds Balance / Fiat Value */}
719
- {props.notEnoughBalance ? (
720
- <Text size="md" color="danger" weight={500}>
721
- {" "}
722
- Exceeds Balance{" "}
723
- </Text>
724
- ) : (
725
- <div
726
- style={{
727
- display: "flex",
728
- alignItems: "center",
729
- gap: "4px",
730
- }}
731
- >
732
- <Text size="md" color="secondaryText" weight={500}>
733
- {getFiatSymbol(props.currency)}
734
- </Text>
735
- {props.amount.isFetching ? (
736
- <Skeleton height={fontSize.md} width="50px" />
737
- ) : (
738
- <div>
739
- <DecimalRenderer
740
- integerSize="md"
741
- fractionSize="sm"
742
- value={totalFiatValue?.toFixed(5) || "0.00"}
743
- color="secondaryText"
744
- weight={500}
817
+ <Container px="md" py="md">
818
+ <Container
819
+ flex="row"
820
+ gap="xs"
821
+ center="y"
822
+ style={{
823
+ flexWrap: "nowrap",
824
+ }}
825
+ >
826
+ {props.amount.isFetching ? (
827
+ <div style={{ flexGrow: 1 }}>
828
+ <Skeleton
829
+ height="30px"
830
+ width="140px"
831
+ style={{
832
+ borderRadius: radius.lg,
833
+ }}
834
+ />
835
+ </div>
836
+ ) : (
837
+ <DecimalInput
838
+ value={props.amount.data}
839
+ setValue={props.setAmount}
745
840
  />
746
- </div>
747
- )}
748
- </div>
749
- )}
841
+ )}
750
842
 
751
- {/* Balance */}
752
- {props.isConnected && props.selectedToken && (
753
- <div>
754
- {props.balance.data === undefined ||
755
- props.selectedToken.data === undefined ? (
756
- <Skeleton height={fontSize.md} width="50px" />
757
- ) : (
758
- <Text
759
- size="md"
760
- color="secondaryText"
761
- weight={500}
843
+ {props.activeWalletInfo &&
844
+ props.onMaxClick &&
845
+ props.selectedToken && (
846
+ <Button
847
+ variant="outline"
848
+ style={{
849
+ paddingInline: spacing.xs,
850
+ paddingBlock: spacing.xxs,
851
+ borderRadius: radius.full,
852
+ fontSize: fontSize.xs,
853
+ fontWeight: 400,
854
+ }}
855
+ onClick={props.onMaxClick}
856
+ >
857
+ Max
858
+ </Button>
859
+ )}
860
+ </Container>
861
+
862
+ <Spacer y="sm" />
863
+
864
+ {/* row3 : fiat value and balance */}
865
+ <div
866
+ style={{
867
+ display: "flex",
868
+ alignItems: "center",
869
+ gap: "4px",
870
+ justifyContent: "space-between",
871
+ }}
872
+ >
873
+ <div
762
874
  style={{
763
875
  display: "flex",
764
876
  alignItems: "center",
765
- gap: spacing.xxs,
877
+ gap: "3px",
766
878
  }}
767
879
  >
768
- <WalletDotIcon size={fontSize.xs} />
769
- <DecimalRenderer
770
- integerSize="md"
771
- fractionSize="sm"
772
- color="secondaryText"
773
- weight={500}
774
- value={formatTokenAmount(
775
- props.balance.data,
776
- props.selectedToken.data.decimals,
777
- 5,
880
+ {props.amount.isFetching ? (
881
+ <Skeleton height={fontSize.xs} width="50px" />
882
+ ) : (
883
+ <Text size="xs" color="secondaryText">
884
+ {formatCurrencyAmount(props.currency, totalFiatValue || 0)}
885
+ </Text>
886
+ )}
887
+ </div>
888
+
889
+ {/* Balance */}
890
+ {props.isConnected && props.selectedToken && (
891
+ <div>
892
+ {props.balance.data === undefined ||
893
+ props.selectedToken.data === undefined ? (
894
+ <Skeleton height={fontSize.xs} width="100px" />
895
+ ) : (
896
+ <div
897
+ style={{
898
+ display: "flex",
899
+ alignItems: "center",
900
+ gap: "3px",
901
+ }}
902
+ >
903
+ <Text size="xs" color="secondaryText">
904
+ Balance:
905
+ </Text>
906
+ <Text size="xs" color="primaryText">
907
+ {formatTokenAmount(
908
+ props.balance.data,
909
+ props.selectedToken.data.decimals,
910
+ 5,
911
+ )}
912
+ </Text>
913
+ </div>
778
914
  )}
779
- />
780
- </Text>
781
- )}
782
- </div>
783
- )}
784
- </div>
915
+ </div>
916
+ )}
917
+ </div>
918
+ </Container>
919
+ </Container>
920
+ </Container>
785
921
  </Container>
786
922
  );
787
923
  }
@@ -797,92 +933,141 @@ function SelectedTokenButton(props: {
797
933
  onSelectToken: () => void;
798
934
  chain: BridgeChain | undefined;
799
935
  }) {
936
+ const theme = useCustomTheme();
800
937
  return (
801
938
  <Button
802
- variant="outline"
803
- bg="secondaryButtonBg"
939
+ variant="ghost-solid"
940
+ hoverBg="secondaryButtonBg"
941
+ fullWidth
804
942
  onClick={props.onSelectToken}
805
- gap="xs"
943
+ gap="sm"
806
944
  style={{
807
- borderRadius: radius.full,
808
- paddingBlock: spacing.xxs,
809
- paddingInline: spacing.xs,
945
+ borderBottom: `1px dashed ${theme.colors.borderColor}`,
946
+ justifyContent: "space-between",
947
+ paddingInline: spacing.md,
948
+ paddingBlock: spacing.md,
949
+ borderRadius: 0,
810
950
  }}
811
951
  >
812
- {/* icons */}
813
- <Container relative color="secondaryText">
814
- {/* token icon */}
815
- <Img
816
- key={props.selectedToken?.data?.iconUri}
817
- src={
818
- props.selectedToken?.data === undefined
819
- ? undefined
820
- : props.selectedToken.data.iconUri || ""
821
- }
822
- client={props.client}
823
- width={iconSize.lg}
824
- height={iconSize.lg}
825
- skeletonColor="modalBg"
826
- fallback={<DiscIcon width={iconSize.lg} height={iconSize.lg} />}
827
- style={{
828
- borderRadius: radius.full,
829
- }}
830
- />
952
+ <Container gap="sm" flex="row" center="y">
953
+ {/* icons */}
954
+ <Container relative color="secondaryText">
955
+ {/* token icon */}
956
+ {props.selectedToken ? (
957
+ <Img
958
+ key={props.selectedToken?.data?.iconUri}
959
+ src={
960
+ props.selectedToken?.data === undefined
961
+ ? undefined
962
+ : props.selectedToken.data.iconUri || ""
963
+ }
964
+ client={props.client}
965
+ width="40"
966
+ height="40"
967
+ fallback={
968
+ <Container
969
+ style={{
970
+ background: `linear-gradient(45deg, white, ${theme.colors.accentText})`,
971
+ borderRadius: radius.full,
972
+ width: "40px",
973
+ height: "40px",
974
+ }}
975
+ />
976
+ }
977
+ style={{
978
+ objectFit: "cover",
979
+ borderRadius: radius.full,
980
+ }}
981
+ />
982
+ ) : (
983
+ <Container
984
+ style={{
985
+ border: `1px solid ${theme.colors.borderColor}`,
986
+ background: `linear-gradient(45deg, white, ${theme.colors.accentText})`,
987
+ borderRadius: radius.full,
988
+ width: "40px",
989
+ height: "40px",
990
+ }}
991
+ />
992
+ )}
831
993
 
832
- {/* chain icon */}
833
- <Container
834
- bg="modalBg"
835
- style={{
836
- padding: "2px",
837
- position: "absolute",
838
- bottom: -2,
839
- right: -2,
840
- display: "flex",
841
- borderRadius: radius.full,
842
- }}
843
- >
844
- <Img
845
- src={props.chain?.icon}
846
- client={props.client}
847
- width={iconSize.sm}
848
- height={iconSize.sm}
849
- style={{
850
- borderRadius: radius.full,
851
- }}
852
- />
994
+ {/* chain icon */}
995
+ {props.chain && (
996
+ <Container
997
+ bg="modalBg"
998
+ style={{
999
+ padding: "2px",
1000
+ position: "absolute",
1001
+ bottom: -2,
1002
+ right: -2,
1003
+ display: "flex",
1004
+ borderRadius: radius.full,
1005
+ }}
1006
+ >
1007
+ <Img
1008
+ src={props.chain?.icon}
1009
+ client={props.client}
1010
+ width={iconSize.sm}
1011
+ height={iconSize.sm}
1012
+ style={{
1013
+ borderRadius: radius.full,
1014
+ }}
1015
+ />
1016
+ </Container>
1017
+ )}
853
1018
  </Container>
854
- </Container>
855
1019
 
856
- {/* token symbol and chain name */}
857
- <Container flex="column" style={{ gap: "2px" }}>
858
- {props.selectedToken?.isFetching ? (
859
- <Skeleton width="40px" height={fontSize.sm} color="modalBg" />
860
- ) : (
861
- <Text size="sm" color="primaryText" weight={500}>
862
- {props.selectedToken?.data?.symbol}
863
- </Text>
864
- )}
1020
+ {/* token symbol and chain name */}
1021
+ {props.selectedToken ? (
1022
+ <Container flex="column" style={{ gap: "3px" }}>
1023
+ {props.selectedToken?.isFetching ? (
1024
+ <Skeleton width="60px" height={fontSize.md} />
1025
+ ) : (
1026
+ <Text size="md" color="primaryText" weight={500}>
1027
+ {props.selectedToken?.data?.symbol}
1028
+ </Text>
1029
+ )}
865
1030
 
866
- {props.chain ? (
867
- <Text
868
- size="xs"
869
- color="secondaryText"
870
- weight={500}
871
- style={{
872
- maxWidth: "100px",
873
- overflow: "hidden",
874
- textOverflow: "ellipsis",
875
- whiteSpace: "nowrap",
876
- }}
877
- >
878
- {cleanedChainName(props.chain.name)}
879
- </Text>
1031
+ {props.chain ? (
1032
+ <Text
1033
+ size="xs"
1034
+ color="secondaryText"
1035
+ style={{
1036
+ overflow: "hidden",
1037
+ textOverflow: "ellipsis",
1038
+ whiteSpace: "nowrap",
1039
+ }}
1040
+ >
1041
+ {cleanedChainName(props.chain.name)}
1042
+ </Text>
1043
+ ) : (
1044
+ <Skeleton width="140px" height={fontSize.sm} />
1045
+ )}
1046
+ </Container>
880
1047
  ) : (
881
- <Skeleton width="70px" height={fontSize.xs} color="modalBg" />
1048
+ <Container flex="column" style={{ gap: "3px" }}>
1049
+ <Text size="md" color="primaryText" weight={500}>
1050
+ Select Token
1051
+ </Text>
1052
+ <Text size="xs" color="secondaryText">
1053
+ Required
1054
+ </Text>
1055
+ </Container>
882
1056
  )}
883
1057
  </Container>
884
- <Container color="secondaryText">
885
- <ChevronRightIcon width={iconSize.xs} height={iconSize.xs} />
1058
+ <Container
1059
+ color="secondaryText"
1060
+ flex="row"
1061
+ center="both"
1062
+ borderColor="borderColor"
1063
+ style={{
1064
+ borderRadius: radius.full,
1065
+ borderWidth: 1,
1066
+ borderStyle: "solid",
1067
+ padding: spacing.xs,
1068
+ }}
1069
+ >
1070
+ <ChevronDownIcon width={iconSize.sm} height={iconSize.sm} />
886
1071
  </Container>
887
1072
  </Button>
888
1073
  );
@@ -894,11 +1079,13 @@ function SwitchButton(props: { onClick: () => void }) {
894
1079
  style={{
895
1080
  display: "flex",
896
1081
  justifyContent: "center",
897
- marginBlock: `-14px`,
1082
+ marginBlock: `-13px`,
1083
+ zIndex: 2,
1084
+ position: "relative",
898
1085
  }}
899
1086
  >
900
1087
  <SwitchButtonInner
901
- variant="outline"
1088
+ variant="ghost-solid"
902
1089
  onClick={(e) => {
903
1090
  props.onClick();
904
1091
  const node = e.currentTarget.querySelector("svg");
@@ -912,7 +1099,7 @@ function SwitchButton(props: { onClick: () => void }) {
912
1099
  }
913
1100
  }}
914
1101
  >
915
- <ArrowUpDownIcon size={iconSize.md} />
1102
+ <ArrowUpDownIcon size={iconSize["sm+"]} />
916
1103
  </SwitchButtonInner>
917
1104
  </div>
918
1105
  );
@@ -922,10 +1109,11 @@ const SwitchButtonInner = /* @__PURE__ */ styled(Button)(() => {
922
1109
  const theme = useCustomTheme();
923
1110
  return {
924
1111
  "&:hover": {
925
- background: theme.colors.modalBg,
1112
+ background: theme.colors.secondaryButtonBg,
926
1113
  },
927
- borderRadius: radius.lg,
1114
+ borderRadius: radius.full,
928
1115
  padding: spacing.xs,
1116
+ color: theme.colors.primaryText,
929
1117
  background: theme.colors.modalBg,
930
1118
  border: `1px solid ${theme.colors.borderColor}`,
931
1119
  };
@@ -948,3 +1136,78 @@ function useTokenBalance(props: {
948
1136
  : undefined,
949
1137
  });
950
1138
  }
1139
+
1140
+ function ActiveWalletDetails(props: {
1141
+ activeWalletInfo: ActiveWalletInfo;
1142
+ client: ThirdwebClient;
1143
+ }) {
1144
+ const wallet = props.activeWalletInfo.activeWallet;
1145
+ const account = props.activeWalletInfo.activeAccount;
1146
+
1147
+ const accountBlobbie = (
1148
+ <AccountBlobbie
1149
+ style={{
1150
+ width: `${iconSize.xs}px`,
1151
+ height: `${iconSize.xs}px`,
1152
+ borderRadius: radius.full,
1153
+ }}
1154
+ />
1155
+ );
1156
+ const accountAvatarFallback = (
1157
+ <WalletIcon
1158
+ style={{
1159
+ width: `${iconSize.xs}px`,
1160
+ height: `${iconSize.xs}px`,
1161
+ }}
1162
+ fallbackComponent={accountBlobbie}
1163
+ loadingComponent={accountBlobbie}
1164
+ />
1165
+ );
1166
+
1167
+ return (
1168
+ <WalletProvider id={props.activeWalletInfo.activeWallet.id}>
1169
+ <AccountProvider address={account.address} client={props.client}>
1170
+ <WalletProvider id={wallet.id}>
1171
+ <Container flex="row" gap="xxs" center="y">
1172
+ <AccountAvatar
1173
+ style={{
1174
+ width: `${iconSize.xs}px`,
1175
+ height: `${iconSize.xs}px`,
1176
+ borderRadius: radius.full,
1177
+ }}
1178
+ fallbackComponent={accountAvatarFallback}
1179
+ loadingComponent={accountAvatarFallback}
1180
+ />
1181
+
1182
+ <span
1183
+ style={{
1184
+ fontSize: fontSize.xs,
1185
+ letterSpacing: "0.025em",
1186
+ }}
1187
+ >
1188
+ <AccountName
1189
+ fallbackComponent={
1190
+ <span>{shortenAddress(account.address)}</span>
1191
+ }
1192
+ loadingComponent={
1193
+ <span>{shortenAddress(account.address)}</span>
1194
+ }
1195
+ />
1196
+ </span>
1197
+ </Container>
1198
+ </WalletProvider>
1199
+ </AccountProvider>
1200
+ </WalletProvider>
1201
+ );
1202
+ }
1203
+
1204
+ const WalletButton = /* @__PURE__ */ styled(Button)(() => {
1205
+ const theme = useCustomTheme();
1206
+ return {
1207
+ color: theme.colors.secondaryText,
1208
+ transition: "color 200ms ease",
1209
+ "&:hover": {
1210
+ color: theme.colors.primaryText,
1211
+ },
1212
+ };
1213
+ });