@0xsequence/marketplace-sdk 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/BellIcon.js +1 -1
  3. package/dist/Card.js +1 -1
  4. package/dist/ShopCard.d.ts +4 -4
  5. package/dist/builder-api.js +1 -1
  6. package/dist/collectible.js +2 -2
  7. package/dist/collectible.js.map +1 -1
  8. package/dist/collection.js +1 -1
  9. package/dist/create-config.d.ts +589 -193
  10. package/dist/create-config.js +1 -1
  11. package/dist/currency.js +3 -3
  12. package/dist/currency.js.map +1 -1
  13. package/dist/dist.js +167 -148
  14. package/dist/dist.js.map +1 -1
  15. package/dist/expirationDateSelect.js +1 -1
  16. package/dist/filter-state.d.ts +1 -1
  17. package/dist/filters.d.ts +1 -1
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +3 -3
  20. package/dist/index10.d.ts +1 -1
  21. package/dist/index11.d.ts +17 -17
  22. package/dist/index12.d.ts +21 -21
  23. package/dist/index14.d.ts +3 -3
  24. package/dist/index15.d.ts +3 -3
  25. package/dist/index16.d.ts +2 -2
  26. package/dist/index17.d.ts +75 -75
  27. package/dist/index18.d.ts +40 -40
  28. package/dist/index19.d.ts +5 -5
  29. package/dist/index2.d.ts +4 -1
  30. package/dist/index21.d.ts +15 -15
  31. package/dist/index22.d.ts +8 -65
  32. package/dist/index23.d.ts +21 -13
  33. package/dist/index26.d.ts +4 -4
  34. package/dist/index27.d.ts +4 -4
  35. package/dist/index28.d.ts +10 -10
  36. package/dist/index3.d.ts +2 -2194
  37. package/dist/index31.d.ts +5 -5
  38. package/dist/index33.d.ts +3 -3
  39. package/dist/index34.d.ts +1 -1
  40. package/dist/index35.d.ts +1 -1
  41. package/dist/index36.d.ts +5 -5
  42. package/dist/index37.d.ts +8 -6
  43. package/dist/index38.d.ts +5 -5
  44. package/dist/index39.d.ts +1 -1
  45. package/dist/index4.d.ts +1356 -1356
  46. package/dist/index40.d.ts +2 -2
  47. package/dist/index8.d.ts +11 -3
  48. package/dist/index9.d.ts +2811 -3
  49. package/dist/inventory.d.ts +4 -4
  50. package/dist/inventory.js +3 -3
  51. package/dist/inventory.js.map +1 -1
  52. package/dist/marketplace2.js +3 -3
  53. package/dist/marketplace2.js.map +1 -1
  54. package/dist/metadata.d.ts +41 -41
  55. package/dist/primary-sale-checkout-options.d.ts +4 -4
  56. package/dist/quantityInput.js +1 -1
  57. package/dist/ranges.d.ts +12 -12
  58. package/dist/react/_internal/index.d.ts +1 -1
  59. package/dist/react/_internal/index.js +1 -1
  60. package/dist/react/index.d.ts +1 -1
  61. package/dist/react/queries/collectible/index.d.ts +1 -1
  62. package/dist/react/queries/index.d.ts +1 -1
  63. package/dist/react/ssr/index.d.ts +3 -3
  64. package/dist/react/ssr/index.js +3 -3
  65. package/dist/react/ui/components/marketplace-collectible-card/index.d.ts +1 -1
  66. package/dist/react/ui/modals/CreateListingModal/internal/hooks/index.d.ts +1 -1
  67. package/dist/react/ui/modals/MakeOfferModal/internal/hooks/index.d.ts +1 -1
  68. package/dist/react/ui/modals/_internal/components/alertMessage/index.d.ts +2 -2
  69. package/dist/react/ui/modals/_internal/components/baseModal/index.d.ts +6 -6
  70. package/dist/react/ui/modals/_internal/components/calendar/index.d.ts +2 -2
  71. package/dist/react/ui/modals/_internal/components/currencyImage/index.d.ts +2 -2
  72. package/dist/react/ui/modals/_internal/components/currencyOptionsSelect/index.d.ts +3 -3
  73. package/dist/react/ui/modals/_internal/components/floorPriceText/index.d.ts +2 -2
  74. package/dist/react/ui/modals/_internal/components/priceInput/index.d.ts +3 -5
  75. package/dist/react/ui/modals/_internal/components/quantityInput/index.d.ts +2 -2
  76. package/dist/react/ui/modals/_internal/components/selectWaasFeeOptions/index.d.ts +2 -2
  77. package/dist/react/ui/modals/_internal/components/switchChainErrorModal/index.d.ts +2 -2
  78. package/dist/react/ui/modals/_internal/components/timeAgo/index.d.ts +2 -2
  79. package/dist/react/ui/modals/_internal/components/tokenPreview/index.d.ts +3 -3
  80. package/dist/react/ui/modals/_internal/components/transaction-footer/index.d.ts +3 -3
  81. package/dist/react/ui/modals/_internal/components/transactionDetails/index.d.ts +3 -3
  82. package/dist/react/ui/modals/_internal/components/transactionPreview/index.d.ts +3 -3
  83. package/dist/react/ui/modals/_internal/components/transactionStatusModal/index.d.ts +3 -3
  84. package/dist/react.js +2279 -1919
  85. package/dist/react.js.map +1 -1
  86. package/dist/styles/index.css +15 -0
  87. package/dist/token-balances.d.ts +28 -28
  88. package/dist/transaction-footer.js +1 -1
  89. package/dist/types/index.d.ts +1 -1
  90. package/dist/types/index.js +1 -1
  91. package/dist/types.d.ts +1 -1
  92. package/dist/url-state.js +1 -1
  93. package/dist/utils/index.d.ts +2 -2
  94. package/dist/utils/index.js +2 -2
  95. package/dist/utils.js +31 -4
  96. package/dist/utils.js.map +1 -1
  97. package/package.json +7 -5
  98. package/src/react/hooks/config/useMarketplaceConfig.test.tsx +1 -0
  99. package/src/react/hooks/currency/list.test.tsx +23 -2
  100. package/src/react/hooks/transactions/useCancelTransactionSteps.tsx +4 -1
  101. package/src/react/hooks/transactions/useMarketTransactionSteps.tsx +55 -15
  102. package/src/react/hooks/utils/useEnsureCorrectChain.ts +10 -5
  103. package/src/react/queries/collectible/market-list.ts +5 -3
  104. package/src/react/queries/currency/list.ts +8 -5
  105. package/src/react/queries/inventory/inventory.ts +5 -3
  106. package/src/react/queries/marketplace/filters.ts +5 -3
  107. package/src/react/ui/modals/BuyModal/components/BuyModalContent.tsx +74 -37
  108. package/src/react/ui/modals/BuyModal/components/CryptoPaymentModal.tsx +74 -11
  109. package/src/react/ui/modals/BuyModal/components/Modal.tsx +62 -1
  110. package/src/react/ui/modals/BuyModal/hooks/useExecuteBundledTransactions.ts +13 -26
  111. package/src/react/ui/modals/BuyModal/hooks/useMarketPlatformFee.ts +5 -5
  112. package/src/react/ui/modals/BuyModal/internal/__tests__/buildTrailsMarketBuyActions.test.ts +213 -0
  113. package/src/react/ui/modals/BuyModal/internal/buildTrailsMarketBuyActions.ts +259 -0
  114. package/src/react/ui/modals/BuyModal/internal/buyModalContext.ts +79 -10
  115. package/src/react/ui/modals/BuyModal/internal/cryptoPaymentModalContext.tsx +44 -17
  116. package/src/react/ui/modals/CreateListingModal/internal/store.ts +5 -2
  117. package/src/react/ui/modals/MakeOfferModal/internal/context.ts +21 -1
  118. package/src/react/ui/modals/MakeOfferModal/internal/helpers/validation.ts +16 -1
  119. package/src/react/ui/modals/MakeOfferModal/internal/store.ts +5 -2
  120. package/src/react/ui/modals/SellModal/internal/store.ts +5 -2
  121. package/src/react/ui/modals/_internal/components/baseModal/errors/ModalInitializationError.tsx +8 -6
  122. package/src/react/ui/modals/_internal/components/currencyOptionsSelect/index.tsx +2 -1
  123. package/src/react/ui/modals/_internal/components/priceInput/index.tsx +13 -19
  124. package/src/react/ui/modals/_internal/components/transactionDetails/index.tsx +5 -2
  125. package/src/react/ui/modals/_internal/helpers/currency.test.ts +27 -0
  126. package/src/react/ui/modals/_internal/helpers/currency.ts +4 -2
  127. package/src/styles/styles.ts +18 -0
  128. package/src/utils/__tests__/getMarketplaceDetails.test.ts +10 -0
  129. package/src/utils/__tests__/getWebRPCErrorMessage.test.ts +28 -0
  130. package/src/utils/__tests__/marketplaceNormalization.test.ts +38 -0
  131. package/src/utils/collection.ts +19 -0
  132. package/src/utils/getConduitAddressForOrderbook.ts +2 -10
  133. package/src/utils/getMarketplaceDetails.ts +11 -4
  134. package/src/utils/getWebRPCErrorMessage.ts +21 -0
  135. package/src/utils/index.ts +1 -0
  136. package/src/utils/normalizeMarketplace.ts +31 -0
