@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.
- package/CHANGELOG.md +18 -0
- package/dist/BellIcon.js +1 -1
- package/dist/Card.js +1 -1
- package/dist/ShopCard.d.ts +4 -4
- package/dist/builder-api.js +1 -1
- package/dist/collectible.js +2 -2
- package/dist/collectible.js.map +1 -1
- package/dist/collection.js +1 -1
- package/dist/create-config.d.ts +589 -193
- package/dist/create-config.js +1 -1
- package/dist/currency.js +3 -3
- package/dist/currency.js.map +1 -1
- package/dist/dist.js +167 -148
- package/dist/dist.js.map +1 -1
- package/dist/expirationDateSelect.js +1 -1
- package/dist/filter-state.d.ts +1 -1
- package/dist/filters.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index10.d.ts +1 -1
- package/dist/index11.d.ts +17 -17
- package/dist/index12.d.ts +21 -21
- package/dist/index14.d.ts +3 -3
- package/dist/index15.d.ts +3 -3
- package/dist/index16.d.ts +2 -2
- package/dist/index17.d.ts +75 -75
- package/dist/index18.d.ts +40 -40
- package/dist/index19.d.ts +5 -5
- package/dist/index2.d.ts +4 -1
- package/dist/index21.d.ts +15 -15
- package/dist/index22.d.ts +8 -65
- package/dist/index23.d.ts +21 -13
- package/dist/index26.d.ts +4 -4
- package/dist/index27.d.ts +4 -4
- package/dist/index28.d.ts +10 -10
- package/dist/index3.d.ts +2 -2194
- package/dist/index31.d.ts +5 -5
- package/dist/index33.d.ts +3 -3
- package/dist/index34.d.ts +1 -1
- package/dist/index35.d.ts +1 -1
- package/dist/index36.d.ts +5 -5
- package/dist/index37.d.ts +8 -6
- package/dist/index38.d.ts +5 -5
- package/dist/index39.d.ts +1 -1
- package/dist/index4.d.ts +1356 -1356
- package/dist/index40.d.ts +2 -2
- package/dist/index8.d.ts +11 -3
- package/dist/index9.d.ts +2811 -3
- package/dist/inventory.d.ts +4 -4
- package/dist/inventory.js +3 -3
- package/dist/inventory.js.map +1 -1
- package/dist/marketplace2.js +3 -3
- package/dist/marketplace2.js.map +1 -1
- package/dist/metadata.d.ts +41 -41
- package/dist/primary-sale-checkout-options.d.ts +4 -4
- package/dist/quantityInput.js +1 -1
- package/dist/ranges.d.ts +12 -12
- package/dist/react/_internal/index.d.ts +1 -1
- package/dist/react/_internal/index.js +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/queries/collectible/index.d.ts +1 -1
- package/dist/react/queries/index.d.ts +1 -1
- package/dist/react/ssr/index.d.ts +3 -3
- package/dist/react/ssr/index.js +3 -3
- package/dist/react/ui/components/marketplace-collectible-card/index.d.ts +1 -1
- package/dist/react/ui/modals/CreateListingModal/internal/hooks/index.d.ts +1 -1
- package/dist/react/ui/modals/MakeOfferModal/internal/hooks/index.d.ts +1 -1
- package/dist/react/ui/modals/_internal/components/alertMessage/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/baseModal/index.d.ts +6 -6
- package/dist/react/ui/modals/_internal/components/calendar/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/currencyImage/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/currencyOptionsSelect/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/floorPriceText/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/priceInput/index.d.ts +3 -5
- package/dist/react/ui/modals/_internal/components/quantityInput/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/selectWaasFeeOptions/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/switchChainErrorModal/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/timeAgo/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/tokenPreview/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/transaction-footer/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/transactionDetails/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/transactionPreview/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/transactionStatusModal/index.d.ts +3 -3
- package/dist/react.js +2279 -1919
- package/dist/react.js.map +1 -1
- package/dist/styles/index.css +15 -0
- package/dist/token-balances.d.ts +28 -28
- package/dist/transaction-footer.js +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/url-state.js +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +2 -2
- package/dist/utils.js +31 -4
- package/dist/utils.js.map +1 -1
- package/package.json +7 -5
- package/src/react/hooks/config/useMarketplaceConfig.test.tsx +1 -0
- package/src/react/hooks/currency/list.test.tsx +23 -2
- package/src/react/hooks/transactions/useCancelTransactionSteps.tsx +4 -1
- package/src/react/hooks/transactions/useMarketTransactionSteps.tsx +55 -15
- package/src/react/hooks/utils/useEnsureCorrectChain.ts +10 -5
- package/src/react/queries/collectible/market-list.ts +5 -3
- package/src/react/queries/currency/list.ts +8 -5
- package/src/react/queries/inventory/inventory.ts +5 -3
- package/src/react/queries/marketplace/filters.ts +5 -3
- package/src/react/ui/modals/BuyModal/components/BuyModalContent.tsx +74 -37
- package/src/react/ui/modals/BuyModal/components/CryptoPaymentModal.tsx +74 -11
- package/src/react/ui/modals/BuyModal/components/Modal.tsx +62 -1
- package/src/react/ui/modals/BuyModal/hooks/useExecuteBundledTransactions.ts +13 -26
- package/src/react/ui/modals/BuyModal/hooks/useMarketPlatformFee.ts +5 -5
- package/src/react/ui/modals/BuyModal/internal/__tests__/buildTrailsMarketBuyActions.test.ts +213 -0
- package/src/react/ui/modals/BuyModal/internal/buildTrailsMarketBuyActions.ts +259 -0
- package/src/react/ui/modals/BuyModal/internal/buyModalContext.ts +79 -10
- package/src/react/ui/modals/BuyModal/internal/cryptoPaymentModalContext.tsx +44 -17
- package/src/react/ui/modals/CreateListingModal/internal/store.ts +5 -2
- package/src/react/ui/modals/MakeOfferModal/internal/context.ts +21 -1
- package/src/react/ui/modals/MakeOfferModal/internal/helpers/validation.ts +16 -1
- package/src/react/ui/modals/MakeOfferModal/internal/store.ts +5 -2
- package/src/react/ui/modals/SellModal/internal/store.ts +5 -2
- package/src/react/ui/modals/_internal/components/baseModal/errors/ModalInitializationError.tsx +8 -6
- package/src/react/ui/modals/_internal/components/currencyOptionsSelect/index.tsx +2 -1
- package/src/react/ui/modals/_internal/components/priceInput/index.tsx +13 -19
- package/src/react/ui/modals/_internal/components/transactionDetails/index.tsx +5 -2
- package/src/react/ui/modals/_internal/helpers/currency.test.ts +27 -0
- package/src/react/ui/modals/_internal/helpers/currency.ts +4 -2
- package/src/styles/styles.ts +18 -0
- package/src/utils/__tests__/getMarketplaceDetails.test.ts +10 -0
- package/src/utils/__tests__/getWebRPCErrorMessage.test.ts +28 -0
- package/src/utils/__tests__/marketplaceNormalization.test.ts +38 -0
- package/src/utils/collection.ts +19 -0
- package/src/utils/getConduitAddressForOrderbook.ts +2 -10
- package/src/utils/getMarketplaceDetails.ts +11 -4
- package/src/utils/getWebRPCErrorMessage.ts +21 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/normalizeMarketplace.ts +31 -0
|
@@ -2,8 +2,9 @@ import type { Address, Hash } from '@0xsequence/api-client';
|
|
|
2
2
|
import { ChevronLeftIcon, Text } from '@0xsequence/design-system';
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
4
|
import { useState } from 'react';
|
|
5
|
-
import type
|
|
5
|
+
import { zeroAddress, type Hex } from 'viem';
|
|
6
6
|
import { useSendTransaction } from 'wagmi';
|
|
7
|
+
import { getErrorMessage } from '../../../../../utils/getErrorMessage';
|
|
7
8
|
import { formatPrice } from '../../../../../utils/price';
|
|
8
9
|
import { type Step, StepType } from '../../../../_internal';
|
|
9
10
|
import { useConnectorMetadata } from '../../../../hooks';
|
|
@@ -74,6 +75,31 @@ type CryptoPaymentModalReturn = {
|
|
|
74
75
|
};
|
|
75
76
|
};
|
|
76
77
|
|
|
78
|
+
const toErrorWithDetails = (error: unknown): Error => {
|
|
79
|
+
if (error instanceof Error) {
|
|
80
|
+
return error;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof error === 'object' && error !== null) {
|
|
84
|
+
const errorLike = error as Record<string, unknown>;
|
|
85
|
+
let message = 'An unexpected error occurred.';
|
|
86
|
+
|
|
87
|
+
if (typeof errorLike.message === 'string') {
|
|
88
|
+
message = errorLike.message;
|
|
89
|
+
} else {
|
|
90
|
+
try {
|
|
91
|
+
message = JSON.stringify(errorLike);
|
|
92
|
+
} catch {
|
|
93
|
+
message = 'An unexpected error occurred.';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Object.assign(new Error(message), errorLike);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new Error(String(error));
|
|
101
|
+
};
|
|
102
|
+
|
|
77
103
|
export function useCryptoPaymentModalContext({
|
|
78
104
|
chainId,
|
|
79
105
|
steps,
|
|
@@ -101,7 +127,6 @@ export function useCryptoPaymentModalContext({
|
|
|
101
127
|
marketOrder,
|
|
102
128
|
primarySaleItem,
|
|
103
129
|
isMarket,
|
|
104
|
-
isShop,
|
|
105
130
|
collection,
|
|
106
131
|
isLoading: isLoadingBuyModalData,
|
|
107
132
|
} = useBuyModalData();
|
|
@@ -115,11 +140,16 @@ export function useCryptoPaymentModalContext({
|
|
|
115
140
|
const priceCurrencyAddress = isMarket
|
|
116
141
|
? marketOrder?.priceCurrencyAddress
|
|
117
142
|
: (primarySaleItem?.currencyAddress as Address);
|
|
143
|
+
const listedPriceAmount = BigInt(priceAmount || 0);
|
|
144
|
+
const buyStepPriceAmount = buyStep.price ? BigInt(buyStep.price) : listedPriceAmount;
|
|
145
|
+
const buyStepValue = buyStep.value ? BigInt(buyStep.value) : buyStepPriceAmount;
|
|
146
|
+
const requiredBalance =
|
|
147
|
+
priceCurrencyAddress === zeroAddress ? buyStepValue : buyStepPriceAmount;
|
|
118
148
|
const isAnyTransactionPending = isApproving || isExecuting;
|
|
119
149
|
|
|
120
150
|
const { data, isLoading: isLoadingBalance } = useHasSufficientBalance({
|
|
121
151
|
chainId,
|
|
122
|
-
value:
|
|
152
|
+
value: requiredBalance,
|
|
123
153
|
tokenAddress: priceCurrencyAddress as Address,
|
|
124
154
|
});
|
|
125
155
|
|
|
@@ -137,7 +167,6 @@ export function useCryptoPaymentModalContext({
|
|
|
137
167
|
} = useExecuteBundledTransactions({
|
|
138
168
|
chainId,
|
|
139
169
|
approvalStep,
|
|
140
|
-
priceAmount: BigInt(priceAmount || 0),
|
|
141
170
|
});
|
|
142
171
|
|
|
143
172
|
const waas = useSelectWaasFeeOptionsStore();
|
|
@@ -169,7 +198,10 @@ export function useCryptoPaymentModalContext({
|
|
|
169
198
|
throw errorDetails;
|
|
170
199
|
}
|
|
171
200
|
|
|
172
|
-
await ensureCorrectChainAsync(chainId);
|
|
201
|
+
const isOnCorrectChain = await ensureCorrectChainAsync(chainId);
|
|
202
|
+
if (!isOnCorrectChain) {
|
|
203
|
+
throw new Error('Failed to switch to the required network.');
|
|
204
|
+
}
|
|
173
205
|
|
|
174
206
|
const hash = await sendTransactionAsync({
|
|
175
207
|
to,
|
|
@@ -195,12 +227,10 @@ export function useCryptoPaymentModalContext({
|
|
|
195
227
|
await executeTransaction(approvalStep);
|
|
196
228
|
setApprovalStep(undefined);
|
|
197
229
|
} catch (error) {
|
|
198
|
-
const errorObj =
|
|
199
|
-
error instanceof Error ? error : new Error(String(error));
|
|
230
|
+
const errorObj = toErrorWithDetails(error);
|
|
200
231
|
setError({
|
|
201
232
|
title: 'Approval failed',
|
|
202
|
-
message:
|
|
203
|
-
errorObj.message || 'Failed to approve token. Please try again.',
|
|
233
|
+
message: getErrorMessage(errorObj),
|
|
204
234
|
details: errorObj,
|
|
205
235
|
});
|
|
206
236
|
console.error('Approval transaction failed:', error);
|
|
@@ -222,7 +252,7 @@ export function useCryptoPaymentModalContext({
|
|
|
222
252
|
const handleTransactionFailed = (error: Error) => {
|
|
223
253
|
setError({
|
|
224
254
|
title: 'Transaction failed',
|
|
225
|
-
message: error
|
|
255
|
+
message: getErrorMessage(error),
|
|
226
256
|
details: error,
|
|
227
257
|
});
|
|
228
258
|
|
|
@@ -242,12 +272,10 @@ export function useCryptoPaymentModalContext({
|
|
|
242
272
|
|
|
243
273
|
onSuccess(hash as Hash);
|
|
244
274
|
} catch (error) {
|
|
245
|
-
const errorObj =
|
|
246
|
-
error instanceof Error ? error : new Error(String(error));
|
|
275
|
+
const errorObj = toErrorWithDetails(error);
|
|
247
276
|
setError({
|
|
248
277
|
title: 'Purchase failed',
|
|
249
|
-
message:
|
|
250
|
-
errorObj.message || 'Failed to complete purchase. Please try again.',
|
|
278
|
+
message: getErrorMessage(errorObj),
|
|
251
279
|
details: errorObj,
|
|
252
280
|
});
|
|
253
281
|
console.error('Buy transaction failed:', error);
|
|
@@ -261,7 +289,7 @@ export function useCryptoPaymentModalContext({
|
|
|
261
289
|
};
|
|
262
290
|
|
|
263
291
|
const formattedPrice = formatPrice(
|
|
264
|
-
|
|
292
|
+
buyStepPriceAmount,
|
|
265
293
|
currency?.decimals || 0,
|
|
266
294
|
);
|
|
267
295
|
const isFree = formattedPrice === '0';
|
|
@@ -330,7 +358,6 @@ export function useCryptoPaymentModalContext({
|
|
|
330
358
|
!isAnyTransactionPending &&
|
|
331
359
|
!isFeeSelectionVisible;
|
|
332
360
|
|
|
333
|
-
const needsBundledTransactions = isShop && !!approvalStep;
|
|
334
361
|
const canBuy =
|
|
335
362
|
hasSufficientBalance &&
|
|
336
363
|
!isLoadingBalance &&
|
|
@@ -338,7 +365,7 @@ export function useCryptoPaymentModalContext({
|
|
|
338
365
|
(isSequenceConnector ? true : !approvalStep) &&
|
|
339
366
|
!isAnyTransactionPending &&
|
|
340
367
|
!isFeeSelectionVisible &&
|
|
341
|
-
|
|
368
|
+
isBundledTransactionsReady;
|
|
342
369
|
|
|
343
370
|
const result: CryptoPaymentModalReturn = {
|
|
344
371
|
data: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Address } from '@0xsequence/api-client';
|
|
2
2
|
import { createStore } from '@xstate/store';
|
|
3
3
|
import { useSelector } from '@xstate/store/react';
|
|
4
|
+
import { findMarketCollection } from '../../../../../utils';
|
|
4
5
|
import { useMarketplaceConfig } from '../../../hooks';
|
|
5
6
|
|
|
6
7
|
export type OpenCreateListingModalArgs = {
|
|
@@ -109,8 +110,10 @@ export const useCreateListingModalState = () => {
|
|
|
109
110
|
} = useSelector(createListingModalStore, (state) => state.context);
|
|
110
111
|
|
|
111
112
|
const { data: marketplaceConfig } = useMarketplaceConfig();
|
|
112
|
-
const orderbookKind =
|
|
113
|
-
|
|
113
|
+
const orderbookKind = findMarketCollection(
|
|
114
|
+
marketplaceConfig?.market.collections ?? [],
|
|
115
|
+
collectionAddress,
|
|
116
|
+
chainId,
|
|
114
117
|
)?.destinationMarketplace;
|
|
115
118
|
|
|
116
119
|
const closeModal = () => createListingModalStore.send({ type: 'close' });
|
|
@@ -5,12 +5,14 @@ import { zeroAddress } from 'viem';
|
|
|
5
5
|
import { useAccount } from 'wagmi';
|
|
6
6
|
import { dateToUnixTime } from '../../../../../utils/date';
|
|
7
7
|
import { getConduitAddressForOrderbook } from '../../../../../utils/getConduitAddressForOrderbook';
|
|
8
|
+
import { isOpenSeaOrderbook } from '../../../../../utils/normalizeMarketplace';
|
|
8
9
|
import {
|
|
9
10
|
useCollectibleMarketLowestListing,
|
|
10
11
|
useCollectibleMetadata,
|
|
11
12
|
useCollectionMetadata,
|
|
12
13
|
useConfig,
|
|
13
14
|
useConnectorMetadata,
|
|
15
|
+
useCurrencyConvertToUSD,
|
|
14
16
|
useCurrencyList,
|
|
15
17
|
useTokenCurrencyBalance,
|
|
16
18
|
} from '../../../../hooks';
|
|
@@ -106,6 +108,18 @@ export function useMakeOfferModalContext() {
|
|
|
106
108
|
},
|
|
107
109
|
});
|
|
108
110
|
|
|
111
|
+
const { data: usdConversion } = useCurrencyConvertToUSD({
|
|
112
|
+
chainId: state.chainId,
|
|
113
|
+
currencyAddress: selectedCurrency?.contractAddress ?? zeroAddress,
|
|
114
|
+
amountRaw: state.priceInput?.toString(),
|
|
115
|
+
query: {
|
|
116
|
+
enabled:
|
|
117
|
+
isOpenSeaOrderbook(state.orderbookKind) &&
|
|
118
|
+
!!selectedCurrency?.contractAddress &&
|
|
119
|
+
!!state.priceInput,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
109
123
|
const expiryDate = useMemo(
|
|
110
124
|
() => new Date(Date.now() + state.expiryDays * 24 * 60 * 60 * 1000),
|
|
111
125
|
[state.expiryDays],
|
|
@@ -139,6 +153,7 @@ export function useMakeOfferModalContext() {
|
|
|
139
153
|
balance: balanceDnum,
|
|
140
154
|
lowestListing: lowestListingDnum,
|
|
141
155
|
orderbookKind: state.orderbookKind,
|
|
156
|
+
usdAmount: usdConversion?.usdAmount,
|
|
142
157
|
});
|
|
143
158
|
|
|
144
159
|
const formIsValid = isFormValid(validation);
|
|
@@ -359,6 +374,7 @@ export function useMakeOfferModalContext() {
|
|
|
359
374
|
quantity: validation.quantity,
|
|
360
375
|
balance: validation.balance,
|
|
361
376
|
openseaCriteria: validation.openseaCriteria,
|
|
377
|
+
openseaMinPrice: validation.openseaMinPrice,
|
|
362
378
|
},
|
|
363
379
|
errors: {
|
|
364
380
|
price: state.isPriceTouched ? validation.price.error : undefined,
|
|
@@ -369,6 +385,9 @@ export function useMakeOfferModalContext() {
|
|
|
369
385
|
openseaCriteria: state.isPriceTouched
|
|
370
386
|
? validation.openseaCriteria?.error
|
|
371
387
|
: undefined,
|
|
388
|
+
openseaMinPrice: state.isPriceTouched
|
|
389
|
+
? validation.openseaMinPrice?.error
|
|
390
|
+
: undefined,
|
|
372
391
|
},
|
|
373
392
|
},
|
|
374
393
|
|
|
@@ -415,7 +434,8 @@ export function useMakeOfferModalContext() {
|
|
|
415
434
|
this.form.errors.price ||
|
|
416
435
|
this.form.errors.quantity ||
|
|
417
436
|
this.form.errors.balance ||
|
|
418
|
-
this.form.errors.openseaCriteria
|
|
437
|
+
this.form.errors.openseaCriteria ||
|
|
438
|
+
this.form.errors.openseaMinPrice
|
|
419
439
|
);
|
|
420
440
|
},
|
|
421
441
|
|
|
@@ -11,6 +11,7 @@ export type OfferValidation = {
|
|
|
11
11
|
quantity: FieldValidation;
|
|
12
12
|
balance: FieldValidation;
|
|
13
13
|
openseaCriteria?: FieldValidation;
|
|
14
|
+
openseaMinPrice?: FieldValidation;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export function validateOfferForm({
|
|
@@ -19,12 +20,14 @@ export function validateOfferForm({
|
|
|
19
20
|
balance,
|
|
20
21
|
lowestListing,
|
|
21
22
|
orderbookKind,
|
|
23
|
+
usdAmount,
|
|
22
24
|
}: {
|
|
23
25
|
price: Dnum;
|
|
24
26
|
quantity: Dnum;
|
|
25
27
|
balance?: Dnum;
|
|
26
28
|
lowestListing?: Dnum;
|
|
27
29
|
orderbookKind?: string;
|
|
30
|
+
usdAmount?: number;
|
|
28
31
|
}): OfferValidation {
|
|
29
32
|
const validation: OfferValidation = {
|
|
30
33
|
price: { isValid: true, error: null },
|
|
@@ -67,6 +70,15 @@ export function validateOfferForm({
|
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
// OpenSea minimum price validation ($0.01 USD)
|
|
74
|
+
if (orderbookKind === 'opensea' && usdAmount !== undefined) {
|
|
75
|
+
const meetsMinPrice = usdAmount >= 0.01;
|
|
76
|
+
validation.openseaMinPrice = {
|
|
77
|
+
isValid: meetsMinPrice,
|
|
78
|
+
error: meetsMinPrice ? null : 'Lowest price must be at least $0.01',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
return validation;
|
|
71
83
|
}
|
|
72
84
|
|
|
@@ -75,7 +87,8 @@ export function isFormValid(validation: OfferValidation): boolean {
|
|
|
75
87
|
validation.price.isValid &&
|
|
76
88
|
validation.quantity.isValid &&
|
|
77
89
|
validation.balance.isValid &&
|
|
78
|
-
(validation.openseaCriteria?.isValid ?? true)
|
|
90
|
+
(validation.openseaCriteria?.isValid ?? true) &&
|
|
91
|
+
(validation.openseaMinPrice?.isValid ?? true)
|
|
79
92
|
);
|
|
80
93
|
}
|
|
81
94
|
|
|
@@ -87,6 +100,8 @@ export function getValidationErrors(validation: OfferValidation): string[] {
|
|
|
87
100
|
if (validation.balance.error) errors.push(validation.balance.error);
|
|
88
101
|
if (validation.openseaCriteria?.error)
|
|
89
102
|
errors.push(validation.openseaCriteria.error);
|
|
103
|
+
if (validation.openseaMinPrice?.error)
|
|
104
|
+
errors.push(validation.openseaMinPrice.error);
|
|
90
105
|
|
|
91
106
|
return errors;
|
|
92
107
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Address } from '@0xsequence/api-client';
|
|
2
2
|
import { createStore } from '@xstate/store';
|
|
3
3
|
import { useSelector } from '@xstate/store/react';
|
|
4
|
+
import { findMarketCollection } from '../../../../../utils';
|
|
4
5
|
import { useMarketplaceConfig } from '../../../hooks';
|
|
5
6
|
|
|
6
7
|
export type OpenMakeOfferModalArgs = {
|
|
@@ -109,8 +110,10 @@ export const useMakeOfferModalState = () => {
|
|
|
109
110
|
} = useSelector(makeOfferModalStore, (state) => state.context);
|
|
110
111
|
|
|
111
112
|
const { data: marketplaceConfig } = useMarketplaceConfig();
|
|
112
|
-
const orderbookKind =
|
|
113
|
-
|
|
113
|
+
const orderbookKind = findMarketCollection(
|
|
114
|
+
marketplaceConfig?.market.collections ?? [],
|
|
115
|
+
collectionAddress,
|
|
116
|
+
chainId,
|
|
114
117
|
)?.destinationMarketplace;
|
|
115
118
|
|
|
116
119
|
const closeModal = () => makeOfferModalStore.send({ type: 'close' });
|
|
@@ -2,6 +2,7 @@ import type { Address } from '@0xsequence/api-client';
|
|
|
2
2
|
import { createStore } from '@xstate/store';
|
|
3
3
|
import { useSelector } from '@xstate/store/react';
|
|
4
4
|
import { zeroAddress } from 'viem';
|
|
5
|
+
import { findMarketCollection } from '../../../../../utils';
|
|
5
6
|
import type { Order } from '../../../../_internal';
|
|
6
7
|
import { useMarketplaceConfig } from '../../../hooks';
|
|
7
8
|
|
|
@@ -56,8 +57,10 @@ export const useSellModalState = () => {
|
|
|
56
57
|
);
|
|
57
58
|
|
|
58
59
|
const { data: marketplaceConfig } = useMarketplaceConfig();
|
|
59
|
-
const orderbookKind =
|
|
60
|
-
|
|
60
|
+
const orderbookKind = findMarketCollection(
|
|
61
|
+
marketplaceConfig?.market.collections ?? [],
|
|
62
|
+
collectionAddress,
|
|
63
|
+
chainId,
|
|
61
64
|
)?.destinationMarketplace;
|
|
62
65
|
|
|
63
66
|
const closeModal = () => sellModalStore.send({ type: 'close' });
|
package/src/react/ui/modals/_internal/components/baseModal/errors/ModalInitializationError.tsx
CHANGED
|
@@ -17,7 +17,7 @@ export const ModalInitializationError = ({
|
|
|
17
17
|
const [showTechnicalDetails, setShowTechnicalDetails] = useState(false);
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
|
-
<div className="flex min-h-[400px] flex-col items-center justify-center p-
|
|
20
|
+
<div className="flex min-h-[400px] flex-col items-center justify-center p-6 text-white">
|
|
21
21
|
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-red-500">
|
|
22
22
|
<WarningIcon className="h-8 w-8 text-white" />
|
|
23
23
|
</div>
|
|
@@ -29,7 +29,7 @@ export const ModalInitializationError = ({
|
|
|
29
29
|
refresh the page.
|
|
30
30
|
</p>
|
|
31
31
|
|
|
32
|
-
{error.
|
|
32
|
+
{(error.cause as string) && (
|
|
33
33
|
<button
|
|
34
34
|
type="button"
|
|
35
35
|
onClick={() => setShowTechnicalDetails(!showTechnicalDetails)}
|
|
@@ -50,19 +50,21 @@ export const ModalInitializationError = ({
|
|
|
50
50
|
d="M19 9l-7 7-7-7"
|
|
51
51
|
/>
|
|
52
52
|
</svg>
|
|
53
|
-
|
|
53
|
+
{showTechnicalDetails
|
|
54
|
+
? 'Hide technical details'
|
|
55
|
+
: 'Show technical details'}
|
|
54
56
|
</button>
|
|
55
57
|
)}
|
|
56
58
|
|
|
57
|
-
{showTechnicalDetails && error.
|
|
59
|
+
{showTechnicalDetails && (error.cause as string) && (
|
|
58
60
|
<div className="mb-8 max-h-64 w-full max-w-md overflow-y-auto rounded-lg border border-red-900 bg-[#2b0000] p-4">
|
|
59
61
|
<pre className="whitespace-pre-wrap break-words text-red-100 text-xs">
|
|
60
|
-
{error.
|
|
62
|
+
{error.cause as string}
|
|
61
63
|
</pre>
|
|
62
64
|
</div>
|
|
63
65
|
)}
|
|
64
66
|
|
|
65
|
-
<div className="flex w-full
|
|
67
|
+
<div className="flex w-full flex-col space-y-3">
|
|
66
68
|
{onTryAgain && (
|
|
67
69
|
<Button
|
|
68
70
|
onClick={onTryAgain}
|
|
@@ -4,6 +4,7 @@ import type { Address } from '@0xsequence/api-client';
|
|
|
4
4
|
import { Skeleton } from '@0xsequence/design-system';
|
|
5
5
|
import { useEffect } from 'react';
|
|
6
6
|
import { compareAddress } from '../../../../../../utils';
|
|
7
|
+
import { isOpenSeaOrderbook } from '../../../../../../utils/normalizeMarketplace';
|
|
7
8
|
import { type Currency, OrderbookKind } from '../../../../../_internal';
|
|
8
9
|
import { useCurrencyList } from '../../../../../hooks';
|
|
9
10
|
import {
|
|
@@ -41,7 +42,7 @@ function CurrencyOptionsSelect({
|
|
|
41
42
|
|
|
42
43
|
// Filter currencies for OpenSea
|
|
43
44
|
let filteredCurrencies = currencies;
|
|
44
|
-
if (currencies && orderbookKind
|
|
45
|
+
if (currencies && isOpenSeaOrderbook(orderbookKind) && modalType) {
|
|
45
46
|
const openseaCurrency = getOpenseaCurrencyForChain(chainId, modalType);
|
|
46
47
|
if (openseaCurrency) {
|
|
47
48
|
// Filter to only show the OpenSea-supported currency
|
|
@@ -15,6 +15,7 @@ import { useAccount } from 'wagmi';
|
|
|
15
15
|
import type { Currency, Price } from '../../../../../../types';
|
|
16
16
|
import { calculateTotalOfferCost, cn } from '../../../../../../utils';
|
|
17
17
|
import { validateOpenseaOfferDecimals } from '../../../../../../utils/price';
|
|
18
|
+
import { isOpenSeaOrderbook } from '../../../../../../utils/normalizeMarketplace';
|
|
18
19
|
import {
|
|
19
20
|
useConvertPriceToUSD,
|
|
20
21
|
useTokenCurrencyBalance,
|
|
@@ -36,7 +37,6 @@ type PriceInputProps = {
|
|
|
36
37
|
};
|
|
37
38
|
disabled?: boolean;
|
|
38
39
|
orderbookKind?: OrderbookKind;
|
|
39
|
-
setOpenseaLowestPriceCriteriaMet?: (state: boolean) => void;
|
|
40
40
|
modalType?: 'listing' | 'offer';
|
|
41
41
|
// Fee data for enhanced balance checking in offers
|
|
42
42
|
feeData?: {
|
|
@@ -55,7 +55,6 @@ export default function PriceInput({
|
|
|
55
55
|
includeNativeCurrency,
|
|
56
56
|
disabled,
|
|
57
57
|
orderbookKind,
|
|
58
|
-
setOpenseaLowestPriceCriteriaMet,
|
|
59
58
|
modalType,
|
|
60
59
|
feeData,
|
|
61
60
|
}: PriceInputProps) {
|
|
@@ -76,14 +75,13 @@ export default function PriceInput({
|
|
|
76
75
|
useConvertPriceToUSD({
|
|
77
76
|
chainId,
|
|
78
77
|
currencyAddress: currencyAddress ?? zeroAddress,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
},
|
|
78
|
+
amountRaw: priceAmountRaw?.toString(),
|
|
79
|
+
query: {
|
|
80
|
+
enabled:
|
|
81
|
+
isOpenSeaOrderbook(orderbookKind) &&
|
|
82
|
+
!!currencyAddress &&
|
|
83
|
+
!!priceAmountRaw,
|
|
84
|
+
},
|
|
87
85
|
});
|
|
88
86
|
|
|
89
87
|
useEffect(() => {
|
|
@@ -163,18 +161,14 @@ export default function PriceInput({
|
|
|
163
161
|
);
|
|
164
162
|
|
|
165
163
|
const openseaLowestPriceCriteriaMet =
|
|
166
|
-
orderbookKind
|
|
167
|
-
|
|
164
|
+
isOpenSeaOrderbook(orderbookKind) &&
|
|
165
|
+
conversion?.usdAmount !== undefined &&
|
|
168
166
|
conversion.usdAmount >= 0.01;
|
|
169
167
|
|
|
170
168
|
if (checkBalance?.enabled) {
|
|
171
169
|
checkBalance.callback(balanceError);
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
if (setOpenseaLowestPriceCriteriaMet) {
|
|
175
|
-
setOpenseaLowestPriceCriteriaMet(openseaLowestPriceCriteriaMet);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
172
|
const [value, setValue] = useState('0');
|
|
179
173
|
const prevCurrencyDecimals = useRef(currencyDecimals);
|
|
180
174
|
const [openseaDecimalError, setOpenseaDecimalError] = useState<string | null>(
|
|
@@ -212,7 +206,7 @@ export default function PriceInput({
|
|
|
212
206
|
if (!price || !onPriceChange) return;
|
|
213
207
|
|
|
214
208
|
// Validate OpenSea decimal constraints for offers
|
|
215
|
-
if (orderbookKind
|
|
209
|
+
if (isOpenSeaOrderbook(orderbookKind) && modalType === 'offer') {
|
|
216
210
|
const validation = validateOpenseaOfferDecimals(newValue);
|
|
217
211
|
if (!validation.isValid) {
|
|
218
212
|
setOpenseaDecimalError(validation.errorMessage || null);
|
|
@@ -325,7 +319,7 @@ export default function PriceInput({
|
|
|
325
319
|
{!balanceError &&
|
|
326
320
|
priceAmountRaw !== 0n &&
|
|
327
321
|
!openseaLowestPriceCriteriaMet &&
|
|
328
|
-
orderbookKind
|
|
322
|
+
isOpenSeaOrderbook(orderbookKind) &&
|
|
329
323
|
!isConversionLoading &&
|
|
330
324
|
modalType === 'offer' &&
|
|
331
325
|
!openseaDecimalError && (
|
|
@@ -339,7 +333,7 @@ export default function PriceInput({
|
|
|
339
333
|
|
|
340
334
|
{!balanceError &&
|
|
341
335
|
openseaDecimalError &&
|
|
342
|
-
orderbookKind
|
|
336
|
+
isOpenSeaOrderbook(orderbookKind) &&
|
|
343
337
|
modalType === 'offer' && (
|
|
344
338
|
<Text className="font-body font-medium text-xs" color="negative">
|
|
345
339
|
{openseaDecimalError}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { useEffect, useState } from 'react';
|
|
12
12
|
import { DEFAULT_MARKETPLACE_FEE_PERCENTAGE } from '../../../../../../consts';
|
|
13
13
|
import type { Price } from '../../../../../../types';
|
|
14
|
+
import { findMarketCollection } from '../../../../../../utils';
|
|
14
15
|
import { calculateEarningsAfterFees } from '../../../../../../utils/price';
|
|
15
16
|
import { useMarketplaceConfig, useRoyalty } from '../../../../../hooks';
|
|
16
17
|
|
|
@@ -37,8 +38,10 @@ export default function TransactionDetails({
|
|
|
37
38
|
const { data, isLoading: marketplaceConfigLoading } = useMarketplaceConfig();
|
|
38
39
|
|
|
39
40
|
const marketplaceFeePercentage = includeMarketplaceFee
|
|
40
|
-
?
|
|
41
|
-
|
|
41
|
+
? findMarketCollection(
|
|
42
|
+
data?.market.collections ?? [],
|
|
43
|
+
collectionAddress,
|
|
44
|
+
chainId,
|
|
42
45
|
)?.feePercentage || DEFAULT_MARKETPLACE_FEE_PERCENTAGE
|
|
43
46
|
: 0;
|
|
44
47
|
// royaltyPercentage is an array of [recipient, percentage]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { OrderbookKind } from '@0xsequence/api-client';
|
|
2
|
+
import * as MarketplaceMocks from '@0xsequence/api-client/mocks/marketplace';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import type { Currency } from '../../../../_internal';
|
|
5
|
+
import { filterCurrenciesForOrderbook } from './currency';
|
|
6
|
+
|
|
7
|
+
const { mockCurrencies } = MarketplaceMocks;
|
|
8
|
+
const typedMockCurrencies: Currency[] = mockCurrencies.map((currency) => ({
|
|
9
|
+
...currency,
|
|
10
|
+
contractAddress: currency.contractAddress as `0x${string}`,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('filterCurrenciesForOrderbook', () => {
|
|
14
|
+
it('should apply OpenSea listing currency limits to Magic Eden orderbooks', () => {
|
|
15
|
+
const filteredCurrencies = filterCurrenciesForOrderbook(
|
|
16
|
+
typedMockCurrencies,
|
|
17
|
+
OrderbookKind.magic_eden,
|
|
18
|
+
1,
|
|
19
|
+
'listing',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
expect(filteredCurrencies).toHaveLength(1);
|
|
23
|
+
expect(filteredCurrencies[0]?.contractAddress).toBe(
|
|
24
|
+
typedMockCurrencies[0]?.contractAddress,
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { zeroAddress } from 'viem';
|
|
2
2
|
import { OrderbookKind } from '../../../../../types';
|
|
3
3
|
import { compareAddress } from '../../../../../utils';
|
|
4
|
+
import { isOpenSeaOrderbook, normalizeOrderbookKind } from '../../../../../utils/normalizeMarketplace';
|
|
4
5
|
import type { Currency } from '../../../../_internal';
|
|
5
6
|
import { getOpenseaCurrencyForChain } from '../../_internal/constants/opensea-currencies';
|
|
6
7
|
|
|
@@ -12,7 +13,7 @@ export function filterCurrenciesForOrderbook(
|
|
|
12
13
|
): Currency[] {
|
|
13
14
|
if (!currencies || currencies.length === 0) return [];
|
|
14
15
|
|
|
15
|
-
if (orderbookKind
|
|
16
|
+
if (isOpenSeaOrderbook(orderbookKind)) {
|
|
16
17
|
const openseaCurrency = getOpenseaCurrencyForChain(chainId, side);
|
|
17
18
|
if (openseaCurrency) {
|
|
18
19
|
return currencies.filter((currency) =>
|
|
@@ -35,8 +36,9 @@ export function getDefaultCurrency(
|
|
|
35
36
|
return currencies[0];
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
const normalizedOrderbookKind = normalizeOrderbookKind(orderbookKind);
|
|
38
40
|
const shouldSkipNative =
|
|
39
|
-
|
|
41
|
+
normalizedOrderbookKind !== OrderbookKind.sequence_marketplace_v2 &&
|
|
40
42
|
currencies.length > 1;
|
|
41
43
|
|
|
42
44
|
return shouldSkipNative ? currencies[1] : currencies[0];
|
package/src/styles/styles.ts
CHANGED
|
@@ -1855,6 +1855,17 @@ export const styles = String.raw`/* Modified Tailwind CSS, to avoid issues with
|
|
|
1855
1855
|
--tw-tracking: var(--tracking-normal);
|
|
1856
1856
|
letter-spacing: var(--tracking-normal);
|
|
1857
1857
|
}
|
|
1858
|
+
.text-small {
|
|
1859
|
+
font-family: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
1860
|
+
font-size: var(--text-xs);
|
|
1861
|
+
line-height: var(--tw-leading, var(--text-xs--line-height));
|
|
1862
|
+
--tw-leading: calc(var(--spacing) * 4);
|
|
1863
|
+
line-height: calc(var(--spacing) * 4);
|
|
1864
|
+
--tw-font-weight: var(--font-weight-medium);
|
|
1865
|
+
font-weight: var(--font-weight-medium);
|
|
1866
|
+
--tw-tracking: var(--tracking-wide);
|
|
1867
|
+
letter-spacing: var(--tracking-wide);
|
|
1868
|
+
}
|
|
1858
1869
|
.font-body {
|
|
1859
1870
|
font-family: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
1860
1871
|
}
|
|
@@ -1985,6 +1996,9 @@ export const styles = String.raw`/* Modified Tailwind CSS, to avoid issues with
|
|
|
1985
1996
|
.break-words {
|
|
1986
1997
|
overflow-wrap: break-word;
|
|
1987
1998
|
}
|
|
1999
|
+
.wrap-anywhere {
|
|
2000
|
+
overflow-wrap: anywhere;
|
|
2001
|
+
}
|
|
1988
2002
|
.text-ellipsis {
|
|
1989
2003
|
text-overflow: ellipsis;
|
|
1990
2004
|
}
|
|
@@ -2313,6 +2327,10 @@ export const styles = String.raw`/* Modified Tailwind CSS, to avoid issues with
|
|
|
2313
2327
|
--tw-duration: 100ms;
|
|
2314
2328
|
transition-duration: 100ms;
|
|
2315
2329
|
}
|
|
2330
|
+
.duration-150 {
|
|
2331
|
+
--tw-duration: 150ms;
|
|
2332
|
+
transition-duration: 150ms;
|
|
2333
|
+
}
|
|
2316
2334
|
.duration-200 {
|
|
2317
2335
|
--tw-duration: 200ms;
|
|
2318
2336
|
transition-duration: 200ms;
|
|
@@ -38,6 +38,16 @@ describe('getMarketplaceDetails', () => {
|
|
|
38
38
|
expect(result?.logo).toBe(OpenSeaLogo);
|
|
39
39
|
expect(result?.displayName).toBe('OpenSea');
|
|
40
40
|
});
|
|
41
|
+
|
|
42
|
+
test('should map Magic Eden kind to OpenSea', () => {
|
|
43
|
+
const result = getMarketplaceDetails({
|
|
44
|
+
originName: 'Magic Eden',
|
|
45
|
+
kind: MarketplaceKind.magic_eden,
|
|
46
|
+
});
|
|
47
|
+
expect(result).toBeDefined();
|
|
48
|
+
expect(result?.logo).toBe(OpenSeaLogo);
|
|
49
|
+
expect(result?.displayName).toBe('OpenSea');
|
|
50
|
+
});
|
|
41
51
|
});
|
|
42
52
|
|
|
43
53
|
describe('detection by marketplace name', () => {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { WebrpcError } from '@0xsequence/api-client';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import { getWebRPCErrorMessage } from '../getWebRPCErrorMessage';
|
|
4
|
+
|
|
5
|
+
describe('getWebRPCErrorMessage', () => {
|
|
6
|
+
test('maps OutOfFund endpoint errors to an insufficient balance message', () => {
|
|
7
|
+
const message = getWebRPCErrorMessage({
|
|
8
|
+
code: 0,
|
|
9
|
+
status: 400,
|
|
10
|
+
name: 'WebrpcEndpoint',
|
|
11
|
+
message: 'endpoint error',
|
|
12
|
+
cause: 'evm error: OutOfFund',
|
|
13
|
+
} as WebrpcError);
|
|
14
|
+
|
|
15
|
+
expect(message).toBe('Insufficient balance to complete this transaction.');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('continues to map known WebRPC error codes', () => {
|
|
19
|
+
const message = getWebRPCErrorMessage({
|
|
20
|
+
code: 3000,
|
|
21
|
+
status: 404,
|
|
22
|
+
name: 'WebrpcNotFound',
|
|
23
|
+
message: 'not found',
|
|
24
|
+
} as WebrpcError);
|
|
25
|
+
|
|
26
|
+
expect(message).toBe('Item not found or no longer available.');
|
|
27
|
+
});
|
|
28
|
+
});
|