@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.
- package/CHANGELOG.md +18 -0
- package/dist/BellIcon.js +1 -1
- package/dist/Card.js +1 -1
- package/dist/ShopCard.d.ts +4 -4
- package/dist/builder-api.js +1 -1
- package/dist/collectible.js +2 -2
- package/dist/collectible.js.map +1 -1
- package/dist/collection.js +1 -1
- package/dist/create-config.d.ts +589 -193
- package/dist/create-config.js +1 -1
- package/dist/currency.js +3 -3
- package/dist/currency.js.map +1 -1
- package/dist/dist.js +167 -148
- package/dist/dist.js.map +1 -1
- package/dist/expirationDateSelect.js +1 -1
- package/dist/filter-state.d.ts +1 -1
- package/dist/filters.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index10.d.ts +1 -1
- package/dist/index11.d.ts +17 -17
- package/dist/index12.d.ts +21 -21
- package/dist/index14.d.ts +3 -3
- package/dist/index15.d.ts +3 -3
- package/dist/index16.d.ts +2 -2
- package/dist/index17.d.ts +75 -75
- package/dist/index18.d.ts +40 -40
- package/dist/index19.d.ts +5 -5
- package/dist/index2.d.ts +4 -1
- package/dist/index21.d.ts +15 -15
- package/dist/index22.d.ts +8 -65
- package/dist/index23.d.ts +21 -13
- package/dist/index26.d.ts +4 -4
- package/dist/index27.d.ts +4 -4
- package/dist/index28.d.ts +10 -10
- package/dist/index3.d.ts +2 -2194
- package/dist/index31.d.ts +5 -5
- package/dist/index33.d.ts +3 -3
- package/dist/index34.d.ts +1 -1
- package/dist/index35.d.ts +1 -1
- package/dist/index36.d.ts +5 -5
- package/dist/index37.d.ts +8 -6
- package/dist/index38.d.ts +5 -5
- package/dist/index39.d.ts +1 -1
- package/dist/index4.d.ts +1356 -1356
- package/dist/index40.d.ts +2 -2
- package/dist/index8.d.ts +11 -3
- package/dist/index9.d.ts +2811 -3
- package/dist/inventory.d.ts +4 -4
- package/dist/inventory.js +3 -3
- package/dist/inventory.js.map +1 -1
- package/dist/marketplace2.js +3 -3
- package/dist/marketplace2.js.map +1 -1
- package/dist/metadata.d.ts +41 -41
- package/dist/primary-sale-checkout-options.d.ts +4 -4
- package/dist/quantityInput.js +1 -1
- package/dist/ranges.d.ts +12 -12
- package/dist/react/_internal/index.d.ts +1 -1
- package/dist/react/_internal/index.js +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/queries/collectible/index.d.ts +1 -1
- package/dist/react/queries/index.d.ts +1 -1
- package/dist/react/ssr/index.d.ts +3 -3
- package/dist/react/ssr/index.js +3 -3
- package/dist/react/ui/components/marketplace-collectible-card/index.d.ts +1 -1
- package/dist/react/ui/modals/CreateListingModal/internal/hooks/index.d.ts +1 -1
- package/dist/react/ui/modals/MakeOfferModal/internal/hooks/index.d.ts +1 -1
- package/dist/react/ui/modals/_internal/components/alertMessage/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/baseModal/index.d.ts +6 -6
- package/dist/react/ui/modals/_internal/components/calendar/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/currencyImage/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/currencyOptionsSelect/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/floorPriceText/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/priceInput/index.d.ts +3 -5
- package/dist/react/ui/modals/_internal/components/quantityInput/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/selectWaasFeeOptions/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/switchChainErrorModal/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/timeAgo/index.d.ts +2 -2
- package/dist/react/ui/modals/_internal/components/tokenPreview/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/transaction-footer/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/transactionDetails/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/transactionPreview/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/transactionStatusModal/index.d.ts +3 -3
- package/dist/react.js +2279 -1919
- package/dist/react.js.map +1 -1
- package/dist/styles/index.css +15 -0
- package/dist/token-balances.d.ts +28 -28
- package/dist/transaction-footer.js +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/url-state.js +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +2 -2
- package/dist/utils.js +31 -4
- package/dist/utils.js.map +1 -1
- package/package.json +7 -5
- package/src/react/hooks/config/useMarketplaceConfig.test.tsx +1 -0
- package/src/react/hooks/currency/list.test.tsx +23 -2
- package/src/react/hooks/transactions/useCancelTransactionSteps.tsx +4 -1
- package/src/react/hooks/transactions/useMarketTransactionSteps.tsx +55 -15
- package/src/react/hooks/utils/useEnsureCorrectChain.ts +10 -5
- package/src/react/queries/collectible/market-list.ts +5 -3
- package/src/react/queries/currency/list.ts +8 -5
- package/src/react/queries/inventory/inventory.ts +5 -3
- package/src/react/queries/marketplace/filters.ts +5 -3
- package/src/react/ui/modals/BuyModal/components/BuyModalContent.tsx +74 -37
- package/src/react/ui/modals/BuyModal/components/CryptoPaymentModal.tsx +74 -11
- package/src/react/ui/modals/BuyModal/components/Modal.tsx +62 -1
- package/src/react/ui/modals/BuyModal/hooks/useExecuteBundledTransactions.ts +13 -26
- package/src/react/ui/modals/BuyModal/hooks/useMarketPlatformFee.ts +5 -5
- package/src/react/ui/modals/BuyModal/internal/__tests__/buildTrailsMarketBuyActions.test.ts +213 -0
- package/src/react/ui/modals/BuyModal/internal/buildTrailsMarketBuyActions.ts +259 -0
- package/src/react/ui/modals/BuyModal/internal/buyModalContext.ts +79 -10
- package/src/react/ui/modals/BuyModal/internal/cryptoPaymentModalContext.tsx +44 -17
- package/src/react/ui/modals/CreateListingModal/internal/store.ts +5 -2
- package/src/react/ui/modals/MakeOfferModal/internal/context.ts +21 -1
- package/src/react/ui/modals/MakeOfferModal/internal/helpers/validation.ts +16 -1
- package/src/react/ui/modals/MakeOfferModal/internal/store.ts +5 -2
- package/src/react/ui/modals/SellModal/internal/store.ts +5 -2
- package/src/react/ui/modals/_internal/components/baseModal/errors/ModalInitializationError.tsx +8 -6
- package/src/react/ui/modals/_internal/components/currencyOptionsSelect/index.tsx +2 -1
- package/src/react/ui/modals/_internal/components/priceInput/index.tsx +13 -19
- package/src/react/ui/modals/_internal/components/transactionDetails/index.tsx +5 -2
- package/src/react/ui/modals/_internal/helpers/currency.test.ts +27 -0
- package/src/react/ui/modals/_internal/helpers/currency.ts +4 -2
- package/src/styles/styles.ts +18 -0
- package/src/utils/__tests__/getMarketplaceDetails.test.ts +10 -0
- package/src/utils/__tests__/getWebRPCErrorMessage.test.ts +28 -0
- package/src/utils/__tests__/marketplaceNormalization.test.ts +38 -0
- package/src/utils/collection.ts +19 -0
- package/src/utils/getConduitAddressForOrderbook.ts +2 -10
- package/src/utils/getMarketplaceDetails.ts +11 -4
- package/src/utils/getWebRPCErrorMessage.ts +21 -0
- package/src/utils/index.ts +1 -0
- 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 {
|
|
2
|
-
|
|
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 {
|
|
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
|
|
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 &&
|
|
71
|
-
? formatUnits(
|
|
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,
|