@0xsequence/marketplace-sdk 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder-types-Jl3Ymws8.d.ts +73 -0
- package/dist/{chunk-XP3WY5AX.js → chunk-7C7ADZ2H.js} +3 -3
- package/dist/{chunk-XP3WY5AX.js.map → chunk-7C7ADZ2H.js.map} +1 -1
- package/dist/{chunk-FCF57DZI.js → chunk-7FN62HOP.js} +5 -9
- package/dist/chunk-7FN62HOP.js.map +1 -0
- package/dist/chunk-DZKPDV63.js +27 -0
- package/dist/chunk-DZKPDV63.js.map +1 -0
- package/dist/{chunk-ZUEQGPLO.js → chunk-J6F5QOW5.js} +2 -2
- package/dist/{chunk-ZUEQGPLO.js.map → chunk-J6F5QOW5.js.map} +1 -1
- package/dist/{chunk-I37CRQ4S.js → chunk-JWNONWD6.js} +259 -173
- package/dist/chunk-JWNONWD6.js.map +1 -0
- package/dist/{chunk-LJAB3S6U.js → chunk-TFRAOS7F.js} +22 -13
- package/dist/chunk-TFRAOS7F.js.map +1 -0
- package/dist/{chunk-MKGSGTQC.js → chunk-TLNRD4BQ.js} +3 -3
- package/dist/{chunk-5NORRVPM.js → chunk-UZIAX32Y.js} +1 -1
- package/dist/{chunk-5NORRVPM.js.map → chunk-UZIAX32Y.js.map} +1 -1
- package/dist/chunk-WGGZEQHL.js +56 -0
- package/dist/chunk-WGGZEQHL.js.map +1 -0
- package/dist/{chunk-MWDG7UTB.js → chunk-ZBLU3Q22.js} +1 -1
- package/dist/{chunk-MSTTVFVQ.js → chunk-ZGVCOQ4I.js} +383 -283
- package/dist/chunk-ZGVCOQ4I.js.map +1 -0
- package/dist/{create-config-BXvwUh55.d.ts → create-config-DOUq8Day.d.ts} +2 -2
- package/dist/index.css +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +10 -6
- package/dist/{sdk-config-B32_2bG3.d.ts → marketplace.gen-D0ADxbfH.d.ts} +1 -24
- package/dist/react/_internal/api/index.d.ts +3 -2
- package/dist/react/_internal/databeat/index.css +82 -0
- package/dist/react/_internal/databeat/index.css.map +1 -0
- package/dist/react/_internal/databeat/index.d.ts +68 -0
- package/dist/react/_internal/databeat/index.js +26 -0
- package/dist/react/_internal/databeat/index.js.map +1 -0
- package/dist/react/_internal/index.d.ts +6 -5
- package/dist/react/_internal/index.js +6 -6
- package/dist/react/_internal/wagmi/index.d.ts +4 -4
- package/dist/react/_internal/wagmi/index.js +1 -1
- package/dist/react/hooks/index.d.ts +367 -22
- package/dist/react/hooks/index.js +9 -7
- package/dist/react/index.css +1 -1
- package/dist/react/index.css.map +1 -1
- package/dist/react/index.d.ts +7 -6
- package/dist/react/index.js +15 -12
- package/dist/react/ssr/index.d.ts +54 -41
- package/dist/react/ssr/index.js +5 -9
- package/dist/react/ssr/index.js.map +1 -1
- package/dist/react/ui/components/collectible-card/index.css +1 -1
- package/dist/react/ui/components/collectible-card/index.css.map +1 -1
- package/dist/react/ui/components/collectible-card/index.d.ts +4 -3
- package/dist/react/ui/components/collectible-card/index.js +9 -8
- package/dist/react/ui/components/marketplace-logos/index.js +1 -1
- package/dist/react/ui/index.css +1 -1
- package/dist/react/ui/index.css.map +1 -1
- package/dist/react/ui/index.d.ts +4 -3
- package/dist/react/ui/index.js +9 -8
- package/dist/react/ui/modals/_internal/components/actionModal/index.d.ts +4 -3
- package/dist/react/ui/modals/_internal/components/actionModal/index.js +6 -6
- package/dist/react/ui/styles/index.d.ts +1 -1
- package/dist/sdk-config-xWkdBdrL.d.ts +24 -0
- package/dist/{services-BRBVE0mm.d.ts → services-Dd2MoBTM.d.ts} +2 -1
- package/dist/styles/index.css +1 -1
- package/dist/styles/index.css.map +1 -1
- package/dist/styles/index.d.ts +1 -1
- package/dist/styles/index.js +1 -1
- package/dist/types/index.d.ts +4 -5
- package/dist/types/index.js +9 -5
- package/dist/{types-Yto6KrTN.d.ts → types-vOfhbBkR.d.ts} +3 -2
- package/dist/utils/index.d.ts +4 -3
- package/dist/utils/index.js +4 -4
- package/package.json +12 -10
- package/src/react/_internal/api/__mocks__/indexer.msw.ts +197 -0
- package/src/react/_internal/api/__mocks__/marketplace.msw.ts +140 -1
- package/src/react/_internal/api/__mocks__/metadata.msw.ts +162 -0
- package/src/react/_internal/databeat/index.ts +63 -0
- package/src/react/_internal/databeat/types.ts +70 -0
- package/src/react/_internal/test/mocks/publicClient.ts +39 -0
- package/src/react/_internal/test/mocks/wagmi.ts +61 -0
- package/src/react/_internal/test/mocks/wallet.ts +61 -0
- package/src/react/_internal/test/setup.ts +28 -0
- package/src/react/_internal/test-utils.tsx +31 -2
- package/src/react/_internal/wagmi/__tests__/create-config.test.ts +53 -20
- package/src/react/_internal/wagmi/create-config.ts +3 -4
- package/src/react/_internal/wagmi/embedded.ts +1 -4
- package/src/react/_internal/wagmi/universal.ts +1 -4
- package/src/react/_internal/wallet/wallet.ts +1 -0
- package/src/react/hooks/__tests__/useAutoSelectFeeOption.test.tsx +314 -0
- package/src/react/hooks/__tests__/useBalanceOfCollectible.test.tsx +148 -0
- package/src/react/hooks/__tests__/useCancelOrder.test.tsx +410 -0
- package/src/react/hooks/__tests__/useCancelTransactionSteps.test.tsx +269 -0
- package/src/react/hooks/__tests__/useCollectible.test.tsx +120 -0
- package/src/react/hooks/__tests__/useCollection.test.tsx +101 -0
- package/src/react/hooks/__tests__/useCollectionBalanceDetails.test.tsx +175 -0
- package/src/react/hooks/__tests__/useCollectionDetails.test.tsx +82 -0
- package/src/react/hooks/__tests__/useCollectionDetailsPolling.test.tsx +133 -0
- package/src/react/hooks/__tests__/useCountListingsForCollectible.test.tsx +108 -0
- package/src/react/hooks/__tests__/useCountOfCollectables.test.tsx +129 -0
- package/src/react/hooks/__tests__/useCountOffersForCollectible.test.tsx +108 -0
- package/src/react/hooks/__tests__/useCurrencies.test.tsx +176 -0
- package/src/react/hooks/__tests__/useCurrency.test.tsx +153 -0
- package/src/react/hooks/__tests__/useCurrencyBalance.test.tsx +111 -0
- package/src/react/hooks/__tests__/useFilters.test.tsx +127 -0
- package/src/react/hooks/__tests__/useFloorOrder.test.tsx +101 -0
- package/src/react/hooks/__tests__/useGenerateBuyTransaction.test.tsx +173 -0
- package/src/react/hooks/__tests__/useGenerateCancelTransaction.test.tsx +207 -0
- package/src/react/hooks/__tests__/useGenerateListingTransaction.test.tsx +207 -0
- package/src/react/hooks/__tests__/useGenerateOfferTransaction.test.tsx +205 -0
- package/src/react/hooks/__tests__/useGenerateSellTransaction.test.tsx +181 -0
- package/src/react/hooks/__tests__/useHighestOffer.test.tsx +118 -0
- package/src/react/hooks/__tests__/useListBalances.test.tsx +136 -0
- package/src/react/hooks/__tests__/useListCollectibleActivities.test.tsx +200 -0
- package/src/react/hooks/__tests__/useListCollectibles.test.tsx +232 -0
- package/src/react/hooks/__tests__/useListCollectiblesPaginated.test.tsx +217 -0
- package/src/react/hooks/__tests__/useListCollectionActivities.test.tsx +235 -0
- package/src/react/hooks/__tests__/useListCollections.test.tsx +296 -0
- package/src/react/hooks/__tests__/useListListingsForCollectible.test.tsx +140 -0
- package/src/react/hooks/__tests__/useListOffersForCollectible.test.tsx +140 -0
- package/src/react/hooks/__tests__/useLowestListing.test.tsx +148 -0
- package/src/react/hooks/__tests__/useMarketplaceConfig.test.tsx +106 -0
- package/src/react/hooks/__tests__/useRoyaltyPercentage.test.tsx +129 -0
- package/src/react/hooks/index.ts +1 -1
- package/src/react/hooks/options/__mocks__/marketplaceConfig.msw.ts +66 -10
- package/src/react/hooks/options/__tests__/marketplaceConfigOptions.test.tsx +2 -11
- package/src/react/hooks/options/marketplaceConfigOptions.ts +8 -3
- package/src/react/hooks/useAutoSelectFeeOption.tsx +4 -3
- package/src/react/hooks/useCancelTransactionSteps.tsx +17 -9
- package/src/react/hooks/useCollectionDetailsPolling.tsx +1 -1
- package/src/react/hooks/useCurrencies.tsx +29 -28
- package/src/react/hooks/useFilters.tsx +75 -2
- package/src/react/hooks/useGenerateBuyTransaction.tsx +13 -5
- package/src/react/hooks/useListCollectibleActivities.tsx +1 -0
- package/src/react/hooks/useListCollectibles.tsx +1 -0
- package/src/react/hooks/useListCollectiblesPaginated.tsx +78 -0
- package/src/react/hooks/useListCollectionActivities.tsx +1 -0
- package/src/react/hooks/useListCollections.tsx +2 -2
- package/src/react/ui/components/_internals/custom-select/__tests__/CustomSelect.test.tsx +6 -2
- package/src/react/ui/components/collectible-card/CollectibleCard.tsx +1 -1
- package/src/react/ui/components/collectible-card/Footer.tsx +9 -5
- package/src/react/ui/modals/BuyModal/Modal.tsx +9 -4
- package/src/react/ui/modals/BuyModal/__tests__/Modal.test.tsx +0 -1
- package/src/react/ui/modals/BuyModal/__tests__/store.test.ts +4 -2
- package/src/react/ui/modals/BuyModal/hooks/__tests__/useBuyCollectable.test.tsx +1 -24
- package/src/react/ui/modals/BuyModal/hooks/__tests__/useCheckoutOptions.test.tsx +152 -210
- package/src/react/ui/modals/BuyModal/hooks/__tests__/useFees.test.tsx +19 -49
- package/src/react/ui/modals/BuyModal/hooks/useFees.ts +6 -6
- package/src/react/ui/modals/BuyModal/modals/Modal1155.tsx +4 -2
- package/src/react/ui/modals/BuyModal/modals/__tests__/Modal1155.test.tsx +161 -52
- package/src/react/ui/modals/BuyModal/store.ts +7 -0
- package/src/react/ui/modals/CreateListingModal/Modal.tsx +1 -3
- package/src/react/ui/modals/CreateListingModal/__tests__/Modal.test.tsx +59 -227
- package/src/react/ui/modals/CreateListingModal/hooks/useCreateListing.tsx +2 -1
- package/src/react/ui/modals/CreateListingModal/hooks/useTransactionSteps.tsx +47 -7
- package/src/react/ui/modals/MakeOfferModal/Modal.tsx +1 -8
- package/src/react/ui/modals/MakeOfferModal/__tests__/Modal.test.tsx +41 -118
- package/src/react/ui/modals/MakeOfferModal/hooks/useMakeOffer.tsx +2 -1
- package/src/react/ui/modals/MakeOfferModal/hooks/useTransactionSteps.tsx +34 -6
- package/src/react/ui/modals/SellModal/Modal.tsx +3 -1
- package/src/react/ui/modals/SellModal/__tests__/Modal.test.tsx +4 -3
- package/src/react/ui/modals/SellModal/hooks/useGetTokenApproval.tsx +33 -31
- package/src/react/ui/modals/SellModal/hooks/useSell.tsx +11 -7
- package/src/react/ui/modals/SellModal/hooks/useTransactionSteps.tsx +58 -16
- package/src/react/ui/modals/SuccessfulPurchaseModal/__tests__/Modal.test.tsx +0 -1
- package/src/react/ui/modals/_internal/components/actionModal/ErrorModal.tsx +4 -2
- package/src/react/ui/modals/_internal/components/currencyOptionsSelect/__tests__/index.test.tsx +129 -57
- package/src/react/ui/modals/_internal/components/currencyOptionsSelect/index.tsx +1 -3
- package/src/react/ui/modals/_internal/components/priceInput/__tests__/index.test.tsx +1 -3
- package/src/react/ui/modals/_internal/components/transactionDetails/index.tsx +2 -2
- package/src/react/ui/modals/_internal/components/transactionStatusModal/__tests__/TransactionStatusModal.test.tsx +8 -8
- package/src/react/ui/modals/_internal/components/transactionStatusModal/hooks/useTransactionStatus.ts +1 -0
- package/src/types/builder-types.ts +79 -0
- package/src/types/index.ts +1 -1
- package/src/utils/__tests__/get-public-rpc-client.test.ts +2 -0
- package/src/utils/getMarketplaceDetails.ts +2 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.js +2 -1
- package/dist/chunk-FCF57DZI.js.map +0 -1
- package/dist/chunk-I37CRQ4S.js.map +0 -1
- package/dist/chunk-LJAB3S6U.js.map +0 -1
- package/dist/chunk-MSTTVFVQ.js.map +0 -1
- package/dist/chunk-RK6KYMZM.js +0 -18
- package/dist/chunk-RK6KYMZM.js.map +0 -1
- package/dist/marketplace-config-znEu4L0K.d.ts +0 -60
- package/src/react/hooks/useCurrencyOptions.tsx +0 -16
- package/src/types/marketplace-config.ts +0 -67
- /package/dist/{chunk-MKGSGTQC.js.map → chunk-TLNRD4BQ.js.map} +0 -0
- /package/dist/{chunk-MWDG7UTB.js.map → chunk-ZBLU3Q22.js.map} +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { useLowestListing } from '../useLowestListing';
|
|
3
|
+
import { renderHook, waitFor } from '../../_internal/test-utils';
|
|
4
|
+
import { zeroAddress } from 'viem';
|
|
5
|
+
import { MarketplaceKind } from '../../_internal/api/marketplace.gen';
|
|
6
|
+
import { http, HttpResponse } from 'msw';
|
|
7
|
+
import {
|
|
8
|
+
mockOrder,
|
|
9
|
+
mockMarketplaceEndpoint,
|
|
10
|
+
} from '../../_internal/api/__mocks__/marketplace.msw';
|
|
11
|
+
import { server } from '../../_internal/test/setup';
|
|
12
|
+
|
|
13
|
+
describe('useLowestListing', () => {
|
|
14
|
+
const defaultArgs = {
|
|
15
|
+
chainId: '1',
|
|
16
|
+
collectionAddress: zeroAddress as `0x${string}`,
|
|
17
|
+
tokenId: '1',
|
|
18
|
+
query: {},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
it('should fetch lowest listing data successfully', async () => {
|
|
22
|
+
const { result } = renderHook(() => useLowestListing(defaultArgs));
|
|
23
|
+
|
|
24
|
+
// Initially loading
|
|
25
|
+
expect(result.current.isLoading).toBe(true);
|
|
26
|
+
expect(result.current.data).toBeUndefined();
|
|
27
|
+
|
|
28
|
+
// Wait for data to be loaded
|
|
29
|
+
await waitFor(() => {
|
|
30
|
+
expect(result.current.isLoading).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Verify the data matches our mock
|
|
34
|
+
expect(result.current.data).toBeDefined();
|
|
35
|
+
expect(result.current.data?.order).toEqual(mockOrder);
|
|
36
|
+
expect(result.current.error).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should handle error states', async () => {
|
|
40
|
+
// Override the handler for this test to return an error
|
|
41
|
+
server.use(
|
|
42
|
+
http.post(mockMarketplaceEndpoint('GetCollectibleLowestListing'), () => {
|
|
43
|
+
return HttpResponse.json(
|
|
44
|
+
{ error: { message: 'Failed to fetch lowest listing' } },
|
|
45
|
+
{ status: 500 },
|
|
46
|
+
);
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const { result } = renderHook(() => useLowestListing(defaultArgs));
|
|
51
|
+
|
|
52
|
+
await waitFor(() => {
|
|
53
|
+
expect(result.current.isError).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(result.current.error).toBeDefined();
|
|
57
|
+
expect(result.current.data).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should refetch when args change', async () => {
|
|
61
|
+
const { result, rerender } = renderHook(() =>
|
|
62
|
+
useLowestListing(defaultArgs),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Wait for initial data
|
|
66
|
+
await waitFor(() => {
|
|
67
|
+
expect(result.current.isLoading).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Change args and rerender
|
|
71
|
+
const newArgs = {
|
|
72
|
+
...defaultArgs,
|
|
73
|
+
collectionAddress:
|
|
74
|
+
'0x1234567890123456789012345678901234567890' as `0x${string}`,
|
|
75
|
+
tokenId: '2',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
rerender(() => useLowestListing(newArgs));
|
|
79
|
+
|
|
80
|
+
// Wait for new data
|
|
81
|
+
await waitFor(() => {
|
|
82
|
+
expect(result.current.data).toBeDefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Verify that the query was refetched with new args
|
|
86
|
+
expect(result.current.data?.order).toEqual(mockOrder);
|
|
87
|
+
expect(result.current.isSuccess).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should handle optional filter parameter', async () => {
|
|
91
|
+
const argsWithFilter = {
|
|
92
|
+
...defaultArgs,
|
|
93
|
+
filters: {
|
|
94
|
+
marketplace: [
|
|
95
|
+
MarketplaceKind.sequence_marketplace_v2,
|
|
96
|
+
] as MarketplaceKind[],
|
|
97
|
+
currencies: [zeroAddress] as string[],
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const { result } = renderHook(() => useLowestListing(argsWithFilter));
|
|
102
|
+
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
expect(result.current.isLoading).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(result.current.data?.order).toEqual(mockOrder);
|
|
108
|
+
expect(result.current.isSuccess).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle query options', async () => {
|
|
112
|
+
const argsWithQuery = {
|
|
113
|
+
...defaultArgs,
|
|
114
|
+
query: {
|
|
115
|
+
enabled: true,
|
|
116
|
+
staleTime: 5000,
|
|
117
|
+
cacheTime: 10000,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const { result } = renderHook(() => useLowestListing(argsWithQuery));
|
|
122
|
+
|
|
123
|
+
await waitFor(() => {
|
|
124
|
+
expect(result.current.isLoading).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(result.current.data?.order).toEqual(mockOrder);
|
|
128
|
+
expect(result.current.isSuccess).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should handle disabled query', async () => {
|
|
132
|
+
const argsWithDisabledQuery = {
|
|
133
|
+
...defaultArgs,
|
|
134
|
+
query: {
|
|
135
|
+
enabled: false,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const { result } = renderHook(() =>
|
|
140
|
+
useLowestListing(argsWithDisabledQuery),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Should not be loading since query is disabled
|
|
144
|
+
expect(result.current.isLoading).toBe(false);
|
|
145
|
+
expect(result.current.data).toBeUndefined();
|
|
146
|
+
expect(result.current.isSuccess).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach } from 'vitest';
|
|
2
|
+
import { useMarketplaceConfig } from '../useMarketplaceConfig';
|
|
3
|
+
import { renderHook, waitFor } from '../../_internal/test-utils';
|
|
4
|
+
import {
|
|
5
|
+
mockConfig,
|
|
6
|
+
mockStyles,
|
|
7
|
+
createConfigHandler,
|
|
8
|
+
createStylesHandler,
|
|
9
|
+
createErrorHandler,
|
|
10
|
+
createStylesErrorHandler,
|
|
11
|
+
} from '../options/__mocks__/marketplaceConfig.msw';
|
|
12
|
+
import { server } from '../../_internal/test/setup';
|
|
13
|
+
|
|
14
|
+
describe('useMarketplaceConfig', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Reset handlers before each test
|
|
17
|
+
server.resetHandlers();
|
|
18
|
+
// Set up default handlers
|
|
19
|
+
server.use(createConfigHandler(), createStylesHandler());
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should fetch marketplace config and styles successfully', async () => {
|
|
23
|
+
const { result } = renderHook(() => useMarketplaceConfig());
|
|
24
|
+
|
|
25
|
+
// Initially loading
|
|
26
|
+
expect(result.current.isLoading).toBe(true);
|
|
27
|
+
expect(result.current.data).toBeUndefined();
|
|
28
|
+
|
|
29
|
+
// Wait for data to be loaded
|
|
30
|
+
await waitFor(() => {
|
|
31
|
+
expect(result.current.isLoading).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Verify the data matches our mock
|
|
35
|
+
expect(result.current.data).toBeDefined();
|
|
36
|
+
expect(result.current.data).toEqual({
|
|
37
|
+
...mockConfig,
|
|
38
|
+
cssString: mockStyles.replaceAll(/['"]/g, ''),
|
|
39
|
+
manifestUrl: expect.stringContaining('/manifest.json'),
|
|
40
|
+
});
|
|
41
|
+
expect(result.current.error).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle config fetch error', async () => {
|
|
45
|
+
// Override the handler for this test to return an error
|
|
46
|
+
server.use(createErrorHandler());
|
|
47
|
+
|
|
48
|
+
const { result } = renderHook(() => useMarketplaceConfig());
|
|
49
|
+
|
|
50
|
+
await waitFor(() => {
|
|
51
|
+
expect(result.current.isError).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result.current.error).toBeDefined();
|
|
55
|
+
expect(result.current.data).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should handle styles fetch error', async () => {
|
|
59
|
+
// Override the handler for this test to return an error
|
|
60
|
+
server.use(createStylesErrorHandler(), createConfigHandler());
|
|
61
|
+
|
|
62
|
+
const { result } = renderHook(() => useMarketplaceConfig());
|
|
63
|
+
|
|
64
|
+
await waitFor(() => {
|
|
65
|
+
expect(result.current.data).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// They just result in an empty cssString
|
|
69
|
+
expect(result.current.isError).toBe(false);
|
|
70
|
+
expect(result.current.data?.cssString).toBe('');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle both config and styles fetch errors', async () => {
|
|
74
|
+
// Override both handlers to return errors
|
|
75
|
+
server.use(createErrorHandler(), createStylesErrorHandler());
|
|
76
|
+
|
|
77
|
+
const { result } = renderHook(() => useMarketplaceConfig());
|
|
78
|
+
|
|
79
|
+
await waitFor(() => {
|
|
80
|
+
expect(result.current.isError).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(result.current.error).toBeDefined();
|
|
84
|
+
expect(result.current.data).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should cache the config data', async () => {
|
|
88
|
+
// First render to populate cache
|
|
89
|
+
const { result, rerender } = renderHook(() => useMarketplaceConfig());
|
|
90
|
+
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
expect(result.current.isLoading).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Trigger a rerender
|
|
96
|
+
rerender();
|
|
97
|
+
|
|
98
|
+
// Should have data immediately from cache
|
|
99
|
+
expect(result.current.isLoading).toBe(false);
|
|
100
|
+
expect(result.current.data).toEqual({
|
|
101
|
+
...mockConfig,
|
|
102
|
+
cssString: mockStyles.replaceAll(/['"]/g, ''),
|
|
103
|
+
manifestUrl: expect.stringContaining('/manifest.json'),
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { Mock } from 'vitest';
|
|
3
|
+
import { useRoyaltyPercentage } from '../useRoyaltyPercentage';
|
|
4
|
+
import { renderHook, waitFor } from '../../_internal/test-utils';
|
|
5
|
+
import {
|
|
6
|
+
createMockPublicClient,
|
|
7
|
+
type MockPublicClient,
|
|
8
|
+
} from '../../_internal/test/mocks/publicClient';
|
|
9
|
+
import { getPublicRpcClient } from '../../../utils/get-public-rpc-client';
|
|
10
|
+
|
|
11
|
+
// Mock the getPublicRpcClient
|
|
12
|
+
vi.mock('../../../utils/get-public-rpc-client');
|
|
13
|
+
|
|
14
|
+
describe('useRoyaltyPercentage', () => {
|
|
15
|
+
const mockAddress = '0x1234567890123456789012345678901234567890' as const;
|
|
16
|
+
const mockArgs = {
|
|
17
|
+
chainId: '1',
|
|
18
|
+
collectionAddress: mockAddress,
|
|
19
|
+
collectibleId: '1',
|
|
20
|
+
query: {},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const mockRoyaltyAmount = 500n; // 5% royalty
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
// Reset all mocks before each test
|
|
27
|
+
vi.resetAllMocks();
|
|
28
|
+
|
|
29
|
+
// Create a mock public client with custom royaltyInfo implementation
|
|
30
|
+
const mockPublicClient = createMockPublicClient({
|
|
31
|
+
readContract: vi.fn().mockImplementation(async ({ functionName }) => {
|
|
32
|
+
if (functionName === 'royaltyInfo') {
|
|
33
|
+
return [
|
|
34
|
+
mockAddress, // receiver address
|
|
35
|
+
mockRoyaltyAmount, // royalty amount
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
return [0n, 0n];
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Mock the getPublicRpcClient to return our mock client
|
|
43
|
+
(getPublicRpcClient as Mock).mockReturnValue(mockPublicClient);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should fetch royalty percentage successfully', async () => {
|
|
47
|
+
const { result } = renderHook(() => useRoyaltyPercentage(mockArgs));
|
|
48
|
+
|
|
49
|
+
// Initially loading
|
|
50
|
+
expect(result.current.isLoading).toBe(true);
|
|
51
|
+
expect(result.current.data).toBeUndefined();
|
|
52
|
+
|
|
53
|
+
// Wait for data to be loaded
|
|
54
|
+
await waitFor(() => {
|
|
55
|
+
expect(result.current.isLoading).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Verify the data matches our mock
|
|
59
|
+
expect(result.current.data).toBeDefined();
|
|
60
|
+
expect(result.current.data).toBe(mockRoyaltyAmount);
|
|
61
|
+
expect(result.current.error).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle contract read error gracefully', async () => {
|
|
65
|
+
// Override the mock to simulate a contract read error
|
|
66
|
+
const mockPublicClient = createMockPublicClient({
|
|
67
|
+
readContract: vi
|
|
68
|
+
.fn()
|
|
69
|
+
.mockRejectedValue(new Error('Contract read failed')),
|
|
70
|
+
});
|
|
71
|
+
(getPublicRpcClient as Mock).mockReturnValue(mockPublicClient);
|
|
72
|
+
|
|
73
|
+
const { result } = renderHook(() => useRoyaltyPercentage(mockArgs));
|
|
74
|
+
|
|
75
|
+
await waitFor(() => {
|
|
76
|
+
expect(result.current.isLoading).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Should return 0 as specified in the hook implementation
|
|
80
|
+
expect(result.current.data).toBe(0n);
|
|
81
|
+
expect(result.current.isError).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should validate input parameters', async () => {
|
|
85
|
+
// Using undefined as an invalid chain ID - this will fail Zod's string coercion
|
|
86
|
+
const invalidArgs = {
|
|
87
|
+
...mockArgs,
|
|
88
|
+
chainId: undefined,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// @ts-expect-error - invalid args
|
|
92
|
+
const { result } = renderHook(() => useRoyaltyPercentage(invalidArgs));
|
|
93
|
+
|
|
94
|
+
// Wait for the query to complete
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(result.current.isLoading).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// The query should fail with a validation error
|
|
100
|
+
expect(result.current.isError).toBe(true);
|
|
101
|
+
expect(result.current.error).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should cache the royalty data', async () => {
|
|
105
|
+
const { result, rerender } = renderHook(() =>
|
|
106
|
+
useRoyaltyPercentage(mockArgs),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
await waitFor(() => {
|
|
110
|
+
expect(result.current.isLoading).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const mockClient = getPublicRpcClient('1') as MockPublicClient;
|
|
114
|
+
const mockReadContract = mockClient.readContract as Mock;
|
|
115
|
+
// Record the number of calls to readContract
|
|
116
|
+
const initialCalls = mockReadContract.mock.calls.length;
|
|
117
|
+
|
|
118
|
+
// Trigger a rerender
|
|
119
|
+
rerender();
|
|
120
|
+
|
|
121
|
+
// Should have data immediately from cache
|
|
122
|
+
expect(result.current.isLoading).toBe(false);
|
|
123
|
+
expect(result.current.data).toBe(mockRoyaltyAmount);
|
|
124
|
+
|
|
125
|
+
// Verify no additional contract calls were made
|
|
126
|
+
const finalCalls = mockReadContract.mock.calls.length;
|
|
127
|
+
expect(finalCalls).toBe(initialCalls);
|
|
128
|
+
});
|
|
129
|
+
});
|
package/src/react/hooks/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ export * from './useCollection';
|
|
|
6
6
|
export * from './useCollectionBalanceDetails';
|
|
7
7
|
export * from './useConfig';
|
|
8
8
|
export * from './useCurrencies';
|
|
9
|
-
export * from './useCurrencyOptions';
|
|
10
9
|
export * from './useCurrency';
|
|
11
10
|
export * from './useFilters';
|
|
12
11
|
export * from './useFloorOrder';
|
|
@@ -14,6 +13,7 @@ export * from './useHighestOffer';
|
|
|
14
13
|
export * from './useListBalances';
|
|
15
14
|
export * from './useListCollectibleActivities';
|
|
16
15
|
export * from './useListCollectibles';
|
|
16
|
+
export * from './useListCollectiblesPaginated';
|
|
17
17
|
export * from './useListCollectionActivities';
|
|
18
18
|
export * from './useListOffersForCollectible';
|
|
19
19
|
export * from './useCountOffersForCollectible';
|
|
@@ -1,30 +1,86 @@
|
|
|
1
1
|
import { http, HttpResponse } from 'msw';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
MarketplaceType,
|
|
4
|
+
MarketplaceWallet,
|
|
5
|
+
OrderbookKind,
|
|
6
|
+
type MarketplaceConfig,
|
|
7
|
+
FilterCondition,
|
|
8
|
+
} from '../../../../types';
|
|
3
9
|
import { mockCurrencies } from '../../../_internal/api/__mocks__/marketplace.msw';
|
|
10
|
+
import { zeroAddress } from 'viem';
|
|
4
11
|
|
|
5
12
|
// Mock data
|
|
6
13
|
export const mockConfig: MarketplaceConfig = {
|
|
7
|
-
projectId: 123,
|
|
8
14
|
publisherId: 'test-publisher',
|
|
9
15
|
title: 'Test Marketplace',
|
|
10
16
|
shortDescription: 'A test marketplace',
|
|
17
|
+
socials: {
|
|
18
|
+
twitter: 'https://twitter.com/test',
|
|
19
|
+
discord: 'https://discord.com/test',
|
|
20
|
+
instagram: 'https://instagram.com/test',
|
|
21
|
+
website: '',
|
|
22
|
+
tiktok: '',
|
|
23
|
+
youtube: '',
|
|
24
|
+
},
|
|
11
25
|
faviconUrl: 'https://example.com/favicon.png',
|
|
12
26
|
landingBannerUrl: 'https://example.com/banner.png',
|
|
13
27
|
logoUrl: 'https://example.com/logo.png',
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
walletOptions: {
|
|
29
|
+
walletType: MarketplaceWallet.UNIVERSAL,
|
|
30
|
+
oidcIssuers: {},
|
|
31
|
+
connectors: ['coinbase', 'walletconnect'],
|
|
32
|
+
includeEIP6963Wallets: true,
|
|
33
|
+
},
|
|
16
34
|
collections: [
|
|
17
35
|
{
|
|
18
|
-
|
|
36
|
+
address: zeroAddress,
|
|
19
37
|
chainId: 1,
|
|
20
|
-
|
|
21
|
-
marketplaceType: 'orderbook',
|
|
38
|
+
marketplaceType: MarketplaceType.ORDERBOOK,
|
|
22
39
|
currencyOptions: mockCurrencies.map((c) => c.contractAddress),
|
|
40
|
+
exchanges: [],
|
|
41
|
+
bannerUrl: '',
|
|
42
|
+
feePercentage: 3.5,
|
|
43
|
+
destinationMarketplace: OrderbookKind.sequence_marketplace_v2,
|
|
44
|
+
filterSettings: {
|
|
45
|
+
filterOrder: ['Type', 'Rarity'],
|
|
46
|
+
exclusions: [
|
|
47
|
+
{
|
|
48
|
+
key: 'Type',
|
|
49
|
+
condition: FilterCondition.SPECIFIC_VALUE,
|
|
50
|
+
value: 'Sample',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
address: '0x1234567890123456789012345678901234567890',
|
|
57
|
+
chainId: 137,
|
|
58
|
+
marketplaceType: MarketplaceType.ORDERBOOK,
|
|
59
|
+
currencyOptions: [mockCurrencies[0].contractAddress],
|
|
60
|
+
exchanges: [],
|
|
61
|
+
bannerUrl: 'https://example.com/collection-banner.png',
|
|
62
|
+
feePercentage: 2.5,
|
|
63
|
+
destinationMarketplace: OrderbookKind.opensea,
|
|
64
|
+
filterSettings: {
|
|
65
|
+
filterOrder: ['Category', 'Level', 'Element'],
|
|
66
|
+
exclusions: [
|
|
67
|
+
{
|
|
68
|
+
key: 'Category',
|
|
69
|
+
condition: FilterCondition.ENTIRE_KEY,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: 'Level',
|
|
73
|
+
condition: FilterCondition.SPECIFIC_VALUE,
|
|
74
|
+
value: 'Legendary',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
23
78
|
},
|
|
24
79
|
],
|
|
25
80
|
landingPageLayout: 'default',
|
|
26
81
|
cssString: '',
|
|
27
82
|
manifestUrl: '',
|
|
83
|
+
bannerUrl: '',
|
|
28
84
|
};
|
|
29
85
|
|
|
30
86
|
export const mockStyles = `
|
|
@@ -54,9 +110,9 @@ const debugLog = (endpoint: string, request: Request, response: Response) => {
|
|
|
54
110
|
|
|
55
111
|
// MSW handlers
|
|
56
112
|
export const createConfigHandler = (config = mockConfig) =>
|
|
57
|
-
http.get('*/marketplace/*/
|
|
113
|
+
http.get('*/marketplace/*/settings.json', ({ request }) => {
|
|
58
114
|
const response = HttpResponse.json(config);
|
|
59
|
-
debugLog('
|
|
115
|
+
debugLog('settings.json', request, response);
|
|
60
116
|
return response;
|
|
61
117
|
});
|
|
62
118
|
|
|
@@ -70,7 +126,7 @@ export const createStylesHandler = (styles = mockStyles) =>
|
|
|
70
126
|
});
|
|
71
127
|
|
|
72
128
|
export const createErrorHandler = () =>
|
|
73
|
-
http.get('*/marketplace/*/
|
|
129
|
+
http.get('*/marketplace/*/settings.json', () => {
|
|
74
130
|
return HttpResponse.json(
|
|
75
131
|
{ code: 3000, msg: 'Project not found' },
|
|
76
132
|
{ status: 404 },
|
|
@@ -1,27 +1,18 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
2
|
-
import { setupServer } from 'msw/node';
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
3
2
|
import { marketplaceConfigOptions } from '../marketplaceConfigOptions';
|
|
4
3
|
import { renderHook, waitFor } from '../../../_internal/test-utils';
|
|
5
4
|
import type { QueryFunctionContext } from '@tanstack/react-query';
|
|
6
5
|
import {
|
|
7
|
-
handlers,
|
|
8
6
|
createErrorHandler,
|
|
9
7
|
createStylesErrorHandler,
|
|
10
8
|
mockConfig,
|
|
11
9
|
mockStyles,
|
|
12
10
|
} from '../__mocks__/marketplaceConfig.msw';
|
|
11
|
+
import { server } from '../../../_internal/test/setup';
|
|
13
12
|
|
|
14
13
|
type MarketplaceConfigQueryKey = ['configs', 'marketplace', string, string];
|
|
15
14
|
type MarketplaceConfigContext = QueryFunctionContext<MarketplaceConfigQueryKey>;
|
|
16
15
|
|
|
17
|
-
// Create MSW server with default handlers
|
|
18
|
-
const server = setupServer(...handlers);
|
|
19
|
-
|
|
20
|
-
// Setup test environment
|
|
21
|
-
beforeAll(() => server.listen());
|
|
22
|
-
afterEach(() => server.resetHandlers());
|
|
23
|
-
afterAll(() => server.close());
|
|
24
|
-
|
|
25
16
|
describe('marketplaceConfigOptions', () => {
|
|
26
17
|
it('should fetch marketplace config and styles successfully', async () => {
|
|
27
18
|
const { result } = renderHook(() =>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { queryOptions } from '@tanstack/react-query';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
Env,
|
|
4
|
+
MarketplaceConfig,
|
|
5
|
+
MarketplaceSettings,
|
|
6
|
+
SdkConfig,
|
|
7
|
+
} from '../../../types';
|
|
3
8
|
import {
|
|
4
9
|
MarketplaceConfigFetchError,
|
|
5
10
|
ProjectNotFoundError,
|
|
@@ -8,7 +13,7 @@ import { builderMarketplaceApi, configKeys } from '../../_internal';
|
|
|
8
13
|
|
|
9
14
|
const fetchBuilderConfig = async (projectId: string, env: Env) => {
|
|
10
15
|
const url = `${builderMarketplaceApi(projectId, env)}`;
|
|
11
|
-
const response = await fetch(`${url}/
|
|
16
|
+
const response = await fetch(`${url}/settings.json`);
|
|
12
17
|
|
|
13
18
|
const json = await response.json();
|
|
14
19
|
if (!response.ok) {
|
|
@@ -22,7 +27,7 @@ const fetchBuilderConfig = async (projectId: string, env: Env) => {
|
|
|
22
27
|
throw new MarketplaceConfigFetchError(json.msg);
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
|
-
return json as
|
|
30
|
+
return json as MarketplaceSettings;
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
const fetchStyles = async (projectId: string, env: Env) => {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Address, zeroAddress } from 'viem';
|
|
2
|
+
|
|
3
|
+
import { useChain } from '@0xsequence/kit';
|
|
4
|
+
import { useCallback } from 'react';
|
|
2
5
|
import { useAccount } from 'wagmi';
|
|
3
6
|
import type { FeeOption } from '../ui/modals/_internal/components/waasFeeOptionsSelect/WaasFeeOptionsSelect';
|
|
4
|
-
import { useCallback } from 'react';
|
|
5
|
-
import { useChain } from '@0xsequence/kit';
|
|
6
7
|
import { useCollectionBalanceDetails } from './useCollectionBalanceDetails';
|
|
7
8
|
|
|
8
9
|
enum AutoSelectFeeOptionError {
|
|
@@ -10,14 +10,17 @@ import { useWallet } from '../_internal/wallet/useWallet';
|
|
|
10
10
|
import type { ModalCallbacks } from '../ui/modals/_internal/types';
|
|
11
11
|
import { useConfig } from './useConfig';
|
|
12
12
|
import { useGenerateCancelTransaction } from './useGenerateCancelTransaction';
|
|
13
|
-
import type {
|
|
14
|
-
|
|
13
|
+
import type {
|
|
14
|
+
SignatureStep,
|
|
15
|
+
TransactionStep as walletTransactionStep,
|
|
16
|
+
} from '../_internal/utils';
|
|
15
17
|
import type { Hex } from 'viem';
|
|
16
18
|
import { useSwitchChainModal } from '../ui/modals/_internal/components/switchChainModal';
|
|
17
19
|
import {
|
|
18
20
|
ChainSwitchUserRejectedError,
|
|
19
21
|
WalletInstanceNotFoundError,
|
|
20
22
|
} from '../../utils/_internal/error/transaction';
|
|
23
|
+
import type { TransactionStep } from './useCancelOrder';
|
|
21
24
|
|
|
22
25
|
interface UseCancelTransactionStepsArgs {
|
|
23
26
|
collectionAddress: string;
|
|
@@ -46,10 +49,10 @@ export const useCancelTransactionSteps = ({
|
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
const getWalletChainId = async () => {
|
|
49
|
-
return await wallet
|
|
52
|
+
return await wallet?.getChainId();
|
|
50
53
|
};
|
|
51
54
|
const switchChain = async () => {
|
|
52
|
-
await wallet
|
|
55
|
+
await wallet?.switchChain(Number(chainId));
|
|
53
56
|
};
|
|
54
57
|
const checkAndSwitchChain = async () => {
|
|
55
58
|
const walletChainId = await getWalletChainId();
|
|
@@ -82,7 +85,11 @@ export const useCancelTransactionSteps = ({
|
|
|
82
85
|
marketplace: MarketplaceKind;
|
|
83
86
|
}) => {
|
|
84
87
|
try {
|
|
85
|
-
const address = await wallet
|
|
88
|
+
const address = await wallet?.address();
|
|
89
|
+
|
|
90
|
+
if (!address) {
|
|
91
|
+
throw new Error('Wallet address not found');
|
|
92
|
+
}
|
|
86
93
|
|
|
87
94
|
const steps = await generateCancelTransactionAsync({
|
|
88
95
|
collectionAddress,
|
|
@@ -139,7 +146,8 @@ export const useCancelTransactionSteps = ({
|
|
|
139
146
|
throw new Error('No transaction or signature step found');
|
|
140
147
|
}
|
|
141
148
|
|
|
142
|
-
let hash: Hex | undefined
|
|
149
|
+
let hash: Hex | undefined;
|
|
150
|
+
let reservoirOrderId: string | undefined;
|
|
143
151
|
|
|
144
152
|
if (transactionStep && wallet) {
|
|
145
153
|
hash = await executeTransaction({ transactionStep });
|
|
@@ -191,9 +199,9 @@ export const useCancelTransactionSteps = ({
|
|
|
191
199
|
}: {
|
|
192
200
|
transactionStep: Step;
|
|
193
201
|
}): Promise<Hex | undefined> => {
|
|
194
|
-
const hash = await wallet
|
|
202
|
+
const hash = await wallet?.handleSendTransactionStep(
|
|
195
203
|
Number(chainId),
|
|
196
|
-
transactionStep as
|
|
204
|
+
transactionStep as walletTransactionStep,
|
|
197
205
|
);
|
|
198
206
|
|
|
199
207
|
return hash;
|
|
@@ -204,7 +212,7 @@ export const useCancelTransactionSteps = ({
|
|
|
204
212
|
}: {
|
|
205
213
|
signatureStep: Step;
|
|
206
214
|
}) => {
|
|
207
|
-
const signature = await wallet
|
|
215
|
+
const signature = await wallet?.handleSignMessageStep(
|
|
208
216
|
signatureStep as SignatureStep,
|
|
209
217
|
);
|
|
210
218
|
|