Skip to content

Commit fbcb1f6

Browse files
authored
chore(passport): ID-4038: Improved Passport sdk-sample-app seaport examples (#2731)
1 parent 3ecb26e commit fbcb1f6

File tree

7 files changed

+216
-18
lines changed

7 files changed

+216
-18
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import React, {
2+
useCallback, useEffect, useMemo, useState,
3+
} from 'react';
4+
import {
5+
Accordion, Form, Spinner, Stack,
6+
} from 'react-bootstrap';
7+
import WorkflowButton from '@/components/WorkflowButton';
8+
import { RequestExampleProps } from '@/types';
9+
import { useImmutableProvider } from '@/context/ImmutableProvider';
10+
import { usePassportProvider } from '@/context/PassportProvider';
11+
import { PreparedTransactionRequest } from 'ethers';
12+
13+
function SeaportCancel({ disabled, handleExampleSubmitted }: RequestExampleProps) {
14+
const { orderbookClient } = useImmutableProvider();
15+
const { zkEvmProvider } = usePassportProvider();
16+
17+
const [orderIds, setOrderIds] = useState<string>('');
18+
const [walletAddress, setWalletAddress] = useState<string>('');
19+
const [isBuildingTransaction, setIsBuildingTransaction] = useState<boolean>(false);
20+
const [transaction, setTransaction] = useState<PreparedTransactionRequest>();
21+
const [transactionError, setTransactionError] = useState<string>('');
22+
23+
const seaportContractAddress = useMemo(
24+
() => (
25+
orderbookClient.config().seaportContractAddress
26+
),
27+
[orderbookClient],
28+
);
29+
30+
useEffect(() => {
31+
const getAddress = async () => {
32+
if (zkEvmProvider) {
33+
const [address] = await zkEvmProvider.request({
34+
method: 'eth_requestAccounts',
35+
});
36+
setWalletAddress(address || '');
37+
}
38+
};
39+
40+
getAddress().catch(console.log);
41+
}, [zkEvmProvider, setWalletAddress]);
42+
43+
useEffect(() => {
44+
setTransactionError('');
45+
setTransaction(undefined);
46+
}, [orderIds]);
47+
48+
const validate = useCallback(async () => {
49+
setTransactionError('');
50+
setTransaction(undefined);
51+
setIsBuildingTransaction(true);
52+
53+
try {
54+
const { cancellationAction } = await orderbookClient.cancelOrdersOnChain(
55+
orderIds.replaceAll(' ', '').split(','),
56+
walletAddress,
57+
);
58+
59+
setTransaction(await cancellationAction.buildTransaction());
60+
} catch (err) {
61+
setTransactionError(`Failed to build Seaport cancellation transaction: ${err}`);
62+
} finally {
63+
setIsBuildingTransaction(false);
64+
}
65+
}, [orderIds, orderbookClient, walletAddress]);
66+
67+
const handleSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement>) => {
68+
e.preventDefault();
69+
e.stopPropagation();
70+
71+
return handleExampleSubmitted({
72+
method: 'eth_sendTransaction',
73+
params: [transaction],
74+
});
75+
}, [handleExampleSubmitted, transaction]);
76+
77+
return (
78+
<Accordion.Item eventKey="7">
79+
<Accordion.Header>Seaport Cancel</Accordion.Header>
80+
<Accordion.Body>
81+
<Form onSubmit={handleSubmit} className="mb-4">
82+
<Form.Group className="mb-3">
83+
<Form.Label>
84+
Seaport Contract Address
85+
</Form.Label>
86+
<Form.Control
87+
required
88+
disabled
89+
type="text"
90+
value={seaportContractAddress}
91+
/>
92+
</Form.Group>
93+
<Form.Group className="mb-3">
94+
<Form.Label>
95+
Order IDs (comma separated)
96+
</Form.Label>
97+
<Form.Control
98+
required
99+
type="text"
100+
value={orderIds}
101+
isValid={transaction && !transactionError}
102+
isInvalid={!!transactionError || !orderIds}
103+
onChange={(e) => setOrderIds(e.target.value)}
104+
/>
105+
<Form.Control.Feedback type="invalid">
106+
{transactionError}
107+
</Form.Control.Feedback>
108+
</Form.Group>
109+
<Stack direction="horizontal" gap={3}>
110+
<WorkflowButton
111+
disabled={disabled || isBuildingTransaction || !!transaction}
112+
type="button"
113+
onClick={validate}
114+
>
115+
Validate
116+
</WorkflowButton>
117+
<WorkflowButton
118+
disabled={disabled || !transaction}
119+
type="submit"
120+
>
121+
Submit
122+
</WorkflowButton>
123+
{ isBuildingTransaction && <Spinner /> }
124+
</Stack>
125+
</Form>
126+
</Accordion.Body>
127+
</Accordion.Item>
128+
);
129+
}
130+
131+
export default SeaportCancel;

