@0xsequence/marketplace-sdk 2.0.1 → 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 +10 -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 +1 -1
- package/dist/collection.js +1 -1
- package/dist/create-config.d.ts +597 -201
- package/dist/create-config.js +1 -1
- package/dist/currency.js +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 +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index10.d.ts +3 -3
- package/dist/index11.d.ts +17 -17
- package/dist/index12.d.ts +26 -26
- 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 +86 -86
- 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 +4 -4
- package/dist/index35.d.ts +1 -1
- package/dist/index36.d.ts +7 -7
- package/dist/index37.d.ts +6 -6
- package/dist/index38.d.ts +8 -8
- package/dist/index39.d.ts +1 -1
- package/dist/index4.d.ts +995 -995
- package/dist/index40.d.ts +2 -2
- package/dist/index8.d.ts +2 -2
- package/dist/index9.d.ts +2811 -3
- package/dist/inventory.d.ts +4 -4
- package/dist/inventory.js +2 -2
- package/dist/marketplace2.js +2 -2
- package/dist/metadata.d.ts +73 -73
- package/dist/primary-sale-checkout-options.d.ts +4 -4
- package/dist/quantityInput.js +1 -1
- package/dist/ranges.d.ts +17 -17
- 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 +1 -1
- package/dist/react/ssr/index.js +2 -2
- package/dist/react/ui/components/marketplace-collectible-card/index.d.ts +1 -1
- package/dist/react/ui/components/marketplace-logos/index.d.ts +21 -21
- 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/calendarDropdown/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 -3
- 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 +2192 -1903
- package/dist/react.js.map +1 -1
- 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.js +1 -1
- package/dist/utils.js +20 -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/transactions/useCancelTransactionSteps.tsx +4 -1
- package/src/react/hooks/utils/useEnsureCorrectChain.ts +10 -5
- package/src/react/ui/modals/BuyModal/components/BuyModalContent.tsx +34 -31
- 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/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/MakeOfferModal/internal/context.ts +2 -1
- package/src/react/ui/modals/_internal/components/currencyOptionsSelect/index.tsx +2 -1
- package/src/react/ui/modals/_internal/components/priceInput/index.tsx +12 -11
- 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/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/getConduitAddressForOrderbook.ts +2 -10
- package/src/utils/getMarketplaceDetails.ts +11 -4
- package/src/utils/getWebRPCErrorMessage.ts +21 -0
- package/src/utils/normalizeMarketplace.ts +31 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Button, Spinner, Text } from '@0xsequence/design-system';
|
|
4
|
-
import { useState } from 'react';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
5
|
import type { Hex } from 'viem';
|
|
6
6
|
import { getPresentableChainName } from '../../../../../utils/network';
|
|
7
7
|
import type { Step } from '../../../../_internal';
|
|
@@ -39,13 +39,36 @@ export const CryptoPaymentModal = ({
|
|
|
39
39
|
} = useCryptoPaymentModalContext({ chainId, steps, onSuccess });
|
|
40
40
|
|
|
41
41
|
const { ensureCorrectChainAsync } = useEnsureCorrectChain();
|
|
42
|
+
const [pendingAction, setPendingAction] = useState<'approval' | 'buy' | null>(
|
|
43
|
+
null,
|
|
44
|
+
);
|
|
42
45
|
const [chainSwitchError, setChainSwitchError] = useState<{
|
|
43
46
|
title: string;
|
|
44
47
|
message: string;
|
|
45
48
|
details?: Error;
|
|
46
49
|
} | null>(null);
|
|
50
|
+
const isPendingApprovalReady =
|
|
51
|
+
pendingAction === 'approval' && isOnCorrectChain && canApprove;
|
|
52
|
+
const isPendingBuyReady = pendingAction === 'buy' && isOnCorrectChain && canBuy;
|
|
53
|
+
const isPendingAction = pendingAction !== null;
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!isPendingApprovalReady && !isPendingBuyReady) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setPendingAction(null);
|
|
61
|
+
|
|
62
|
+
void (pendingAction === 'approval' ? executeApproval() : executeBuy());
|
|
63
|
+
}, [
|
|
64
|
+
executeApproval,
|
|
65
|
+
executeBuy,
|
|
66
|
+
isPendingApprovalReady,
|
|
67
|
+
isPendingBuyReady,
|
|
68
|
+
pendingAction,
|
|
69
|
+
]);
|
|
47
70
|
|
|
48
|
-
const handleChainSwitchError = (error
|
|
71
|
+
const handleChainSwitchError = (error?: Error) => {
|
|
49
72
|
const chainName = getPresentableChainName(chainId);
|
|
50
73
|
setChainSwitchError({
|
|
51
74
|
title: 'Chain switch failed',
|
|
@@ -58,27 +81,59 @@ export const CryptoPaymentModal = ({
|
|
|
58
81
|
setChainSwitchError(null);
|
|
59
82
|
};
|
|
60
83
|
|
|
84
|
+
const executeAction = async (action: 'approval' | 'buy') => {
|
|
85
|
+
setPendingAction(null);
|
|
86
|
+
|
|
87
|
+
if (action === 'approval') {
|
|
88
|
+
await executeApproval();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await executeBuy();
|
|
93
|
+
};
|
|
94
|
+
|
|
61
95
|
const executeWithChainSwitch = async (action: 'approval' | 'buy') => {
|
|
62
96
|
dismissChainSwitchError();
|
|
97
|
+
|
|
98
|
+
if (isOnCorrectChain) {
|
|
99
|
+
await executeAction(action);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setPendingAction(action);
|
|
104
|
+
|
|
63
105
|
try {
|
|
64
|
-
await ensureCorrectChainAsync(chainId);
|
|
106
|
+
const didRequestChainSwitch = await ensureCorrectChainAsync(chainId);
|
|
107
|
+
if (!didRequestChainSwitch) {
|
|
108
|
+
setPendingAction(null);
|
|
109
|
+
handleChainSwitchError();
|
|
110
|
+
}
|
|
65
111
|
} catch (error) {
|
|
112
|
+
setPendingAction(null);
|
|
113
|
+
|
|
66
114
|
if (error instanceof Error) {
|
|
67
115
|
handleChainSwitchError(error);
|
|
116
|
+
} else {
|
|
117
|
+
handleChainSwitchError();
|
|
68
118
|
}
|
|
69
119
|
}
|
|
70
|
-
|
|
71
|
-
if (action === 'approval') {
|
|
72
|
-
await executeApproval();
|
|
73
|
-
} else {
|
|
74
|
-
await executeBuy();
|
|
75
|
-
}
|
|
76
120
|
};
|
|
77
121
|
|
|
122
|
+
const isWaitingForChainSwitch = isPendingAction && !isOnCorrectChain;
|
|
123
|
+
const isPreparingPendingAction = isPendingAction && isOnCorrectChain;
|
|
124
|
+
|
|
78
125
|
const approvalButtonLabel = isApproving ? (
|
|
79
126
|
<div className="flex items-center gap-2">
|
|
80
127
|
<Spinner size="sm" /> Approving Token...
|
|
81
128
|
</div>
|
|
129
|
+
) : isWaitingForChainSwitch && pendingAction === 'approval' ? (
|
|
130
|
+
<div className="flex items-center gap-2">
|
|
131
|
+
<Spinner size="sm" /> Switching Network...
|
|
132
|
+
</div>
|
|
133
|
+
) : isPreparingPendingAction && pendingAction === 'approval' ? (
|
|
134
|
+
<div className="flex items-center gap-2">
|
|
135
|
+
<Spinner size="sm" /> Preparing Approval...
|
|
136
|
+
</div>
|
|
82
137
|
) : (
|
|
83
138
|
'Approve Token'
|
|
84
139
|
);
|
|
@@ -87,6 +142,14 @@ export const CryptoPaymentModal = ({
|
|
|
87
142
|
<div className="flex items-center gap-2">
|
|
88
143
|
<Spinner size="sm" /> Confirming Purchase...
|
|
89
144
|
</div>
|
|
145
|
+
) : isWaitingForChainSwitch && pendingAction === 'buy' ? (
|
|
146
|
+
<div className="flex items-center gap-2">
|
|
147
|
+
<Spinner size="sm" /> Switching Network...
|
|
148
|
+
</div>
|
|
149
|
+
) : isPreparingPendingAction && pendingAction === 'buy' ? (
|
|
150
|
+
<div className="flex items-center gap-2">
|
|
151
|
+
<Spinner size="sm" /> Preparing Purchase...
|
|
152
|
+
</div>
|
|
90
153
|
) : (
|
|
91
154
|
'Buy now'
|
|
92
155
|
);
|
|
@@ -129,7 +192,7 @@ export const CryptoPaymentModal = ({
|
|
|
129
192
|
onClick={async () => {
|
|
130
193
|
await executeWithChainSwitch('approval');
|
|
131
194
|
}}
|
|
132
|
-
disabled={!canApprove}
|
|
195
|
+
disabled={!canApprove || isPendingAction}
|
|
133
196
|
variant="primary"
|
|
134
197
|
size="lg"
|
|
135
198
|
className="w-full"
|
|
@@ -143,7 +206,7 @@ export const CryptoPaymentModal = ({
|
|
|
143
206
|
onClick={async () => {
|
|
144
207
|
await executeWithChainSwitch('buy');
|
|
145
208
|
}}
|
|
146
|
-
disabled={!canBuy}
|
|
209
|
+
disabled={!canBuy || isPendingAction}
|
|
147
210
|
variant="primary"
|
|
148
211
|
size="lg"
|
|
149
212
|
className="w-full"
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
type WagmiAdapterOptions,
|
|
5
|
+
wagmiAdapter,
|
|
6
|
+
} from '@0xtrails/adapter-wagmi';
|
|
7
|
+
import { TrailsProvider } from '0xtrails/widget';
|
|
8
|
+
import { useConfig as useWagmiConfig } from 'wagmi';
|
|
9
|
+
import {
|
|
10
|
+
getSequenceApiUrl,
|
|
11
|
+
getSequenceIndexerUrl,
|
|
12
|
+
getSequenceNodeGatewayUrl,
|
|
13
|
+
getTrailsApiUrl,
|
|
14
|
+
} from '../../../../_internal/api/services';
|
|
15
|
+
import { useConfig } from '../../../../hooks';
|
|
16
|
+
import { useWaasFeeOptions } from '../../../../hooks/utils/useWaasFeeOptions';
|
|
3
17
|
import { useIsOpen } from '../store';
|
|
4
18
|
import { BuyModalContent } from './BuyModalContent';
|
|
5
19
|
|
|
20
|
+
type TrailsUseWaasFeeOptions = NonNullable<
|
|
21
|
+
NonNullable<WagmiAdapterOptions['sequence']>['useWaasFeeOptions']
|
|
22
|
+
>;
|
|
23
|
+
|
|
6
24
|
export const BuyModal = () => {
|
|
7
25
|
const isOpen = useIsOpen();
|
|
8
26
|
|
|
@@ -10,5 +28,48 @@ export const BuyModal = () => {
|
|
|
10
28
|
return null;
|
|
11
29
|
}
|
|
12
30
|
|
|
13
|
-
return <
|
|
31
|
+
return <BuyModalWithTrailsProvider />;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const useTrailsWaasFeeOptions: TrailsUseWaasFeeOptions = ({
|
|
35
|
+
chainIdOverride,
|
|
36
|
+
} = {}) => {
|
|
37
|
+
const config = useConfig();
|
|
38
|
+
const {
|
|
39
|
+
pendingFeeOptionConfirmation,
|
|
40
|
+
confirmPendingFeeOption,
|
|
41
|
+
rejectPendingFeeOption,
|
|
42
|
+
} = useWaasFeeOptions(chainIdOverride ?? 0, config);
|
|
43
|
+
|
|
44
|
+
return [
|
|
45
|
+
pendingFeeOptionConfirmation,
|
|
46
|
+
confirmPendingFeeOption,
|
|
47
|
+
rejectPendingFeeOption,
|
|
48
|
+
];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const BuyModalWithTrailsProvider = () => {
|
|
52
|
+
const config = useConfig();
|
|
53
|
+
const wagmiConfig = useWagmiConfig();
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<TrailsProvider
|
|
57
|
+
config={{
|
|
58
|
+
trailsApiKey: config.projectAccessKey,
|
|
59
|
+
trailsApiUrl: getTrailsApiUrl(config),
|
|
60
|
+
sequenceIndexerUrl: getSequenceIndexerUrl(config),
|
|
61
|
+
sequenceNodeGatewayUrl: getSequenceNodeGatewayUrl(config),
|
|
62
|
+
sequenceApiUrl: getSequenceApiUrl(config),
|
|
63
|
+
walletConnectProjectId: config.walletConnectProjectId,
|
|
64
|
+
adapters: [
|
|
65
|
+
wagmiAdapter({
|
|
66
|
+
wagmiConfig,
|
|
67
|
+
sequence: { useWaasFeeOptions: useTrailsWaasFeeOptions },
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<BuyModalContent />
|
|
73
|
+
</TrailsProvider>
|
|
74
|
+
);
|
|
14
75
|
};
|
|
@@ -5,7 +5,6 @@ import type { Hex } from 'viem';
|
|
|
5
5
|
import { useAccount, usePublicClient, useWalletClient } from 'wagmi';
|
|
6
6
|
import { useConfig } from '../../../..';
|
|
7
7
|
import { getIndexerClient, type Step } from '../../../../_internal';
|
|
8
|
-
import { useBuyModalData } from './useBuyModalData';
|
|
9
8
|
|
|
10
9
|
// https://github.com/0xsequence/web-sdk/blob/620b6fe7681ae49efd4eb3fa7607ef01dd7ede54/packages/connect/src/utils/transactions.ts#L11-L19
|
|
11
10
|
class FeeOptionInsufficientFundsError extends Error {
|
|
@@ -21,31 +20,25 @@ class FeeOptionInsufficientFundsError extends Error {
|
|
|
21
20
|
type UseExecuteBundledTransactions = {
|
|
22
21
|
chainId: number;
|
|
23
22
|
approvalStep?: Step;
|
|
24
|
-
priceAmount: bigint;
|
|
25
23
|
};
|
|
26
24
|
|
|
27
25
|
const useExecuteBundledTransactions = ({
|
|
28
26
|
chainId,
|
|
29
27
|
approvalStep,
|
|
30
|
-
priceAmount,
|
|
31
28
|
}: UseExecuteBundledTransactions) => {
|
|
32
29
|
const config = useConfig();
|
|
33
30
|
const [isExecuting, setIsExecuting] = useState(false);
|
|
34
31
|
const { address, connector } = useAccount();
|
|
35
|
-
const publicClient = usePublicClient();
|
|
32
|
+
const publicClient = usePublicClient({ chainId });
|
|
36
33
|
const { data: walletClient } = useWalletClient();
|
|
37
34
|
const indexerClient = getIndexerClient(chainId, config);
|
|
38
35
|
|
|
39
|
-
const { collection, currency } = useBuyModalData();
|
|
40
|
-
|
|
41
36
|
const isReady =
|
|
42
37
|
!!address &&
|
|
43
38
|
!!publicClient &&
|
|
44
39
|
!!walletClient &&
|
|
45
40
|
!!indexerClient &&
|
|
46
|
-
!!connector
|
|
47
|
-
!!collection?.address &&
|
|
48
|
-
priceAmount != null;
|
|
41
|
+
!!connector;
|
|
49
42
|
|
|
50
43
|
const executeBundledTransactions = async ({
|
|
51
44
|
step,
|
|
@@ -79,28 +72,22 @@ const useExecuteBundledTransactions = ({
|
|
|
79
72
|
throw new Error('Connector not found');
|
|
80
73
|
}
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
75
|
+
const buildTransaction = (step: Step) => {
|
|
76
|
+
const value = step.value ? BigInt(step.value) : 0n;
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
return {
|
|
79
|
+
to: step.to,
|
|
80
|
+
data: step.data as Hex,
|
|
81
|
+
chainId,
|
|
82
|
+
...(value > 0n ? { value } : {}),
|
|
83
|
+
};
|
|
84
|
+
};
|
|
89
85
|
|
|
90
86
|
const approvalData = approvalStep
|
|
91
|
-
?
|
|
92
|
-
to: approvalStep.to,
|
|
93
|
-
data: approvalStep.data as Hex,
|
|
94
|
-
chainId,
|
|
95
|
-
}
|
|
87
|
+
? buildTransaction(approvalStep)
|
|
96
88
|
: undefined;
|
|
97
89
|
|
|
98
|
-
const transactionData =
|
|
99
|
-
to: step.to,
|
|
100
|
-
data: step.data as Hex,
|
|
101
|
-
chainId,
|
|
102
|
-
...(currency?.nativeCurrency ? { value: priceAmount } : {}),
|
|
103
|
-
};
|
|
90
|
+
const transactionData = buildTransaction(step);
|
|
104
91
|
|
|
105
92
|
const transactions = [
|
|
106
93
|
...(approvalData ? [approvalData] : []),
|
|
@@ -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
|
+
});
|