@@ -0,0 +1,213 @@
1
+ import {
2
+ type BuyStep,
3
+ ContractType,
4
+ MarketplaceKind,
5
+ type Order,
6
+ StepType,
7
+ } from '@0xsequence/api-client';
8
+ import { encodeDestinationCalls } from '0xtrails';
9
+ import { type Address, decodeFunctionData, erc20Abi, zeroAddress } from 'viem';
10
+ import { describe, expect, it } from 'vitest';
11
+ import { OPENSEA_SEAPORT_CONDUIT_ADDRESS } from '../../../../../../utils/getConduitAddressForOrderbook';
12
+ import { buildTrailsMarketBuyActions } from '../buildTrailsMarketBuyActions';
13
+
14
+ const BUY_TARGET = '0x1000000000000000000000000000000000000001' as Address;
15
+ const USER_WALLET = '0x2000000000000000000000000000000000000002' as Address;
16
+ const ERC20_CURRENCY = '0x3000000000000000000000000000000000000003' as Address;
17
+ const COLLECTION_ADDRESS =
18
+ '0x4000000000000000000000000000000000000004' as Address;
19
+ const SEAPORT_ADDRESS = '0x0000000000000068F116a894984e2DB1123eB395' as Address;
20
+ const BASE_WETH = '0x4200000000000000000000000000000000000006' as Address;
21
+ const OPENSEA_FULFILL_BASIC_ORDER_SELECTOR = '0xfb0f3ee1';
22
+
23
+ const createBuyStep = (overrides: Partial<BuyStep> = {}): BuyStep =>
24
+ ({
25
+ id: StepType.buy,
26
+ to: BUY_TARGET,
27
+ data: '0xabcdef',
28
+ value: 0n,
29
+ price: 100n,
30
+ ...overrides,
31
+ }) as BuyStep;
32
+
33
+ const createOrder = (overrides: Partial<Order> = {}): Order =>
34
+ ({
35
+ orderId: 'order-1',
36
+ marketplace: MarketplaceKind.sequence_marketplace_v2,
37
+ collectionContractAddress: COLLECTION_ADDRESS,
38
+ tokenId: 7n,
39
+ priceCurrencyAddress: ERC20_CURRENCY,
40
+ priceAmount: 100n,
41
+ ...overrides,
42
+ }) as Order;
43
+
44
+ const build = (
45
+ overrides: {
46
+ buyStep?: Partial<BuyStep>;
47
+ marketOrder?: Partial<Order>;
48
+ contractType?: ContractType.ERC721 | ContractType.ERC1155;
49
+ } = {},
50
+ ) =>
51
+ buildTrailsMarketBuyActions({
52
+ chainId: 8453,
53
+ buyStep: createBuyStep(overrides.buyStep),
54
+ marketOrder: createOrder(overrides.marketOrder),
55
+ contractType: overrides.contractType ?? ContractType.ERC721,
56
+ recipientAddress: USER_WALLET,
57
+ });
58
+
59
+ describe('buildTrailsMarketBuyActions', () => {
60
+ it('builds ERC20 approval and marketplace buy calls using the required payment amount', () => {
61
+ const result = build();
62
+
63
+ expect(result).toBeDefined();
64
+ expect(result?.paymentTokenAddress).toBe(ERC20_CURRENCY);
65
+ expect(result?.paymentAmount).toBe(100n);
66
+ expect(result?.calls).toHaveLength(2);
67
+ expect(result?.calls[0]).toMatchObject({
68
+ to: ERC20_CURRENCY,
69
+ });
70
+ expect(result?.calls[0]?.data).toMatch(/^0x095ea7b3/);
71
+ const approval = decodeFunctionData({
72
+ abi: erc20Abi,
73
+ data: result?.calls[0]?.data ?? '0x',
74
+ });
75
+ expect(approval.args[1]).toBe(100n);
76
+
77
+ expect(result?.calls[1]).toMatchObject({
78
+ to: BUY_TARGET,
79
+ data: '0xabcdef',
80
+ });
81
+ expect(result?.calls[1]?.value).toBe(undefined);
82
+
83
+ expect(() =>
84
+ encodeDestinationCalls({
85
+ calls: result?.calls ?? [],
86
+ tokenAddress: ERC20_CURRENCY,
87
+ sweepTarget: USER_WALLET,
88
+ }),
89
+ ).not.toThrow();
90
+ });
91
+
92
+ it('encodes OpenSea ERC20 buys even when approval spender is the OpenSea conduit', () => {
93
+ const result = build({
94
+ buyStep: {
95
+ to: SEAPORT_ADDRESS,
96
+ data: OPENSEA_FULFILL_BASIC_ORDER_SELECTOR,
97
+ },
98
+ marketOrder: {
99
+ marketplace: MarketplaceKind.opensea,
100
+ },
101
+ });
102
+
103
+ expect(result).toBeDefined();
104
+ expect(result?.calls).toHaveLength(3);
105
+ const approval = decodeFunctionData({
106
+ abi: erc20Abi,
107
+ data: result?.calls[0]?.data ?? '0x',
108
+ });
109
+ expect(approval.args[0]).toBe(OPENSEA_SEAPORT_CONDUIT_ADDRESS);
110
+ expect(approval.args[1]).toBe(100n);
111
+ expect(result?.calls[1]).toMatchObject({
112
+ to: SEAPORT_ADDRESS,
113
+ data: OPENSEA_FULFILL_BASIC_ORDER_SELECTOR,
114
+ });
115
+ expect(result?.calls[2]?.to).toBe(COLLECTION_ADDRESS);
116
+ expect(result?.calls[2]?.data).toMatch(/^0x23b872dd/);
117
+
118
+ expect(() =>
119
+ encodeDestinationCalls({
120
+ calls: result?.calls ?? [],
121
+ tokenAddress: result?.paymentTokenAddress ?? ERC20_CURRENCY,
122
+ sweepTarget: USER_WALLET,
123
+ }),
124
+ ).not.toThrow();
125
+ });
126
+
127
+ it('uses wrapped native currency for native marketplace buys', () => {
128
+ const result = build({
129
+ buyStep: {
130
+ value: 123n,
131
+ price: 0n,
132
+ },
133
+ marketOrder: {
134
+ priceCurrencyAddress: zeroAddress,
135
+ priceAmount: 123n,
136
+ },
137
+ });
138
+
139
+ expect(result).toBeDefined();
140
+ expect(result?.paymentTokenAddress).toBe(BASE_WETH);
141
+ expect(result?.paymentAmount).toBe(123n);
142
+ expect(result?.calls).toHaveLength(2);
143
+ expect(result?.calls[0]).toMatchObject({
144
+ to: BASE_WETH,
145
+ });
146
+ expect(result?.calls[0]?.data).toMatch(/^0x2e1a7d4d/);
147
+ expect(result?.calls[1]).toMatchObject({
148
+ to: BUY_TARGET,
149
+ data: '0xabcdef',
150
+ value: 123n,
151
+ sweepTokens: [zeroAddress],
152
+ });
153
+
154
+ expect(() =>
155
+ encodeDestinationCalls({
156
+ calls: result?.calls ?? [],
157
+ tokenAddress: result?.paymentTokenAddress ?? BASE_WETH,
158
+ sweepTarget: USER_WALLET,
159
+ }),
160
+ ).not.toThrow();
161
+ });
162
+
163
+ it('wraps native OpenSea buys through WETH and appends an NFT transfer', () => {
164
+ const result = build({
165
+ buyStep: {
166
+ to: SEAPORT_ADDRESS,
167
+ data: OPENSEA_FULFILL_BASIC_ORDER_SELECTOR,
168
+ value: 123n,
169
+ price: 0n,
170
+ },
171
+ marketOrder: {
172
+ marketplace: MarketplaceKind.opensea,
173
+ priceCurrencyAddress: zeroAddress,
174
+ priceAmount: 123n,
175
+ },
176
+ });
177
+
178
+ expect(result?.paymentTokenAddress).toBe(BASE_WETH);
179
+ expect(result?.paymentAmount).toBe(123n);
180
+ expect(result?.calls).toHaveLength(3);
181
+ expect(result?.calls[0]?.data).toMatch(/^0x2e1a7d4d/);
182
+ expect(result?.calls[1]).toMatchObject({
183
+ to: SEAPORT_ADDRESS,
184
+ value: 123n,
185
+ });
186
+ expect(result?.calls[2]?.to).toBe(COLLECTION_ADDRESS);
187
+
188
+ expect(() =>
189
+ encodeDestinationCalls({
190
+ calls: result?.calls ?? [],
191
+ tokenAddress: result?.paymentTokenAddress ?? BASE_WETH,
192
+ sweepTarget: USER_WALLET,
193
+ }),
194
+ ).not.toThrow();
195
+ });
196
+
197
+ it('falls back to order price as native value when the buy step value is empty', () => {
198
+ const result = build({
199
+ buyStep: { value: 0n, price: 0n },
200
+ marketOrder: {
201
+ priceCurrencyAddress: zeroAddress,
202
+ priceAmount: 123n,
203
+ },
204
+ });
205
+
206
+ expect(result?.paymentAmount).toBe(123n);
207
+ expect(result?.calls[1]).toMatchObject({
208
+ to: BUY_TARGET,
209
+ data: '0xabcdef',
210
+ value: 123n,
211
+ });
212
+ });
213
+ });
@@ -0,0 +1,259 @@
1
+ import {
2
+ type Address,
3
+ type ApprovalStep,
4
+ type BuyStep,
5
+ ContractType,
6
+ type Hash,
7
+ MarketplaceKind,
8
+ type Order,
9
+ type OrderbookKind,
10
+ } from '@0xsequence/api-client';
11
+ import { buildErc20Approve, type Call, self } from '0xtrails';
12
+ import { encodeFunctionData, zeroAddress } from 'viem';
13
+ import { getConduitAddressForOrderbook } from '../../../../../utils/getConduitAddressForOrderbook';
14
+ import { normalizeMarketplaceKind } from '../../../../../utils/normalizeMarketplace';
15
+ import { OPENSEA_CHAIN_CURRENCIES } from '../../_internal/constants/opensea-currencies';
16
+
17
+ export type TrailsMarketBuyCall = Call;
18
+
19
+ export type TrailsMarketBuyActions = {
20
+ calls: TrailsMarketBuyCall[];
21
+ paymentTokenAddress: Address;
22
+ paymentAmount: bigint;
23
+ };
24
+
25
+ type BuildTrailsMarketBuyActionsParams = {
26
+ chainId: number;
27
+ buyStep: BuyStep;
28
+ marketOrder: Order;
29
+ contractType: ContractType.ERC721 | ContractType.ERC1155;
30
+ recipientAddress: Address;
31
+ approvalStep?: ApprovalStep;
32
+ quantity?: bigint;
33
+ };
34
+
35
+ const WETH_ABI = [
36
+ {
37
+ type: 'function',
38
+ name: 'withdraw',
39
+ stateMutability: 'nonpayable',
40
+ inputs: [{ name: 'wad', type: 'uint256' }],
41
+ outputs: [],
42
+ },
43
+ ] as const;
44
+
45
+ const ERC721_TRANSFER_FROM_ABI = [
46
+ {
47
+ type: 'function',
48
+ name: 'transferFrom',
49
+ stateMutability: 'nonpayable',
50
+ inputs: [
51
+ { name: 'from', type: 'address' },
52
+ { name: 'to', type: 'address' },
53
+ { name: 'tokenId', type: 'uint256' },
54
+ ],
55
+ outputs: [],
56
+ },
57
+ ] as const;
58
+
59
+ const ERC1155_SAFE_TRANSFER_FROM_ABI = [
60
+ {
61
+ type: 'function',
62
+ name: 'safeTransferFrom',
63
+ stateMutability: 'nonpayable',
64
+ inputs: [
65
+ { name: 'from', type: 'address' },
66
+ { name: 'to', type: 'address' },
67
+ { name: 'id', type: 'uint256' },
68
+ { name: 'amount', type: 'uint256' },
69
+ { name: 'data', type: 'bytes' },
70
+ ],
71
+ outputs: [],
72
+ },
73
+ ] as const;
74
+
75
+ const OPENSEA_FULFILL_WITHOUT_RECIPIENT_SELECTORS = new Set([
76
+ '0xfb0f3ee1', // fulfillBasicOrder(...)
77
+ '0xb1b747c3', // fulfillOrder(...)
78
+ ]);
79
+
80
+ const isNativeCurrency = (currencyAddress: Address) =>
81
+ currencyAddress.toLowerCase() === zeroAddress;
82
+
83
+ const getPaymentAmount = ({
84
+ buyStep,
85
+ marketOrder,
86
+ isErc20Payment,
87
+ }: {
88
+ buyStep: BuyStep;
89
+ marketOrder: Order;
90
+ isErc20Payment: boolean;
91
+ }) => {
92
+ if (isErc20Payment) {
93
+ return buyStep.price > 0n ? buyStep.price : marketOrder.priceAmount;
94
+ }
95
+
96
+ if (buyStep.value > 0n) {
97
+ return buyStep.value;
98
+ }
99
+
100
+ return buyStep.price > 0n ? buyStep.price : marketOrder.priceAmount;
101
+ };
102
+
103
+ const getWrappedNativeCurrencyAddress = (chainId: number) =>
104
+ OPENSEA_CHAIN_CURRENCIES[chainId.toString()]?.wrappedNativeCurrency.address as
105
+ | Address
106
+ | undefined;
107
+
108
+ const buildWrappedNativeWithdrawCall = ({
109
+ wrappedNativeAddress,
110
+ amount,
111
+ }: {
112
+ wrappedNativeAddress: Address;
113
+ amount: bigint;
114
+ }): TrailsMarketBuyCall => ({
115
+ to: wrappedNativeAddress,
116
+ data: encodeFunctionData({
117
+ abi: WETH_ABI,
118
+ functionName: 'withdraw',
119
+ args: [amount],
120
+ }),
121
+ });
122
+
123
+ const isOpenSeaFulfillWithoutRecipient = (calldata: Hash) =>
124
+ OPENSEA_FULFILL_WITHOUT_RECIPIENT_SELECTORS.has(
125
+ calldata.slice(0, 10).toLowerCase(),
126
+ );
127
+
128
+ const buildOpenSeaNftTransferCall = ({
129
+ marketOrder,
130
+ contractType,
131
+ recipientAddress,
132
+ quantity,
133
+ }: {
134
+ marketOrder: Order;
135
+ contractType: ContractType.ERC721 | ContractType.ERC1155;
136
+ recipientAddress: Address;
137
+ quantity: bigint;
138
+ }): TrailsMarketBuyCall | undefined => {
139
+ if (marketOrder.tokenId === undefined) {
140
+ return undefined;
141
+ }
142
+
143
+ const collectionAddress = marketOrder.collectionContractAddress as Address;
144
+ const executorAddress = self();
145
+
146
+ if (contractType === ContractType.ERC1155) {
147
+ return {
148
+ to: collectionAddress,
149
+ data: encodeFunctionData({
150
+ abi: ERC1155_SAFE_TRANSFER_FROM_ABI,
151
+ functionName: 'safeTransferFrom',
152
+ args: [
153
+ executorAddress,
154
+ recipientAddress,
155
+ marketOrder.tokenId,
156
+ quantity,
157
+ '0x',
158
+ ],
159
+ }),
160
+ };
161
+ }
162
+
163
+ return {
164
+ to: collectionAddress,
165
+ data: encodeFunctionData({
166
+ abi: ERC721_TRANSFER_FROM_ABI,
167
+ functionName: 'transferFrom',
168
+ args: [executorAddress, recipientAddress, marketOrder.tokenId],
169
+ }),
170
+ };
171
+ };
172
+
173
+ export function buildTrailsMarketBuyActions({
174
+ chainId,
175
+ buyStep,
176
+ marketOrder,
177
+ contractType,
178
+ recipientAddress,
179
+ approvalStep,
180
+ quantity = 1n,
181
+ }: BuildTrailsMarketBuyActionsParams): TrailsMarketBuyActions | undefined {
182
+ const calls: TrailsMarketBuyCall[] = [];
183
+ const currencyAddress = marketOrder.priceCurrencyAddress;
184
+ const isErc20Payment = !isNativeCurrency(currencyAddress);
185
+ const paymentAmount = getPaymentAmount({
186
+ buyStep,
187
+ marketOrder,
188
+ isErc20Payment,
189
+ });
190
+ let paymentTokenAddress = currencyAddress;
191
+
192
+ if (isErc20Payment && paymentAmount > 0n) {
193
+ if (approvalStep) {
194
+ calls.push({
195
+ to: approvalStep.to,
196
+ data: approvalStep.data,
197
+ });
198
+ } else {
199
+ const spenderAddress = getConduitAddressForOrderbook(
200
+ marketOrder.marketplace as unknown as OrderbookKind,
201
+ );
202
+
203
+ if (spenderAddress) {
204
+ calls.push(
205
+ buildErc20Approve({
206
+ tokenAddress: currencyAddress,
207
+ spender: spenderAddress,
208
+ amount: paymentAmount,
209
+ }),
210
+ );
211
+ }
212
+ }
213
+ } else if (!isErc20Payment) {
214
+ const wrappedNativeAddress = getWrappedNativeCurrencyAddress(chainId);
215
+ if (!wrappedNativeAddress) {
216
+ return undefined;
217
+ }
218
+
219
+ paymentTokenAddress = wrappedNativeAddress;
220
+ if (paymentAmount > 0n) {
221
+ calls.push(
222
+ buildWrappedNativeWithdrawCall({
223
+ wrappedNativeAddress,
224
+ amount: paymentAmount,
225
+ }),
226
+ );
227
+ }
228
+ }
229
+
230
+ calls.push({
231
+ to: buyStep.to,
232
+ data: buyStep.data,
233
+ ...(!isErc20Payment && paymentAmount > 0n
234
+ ? { value: paymentAmount, sweepTokens: [zeroAddress] }
235
+ : {}),
236
+ });
237
+
238
+ const isOpenSeaOrder =
239
+ normalizeMarketplaceKind(marketOrder.marketplace) ===
240
+ MarketplaceKind.opensea;
241
+ if (isOpenSeaOrder && isOpenSeaFulfillWithoutRecipient(buyStep.data)) {
242
+ const nftTransferCall = buildOpenSeaNftTransferCall({
243
+ marketOrder,
244
+ contractType,
245
+ recipientAddress,
246
+ quantity,
247
+ });
248
+
249
+ if (nftTransferCall) {
250
+ calls.push(nftTransferCall);
251
+ }
252
+ }
253
+
254
+ return {
255
+ calls,
256
+ paymentTokenAddress,
257
+ paymentAmount,
258
+ };
259
+ }
@@ -1,6 +1,14 @@
1
- import { ContractType, type Hash } from '@0xsequence/api-client';
2
- import { useSupportedChains } from '0xtrails';
1
+ import {
2
+ type Address,
3
+ ContractType,
4
+ findApprovalStep,
5
+ findBuyStep,
6
+ type Hash,
7
+ } from '@0xsequence/api-client';
8
+ import { encodeDestinationCalls, useSupportedChains } from '0xtrails';
9
+ import { useMemo } from 'react';
3
10
  import { type Chain, formatUnits } from 'viem';