packages/passport/sdk-sample-app/src/components/zkevm/EthSendTransactionExamples/SeaportFulfillAvailableAdvancedOrders.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function SeaportFulfillAvailableAdvancedOrders({ disabled, handleExampleSubmitte
5353

5454
try {
5555
const fulfillResponse = await orderbookClient.fulfillBulkOrders(
56-
listingIds.replace(' ', '').split(',').map((orderId) => ({
56+
listingIds.replaceAll(' ', '').split(',').map((orderId) => ({
5757
listingId: orderId,
5858
takerFees: [],
5959
})),

packages/passport/sdk-sample-app/src/components/zkevm/EthSendTransactionExamples/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import TransferImx from './TransferImx';
22
import SpendingCapApproval from './SpendingCapApproval';
33
import NFTApproval from './NFTApproval';
44
import SeaportFulfillAvailableAdvancedOrders from './SeaportFulfillAvailableAdvancedOrders';
5+
import SeaportCancel from './SeaportCancel';
56
import ShowGenericConfirmationScreen from './DefaultTransaction';
67
import TransferERC20 from './TransferERC20';
78
import NFTTransfer from './NFTTransfer';
@@ -14,6 +15,7 @@ const EthSendTransactionExamples = [
1415
NFTApproval,
1516
NFTTransfer,
1617
SeaportFulfillAvailableAdvancedOrders,
18+
SeaportCancel,
1719
];
1820

1921
export default EthSendTransactionExamples;

packages/passport/sdk-sample-app/src/components/zkevm/EthSignTypedDataV4Examples/SeaportCreateListing.tsx

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import { RequestExampleProps } from '@/types';
99
import { useImmutableProvider } from '@/context/ImmutableProvider';
1010
import { usePassportProvider } from '@/context/PassportProvider';
1111
import {
12-
ActionType, ERC1155Item, ERC721Item, SignableAction, SignablePurpose,
12+
ActionType, ERC1155Item, ERC721Item, CreateListingParams, SignableAction, SignablePurpose,
13+
TransactionAction,
14+
TransactionPurpose,
1315
} from '@imtbl/orderbook';
14-
import { TypedDataEncoder } from 'ethers';
16+
import { BrowserProvider, TypedDataEncoder } from 'ethers';
1517

1618
type TokenType = 'NATIVE' | 'ERC20';
1719

@@ -32,6 +34,9 @@ function SeaportCreateListing({ disabled, handleExampleSubmitted }: RequestExamp
3234
const [sellTokenUnits, setSellTokenUnits] = useState<string>('1');
3335
const [showSellTokenUnitsField, setShowSellTokenUnitsField] = useState(false);
3436
const [sellTokenType, setSellTokenType] = useState<'ERC721' | 'ERC1155'>('ERC721');
37+
const [submitTransaction, setSubmitTransaction] = useState<boolean>(false);
38+
const [orderComponents, setOrderComponents] = useState<CreateListingParams['orderComponents'] | undefined>();
39+
const [orderHash, setOrderHash] = useState<string>('');
3540

3641
const seaportContractAddress = useMemo(
3742
() => (
@@ -67,6 +72,9 @@ function SeaportCreateListing({ disabled, handleExampleSubmitted }: RequestExamp
6772
setBuyType('NATIVE');
6873
setSellTokenUnits('1');
6974
setShowSellTokenUnitsField(false);
75+
setSubmitTransaction(false);
76+
setOrderComponents(undefined);
77+
setOrderHash('');
7078
};
7179

7280
const validate = useCallback(async () => {
@@ -92,13 +100,36 @@ function SeaportCreateListing({ disabled, handleExampleSubmitted }: RequestExamp
92100
throw new Error('Units for sale must be greater than 0');
93101
}
94102

95-
const { actions } = await orderbookClient.prepareListing({
103+
const preparedListing = await orderbookClient.prepareListing({
96104
makerAddress: walletAddress,
97105
buy,
98106
sell,
99107
});
100108

101-
const signAction = actions.find((action) => (
109+
if (submitTransaction) {
110+
const approvalActions = preparedListing.actions.filter((action) => (
111+
action.type === ActionType.TRANSACTION && action.purpose === TransactionPurpose.APPROVAL
112+
)) as TransactionAction[];
113+
114+
for (const approvalAction of approvalActions) {
115+
// eslint-disable-next-line no-await-in-loop
116+
const unsignedTx = await approvalAction.buildTransaction();
117+
118+
// eslint-disable-next-line no-await-in-loop
119+
const transactionHash = await zkEvmProvider!.request({
120+
method: 'eth_sendTransaction',
121+
params: [unsignedTx],
122+
});
123+
124+
// eslint-disable-next-line no-await-in-loop
125+
await new BrowserProvider(zkEvmProvider!).waitForTransaction(transactionHash);
126+
}
127+
128+
setOrderComponents(preparedListing.orderComponents);
129+
setOrderHash(preparedListing.orderHash);
130+
}
131+
132+
const signAction = preparedListing.actions.find((action) => (
102133
action.type === ActionType.SIGNABLE && action.purpose === SignablePurpose.CREATE_LISTING
103134
)) as SignableAction | undefined;
104135

@@ -117,7 +148,7 @@ function SeaportCreateListing({ disabled, handleExampleSubmitted }: RequestExamp
117148
setIsBuildingTransaction(false);
118149
}
119150
}, [NFTContractAddress, buyAmount, buyType, orderbookClient,
120-
tokenContractAddress, tokenId, walletAddress, sellTokenUnits, sellTokenType]);
151+
tokenContractAddress, tokenId, walletAddress, sellTokenUnits, sellTokenType, submitTransaction, zkEvmProvider]);
121152

122153
const handleSetSellTokenType = (e: React.ChangeEvent<HTMLSelectElement>) => {
123154
resetForm();
@@ -133,19 +164,36 @@ function SeaportCreateListing({ disabled, handleExampleSubmitted }: RequestExamp
133164
e.preventDefault();
134165
e.stopPropagation();
135166

167+
let onSuccess;
168+
if (submitTransaction) {
169+
onSuccess = async (result: any) => {
170+
await orderbookClient.createListing({
171+
orderComponents: orderComponents!,
172+
orderHash,
173+
orderSignature: result,
174+
makerFees: [],
175+
});
176+
};
177+
}
178+
136179
return handleExampleSubmitted({
137180
method: 'eth_signTypedData_v4',
138181
params: [walletAddress, transaction],
139-
});
140-
}, [handleExampleSubmitted, transaction, walletAddress]);
182+
}, onSuccess);
183+
}, [
184+
handleExampleSubmitted,
185+
transaction,
186+
walletAddress,
187+
orderComponents,
188+
orderHash,
189+
submitTransaction,
190+
orderbookClient,
191+
]);
141192

142193
return (
143194
<Accordion.Item eventKey="3">
144195
<Accordion.Header>Seaport Create Listing</Accordion.Header>
145196
<Accordion.Body>
146-
<Alert variant="warning">
147-
Note: This method only returns a signed message, it does not submit an order to the orderbook.
148-
</Alert>
149197
{transactionError
150198
&& (
151199
<Alert variant="danger" style={{ wordBreak: 'break-word' }}>
@@ -255,6 +303,15 @@ function SeaportCreateListing({ disabled, handleExampleSubmitted }: RequestExamp
255303
onChange={(e) => setBuyAmount(e.target.value)}
256304
/>
257305
</Form.Group>
306+
<Form.Group className="mb-3">
307+
<Form.Check
308+
onClick={() => {
309+
setSubmitTransaction(!submitTransaction);
310+
}}
311+
type="checkbox"
312+
label="Submit Transaction"
313+
/>
314+
</Form.Group>
258315
<Stack direction="horizontal" gap={3}>
259316
<WorkflowButton
260317
disabled={disabled || isBuldingTransaction || !!transaction || !!transactionError}

packages/passport/sdk-sample-app/src/components/zkevm/Request.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,21 +169,24 @@ function Request({ showModal, setShowModal }: ModalProps) {
169169
setShowModal(false);
170170
};
171171

172-
const performRequest = async (request: RequestArguments) => {
172+
const performRequest = async (request: RequestArguments, onSuccess?: (result?: any) => Promise<void>) => {
173173
setInvalid(false);
174174
setLoadingRequest(true);
175175
try {
176176
const result = await zkEvmProvider?.request(request);
177177
setLoadingRequest(false);
178178
addMessage(request.method, result);
179+
if (onSuccess) {
180+
await onSuccess(result);
181+
}
179182
handleClose();
180183
} catch (err) {
181184
addMessage('Request', err);
182185
handleClose();
183186
}
184187
};
185188

186-
const handleExampleSubmitted = async (request: RequestArguments) => {
189+
const handleExampleSubmitted = async (request: RequestArguments, onSuccess?: (result?: any) => Promise<void>) => {
187190
if (request.params) {
188191
const newParams = params;
189192
request.params.forEach((param, i) => {
@@ -195,7 +198,7 @@ function Request({ showModal, setShowModal }: ModalProps) {
195198
});
196199
setParams(newParams);
197200
}
198-
await performRequest(request);
201+
return performRequest(request, onSuccess);
199202
};
200203

201204
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {

packages/passport/sdk-sample-app/src/components/zkevm/RequestExampleAccordion.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ function RequestExampleAccordion({ disabled, examples, handleExampleSubmitted }:
2525
examples?.map((component) => (
2626
React.createElement(component, {
2727
key: component.name,
28-
handleExampleSubmitted: (request: RequestArguments) => {
28+
handleExampleSubmitted: (request: RequestArguments, onSuccess?: (result?: any) => Promise<void>) => {
2929
setActiveAccordionKey('');
30-
return handleExampleSubmitted(request);
30+
return handleExampleSubmitted(request, onSuccess);
3131
},
3232
disabled,
3333
})

packages/passport/sdk-sample-app/src/types/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,18 @@ export interface ViewOffersModalProps extends ModalProps {
3737
onClose?: () => void;
3838
}
3939

40+
export type HandleExampleSubmitted = (
41+
request: RequestArguments,
42+
onSuccess?: (result?: any) => Promise<void>,
43+
) => Promise<void>;
44+
4045
export interface RequestExampleProps {
41-
handleExampleSubmitted: (request: RequestArguments) => Promise<void>;
46+
handleExampleSubmitted: HandleExampleSubmitted;
4247
disabled: boolean;
4348
}
4449

4550
export interface RequestExampleAccordionProps {
4651
disabled: boolean;
47-
handleExampleSubmitted: (request: RequestArguments) => Promise<void>;
52+
handleExampleSubmitted: HandleExampleSubmitted;
4853
examples: Array<ComponentType<RequestExampleProps>>;
4954
}

0 commit comments

Comments
 (0)