@0xsequence/marketplace-sdk 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/{chunk-IOTKCWOB.js → chunk-2AMLJ2TA.js} +2904 -2414
  2. package/dist/chunk-2AMLJ2TA.js.map +1 -0
  3. package/dist/{chunk-RZSZNVEH.js → chunk-2OJB35FS.js} +7 -7
  4. package/dist/{chunk-RZSZNVEH.js.map → chunk-2OJB35FS.js.map} +1 -1
  5. package/dist/{chunk-KNX2LER4.js → chunk-4VS5NKDD.js} +2 -2
  6. package/dist/{chunk-GSDUAHL3.js → chunk-6WB4GCCJ.js} +1 -1
  7. package/dist/chunk-6WB4GCCJ.js.map +1 -0
  8. package/dist/{chunk-T5T6JNB2.js → chunk-ATDCYXXV.js} +49 -49
  9. package/dist/chunk-ATDCYXXV.js.map +1 -0
  10. package/dist/{chunk-LSMQVX77.js → chunk-CP2IVRMX.js} +2 -2
  11. package/dist/{chunk-EVRILXOH.js → chunk-DNDPYQKV.js} +56 -36
  12. package/dist/chunk-DNDPYQKV.js.map +1 -0
  13. package/dist/{chunk-7NJETFMF.js → chunk-FI723DGL.js} +3 -3
  14. package/dist/{chunk-7NJETFMF.js.map → chunk-FI723DGL.js.map} +1 -1
  15. package/dist/{chunk-QMO2CUNM.js → chunk-IQXJZBMR.js} +2 -2
  16. package/dist/{chunk-4ESPWOBV.js → chunk-NC4KGXXE.js} +3 -3
  17. package/dist/{chunk-4ESPWOBV.js.map → chunk-NC4KGXXE.js.map} +1 -1
  18. package/dist/{chunk-WQCWBXBM.js → chunk-PAZ4MQXZ.js} +1 -1
  19. package/dist/chunk-PAZ4MQXZ.js.map +1 -0
  20. package/dist/{chunk-MIYMMP2K.js → chunk-ST6RH2IB.js} +303 -362
  21. package/dist/chunk-ST6RH2IB.js.map +1 -0
  22. package/dist/{chunk-O642NH7U.js → chunk-TDTORZHC.js} +3 -3
  23. package/dist/{chunk-O642NH7U.js.map → chunk-TDTORZHC.js.map} +1 -1
  24. package/dist/{chunk-5GDO4ZBC.js → chunk-URX7ZHX4.js} +2 -2
  25. package/dist/{chunk-UPLTM63S.js → chunk-VYSWBIWC.js} +34 -9
  26. package/dist/chunk-VYSWBIWC.js.map +1 -0
  27. package/dist/chunk-Y75XGZOB.js +11 -0
  28. package/dist/chunk-Y75XGZOB.js.map +1 -0
  29. package/dist/chunk-YZE7RXC2.js +417 -0
  30. package/dist/chunk-YZE7RXC2.js.map +1 -0
  31. package/dist/{chunk-Q2BVDQ3G.js → chunk-ZEKRTFBU.js} +1 -1
  32. package/dist/{chunk-Q2BVDQ3G.js.map → chunk-ZEKRTFBU.js.map} +1 -1
  33. package/dist/index.d.ts +3 -2
  34. package/dist/index.js +22 -20
  35. package/dist/index.js.map +1 -1
  36. package/dist/react/_internal/api/index.js +2 -2
  37. package/dist/react/_internal/index.d.ts +1 -1
  38. package/dist/react/_internal/index.js +9 -9
  39. package/dist/react/_internal/wagmi/index.js +2 -2
  40. package/dist/react/hooks/index.d.ts +57 -47
  41. package/dist/react/hooks/index.js +10 -8
  42. package/dist/react/index.css +24 -24
  43. package/dist/react/index.css.map +1 -1
  44. package/dist/react/index.d.ts +2 -2
  45. package/dist/react/index.js +19 -17
  46. package/dist/react/ssr/index.js.map +1 -1
  47. package/dist/react/ui/components/index.css +24 -24
  48. package/dist/react/ui/components/index.css.map +1 -1
  49. package/dist/react/ui/components/index.d.ts +1 -1
  50. package/dist/react/ui/components/index.js +15 -13
  51. package/dist/react/ui/icons/index.js +1 -1
  52. package/dist/react/ui/index.css +24 -24
  53. package/dist/react/ui/index.css.map +1 -1
  54. package/dist/react/ui/index.d.ts +16 -9
  55. package/dist/react/ui/index.js +15 -13
  56. package/dist/react/ui/modals/_internal/components/actionModal/index.css +46 -0
  57. package/dist/react/ui/modals/_internal/components/actionModal/index.css.map +1 -1
  58. package/dist/react/ui/modals/_internal/components/actionModal/index.d.ts +1 -1
  59. package/dist/react/ui/modals/_internal/components/actionModal/index.js +13 -8
  60. package/dist/react/ui/styles/index.js +1 -1
  61. package/dist/styles/index.js +4 -4
  62. package/dist/types/index.js +4 -4
  63. package/dist/{types-BlDoGvJV.d.ts → types-DZb7GsfL.d.ts} +10 -1
  64. package/dist/utils/abi/index.js +5 -5
  65. package/dist/utils/index.d.ts +1 -1
  66. package/dist/utils/index.js +7 -7
  67. package/package.json +8 -8
  68. package/src/consts.ts +3 -0
  69. package/src/react/_internal/transaction-machine/execute-transaction.ts +70 -113
  70. package/src/react/_internal/transaction-machine/useTransactionMachine.ts +15 -37
  71. package/src/react/_internal/transaction-machine/useWallet.ts +58 -0
  72. package/src/react/_internal/transaction-machine/utils.ts +50 -0
  73. package/src/react/_internal/transaction-machine/wallet.ts +237 -0
  74. package/src/react/_internal/types.ts +11 -0
  75. package/src/react/hooks/useBuyCollectable.tsx +14 -5
  76. package/src/react/hooks/useCancelOrder.tsx +26 -33
  77. package/src/react/hooks/useCancelTransactionSteps.tsx +222 -0
  78. package/src/react/hooks/useGenerateListingTransaction.tsx +1 -3
  79. package/src/react/hooks/useGenerateOfferTransaction.tsx +1 -3
  80. package/src/react/hooks/useGetReceiptFromHash.tsx +32 -0
  81. package/src/react/ui/modals/BuyModal/Modal.tsx +97 -0
  82. package/src/react/ui/modals/BuyModal/hooks/useBuyCollectable.ts +108 -0
  83. package/src/react/ui/modals/BuyModal/hooks/useCheckoutOptions.ts +50 -0
  84. package/src/react/ui/modals/BuyModal/hooks/useFees.ts +38 -0
  85. package/src/react/ui/modals/BuyModal/hooks/useLoadData.ts +58 -0
  86. package/src/react/ui/modals/BuyModal/index.tsx +3 -194
  87. package/src/react/ui/modals/BuyModal/modals/CheckoutModal.tsx +29 -0
  88. package/src/react/ui/modals/BuyModal/modals/Modal1155.tsx +84 -0
  89. package/src/react/ui/modals/CreateListingModal/Modal.tsx +195 -0
  90. package/src/react/ui/modals/CreateListingModal/hooks/useCreateListing.tsx +62 -0
  91. package/src/react/ui/modals/CreateListingModal/hooks/useGetTokenApproval.ts +75 -0
  92. package/src/react/ui/modals/CreateListingModal/hooks/useTransactionSteps.tsx +195 -0
  93. package/src/react/ui/modals/CreateListingModal/index.tsx +6 -284
  94. package/src/react/ui/modals/CreateListingModal/store.ts +84 -0
  95. package/src/react/ui/modals/MakeOfferModal/Modal.tsx +178 -0
  96. package/src/react/ui/modals/MakeOfferModal/hooks/useGetTokenApproval.tsx +76 -0
  97. package/src/react/ui/modals/MakeOfferModal/hooks/useMakeOffer.tsx +62 -0
  98. package/src/react/ui/modals/MakeOfferModal/hooks/useTransactionSteps.tsx +197 -0
  99. package/src/react/ui/modals/MakeOfferModal/index.tsx +8 -262
  100. package/src/react/ui/modals/MakeOfferModal/{_store.ts → store.ts} +38 -15
  101. package/src/react/ui/modals/SellModal/Modal.tsx +145 -0
  102. package/src/react/ui/modals/SellModal/hooks/useGetTokenApproval.tsx +73 -0
  103. package/src/react/ui/modals/SellModal/hooks/useSell.tsx +65 -0
  104. package/src/react/ui/modals/SellModal/hooks/useTransactionSteps.tsx +205 -0
  105. package/src/react/ui/modals/SellModal/index.tsx +8 -238
  106. package/src/react/ui/modals/SellModal/{_store.ts → store.ts} +27 -10
  107. package/src/react/ui/modals/_internal/components/actionModal/ActionModal.tsx +26 -1
  108. package/src/react/ui/modals/_internal/components/priceInput/index.tsx +11 -1
  109. package/src/react/ui/modals/_internal/components/switchChainModal/index.tsx +6 -1
  110. package/src/react/ui/modals/_internal/components/transaction-footer/index.tsx +3 -2
  111. package/src/react/ui/modals/_internal/components/transactionStatusModal/index.tsx +2 -1
  112. package/src/react/ui/modals/modal-provider.tsx +4 -4
  113. package/src/utils/_internal/error/transaction.ts +21 -1
  114. package/src/utils/date.ts +2 -0
  115. package/tsconfig.tsbuildinfo +1 -1
  116. package/dist/chunk-EVRILXOH.js.map +0 -1
  117. package/dist/chunk-GSDUAHL3.js.map +0 -1
  118. package/dist/chunk-IOTKCWOB.js.map +0 -1
  119. package/dist/chunk-MIYMMP2K.js.map +0 -1
  120. package/dist/chunk-T5T6JNB2.js.map +0 -1
  121. package/dist/chunk-UPLTM63S.js.map +0 -1
  122. package/dist/chunk-WQCWBXBM.js.map +0 -1
  123. package/src/react/hooks/useCreateListing.tsx +0 -128
  124. package/src/react/hooks/useMakeOffer.tsx +0 -125
  125. package/src/react/hooks/useSell.tsx +0 -124
  126. package/src/react/ui/modals/CreateListingModal/_store.ts +0 -55
  127. /package/dist/{chunk-KNX2LER4.js.map → chunk-4VS5NKDD.js.map} +0 -0
  128. /package/dist/{chunk-LSMQVX77.js.map → chunk-CP2IVRMX.js.map} +0 -0
  129. /package/dist/{chunk-QMO2CUNM.js.map → chunk-IQXJZBMR.js.map} +0 -0
  130. /package/dist/{chunk-5GDO4ZBC.js.map → chunk-URX7ZHX4.js.map} +0 -0
  131. /package/src/react/ui/modals/BuyModal/{_store.ts → store.ts} +0 -0
