@0xsequence/marketplace-sdk 0.8.3 → 0.8.5
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 +16 -1
- package/dist/{chunk-25CAMYCG.js → chunk-BB2PTJHI.js} +22 -20
- package/dist/chunk-BB2PTJHI.js.map +1 -0
- package/dist/{chunk-5ATGT5S4.js → chunk-EZFCQZHU.js} +14 -6
- package/dist/chunk-EZFCQZHU.js.map +1 -0
- package/dist/{chunk-DFI52A2E.js → chunk-KCLMSSPS.js} +364 -242
- package/dist/chunk-KCLMSSPS.js.map +1 -0
- package/dist/{chunk-XUNDLCEH.js → chunk-LDZZUYG7.js} +2 -2
- package/dist/{chunk-QTV77W42.js → chunk-SFSFIGHM.js} +45 -35
- package/dist/chunk-SFSFIGHM.js.map +1 -0
- package/dist/{chunk-FSJKN4YN.js → chunk-ZSCZLHKX.js} +194 -2
- package/dist/chunk-ZSCZLHKX.js.map +1 -0
- package/dist/{chunk-FH4TZRDV.js → chunk-ZVTG6US2.js} +2 -2
- package/dist/index.css +4 -4
- package/dist/index.css.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{lowestListing-DUZ_nYml.d.ts → lowestListing-W7P4EkC3.d.ts} +34 -11
- package/dist/react/_internal/databeat/index.js +5 -5
- package/dist/react/_internal/index.d.ts +1 -1
- package/dist/react/_internal/index.js +3 -1
- package/dist/react/_internal/wagmi/index.d.ts +3 -2
- package/dist/react/_internal/wagmi/index.js +3 -1
- package/dist/react/hooks/index.d.ts +8 -5
- package/dist/react/hooks/index.js +6 -4
- package/dist/react/hooks/options/index.js +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +9 -7
- package/dist/react/queries/index.d.ts +1 -1
- package/dist/react/queries/index.js +6 -2
- package/dist/react/ssr/index.js +1 -1
- package/dist/react/ui/components/collectible-card/index.d.ts +3 -2
- package/dist/react/ui/components/collectible-card/index.js +7 -7
- package/dist/react/ui/icons/index.js +1 -1
- package/dist/react/ui/index.js +7 -7
- package/dist/react/ui/modals/_internal/components/actionModal/index.js +5 -5
- package/dist/types/index.js +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +19 -19
- package/src/react/_internal/api/__mocks__/marketplace.msw.ts +35 -21
- package/src/react/_internal/wagmi/__tests__/create-config.test.ts +1 -11
- package/src/react/_internal/wagmi/get-connectors.ts +27 -24
- 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/components/_internals/action-button/__tests__/ActionButtonBody.test.tsx +27 -94
- package/src/react/ui/components/_internals/action-button/__tests__/NonOwnerActions.test.tsx +59 -0
- package/src/react/ui/components/_internals/action-button/__tests__/OwnerActions.test.tsx +73 -0
- package/src/react/ui/components/_internals/action-button/__tests__/useActionButtonLogic.test.tsx +77 -0
- package/src/react/ui/components/_internals/action-button/components/ActionButtonBody.tsx +3 -2
- package/src/react/ui/components/_internals/action-button/hooks/useActionButtonLogic.ts +4 -3
- package/src/react/ui/components/collectible-card/CollectibleAsset.tsx +1 -0
- package/src/react/ui/components/collectible-card/CollectibleCard.tsx +18 -12
- package/src/react/ui/components/collectible-card/__tests__/CollectibleAsset.test.tsx +200 -0
- package/src/react/ui/components/collectible-card/__tests__/CollectibleCard.test.tsx +92 -123
- package/src/react/ui/components/collectible-card/__tests__/Footer.test.tsx +136 -0
- package/src/react/ui/modals/BuyModal/__tests__/Modal.test.tsx +2 -8
- 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/SellModal/__tests__/Modal.test.tsx +72 -135
- 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/selectWaasFeeOptions/__tests__/ActionButtons.test.tsx +72 -0
- package/src/react/ui/modals/_internal/components/selectWaasFeeOptions/__tests__/BalanceIndicator.test.tsx +50 -0
- package/src/react/ui/modals/_internal/components/selectWaasFeeOptions/__tests__/SelectWaasFeeOptions.test.tsx +193 -0
- package/src/react/ui/modals/_internal/components/switchChainModal/index.tsx +2 -2
- package/test/const.ts +24 -0
- package/test/test-utils.tsx +85 -47
- 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-25CAMYCG.js.map +0 -1
- 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/components/_internals/action-button/__tests__/ActionButton.test.tsx +0 -107
- package/src/react/ui/modals/_internal/components/priceInput/__tests__/index.test.tsx +0 -164
- /package/dist/{chunk-XUNDLCEH.js.map → chunk-LDZZUYG7.js.map} +0 -0
- /package/dist/{chunk-FH4TZRDV.js.map → chunk-ZVTG6US2.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)
|
package/src/react/ui/components/_internals/action-button/__tests__/ActionButtonBody.test.tsx
CHANGED
|
@@ -1,133 +1,66 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import * as kit from '@0xsequence/connect';
|
|
4
3
|
import { fireEvent, render, screen } from '@test';
|
|
4
|
+
import { createMockWallet } from '@test/mocks/wallet';
|
|
5
5
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
-
import * as
|
|
6
|
+
import * as walletModule from '../../../../../_internal/wallet/useWallet';
|
|
7
7
|
import { ActionButtonBody } from '../components/ActionButtonBody';
|
|
8
8
|
import { setPendingAction } from '../store';
|
|
9
9
|
import { CollectibleCardAction } from '../types';
|
|
10
10
|
|
|
11
|
-
// Mock the hooks
|
|
12
|
-
vi.mock('wagmi', async () => {
|
|
13
|
-
const actual = await vi.importActual('wagmi');
|
|
14
|
-
return {
|
|
15
|
-
...actual,
|
|
16
|
-
useAccount: vi.fn(),
|
|
17
|
-
};
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
vi.mock('@0xsequence/connect', () => ({
|
|
21
|
-
useOpenConnectModal: vi.fn(),
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
// Mock the store
|
|
25
11
|
vi.mock('../store', () => ({
|
|
26
12
|
setPendingAction: vi.fn(),
|
|
27
13
|
}));
|
|
28
14
|
|
|
29
|
-
describe
|
|
15
|
+
describe('ActionButtonBody', () => {
|
|
16
|
+
const mockOnClick = vi.fn();
|
|
30
17
|
const defaultProps = {
|
|
31
18
|
label: 'Buy now' as const,
|
|
32
|
-
tokenId: '
|
|
33
|
-
onClick:
|
|
19
|
+
tokenId: '123',
|
|
20
|
+
onClick: mockOnClick,
|
|
34
21
|
action: CollectibleCardAction.BUY as CollectibleCardAction.BUY,
|
|
35
|
-
disabled: false,
|
|
36
|
-
loading: false,
|
|
37
22
|
};
|
|
38
23
|
|
|
39
24
|
beforeEach(() => {
|
|
40
25
|
vi.clearAllMocks();
|
|
41
|
-
|
|
42
|
-
vi.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
26
|
+
|
|
27
|
+
vi.spyOn(walletModule, 'useWallet').mockReturnValue({
|
|
28
|
+
wallet: createMockWallet(),
|
|
29
|
+
isLoading: false,
|
|
30
|
+
isError: false,
|
|
46
31
|
});
|
|
47
32
|
});
|
|
48
33
|
|
|
49
|
-
it('
|
|
34
|
+
it('executes onClick directly when user is connected', () => {
|
|
50
35
|
render(<ActionButtonBody {...defaultProps} />);
|
|
51
|
-
expect(screen.getByText('Buy now')).toBeInTheDocument();
|
|
52
|
-
});
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
vi.mocked(wagmi.useAccount).mockReturnValue({ address: '0x123' });
|
|
57
|
-
render(<ActionButtonBody {...defaultProps} />);
|
|
37
|
+
const button = screen.getByRole('button', { name: defaultProps.label });
|
|
38
|
+
fireEvent.click(button);
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
expect(defaultProps.onClick).toHaveBeenCalled();
|
|
40
|
+
expect(mockOnClick).toHaveBeenCalledTimes(1);
|
|
61
41
|
expect(setPendingAction).not.toHaveBeenCalled();
|
|
62
42
|
});
|
|
63
43
|
|
|
64
44
|
it('sets pending action and opens connect modal when user is not connected', () => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
45
|
+
vi.spyOn(walletModule, 'useWallet').mockReturnValue({
|
|
46
|
+
wallet: {
|
|
47
|
+
...createMockWallet(),
|
|
48
|
+
// @ts-expect-error - address is undefined for testing
|
|
49
|
+
address: undefined,
|
|
50
|
+
},
|
|
51
|
+
isLoading: false,
|
|
52
|
+
isError: false,
|
|
69
53
|
});
|
|
70
|
-
|
|
71
54
|
render(<ActionButtonBody {...defaultProps} />);
|
|
72
55
|
|
|
73
|
-
|
|
56
|
+
const button = screen.getByRole('button', { name: defaultProps.label });
|
|
57
|
+
fireEvent.click(button);
|
|
74
58
|
|
|
59
|
+
expect(mockOnClick).not.toHaveBeenCalled();
|
|
75
60
|
expect(setPendingAction).toHaveBeenCalledWith(
|
|
76
|
-
|
|
61
|
+
defaultProps.action,
|
|
77
62
|
defaultProps.onClick,
|
|
78
63
|
defaultProps.tokenId,
|
|
79
64
|
);
|
|
80
|
-
expect(setOpenConnectModal).toHaveBeenCalledWith(true);
|
|
81
|
-
expect(defaultProps.onClick).not.toHaveBeenCalled();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('prevents event propagation on click', () => {
|
|
85
|
-
const preventDefault = vi.fn();
|
|
86
|
-
const stopPropagation = vi.fn();
|
|
87
|
-
|
|
88
|
-
render(<ActionButtonBody {...defaultProps} />);
|
|
89
|
-
|
|
90
|
-
const button = screen.getByRole('button');
|
|
91
|
-
const mockEvent = new MouseEvent('click', {
|
|
92
|
-
bubbles: true,
|
|
93
|
-
cancelable: true,
|
|
94
|
-
});
|
|
95
|
-
Object.defineProperties(mockEvent, {
|
|
96
|
-
preventDefault: { value: preventDefault },
|
|
97
|
-
stopPropagation: { value: stopPropagation },
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
fireEvent(button, mockEvent);
|
|
101
|
-
|
|
102
|
-
expect(preventDefault).toHaveBeenCalled();
|
|
103
|
-
expect(stopPropagation).toHaveBeenCalled();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('renders with custom icon when provided', () => {
|
|
107
|
-
const TestIcon = () => <span data-testid="test-icon">Icon</span>;
|
|
108
|
-
render(<ActionButtonBody {...defaultProps} icon={TestIcon} />);
|
|
109
|
-
expect(screen.getByTestId('test-icon')).toBeInTheDocument();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('renders with different labels', () => {
|
|
113
|
-
const labels = [
|
|
114
|
-
'Sell',
|
|
115
|
-
'Make an offer',
|
|
116
|
-
'Create listing',
|
|
117
|
-
'Transfer',
|
|
118
|
-
] as const;
|
|
119
|
-
|
|
120
|
-
for (const label of labels) {
|
|
121
|
-
render(<ActionButtonBody {...defaultProps} label={label} />);
|
|
122
|
-
expect(screen.getByText(label)).toBeInTheDocument();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('calls onClick when clicked and not disabled', () => {
|
|
127
|
-
// @ts-expect-error
|
|
128
|
-
vi.mocked(wagmi.useAccount).mockReturnValue({ address: '0x123' });
|
|
129
|
-
render(<ActionButtonBody {...defaultProps} />);
|
|
130
|
-
fireEvent.click(screen.getByRole('button'));
|
|
131
|
-
expect(defaultProps.onClick).toHaveBeenCalled();
|
|
132
65
|
});
|
|
133
66
|
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@test';
|
|
4
|
+
import { createMockWallet } from '@test/mocks/wallet';
|
|
5
|
+
import { zeroAddress } from 'viem';
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { OrderSide } from '../../../../../_internal';
|
|
8
|
+
import { mockOrder } from '../../../../../_internal/api/__mocks__/marketplace.msw';
|
|
9
|
+
import * as walletModule from '../../../../../_internal/wallet/useWallet';
|
|
10
|
+
import { NonOwnerActions } from '../components/NonOwnerActions';
|
|
11
|
+
import { CollectibleCardAction } from '../types';
|
|
12
|
+
|
|
13
|
+
describe('NonOwnerActions', () => {
|
|
14
|
+
const defaultProps = {
|
|
15
|
+
action: CollectibleCardAction.BUY,
|
|
16
|
+
tokenId: '1',
|
|
17
|
+
collectionAddress: zeroAddress,
|
|
18
|
+
chainId: 1,
|
|
19
|
+
lowestListing: { ...mockOrder, side: OrderSide.listing },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
vi.resetAllMocks();
|
|
25
|
+
vi.restoreAllMocks();
|
|
26
|
+
|
|
27
|
+
vi.spyOn(walletModule, 'useWallet').mockReturnValue({
|
|
28
|
+
wallet: createMockWallet(),
|
|
29
|
+
isLoading: false,
|
|
30
|
+
isError: false,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders Buy now button for BUY action', () => {
|
|
35
|
+
render(<NonOwnerActions {...defaultProps} />);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders Make an offer button for OFFER action', () => {
|
|
39
|
+
render(
|
|
40
|
+
<NonOwnerActions
|
|
41
|
+
{...defaultProps}
|
|
42
|
+
action={CollectibleCardAction.OFFER}
|
|
43
|
+
/>,
|
|
44
|
+
);
|
|
45
|
+
expect(screen.getByText('Make an offer')).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('returns null for unsupported actions', () => {
|
|
49
|
+
const props = {
|
|
50
|
+
...defaultProps,
|
|
51
|
+
action: CollectibleCardAction.LIST,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const { container } = render(
|
|
55
|
+
<NonOwnerActions {...(props as Parameters<typeof NonOwnerActions>[0])} />,
|
|
56
|
+
);
|
|
57
|
+
expect(container).toBeEmptyDOMElement();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@test';
|
|
4
|
+
import { createMockWallet } from '@test/mocks/wallet';
|
|
5
|
+
import { zeroAddress } from 'viem';
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { OrderSide } from '../../../../../_internal';
|
|
8
|
+
import { mockOrder } from '../../../../../_internal/api/__mocks__/marketplace.msw';
|
|
9
|
+
import * as walletModule from '../../../../../_internal/wallet/useWallet';
|
|
10
|
+
import { OwnerActions } from '../components/OwnerActions';
|
|
11
|
+
import { CollectibleCardAction } from '../types';
|
|
12
|
+
|
|
13
|
+
describe('OwnerActions', () => {
|
|
14
|
+
const defaultProps = {
|
|
15
|
+
action: CollectibleCardAction.BUY,
|
|
16
|
+
tokenId: '1',
|
|
17
|
+
collectionAddress: zeroAddress,
|
|
18
|
+
chainId: 1,
|
|
19
|
+
lowestListing: { ...mockOrder, side: OrderSide.listing },
|
|
20
|
+
highestOffer: { ...mockOrder, side: OrderSide.offer },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
vi.resetAllMocks();
|
|
26
|
+
vi.restoreAllMocks();
|
|
27
|
+
|
|
28
|
+
vi.spyOn(walletModule, 'useWallet').mockReturnValue({
|
|
29
|
+
wallet: createMockWallet(),
|
|
30
|
+
isLoading: false,
|
|
31
|
+
isError: false,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders Create listing button for LIST action', () => {
|
|
36
|
+
render(
|
|
37
|
+
<OwnerActions {...defaultProps} action={CollectibleCardAction.LIST} />,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(screen.getByText('Create listing')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders Transfer button for TRANSFER action', () => {
|
|
44
|
+
render(
|
|
45
|
+
<OwnerActions
|
|
46
|
+
{...defaultProps}
|
|
47
|
+
action={CollectibleCardAction.TRANSFER}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(screen.getByText('Transfer')).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders Sell button for SELL action', () => {
|
|
55
|
+
render(
|
|
56
|
+
<OwnerActions {...defaultProps} action={CollectibleCardAction.SELL} />,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(screen.getByText('Sell')).toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('returns null for unsupported actions', () => {
|
|
63
|
+
const props = {
|
|
64
|
+
...defaultProps,
|
|
65
|
+
action: CollectibleCardAction.BUY,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const { container } = render(
|
|
69
|
+
<OwnerActions {...(props as Parameters<typeof OwnerActions>[0])} />,
|
|
70
|
+
);
|
|
71
|
+
expect(container).toBeEmptyDOMElement();
|
|
72
|
+
});
|
|
73
|
+
});
|