@human-protocol/sdk 0.0.10
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/README.md +9 -0
- package/example/simple-existing-job.ts +86 -0
- package/example/simple-new-job-public.ts +74 -0
- package/example/simple-new-job.ts +72 -0
- package/package.json +52 -0
- package/src/constants.ts +9 -0
- package/src/error.ts +43 -0
- package/src/index.ts +4 -0
- package/src/job.ts +1064 -0
- package/src/logger.ts +29 -0
- package/src/storage.ts +135 -0
- package/src/types.ts +676 -0
- package/src/utils.ts +164 -0
- package/test/job.test.ts +817 -0
- package/test/utils/constants.ts +30 -0
- package/test/utils/manifest.ts +33 -0
package/src/job.ts
ADDED
|
@@ -0,0 +1,1064 @@
|
|
|
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
|
+
getStaking,
|
|
24
|
+
toFullDigit,
|
|
25
|
+
} from './utils';
|
|
26
|
+
import {
|
|
27
|
+
ErrorHMTokenMissing,
|
|
28
|
+
ErrorJobAlreadyLaunched,
|
|
29
|
+
ErrorJobNotInitialized,
|
|
30
|
+
ErrorJobNotLaunched,
|
|
31
|
+
ErrorManifestMissing,
|
|
32
|
+
ErrorReputationOracleMissing,
|
|
33
|
+
ErrorStakingMissing,
|
|
34
|
+
ErrorStorageAccessDataMissing,
|
|
35
|
+
} from './error';
|
|
36
|
+
import { createLogger } from './logger';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @class Human Protocol Job
|
|
40
|
+
*/
|
|
41
|
+
export class Job {
|
|
42
|
+
/**
|
|
43
|
+
* Ethers provider data
|
|
44
|
+
*/
|
|
45
|
+
providerData?: ProviderData;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Job manifest & result data
|
|
49
|
+
*/
|
|
50
|
+
manifestData?: ManifestData;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Job smart contract data
|
|
54
|
+
*/
|
|
55
|
+
contractData?: ContractData;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Cloud storage access data
|
|
59
|
+
*/
|
|
60
|
+
storageAccessData?: StorageAccessData;
|
|
61
|
+
|
|
62
|
+
private _logger: winston.Logger;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the total cost of the job
|
|
66
|
+
* @return {number} The total cost of the job
|
|
67
|
+
*/
|
|
68
|
+
get amount(): number {
|
|
69
|
+
return (
|
|
70
|
+
(this.manifestData?.manifest?.task_bid_price || 0) *
|
|
71
|
+
(this.manifestData?.manifest?.job_total_tasks || 0)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* **Job constructor**
|
|
77
|
+
*
|
|
78
|
+
* If the network is not specified, it'll connect to http://localhost:8545.
|
|
79
|
+
* If the network other than hardhat is specified, either of Infura/Alchemy key is required to connect.
|
|
80
|
+
*
|
|
81
|
+
* @param {JobArguments} args - Job arguments
|
|
82
|
+
*/
|
|
83
|
+
constructor({
|
|
84
|
+
network,
|
|
85
|
+
alchemyKey,
|
|
86
|
+
infuraKey,
|
|
87
|
+
gasPayer,
|
|
88
|
+
reputationOracle,
|
|
89
|
+
trustedHandlers,
|
|
90
|
+
hmTokenAddr,
|
|
91
|
+
factoryAddr,
|
|
92
|
+
escrowAddr,
|
|
93
|
+
manifest,
|
|
94
|
+
storageAccessKeyId,
|
|
95
|
+
storageSecretAccessKey,
|
|
96
|
+
storageEndpoint,
|
|
97
|
+
storagePublicBucket,
|
|
98
|
+
storageBucket,
|
|
99
|
+
stakingAddr,
|
|
100
|
+
logLevel = 'info',
|
|
101
|
+
}: JobArguments) {
|
|
102
|
+
const provider = network
|
|
103
|
+
? ethers.getDefaultProvider(network, {
|
|
104
|
+
alchemy: alchemyKey,
|
|
105
|
+
infura: infuraKey,
|
|
106
|
+
})
|
|
107
|
+
: new ethers.providers.JsonRpcProvider();
|
|
108
|
+
|
|
109
|
+
this.providerData = {
|
|
110
|
+
provider,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (typeof gasPayer === 'string') {
|
|
114
|
+
this.providerData.gasPayer = new ethers.Wallet(gasPayer, provider);
|
|
115
|
+
} else {
|
|
116
|
+
this.providerData.gasPayer = gasPayer;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (typeof reputationOracle === 'string') {
|
|
120
|
+
this.providerData.reputationOracle = new ethers.Wallet(
|
|
121
|
+
reputationOracle,
|
|
122
|
+
provider
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
this.providerData.reputationOracle = reputationOracle;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.providerData.trustedHandlers =
|
|
129
|
+
trustedHandlers?.map((trustedHandler) => {
|
|
130
|
+
if (typeof trustedHandler === 'string') {
|
|
131
|
+
return new ethers.Wallet(trustedHandler, provider);
|
|
132
|
+
}
|
|
133
|
+
return trustedHandler;
|
|
134
|
+
}) || [];
|
|
135
|
+
|
|
136
|
+
this.contractData = {
|
|
137
|
+
hmTokenAddr,
|
|
138
|
+
escrowAddr,
|
|
139
|
+
factoryAddr,
|
|
140
|
+
stakingAddr,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
this.manifestData = { manifest };
|
|
144
|
+
|
|
145
|
+
this.storageAccessData = {
|
|
146
|
+
accessKeyId: storageAccessKeyId || '',
|
|
147
|
+
secretAccessKey: storageSecretAccessKey || '',
|
|
148
|
+
endpoint: storageEndpoint,
|
|
149
|
+
publicBucket: storagePublicBucket || DEFAULT_PUBLIC_BUCKET,
|
|
150
|
+
bucket: storageBucket || DEFAULT_BUCKET,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
this._logger = createLogger(logLevel);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* **Initialize the escrow**
|
|
158
|
+
*
|
|
159
|
+
* For existing escrows, access the escrow on-chain, and read manifest.
|
|
160
|
+
* For new escrows, deploy escrow factory to launch new escrow.
|
|
161
|
+
*
|
|
162
|
+
* @returns {Promise<boolean>} - True if escrow is initialized successfully.
|
|
163
|
+
*/
|
|
164
|
+
async initialize(): Promise<boolean> {
|
|
165
|
+
if (!this.contractData) {
|
|
166
|
+
this._logError(new Error('Contract data is missing'));
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.contractData.hmToken = await getHmToken(
|
|
171
|
+
this.contractData.hmTokenAddr,
|
|
172
|
+
this.providerData?.gasPayer
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (!this.contractData?.escrowAddr) {
|
|
176
|
+
if (!this.manifestData?.manifest) {
|
|
177
|
+
this._logError(ErrorManifestMissing);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!this.contractData.stakingAddr) {
|
|
182
|
+
this._logError(new Error('Staking contract is missing'));
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this._logger.info('Getting staking...');
|
|
187
|
+
this.contractData.staking = await getStaking(
|
|
188
|
+
this.contractData.stakingAddr,
|
|
189
|
+
this.providerData?.gasPayer
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
this._logger.info('Deploying escrow factory...');
|
|
193
|
+
this.contractData.factory = await deployEscrowFactory(
|
|
194
|
+
this.contractData.hmTokenAddr,
|
|
195
|
+
this.contractData.stakingAddr,
|
|
196
|
+
this.providerData?.gasPayer
|
|
197
|
+
);
|
|
198
|
+
this.contractData.factoryAddr = this.contractData.factory.address;
|
|
199
|
+
this._logger.info(
|
|
200
|
+
`Escrow factory is deployed at ${this.contractData.factory.address}.`
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
if (!this.contractData?.factoryAddr) {
|
|
204
|
+
this._logError(
|
|
205
|
+
new Error('Factory address is required for existing escrow')
|
|
206
|
+
);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this._logger.info('Getting escrow factory...');
|
|
211
|
+
this.contractData.factory = await getEscrowFactory(
|
|
212
|
+
this.contractData?.factoryAddr,
|
|
213
|
+
this.providerData?.gasPayer
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
this._logger.info('Checking if staking is configured...');
|
|
217
|
+
const stakingAddr = await this.contractData.factory.staking();
|
|
218
|
+
if (!stakingAddr) {
|
|
219
|
+
this._logError(new Error('Factory is not configured with staking'));
|
|
220
|
+
this.contractData.factory = undefined;
|
|
221
|
+
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
this._logger.info('Getting staking...');
|
|
225
|
+
this.contractData.staking = await getStaking(
|
|
226
|
+
stakingAddr,
|
|
227
|
+
this.providerData?.gasPayer
|
|
228
|
+
);
|
|
229
|
+
this.contractData.stakingAddr = stakingAddr;
|
|
230
|
+
|
|
231
|
+
this._logger.info('Checking if reward pool is configured...');
|
|
232
|
+
const rewardPoolAddr = await this.contractData.staking.rewardPool();
|
|
233
|
+
if (!rewardPoolAddr) {
|
|
234
|
+
this._logError(new Error('Staking is not configured with reward pool'));
|
|
235
|
+
this.contractData.staking = undefined;
|
|
236
|
+
this.contractData.factory = undefined;
|
|
237
|
+
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this._logger.info('Checking if escrow exists in the factory...');
|
|
242
|
+
const hasEscrow = await this.contractData?.factory.hasEscrow(
|
|
243
|
+
this.contractData?.escrowAddr
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (!hasEscrow) {
|
|
247
|
+
this._logError(new Error('Factory does not contain the escrow'));
|
|
248
|
+
this.contractData.factory = undefined;
|
|
249
|
+
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this._logger.info('Accessing the escrow...');
|
|
254
|
+
this.contractData.escrow = await getEscrow(
|
|
255
|
+
this.contractData?.escrowAddr,
|
|
256
|
+
this.providerData?.gasPayer
|
|
257
|
+
);
|
|
258
|
+
this._logger.info('Accessed the escrow successfully.');
|
|
259
|
+
|
|
260
|
+
const manifestUrl = await this.contractData?.escrow.manifestUrl();
|
|
261
|
+
const manifestHash = await this.contractData?.escrow.manifestHash();
|
|
262
|
+
|
|
263
|
+
if (
|
|
264
|
+
(!manifestUrl.length || !manifestHash.length) &&
|
|
265
|
+
!this.manifestData?.manifest
|
|
266
|
+
) {
|
|
267
|
+
this._logError(ErrorManifestMissing);
|
|
268
|
+
|
|
269
|
+
this.contractData.factory = undefined;
|
|
270
|
+
this.contractData.escrow = undefined;
|
|
271
|
+
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (manifestUrl.length && manifestHash.length) {
|
|
276
|
+
this.manifestData = {
|
|
277
|
+
...this.manifestData,
|
|
278
|
+
manifestlink: {
|
|
279
|
+
url: manifestUrl,
|
|
280
|
+
hash: manifestHash,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
this.manifestData.manifest = (await this._download(
|
|
285
|
+
manifestUrl
|
|
286
|
+
)) as Manifest;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* **Launch the escrow**
|
|
295
|
+
*
|
|
296
|
+
* Deploy new escrow contract, and uploads manifest.
|
|
297
|
+
*
|
|
298
|
+
* @returns {Promise<boolean>} - True if the escrow is launched successfully.
|
|
299
|
+
*/
|
|
300
|
+
async launch(): Promise<boolean> {
|
|
301
|
+
if (!this.contractData || !this.contractData.factory) {
|
|
302
|
+
this._logError(ErrorJobNotInitialized);
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!this.contractData || this.contractData.escrow) {
|
|
307
|
+
this._logError(ErrorJobAlreadyLaunched);
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!this.providerData || !this.providerData.reputationOracle) {
|
|
312
|
+
this._logError(ErrorReputationOracleMissing);
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this._logger.info('Launching escrow...');
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const txReceipt = await this.contractData?.factory?.createEscrow(
|
|
320
|
+
this.providerData?.trustedHandlers?.map(
|
|
321
|
+
(trustedHandler) => trustedHandler.address
|
|
322
|
+
) || []
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const txResponse = await txReceipt?.wait();
|
|
326
|
+
|
|
327
|
+
const event = txResponse?.events?.find(
|
|
328
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
329
|
+
(event: any) => event.event === 'Launched'
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const escrowAddr = event?.args?.[1];
|
|
333
|
+
this._logger.info(`Escrow is deployed at ${escrowAddr}.`);
|
|
334
|
+
|
|
335
|
+
this.contractData.escrowAddr = escrowAddr;
|
|
336
|
+
this.contractData.escrow = await getEscrow(
|
|
337
|
+
escrowAddr,
|
|
338
|
+
this.providerData?.gasPayer
|
|
339
|
+
);
|
|
340
|
+
} catch {
|
|
341
|
+
this._logError(new Error('Error creating escrow...'));
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
(await this.status()) == EscrowStatus.Launched &&
|
|
347
|
+
(await this.balance())?.toNumber() === 0
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Setup escrow
|
|
353
|
+
*
|
|
354
|
+
* Sets the escrow contract to be ready to receive answers from the Recording Oracle.
|
|
355
|
+
*
|
|
356
|
+
* @param {string | undefined} senderAddr - Address of HMToken sender
|
|
357
|
+
* @returns {Promise<boolean>} True if the escrow is setup successfully.
|
|
358
|
+
*/
|
|
359
|
+
async setup(senderAddr?: string): Promise<boolean> {
|
|
360
|
+
if (!this.contractData?.escrow) {
|
|
361
|
+
this._logError(ErrorJobNotLaunched);
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!this.manifestData || !this.manifestData.manifest) {
|
|
366
|
+
this._logError(ErrorManifestMissing);
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (this.manifestData.manifestlink) {
|
|
371
|
+
this._logError(new Error('Job is already setup'));
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!this.contractData.hmToken) {
|
|
376
|
+
this._logError(ErrorHMTokenMissing);
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const reputationOracleStake =
|
|
381
|
+
(this.manifestData?.manifest?.oracle_stake || 0) * 100;
|
|
382
|
+
const recordingOracleStake =
|
|
383
|
+
(this.manifestData?.manifest?.oracle_stake || 0) * 100;
|
|
384
|
+
const repuationOracleAddr =
|
|
385
|
+
this.manifestData?.manifest?.reputation_oracle_addr || '';
|
|
386
|
+
const recordingOracleAddr =
|
|
387
|
+
this.manifestData?.manifest?.recording_oracle_addr || '';
|
|
388
|
+
|
|
389
|
+
this._logger.info(
|
|
390
|
+
`Transferring ${this.amount} HMT to ${this.contractData.escrow.address}...`
|
|
391
|
+
);
|
|
392
|
+
const transferred = await (senderAddr
|
|
393
|
+
? this._raffleExecute(
|
|
394
|
+
this.contractData.hmToken,
|
|
395
|
+
'transferFrom',
|
|
396
|
+
senderAddr,
|
|
397
|
+
this.contractData.escrow.address,
|
|
398
|
+
toFullDigit(this.amount)
|
|
399
|
+
)
|
|
400
|
+
: this._raffleExecute(
|
|
401
|
+
this.contractData.hmToken,
|
|
402
|
+
'transfer',
|
|
403
|
+
this.contractData.escrow.address,
|
|
404
|
+
toFullDigit(this.amount)
|
|
405
|
+
));
|
|
406
|
+
|
|
407
|
+
if (!transferred) {
|
|
408
|
+
this._logError(
|
|
409
|
+
new Error(
|
|
410
|
+
'Failed to transfer HMT with all credentials, not continuing to setup'
|
|
411
|
+
)
|
|
412
|
+
);
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
this._logger.info('HMT transferred.');
|
|
416
|
+
|
|
417
|
+
this._logger.info('Uploading manifest...');
|
|
418
|
+
const uploadResult = await this._upload(this.manifestData.manifest);
|
|
419
|
+
if (!uploadResult) {
|
|
420
|
+
this._logError(new Error('Error uploading manifest'));
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
this.manifestData.manifestlink = {
|
|
425
|
+
url: uploadResult.key,
|
|
426
|
+
hash: uploadResult.hash,
|
|
427
|
+
};
|
|
428
|
+
this._logger.info(
|
|
429
|
+
`Uploaded manifest.\n\tKey: ${uploadResult.key}\n\tHash: ${uploadResult.hash}`
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
this._logger.info('Setting up the escrow...');
|
|
433
|
+
const contractSetup = await this._raffleExecute(
|
|
434
|
+
this.contractData.escrow,
|
|
435
|
+
'setup',
|
|
436
|
+
repuationOracleAddr,
|
|
437
|
+
recordingOracleAddr,
|
|
438
|
+
reputationOracleStake,
|
|
439
|
+
recordingOracleStake,
|
|
440
|
+
this.manifestData?.manifestlink?.url,
|
|
441
|
+
this.manifestData?.manifestlink?.hash
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
if (!contractSetup) {
|
|
445
|
+
this._logError(new Error('Failed to setup contract'));
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
this._logger.info('Escrow is set up.');
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
(await this.status()) === EscrowStatus.Pending &&
|
|
453
|
+
(await this.balance())?.toString() === toFullDigit(this.amount).toString()
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Add trusted handlers
|
|
459
|
+
*
|
|
460
|
+
* Add trusted handlers that can freely transact with the contract and
|
|
461
|
+
* perform aborts and cancels for example.
|
|
462
|
+
*
|
|
463
|
+
* @param {string[]} handlers - Trusted handlers to add
|
|
464
|
+
* @returns {Promise<boolean>} - True if trusted handlers are added successfully.
|
|
465
|
+
*/
|
|
466
|
+
async addTrustedHandlers(handlers: string[]): Promise<boolean> {
|
|
467
|
+
if (!this.contractData?.escrow) {
|
|
468
|
+
this._logError(ErrorJobNotLaunched);
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const result = await this._raffleExecute(
|
|
473
|
+
this.contractData.escrow,
|
|
474
|
+
'addTrustedHandlers',
|
|
475
|
+
handlers
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
if (!result) {
|
|
479
|
+
this._logError(new Error('Failed to add trusted handlers to the job'));
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* **Bulk payout**
|
|
488
|
+
*
|
|
489
|
+
* Payout the workers submitting the result.
|
|
490
|
+
*
|
|
491
|
+
* @param {Payout[]} payouts - Workers address & amount to payout
|
|
492
|
+
* @param {Result} result - Job result submitted
|
|
493
|
+
* @param {bool} encrypt - Whether to encrypt the result, or not
|
|
494
|
+
* @param {bool} isPublic - Whether to store data in public storage, or private.
|
|
495
|
+
* @returns {Promise<boolean>} - True if the workers are paid out successfully.
|
|
496
|
+
*/
|
|
497
|
+
async bulkPayout(
|
|
498
|
+
payouts: Payout[],
|
|
499
|
+
result: Result,
|
|
500
|
+
encrypt = true,
|
|
501
|
+
isPublic = false
|
|
502
|
+
): Promise<boolean> {
|
|
503
|
+
if (!this.providerData?.reputationOracle) {
|
|
504
|
+
this._logError(ErrorReputationOracleMissing);
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (!this.contractData?.escrow) {
|
|
509
|
+
this._logError(ErrorJobNotLaunched);
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this._logger.info('Uploading result...');
|
|
514
|
+
const uploadResult = await this._upload(result, encrypt, isPublic);
|
|
515
|
+
|
|
516
|
+
if (!uploadResult) {
|
|
517
|
+
this._logError(new Error('Error uploading result'));
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const { key, hash } = uploadResult;
|
|
522
|
+
this._logger.info(`Uploaded result.\n\tKey: ${key}\n\tHash: ${hash}`);
|
|
523
|
+
|
|
524
|
+
if (!this.storageAccessData) {
|
|
525
|
+
this._logError(ErrorStorageAccessDataMissing);
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const url = isPublic ? getPublicURL(this.storageAccessData, key) : key;
|
|
530
|
+
|
|
531
|
+
this._logger.info('Bulk paying out the workers...');
|
|
532
|
+
await this._raffleExecute(
|
|
533
|
+
this.contractData.escrow,
|
|
534
|
+
'bulkPayOut',
|
|
535
|
+
payouts.map(({ address }) => address),
|
|
536
|
+
payouts.map(({ amount }) => toFullDigit(amount)),
|
|
537
|
+
url,
|
|
538
|
+
hash,
|
|
539
|
+
1
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
const bulkPaid = await this.contractData.escrow.bulkPaid();
|
|
543
|
+
if (!bulkPaid) {
|
|
544
|
+
this._logError(new Error('Failed to bulk payout users'));
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
this._logger.info('Workers are paid out.');
|
|
549
|
+
|
|
550
|
+
return bulkPaid;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* **Abort the escrow**
|
|
555
|
+
*
|
|
556
|
+
* @returns {Promise<boolean>} - True if the escrow is aborted successfully.
|
|
557
|
+
*/
|
|
558
|
+
async abort(): Promise<boolean> {
|
|
559
|
+
if (!this.contractData?.escrow) {
|
|
560
|
+
this._logError(ErrorJobNotLaunched);
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
this._logger.info('Aborting the job...');
|
|
565
|
+
const aborted = await this._raffleExecute(
|
|
566
|
+
this.contractData.escrow,
|
|
567
|
+
'abort'
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
if (!aborted) {
|
|
571
|
+
this._logError(new Error('Failed to abort the job'));
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
this._logger.info('Job is aborted successfully.');
|
|
575
|
+
|
|
576
|
+
return aborted;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* **Cancel the escrow**
|
|
581
|
+
*
|
|
582
|
+
* @returns {Promise<boolean>} - True if the escrow is cancelled successfully.
|
|
583
|
+
*/
|
|
584
|
+
async cancel(): Promise<boolean> {
|
|
585
|
+
if (!this.contractData?.escrow) {
|
|
586
|
+
this._logError(ErrorJobNotLaunched);
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
this._logger.info('Cancelling the job...');
|
|
591
|
+
const cancelled = await this._raffleExecute(
|
|
592
|
+
this.contractData.escrow,
|
|
593
|
+
'cancel'
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
if (!cancelled) {
|
|
597
|
+
this._logError(new Error('Failed to cancel the job'));
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
this._logger.info('Job is cancelled successfully.');
|
|
601
|
+
|
|
602
|
+
return (await this.status()) === EscrowStatus.Cancelled;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* **Store intermediate result**
|
|
607
|
+
*
|
|
608
|
+
* Uploads intermediate result to the storage, and saves the URL/Hash on-chain.
|
|
609
|
+
*
|
|
610
|
+
* @param {Result} result - Intermediate result
|
|
611
|
+
* @returns {Promise<boolean>} - True if the intermediate result is stored successfully.
|
|
612
|
+
*/
|
|
613
|
+
async storeIntermediateResults(result: Result): Promise<boolean> {
|
|
614
|
+
if (!this.providerData?.reputationOracle) {
|
|
615
|
+
this._logError(ErrorReputationOracleMissing);
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (!this.contractData?.escrow) {
|
|
620
|
+
this._logError(ErrorJobNotLaunched);
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
this._logger.info('Uploading intermediate result...');
|
|
625
|
+
const uploadResult = await this._upload(result);
|
|
626
|
+
|
|
627
|
+
if (!uploadResult) {
|
|
628
|
+
this._logError(new Error('Error uploading intermediate result'));
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const { key, hash } = uploadResult;
|
|
633
|
+
this._logger.info(
|
|
634
|
+
`Uploaded intermediate result.\n\tKey: ${key}\n\tHash: ${hash}`
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
this._logger.info('Saving intermediate result on-chain...');
|
|
638
|
+
const resultStored = await this._raffleExecute(
|
|
639
|
+
this.contractData.escrow,
|
|
640
|
+
'storeResults',
|
|
641
|
+
key,
|
|
642
|
+
hash
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
if (!resultStored) {
|
|
646
|
+
this._logError(new Error('Failed to store results'));
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
this._logger.info('Intermediate result is stored on-chain successfully.');
|
|
650
|
+
|
|
651
|
+
this.manifestData = {
|
|
652
|
+
...this.manifestData,
|
|
653
|
+
intermediateResultLink: { url: key, hash },
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
return resultStored;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* **Complete the escrow**
|
|
661
|
+
*
|
|
662
|
+
* @returns {Promise<boolean>} - True if the escrow if completed successfully.
|
|
663
|
+
*/
|
|
664
|
+
async complete() {
|
|
665
|
+
if (!this.contractData?.escrow) {
|
|
666
|
+
this._logError(ErrorJobNotLaunched);
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const completed = await this._raffleExecute(
|
|
671
|
+
this.contractData.escrow,
|
|
672
|
+
'complete'
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
if (!completed) {
|
|
676
|
+
this._logError(new Error('Failed to complete the job'));
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return (await this.status()) === EscrowStatus.Complete;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* **Stake HMTokens**
|
|
685
|
+
*
|
|
686
|
+
* @param {number} amount - Amount to stake
|
|
687
|
+
* @param {string | undefined} from - Address to stake
|
|
688
|
+
* @returns {Promise<boolean>} - True if the token is staked
|
|
689
|
+
*/
|
|
690
|
+
async stake(amount: number, from?: string) {
|
|
691
|
+
if (!this.contractData?.staking) {
|
|
692
|
+
this._logError(ErrorStakingMissing);
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
if (!this.contractData.hmToken) {
|
|
696
|
+
this._logError(ErrorHMTokenMissing);
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const operator = this._findOperator(from);
|
|
701
|
+
|
|
702
|
+
if (!operator) {
|
|
703
|
+
this._logError(new Error('Unknown wallet'));
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
const approved = await this.contractData.hmToken
|
|
709
|
+
.connect(operator)
|
|
710
|
+
.approve(this.contractData.staking.address, toFullDigit(amount));
|
|
711
|
+
|
|
712
|
+
if (!approved) {
|
|
713
|
+
throw new Error('Not approved');
|
|
714
|
+
}
|
|
715
|
+
} catch {
|
|
716
|
+
this._logError(new Error('Error approving HMTokens for staking'));
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
try {
|
|
721
|
+
await this.contractData.staking
|
|
722
|
+
.connect(operator)
|
|
723
|
+
.stake(toFullDigit(amount));
|
|
724
|
+
} catch {
|
|
725
|
+
this._logError(new Error(`Error executing transaction from ${from}`));
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* **Unstake HMTokens**
|
|
733
|
+
*
|
|
734
|
+
* @param {number} amount - Amount to unstake
|
|
735
|
+
* @param {string | undefined} from - Address to unstake
|
|
736
|
+
* @returns {Promise<boolean>} - True if the token is unstaked
|
|
737
|
+
*/
|
|
738
|
+
async unstake(amount: number, from?: string) {
|
|
739
|
+
if (!this.contractData?.staking) {
|
|
740
|
+
this._logError(ErrorStakingMissing);
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const operator = this._findOperator(from);
|
|
745
|
+
|
|
746
|
+
if (!operator) {
|
|
747
|
+
this._logError(new Error('Unknown wallet'));
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
try {
|
|
752
|
+
await this.contractData.staking
|
|
753
|
+
.connect(operator)
|
|
754
|
+
.unstake(toFullDigit(amount));
|
|
755
|
+
} catch {
|
|
756
|
+
this._logError(new Error(`Error executing transaction from ${from}`));
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* **Withdraw unstaked HMTokens**
|
|
764
|
+
*
|
|
765
|
+
* @param {string | undefined} from - Address to withdraw
|
|
766
|
+
* @returns {Promise<boolean>} - True if the token is withdrawn
|
|
767
|
+
*/
|
|
768
|
+
async withdraw(from?: string) {
|
|
769
|
+
if (!this.contractData?.staking) {
|
|
770
|
+
this._logError(ErrorStakingMissing);
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const operator = this._findOperator(from);
|
|
775
|
+
|
|
776
|
+
if (!operator) {
|
|
777
|
+
this._logError(new Error('Unknown wallet'));
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
try {
|
|
782
|
+
await this.contractData.staking.connect(operator).withdraw();
|
|
783
|
+
} catch {
|
|
784
|
+
this._logError(new Error(`Error executing transaction from ${from}`));
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* **Allocate HMTokens staked to the job**
|
|
792
|
+
*
|
|
793
|
+
* @param {number} amount - Amount to allocate
|
|
794
|
+
* @param {string | undefined} - Address to allocate with
|
|
795
|
+
* @returns {Promise<boolean>} - True if the token is allocated
|
|
796
|
+
*/
|
|
797
|
+
async allocate(amount: number, from?: string) {
|
|
798
|
+
if (!this.contractData?.staking) {
|
|
799
|
+
this._logError(ErrorStakingMissing);
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (!this.contractData.escrowAddr) {
|
|
804
|
+
this._logError(ErrorJobNotLaunched);
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const operator = this._findOperator(from);
|
|
809
|
+
|
|
810
|
+
if (!operator) {
|
|
811
|
+
this._logError(new Error('Unknown wallet'));
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
try {
|
|
816
|
+
await this.contractData.staking
|
|
817
|
+
.connect(operator)
|
|
818
|
+
.allocate(this.contractData.escrowAddr, toFullDigit(amount));
|
|
819
|
+
} catch {
|
|
820
|
+
this._logError(new Error(`Error executing transaction from ${from}`));
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* **Unallocate HMTokens from the job**
|
|
828
|
+
*
|
|
829
|
+
* @param {string | undefined} - Address to close allocation with
|
|
830
|
+
* @returns {Promise<boolean>} - True if the token is unallocated.
|
|
831
|
+
*/
|
|
832
|
+
async closeAllocation(from?: string) {
|
|
833
|
+
if (!this.contractData?.staking) {
|
|
834
|
+
this._logError(ErrorStakingMissing);
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (!this.contractData.escrowAddr) {
|
|
839
|
+
this._logError(ErrorJobNotLaunched);
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const operator = this._findOperator(from);
|
|
844
|
+
|
|
845
|
+
if (!operator) {
|
|
846
|
+
this._logError(new Error('Unknown wallet'));
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
await this.contractData.staking
|
|
852
|
+
.connect(operator)
|
|
853
|
+
.closeAllocation(this.contractData.escrowAddr);
|
|
854
|
+
} catch {
|
|
855
|
+
this._logError(new Error(`Error executing transaction from ${from}`));
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* **Get current status of the escrow**
|
|
863
|
+
*
|
|
864
|
+
* @returns {Promise<EscrowStatus | undefined>} - Status of the escrow
|
|
865
|
+
*/
|
|
866
|
+
async status(): Promise<EscrowStatus | undefined> {
|
|
867
|
+
if (!this.contractData?.escrow) {
|
|
868
|
+
this._logError(ErrorJobNotLaunched);
|
|
869
|
+
return undefined;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return (await this.contractData.escrow.status()) as EscrowStatus;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* **Get current balance of the escrow**
|
|
877
|
+
*
|
|
878
|
+
* @returns {Promise<BigNumber | undefined>} - Balance of the escrow
|
|
879
|
+
*/
|
|
880
|
+
async balance(): Promise<BigNumber | undefined> {
|
|
881
|
+
if (!this.contractData?.escrow) {
|
|
882
|
+
this._logError(ErrorJobNotLaunched);
|
|
883
|
+
return undefined;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return await this.contractData.escrow.getBalance();
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* **Get intermediate result stored**
|
|
891
|
+
*
|
|
892
|
+
* @returns {Promise<Result | undefined>} - Intermediate result
|
|
893
|
+
*/
|
|
894
|
+
async intermediateResults(): Promise<Result | undefined> {
|
|
895
|
+
if (!this.manifestData?.intermediateResultLink) {
|
|
896
|
+
this._logError(new Error('Intermediate result is missing.'));
|
|
897
|
+
return undefined;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return this._download(this.manifestData.intermediateResultLink.url);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* **Get final result stored**
|
|
905
|
+
*
|
|
906
|
+
* @returns {Promise<Result | undefined>} - Final result
|
|
907
|
+
*/
|
|
908
|
+
async finalResults(): Promise<Result | undefined> {
|
|
909
|
+
if (!this.contractData?.escrow) {
|
|
910
|
+
this._logError(ErrorJobNotLaunched);
|
|
911
|
+
return undefined;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const finalResultsURL = await this.contractData?.escrow?.finalResultsUrl();
|
|
915
|
+
|
|
916
|
+
if (!finalResultsURL) {
|
|
917
|
+
return undefined;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const key = getKeyFromURL(finalResultsURL);
|
|
921
|
+
|
|
922
|
+
return await this._download(key);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* **Check if handler is trusted**
|
|
927
|
+
*
|
|
928
|
+
* @param {string} handlerAddr Address of the handler
|
|
929
|
+
* @returns {Promise<boolean>} - True if the handler is trusted
|
|
930
|
+
*/
|
|
931
|
+
async isTrustedHandler(handlerAddr: string): Promise<boolean> {
|
|
932
|
+
if (!this.contractData?.escrow) {
|
|
933
|
+
this._logError(ErrorJobNotLaunched);
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return await this.contractData?.escrow?.areTrustedHandlers(handlerAddr);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* **Download result from cloud storage**
|
|
942
|
+
*
|
|
943
|
+
* @param {string | undefined} url - Result URL to download
|
|
944
|
+
* @returns {Result | undefined} - Downloaded result
|
|
945
|
+
*/
|
|
946
|
+
private async _download(url?: string): Promise<Result | undefined> {
|
|
947
|
+
if (!url || !this.providerData?.reputationOracle) {
|
|
948
|
+
return undefined;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (!this.storageAccessData) {
|
|
952
|
+
this._logError(ErrorStorageAccessDataMissing);
|
|
953
|
+
return undefined;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return await download(
|
|
957
|
+
this.storageAccessData,
|
|
958
|
+
url,
|
|
959
|
+
this.providerData?.reputationOracle.privateKey
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* **Uploads result to cloud storage**
|
|
965
|
+
*
|
|
966
|
+
* @param {Result} result - Result to upload
|
|
967
|
+
* @param {boolean} encrypt - Whether to encrypt result, or not.
|
|
968
|
+
* @param {bool} isPublic - Whether to store data in public storage, or private.
|
|
969
|
+
* @returns {Promise<UploadResult | undefined>} - Uploaded result
|
|
970
|
+
*/
|
|
971
|
+
private async _upload(
|
|
972
|
+
result: Result,
|
|
973
|
+
encrypt = true,
|
|
974
|
+
isPublic = false
|
|
975
|
+
): Promise<UploadResult | undefined> {
|
|
976
|
+
if (!this.providerData?.reputationOracle) {
|
|
977
|
+
this._logError(ErrorReputationOracleMissing);
|
|
978
|
+
return undefined;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
if (!this.storageAccessData) {
|
|
982
|
+
this._logError(ErrorStorageAccessDataMissing);
|
|
983
|
+
return undefined;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return await upload(
|
|
987
|
+
this.storageAccessData,
|
|
988
|
+
result,
|
|
989
|
+
this.providerData?.reputationOracle?.publicKey,
|
|
990
|
+
encrypt,
|
|
991
|
+
isPublic
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* **Raffle executes on-chain call**
|
|
997
|
+
*
|
|
998
|
+
* Try to execute the on-chain call from all possible trusted handlers
|
|
999
|
+
*
|
|
1000
|
+
* @param {Function} txn - On-chain call to execute
|
|
1001
|
+
* @param {any} args - On-chain call arguments
|
|
1002
|
+
* @returns {Promise<boolean>} - True if one of handler succeed to execute the on-chain call
|
|
1003
|
+
*/
|
|
1004
|
+
private async _raffleExecute(
|
|
1005
|
+
contract: Contract,
|
|
1006
|
+
functionName: string,
|
|
1007
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1008
|
+
...args: any
|
|
1009
|
+
): Promise<boolean> {
|
|
1010
|
+
try {
|
|
1011
|
+
if (this.providerData?.gasPayer) {
|
|
1012
|
+
await contract
|
|
1013
|
+
.connect(this.providerData.gasPayer)
|
|
1014
|
+
.functions[functionName](...args);
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
this._logger.info(
|
|
1018
|
+
'Default gas payer is missing, trying with other trusted handlers...'
|
|
1019
|
+
);
|
|
1020
|
+
} catch (err) {
|
|
1021
|
+
this._logger.info(
|
|
1022
|
+
'Error executing the transaction from default gas payer, trying with other trusted handlers...'
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
for (const trustedHandler of this.providerData?.trustedHandlers || []) {
|
|
1027
|
+
try {
|
|
1028
|
+
await contract.connect(trustedHandler).functions[functionName](...args);
|
|
1029
|
+
return true;
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
this._logError(
|
|
1032
|
+
new Error(
|
|
1033
|
+
'Error executing the transaction from all of the trusted handlers. Stop continue executing...'
|
|
1034
|
+
)
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* **Error log**
|
|
1043
|
+
*
|
|
1044
|
+
* @param {Error} error - Occured error
|
|
1045
|
+
*/
|
|
1046
|
+
private _logError(error: Error) {
|
|
1047
|
+
this._logger.error(error.message);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* **Find operator to execute tx**
|
|
1052
|
+
*
|
|
1053
|
+
* @param {string} addr - Address of the operator
|
|
1054
|
+
* @returns {ethers.Wallet | undefined} - Operator wallet
|
|
1055
|
+
*/
|
|
1056
|
+
private _findOperator(addr?: string): ethers.Wallet | undefined {
|
|
1057
|
+
return addr
|
|
1058
|
+
? [
|
|
1059
|
+
this.providerData?.gasPayer,
|
|
1060
|
+
...(this.providerData?.trustedHandlers || []),
|
|
1061
|
+
].find((account?: ethers.Wallet) => account?.address === addr)
|
|
1062
|
+
: this.providerData?.gasPayer;
|
|
1063
|
+
}
|
|
1064
|
+
}
|