@@ -1,293 +1,15 @@
1
- import { Box } from '@0xsequence/design-system';
2
- import { Show, observer } from '@legendapp/state/react';
3
- import type { QueryKey } from '@tanstack/react-query';
4
- import { useEffect, useState } from 'react';
5
- import type { Hash, Hex } from 'viem';
6
- import { parseUnits } from 'viem';
7
- import { useAccount } from 'wagmi';
8
- import {
9
- type ContractType,
10
- type OrderbookKind,
11
- collectableKeys,
12
- } from '../../../_internal';
13
- import { TransactionType } from '../../../_internal/transaction-machine/execute-transaction';
14
- import {
15
- useBalanceOfCollectible,
16
- useCollectible,
17
- useCollection,
18
- } from '../../../hooks';
19
- import { useCreateListing } from '../../../hooks/useCreateListing';
20
- import {
21
- ActionModal,
22
- type ActionModalProps,
23
- } from '../_internal/components/actionModal/ActionModal';
24
- import { ErrorModal } from '../_internal/components/actionModal/ErrorModal';
25
- import { LoadingModal } from '../_internal/components/actionModal/LoadingModal';
26
- import ExpirationDateSelect from '../_internal/components/expirationDateSelect';
27
- import FloorPriceText from '../_internal/components/floorPriceText';
28
- import PriceInput from '../_internal/components/priceInput';
29
- import QuantityInput from '../_internal/components/quantityInput';
30
- import TokenPreview from '../_internal/components/tokenPreview';
31
- import TransactionDetails from '../_internal/components/transactionDetails';
32
- import { useTransactionStatusModal } from '../_internal/components/transactionStatusModal';
33
1
  import type { ModalCallbacks } from '../_internal/types';