11
+ import { useAccount } from 'wagmi';
4
12
  import { TransactionType } from '../../../../_internal';
5
13
  import { useConfig } from '../../../../hooks';
6
14
  import { useBuyTransaction } from '../../../../hooks/transactions/useBuyTransaction';
@@ -10,15 +18,25 @@ import { useTransactionStatusModal } from '../../_internal/components/transactio
10
18
  import { useBuyModal } from '..';
11
19
  import { useBuyModalData } from '../hooks/useBuyModalData';
12
20
  import { useBuyModalProps } from '../store';
21
+ import { buildTrailsMarketBuyActions } from './buildTrailsMarketBuyActions';
13
22
  import { determineCheckoutMode } from './determineCheckoutMode';
14
23
 
24
+ type TrailsDestination = {
25
+ recipient: Address;
26
+ destinationCalldata: Hash;
27
+ paymentTokenAddress: Address;
28
+ paymentAmount: bigint;
29
+ };
30
+
15
31
  export function useBuyModalContext() {
16
32
  const config = useConfig();
17
33
  const modalProps = useBuyModalProps();
18
34
  const checkoutModeConfig: CheckoutMode = config.checkoutMode ?? 'trails';
19
35
  const { close } = useBuyModal();
20
36
  const transactionStatusModal = useTransactionStatusModal();
21
- const { supportedChains, isLoadingChains } = useSupportedChains();
37
+ const { address: userWalletAddress } = useAccount();
38
+ const { data: supportedChains = [], isLoading: isLoadingChains } =
39
+ useSupportedChains();
22
40
 
23
41
  const {
24
42
  collectible,
@@ -34,16 +52,18 @@ export function useBuyModalContext() {
34
52
  refetchQueries,
35
53
  } = useBuyModalData();
36
54
 
55
+ const contractType =
56
+ collection?.type === ContractType.ERC1155
57
+ ? ContractType.ERC1155
58
+ : ContractType.ERC721;
59
+
37
60
  const transactionData = useBuyTransaction({
38
61
  modalProps,
39
62
  primarySalePrice: {
40
63
  amount: primarySaleItem?.priceAmount,
41
64
  currencyAddress: primarySaleItem?.currencyAddress,
42
65
  },
43
- contractType:
44
- collection?.type === ContractType.ERC1155
45
- ? ContractType.ERC1155
46
- : ContractType.ERC721,
66
+ contractType,
47
67
  });
48
68
  const steps = transactionData.data?.steps;
49
69
  const canBeUsedWithTrails =
@@ -58,7 +78,47 @@ export function useBuyModalContext() {
58
78
 
59
79
  const isLoading = isLoadingSteps || isLoadingChains || isBuyModalDataLoading;
60
80
 
61
- const buyStep = steps?.find((step) => step.id === 'buy');
81
+ const buyStep = steps ? findBuyStep(steps) : undefined;
82
+ const approvalStep = steps ? findApprovalStep(steps) : undefined;
83
+
84
+ const trailsDestination = useMemo<TrailsDestination | undefined>(() => {
85
+ if (!isMarket || !marketOrder || !buyStep || !userWalletAddress) {
86
+ return undefined;
87
+ }
88
+
89
+ const trailsMarketBuyActions = buildTrailsMarketBuyActions({
90
+ chainId: modalProps.chainId,
91
+ buyStep,
92
+ marketOrder,
93
+ contractType,
94
+ recipientAddress: userWalletAddress,
95
+ approvalStep,
96
+ });
97
+ if (!trailsMarketBuyActions) {
98
+ return undefined;
99
+ }
100
+
101
+ const destination = encodeDestinationCalls({
102
+ calls: trailsMarketBuyActions.calls,
103
+ tokenAddress: trailsMarketBuyActions.paymentTokenAddress,
104
+ sweepTarget: userWalletAddress,
105
+ });
106
+
107
+ return {
108
+ recipient: destination.recipient,
109
+ destinationCalldata: destination.destinationCalldata,
110
+ paymentTokenAddress: trailsMarketBuyActions.paymentTokenAddress,
111
+ paymentAmount: trailsMarketBuyActions.paymentAmount,
112
+ };
113
+ }, [
114
+ approvalStep,
115
+ buyStep,
116
+ contractType,
117
+ isMarket,
118
+ marketOrder,
119
+ modalProps.chainId,
120
+ userWalletAddress,
121
+ ]);
62
122
 
63
123
  const checkoutMode = determineCheckoutMode({
64
124
  checkoutModeConfig,
@@ -66,9 +126,16 @@ export function useBuyModalContext() {
66
126
  canBeUsedWithTrails,
67
127
  });
68
128
 
129
+ const paymentAmount =
130
+ trailsDestination?.paymentAmount ??
131
+ (buyStep
132
+ ? buyStep.price > 0n
133
+ ? buyStep.price
134
+ : buyStep.value
135
+ : undefined);
69
136
  const formattedAmount =
70
- currency?.decimals && buyStep?.price
71
- ? formatUnits(BigInt(buyStep.price), currency.decimals)
137
+ currency?.decimals !== undefined && paymentAmount !== undefined
138
+ ? formatUnits(paymentAmount, currency.decimals)
72
139
  : undefined;
73
140
 
74
141
  const handleTransactionSuccess = (hash: Hash) => {
@@ -131,8 +198,10 @@ export function useBuyModalContext() {
131
198
  collection,
132
199
  primarySaleItem,
133
200
  marketOrder,
201
+ isMarket,
134
202
  isShop,
135
203
  buyStep,
204
+ trailsDestination,
136
205
  isLoading,
137
206
  checkoutMode,
138
207
  formattedAmount,