@human-protocol/sdk 1.0.0

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/src/job.ts ADDED
@@ -0,0 +1,797 @@
1
+ import { BigNumber, Contract, ethers } from 'ethers';
2
+ import winston from 'winston';
3
+
4
+ import { DEFAULT_BUCKET, DEFAULT_PUBLIC_BUCKET } from './constants';
5
+ import { download, getKeyFromURL, getPublicURL, upload } from './storage';
6
+ import {
7
+ EscrowStatus,
8
+ Manifest,
9
+ Payout,
10
+ ProviderData,
11
+ Result,
12
+ UploadResult,
13
+ ManifestData,
14
+ ContractData,
15
+ JobArguments,
16
+ StorageAccessData,
17
+ } from './types';
18
+ import {
19
+ deployEscrowFactory,
20
+ getEscrow,
21
+ getEscrowFactory,
22
+ getHmToken,
23
+ toFullDigit,
24
+ } from './utils';
25
+ import {
26
+ ErrorHMTokenMissing,
27
+ ErrorJobAlreadyLaunched,
28
+ ErrorJobNotInitialized,
29
+ ErrorJobNotLaunched,
30
+ ErrorManifestMissing,
31
+ ErrorReputationOracleMissing,
32
+ ErrorStorageAccessDataMissing,
33
+ } from './error';
34
+ import { createLogger } from './logger';
35
+
36
+ /**
37
+ * @class Human Protocol Job
38
+ */
39
+ export class Job {
40
+ /**
41
+ * Ethers provider data
42
+ */
43
+ providerData?: ProviderData;
44
+
45
+ /**
46
+ * Job manifest & result data
47
+ */
48
+ manifestData?: ManifestData;
49
+
50
+ /**
51
+ * Job smart contract data
52
+ */
53
+ contractData?: ContractData;
54
+
55
+ /**
56
+ * Cloud storage access data
57
+ */
58
+ storageAccessData?: StorageAccessData;
59
+
60
+ private _logger: winston.Logger;
61
+
62
+ /**
63
+ * Get the total cost of the job
64
+ * @return {number} The total cost of the job
65
+ */
66
+ get amount(): number {
67
+ return (
68
+ (this.manifestData?.manifest?.task_bid_price || 0) *
69
+ (this.manifestData?.manifest?.job_total_tasks || 0)
70
+ );
71
+ }
72
+
73
+ /**
74
+ * **Job constructor**
75
+ *
76
+ * If the network is not specified, it'll connect to http://localhost:8545.
77
+ * If the network other than hardhat is specified, either of Infura/Alchemy key is required to connect.
78
+ *
79
+ * @param {JobArguments} args - Job arguments
80
+ */
81
+ constructor({
82
+ network,
83
+ alchemyKey,
84
+ infuraKey,
85
+ gasPayer,
86
+ reputationOracle,
87
+ trustedHandlers,
88
+ hmTokenAddr,
89
+ factoryAddr,
90
+ escrowAddr,
91
+ manifest,
92
+ storageAccessKeyId,
93
+ storageSecretAccessKey,
94
+ storageEndpoint,
95
+ storagePublicBucket,
96
+ storageBucket,
97
+ logLevel = 'info',
98
+ }: JobArguments) {
99
+ const provider = network
100
+ ? ethers.getDefaultProvider(network, {
101
+ alchemy: alchemyKey,
102
+ infura: infuraKey,
103
+ })
104
+ : new ethers.providers.JsonRpcProvider();
105
+
106
+ this.providerData = {
107
+ provider,
108
+ };
109
+
110
+ if (typeof gasPayer === 'string') {
111
+ this.providerData.gasPayer = new ethers.Wallet(gasPayer, provider);
112
+ } else {
113
+ this.providerData.gasPayer = gasPayer;
114
+ }
115
+
116
+ if (typeof reputationOracle === 'string') {
117
+ this.providerData.reputationOracle = new ethers.Wallet(
118
+ reputationOracle,
119
+ provider
120
+ );
121
+ } else {
122
+ this.providerData.reputationOracle = reputationOracle;
123
+ }
124
+
125
+ this.providerData.trustedHandlers =
126
+ trustedHandlers?.map((trustedHandler) => {
127
+ if (typeof trustedHandler === 'string') {
128
+ return new ethers.Wallet(trustedHandler, provider);
129
+ }
130
+ return trustedHandler;
131
+ }) || [];
132
+
133
+ this.contractData = {
134
+ hmTokenAddr,
135
+ escrowAddr,
136
+ factoryAddr,
137
+ };
138
+
139
+ this.manifestData = { manifest };
140
+
141
+ this.storageAccessData = {
142
+ accessKeyId: storageAccessKeyId || '',
143
+ secretAccessKey: storageSecretAccessKey || '',
144
+ endpoint: storageEndpoint,
145
+ publicBucket: storagePublicBucket || DEFAULT_BUCKET,
146
+ bucket: storageBucket || DEFAULT_PUBLIC_BUCKET,
147
+ };
148
+
149
+ this._logger = createLogger(logLevel);
150
+ }
151
+
152
+ /**
153
+ * **Initialize the escrow**
154
+ *
155
+ * For existing escrows, access the escrow on-chain, and read manifest.
156
+ * For new escrows, deploy escrow factory to launch new escrow.
157
+ *
158
+ * @returns {Promise<boolean>} - True if escrow is initialized successfully.
159
+ */
160
+ async initialize(): Promise<boolean> {
161
+ if (!this.contractData) {
162
+ this._logError(new Error('Contract data is missing'));
163
+ return false;
164
+ }
165
+
166
+ this.contractData.hmToken = await getHmToken(
167
+ this.contractData.hmTokenAddr,
168
+ this.providerData?.gasPayer
169
+ );
170
+
171
+ if (!this.contractData?.escrowAddr) {
172
+ if (!this.manifestData?.manifest) {
173
+ this._logError(ErrorManifestMissing);
174
+ return false;
175
+ }
176
+
177
+ this._logger.info('Deploying escrow factory...');
178
+ this.contractData.factory = await deployEscrowFactory(
179
+ this.contractData.hmTokenAddr,
180
+ this.providerData?.gasPayer
181
+ );
182
+ this.contractData.factoryAddr = this.contractData.factory.address;
183
+ this._logger.info(
184
+ `Escrow factory is deployed at ${this.contractData.factory.address}.`
185
+ );
186
+ } else {
187
+ if (!this.contractData?.factoryAddr) {
188
+ this._logError(
189
+ new Error('Factory address is required for existing escrow')
190
+ );
191
+ return false;
192
+ }
193
+
194
+ this._logger.info('Getting escrow factory...');
195
+ this.contractData.factory = await getEscrowFactory(
196
+ this.contractData?.factoryAddr,
197
+ this.providerData?.gasPayer
198
+ );
199
+
200
+ this._logger.info('Checking if escrow exists in the factory...');
201
+ const hasEscrow = await this.contractData?.factory.hasEscrow(
202
+ this.contractData?.escrowAddr
203
+ );
204
+
205
+ if (!hasEscrow) {
206
+ this._logError(new Error('Factory does not contain the escrow'));
207
+ return false;
208
+ }
209
+
210
+ this._logger.info('Accessing the escrow...');
211
+ this.contractData.escrow = await getEscrow(
212
+ this.contractData?.escrowAddr,
213
+ this.providerData?.gasPayer
214
+ );
215
+ this._logger.info('Accessed the escrow successfully.');
216
+
217
+ this.manifestData = {
218
+ ...this.manifestData,
219
+ manifestlink: {
220
+ url: await this.contractData?.escrow.manifestUrl(),
221
+ hash: await this.contractData?.escrow.manifestHash(),
222
+ },
223
+ };
224
+
225
+ this.manifestData.manifest = (await this._download(
226
+ this.manifestData.manifestlink?.url
227
+ )) as Manifest;
228
+ }
229
+
230
+ return true;
231
+ }
232
+
233
+ /**
234
+ * **Launch the escrow**
235
+ *
236
+ * Deploy new escrow contract, and uploads manifest.
237
+ *
238
+ * @returns {Promise<boolean>} - True if the escrow is launched successfully.
239
+ */
240
+ async launch(): Promise<boolean> {
241
+ if (!this.contractData || this.contractData.escrow) {
242
+ this._logError(ErrorJobAlreadyLaunched);
243
+ return false;
244
+ }
245
+
246
+ if (!this.contractData || !this.contractData.factory) {
247
+ this._logError(ErrorJobNotInitialized);
248
+ return false;
249
+ }
250
+
251
+ if (!this.manifestData || !this.manifestData.manifest) {
252
+ this._logError(ErrorManifestMissing);
253
+ return false;
254
+ }
255
+
256
+ if (!this.providerData || !this.providerData.reputationOracle) {
257
+ this._logError(ErrorReputationOracleMissing);
258
+ return false;
259
+ }
260
+
261
+ this._logger.info('Launching escrow...');
262
+
263
+ const txReceipt = await this.contractData?.factory?.createEscrow(
264
+ this.providerData?.trustedHandlers?.map(
265
+ (trustedHandler) => trustedHandler.address
266
+ ) || []
267
+ );
268
+
269
+ const txResponse = await txReceipt?.wait();
270
+
271
+ const event = txResponse?.events?.find(
272
+ (event) => event.event === 'Launched'
273
+ );
274
+
275
+ const escrowAddr = event?.args?.[1];
276
+ this._logger.info(`Escrow is deployed at ${escrowAddr}.`);
277
+
278
+ this.contractData.escrowAddr = escrowAddr;
279
+ this.contractData.escrow = await getEscrow(
280
+ escrowAddr,
281
+ this.providerData?.gasPayer
282
+ );
283
+
284
+ this._logger.info('Uploading manifest...');
285
+ const uploadResult = await this._upload(this.manifestData.manifest);
286
+ if (!uploadResult) {
287
+ this._logError(new Error('Error uploading manifest'));
288
+ return false;
289
+ }
290
+
291
+ this.manifestData.manifestlink = {
292
+ url: uploadResult.key,
293
+ hash: uploadResult.hash,
294
+ };
295
+ this._logger.info(
296
+ `Uploaded manifest.\n\tKey: ${uploadResult.key}\n\tHash: ${uploadResult.hash}`
297
+ );
298
+
299
+ return (
300
+ (await this.status()) == EscrowStatus.Launched &&
301
+ (await this.balance())?.toNumber() === 0
302
+ );
303
+ }
304
+
305
+ /**
306
+ * Setup escrow
307
+ *
308
+ * Sets the escrow contract to be ready to receive answers from the Recording Oracle.
309
+ *
310
+ * @param {string | undefined} senderAddr - Address of HMToken sender
311
+ * @returns {Promise<boolean>} True if the escrow is setup successfully.
312
+ */
313
+ async setup(senderAddr?: string): Promise<boolean> {
314
+ if (!this.contractData?.escrow) {
315
+ this._logError(ErrorJobNotLaunched);
316
+ return false;
317
+ }
318
+
319
+ if (!this.contractData.hmToken) {
320
+ this._logError(ErrorHMTokenMissing);
321
+ return false;
322
+ }
323
+
324
+ const reputationOracleStake =
325
+ (this.manifestData?.manifest?.oracle_stake || 0) * 100;
326
+ const recordingOracleStake =
327
+ (this.manifestData?.manifest?.oracle_stake || 0) * 100;
328
+ const repuationOracleAddr =
329
+ this.manifestData?.manifest?.reputation_oracle_addr || '';
330
+ const recordingOracleAddr =
331
+ this.manifestData?.manifest?.recording_oracle_addr || '';
332
+
333
+ this._logger.info(
334
+ `Transferring ${this.amount} HMT to ${this.contractData.escrow.address}...`
335
+ );
336
+ const transferred = await (senderAddr
337
+ ? this._raffleExecute(
338
+ this.contractData.hmToken,
339
+ 'transferFrom',
340
+ senderAddr,
341
+ this.contractData.escrow.address,
342
+ toFullDigit(this.amount)
343
+ )
344
+ : this._raffleExecute(
345
+ this.contractData.hmToken,
346
+ 'transfer',
347
+ this.contractData.escrow.address,
348
+ toFullDigit(this.amount)
349
+ ));
350
+
351
+ if (!transferred) {
352
+ this._logError(
353
+ new Error(
354
+ 'Failed to transfer HMT with all credentials, not continuing to setup'
355
+ )
356
+ );
357
+ return false;
358
+ }
359
+ this._logger.info('HMT transferred.');
360
+
361
+ this._logger.info('Setting up the escrow...');
362
+ const contractSetup = await this._raffleExecute(
363
+ this.contractData.escrow,
364
+ 'setup',
365
+ repuationOracleAddr,
366
+ recordingOracleAddr,
367
+ reputationOracleStake,
368
+ recordingOracleStake,
369
+ this.manifestData?.manifestlink?.url,
370
+ this.manifestData?.manifestlink?.hash
371
+ );
372
+
373
+ if (!contractSetup) {
374
+ this._logError(new Error('Failed to setup contract'));
375
+ return false;
376
+ }
377
+
378
+ this._logger.info('Escrow is set up.');
379
+
380
+ return (
381
+ (await this.status()) === EscrowStatus.Pending &&
382
+ (await this.balance())?.toString() === toFullDigit(this.amount).toString()
383
+ );
384
+ }
385
+
386
+ /**
387
+ * Add trusted handlers
388
+ *
389
+ * Add trusted handlers that can freely transact with the contract and
390
+ * perform aborts and cancels for example.
391
+ *
392
+ * @param {string[]} handlers - Trusted handlers to add
393
+ * @returns {Promise<boolean>} - True if trusted handlers are added successfully.
394
+ */
395
+ async addTrustedHandlers(handlers: string[]): Promise<boolean> {
396
+ if (!this.contractData?.escrow) {
397
+ this._logError(ErrorJobNotLaunched);
398
+ return false;
399
+ }
400
+
401
+ const result = await this._raffleExecute(
402
+ this.contractData.escrow,
403
+ 'addTrustedHandlers',
404
+ handlers
405
+ );
406
+
407
+ if (!result) {
408
+ this._logError(new Error('Failed to add trusted handlers to the job'));
409
+ return false;
410
+ }
411
+
412
+ return result;
413
+ }
414
+
415
+ /**
416
+ * **Bulk payout**
417
+ *
418
+ * Payout the workers submitting the result.
419
+ *
420
+ * @param {Payout[]} payouts - Workers address & amount to payout
421
+ * @param {Result} result - Job result submitted
422
+ * @param {bool} encrypt - Whether to encrypt the result, or not
423
+ * @param {bool} isPublic - Whether to store data in public storage, or private.
424
+ * @returns {Promise<boolean>} - True if the workers are paid out successfully.
425
+ */
426
+ async bulkPayout(
427
+ payouts: Payout[],
428
+ result: Result,
429
+ encrypt = true,
430
+ isPublic = false
431
+ ): Promise<boolean> {
432
+ if (!this.providerData?.reputationOracle) {
433
+ this._logError(ErrorReputationOracleMissing);
434
+ return false;
435
+ }
436
+
437
+ if (!this.contractData?.escrow) {
438
+ this._logError(ErrorJobNotLaunched);
439
+ return false;
440
+ }
441
+
442
+ this._logger.info('Uploading result...');
443
+ const uploadResult = await this._upload(result, encrypt, isPublic);
444
+
445
+ if (!uploadResult) {
446
+ this._logError(new Error('Error uploading result'));
447
+ return false;
448
+ }
449
+
450
+ const { key, hash } = uploadResult;
451
+ this._logger.info(`Uploaded result.\n\tKey: ${key}\n\tHash: ${hash}`);
452
+
453
+ if (!this.storageAccessData) {
454
+ this._logError(ErrorStorageAccessDataMissing);
455
+ return false;
456
+ }
457
+
458
+ const url = isPublic ? getPublicURL(this.storageAccessData, key) : key;
459
+
460
+ this._logger.info('Bulk paying out the workers...');
461
+ await this._raffleExecute(
462
+ this.contractData.escrow,
463
+ 'bulkPayOut',
464
+ payouts.map(({ address }) => address),
465
+ payouts.map(({ amount }) => toFullDigit(amount)),
466
+ url,
467
+ hash,
468
+ 1
469
+ );
470
+
471
+ const bulkPaid = await this.contractData.escrow.bulkPaid();
472
+ if (!bulkPaid) {
473
+ this._logError(new Error('Failed to bulk payout users'));
474
+ return false;
475
+ }
476
+
477
+ this._logger.info('Workers are paid out.');
478
+
479
+ return bulkPaid;
480
+ }
481
+
482
+ /**
483
+ * **Abort the escrow**
484
+ *
485
+ * @returns {Promise<boolean>} - True if the escrow is aborted successfully.
486
+ */
487
+ async abort(): Promise<boolean> {
488
+ if (!this.contractData?.escrow) {
489
+ this._logError(ErrorJobNotLaunched);
490
+ return false;
491
+ }
492
+
493
+ this._logger.info('Aborting the job...');
494
+ const aborted = await this._raffleExecute(
495
+ this.contractData.escrow,
496
+ 'abort'
497
+ );
498
+
499
+ if (!aborted) {
500
+ this._logError(new Error('Failed to abort the job'));
501
+ return false;
502
+ }
503
+ this._logger.info('Job is aborted successfully.');
504
+
505
+ return aborted;
506
+ }
507
+
508
+ /**
509
+ * **Cancel the escrow**
510
+ *
511
+ * @returns {Promise<boolean>} - True if the escrow is cancelled successfully.
512
+ */
513
+ async cancel(): Promise<boolean> {
514
+ if (!this.contractData?.escrow) {
515
+ this._logError(ErrorJobNotLaunched);
516
+ return false;
517
+ }
518
+
519
+ this._logger.info('Cancelling the job...');
520
+ const cancelled = await this._raffleExecute(
521
+ this.contractData.escrow,
522
+ 'cancel'
523
+ );
524
+
525
+ if (!cancelled) {
526
+ this._logError(new Error('Failed to cancel the job'));
527
+ return false;
528
+ }
529
+ this._logger.info('Job is cancelled successfully.');
530
+
531
+ return (await this.status()) === EscrowStatus.Cancelled;
532
+ }
533
+
534
+ /**
535
+ * **Store intermediate result**
536
+ *
537
+ * Uploads intermediate result to the storage, and saves the URL/Hash on-chain.
538
+ *
539
+ * @param {Result} result - Intermediate result
540
+ * @returns {Promise<boolean>} - True if the intermediate result is stored successfully.
541
+ */
542
+ async storeIntermediateResults(result: Result): Promise<boolean> {
543
+ if (!this.providerData?.reputationOracle) {
544
+ this._logError(ErrorReputationOracleMissing);
545
+ return false;
546
+ }
547
+
548
+ if (!this.contractData?.escrow) {
549
+ this._logError(ErrorJobNotLaunched);
550
+ return false;
551
+ }
552
+
553
+ this._logger.info('Uploading intermediate result...');
554
+ const uploadResult = await this._upload(result);
555
+
556
+ if (!uploadResult) {
557
+ this._logError(new Error('Error uploading intermediate result'));
558
+ return false;
559
+ }
560
+
561
+ const { key, hash } = uploadResult;
562
+ this._logger.info(
563
+ `Uploaded intermediate result.\n\tKey: ${key}\n\tHash: ${hash}`
564
+ );
565
+
566
+ this._logger.info('Saving intermediate result on-chain...');
567
+ const resultStored = await this._raffleExecute(
568
+ this.contractData.escrow,
569
+ 'storeResults',
570
+ key,
571
+ hash
572
+ );
573
+
574
+ if (!resultStored) {
575
+ this._logError(new Error('Failed to store results'));
576
+ return false;
577
+ }
578
+ this._logger.info('Intermediate result is stored on-chain successfully.');
579
+
580
+ this.manifestData = {
581
+ ...this.manifestData,
582
+ intermediateResultLink: { url: key, hash },
583
+ };
584
+
585
+ return resultStored;
586
+ }
587
+
588
+ /**
589
+ * **Complete the escrow**
590
+ *
591
+ * @returns {Promise<boolean>} - True if the escrow if completed successfully.
592
+ */
593
+ async complete() {
594
+ if (!this.contractData?.escrow) {
595
+ this._logError(ErrorJobNotLaunched);
596
+ return false;
597
+ }
598
+
599
+ const completed = await this._raffleExecute(
600
+ this.contractData.escrow,
601
+ 'complete'
602
+ );
603
+
604
+ if (!completed) {
605
+ this._logError(new Error('Failed to complete the job'));
606
+ return false;
607
+ }
608
+
609
+ return (await this.status()) === EscrowStatus.Complete;
610
+ }
611
+
612
+ /**
613
+ * **Get current status of the escrow**
614
+ *
615
+ * @returns {Promise<EscrowStatus | undefined>} - Status of the escrow
616
+ */
617
+ async status(): Promise<EscrowStatus | undefined> {
618
+ if (!this.contractData?.escrow) {
619
+ this._logError(ErrorJobNotLaunched);
620
+ return undefined;
621
+ }
622
+
623
+ return (await this.contractData.escrow.status()) as EscrowStatus;
624
+ }
625
+
626
+ /**
627
+ * **Get current balance of the escrow**
628
+ *
629
+ * @returns {Promise<BigNumber | undefined>} - Balance of the escrow
630
+ */
631
+ async balance(): Promise<BigNumber | undefined> {
632
+ if (!this.contractData?.escrow) {
633
+ this._logError(ErrorJobNotLaunched);
634
+ return undefined;
635
+ }
636
+
637
+ return await this.contractData.escrow.getBalance();
638
+ }
639
+
640
+ /**
641
+ * **Get intermediate result stored**
642
+ *
643
+ * @returns {Promise<Result | undefined>} - Intermediate result
644
+ */
645
+ async intermediateResults(): Promise<Result | undefined> {
646
+ if (!this.manifestData?.intermediateResultLink) {
647
+ this._logError(new Error('Intermediate result is missing.'));
648
+ return undefined;
649
+ }
650
+
651
+ return this._download(this.manifestData.intermediateResultLink.url);
652
+ }
653
+
654
+ /**
655
+ * **Get final result stored**
656
+ *
657
+ * @returns {Promise<Result | undefined>} - Final result
658
+ */
659
+ async finalResults(): Promise<Result | undefined> {
660
+ if (!this.contractData?.escrow) {
661
+ this._logError(ErrorJobNotLaunched);
662
+ return undefined;
663
+ }
664
+
665
+ const finalResultsURL = await this.contractData?.escrow?.finalResultsUrl();
666
+
667
+ if (!finalResultsURL) {
668
+ return undefined;
669
+ }
670
+
671
+ const key = getKeyFromURL(finalResultsURL);
672
+
673
+ return await this._download(key);
674
+ }
675
+
676
+ /**
677
+ * **Check if handler is trusted**
678
+ *
679
+ * @param {string} handlerAddr Address of the handler
680
+ * @returns {Promise<boolean>} - True if the handler is trusted
681
+ */
682
+ async isTrustedHandler(handlerAddr: string): Promise<boolean> {
683
+ if (!this.contractData?.escrow) {
684
+ this._logError(ErrorJobNotLaunched);
685
+ return false;
686
+ }
687
+
688
+ return await this.contractData?.escrow?.areTrustedHandlers(handlerAddr);
689
+ }
690
+
691
+ /**
692
+ * **Download result from cloud storage**
693
+ *
694
+ * @param {string | undefined} url - Result URL to download
695
+ * @returns {Result | undefined} - Downloaded result
696
+ */
697
+ private async _download(url?: string): Promise<Result | undefined> {
698
+ if (!url || !this.providerData?.reputationOracle) {
699
+ return undefined;
700
+ }
701
+
702
+ if (!this.storageAccessData) {
703
+ this._logError(ErrorStorageAccessDataMissing);
704
+ return undefined;
705
+ }
706
+
707
+ return await download(
708
+ this.storageAccessData,
709
+ url,
710
+ this.providerData?.reputationOracle.privateKey
711
+ );
712
+ }
713
+
714
+ /**
715
+ * **Uploads result to cloud storage**
716
+ *
717
+ * @param {Result} result - Result to upload
718
+ * @param {boolean} encrypt - Whether to encrypt result, or not.
719
+ * @param {bool} isPublic - Whether to store data in public storage, or private.
720
+ * @returns {Promise<UploadResult | undefined>} - Uploaded result
721
+ */
722
+ private async _upload(
723
+ result: Result,
724
+ encrypt = true,
725
+ isPublic = false
726
+ ): Promise<UploadResult | undefined> {
727
+ if (!this.providerData?.reputationOracle) {
728
+ this._logError(ErrorReputationOracleMissing);
729
+ return undefined;
730
+ }
731
+
732
+ if (!this.storageAccessData) {
733
+ this._logError(ErrorStorageAccessDataMissing);
734
+ return undefined;
735
+ }
736
+
737
+ return await upload(
738
+ this.storageAccessData,
739
+ result,
740
+ this.providerData?.reputationOracle?.publicKey,
741
+ encrypt,
742
+ isPublic
743
+ );
744
+ }
745
+
746
+ /**
747
+ * **Raffle executes on-chain call**
748
+ *
749
+ * Try to execute the on-chain call from all possible trusted handlers
750
+ *
751
+ * @param {Function} txn - On-chain call to execute
752
+ * @param {any} args - On-chain call arguments
753
+ * @returns {Promise<boolean>} - True if one of handler succeed to execute the on-chain call
754
+ */
755
+ private async _raffleExecute(
756
+ contract: Contract,
757
+ functionName: string,
758
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
759
+ ...args: any
760
+ ): Promise<boolean> {
761
+ try {
762
+ if (this.providerData?.gasPayer) {
763
+ await contract
764
+ .connect(this.providerData.gasPayer)
765
+ .functions[functionName](...args);
766
+ return true;
767
+ }
768
+ this._logger.info(
769
+ 'Default gas payer is missing, trying with other trusted handlers...'
770
+ );
771
+ } catch (err) {
772
+ this._logger.info(
773
+ 'Error executing the transaction from default gas payer, trying with other trusted handlers...'
774
+ );
775
+ }
776
+
777
+ for (const trustedHandler of this.providerData?.trustedHandlers || []) {
778
+ try {
779
+ await contract.connect(trustedHandler).functions[functionName](...args);
780
+ return true;
781
+ } catch (err) {
782
+ new Error(
783
+ 'Error executing the transaction from all of the trusted handlers. Stop continue executing...'
784
+ );
785
+ }
786
+ }
787
+ return false;
788
+ }
789
+
790
+ /**
791
+ * **Error log**
792
+ * @param {Error} error - Occured error
793
+ */
794
+ private async _logError(error: Error) {
795
+ this._logger.error(error.message);
796
+ }
797
+ }