34
- import { createListingModal$ } from './_store';
2
+ import { createListingModal$, type OpenCreateListingModalArgs } from './store';
35
3
 
36
- export type ShowCreateListingModalArgs = {
37
- collectionAddress: Hex;
38
- chainId: string;
39
- collectibleId: string;
40
- orderbookKind: OrderbookKind;
41
- onSuccess?: (hash?: Hash) => void;
42
- onError?: (error: Error) => void;
43
- };
4
+ type ShowCreateListingModalArgs = Exclude<
5
+ OpenCreateListingModalArgs,
6
+ 'callbacks'
7
+ >;
44
8
 
45
9
  export const useCreateListingModal = (callbacks?: ModalCallbacks) => {
46
10
  return {
47
11
  show: (args: ShowCreateListingModalArgs) =>
48
- createListingModal$.open({ ...args, defaultCallbacks: callbacks }),
12
+ createListingModal$.open({ ...args, callbacks }),
49
13
  close: () => createListingModal$.close(),
50
14
  };
51
15
  };
52
-
53
- export const CreateListingModal = () => {
54
- const { show: showTransactionStatusModal } = useTransactionStatusModal();
55
- return (
56
- <Show if={createListingModal$.isOpen}>
57
- <Modal showTransactionStatusModal={showTransactionStatusModal} />
58
- </Show>
59
- );
60
- };
61
-
62
- type TransactionStatusModalReturn = ReturnType<
63
- typeof useTransactionStatusModal
64
- >;
65
-
66
- export const Modal = observer(
67
- ({
68
- showTransactionStatusModal,
69
- }: {
70
- showTransactionStatusModal: TransactionStatusModalReturn['show'];
71
- }) => {
72
- const state = createListingModal$.get();
73
- const {
74
- collectionAddress,
75
- chainId,
76
- listingPrice,
77
- listingPriceChanged,
78
- collectibleId,
79
- orderbookKind,
80
- callbacks,
81
- } = state;
82
- const currencyAddress = listingPrice.currency.contractAddress;
83
- const {
84
- data: collectible,
85
- isLoading: collectableIsLoading,
86
- isError: collectableIsError,
87
- } = useCollectible({
88
- chainId,
89
- collectionAddress,
90
- collectibleId,
91
- });
92
-
93
- const {
94
- data: collection,
95
- isLoading: collectionIsLoading,
96
- isError: collectionIsError,
97
- } = useCollection({
98
- chainId,
99
- collectionAddress,
100
- });
101
- const [approvalExecutedSuccess, setApprovalExecutedSuccess] =
102
- useState(false);
103
-
104
- const { address } = useAccount();
105
-
106
- const { data: balance } = useBalanceOfCollectible({
107
- chainId,
108
- collectionAddress,
109
- collectableId: collectibleId,
110
- userAddress: address ?? undefined,
111
- });
112
-
113
- const { getListingSteps, isLoading: machineLoading, createListing} = useCreateListing({
114
- orderbookKind,
115
- chainId,
116
- collectionAddress,
117
- enabled: createListingModal$.isOpen.get(),
118
- onSwitchChainRefused: () => createListingModal$.close(),
119
- onApprovalSuccess: () => setApprovalExecutedSuccess(true),
120
- onTransactionSent: (hash, orderId) => {
121
- if (!hash && !orderId) return;
122
-
123
- showTransactionStatusModal({
124
- hash,
125
- orderId,
126
- collectionAddress,
127
- chainId,
128
- price: createListingModal$.listingPrice.get(),
129
- collectibleId,
130
- type: TransactionType.LISTING,
131
- queriesToInvalidate: collectableKeys.all as unknown as QueryKey[],
132
- callbacks,
133
- });
134
- createListingModal$.close();
135
- },
136
- });
137
-
138
- useEffect(() => {
139
- if (!currencyAddress) return;
140
-
141
- refreshSteps();
142
- }, [currencyAddress]);
143
-
144
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
145
- const handleStepExecution = async (execute?: any) => {
146
- if (!execute) return;
147
- try {
148
- await refreshSteps();
149
- await execute();
150
- } catch (error) {
151
- if (callbacks?.onError) {
152
- callbacks.onError(error as Error);
153
- } else {
154
- console.debug('onError callback not provided:', error);
155
- }
156
- }
157
- };
158
-
159
- if (collectableIsLoading || collectionIsLoading || machineLoading) {
160
- return (
161
- <LoadingModal
162
- isOpen={createListingModal$.isOpen.get()}
163
- chainId={Number(chainId)}
164
- onClose={createListingModal$.close}
165
- title="List item for sale"
166
- />
167
- );
168
- }
169
-
170
- if (collectableIsError || collectionIsError) {
171
- return (
172
- <ErrorModal
173
- isOpen={createListingModal$.isOpen.get()}
174
- chainId={Number(chainId)}
175
- onClose={createListingModal$.close}
176
- title="List item for sale"
177
- />
178
- );
179
- }
180
-
181
- const dateToUnixTime = (date: Date) =>
182
- Math.floor(date.getTime() / 1000).toString();
183
-
184
- const { isLoading, steps, refreshSteps } = getListingSteps({
185
- contractType: collection?.type as ContractType,
186
- listing: {
187
- tokenId: collectibleId,
188
- quantity: parseUnits(
189
- createListingModal$.quantity.get(),
190
- collectible?.decimals || 0,
191
- ).toString(),
192
- expiry: dateToUnixTime(createListingModal$.expiry.get()),
193
- currencyAddress: listingPrice.currency.contractAddress,
194
- pricePerToken: listingPrice.amountRaw,
195
- },
196
- });
197
- const approvalNeeded = steps?.approval.isPending;
198
-
199
- const ctas = [
200
- {
201
- label: 'Approve TOKEN',
202
- onClick: () => handleStepExecution(() => steps?.approval.execute()),
203
- hidden: !approvalNeeded || approvalExecutedSuccess,
204
- pending: steps?.approval.isExecuting || isLoading,
205
- variant: 'glass' as const,
206
- disabled:
207
- createListingModal$.invalidQuantity.get() ||
208
- isLoading ||
209
- !listingPriceChanged ||
210
- listingPrice.amountRaw === '0' ||
211
- steps?.transaction.isExecuting,
212
- },
213
- {
214
- label: 'List item for sale',
215
- onClick: () => createListing({
216
- contractType: collection?.type as ContractType,
217
- listing: {
218
- tokenId: collectibleId,
219
- quantity: parseUnits(
220
- createListingModal$.quantity.get(),
221
- collectible?.decimals || 0,
222
- ).toString(),
223
- expiry: dateToUnixTime(createListingModal$.expiry.get()),
224
- currencyAddress: listingPrice.currency.contractAddress,
225
- pricePerToken: listingPrice.amountRaw,
226
- },
227
- }),
228
- pending: steps?.transaction.isExecuting || isLoading,
229
- disabled:
230
- (!approvalExecutedSuccess && approvalNeeded) ||
231
- listingPrice.amountRaw === '0' ||
232
- isLoading ||
233
- createListingModal$.invalidQuantity.get() ||
234
- !listingPriceChanged,
235
- },
236
- ] satisfies ActionModalProps['ctas'];
237
-
238
- return (
239
- <ActionModal
240
- isOpen={createListingModal$.isOpen.get()}
241
- chainId={Number(chainId)}
242
- onClose={() => createListingModal$.close()}
243
- title="List item for sale"
244
- ctas={ctas}
245
- >
246
- <TokenPreview
247
- collectionName={collection?.name}
248
- collectionAddress={collectionAddress}
249
- collectibleId={collectibleId}
250
- chainId={chainId}
251
- />
252
-
253
- <Box display="flex" flexDirection="column" width="full" gap="1">
254
- <PriceInput
255
- chainId={chainId}
256
- collectionAddress={collectionAddress}
257
- $listingPrice={createListingModal$.listingPrice}
258
- onPriceChange={() => createListingModal$.listingPriceChanged.set(true)}
259
- />
260
-
261
- {listingPrice.amountRaw !== '0' && listingPriceChanged && (
262
- <FloorPriceText
263
- tokenId={collectibleId}
264
- chainId={chainId}
265
- collectionAddress={collectionAddress}
266
- price={listingPrice}
267
- />
268
- )}
269
- </Box>
270
-
271
- {collection?.type === 'ERC1155' && balance && (
272
- <QuantityInput
273
- $quantity={createListingModal$.quantity}
274
- $invalidQuantity={createListingModal$.invalidQuantity}
275
- decimals={collectible?.decimals || 0}
276
- maxQuantity={balance?.balance}
277
- />
278
- )}
279
-
280
- <ExpirationDateSelect $date={createListingModal$.expiry} />
281
-
282
- <TransactionDetails
283
- collectibleId={collectibleId}
284
- collectionAddress={collectionAddress}
285
- chainId={chainId}
286
- price={createListingModal$.listingPrice.get()}
287
- showPlaceholderPrice={!listingPriceChanged}
288
- currencyImageUrl={listingPrice.currency.imageUrl}
289
- />
290
- </ActionModal>
291
- );
292
- },
293
- );
@@ -0,0 +1,84 @@
1
+ import { observable } from '@legendapp/state';
2
+ import { addDays } from 'date-fns/addDays';
3
+ import type { Hex } from 'viem';
4
+ import { type Currency, OrderbookKind } from '../../../../types';
5
+ import type { BaseModalState, ModalCallbacks } from '../_internal/types';
6
+ import type { CollectionType, TransactionSteps } from '../../../_internal';
7
+
8
+ type CreateListingState = BaseModalState & {
9
+ collectibleId: string;
10
+ collectionName: string;
11
+ orderbookKind: OrderbookKind;
12
+ collectionType: CollectionType | undefined;
13
+ listingPrice: {
14
+ amountRaw: string;
15
+ currency: Currency;
16
+ };
17
+ quantity: string;
18
+ invalidQuantity: boolean;
19
+ expiry: Date;
20
+ steps: TransactionSteps;
21
+ };
22
+
23
+ export type OpenCreateListingModalArgs = {
24
+ collectionAddress: Hex;
25
+ chainId: string;
26
+ collectibleId: string;
27
+ orderbookKind: OrderbookKind;
28
+ callbacks?: ModalCallbacks;
29
+ };
30
+
31
+ type Actions = {
32
+ open: (args: OpenCreateListingModalArgs) => void;
33
+ close: () => void;
34
+ };
35
+
36
+ const initialState: CreateListingState = {
37
+ isOpen: false,
38
+ collectionAddress: '' as Hex,
39
+ chainId: '',
40
+ collectibleId: '',
41
+ orderbookKind: OrderbookKind.sequence_marketplace_v2,
42
+ collectionName: '',
43
+ collectionType: undefined,
44
+ listingPrice: {
45
+ amountRaw: '0',
46
+ currency: {} as Currency,
47
+ },
48
+ quantity: '1',
49
+ invalidQuantity: false,
50
+ expiry: new Date(addDays(new Date(), 7).toJSON()),
51
+ callbacks: undefined as ModalCallbacks | undefined,
52
+ steps: {
53
+ approval: {
54
+ exist: false,
55
+ isExecuting: false,
56
+ execute: () => Promise.resolve(),
57
+ },
58
+ transaction: {
59
+ exist: false,
60
+ isExecuting: false,
61
+ execute: () => Promise.resolve(),
62
+ },
63
+ },
64
+ };
65
+
66
+ const actions: Actions = {
67
+ open: (args) => {
68
+ createListingModal$.collectionAddress.set(args.collectionAddress);
69
+ createListingModal$.chainId.set(args.chainId);
70
+ createListingModal$.collectibleId.set(args.collectibleId);
71
+ createListingModal$.orderbookKind.set(args.orderbookKind);
72
+ createListingModal$.callbacks.set(args.callbacks);
73
+ createListingModal$.isOpen.set(true);
74
+ },
75
+ close: () => {
76
+ createListingModal$.isOpen.set(false);
77
+ createListingModal$.set({ ...initialState, ...actions });
78
+ },
79
+ };
80
+
81
+ export const createListingModal$ = observable<CreateListingState & Actions>({
82
+ ...initialState,
83
+ ...actions,
84
+ });
@@ -0,0 +1,178 @@
1
+ import { Show, observer } from '@legendapp/state/react';
2
+ import { useState } from 'react';
3
+ import { parseUnits } from 'viem';
4
+ import { dateToUnixTime } from '../../../../utils/date';
5
+ import { ContractType } from '../../../_internal';
6
+ import { useCollectible, useCollection } from '../../../hooks';
7
+ import { useMakeOffer } from './hooks/useMakeOffer';
8
+ import { ActionModal } from '../_internal/components/actionModal/ActionModal';
9
+ import { ErrorModal } from '../_internal/components/actionModal/ErrorModal';
10
+ import { LoadingModal } from '../_internal/components/actionModal/LoadingModal';
11
+ import ExpirationDateSelect from '../_internal/components/expirationDateSelect';
12
+ import FloorPriceText from '../_internal/components/floorPriceText';
13
+ import PriceInput from '../_internal/components/priceInput';
14
+ import QuantityInput from '../_internal/components/quantityInput';
15
+ import TokenPreview from '../_internal/components/tokenPreview';
16
+ import { makeOfferModal$ } from './store';
17
+
18
+ export const MakeOfferModal = () => {
19
+ return <Show if={makeOfferModal$.isOpen}>{() => <Modal />}</Show>;
20
+ };
21
+
22
+ const Modal = observer(() => {
23
+ const state = makeOfferModal$.get();
24
+ const {
25
+ collectionAddress,
26
+ chainId,
27
+ offerPrice,
28
+ offerPriceChanged,
29
+ invalidQuantity,
30
+ collectibleId,
31
+ orderbookKind,
32
+ callbacks,
33
+ } = state;
34
+ const steps$ = makeOfferModal$.steps;
35
+ const [insufficientBalance, setInsufficientBalance] = useState(false);
36
+ const {
37
+ data: collectible,
38
+ isLoading: collectableIsLoading,
39
+ isError: collectableIsError,
40
+ } = useCollectible({
41
+ chainId,
42
+ collectionAddress,
43
+ collectibleId,
44
+ });
45
+
46
+ const {
47
+ data: collection,
48
+ isLoading: collectionIsLoading,
49
+ isError: collectionIsError,
50
+ } = useCollection({
51
+ chainId,
52
+ collectionAddress,
53
+ });
54
+ const { isLoading, executeApproval, makeOffer } = useMakeOffer({
55
+ offerInput: {
56
+ contractType: collection?.type as ContractType,
57
+ offer: {
58
+ tokenId: collectibleId,
59
+ quantity: parseUnits(
60
+ makeOfferModal$.quantity.get(),
61
+ collectible?.decimals || 0,
62
+ ).toString(),
63
+ expiry: dateToUnixTime(makeOfferModal$.expiry.get()),
64
+ currencyAddress: offerPrice.currency.contractAddress,
65
+ pricePerToken: offerPrice.amountRaw,
66
+ },
67
+ },
68
+ chainId,
69
+ collectionAddress,
70
+ orderbookKind,
71
+ callbacks,
72
+ closeMainModal: () => makeOfferModal$.close(),
73
+ steps$: steps$,
74
+ });
75
+
76
+ if (collectableIsLoading || collectionIsLoading) {
77
+ return (
78
+ <LoadingModal
79
+ isOpen={makeOfferModal$.isOpen.get()}
80
+ chainId={Number(chainId)}
81
+ onClose={makeOfferModal$.close}
82
+ title="Make an offer"
83
+ />
84
+ );
85
+ }
86
+
87
+ if (collectableIsError || collectionIsError) {
88
+ return (
89
+ <ErrorModal
90
+ isOpen={makeOfferModal$.isOpen.get()}
91
+ chainId={Number(chainId)}
92
+ onClose={makeOfferModal$.close}
93
+ title="Make an offer"
94
+ />
95
+ );
96
+ }
97
+
98
+ const ctas = [
99
+ {
100
+ label: 'Approve TOKEN',
101
+ onClick: async () => await executeApproval(),
102
+ hidden: !steps$.approval.exist.get(),
103
+ pending: steps$.approval.isExecuting.get(),
104
+ variant: 'glass' as const,
105
+ disabled:
106
+ invalidQuantity ||
107
+ isLoading ||
108
+ insufficientBalance ||
109
+ offerPrice.amountRaw === '0' ||
110
+ !offerPriceChanged,
111
+ },
112
+ {
113
+ label: 'Make offer',
114
+ onClick: () => makeOffer(),
115
+ pending: steps$.transaction.isExecuting.get(),
116
+ disabled:
117
+ steps$.approval.isExecuting.get() ||
118
+ steps$.approval.exist.get() ||
119
+ offerPrice.amountRaw === '0' ||
120
+ insufficientBalance ||
121
+ isLoading ||
122
+ invalidQuantity ||
123
+ offerPrice.amountRaw === '0',
124
+ },
125
+ ];
126
+
127
+ return (
128
+ <>
129
+ <ActionModal
130
+ isOpen={makeOfferModal$.isOpen.get()}
131
+ chainId={Number(chainId)}
132
+ onClose={() => makeOfferModal$.close()}
133
+ title="Make an offer"
134
+ ctas={ctas}
135
+ >
136
+ <TokenPreview
137
+ collectionName={collection?.name}
138
+ collectionAddress={collectionAddress}
139
+ collectibleId={collectibleId}
140
+ chainId={chainId}
141
+ />
142
+
143
+ <PriceInput
144
+ chainId={chainId}
145
+ collectionAddress={collectionAddress}
146
+ $listingPrice={makeOfferModal$.offerPrice}
147
+ onPriceChange={() => makeOfferModal$.offerPriceChanged.set(true)}
148
+ checkBalance={{
149
+ enabled: true,
150
+ callback: (state) => setInsufficientBalance(state),
151
+ }}
152
+ />
153
+
154
+ {collection?.type === ContractType.ERC1155 && (
155
+ <QuantityInput
156
+ $quantity={makeOfferModal$.quantity}
157
+ $invalidQuantity={makeOfferModal$.invalidQuantity}
158
+ decimals={collectible?.decimals || 0}
159
+ maxQuantity={String(Number.MAX_SAFE_INTEGER)}
160
+ />
161
+ )}
162
+
163
+ {offerPrice.amountRaw !== '0' &&
164
+ offerPriceChanged &&
165
+ !insufficientBalance && (
166
+ <FloorPriceText
167
+ tokenId={collectibleId}
168
+ chainId={chainId}
169
+ collectionAddress={collectionAddress}
170
+ price={offerPrice}
171
+ />
172
+ )}
173
+
174
+ <ExpirationDateSelect $date={makeOfferModal$.expiry} />
175
+ </ActionModal>
176
+ </>
177
+ );
178
+ });
@@ -0,0 +1,76 @@
1
+ import {
2
+ type ContractType,
3
+ type CreateReq,
4
+ GenerateOfferTransactionArgs,
5
+ getMarketplaceClient,
6
+ type OrderbookKind,
7
+ StepType,
8
+ } from '../../../../_internal';
9
+ import { useQuery } from '@tanstack/react-query';
10
+ import { useWallet } from '../../../../_internal/transaction-machine/useWallet';
11
+ import { useConfig } from '../../../../hooks/useConfig';
12
+ import { dateToUnixTime } from '../../../../../utils/date';
13
+
14
+ export interface UseGetTokenApprovalDataArgs {
15
+ chainId: string;
16
+ tokenId: string;
17
+ collectionAddress: string;
18
+ currencyAddress: string;
19
+ contractType: ContractType;
20
+ orderbook: OrderbookKind;
21
+ }
22
+
23
+ const ONE_DAY_IN_SECONDS = 60 * 60 * 24;
24
+
25
+ export const useGetTokenApprovalData = (
26
+ params: UseGetTokenApprovalDataArgs,
27
+ ) => {
28
+ const config = useConfig();
29
+ const { wallet } = useWallet();
30
+ const marketplaceClient = getMarketplaceClient(params.chainId, config);
31
+
32
+ const offer = {
33
+ tokenId: params.tokenId,
34
+ quantity: '1',
35
+ currencyAddress: params.currencyAddress,
36
+ pricePerToken: '100000',
37
+ expiry: String(Number(dateToUnixTime(new Date())) + ONE_DAY_IN_SECONDS),
38
+ } satisfies CreateReq;
39
+
40
+ const { data, isLoading, isSuccess } = useQuery({
41
+ queryKey: ['token-approval-data', params.currencyAddress],
42
+ queryFn: async () => {
43
+ const args = {
44
+ collectionAddress: params.collectionAddress,
45
+ maker: await wallet!.address(),
46
+ walletType: wallet!.walletKind,
47
+ contractType: params.contractType,
48
+ orderbook: params.orderbook,
49
+ offer,
50
+ } satisfies GenerateOfferTransactionArgs;
51
+ const steps = await marketplaceClient
52
+ .generateOfferTransaction(args)
53
+ .then((resp) => resp.steps);
54
+
55
+ const tokenApprovalStep = steps.find(
56
+ (step) => step.id === StepType.tokenApproval,
57
+ );
58
+ if (!tokenApprovalStep) {
59
+ return {
60
+ step: null,
61
+ };
62
+ }
63
+
64
+ return {
65
+ step: tokenApprovalStep,
66
+ };
67
+ },
68
+ enabled: !!wallet && !!params.collectionAddress && !!params.currencyAddress,
69
+ });
70
+
71
+ return {
72
+ data,
73
+ isLoading,
74
+ isSuccess,
75
+ };
76
+ };
@@ -0,0 +1,62 @@
1
+ import { Observable } from '@legendapp/state';
2
+ import { OrderbookKind } from '../../../../../types';
3
+ import { OfferInput } from '../../../../_internal/transaction-machine/execute-transaction';
4
+ import { ModalCallbacks } from '../../_internal/types';
5
+ import { useGetTokenApprovalData } from './useGetTokenApproval';
6
+ import { useTransactionSteps } from './useTransactionSteps';
7
+ import { useEffect } from 'react';
8
+ import { TransactionSteps } from '../../../../_internal';
9
+
10
+ interface UseMakeOfferArgs {
11
+ offerInput: OfferInput;
12
+ chainId: string;
13
+ collectionAddress: string;
14
+ orderbookKind?: OrderbookKind;
15
+ callbacks?: ModalCallbacks;
16
+ closeMainModal: () => void;
17
+ steps$: Observable<TransactionSteps>;
18
+ }
19
+
20
+ export const useMakeOffer = ({
21
+ offerInput,
22
+ chainId,
23
+ collectionAddress,
24
+ orderbookKind = OrderbookKind.sequence_marketplace_v2,
25
+ callbacks,
26
+ closeMainModal,
27
+ steps$,
28
+ }: UseMakeOfferArgs) => {
29
+ const { data: tokenApproval, isLoading: tokenApprovalIsLoading } =
30
+ useGetTokenApprovalData({
31
+ chainId,
32
+ tokenId: offerInput.offer.tokenId,
33
+ collectionAddress,
34
+ currencyAddress: offerInput.offer.currencyAddress,
35
+ contractType: offerInput.contractType,
36
+ orderbook: orderbookKind,
37
+ });
38
+
39
+ useEffect(() => {
40
+ if (tokenApproval?.step && !tokenApprovalIsLoading) {
41
+ steps$.approval.exist.set(true);
42
+ }
43
+ }, [tokenApproval?.step, tokenApprovalIsLoading]);
44
+
45
+ const { generatingSteps, executeApproval, makeOffer } = useTransactionSteps({
46
+ offerInput,
47
+ chainId,
48
+ collectionAddress,
49
+ orderbookKind,
50
+ callbacks,
51
+ closeMainModal,
52
+ steps$,
53
+ });
54
+
55
+ return {
56
+ isLoading: generatingSteps,
57
+ executeApproval,
58
+ makeOffer,
59
+ tokenApprovalStepExists: tokenApproval?.step !== null,
60
+ tokenApprovalIsLoading,
61
+ };
62
+ };