@0xsequence/marketplace-sdk 0.8.3 → 0.8.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/CHANGELOG.md +8 -1
- package/dist/{chunk-FH4TZRDV.js → chunk-44YGZVBS.js} +2 -2
- package/dist/{chunk-QTV77W42.js → chunk-HRL2TMXU.js} +5 -5
- package/dist/chunk-HRL2TMXU.js.map +1 -0
- package/dist/{chunk-5ATGT5S4.js → chunk-VBRJ2OPM.js} +14 -6
- package/dist/chunk-VBRJ2OPM.js.map +1 -0
- package/dist/{chunk-DFI52A2E.js → chunk-VF3LWBQB.js} +365 -243
- package/dist/chunk-VF3LWBQB.js.map +1 -0
- package/dist/{chunk-FSJKN4YN.js → chunk-ZSCZLHKX.js} +194 -2
- package/dist/chunk-ZSCZLHKX.js.map +1 -0
- package/dist/index.js +5 -5
- package/dist/{lowestListing-DUZ_nYml.d.ts → lowestListing-W7P4EkC3.d.ts} +34 -11
- package/dist/react/_internal/databeat/index.js +4 -4
- package/dist/react/hooks/index.d.ts +8 -5
- package/dist/react/hooks/index.js +5 -3
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +8 -6
- package/dist/react/queries/index.d.ts +1 -1
- package/dist/react/queries/index.js +5 -1
- package/dist/react/ui/components/collectible-card/index.js +6 -6
- package/dist/react/ui/icons/index.js +1 -1
- package/dist/react/ui/index.js +6 -6
- package/dist/react/ui/modals/_internal/components/actionModal/index.js +4 -4
- package/dist/utils/abi/index.js +5 -5
- package/dist/utils/index.js +5 -5
- package/package.json +1 -1
- package/src/react/_internal/api/__mocks__/marketplace.msw.ts +35 -21
- package/src/react/hooks/__tests__/useCancelTransactionSteps.test.tsx +4 -9
- package/src/react/hooks/__tests__/useGenerateCancelTransaction.test.tsx +5 -4
- package/src/react/hooks/__tests__/useGenerateListingTransaction.test.tsx +14 -10
- package/src/react/hooks/__tests__/useGenerateOfferTransaction.test.tsx +115 -65
- package/src/react/hooks/__tests__/useGenerateSellTransaction.test.tsx +10 -7
- package/src/react/hooks/__tests__/useInventory.test.tsx +294 -0
- package/src/react/hooks/index.ts +1 -0
- package/src/react/hooks/useAutoSelectFeeOption.tsx +10 -3
- package/src/react/hooks/useCancelOrder.tsx +1 -0
- package/src/react/hooks/useCancelTransactionSteps.tsx +18 -4
- package/src/react/hooks/useGenerateOfferTransaction.tsx +11 -1
- package/src/react/hooks/useInventory.tsx +15 -0
- package/src/react/hooks/util/optimisticCancelUpdates.ts +115 -0
- package/src/react/queries/index.ts +1 -0
- package/src/react/queries/inventory.ts +303 -0
- package/src/react/queries/listBalances.ts +1 -8
- package/src/react/queries/listCollectibles.ts +12 -3
- package/src/react/ui/modals/CreateListingModal/__tests__/Modal.test.tsx +74 -104
- package/src/react/ui/modals/MakeOfferModal/__tests__/Modal.test.tsx +108 -78
- package/src/react/ui/modals/_internal/components/actionModal/ActionModal.test.tsx +286 -0
- package/src/react/ui/modals/_internal/components/actionModal/ActionModal.tsx +16 -4
- package/src/react/ui/modals/_internal/components/currencyOptionsSelect/__tests__/index.test.tsx +35 -132
- package/src/react/ui/modals/_internal/components/floorPriceText/__tests__/FloorPriceText.test.tsx +199 -0
- package/src/react/ui/modals/_internal/components/priceInput/__tests__/PriceInput.test.tsx +55 -0
- package/src/react/ui/modals/_internal/components/priceInput/index.tsx +1 -1
- package/src/react/ui/modals/_internal/components/switchChainModal/index.tsx +2 -2
- package/test/const.ts +24 -0
- package/test/test-utils.tsx +74 -26
- package/.changeset/flat-parks-clean.md +0 -8
- package/.changeset/red-buckets-deny.md +0 -6
- package/.changeset/seven-doors-taste.md +0 -5
- package/dist/chunk-5ATGT5S4.js.map +0 -1
- package/dist/chunk-DFI52A2E.js.map +0 -1
- package/dist/chunk-FSJKN4YN.js.map +0 -1
- package/dist/chunk-QTV77W42.js.map +0 -1
- package/src/react/ui/modals/_internal/components/priceInput/__tests__/index.test.tsx +0 -164
- /package/dist/{chunk-FH4TZRDV.js.map → chunk-44YGZVBS.js.map} +0 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContractInfo,
|
|
3
|
+
Page as IndexerPage,
|
|
4
|
+
TokenBalance,
|
|
5
|
+
} from '@0xsequence/indexer';
|
|
6
|
+
import { infiniteQueryOptions } from '@tanstack/react-query';
|
|
7
|
+
import type { Address } from 'viem';
|
|
8
|
+
import { OrderSide, type Page, type SdkConfig } from '../../types';
|
|
9
|
+
import {
|
|
10
|
+
type CollectibleOrder,
|
|
11
|
+
type ContractType,
|
|
12
|
+
getIndexerClient,
|
|
13
|
+
} from '../_internal';
|
|
14
|
+
import { fetchCollectibles } from './listCollectibles';
|
|
15
|
+
|
|
16
|
+
export interface UseInventoryArgs {
|
|
17
|
+
accountAddress: Address;
|
|
18
|
+
collectionAddress: Address;
|
|
19
|
+
chainId: number;
|
|
20
|
+
isLaos721?: boolean;
|
|
21
|
+
query?: {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Maintain collection state across calls
|
|
27
|
+
interface InventoryState {
|
|
28
|
+
seenTokenIds: Set<string>;
|
|
29
|
+
marketplaceFinished: boolean;
|
|
30
|
+
// Track if we've already fetched all indexer tokens
|
|
31
|
+
indexerTokensFetched: boolean;
|
|
32
|
+
// Store the token balances from the indexer
|
|
33
|
+
indexerTokenBalances: Map<string, CollectibleWithBalance>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Store state per collection
|
|
37
|
+
const stateByCollection = new Map<string, InventoryState>();
|
|
38
|
+
|
|
39
|
+
const getCollectionKey = (args: UseInventoryArgs) =>
|
|
40
|
+
`${args.chainId}-${args.collectionAddress}-${args.accountAddress}`;
|
|
41
|
+
|
|
42
|
+
interface GetInventoryArgs extends Omit<UseInventoryArgs, 'query'> {
|
|
43
|
+
isLaos721: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface CollectibleWithBalance extends CollectibleOrder {
|
|
47
|
+
balance: string;
|
|
48
|
+
contractInfo?: ContractInfo;
|
|
49
|
+
contractType: ContractType.ERC1155 | ContractType.ERC721;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CollectiblesResponse {
|
|
53
|
+
collectibles: CollectibleWithBalance[];
|
|
54
|
+
page: Page;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getOrInitState(collectionKey: string): InventoryState {
|
|
58
|
+
if (!stateByCollection.has(collectionKey)) {
|
|
59
|
+
stateByCollection.set(collectionKey, {
|
|
60
|
+
seenTokenIds: new Set<string>(),
|
|
61
|
+
marketplaceFinished: false,
|
|
62
|
+
indexerTokensFetched: false,
|
|
63
|
+
indexerTokenBalances: new Map(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// biome-ignore lint/style/noNonNullAssertion: guaranteed to exist, by the above init
|
|
68
|
+
return stateByCollection.get(collectionKey)!;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function collectibleFromTokenBalance(
|
|
72
|
+
token: TokenBalance,
|
|
73
|
+
): CollectibleWithBalance {
|
|
74
|
+
return {
|
|
75
|
+
metadata: {
|
|
76
|
+
tokenId: token.tokenID ?? '',
|
|
77
|
+
attributes: token.tokenMetadata?.attributes ?? [],
|
|
78
|
+
image: token.tokenMetadata?.image,
|
|
79
|
+
name: token.tokenMetadata?.name ?? '',
|
|
80
|
+
description: token.tokenMetadata?.description,
|
|
81
|
+
video: token.tokenMetadata?.video,
|
|
82
|
+
audio: token.tokenMetadata?.audio,
|
|
83
|
+
},
|
|
84
|
+
contractInfo: token.contractInfo,
|
|
85
|
+
contractType: token.contractType as
|
|
86
|
+
| ContractType.ERC1155
|
|
87
|
+
| ContractType.ERC721,
|
|
88
|
+
balance: token.balance,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function fetchAllIndexerTokens(
|
|
93
|
+
chainId: number,
|
|
94
|
+
accountAddress: Address,
|
|
95
|
+
collectionAddress: Address,
|
|
96
|
+
config: SdkConfig,
|
|
97
|
+
state: InventoryState,
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
const indexerClient = getIndexerClient(chainId, config);
|
|
100
|
+
|
|
101
|
+
let page: IndexerPage = {
|
|
102
|
+
pageSize: 50,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
while (true) {
|
|
106
|
+
const { balances, page: nextPage } = await indexerClient.getTokenBalances({
|
|
107
|
+
accountAddress,
|
|
108
|
+
contractAddress: collectionAddress,
|
|
109
|
+
includeMetadata: true,
|
|
110
|
+
page: page,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
for (const balance of balances) {
|
|
114
|
+
if (balance.tokenID) {
|
|
115
|
+
state.indexerTokenBalances.set(
|
|
116
|
+
balance.tokenID,
|
|
117
|
+
collectibleFromTokenBalance(balance),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!nextPage.more) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
page = nextPage;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
state.indexerTokensFetched = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Process indexer tokens that we haven't seen yet
|
|
132
|
+
function processRemainingIndexerTokens(
|
|
133
|
+
state: InventoryState,
|
|
134
|
+
page: Page,
|
|
135
|
+
): CollectiblesResponse {
|
|
136
|
+
const allTokens = Array.from(state.indexerTokenBalances.values());
|
|
137
|
+
|
|
138
|
+
// Filter out tokens that we've already seen
|
|
139
|
+
const newTokens = allTokens.filter(
|
|
140
|
+
(token) => !state.seenTokenIds.has(token.metadata.tokenId),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Calculate pagination
|
|
144
|
+
const startIndex = (page.page - 1) * page.pageSize;
|
|
145
|
+
const endIndex = startIndex + page.pageSize;
|
|
146
|
+
const paginatedTokens = newTokens.slice(startIndex, endIndex);
|
|
147
|
+
|
|
148
|
+
// Add new token IDs to the set
|
|
149
|
+
for (const token of paginatedTokens) {
|
|
150
|
+
state.seenTokenIds.add(token.metadata.tokenId);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
collectibles: paginatedTokens,
|
|
155
|
+
page: {
|
|
156
|
+
page: page.page,
|
|
157
|
+
pageSize: page.pageSize,
|
|
158
|
+
more: endIndex < newTokens.length,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function processMarketplaceCollectibles(
|
|
164
|
+
collectibles: CollectibleOrder[],
|
|
165
|
+
state: InventoryState,
|
|
166
|
+
page: Page,
|
|
167
|
+
): {
|
|
168
|
+
enrichedCollectibles: CollectibleWithBalance[];
|
|
169
|
+
missingTokens: CollectibleWithBalance[];
|
|
170
|
+
} {
|
|
171
|
+
// Add new token IDs to the set
|
|
172
|
+
for (const c of collectibles) {
|
|
173
|
+
state.seenTokenIds.add(c.metadata.tokenId);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Enrich marketplace collectibles with balance data from indexer
|
|
177
|
+
const enrichedCollectibles = collectibles.map((c: CollectibleOrder) => {
|
|
178
|
+
const tokenId = c.metadata.tokenId;
|
|
179
|
+
const indexerData = state.indexerTokenBalances.get(tokenId);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
...c,
|
|
183
|
+
balance: indexerData?.balance,
|
|
184
|
+
contractInfo: indexerData?.contractInfo,
|
|
185
|
+
contractType: indexerData?.contractType,
|
|
186
|
+
} as CollectibleWithBalance;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Check for missing tokens in the marketplace data
|
|
190
|
+
const marketplaceTokenIds = new Set(
|
|
191
|
+
enrichedCollectibles.map((c) => c.metadata.tokenId),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const missingTokens = Array.from(state.indexerTokenBalances.entries())
|
|
195
|
+
.filter(([tokenId]) => !marketplaceTokenIds.has(tokenId))
|
|
196
|
+
.map(([_, balance]) => balance)
|
|
197
|
+
.slice(0, page.pageSize);
|
|
198
|
+
|
|
199
|
+
return { enrichedCollectibles, missingTokens };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function fetchInventory(
|
|
203
|
+
args: GetInventoryArgs,
|
|
204
|
+
config: SdkConfig,
|
|
205
|
+
page: Page,
|
|
206
|
+
): Promise<CollectiblesResponse> {
|
|
207
|
+
const { accountAddress, collectionAddress, chainId, isLaos721 } = args;
|
|
208
|
+
const collectionKey = getCollectionKey(args);
|
|
209
|
+
const state = getOrInitState(collectionKey);
|
|
210
|
+
|
|
211
|
+
// On first run, fetch all pages from the indexer
|
|
212
|
+
if (!state.indexerTokensFetched) {
|
|
213
|
+
await fetchAllIndexerTokens(
|
|
214
|
+
chainId,
|
|
215
|
+
accountAddress,
|
|
216
|
+
collectionAddress,
|
|
217
|
+
config,
|
|
218
|
+
state,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If marketplace API has no more results, use the indexer data
|
|
223
|
+
if (state.marketplaceFinished) {
|
|
224
|
+
return processRemainingIndexerTokens(state, page);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Fetch collectibles from marketplace API
|
|
228
|
+
const collectibles = await fetchCollectibles(
|
|
229
|
+
{
|
|
230
|
+
chainId,
|
|
231
|
+
collectionAddress,
|
|
232
|
+
filter: {
|
|
233
|
+
inAccounts: [accountAddress],
|
|
234
|
+
includeEmpty: true,
|
|
235
|
+
},
|
|
236
|
+
side: OrderSide.listing,
|
|
237
|
+
isLaos721,
|
|
238
|
+
},
|
|
239
|
+
config,
|
|
240
|
+
page,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Process the collectibles and find missing tokens
|
|
244
|
+
const { enrichedCollectibles, missingTokens } =
|
|
245
|
+
processMarketplaceCollectibles(collectibles.collectibles, state, page);
|
|
246
|
+
|
|
247
|
+
// If there are no more results from the marketplace API
|
|
248
|
+
if (!collectibles.page?.more) {
|
|
249
|
+
// Mark marketplace as finished and start using indexer data on next call
|
|
250
|
+
state.marketplaceFinished = true;
|
|
251
|
+
return {
|
|
252
|
+
collectibles: [...enrichedCollectibles, ...missingTokens],
|
|
253
|
+
page: {
|
|
254
|
+
page: collectibles.page?.page ?? page.page,
|
|
255
|
+
pageSize: collectibles.page?.pageSize ?? page.pageSize,
|
|
256
|
+
more: missingTokens.length > 0,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
collectibles: enrichedCollectibles,
|
|
263
|
+
page: {
|
|
264
|
+
page: collectibles.page?.page ?? page.page,
|
|
265
|
+
pageSize: collectibles.page?.pageSize ?? page.pageSize,
|
|
266
|
+
more: Boolean(collectibles.page?.more),
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function inventoryOptions(args: UseInventoryArgs, config: SdkConfig) {
|
|
272
|
+
const collectionKey = getCollectionKey(args);
|
|
273
|
+
const enabledQuery = args.query?.enabled ?? true;
|
|
274
|
+
const enabled =
|
|
275
|
+
enabledQuery && !!args.accountAddress && !!args.collectionAddress;
|
|
276
|
+
|
|
277
|
+
return infiniteQueryOptions({
|
|
278
|
+
queryKey: [
|
|
279
|
+
'inventory',
|
|
280
|
+
args.accountAddress,
|
|
281
|
+
args.collectionAddress,
|
|
282
|
+
args.chainId,
|
|
283
|
+
],
|
|
284
|
+
queryFn: ({ pageParam }) =>
|
|
285
|
+
fetchInventory(
|
|
286
|
+
{
|
|
287
|
+
...args,
|
|
288
|
+
isLaos721: args.isLaos721 ?? false,
|
|
289
|
+
},
|
|
290
|
+
config,
|
|
291
|
+
pageParam,
|
|
292
|
+
),
|
|
293
|
+
initialPageParam: { page: 1, pageSize: 30 } as Page,
|
|
294
|
+
getNextPageParam: (lastPage) =>
|
|
295
|
+
lastPage.page?.more ? lastPage.page : undefined,
|
|
296
|
+
enabled,
|
|
297
|
+
meta: {
|
|
298
|
+
onInvalidate: () => {
|
|
299
|
+
stateByCollection.delete(collectionKey);
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
}
|
|
@@ -24,14 +24,6 @@ export type UseListBalancesArgs = {
|
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* Fetches a list of token balances with pagination support
|
|
29
|
-
*
|
|
30
|
-
* @param args - Arguments for the API call
|
|
31
|
-
* @param config - SDK configuration
|
|
32
|
-
* @param page - Page parameters for pagination
|
|
33
|
-
* @returns The token balances data
|
|
34
|
-
*/
|
|
35
27
|
export async function fetchBalances(
|
|
36
28
|
args: UseListBalancesArgs,
|
|
37
29
|
config: SdkConfig,
|
|
@@ -48,6 +40,7 @@ export async function fetchBalances(
|
|
|
48
40
|
body: JSON.stringify({
|
|
49
41
|
chainId: args.chainId.toString(),
|
|
50
42
|
accountAddress: args.accountAddress,
|
|
43
|
+
contractAddress: args.contractAddress,
|
|
51
44
|
includeMetadata: args.includeMetadata ?? true,
|
|
52
45
|
page: {
|
|
53
46
|
sort: [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { infiniteQueryOptions } from '@tanstack/react-query';
|
|
2
|
-
import type { Hex } from 'viem';
|
|
2
|
+
import type { Address, Hex } from 'viem';
|
|
3
3
|
import type { Page, SdkConfig } from '../../types';
|
|
4
4
|
import type {
|
|
5
5
|
CollectibleOrder,
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
ListCollectiblesReturn,
|
|
9
9
|
} from '../_internal';
|
|
10
10
|
import { OrderSide, collectableKeys, getMarketplaceClient } from '../_internal';
|
|
11
|
-
import { fetchBalances } from './listBalances';
|
|
11
|
+
import { type UseListBalancesArgs, fetchBalances } from './listBalances';
|
|
12
12
|
export type UseListCollectiblesArgs = {
|
|
13
13
|
collectionAddress: Hex;
|
|
14
14
|
chainId: number;
|
|
@@ -43,7 +43,16 @@ export async function fetchCollectibles(
|
|
|
43
43
|
|
|
44
44
|
if (args.isLaos721 && args.side === OrderSide.listing) {
|
|
45
45
|
try {
|
|
46
|
-
const
|
|
46
|
+
const fetchBalancesArgs = {
|
|
47
|
+
chainId: args.chainId,
|
|
48
|
+
accountAddress: args.filter?.inAccounts?.[0] as Address,
|
|
49
|
+
contractAddress: args.collectionAddress,
|
|
50
|
+
page: page,
|
|
51
|
+
includeMetadata: true,
|
|
52
|
+
isLaos721: true,
|
|
53
|
+
} satisfies UseListBalancesArgs;
|
|
54
|
+
|
|
55
|
+
const balances = await fetchBalances(fetchBalancesArgs, config, page);
|
|
47
56
|
const collectibles: CollectibleOrder[] = balances.balances.map(
|
|
48
57
|
(balance) => {
|
|
49
58
|
if (!balance.tokenMetadata)
|
|
@@ -1,137 +1,107 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { cleanup, render, renderHook, screen, waitFor } from '@test';
|
|
2
|
+
import { TEST_COLLECTIBLE } from '@test/const';
|
|
3
|
+
import { createMockWallet } from '@test/mocks/wallet';
|
|
4
4
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import
|
|
5
|
+
import { useCreateListingModal } from '..';
|
|
6
|
+
import { StepType, WalletKind } from '../../../../_internal';
|
|
7
|
+
import { createMockStep } from '../../../../_internal/api/__mocks__/marketplace.msw';
|
|
8
|
+
import * as walletModule from '../../../../_internal/wallet/useWallet';
|
|
6
9
|
import { CreateListingModal } from '../Modal';
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
vi.mock(import('../../../../hooks'), async (importOriginal) => {
|
|
10
|
-
const actual = await importOriginal();
|
|
11
|
-
return {
|
|
12
|
-
...actual,
|
|
13
|
-
useCollectible: vi.fn(actual.useCollectible),
|
|
14
|
-
useCollection: vi.fn(actual.useCollection),
|
|
15
|
-
useCurrencies: vi.fn(actual.useCurrencies),
|
|
16
|
-
useMarketplaceConfig: vi.fn(actual.useMarketplaceConfig),
|
|
17
|
-
useLowestListing: vi.fn(actual.useLowestListing),
|
|
18
|
-
};
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
vi.mock('@0xsequence/kit', () => ({
|
|
22
|
-
useWaasFeeOptions: vi.fn(),
|
|
23
|
-
}));
|
|
10
|
+
import * as useGetTokenApprovalDataModule from '../hooks/useGetTokenApproval';
|
|
24
11
|
|
|
25
12
|
const defaultArgs = {
|
|
26
|
-
collectionAddress:
|
|
27
|
-
chainId:
|
|
28
|
-
collectibleId:
|
|
13
|
+
collectionAddress: TEST_COLLECTIBLE.collectionAddress,
|
|
14
|
+
chainId: TEST_COLLECTIBLE.chainId,
|
|
15
|
+
collectibleId: TEST_COLLECTIBLE.collectibleId,
|
|
29
16
|
};
|
|
30
17
|
|
|
31
|
-
describe
|
|
18
|
+
describe('MakeOfferModal', () => {
|
|
19
|
+
const mockWallet = createMockWallet();
|
|
20
|
+
|
|
32
21
|
beforeEach(() => {
|
|
33
22
|
cleanup();
|
|
34
23
|
// Reset all mocks
|
|
35
24
|
vi.clearAllMocks();
|
|
36
25
|
vi.resetAllMocks();
|
|
37
|
-
vi.
|
|
26
|
+
vi.restoreAllMocks();
|
|
38
27
|
});
|
|
39
28
|
|
|
40
|
-
it('should
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
data: undefined,
|
|
29
|
+
it('should show main button if there is no approval step', async () => {
|
|
30
|
+
// Mock sequence wallet
|
|
31
|
+
const sequenceWallet = {
|
|
32
|
+
...mockWallet,
|
|
33
|
+
walletKind: WalletKind.sequence,
|
|
34
|
+
};
|
|
35
|
+
vi.spyOn(walletModule, 'useWallet').mockReturnValue({
|
|
36
|
+
wallet: sequenceWallet,
|
|
49
37
|
isLoading: false,
|
|
50
|
-
isError:
|
|
38
|
+
isError: false,
|
|
51
39
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
it('should render main form when data is loaded', async () => {
|
|
63
|
-
createListingModal$.open(defaultArgs);
|
|
64
|
-
render(<CreateListingModal />);
|
|
65
|
-
|
|
66
|
-
// Check for the collection name in the token preview
|
|
67
|
-
expect(await screen.findByText('Mock Collection')).toBeInTheDocument();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should reset store values when modal is closed and reopened', () => {
|
|
71
|
-
// Open modal first time
|
|
72
|
-
createListingModal$.open(defaultArgs);
|
|
73
|
-
|
|
74
|
-
// Set some values in the store
|
|
75
|
-
createListingModal$.listingPrice.amountRaw.set('1000000000000000000');
|
|
76
|
-
createListingModal$.quantity.set('5');
|
|
77
|
-
|
|
78
|
-
// Close modal
|
|
79
|
-
createListingModal$.close();
|
|
80
|
-
|
|
81
|
-
// Verify store is reset
|
|
82
|
-
expect(createListingModal$.listingPrice.amountRaw.get()).toBe('0');
|
|
83
|
-
expect(createListingModal$.quantity.get()).toBe('1');
|
|
84
|
-
|
|
85
|
-
// Reopen modal
|
|
86
|
-
createListingModal$.open({
|
|
87
|
-
collectionAddress: '0x456',
|
|
88
|
-
chainId: 1,
|
|
89
|
-
collectibleId: '2',
|
|
40
|
+
vi.spyOn(
|
|
41
|
+
useGetTokenApprovalDataModule,
|
|
42
|
+
'useGetTokenApprovalData',
|
|
43
|
+
).mockReturnValue({
|
|
44
|
+
data: {
|
|
45
|
+
step: null,
|
|
46
|
+
},
|
|
47
|
+
isLoading: false,
|
|
48
|
+
isSuccess: true,
|
|
90
49
|
});
|
|
91
50
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should update state based on price input', async () => {
|
|
98
|
-
createListingModal$.open(defaultArgs);
|
|
51
|
+
// Render the modal
|
|
52
|
+
const { result } = renderHook(() => useCreateListingModal());
|
|
53
|
+
result.current.show(defaultArgs);
|
|
99
54
|
|
|
100
55
|
render(<CreateListingModal />);
|
|
101
56
|
|
|
102
|
-
//
|
|
103
|
-
expect(createListingModal$.listingPrice.amountRaw.get()).toBe('0');
|
|
104
|
-
|
|
105
|
-
// Find and interact with price input using id
|
|
106
|
-
const priceInput = await screen.findByRole('textbox', {
|
|
107
|
-
name: /enter price/i,
|
|
108
|
-
});
|
|
109
|
-
expect(priceInput).toBeInTheDocument();
|
|
110
|
-
|
|
111
|
-
fireEvent.change(priceInput, { target: { value: '1.5' } });
|
|
112
|
-
|
|
113
|
-
// Wait for the state to update and verify it's not 0 anymore
|
|
57
|
+
// Wait for the component to update
|
|
114
58
|
await waitFor(() => {
|
|
115
|
-
|
|
59
|
+
// The Approve TOKEN button should not exist
|
|
60
|
+
expect(screen.queryByText('Approve TOKEN')).toBeNull();
|
|
61
|
+
|
|
62
|
+
// The List item for sale button should exist
|
|
63
|
+
expect(
|
|
64
|
+
screen.getByRole('button', { name: 'List item for sale' }),
|
|
65
|
+
).toBeDefined();
|
|
116
66
|
});
|
|
117
67
|
});
|
|
118
68
|
|
|
119
|
-
it('should show
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
69
|
+
it('(non-sequence wallets) should show approve token button if there is an approval step, disable main button', async () => {
|
|
70
|
+
const nonSequenceWallet = {
|
|
71
|
+
...mockWallet,
|
|
72
|
+
walletKind: 'unknown' as WalletKind,
|
|
73
|
+
};
|
|
74
|
+
vi.spyOn(walletModule, 'useWallet').mockReturnValue({
|
|
75
|
+
wallet: nonSequenceWallet,
|
|
123
76
|
isLoading: false,
|
|
124
77
|
isError: false,
|
|
125
78
|
});
|
|
79
|
+
vi.spyOn(
|
|
80
|
+
useGetTokenApprovalDataModule,
|
|
81
|
+
'useGetTokenApprovalData',
|
|
82
|
+
).mockReturnValue({
|
|
83
|
+
data: {
|
|
84
|
+
step: createMockStep(StepType.tokenApproval),
|
|
85
|
+
},
|
|
86
|
+
isLoading: false,
|
|
87
|
+
isSuccess: true,
|
|
88
|
+
});
|
|
126
89
|
|
|
127
|
-
|
|
90
|
+
// Render the modal
|
|
91
|
+
const { result } = renderHook(() => useCreateListingModal());
|
|
92
|
+
result.current.show(defaultArgs);
|
|
128
93
|
|
|
129
94
|
render(<CreateListingModal />);
|
|
130
95
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
expect(screen.getByText('Approve TOKEN')).toBeDefined();
|
|
98
|
+
|
|
99
|
+
expect(
|
|
100
|
+
screen.getByRole('button', { name: 'List item for sale' }),
|
|
101
|
+
).toBeDefined();
|
|
102
|
+
expect(
|
|
103
|
+
screen.getByRole('button', { name: 'List item for sale' }),
|
|
104
|
+
).toBeDisabled();
|
|
105
|
+
});
|
|
136
106
|
});
|
|
137
107
|
});
|