-
Notifications
You must be signed in to change notification settings - Fork 71
/
useOpenAction.ts
284 lines (278 loc) · 8.89 KB
/
useOpenAction.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
import {
InsufficientGasError,
PendingSigningRequestError,
UserRejectedError,
WalletConnectionError,
} from '@lens-protocol/domain/entities';
import { BroadcastingError } from '@lens-protocol/domain/use-cases/transactions';
import {
InsufficientAllowanceError,
InsufficientFundsError,
} from '@lens-protocol/domain/use-cases/wallets';
import { invariant } from '@lens-protocol/shared-kernel';
import { useSession } from '../../authentication';
import { useDeferredTask, UseDeferredTask } from '../../helpers/tasks';
import { AsyncTransactionResult } from '../adapters/AsyncTransactionResult';
import { useOpenActionController } from '../adapters/useOpenActionController';
import { useSponsoredConfig } from '../shared/useSponsoredConfig';
import { createOpenActionRequest } from './createOpenActionRequest';
import { OpenActionArgs, UseOpenActionArgs } from './types';
/**
* An object representing the result of an open action is finalized.
*
* It allows to wait for the action to be fully processed and indexed.
*/
export type OpenActionAsyncResult = AsyncTransactionResult<void>;
/**
* `useOpenAction` is a React Hook that allows to perform an Open Action on a publication.
*
* You MUST be authenticated via {@link useLogin} to use this hook.
*
* @example
* ```ts
* const { execute, error, loading } = useOpenAction({
* action: {
* kind: OpenActionKind.COLLECT,
* }
* });
* ```
*
* ## Collect a publication
*
* You can use the `useOpenAction` hook to collect a publication.
*
* ```ts
* const { execute, error, loading } = useOpenAction({
* action: {
* kind: OpenActionKind.COLLECT,
* }
* });
*
* const collect = async (publication: AnyPublication) => {
* const result = await execute({ publication });
* }
* ```
*
* It supports seamlessly new collect Open Action modules as well as legacy collect modules.
*
* ## Failure scenarios
*
* You can handle possible failure scenarios by checking the `result` value.
*
* ```ts
* const collect = async (publication: AnyPublication) => {
* const result = await execute({ publication });
*
* if (result.isFailure()) {
* switch (result.error.name) {
* case 'BroadcastingError':
* console.log('There was an error broadcasting the transaction', error.message);
* break;
*
* case 'PendingSigningRequestError':
* console.log(
* 'There is a pending signing request in your wallet. ' +
* 'Approve it or discard it and try again.'
* );
* break;
*
* case 'InsufficientAllowanceError':
* const requestedAmount = result.error.requestedAmount;
* console.log(
* 'You must approve the contract to spend at least: '+
* `${requestedAmount.asset.symbol} ${requestedAmount.toSignificantDigits(6)}`
* );
* break;
*
* case 'InsufficientFundsError':
* const requestedAmount = result.error.requestedAmount;
* console.log(
* 'You do not have enough funds to pay for this collect fee: '+
* `${requestedAmount.asset.symbol} ${requestedAmount.toSignificantDigits(6)}`
* );
* break;
*
* case 'WalletConnectionError':
* console.log('There was an error connecting to your wallet', error.message);
* break;
*
* case 'UserRejectedError':
* // the user decided to not sign, usually this is silently ignored by UIs
* break;
* }
* return;
* }
* };
* ```
*
* ## Wait for completion
*
* You can always wait the operation to be fully processed and indexed by Lens API.
*
* ```ts
* const collect = async (publication: AnyPublication) => {
* const result = await execute({ publication });
*
* if (result.isFailure()) {
* // handle failure scenarios
* return;
* }
*
* // this might take a while depending on the congestion of the network
* const completion = await result.value.waitForCompletion();
*
* if (completion.isFailure()) {
* console.log('There was an processing the transaction', completion.error.message);
* return;
* }
*
* console.log('Open action executed successfully');
* };
* ```
*
* ## Collect referrers
*
* When collecting a publication using the new SimpleCollectOpenAction or MultirecipientFeeCollectOpenAction
* you can specify a list of referrer Publication and/or Profile IDs.
*
* ```ts
* const { execute, error, loading } = useOpenAction({
* action: {
* kind: OpenActionKind.COLLECT,
* referrers: [
* publicationId,
* profileId,
* ],
* },
* });
* ```
*
* The referrers will split the referral reward of any collect fee paid by the collector.
*
* ## Public collect
*
* You can use the `useOpenAction` hook to collect a publication with just a wallet.
* First make sure you logged-in via {@link useLogin} with just an EVM address.
*
* Then you can use the `useOpenAction` to collect a publication as mentioned above.
*
* ## Custom open action
*
* You can use the `useOpenAction` hook to execute a custom Open Action.
*
* You must know the address of the Open Action module and the data required to execute it.
*
* ```ts
* const { execute, error, loading } = useOpenAction({
* action: {
* kind: OpenActionKind.UNKNOWN,
* address: '0x...', // the address of the Open Action module
* data: '0x...', // any data needed to execute the Open Action
* }
* });
*
* const collect = async (publication: AnyPublication) => {
* const result = await execute({ publication });
*
* // ...
* }
* ```
*
* ## Self-funded approach
*
* It just takes a single parameter to disable the sponsorship of the transaction gas costs.
*
* ```ts
* const collect = async (publication: AnyPublication) => {
* const result = await execute({
* publication,
* sponsored: false,
* });
*
* if (result.isFailure()) {
* switch (result.error.name) {
* case 'InsufficientGasError':
* console.log('You do not have enough funds to pay for the transaction gas cost.');
* break;
*
* // ...
* }
* return;
* }
*
* // ...
* }
* ```
* In this example you can also see a new error type: {@link InsufficientGasError}. This
* error happens only with self-funded transactions and it means that the wallet does not
* have enough funds to pay for the transaction gas costs.
*
* ## Self-funded fallback
*
* If for some reason the Lens API cannot sponsor the transaction, the hook will fail with a {@link BroadcastingError} with one of the following reasons:
* - {@link BroadcastingErrorReason.NOT_SPONSORED} - the profile is not sponsored
* - {@link BroadcastingErrorReason.RATE_LIMITED} - the profile reached the rate limit
* - {@link BroadcastingErrorReason.APP_NOT_ALLOWED} - the app is not whitelisted for gasless transactions
*
* In those cases you can retry the transaction as self-funded like in the following example:
*
* ```ts
* const collect = async (publication: AnyPublication) => {
* const sponsoredResult = await execute({ publication });
*
* if (sponsoredResult.isFailure()) {
* switch (sponsoredResult.error.name) {
* case 'BroadcastingError':
* if ([BroadcastingErrorReason.NOT_SPONSORED, BroadcastingErrorReason.RATE_LIMITED].includes(sponsoredResult.error.reason)) {
*
* const selfFundedResult = await execute({ publication, sponsored: false });
*
* // continue with selfFundedResult as in the previous example
* }
* break;
*
* // ...
* }
* }
* ```
*
* In this example we omitted {@link BroadcastingErrorReason.APP_NOT_ALLOWED} as it's not normally a problem per-se.
* It just requires the app to apply for whitelisting. See https://docs.lens.xyz/docs/gasless-and-signless#whitelisting-your-app.
*
* You can still include it in your fallback logic if you want to. For example to unblock testing your app from a domain that is not the
* whitelisted one (e.g. localhost).
*
* @category Publications
* @group Hooks
*/
export function useOpenAction(
args: UseOpenActionArgs,
): UseDeferredTask<
OpenActionAsyncResult,
| BroadcastingError
| InsufficientAllowanceError
| InsufficientFundsError
| InsufficientGasError
| PendingSigningRequestError
| UserRejectedError
| WalletConnectionError,
OpenActionArgs
> {
const { data: session } = useSession();
const openAction = useOpenActionController();
const configureRequest = useSponsoredConfig();
return useDeferredTask(async ({ publication, sponsored = true }: OpenActionArgs) => {
invariant(
session?.authenticated,
'You must be authenticated to execute an Open Action a post. Use `useLogin` hook to authenticate.',
);
invariant(
publication.momoka === null,
'You cannot execute an Open Action on a Momoka publication.',
);
const request = configureRequest(
createOpenActionRequest({ publication, sponsored }, args.action, session),
);
return openAction(request);
});
}