UNPKG

18.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.TasksService = void 0;
4const antelope_1 = require("@wharfkit/antelope");
5const session_1 = require("@wharfkit/session");
6const utils_1 = require("./utils");
7class TasksService {
8 constructor(client) {
9 this.client = client;
10 /**
11 *
12 */
13 this.getAllAccTaskIdx = async () => {
14 try {
15 const response = await this.client.eos.v1.chain.get_table_rows({
16 code: this.client.config.tasksContract,
17 table: 'acctaskidx',
18 scope: this.client.config.tasksContract,
19 });
20 // console.debug('getAllAccTaskIdx', response)
21 return response.rows;
22 }
23 catch (error) {
24 console.error(error);
25 throw error;
26 }
27 };
28 /**
29 * Get payout delay
30 * @returns the payout delay in seconds
31 * @throws error if the payout delay is not available
32 */
33 this.getForceSettings = async () => {
34 try {
35 const response = await this.client.eos.v1.chain.get_table_rows({
36 code: this.client.config.tasksContract,
37 scope: this.client.config.tasksContract,
38 table: 'settings',
39 });
40 const [config] = response.rows;
41 return config;
42 }
43 catch (error) {
44 console.error(error);
45 throw new Error('Error retrieving Force settings');
46 }
47 };
48 }
49 // TODO: https://wharfkit.com/guides/contract-kit/reading-tables
50 // This needs to be tested when their are more campaigns on jungle.
51 /**
52 * Retrieve all campaigns published to Effect Network
53 * @returns {Campaign[]} Promise<Campaign[]>
54 */
55 async getAllCampaigns(ipfsFetch = true) {
56 const rows = [];
57 const boundDelta = 20;
58 let lowerBound = antelope_1.UInt128.from(0);
59 let upperBound = antelope_1.UInt128.from(boundDelta);
60 let more = true;
61 while (more) {
62 const response = await this.client.eos.v1.chain.get_table_rows({
63 code: this.client.config.tasksContract,
64 table: 'campaign',
65 scope: this.client.config.tasksContract,
66 lower_bound: lowerBound,
67 upper_bound: upperBound,
68 });
69 rows.push(...response.rows);
70 if (response.more) {
71 const lastRow = response.rows[response.rows.length - 1];
72 lowerBound = antelope_1.UInt128.from(lastRow.id + 1);
73 upperBound = antelope_1.UInt128.from(lastRow.id + boundDelta);
74 }
75 else {
76 more = false;
77 }
78 }
79 if (ipfsFetch) {
80 for (const campaign of rows) {
81 campaign.info = await this.client.ipfs.fetch(campaign.content.field_1);
82 }
83 }
84 return rows;
85 }
86 /**
87 * Retrieve campaign by id
88 * @param id id of the campaign
89 * @returns {Promise<Campaign>} Campaign
90 */
91 async getCampaign(id, fetchIpfs = true) {
92 try {
93 const response = await this.client.eos.v1.chain.get_table_rows({
94 code: this.client.config.tasksContract,
95 table: 'campaign',
96 scope: this.client.config.tasksContract,
97 lower_bound: antelope_1.UInt128.from(id),
98 upper_bound: antelope_1.UInt128.from(id),
99 limit: 1,
100 });
101 const [campaign] = response.rows;
102 if (campaign === undefined) {
103 throw new Error(`Campaign with id ${id} not found`);
104 }
105 if (fetchIpfs) {
106 campaign.info = await this.client.ipfs.fetch(campaign.content.field_1);
107 }
108 return campaign;
109 }
110 catch (error) {
111 console.error(error);
112 throw error;
113 }
114 }
115 /**
116 * Create a new campaign
117 * @param campaign InitCampaign
118 */
119 async makeCampaign(campaign) {
120 const authorization = this.client.sessionAuth();
121 try {
122 const hash = await this.client.ipfs.upload(campaign.info);
123 const response = await this.client.session.transact({
124 action: {
125 account: this.client.config.tasksContract,
126 name: 'mkcampaign',
127 authorization,
128 data: {
129 owner: this.client.session.actor,
130 content: { field_0: 0, field_1: hash },
131 max_task_time: campaign.max_task_time,
132 reward: { quantity: campaign.quantity, contract: this.client.config.tokenContract },
133 qualis: campaign.qualis ?? [],
134 payer: this.client.session.actor,
135 },
136 }
137 });
138 return response;
139 }
140 catch (error) {
141 console.error(error);
142 throw error;
143 }
144 }
145 /**
146 * Retrieve Batch by id
147 * @param id id of the batch
148 * @returns {Promise<Batch>} Batch
149 */
150 async getBatch(batchId) {
151 const response = await this.client.eos.v1.chain.get_table_rows({
152 code: this.client.config.tasksContract,
153 table: 'batch',
154 scope: this.client.config.tasksContract,
155 lower_bound: antelope_1.UInt128.from(batchId),
156 upper_bound: antelope_1.UInt128.from(batchId),
157 limit: 1,
158 });
159 const [batch] = response.rows;
160 return batch;
161 }
162 /**
163 * Create batch
164 */
165 async makeBatch(initBatch) {
166 this.client.requireSession();
167 try {
168 const vacc = await this.client.vaccount.get();
169 const campaign = await this.getCampaign(initBatch.campaign_id);
170 const assetQuantity = session_1.Asset.from(campaign.reward.quantity);
171 const batchPrice = assetQuantity.value * initBatch.repetitions;
172 // Check if the user has enough funds to pay for the batch
173 // if (Asset.from(vacc.balance.quantity).value < batchPrice) {
174 // throw new Error('Not enough funds in vAccount to pay for batch')
175 // }
176 // Validate the batch before uploading, will throw error
177 if (campaign.info?.input_schema) {
178 (0, utils_1.validateBatchData)(initBatch, campaign);
179 }
180 const newBatchId = campaign.num_batches + 1;
181 const hash = await this.client.ipfs.upload(initBatch.data);
182 const makeBatch = await this.client.action.makeBatchAction(initBatch, hash);
183 const vTransfer = this.client.action.vTransferAction(vacc, batchPrice);
184 const publishBatch = this.client.action.publishBatchAction(newBatchId, initBatch.repetitions); // TODO Check if batchId is correct.
185 let actions;
186 if (session_1.Asset.from(vacc.balance.quantity).value < batchPrice) {
187 const depositAction = this.client.action.depositAction(assetQuantity.value, vacc);
188 actions = [depositAction, makeBatch, vTransfer, publishBatch];
189 }
190 else {
191 actions = [makeBatch, vTransfer, publishBatch];
192 }
193 const response = await this.client.session.transact({ actions });
194 return response;
195 }
196 catch (error) {
197 console.error(error);
198 throw error;
199 }
200 }
201 /**
202 * Fetch the task data
203 * Load the batch the task is in (get _task_.batch_id from the batch table)
204 * Get the batch IPFS hash from batch.content.value
205 * Load the IPFS object and confirm it is a JSON array. Get the _task_.task_idxth item from the array
206 * Render the campaign template with that task data
207 */
208 async getTaskData(reservation) {
209 try {
210 const batch = await this.getBatch(reservation.batch_id);
211 const ipfsData = await this.client.ipfs.fetch(batch.content.field_1);
212 // check if the ipfsData is an array
213 if (!Array.isArray(ipfsData)) {
214 throw new Error(`Task data retrieved from IPFS is not an array. \n${String(ipfsData)}`);
215 }
216 // Check if there is a task at the index
217 const taskIndex = reservation.task_idx;
218 if (ipfsData.length <= taskIndex || taskIndex < 0) {
219 throw new Error(`Task data retrieved from IPFS does not have a task at index ${taskIndex}. \n${JSON.stringify(ipfsData)}`);
220 }
221 return ipfsData[taskIndex];
222 }
223 catch (error) {
224 console.error(error);
225 throw new Error(error.message);
226 }
227 }
228 /**
229 * Task availability
230 * TODO: This is a WIP
231 */
232 async taskAvailable(reservation) {
233 try {
234 const batch = await this.getBatch(reservation.batch_id);
235 // Is it true that when the num_tasks is equal to the task_idx that the batch there is still work to be done in the batch?
236 // How is this affected when there are more than 1 batches?
237 // How is this affected when there are more reps?
238 // Does num_tasks change when new tasks are added or removed?
239 return batch.num_tasks >= reservation.task_idx;
240 }
241 catch (error) {
242 console.error(error);
243 throw error;
244 }
245 }
246 /**
247 * Get repitions done for a task in a campaign.
248 */
249 async getAllRepsDone() {
250 try {
251 const response = await this.client.eos.v1.chain.get_table_rows({
252 code: this.client.config.tasksContract,
253 table: 'repsdone',
254 scope: this.client.config.tasksContract,
255 });
256 return response.rows;
257 }
258 catch (error) {
259 console.error(error);
260 throw error;
261 }
262 }
263 /**
264 * Submit task
265 * Call submittask(camapign_id, task_idx, data, account_id, sig). Note to use _task_.task_idx for the task_idx parameter (not the ID).
266 * sig (for BSC only): to avoid replay attacks, the signature is (mark)(campaign_id)(task_idx)(data). The mark value is 5.
267 */
268 async submitTask(reservation, data) {
269 const authorization = this.client.sessionAuth();
270 try {
271 const ipfsData = await this.client.ipfs.upload(data);
272 const response = await this.client.session.transact({
273 action: {
274 account: this.client.config.tasksContract,
275 name: 'submittask',
276 authorization,
277 data: {
278 campaign_id: antelope_1.UInt32.from(reservation.campaign_id),
279 account_id: antelope_1.UInt32.from(reservation.account_id),
280 task_idx: antelope_1.UInt32.from(reservation.task_idx),
281 data: ipfsData,
282 payer: this.client.session.actor,
283 sig: null,
284 },
285 }
286 });
287 return response;
288 }
289 catch (error) {
290 console.error(error);
291 throw error;
292 }
293 }
294 /**
295 * Retrieve all reservations
296 * @returns {Promise<Reservation[]>} Reservation[]
297 */
298 async getAllReservations() {
299 try {
300 const response = await this.client.eos.v1.chain.get_table_rows({
301 code: this.client.config.tasksContract,
302 table: 'reservation',
303 scope: this.client.config.tasksContract,
304 });
305 while (response.more) {
306 const lastRow = response.rows[response.rows.length - 1];
307 const lowerBound = antelope_1.UInt64.from(lastRow.id + 1);
308 const upperBound = antelope_1.UInt64.from(lastRow.id + 21);
309 const moreResponse = await this.client.eos.v1.chain.get_table_rows({
310 code: this.client.config.tasksContract,
311 table: 'reservation',
312 scope: this.client.config.tasksContract,
313 lower_bound: lowerBound,
314 upper_bound: upperBound,
315 });
316 response.rows.push(...moreResponse.rows);
317 response.more = moreResponse.more;
318 }
319 return response.rows;
320 }
321 catch (error) {
322 console.error(error);
323 throw error;
324 }
325 }
326 /**
327 * Get Campaign Reservation for user
328 * @param campaignId id of the campaign
329 * @param accountId id of the account
330 * @returns {Promise<Reservation>} Reservation
331 */
332 async getCampaignReservation(campaignId, accountId) {
333 try {
334 // create a composite Uint64 key from two Uint32 keys
335 const a = new Uint8Array(8);
336 a.set(antelope_1.UInt32.from(campaignId).byteArray, 0);
337 a.set(antelope_1.UInt32.from(accountId).byteArray, 4);
338 const bound = antelope_1.UInt64.from(a);
339 const response = await this.client.eos.v1.chain.get_table_rows({
340 code: this.client.config.tasksContract,
341 table: 'reservation',
342 index_position: 'secondary',
343 scope: this.client.config.tasksContract,
344 upper_bound: bound,
345 lower_bound: bound,
346 });
347 const [reservation] = response.rows;
348 return reservation;
349 }
350 catch (error) {
351 console.error(error);
352 throw error;
353 }
354 }
355 /**
356 * Get the reservation of logged in user for a campaign.
357 * @param campaignId id of the campaign
358 * @returns {Promise<Reservation>} Reservation
359 */
360 async getMyReservation(campaignId) {
361 try {
362 this.client.requireSession();
363 const vaccount = await this.client.vaccount.get();
364 // create a composite Uint64 key from two Uint32 keys
365 const a = new Uint8Array(8);
366 a.set(antelope_1.UInt32.from(campaignId).byteArray, 0);
367 a.set(antelope_1.UInt32.from(vaccount.id).byteArray, 4);
368 const bound = antelope_1.UInt64.from(a);
369 const response = await this.client.eos.v1.chain.get_table_rows({
370 code: this.client.config.tasksContract,
371 table: 'reservation',
372 index_position: 'secondary',
373 scope: this.client.config.tasksContract,
374 upper_bound: bound,
375 lower_bound: bound,
376 });
377 const [reservation] = response.rows;
378 return reservation;
379 }
380 catch (error) {
381 console.error(error);
382 throw error;
383 }
384 }
385 /**
386 * Reserve a task, will check if the user already has a reservation for this campaign and return it, if not will create a new reservation and return it.
387 * @param campaignId id of the campaign
388 * @param qualiAssets can be null, then the smart contract will search through all the assets of the user.
389 * @returns {Promise<Reservation>} Reservation
390 *
391 * ```mermaid
392 * sequenceDiagram
393 * participant User
394 * participant Client
395 * participant Smart Contract
396 * User->>Client: Login
397 * Client->>Smart Contract: reserveTask(campaignId, qualiAssets)
398 * Smart Contract->>Smart Contract: Check if user already has a reservation for this campaign
399 * Smart Contract->>Smart Contract: If not, create a new reservation
400 * Smart Contract->>Client: Return reservation
401 * Client->>User: Return reservation
402 * ```
403 */
404 async reserveTask(campaignId, qualiAssets) {
405 const authorization = this.client.sessionAuth();
406 const myReservation = await this.getMyReservation(campaignId);
407 if (myReservation) {
408 return myReservation;
409 }
410 else {
411 const vacc = await this.client.vaccount.get();
412 await this.client.session.transact({
413 action: {
414 account: this.client.config.tasksContract,
415 name: 'reservetask',
416 authorization,
417 data: {
418 campaign_id: campaignId,
419 account_id: vacc.id,
420 quali_assets: qualiAssets,
421 payer: this.client.session.actor,
422 sig: null,
423 },
424 }
425 });
426 // TODO: Sleep for a bit for now, use finality plugin later.
427 await new Promise(resolve => setTimeout(resolve, 3000));
428 return await this.getMyReservation(campaignId);
429 }
430 }
431 /**
432 * Retrieve Effect Network Qualification NFT for user.
433 * @param accountId id of the account
434 * @returns {Promise<Qualification>} Qualification NFT
435 */
436 async getQualifications(accountId) {
437 // We should look at the current implementation for how AtomicAssets implemented this.
438 // We can mock this by using atomic assets nfts on jungle
439 const response = await this.client.eos.v1.chain.get_table_rows({
440 code: this.client.config.atomicAssetsContract,
441 table: 'assets',
442 scope: this.client.config.atomicAssetsContract,
443 limit: 50,
444 upper_bound: antelope_1.UInt128.from(accountId),
445 lower_bound: antelope_1.UInt128.from(accountId),
446 index_position: 'secondary', // TODO: Which index do I need to have?
447 // key_type: 'sha256', // TODO: Is this needed? if this is set than the lowerbound needs to be of type Checksum
448 });
449 return response.rows;
450 }
451 /**
452 * TODO: Figure out the interface for a Qualification NFT Asset
453 * Retrieve Effect Network Qualification NFT Collection
454 *
455 */
456 async getQualificationCollection() {
457 const bounds = 'effect.network';
458 const response = await this.client.eos.v1.chain.get_table_rows({
459 code: this.client.config.atomicAssetsContract,
460 table: 'collections',
461 scope: this.client.config.atomicAssetsContract,
462 limit: 1,
463 upper_bound: antelope_1.UInt128.from(1),
464 lower_bound: antelope_1.UInt128.from(1),
465 index_position: 'primary',
466 });
467 }
468}
469exports.TasksService = TasksService;
470//# sourceMappingURL=tasks.js.map
\No newline at end of file