Tasks and Payments
The most useful abstractions within a colony are tasks and payments. Tasks and payments are used to reward colony contributors with tokens and reputation.
Task Roles
There are three "task roles" that can be assigned to a task: MANAGER
, EVALUATOR
, and WORKER
.
Each "task role" has permissions for calling and approving actions associated with the task. Actions that modify a task require approval from two "task roles" if those roles have already been assigned within the given task (see Modify Task for more information).
Role Permissions
Manager | Evaluator | Worker | Other | |
---|---|---|---|---|
addTask | X (ROOT or ADMINISTRATION ) | |||
cancelTask | * | * | ||
setTaskBrief | * | * | ||
setTaskDomain | * | * | ||
setTaskSkill | * | * | ||
setTaskDueDate | * | * | ||
setTaskManagerPayout | * | * | ||
setTaskEvaluatorPayout | * | * | ||
setTaskWorkerPayout | * | * | ||
setTaskManagerRole | * | * new MANAGER (ROOT or ADMINISTRATION ) | ||
setTaskEvaluatorRole | * | * new EVALUATOR | ||
setTaskWorkerRole | * | * new WORKER | ||
removeTaskWorkerRole | * | * | ||
removeTaskEvaluatorRole | * | * | ||
submitTaskDeliverable | X | |||
submitTaskWorkRating | X | X | X | |
revealTaskWorkRating | X | X | X | |
claimPayout | X | X | X | |
finalizeTask |
( X ) - This role can call the method. If there is no "X" in the row, any address can call the method.
( * ) - If the task has been assigned to this role, the operation will require a signature from this role.
Task Lifecycle
All methods associated with tasks can be called using a ColonyClient instance. The examples below are ordered in a way that reflects a general flow of the task lifecycle from start to finish.
Add Task
When adding a task, the task must be assigned a specificationHash
.
// Add a task
await colonyClient.addTask.send({
specificationHash: 'Qm...',
});
The task specification, also known as "task brief", is a description or document that outlines the work required to be considered sufficient for a task payout. The specificationHash
can be any arbitrary hash string (32 bytes) but the specificationHash
was specially designed to support an IPFS content hash. See Using IPFS for more information about using an IPFS content hash.
When creating a task, the task can also be assigned a domainId
, skillId
and dueDate
.
// Add a task
await colonyClient.addTask.send({
specificationHash: 'Qm...',
domainId: 1,
skillId: 2,
dueDate: new Date('...'),
});
An assigned domain is required for every task because reputation earned for completing a task is always earned within the context of domains. The root domain of every colony is 1
, which is the default value if the domainId
is not specified.
An assigned skill is also required for every task. The skillId
is optional when adding a task but it has no default value, therefore, the skillId
must be provided before a WORKER
can be assigned to a task. Like domains, reputation is earned within the context of skills.
The due date is when the completed task work is due. The task work cannot be submitted after the due date has passed. If the dueDate
is not specified when creating a task, the default dueDate
will be 90 days from the time that the task was created.
View Task
Once a task has been created, you can view the task using the getTask
method.
// Get a task
await colonyClient.getTask.call({
taskId: 1,
});
The object returned from the getTask
method will look something like this:
{
specificationHash: 'QmWvM3isCmEY8bsixThuFeUJmE5MN2he1UxaPzMngLZ7Wq',
deliverableHash: null,
status: 'ACTIVE',
dueDate: '2019-01-01T00:00:00.000Z',
potId: 2,
completionDate: null,
domainId: 1,
skillId: 2,
}
We already introduced specificationHash
, domainId
, skillId
and dueDate
. The status
is the current status of the task, which will either be ACTIVE
, CANCELLED
or FINALIZED
(see Cancel Task or Finalize Task for more information). We will further explain potId
and deliverableHash
in the sections below (potId
in Fund Task and deliverableHash
in Submit Work).
Fund Task
Whenever a task is added, a "funding pot" is also added and assigned to the task. Each funding pot assigned to a task is specific to that task, therefore, each task has a unique potId
.
In order to fund a task, you need the potId
of the task (the toPot
in the example below) and the potId
of the domain from which the funds will be moved (the fromPot
in the example below).
// Move funds between pots
await colonyClient.moveFundsBetweenPots.send({
fromPot: 1,
toPot: 2,
amount: new BN('1000000000000000000'),
token: '0x...',
});
The funding pot associated with our task must have enough allocated funds to distribute to each of the task roles that are assigned a payout. If the total payout exceeds the amount of allocated funds in the funding pot, the task cannot be finalized.
You can use getFundingPotInfo
and getFundingPotBalance
to check the status of a funding pot and the current balance of a funding pot. See Tokens and Funding for more information.
Modify Task
Modifying a task must be approved by multiple "task roles" with signatures before the modification will take affect. See Using Multisignature for more information. Task modification methods and the required signatures for each are listed below.
Changing the task brief (MANAGER
and WORKER
):
// Set task brief
await colonyClient.setTaskBrief.startOperation({
taskId: 1,
specificationHash: 'Qm...',
});
Changing the task domain (MANAGER
and WORKER
):
// Set task domain
await colonyClient.setTaskDomain.startOperation({
taskId: 1,
domainId: 1,
});
Changing or setting the task skill (MANAGER
and WORKER
):
// Set task skill
await colonyClient.setTaskSkill.startOperation({
taskId: 1,
skillId: 2,
});
Changing the task due date (MANAGER
and WORKER
):
// Set task due date
await colonyClient.setTaskDueDate.startOperation({
taskId: 1,
dueDate: new Date('...'),
});
Changing or setting the task manager payout (MANAGER
and WORKER
):
// Set task manager payout
await colonyClient.setTaskManagerPayout.startOperation({
taskId: 1,
token: '0x0...',
amount: new BN('1000000000000000000'),
});
Changing or setting the task evaluator payout (MANAGER
and EVALUATOR
):
// Set task evaluator payout
await colonyClient.setTaskEvaluatorPayout.startOperation({
taskId: 1,
token: '0x0...',
amount: new BN('1000000000000000000'),
});
Changing or setting the task worker payout (MANAGER
and WORKER
):
// Set task worker payout
await colonyClient.setTaskWorkerPayout.startOperation({
taskId: 1,
token: '0x0...',
amount: new BN('1000000000000000000'),
});
Changing the task manager role (MANAGER
and proposed MANAGER
):
// Set task manager role
await colonyClient.setTaskManagerRole.startOperation({
taskId: 1,
address: '0x0...',
});
Setting the task evaluator role (MANAGER
and proposed EVALUATOR
):
// Set task evaluator role
await colonyClient.setTaskEvaluatorRole.startOperation({
taskId: 1,
address: '0x0...',
});
Setting the task worker role (MANAGER
and proposed WORKER
):
// Set task worker role
await colonyClient.setTaskWorkerRole.startOperation({
taskId: 1,
address: '0x0...',
});
Removing the task evaluator role (MANAGER
and EVALUATOR
):
// Remove the task evaluator role
await colonyClient.removeTaskEvaluatorRole.startOperation({
taskId: 1,
address: '0x0...',
});
Removing the task worker role (MANAGER
and WORKER
):
// Remove the task worker role
await colonyClient.removeTaskWorkerRole.startOperation({
taskId: 1,
address: '0x0...',
});
Note: Attempting to use these methods without startOperation
will throw an error.
Cancel Task
Any time before a task has been completed, the task can be cancelled, which allows for the funding to be returned to the colony and halts any further modification of the task. Cancelling a task must be approved by both the MANAGER
and the WORKER
.
// Cancel a task
await colonyClient.cancelTask.startOperation({
taskId: 1,
});
Submit Work
After a WORKER
has been assigned a task and before the due date, the WORKER
can submit the task work, also known as the "task deliverable".
Like the specificationHash
, the deliverableHash
can be any arbitrary hash string (32 bytes) but the deliverableHash
was specially designed to support an IPFS content hash. See Using IPFS for more information about using an IPFS content hash.
// Submit the task work
await colonyClient.submitTaskDeliverable.send({
taskId: 1,
deliverableHash: 'Qm...',
});
Rate and Reveal
After the task deliverable has been submitted (or the due date has passed), the work rating period begins. Task payouts are determined by work ratings based on a 3-star system.
- The
EVALUATOR
reviews the deliverable and submits a rating for theWORKER
. - The
WORKER
reviews the specification and submits a rating for theMANAGER
.
Because work ratings are on-chain, they follow a "commit and reveal" pattern in which ratings are obscured in order to prevent ratings from influencing each other.
During the "commit period", ratings that have been submitted remain hidden. The commit period will last until both ratings have been submitted or until the commit period has expired (5 days after the task deliverable has been submitted).
// Generate secret
const { secret } = await colonyClient.generateSecret.call({
salt: 'secret',
value: 2,
});
// Submit work rating
await colonyClient.submitTaskWorkRating.send({
taskId: 1,
role: 'WORKER',
secret,
});
During the "reveal period", ratings must be revealed by each address that submitted a rating. The reveal period will last until both ratings have been revealed or until the reveal period has expired (5 days after both rating have been submitted or the commit period has expired).
// Reveal work rating
await colonyClient.revealTaskWorkRating.send({
taskId: 1,
role: 'WORKER',
rating: 2,
salt: 'secret',
});
During the "rating period" (which includes both the "commit period" and "reveal period"), if either party fails to commit or reveal their rating, their counterpart will be given the highest possible rating and they will be given the lowest possible rating.
You can check the status of submissions:
// Get work rating secrets information
await colonyClient.getTaskWorkRatingSecretsInfo.call({
taskId: 1,
});
Finalize Task
After the "rating period" has finished (the EVALUATOR
and WORKER
have submitted and revealed their ratings), the task can be finalized, which prevents any further modifications to the task.
// Finalize task
await colonyClient.finalizeTask.send({
taskId: 1,
});
Claim Payout
Once a task is finalized, each task role can then claim their payout:
// Claim task payout
await colonyClient.claimTaskPayout.send({
taskId: 1,
role: 'WORKER',
token: '0x0...',
});
Payment Lifecycle
All methods associated with payments can be called using a ColonyClient instance. The examples below are ordered in a way that reflects a general flow of the payment lifecycle from start to finish.
Add Payment
When adding a payment, all payment properties must be assigned upon creation:
// Add payment
await colonyClient.addPayment.send({
recipient: '0x0...',
token: '0x0...',
amount: new BN('1000000000000000000'),
domainId: 1,
skillId: 2,
});
View Payment
You can view information about a payment after it has been added:
// Get payment
await colonyClient.getPayment.send({
paymentId: 1,
});
Fund Payment
Just like tasks, each time a payment is added, a funding pot is also added and assigned to the payment. Each funding pot assigned to a payment is specific to that payment, therefore, each payment has a unique potId
.
In order to fund a payment, you need the potId
of the payment (the toPot
in the example below) and the potId
of the domain from which the funds will be moved (the fromPot
in the example below).
// Move funds between pots
await colonyClient.moveFundsBetweenPots.send({
fromPot: 1,
toPot: 2,
amount: new BN('1000000000000000000'),
token: '0x...',
});
The "funding pot" associated with our payment must have enough allocated funds to make the payment assigned to the recipient. If the assigned payment amount exceeds the amount of allocated funds in the funding pot, the payment cannot be finalized.
For more information about how funding works within a colony, check out Tokens and Funding.
Modify Payment
Unlike tasks, payments do not require signatures to be modified. A payment can be modified by addresses assigned the ROOT
colony role and the ADMINISTRATION
colony role.
// Set payment domain
await colonyClient.setPaymentDomain.send({
paymentId: 1,
domainId: 1,
});
// Set payment payout
await colonyClient.setPaymentPayout.send({
paymentId: 1,
token: '0x0...',
amount: new BN('1000000000000000000'),
});
// Set payment recipient
await colonyClient.setPaymentRecipient.send({
paymentId: 1,
recipient: '0x0...',
});
// Set payment skill
await colonyClient.setPaymentSkill.send({
paymentId: 1,
skillId: 2,
});
Finalize Payment
Once a payment has been added and funded, the payment can be finalized:
// Finalize payment
await colonyClient.finalizePayment.send({
paymentId: 1,
});
Claim Payment
Once a payment has been finalized, the payment can be claimed:
// Finalize payment
await colonyClient.claimPayment.send({
paymentId: 1,
token: '0x0...',
});
Support
Questions? Problems? Existential dilemmas? We’re here to help!
Improve this doc.
All improvements to documentation are welcome and encouraged. Submit a PR for documentation on GitHub.