@0xsequence/marketplace-sdk 2.0.0 → 2.0.2

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 (136) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/BellIcon.js +1 -1
  3. package/dist/Card.js +1 -1
  4. package/dist/ShopCard.d.ts +4 -4
  5. package/dist/builder-api.js +1 -1
  6. package/dist/collectible.js +2 -2
  7. package/dist/collectible.js.map +1 -1
  8. package/dist/collection.js +1 -1
  9. package/dist/create-config.d.ts +589 -193
  10. package/dist/create-config.js +1 -1
  11. package/dist/currency.js +3 -3
  12. package/dist/currency.js.map +1 -1
  13. package/dist/dist.js +167 -148
  14. package/dist/dist.js.map +1 -1
  15. package/dist/expirationDateSelect.js +1 -1
  16. package/dist/filter-state.d.ts +1 -1
  17. package/dist/filters.d.ts +1 -1
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +3 -3
  20. package/dist/index10.d.ts +1 -1
  21. package/dist/index11.d.ts +17 -17
  22. package/dist/index12.d.ts +21 -21
  23. package/dist/index14.d.ts +3 -3
  24. package/dist/index15.d.ts +3 -3
  25. package/dist/index16.d.ts +2 -2
  26. package/dist/index17.d.ts +75 -75
  27. package/dist/index18.d.ts +40 -40
  28. package/dist/index19.d.ts +5 -5
  29. package/dist/index2.d.ts +4 -1
  30. package/dist/index21.d.ts +15 -15
  31. package/dist/index22.d.ts +8 -65
  32. package/dist/index23.d.ts +21 -13
  33. package/dist/index26.d.ts +4 -4
  34. package/dist/index27.d.ts +4 -4
  35. package/dist/index28.d.ts +10 -10
  36. package/dist/index3.d.ts +2 -2194
  37. package/dist/index31.d.ts +5 -5
  38. package/dist/index33.d.ts +3 -3
  39. package/dist/index34.d.ts +1 -1
  40. package/dist/index35.d.ts +1 -1
  41. package/dist/index36.d.ts +5 -5
  42. package/dist/index37.d.ts +8 -6
  43. package/dist/index38.d.ts +5 -5
  44. package/dist/index39.d.ts +1 -1
  45. package/dist/index4.d.ts +1356 -1356
  46. package/dist/index40.d.ts +2 -2
  47. package/dist/index8.d.ts +11 -3
  48. package/dist/index9.d.ts +2811 -3
  49. package/dist/inventory.d.ts +4 -4
  50. package/dist/inventory.js +3 -3
  51. package/dist/inventory.js.map +1 -1
  52. package/dist/marketplace2.js +3 -3
  53. package/dist/marketplace2.js.map +1 -1
  54. package/dist/metadata.d.ts +41 -41
  55. package/dist/primary-sale-checkout-options.d.ts +4 -4
  56. package/dist/quantityInput.js +1 -1
  57. package/dist/ranges.d.ts +12 -12
  58. package/dist/react/_internal/index.d.ts +1 -1
  59. package/dist/react/_internal/index.js +1 -1
  60. package/dist/react/index.d.ts +1 -1
  61. package/dist/react/queries/collectible/index.d.ts +1 -1
  62. package/dist/react/queries/index.d.ts +1 -1
  63. package/dist/react/ssr/index.d.ts +3 -3
  64. package/dist/react/ssr/index.js +3 -3
  65. package/dist/react/ui/components/marketplace-collectible-card/index.d.ts +1 -1
  66. package/dist/react/ui/modals/CreateListingModal/internal/hooks/index.d.ts +1 -1
  67. package/dist/react/ui/modals/MakeOfferModal/internal/hooks/index.d.ts +1 -1
  68. package/dist/react/ui/modals/_internal/components/alertMessage/index.d.ts +2 -2
  69. package/dist/react/ui/modals/_internal/components/baseModal/index.d.ts +6 -6
  70. package/dist/react/ui/modals/_internal/components/calendar/index.d.ts +2 -2
  71. package/dist/react/ui/modals/_internal/components/currencyImage/index.d.ts +2 -2
  72. package/dist/react/ui/modals/_internal/components/currencyOptionsSelect/index.d.ts +3 -3
  73. package/dist/react/ui/modals/_internal/components/floorPriceText/index.d.ts +2 -2
  74. package/dist/react/ui/modals/_internal/components/priceInput/index.d.ts +3 -5
  75. package/dist/react/ui/modals/_internal/components/quantityInput/index.d.ts +2 -2
  76. package/dist/react/ui/modals/_internal/components/selectWaasFeeOptions/index.d.ts +2 -2
  77. package/dist/react/ui/modals/_internal/components/switchChainErrorModal/index.d.ts +2 -2
  78. package/dist/react/ui/modals/_internal/components/timeAgo/index.d.ts +2 -2
  79. package/dist/react/ui/modals/_internal/components/tokenPreview/index.d.ts +3 -3
  80. package/dist/react/ui/modals/_internal/components/transaction-footer/index.d.ts +3 -3
  81. package/dist/react/ui/modals/_internal/components/transactionDetails/index.d.ts +3 -3
  82. package/dist/react/ui/modals/_internal/components/transactionPreview/index.d.ts +3 -3
  83. package/dist/react/ui/modals/_internal/components/transactionStatusModal/index.d.ts +3 -3
  84. package/dist/react.js +2279 -1919
  85. package/dist/react.js.map +1 -1
  86. package/dist/styles/index.css +15 -0
  87. package/dist/token-balances.d.ts +28 -28
  88. package/dist/transaction-footer.js +1 -1
  89. package/dist/types/index.d.ts +1 -1
  90. package/dist/types/index.js +1 -1
  91. package/dist/types.d.ts +1 -1
  92. package/dist/url-state.js +1 -1
  93. package/dist/utils/index.d.ts +2 -2
  94. package/dist/utils/index.js +2 -2
  95. package/dist/utils.js +31 -4
  96. package/dist/utils.js.map +1 -1
  97. package/package.json +7 -5
  98. package/src/react/hooks/config/useMarketplaceConfig.test.tsx +1 -0
  99. package/src/react/hooks/currency/list.test.tsx +23 -2
  100. package/src/react/hooks/transactions/useCancelTransactionSteps.tsx +4 -1
  101. package/src/react/hooks/transactions/useMarketTransactionSteps.tsx +55 -15
  102. package/src/react/hooks/utils/useEnsureCorrectChain.ts +10 -5
  103. package/src/react/queries/collectible/market-list.ts +5 -3
  104. package/src/react/queries/currency/list.ts +8 -5
  105. package/src/react/queries/inventory/inventory.ts +5 -3
  106. package/src/react/queries/marketplace/filters.ts +5 -3
  107. package/src/react/ui/modals/BuyModal/components/BuyModalContent.tsx +74 -37
  108. package/src/react/ui/modals/BuyModal/components/CryptoPaymentModal.tsx +74 -11
  109. package/src/react/ui/modals/BuyModal/components/Modal.tsx +62 -1
  110. package/src/react/ui/modals/BuyModal/hooks/useExecuteBundledTransactions.ts +13 -26
  111. package/src/react/ui/modals/BuyModal/hooks/useMarketPlatformFee.ts +5 -5
  112. package/src/react/ui/modals/BuyModal/internal/__tests__/buildTrailsMarketBuyActions.test.ts +213 -0
  113. package/src/react/ui/modals/BuyModal/internal/buildTrailsMarketBuyActions.ts +259 -0
  114. package/src/react/ui/modals/BuyModal/internal/buyModalContext.ts +79 -10
  115. package/src/react/ui/modals/BuyModal/internal/cryptoPaymentModalContext.tsx +44 -17
  116. package/src/react/ui/modals/CreateListingModal/internal/store.ts +5 -2
  117. package/src/react/ui/modals/MakeOfferModal/internal/context.ts +21 -1
  118. package/src/react/ui/modals/MakeOfferModal/internal/helpers/validation.ts +16 -1
  119. package/src/react/ui/modals/MakeOfferModal/internal/store.ts +5 -2
  120. package/src/react/ui/modals/SellModal/internal/store.ts +5 -2
  121. package/src/react/ui/modals/_internal/components/baseModal/errors/ModalInitializationError.tsx +8 -6
  122. package/src/react/ui/modals/_internal/components/currencyOptionsSelect/index.tsx +2 -1
  123. package/src/react/ui/modals/_internal/components/priceInput/index.tsx +13 -19
  124. package/src/react/ui/modals/_internal/components/transactionDetails/index.tsx +5 -2
  125. package/src/react/ui/modals/_internal/helpers/currency.test.ts +27 -0
  126. package/src/react/ui/modals/_internal/helpers/currency.ts +4 -2
  127. package/src/styles/styles.ts +18 -0
  128. package/src/utils/__tests__/getMarketplaceDetails.test.ts +10 -0
  129. package/src/utils/__tests__/getWebRPCErrorMessage.test.ts +28 -0
  130. package/src/utils/__tests__/marketplaceNormalization.test.ts +38 -0
  131. package/src/utils/collection.ts +19 -0
  132. package/src/utils/getConduitAddressForOrderbook.ts +2 -10
  133. package/src/utils/getMarketplaceDetails.ts +11 -4
  134. package/src/utils/getWebRPCErrorMessage.ts +21 -0
  135. package/src/utils/index.ts +1 -0
  136. package/src/utils/normalizeMarketplace.ts +31 -0
