@effectai/effect-js
Version:
Effect Network Javscript/Typescript SDK (for [https://effect.network](https://effect.network))
470 lines • 18.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TasksService = void 0;
const antelope_1 = require("@wharfkit/antelope");
const session_1 = require("@wharfkit/session");
const utils_1 = require("./utils");
class TasksService {
constructor(client) {
this.client = client;
/**
*
*/
this.getAllAccTaskIdx = async () => {
try {
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'acctaskidx',
scope: this.client.config.tasksContract,
});
// console.debug('getAllAccTaskIdx', response)
return response.rows;
}
catch (error) {
console.error(error);
throw error;
}
};
/**
* Get payout delay
* @returns the payout delay in seconds
* @throws error if the payout delay is not available
*/
this.getForceSettings = async () => {
try {
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
scope: this.client.config.tasksContract,
table: 'settings',
});
const [config] = response.rows;
return config;
}
catch (error) {
console.error(error);
throw new Error('Error retrieving Force settings');
}
};
}
// TODO: https://wharfkit.com/guides/contract-kit/reading-tables
// This needs to be tested when their are more campaigns on jungle.
/**
* Retrieve all campaigns published to Effect Network
* @returns {Campaign[]} Promise<Campaign[]>
*/
async getAllCampaigns(ipfsFetch = true) {
const rows = [];
const boundDelta = 20;
let lowerBound = antelope_1.UInt128.from(0);
let upperBound = antelope_1.UInt128.from(boundDelta);
let more = true;
while (more) {
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'campaign',
scope: this.client.config.tasksContract,
lower_bound: lowerBound,
upper_bound: upperBound,
});
rows.push(...response.rows);
if (response.more) {
const lastRow = response.rows[response.rows.length - 1];
lowerBound = antelope_1.UInt128.from(lastRow.id + 1);
upperBound = antelope_1.UInt128.from(lastRow.id + boundDelta);
}
else {
more = false;
}
}
if (ipfsFetch) {
for (const campaign of rows) {
campaign.info = await this.client.ipfs.fetch(campaign.content.field_1);
}
}
return rows;
}
/**
* Retrieve campaign by id
* @param id id of the campaign
* @returns {Promise<Campaign>} Campaign
*/
async getCampaign(id, fetchIpfs = true) {
try {
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'campaign',
scope: this.client.config.tasksContract,
lower_bound: antelope_1.UInt128.from(id),
upper_bound: antelope_1.UInt128.from(id),
limit: 1,
});
const [campaign] = response.rows;
if (campaign === undefined) {
throw new Error(`Campaign with id ${id} not found`);
}
if (fetchIpfs) {
campaign.info = await this.client.ipfs.fetch(campaign.content.field_1);
}
return campaign;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* Create a new campaign
* @param campaign InitCampaign
*/
async makeCampaign(campaign) {
const authorization = this.client.sessionAuth();
try {
const hash = await this.client.ipfs.upload(campaign.info);
const response = await this.client.session.transact({
action: {
account: this.client.config.tasksContract,
name: 'mkcampaign',
authorization,
data: {
owner: this.client.session.actor,
content: { field_0: 0, field_1: hash },
max_task_time: campaign.max_task_time,
reward: { quantity: campaign.quantity, contract: this.client.config.tokenContract },
qualis: campaign.qualis ?? [],
payer: this.client.session.actor,
},
}
});
return response;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* Retrieve Batch by id
* @param id id of the batch
* @returns {Promise<Batch>} Batch
*/
async getBatch(batchId) {
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'batch',
scope: this.client.config.tasksContract,
lower_bound: antelope_1.UInt128.from(batchId),
upper_bound: antelope_1.UInt128.from(batchId),
limit: 1,
});
const [batch] = response.rows;
return batch;
}
/**
* Create batch
*/
async makeBatch(initBatch) {
this.client.requireSession();
try {
const vacc = await this.client.vaccount.get();
const campaign = await this.getCampaign(initBatch.campaign_id);
const assetQuantity = session_1.Asset.from(campaign.reward.quantity);
const batchPrice = assetQuantity.value * initBatch.repetitions;
// Check if the user has enough funds to pay for the batch
// if (Asset.from(vacc.balance.quantity).value < batchPrice) {
// throw new Error('Not enough funds in vAccount to pay for batch')
// }
// Validate the batch before uploading, will throw error
if (campaign.info?.input_schema) {
(0, utils_1.validateBatchData)(initBatch, campaign);
}
const newBatchId = campaign.num_batches + 1;
const hash = await this.client.ipfs.upload(initBatch.data);
const makeBatch = await this.client.action.makeBatchAction(initBatch, hash);
const vTransfer = this.client.action.vTransferAction(vacc, batchPrice);
const publishBatch = this.client.action.publishBatchAction(newBatchId, initBatch.repetitions); // TODO Check if batchId is correct.
let actions;
if (session_1.Asset.from(vacc.balance.quantity).value < batchPrice) {
const depositAction = this.client.action.depositAction(assetQuantity.value, vacc);
actions = [depositAction, makeBatch, vTransfer, publishBatch];
}
else {
actions = [makeBatch, vTransfer, publishBatch];
}
const response = await this.client.session.transact({ actions });
return response;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* Fetch the task data
* Load the batch the task is in (get _task_.batch_id from the batch table)
* Get the batch IPFS hash from batch.content.value
* Load the IPFS object and confirm it is a JSON array. Get the _task_.task_idxth item from the array
* Render the campaign template with that task data
*/
async getTaskData(reservation) {
try {
const batch = await this.getBatch(reservation.batch_id);
const ipfsData = await this.client.ipfs.fetch(batch.content.field_1);
// check if the ipfsData is an array
if (!Array.isArray(ipfsData)) {
throw new Error(`Task data retrieved from IPFS is not an array. \n${String(ipfsData)}`);
}
// Check if there is a task at the index
const taskIndex = reservation.task_idx;
if (ipfsData.length <= taskIndex || taskIndex < 0) {
throw new Error(`Task data retrieved from IPFS does not have a task at index ${taskIndex}. \n${JSON.stringify(ipfsData)}`);
}
return ipfsData[taskIndex];
}
catch (error) {
console.error(error);
throw new Error(error.message);
}
}
/**
* Task availability
* TODO: This is a WIP
*/
async taskAvailable(reservation) {
try {
const batch = await this.getBatch(reservation.batch_id);
// 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?
// How is this affected when there are more than 1 batches?
// How is this affected when there are more reps?
// Does num_tasks change when new tasks are added or removed?
return batch.num_tasks >= reservation.task_idx;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* Get repitions done for a task in a campaign.
*/
async getAllRepsDone() {
try {
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'repsdone',
scope: this.client.config.tasksContract,
});
return response.rows;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* Submit task
* Call submittask(camapign_id, task_idx, data, account_id, sig). Note to use _task_.task_idx for the task_idx parameter (not the ID).
* sig (for BSC only): to avoid replay attacks, the signature is (mark)(campaign_id)(task_idx)(data). The mark value is 5.
*/
async submitTask(reservation, data) {
const authorization = this.client.sessionAuth();
try {
const ipfsData = await this.client.ipfs.upload(data);
const response = await this.client.session.transact({
action: {
account: this.client.config.tasksContract,
name: 'submittask',
authorization,
data: {
campaign_id: antelope_1.UInt32.from(reservation.campaign_id),
account_id: antelope_1.UInt32.from(reservation.account_id),
task_idx: antelope_1.UInt32.from(reservation.task_idx),
data: ipfsData,
payer: this.client.session.actor,
sig: null,
},
}
});
return response;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* Retrieve all reservations
* @returns {Promise<Reservation[]>} Reservation[]
*/
async getAllReservations() {
try {
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'reservation',
scope: this.client.config.tasksContract,
});
while (response.more) {
const lastRow = response.rows[response.rows.length - 1];
const lowerBound = antelope_1.UInt64.from(lastRow.id + 1);
const upperBound = antelope_1.UInt64.from(lastRow.id + 21);
const moreResponse = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'reservation',
scope: this.client.config.tasksContract,
lower_bound: lowerBound,
upper_bound: upperBound,
});
response.rows.push(...moreResponse.rows);
response.more = moreResponse.more;
}
return response.rows;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* Get Campaign Reservation for user
* @param campaignId id of the campaign
* @param accountId id of the account
* @returns {Promise<Reservation>} Reservation
*/
async getCampaignReservation(campaignId, accountId) {
try {
// create a composite Uint64 key from two Uint32 keys
const a = new Uint8Array(8);
a.set(antelope_1.UInt32.from(campaignId).byteArray, 0);
a.set(antelope_1.UInt32.from(accountId).byteArray, 4);
const bound = antelope_1.UInt64.from(a);
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'reservation',
index_position: 'secondary',
scope: this.client.config.tasksContract,
upper_bound: bound,
lower_bound: bound,
});
const [reservation] = response.rows;
return reservation;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* Get the reservation of logged in user for a campaign.
* @param campaignId id of the campaign
* @returns {Promise<Reservation>} Reservation
*/
async getMyReservation(campaignId) {
try {
this.client.requireSession();
const vaccount = await this.client.vaccount.get();
// create a composite Uint64 key from two Uint32 keys
const a = new Uint8Array(8);
a.set(antelope_1.UInt32.from(campaignId).byteArray, 0);
a.set(antelope_1.UInt32.from(vaccount.id).byteArray, 4);
const bound = antelope_1.UInt64.from(a);
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.tasksContract,
table: 'reservation',
index_position: 'secondary',
scope: this.client.config.tasksContract,
upper_bound: bound,
lower_bound: bound,
});
const [reservation] = response.rows;
return reservation;
}
catch (error) {
console.error(error);
throw error;
}
}
/**
* 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.
* @param campaignId id of the campaign
* @param qualiAssets can be null, then the smart contract will search through all the assets of the user.
* @returns {Promise<Reservation>} Reservation
*
* ```mermaid
* sequenceDiagram
* participant User
* participant Client
* participant Smart Contract
* User->>Client: Login
* Client->>Smart Contract: reserveTask(campaignId, qualiAssets)
* Smart Contract->>Smart Contract: Check if user already has a reservation for this campaign
* Smart Contract->>Smart Contract: If not, create a new reservation
* Smart Contract->>Client: Return reservation
* Client->>User: Return reservation
* ```
*/
async reserveTask(campaignId, qualiAssets) {
const authorization = this.client.sessionAuth();
const myReservation = await this.getMyReservation(campaignId);
if (myReservation) {
return myReservation;
}
else {
const vacc = await this.client.vaccount.get();
await this.client.session.transact({
action: {
account: this.client.config.tasksContract,
name: 'reservetask',
authorization,
data: {
campaign_id: campaignId,
account_id: vacc.id,
quali_assets: qualiAssets,
payer: this.client.session.actor,
sig: null,
},
}
});
// TODO: Sleep for a bit for now, use finality plugin later.
await new Promise(resolve => setTimeout(resolve, 3000));
return await this.getMyReservation(campaignId);
}
}
/**
* Retrieve Effect Network Qualification NFT for user.
* @param accountId id of the account
* @returns {Promise<Qualification>} Qualification NFT
*/
async getQualifications(accountId) {
// We should look at the current implementation for how AtomicAssets implemented this.
// We can mock this by using atomic assets nfts on jungle
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.atomicAssetsContract,
table: 'assets',
scope: this.client.config.atomicAssetsContract,
limit: 50,
upper_bound: antelope_1.UInt128.from(accountId),
lower_bound: antelope_1.UInt128.from(accountId),
index_position: 'secondary', // TODO: Which index do I need to have?
// key_type: 'sha256', // TODO: Is this needed? if this is set than the lowerbound needs to be of type Checksum
});
return response.rows;
}
/**
* TODO: Figure out the interface for a Qualification NFT Asset
* Retrieve Effect Network Qualification NFT Collection
*
*/
async getQualificationCollection() {
const bounds = 'effect.network';
const response = await this.client.eos.v1.chain.get_table_rows({
code: this.client.config.atomicAssetsContract,
table: 'collections',
scope: this.client.config.atomicAssetsContract,
limit: 1,
upper_bound: antelope_1.UInt128.from(1),
lower_bound: antelope_1.UInt128.from(1),
index_position: 'primary',
});
}
}
exports.TasksService = TasksService;
//# sourceMappingURL=tasks.js.map