@@ -26,13 +26,18 @@ export const useEnsureCorrectChain = () => {
26
26
  const ensureCorrectChainAsync = useCallback(
27
27
  async (targetChainId: number) => {
28
28
  if (currentChainId === targetChainId) {
29
- return Promise.resolve();
29
+ return true;
30
30
  }
31
- return switchChainAsync({ chainId: targetChainId }).catch(() => {
32
- showSwitchChainErrorModal({
33
- chainIdToSwitchTo: targetChainId,
31
+
32
+ return switchChainAsync({ chainId: targetChainId })
33
+ .then(() => true)
34
+ .catch(() => {
35
+ showSwitchChainErrorModal({
36
+ chainIdToSwitchTo: targetChainId,
37
+ });
38
+
39
+ return false;
34
40
  });
35
- });
36
41
  },
37
42
  [currentChainId, isWaaS, switchChainAsync, showSwitchChainErrorModal],
38
43
  );
@@ -1,7 +1,7 @@
1
1
  import { isAddress } from 'viem';
2
2
  import type { Page } from '../../../types';
3
3
  import type { CardType } from '../../../types/types';
4
- import { compareAddress } from '../../../utils';
4
+ import { findMarketCollection } from '../../../utils';
5
5
  import type {
6
6
  ListCollectiblesRequest,
7
7
  ListCollectiblesResponse,
@@ -37,8 +37,10 @@ export async function fetchListCollectibles(
37
37
  params;
38
38
  const marketplaceClient = getMarketplaceClient(config);
39
39
  const marketplaceConfig = await fetchMarketplaceConfig({ config });
40
- const isMarketCollection = marketplaceConfig?.market.collections.some(
41
- (collection) => compareAddress(collection.itemsAddress, collectionAddress),
40
+ const isMarketCollection = !!findMarketCollection(
41
+ marketplaceConfig?.market.collections ?? [],
42
+ collectionAddress,
43
+ chainId,
42
44
  );
43
45
 
44
46
  // If it's not a market collection, return an empty list. those collections are not compatible with the ListCollectibles endpoint.
@@ -4,7 +4,7 @@ import type {
4
4
  ListCurrenciesRequest,
5
5
  } from '@0xsequence/api-client';
6
6
  import { zeroAddress } from 'viem';
7
- import { compareAddress } from '../../../utils';
7
+ import { compareAddress, findMarketCollection } from '../../../utils';
8
8
  import {
9
9
  buildQueryOptions,
10
10
  getMarketplaceClient,
@@ -50,15 +50,18 @@ export async function fetchMarketCurrencies(
50
50
  marketplaceConfigOptions(config),
51
51
  );
52
52
 
53
- const currenciesOptions = marketplaceConfig.market.collections.find(
54
- (collection) =>
55
- compareAddress(collection.itemsAddress, collectionAddress),
53
+ const currenciesOptions = findMarketCollection(
54
+ marketplaceConfig.market.collections,
55
+ collectionAddress,
56
+ chainId,
56
57
  )?.currencyOptions;
57
58
 
58
59
  // Filter currencies based on collection currency options
59
60
  if (currenciesOptions) {
60
61
  currencies = currencies.filter((currency) =>
61
- currenciesOptions.includes(currency.contractAddress),
62
+ currenciesOptions.some((option) =>
63
+ compareAddress(option, currency.contractAddress),
64
+ ),
62
65
  );
63
66
  }
64
67
  }
@@ -7,7 +7,7 @@ import type {
7
7
  import { ContractType, MetadataStatus } from '@0xsequence/api-client';
8
8
  import { isAddress } from 'viem';
9
9
  import type { Page } from '../../../types';
10
- import { compareAddress } from '../../../utils';
10
+ import { findMarketCollection } from '../../../utils';
11
11
  import {
12
12
  buildQueryOptions,
13
13
  getQueryClient,
@@ -148,8 +148,10 @@ export async function fetchInventory(
148
148
 
149
149
  const marketCollections = marketplaceConfig?.market.collections || [];
150
150
 
151
- const isMarketCollection = marketCollections.some((collection) =>
152
- compareAddress(collection.itemsAddress, collectionAddress),
151
+ const isMarketCollection = !!findMarketCollection(
152
+ marketCollections,
153
+ collectionAddress,
154
+ chainId,
153
155
  );
154
156
 
155
157
  // Determine if this collection is tradable (market collection vs shop collection)
@@ -1,7 +1,7 @@
1
1
  import type { GetFiltersArgs, PropertyFilter } from '@0xsequence/api-client';
2
2
  import { isAddress } from 'viem';
3
3
  import { FilterCondition } from '../../../types';
4
- import { compareAddress } from '../../../utils';
4
+ import { findMarketCollection } from '../../../utils';
5
5
  import {
6
6
  buildQueryOptions,
7
7
  getMetadataClient,
@@ -49,8 +49,10 @@ export async function fetchFilters(
49
49
  const marketplaceConfig = await queryClient.fetchQuery(
50
50
  marketplaceConfigOptions(config),
51
51
  );
52
- const collectionFilters = marketplaceConfig.market.collections.find((c) =>
53
- compareAddress(c.itemsAddress, collectionAddress),
52
+ const collectionFilters = findMarketCollection(
53
+ marketplaceConfig.market.collections,
54
+ collectionAddress,
55
+ chainId,
54
56
  )?.filterSettings;
55
57
 
56
58
  const filterOrder = collectionFilters?.filterOrder;
@@ -10,12 +10,8 @@ import {
10
10
  Text,
11
11
  } from '@0xsequence/design-system';
12
12
  import { TrailsWidget } from '0xtrails/widget';
13
- import {
14
- getSequenceApiUrl,
15
- getSequenceIndexerUrl,
16
- getSequenceNodeGatewayUrl,
17
- getTrailsApiUrl,
18
- } from '../../../../_internal/api/services';
13
+ import { useRef, useState } from 'react';
14
+ import { cn } from '../../../../ssr';
19
15
  import { ModalInitializationError } from '../../_internal/components/baseModal/errors/ModalInitializationError';
20
16
  import { MODAL_OVERLAY_PROPS } from '../../_internal/components/consts';
21
17
  import { useBuyModalContext } from '../internal/buyModalContext';
@@ -26,7 +22,6 @@ import { TRAILS_CUSTOM_CSS } from './TrailsCss';
26
22
 
27
23
  export const BuyModalContent = () => {
28
24
  const {
29
- config,
30
25
  modalProps,
31
26
  close,
32
27
  steps,
@@ -34,10 +29,12 @@ export const BuyModalContent = () => {
34
29
  marketOrder,
35
30
  collectible,
36
31
  buyStep,
32
+ trailsDestination,
37
33
  isLoading,
38
34
  collection,
39
35
  checkoutMode,
40
36
  formattedAmount,
37
+ isMarket,
41
38
  isShop,
42
39
  handleTrailsSuccess,
43
40
  handleTransactionSuccess,
@@ -48,20 +45,15 @@ export const BuyModalContent = () => {
48
45
  ? primarySaleItem?.currencyAddress
49
46
  : marketOrder?.priceCurrencyAddress;
50
47
 
51
- const trailsApiUrl = getTrailsApiUrl(config);
52
- const sequenceIndexerUrl = getSequenceIndexerUrl(config);
53
- const sequenceNodeGatewayUrl = getSequenceNodeGatewayUrl(config);
54
- const sequenceApiUrl = getSequenceApiUrl(config);
55
-
56
48
  if (error) {
57
49
  return (
58
50
  <Modal
59
- isDismissible={false}
51
+ isDismissible={true}
60
52
  onClose={close}
61
53
  overlayProps={MODAL_OVERLAY_PROPS}
62
54
  contentProps={{
63
55
  style: {
64
- width: '450px',
56
+ width: '400px',
65
57
  height: 'auto',
66
58
  },
67
59
  className: 'overflow-y-auto',
@@ -101,10 +93,7 @@ export const BuyModalContent = () => {
101
93
 
102
94
  {isLoading && (
103
95
  <div className="flex w-full items-center justify-center py-8">
104
- <div className="flex flex-col items-center gap-4">
105
- <Spinner size="lg" />
106
- <Text className="text-text-80">Loading payment options...</Text>
107
- </div>
96
+ <ProgressiveLoadingMessage />
108
97
  </div>
109
98
  )}
110
99
 
@@ -123,6 +112,7 @@ export const BuyModalContent = () => {
123
112
  {!isLoading &&
124
113
  checkoutMode === 'trails' &&
125
114
  buyStep &&
115
+ (!isMarket || trailsDestination) &&
126
116
  !(isShop && primarySaleItem?.priceAmount === 0n) && (
127
117
  <div className="pointer-events-auto w-full">
128
118
  {collectible && (
@@ -133,25 +123,37 @@ export const BuyModalContent = () => {
133
123
  />
134
124
  )}
135
125
 
136
- <TrailsWidget
137
- apiKey={config.projectAccessKey}
138
- trailsApiUrl={trailsApiUrl}
139
- sequenceIndexerUrl={sequenceIndexerUrl}
140
- sequenceNodeGatewayUrl={sequenceNodeGatewayUrl}
141
- sequenceApiUrl={sequenceApiUrl}
142
- walletConnectProjectId={config.walletConnectProjectId}
143
- toChainId={modalProps.chainId}
144
- toAddress={buyStep.to}
145
- toToken={currencyAddress}
146
- toCalldata={buyStep.data}
147
- toAmount={formattedAmount}
148
- renderInline={true}
149
- theme="dark"
150
- mode="pay"
151
- customCss={TRAILS_CUSTOM_CSS}
152
- onDestinationConfirmation={handleTrailsSuccess}
153
- payMessage="{TO_TOKEN_IMAGE}{TO_AMOUNT}{TO_TOKEN_SYMBOL}{TO_AMOUNT_USD}"
154
- />
126
+ {isMarket && trailsDestination ? (
127
+ <TrailsWidget
128
+ key={`market-${marketOrder?.orderId}-${trailsDestination.destinationCalldata}`}
129
+ toChainId={modalProps.chainId}
130
+ toAddress={trailsDestination.recipient}
131
+ toToken={trailsDestination.paymentTokenAddress}
132
+ toAmount={formattedAmount}
133
+ toCalldata={trailsDestination.destinationCalldata}
134
+ renderInline={true}
135
+ theme="dark"
136
+ mode="pay"
137
+ customCss={TRAILS_CUSTOM_CSS}
138
+ onDestinationConfirmation={handleTrailsSuccess}
139
+ payMessage="{TO_TOKEN_IMAGE}{TO_AMOUNT}{TO_TOKEN_SYMBOL}{TO_AMOUNT_USD}"
140
+ />
141
+ ) : (
142
+ <TrailsWidget
143
+ key={`direct-${buyStep.to}-${buyStep.data}`}
144
+ toChainId={modalProps.chainId}
145
+ toAddress={buyStep.to}
146
+ toToken={currencyAddress}
147
+ toAmount={formattedAmount}
148
+ toCalldata={buyStep.data}
149
+ renderInline={true}
150
+ theme="dark"
151
+ mode="pay"
152
+ customCss={TRAILS_CUSTOM_CSS}
153
+ onDestinationConfirmation={handleTrailsSuccess}
154
+ payMessage="{TO_TOKEN_IMAGE}{TO_AMOUNT}{TO_TOKEN_SYMBOL}{TO_AMOUNT_USD}"
155
+ />
156
+ )}
155
157
  </div>
156
158
  )}
157
159
  </div>
@@ -159,3 +161,38 @@ export const BuyModalContent = () => {
159
161
  </Dialog>
160
162
  );
161
163
  };
164
+
165
+ const ProgressiveLoadingMessage = () => {
166
+ const [showSecondaryMessage, setShowSecondaryMessage] = useState(false);
167
+ const timerRef = useRef<NodeJS.Timeout | null>(null);
168
+
169
+ if (!timerRef.current) {
170
+ timerRef.current = setTimeout(() => {
171
+ setShowSecondaryMessage(true);
172
+ }, 3000);
173
+ }
174
+
175
+ return (
176
+ <div className="flex items-center gap-4">
177
+ <div
178
+ className={cn(
179
+ 'transition-all duration-300',
180
+ showSecondaryMessage ? 'h-10 w-10' : 'h-5 w-5',
181
+ )}
182
+ >
183
+ <Spinner className="h-full w-full transition-all duration-150" />
184
+ </div>
185
+
186
+ <div className="flex flex-col gap-2">
187
+ <p className="animate-pulse text-text-100">
188
+ Loading payment options...
189
+ </p>
190
+ {showSecondaryMessage && (
191
+ <p className="text-small text-text-50">
192
+ This is taking longer than expected.
193
+ </p>
194
+ )}
195
+ </div>
196
+ </div>
197
+ );
198
+ };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Button, Spinner, Text } from '@0xsequence/design-system';
4
- import { useState } from 'react';
4
+ import { useEffect, useState } from 'react';
5
5
  import type { Hex } from 'viem';
6
6
  import { getPresentableChainName } from '../../../../../utils/network';
7
7
  import type { Step } from '../../../../_internal';
@@ -39,13 +39,36 @@ export const CryptoPaymentModal = ({
39
39
  } = useCryptoPaymentModalContext({ chainId, steps, onSuccess });
40
40
 
41
41
  const { ensureCorrectChainAsync } = useEnsureCorrectChain();
42
+ const [pendingAction, setPendingAction] = useState<'approval' | 'buy' | null>(
43
+ null,
44
+ );
42
45
  const [chainSwitchError, setChainSwitchError] = useState<{
43
46
  title: string;
44
47
  message: string;
45
48
  details?: Error;
46
49
  } | null>(null);
50
+ const isPendingApprovalReady =
51
+ pendingAction === 'approval' && isOnCorrectChain && canApprove;
52
+ const isPendingBuyReady = pendingAction === 'buy' && isOnCorrectChain && canBuy;
53
+ const isPendingAction = pendingAction !== null;
54
+
55
+ useEffect(() => {
56
+ if (!isPendingApprovalReady && !isPendingBuyReady) {
57
+ return;
58
+ }
59
+
60
+ setPendingAction(null);
61
+
62
+ void (pendingAction === 'approval' ? executeApproval() : executeBuy());
63
+ }, [
64
+ executeApproval,
65
+ executeBuy,
66
+ isPendingApprovalReady,
67
+ isPendingBuyReady,
68
+ pendingAction,
69
+ ]);
47
70
 
48
- const handleChainSwitchError = (error: Error) => {
71
+ const handleChainSwitchError = (error?: Error) => {
49
72
  const chainName = getPresentableChainName(chainId);
50
73
  setChainSwitchError({
51
74
  title: 'Chain switch failed',
@@ -58,27 +81,59 @@ export const CryptoPaymentModal = ({
58
81
  setChainSwitchError(null);
59
82
  };
60
83
 
84
+ const executeAction = async (action: 'approval' | 'buy') => {
85
+ setPendingAction(null);
86
+
87
+ if (action === 'approval') {
88
+ await executeApproval();
89
+ return;
90
+ }
91
+
92
+ await executeBuy();
93
+ };
94
+
61
95
  const executeWithChainSwitch = async (action: 'approval' | 'buy') => {
62
96
  dismissChainSwitchError();
97
+
98
+ if (isOnCorrectChain) {
99
+ await executeAction(action);
100
+ return;
101
+ }
102
+
103
+ setPendingAction(action);
104
+
63
105
  try {
64
- await ensureCorrectChainAsync(chainId);
106
+ const didRequestChainSwitch = await ensureCorrectChainAsync(chainId);
107
+ if (!didRequestChainSwitch) {
108
+ setPendingAction(null);
109
+ handleChainSwitchError();
110
+ }
65
111
  } catch (error) {
112
+ setPendingAction(null);
113
+
66
114
  if (error instanceof Error) {
67
115
  handleChainSwitchError(error);
116
+ } else {
117
+ handleChainSwitchError();
68
118
  }
69
119
  }
70
-
71
- if (action === 'approval') {
72
- await executeApproval();
73
- } else {
74
- await executeBuy();
75
- }
76
120
  };
77
121
 
122
+ const isWaitingForChainSwitch = isPendingAction && !isOnCorrectChain;
123
+ const isPreparingPendingAction = isPendingAction && isOnCorrectChain;
124
+
78
125
  const approvalButtonLabel = isApproving ? (
79
126
  <div className="flex items-center gap-2">
80
127
  <Spinner size="sm" /> Approving Token...
81
128
  </div>
129
+ ) : isWaitingForChainSwitch && pendingAction === 'approval' ? (
130
+ <div className="flex items-center gap-2">
131
+ <Spinner size="sm" /> Switching Network...
132
+ </div>
133
+ ) : isPreparingPendingAction && pendingAction === 'approval' ? (
134
+ <div className="flex items-center gap-2">
135
+ <Spinner size="sm" /> Preparing Approval...
136
+ </div>
82
137
  ) : (
83
138
  'Approve Token'
84
139
  );
@@ -87,6 +142,14 @@ export const CryptoPaymentModal = ({
87
142
  <div className="flex items-center gap-2">
88
143
  <Spinner size="sm" /> Confirming Purchase...
89
144
  </div>
145
+ ) : isWaitingForChainSwitch && pendingAction === 'buy' ? (
146
+ <div className="flex items-center gap-2">
147
+ <Spinner size="sm" /> Switching Network...
148
+ </div>
149
+ ) : isPreparingPendingAction && pendingAction === 'buy' ? (
150
+ <div className="flex items-center gap-2">
151
+ <Spinner size="sm" /> Preparing Purchase...
152
+ </div>
90
153
  ) : (
91
154
  'Buy now'
92
155
  );
@@ -129,7 +192,7 @@ export const CryptoPaymentModal = ({
129
192
  onClick={async () => {
130
193
  await executeWithChainSwitch('approval');
131
194
  }}
132
- disabled={!canApprove}
195
+ disabled={!canApprove || isPendingAction}
133
196
  variant="primary"
134
197
  size="lg"
135
198
  className="w-full"
@@ -143,7 +206,7 @@ export const CryptoPaymentModal = ({
143
206
  onClick={async () => {
144
207
  await executeWithChainSwitch('buy');
145
208
  }}
146
- disabled={!canBuy}
209
+ disabled={!canBuy || isPendingAction}
147
210
  variant="primary"
148
211
  size="lg"
149
212
  className="w-full"
@@ -1,8 +1,26 @@
1
1
  'use client';
2
2
 
3
+ import {
4
+ type WagmiAdapterOptions,
5
+ wagmiAdapter,
6
+ } from '@0xtrails/adapter-wagmi';
7
+ import { TrailsProvider } from '0xtrails/widget';
8
+ import { useConfig as useWagmiConfig } from 'wagmi';
9
+ import {
10
+ getSequenceApiUrl,
11
+ getSequenceIndexerUrl,
12
+ getSequenceNodeGatewayUrl,
13
+ getTrailsApiUrl,
14
+ } from '../../../../_internal/api/services';
15
+ import { useConfig } from '../../../../hooks';
16
+ import { useWaasFeeOptions } from '../../../../hooks/utils/useWaasFeeOptions';
3
17
  import { useIsOpen } from '../store';
4
18
  import { BuyModalContent } from './BuyModalContent';
5
19
 
20
+ type TrailsUseWaasFeeOptions = NonNullable<
21
+ NonNullable<WagmiAdapterOptions['sequence']>['useWaasFeeOptions']
22
+ >;
23
+
6
24
  export const BuyModal = () => {
7
25
  const isOpen = useIsOpen();
8
26
 
@@ -10,5 +28,48 @@ export const BuyModal = () => {
10
28
  return null;
11
29
  }
12
30
 
13
- return <BuyModalContent />;
31
+ return <BuyModalWithTrailsProvider />;
32
+ };
33
+
34
+ const useTrailsWaasFeeOptions: TrailsUseWaasFeeOptions = ({
35
+ chainIdOverride,
36
+ } = {}) => {
37
+ const config = useConfig();
38
+ const {
39
+ pendingFeeOptionConfirmation,
40
+ confirmPendingFeeOption,
41
+ rejectPendingFeeOption,
42
+ } = useWaasFeeOptions(chainIdOverride ?? 0, config);
43
+
44
+ return [
45
+ pendingFeeOptionConfirmation,
46
+ confirmPendingFeeOption,
47
+ rejectPendingFeeOption,
48
+ ];
49
+ };
50
+
51
+ const BuyModalWithTrailsProvider = () => {
52
+ const config = useConfig();
53
+ const wagmiConfig = useWagmiConfig();
54
+
55
+ return (
56
+ <TrailsProvider
57
+ config={{
58
+ trailsApiKey: config.projectAccessKey,
59
+ trailsApiUrl: getTrailsApiUrl(config),
60
+ sequenceIndexerUrl: getSequenceIndexerUrl(config),
61
+ sequenceNodeGatewayUrl: getSequenceNodeGatewayUrl(config),
62
+ sequenceApiUrl: getSequenceApiUrl(config),
63
+ walletConnectProjectId: config.walletConnectProjectId,
64
+ adapters: [
65
+ wagmiAdapter({
66
+ wagmiConfig,
67
+ sequence: { useWaasFeeOptions: useTrailsWaasFeeOptions },
68
+ }),
69
+ ],
70
+ }}
71
+ >
72
+ <BuyModalContent />
73
+ </TrailsProvider>
74
+ );
14
75
  };
@@ -5,7 +5,6 @@ import type { Hex } from 'viem';
5
5
  import { useAccount, usePublicClient, useWalletClient } from 'wagmi';
6
6
  import { useConfig } from '../../../..';
7
7
  import { getIndexerClient, type Step } from '../../../../_internal';
8
- import { useBuyModalData } from './useBuyModalData';
9
8
 
10
9
  // https://github.com/0xsequence/web-sdk/blob/620b6fe7681ae49efd4eb3fa7607ef01dd7ede54/packages/connect/src/utils/transactions.ts#L11-L19
11
10
  class FeeOptionInsufficientFundsError extends Error {
@@ -21,31 +20,25 @@ class FeeOptionInsufficientFundsError extends Error {
21
20
  type UseExecuteBundledTransactions = {
22
21
  chainId: number;
23
22
  approvalStep?: Step;
24
- priceAmount: bigint;
25
23
  };
26
24
 
27
25
  const useExecuteBundledTransactions = ({
28
26
  chainId,
29
27
  approvalStep,
30
- priceAmount,
31
28
  }: UseExecuteBundledTransactions) => {
32
29
  const config = useConfig();
33
30
  const [isExecuting, setIsExecuting] = useState(false);
34
31
  const { address, connector } = useAccount();
35
- const publicClient = usePublicClient();
32
+ const publicClient = usePublicClient({ chainId });
36
33
  const { data: walletClient } = useWalletClient();
37
34
  const indexerClient = getIndexerClient(chainId, config);
38
35
 
39
- const { collection, currency } = useBuyModalData();
40
-
41
36
  const isReady =
42
37
  !!address &&
43
38
  !!publicClient &&
44
39
  !!walletClient &&
45
40
  !!indexerClient &&
46
- !!connector &&
47
- !!collection?.address &&
48
- priceAmount != null;
41
+ !!connector;
49
42
 
50
43
  const executeBundledTransactions = async ({
51
44
  step,
@@ -79,28 +72,22 @@ const useExecuteBundledTransactions = ({
79
72
  throw new Error('Connector not found');
80
73
  }
81
74
 
82
- if (!collection?.address) {
83
- throw new Error('Collection address not found');
84
- }
75
+ const buildTransaction = (step: Step) => {
76
+ const value = step.value ? BigInt(step.value) : 0n;
85
77
 
86
- if (priceAmount == null) {
87
- throw new Error('Price amount not found');
88
- }
78
+ return {
79
+ to: step.to,
80
+ data: step.data as Hex,
81
+ chainId,
82
+ ...(value > 0n ? { value } : {}),
83
+ };
84
+ };
89
85
 
90
86
  const approvalData = approvalStep
91
- ? {
92
- to: approvalStep.to,
93
- data: approvalStep.data as Hex,
94
- chainId,
95
- }
87
+ ? buildTransaction(approvalStep)
96
88
  : undefined;
97
89
 
98
- const transactionData = {
99
- to: step.to,
100
- data: step.data as Hex,
101
- chainId,
102
- ...(currency?.nativeCurrency ? { value: priceAmount } : {}),
103
- };
90
+ const transactionData = buildTransaction(step);
104
91
 
105
92
  const transactions = [
106
93
  ...(approvalData ? [approvalData] : []),
@@ -1,6 +1,6 @@
1
1
  import { skipToken } from '@tanstack/react-query';
2
2
  import { avalanche, optimism } from 'viem/chains';
3
- import { compareAddress } from '../../../../../utils';
3
+ import { findMarketCollection } from '../../../../../utils';
4
4
  import type { AdditionalFee } from '../../../../_internal';
5
5
  import { useMarketplaceConfig } from '../../../../hooks';
6
6
 
@@ -28,10 +28,10 @@ export const useMarketPlatformFee = (params: FeesParams | typeof skipToken) => {
28
28
  const { chainId, collectionAddress } = params;
29
29
 
30
30
  // Find collection in market collections only (not shop collections)
31
- const marketCollection = marketplaceConfig?.market?.collections?.find(
32
- (col) =>
33
- compareAddress(col.itemsAddress, collectionAddress) &&
34
- String(col.chainId) === String(chainId),
31
+ const marketCollection = findMarketCollection(
32
+ marketplaceConfig?.market?.collections ?? [],
33
+ collectionAddress,
34
+ chainId,
35
35
  );
36
36
 
37
37
  const avalancheOrOptimism =