diff --git a/backend/src/api/import-from-matrix.js b/backend/src/api/import-from-matrix.js index 867b1b3..cbb018e 100644 --- a/backend/src/api/import-from-matrix.js +++ b/backend/src/api/import-from-matrix.js @@ -42,8 +42,8 @@ module.exports = async (req, res) => { // We want to add a post representing this matrix message. const authors = [{ authorAddress, weightPPM: 1000000 }]; - // TODO: Take citations as input to this API call, referencing other posts or matrix events - const citations = []; + // TODO: Take references as input to this API call, referencing other posts or matrix events + const references = []; const content = `Matrix event URI: ${eventUri}`; const embeddedData = { roomId, @@ -55,11 +55,11 @@ module.exports = async (req, res) => { const signature = await wallet.signMessage(contentToVerify); const { hash } = await write({ - sender, authors, citations, content, embeddedData, signature, + sender, authors, references, content, embeddedData, signature, }); // Now we want to add a post on-chain - const { alreadyAdded } = await addPostWithRetry(authors, hash, citations); + const { alreadyAdded } = await addPostWithRetry(authors, hash, references); if (alreadyAdded) { console.log(`Post already added for matrix event ${eventUri}`); diff --git a/backend/src/api/import-from-ss.js b/backend/src/api/import-from-ss.js index 98b254c..89e4b0a 100644 --- a/backend/src/api/import-from-ss.js +++ b/backend/src/api/import-from-ss.js @@ -8,8 +8,8 @@ const { authorAddresses, authorPrivKeys } = require('../util/db'); const { dao } = require('../util/contracts'); const write = require('../util/forum/write'); -// Each post allocates 30% of its reputation to citations -const PPM_TO_CITATIONS = 300000; +// Each post allocates 30% of its reputation to references +const PPM_TO_REFERENCES = 300000; const fetchWithRetry = async (url, retryDelay = 5000) => { let retry = false; @@ -116,14 +116,14 @@ HREF ${paper.url}`; }; }; -const addPostWithRetry = async (authors, hash, citations, retryDelay = 5000) => { +const addPostWithRetry = async (authors, hash, references, retryDelay = 5000) => { try { - await dao.addPost(authors, hash, citations); + await dao.addPost(authors, hash, references); } catch (e) { if (e.code === 'REPLACEMENT_UNDERPRICED') { console.log('retry delay (sec):', retryDelay / 1000); await Promise.delay(retryDelay); - return addPostWithRetry(authors, hash, citations, retryDelay * 2); + return addPostWithRetry(authors, hash, references, retryDelay * 2); } if (e.reason === 'A post with this postId already exists') { return { alreadyAdded: true }; } @@ -136,18 +136,18 @@ const importPaper = async (paper) => { console.log('references count:', paper.references.length); const { paperId } = paper; const references = paper.references.filter((x) => !!x.paperId); - const eachCitationWeightPPM = Math.floor(PPM_TO_CITATIONS / references.length); - const citations = (await Promise.mapSeries( + const eachReferenceWeightPPM = Math.floor(PPM_TO_REFERENCES / references.length); + const references = (await Promise.mapSeries( references, async (citedPaper) => { // We need to fetch this paper so we can generate the post we WOULD add to the forum. // That way, if we later add the cited paper to the blockchain it will have the correct hash. - // The forum allows dangling citations to support this use case. + // The forum allows dangling references to support this use case. try { const citedPost = await generatePost(citedPaper); const citedPostHash = objectHash(citedPost); return { - weightPPM: eachCitationWeightPPM, + weightPPM: eachReferenceWeightPPM, targetPostId: citedPostHash, }; } catch (e) { @@ -156,10 +156,10 @@ const importPaper = async (paper) => { }, )).filter((x) => !!x); - // Make sure citation weights sum to the designated total - if (citations.length) { - const totalCitationWeight = citations.reduce((t, { weightPPM }) => t + weightPPM, 0); - citations[0].weightPPM += PPM_TO_CITATIONS - totalCitationWeight; + // Make sure reference weights sum to the designated total + if (references.length) { + const totalReferenceWeight = references.reduce((t, { weightPPM }) => t + weightPPM, 0); + references[0].weightPPM += PPM_TO_REFERENCES - totalReferenceWeight; } // Create a post for this paper @@ -169,12 +169,12 @@ const importPaper = async (paper) => { // Write the new post to our database const { hash } = await write({ - authors, content, signature, embeddedData, citations, + authors, content, signature, embeddedData, references, }); // Add the post to the forum (on-chain) - console.log('addPostWithRetry', { authors, hash, citations }); - const { alreadyAdded } = await addPostWithRetry(authors, hash, citations); + console.log('addPostWithRetry', { authors, hash, references }); + const { alreadyAdded } = await addPostWithRetry(authors, hash, references); if (alreadyAdded) { console.log(`Post already added for paper ${paperId}`); } else { diff --git a/backend/src/event-handlers/rollup/batch/submit-rollup.js b/backend/src/event-handlers/rollup/batch/submit-rollup.js index cf60abd..cbcf9ea 100644 --- a/backend/src/event-handlers/rollup/batch/submit-rollup.js +++ b/backend/src/event-handlers/rollup/batch/submit-rollup.js @@ -33,8 +33,8 @@ const submitRollup = async () => { if (!authors.length) { return { batchItems: [] }; } - // TODO: Compute citations as aggregate of the citations of posts in the batch - const citations = []; + // TODO: Compute references as aggregate of the references of posts in the batch + const references = []; const content = `Batch of ${batchItems.length} items`; const embeddedData = { batchItems, @@ -45,11 +45,11 @@ const submitRollup = async () => { const signature = await wallet.signMessage(contentToVerify); // Write to the forum database const { hash: batchPostId } = await write({ - sender, authors, citations, content, embeddedData, signature, + sender, authors, references, content, embeddedData, signature, }); // Add rollup post on-chain - console.log('adding batch post on-chain', { authors, batchPostId, citations }); - await addPostWithRetry(authors, batchPostId, citations); + console.log('adding batch post on-chain', { authors, batchPostId, references }); + await addPostWithRetry(authors, batchPostId, references); // Stake our availability to be the next rollup worker console.log('staking availability to be the next rollup worker'); await stakeRollupAvailability(); diff --git a/backend/src/util/add-post-with-retry.js b/backend/src/util/add-post-with-retry.js index 667be2f..68be4ba 100644 --- a/backend/src/util/add-post-with-retry.js +++ b/backend/src/util/add-post-with-retry.js @@ -1,9 +1,9 @@ const callWithRetry = require('./call-with-retry'); const { dao } = require('./contracts'); -const addPostWithRetry = async (authors, hash, citations) => { +const addPostWithRetry = async (authors, hash, references) => { try { - await callWithRetry(() => dao.addPost(authors, hash, citations)); + await callWithRetry(() => dao.addPost(authors, hash, references)); } catch (e) { if (e.reason === 'A post with this postId already exists') { return { alreadyAdded: true }; diff --git a/backend/src/util/forum/read.js b/backend/src/util/forum/read.js index 988fd13..727bea1 100644 --- a/backend/src/util/forum/read.js +++ b/backend/src/util/forum/read.js @@ -10,7 +10,7 @@ const read = async (hash) => { data.embeddedData = data.embeddedData || undefined; const { - sender, authors, content, signature, embeddedData, citations, + sender, authors, content, signature, embeddedData, references, } = data; // Verify hash @@ -29,7 +29,7 @@ const read = async (hash) => { } return { - sender, authors, content, signature, embeddedData, citations, + sender, authors, content, signature, embeddedData, references, }; }; diff --git a/backend/src/util/forum/write.js b/backend/src/util/forum/write.js index 8f0b070..5d1fdea 100644 --- a/backend/src/util/forum/write.js +++ b/backend/src/util/forum/write.js @@ -4,7 +4,7 @@ const verifySignature = require('../verify-signature'); const { forum } = require('../db'); const write = async ({ - sender, authors, content, citations, embeddedData, signature, + sender, authors, content, references, embeddedData, signature, }) => { // Check author signature if (!verifySignature({ @@ -18,9 +18,9 @@ const write = async ({ // Compute content hash const data = { - sender, authors, content, signature, embeddedData, citations, + sender, authors, content, signature, embeddedData, references, }; - // We omit citations from the hash in order to support forum graph import. + // We omit references from the hash in order to support forum graph import. // When a post is imported, the hashes can be precomputed for cited posts, // without traversing the graph infinitely to compute hashes along entire reference chain. const hash = objectHash({ diff --git a/ethereum/contracts/Onboarding.sol b/ethereum/contracts/Onboarding.sol index 2913d82..cfd765c 100644 --- a/ethereum/contracts/Onboarding.sol +++ b/ethereum/contracts/Onboarding.sol @@ -29,7 +29,7 @@ contract Onboarding is Work, IOnValidate { // Make work evidence post Author[] memory authors = new Author[](1); authors[0] = Author(1000000, stake.worker); - dao.addPost(authors, request.evidencePostId, request.citations); + dao.addPost(authors, request.evidencePostId, request.references); emit WorkApprovalSubmitted(requestIndex, approval); // Initiate validation pool uint poolIndex = dao.initiateValidationPool{ @@ -73,10 +73,10 @@ contract Onboarding is Work, IOnValidate { return; } // Make onboarding post - Citation[] memory emptyCitations; + Reference[] memory emptyReferences; Author[] memory authors = new Author[](1); authors[0] = Author(1000000, request.customer); - dao.addPost(authors, request.requestPostId, emptyCitations); + dao.addPost(authors, request.requestPostId, emptyReferences); dao.initiateValidationPool{value: request.fee / 10}( request.requestPostId, POOL_DURATION, diff --git a/ethereum/contracts/RollableWork.sol b/ethereum/contracts/RollableWork.sol index e61bc97..6740260 100644 --- a/ethereum/contracts/RollableWork.sol +++ b/ethereum/contracts/RollableWork.sol @@ -34,7 +34,7 @@ abstract contract RollableWork is Work { // Make work evidence post Author[] memory authors = new Author[](1); authors[0] = Author(1000000, stake.worker); - dao.addPost(authors, request.evidencePostId, request.citations); + dao.addPost(authors, request.evidencePostId, request.references); // send worker stakes and customer fee to rollup contract dao.forwardAllowance( diff --git a/ethereum/contracts/Work.sol b/ethereum/contracts/Work.sol index a42f684..e967a4e 100644 --- a/ethereum/contracts/Work.sol +++ b/ethereum/contracts/Work.sol @@ -22,7 +22,7 @@ abstract contract Work is Availability, IOnProposalAccepted { uint stakeIndex; string requestPostId; string evidencePostId; - Citation[] citations; + Reference[] references; bool approval; } @@ -71,7 +71,7 @@ abstract contract Work is Availability, IOnProposalAccepted { function submitWorkEvidence( uint requestIndex, string calldata evidencePostId, - Citation[] calldata citations + Reference[] calldata references ) external { WorkRequest storage request = requests[requestIndex]; require( @@ -85,8 +85,8 @@ abstract contract Work is Availability, IOnProposalAccepted { ); request.status = WorkStatus.EvidenceSubmitted; request.evidencePostId = evidencePostId; - for (uint i = 0; i < citations.length; i++) { - request.citations.push(citations[i]); + for (uint i = 0; i < references.length; i++) { + request.references.push(references[i]); } emit WorkEvidenceSubmitted(requestIndex); } @@ -107,7 +107,7 @@ abstract contract Work is Availability, IOnProposalAccepted { // Make work evidence post Author[] memory authors = new Author[](1); authors[0] = Author(1000000, stake.worker); - dao.addPost(authors, request.evidencePostId, request.citations); + dao.addPost(authors, request.evidencePostId, request.references); emit WorkApprovalSubmitted(requestIndex, approval); // Initiate validation pool uint poolIndex = dao.initiateValidationPool{value: request.fee}( diff --git a/ethereum/contracts/core/DAO.sol b/ethereum/contracts/core/DAO.sol index 3500fc4..88d4903 100644 --- a/ethereum/contracts/core/DAO.sol +++ b/ethereum/contracts/core/DAO.sol @@ -251,9 +251,9 @@ contract DAO { function addPost( Author[] calldata authors, string calldata postId, - Citation[] calldata citations + Reference[] calldata references ) public { - forum.addPost(msg.sender, authors, postId, citations); + forum.addPost(msg.sender, authors, postId, references); } function posts( diff --git a/ethereum/contracts/core/Forum.sol b/ethereum/contracts/core/Forum.sol index 25b44c1..d3b3ada 100644 --- a/ethereum/contracts/core/Forum.sol +++ b/ethereum/contracts/core/Forum.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.24; import "./DAO.sol"; -struct Citation { +struct Reference { int weightPPM; string targetPostId; } @@ -17,7 +17,7 @@ struct Post { string id; address sender; Author[] authors; - Citation[] citations; + Reference[] references; uint reputation; // TODO: timestamp } @@ -47,7 +47,7 @@ contract Forum { address sender, Author[] calldata authors, string calldata postId, - Citation[] calldata citations + Reference[] calldata references ) external { require( msg.sender == address(dao), @@ -72,31 +72,31 @@ contract Forum { authorTotalWeightPPM == 1000000, "Author weights must sum to 1000000" ); - for (uint i = 0; i < citations.length; i++) { - post.citations.push(citations[i]); + for (uint i = 0; i < references.length; i++) { + post.references.push(references[i]); } - int totalCitationWeightPos; - int totalCitationWeightNeg; - for (uint i = 0; i < post.citations.length; i++) { - int weight = post.citations[i].weightPPM; + int totalReferenceWeightPos; + int totalReferenceWeightNeg; + for (uint i = 0; i < post.references.length; i++) { + int weight = post.references[i].weightPPM; require( weight >= -1000000, - "Each citation weight must be >= -1000000" + "Each reference weight must be >= -1000000" ); require( weight <= 1000000, - "Each citation weight must be <= 1000000" + "Each reference weight must be <= 1000000" ); - if (weight > 0) totalCitationWeightPos += weight; - else totalCitationWeightNeg += weight; + if (weight > 0) totalReferenceWeightPos += weight; + else totalReferenceWeightNeg += weight; } require( - totalCitationWeightPos <= 1000000, - "Sum of positive citations must be <= 1000000" + totalReferenceWeightPos <= 1000000, + "Sum of positive references must be <= 1000000" ); require( - totalCitationWeightNeg >= -1000000, - "Sum of negative citations must be >= -1000000" + totalReferenceWeightNeg >= -1000000, + "Sum of negative references must be >= -1000000" ); dao.emitPostAdded(postId); } @@ -108,15 +108,15 @@ contract Forum { return post.authors; } - function _handleCitation( + function _handleReference( string memory postId, - Citation memory citation, + Reference memory ref, int amount, bool initialNegative, uint depth ) internal returns (int outboundAmount) { - outboundAmount = (amount * citation.weightPPM) / 1000000; - if (bytes(citation.targetPostId).length == 0) { + outboundAmount = (amount * ref.weightPPM) / 1000000; + if (bytes(ref.targetPostId).length == 0) { // Incineration require( outboundAmount >= 0, @@ -125,7 +125,7 @@ contract Forum { dao.burn(address(dao), uint(outboundAmount)); return outboundAmount; } - int balanceToOutbound = _edgeBalances[postId][citation.targetPostId]; + int balanceToOutbound = _edgeBalances[postId][ref.targetPostId]; if (initialNegative) { if (outboundAmount < 0) { outboundAmount = outboundAmount > -balanceToOutbound @@ -138,13 +138,13 @@ contract Forum { } } int refund = propagateReputation( - citation.targetPostId, + ref.targetPostId, outboundAmount, - initialNegative || (depth == 0 && citation.weightPPM < 0), + initialNegative || (depth == 0 && ref.weightPPM < 0), depth + 1 ); outboundAmount -= refund; - _edgeBalances[postId][citation.targetPostId] += outboundAmount; + _edgeBalances[postId][ref.targetPostId] += outboundAmount; } function _distributeAmongAuthors( @@ -200,28 +200,28 @@ contract Forum { } Post storage post = posts[postId]; if (post.authors.length == 0) { - // We most likely got here via a citation to a post that hasn't been added yet. - // We support this scenario so that a citation graph can be imported one post at a time. + // We most likely got here via a reference to a post that hasn't been added yet. + // We support this scenario so that a reference graph can be imported one post at a time. return amount; } - // Propagate negative citations first - for (uint i = 0; i < post.citations.length; i++) { - if (post.citations[i].weightPPM < 0) { - amount -= _handleCitation( + // Propagate negative references first + for (uint i = 0; i < post.references.length; i++) { + if (post.references[i].weightPPM < 0) { + amount -= _handleReference( postId, - post.citations[i], + post.references[i], amount, initialNegative, depth ); } } - // Now propagate positive citations - for (uint i = 0; i < post.citations.length; i++) { - if (post.citations[i].weightPPM > 0) { - amount -= _handleCitation( + // Now propagate positive references + for (uint i = 0; i < post.references.length; i++) { + if (post.references[i].weightPPM > 0) { + amount -= _handleReference( postId, - post.citations[i], + post.references[i], amount, initialNegative, depth diff --git a/ethereum/test/Forum.js b/ethereum/test/Forum.js index 5b3f4e0..516422d 100644 --- a/ethereum/test/Forum.js +++ b/ethereum/test/Forum.js @@ -39,10 +39,10 @@ describe('Forum', () => { { value: fee ?? POOL_FEE }, ); - const addPost = (author, postId, citations) => dao.addPost([{ + const addPost = (author, postId, references) => dao.addPost([{ weightPPM: 1000000, authorAddress: author, - }], postId, citations); + }], postId, references); describe('Post', () => { beforeEach(async () => { @@ -113,7 +113,7 @@ describe('Forum', () => { ], postId, [])).to.be.rejectedWith('Author weights must sum to 1000000'); }); - it('should be able to donate reputation via citations', async () => { + it('should be able to donate reputation via references', async () => { await addPost(account1, 'content-id', []); await addPost(account2, 'second-content-id', [{ weightPPM: 500000, targetPostId: 'content-id' }]); await initiateValidationPool({ postId: 'second-content-id' }); @@ -124,7 +124,7 @@ describe('Forum', () => { expect(await dao.balanceOf(account2)).to.equal(50); }); - it('should be able to leach reputation via citations', async () => { + it('should be able to leach reputation via references', async () => { await addPost(account1, 'content-id', []); expect((await dao.posts('content-id')).reputation).to.equal(0); await initiateValidationPool({ postId: 'content-id' }); @@ -144,7 +144,7 @@ describe('Forum', () => { expect((await dao.posts('second-content-id')).reputation).to.equal(150); }); - it('should be able to redistribute power via citations', async () => { + it('should be able to redistribute power via references', async () => { await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); @@ -165,7 +165,7 @@ describe('Forum', () => { expect(await dao.balanceOf(account3)).to.equal(0); }); - it('should be able to reverse a negative citation with a negative citation', async () => { + it('should be able to reverse a negative reference with a negative reference', async () => { await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); await dao.evaluateOutcome(0); @@ -294,7 +294,7 @@ describe('Forum', () => { expect(await dao.totalSupply()).to.equal(50); }); - describe('negative citation of a post, the author having already staked and lost reputation', async () => { + describe('negative reference of a post, the author having already staked and lost reputation', async () => { beforeEach(async () => { await addPost(account1, 'content-id', []); await initiateValidationPool({ postId: 'content-id' }); @@ -372,7 +372,7 @@ describe('Forum', () => { }); }); - describe('negative citation of a post with multiple authors', async () => { + describe('negative reference of a post with multiple authors', async () => { beforeEach(async () => { await dao.addPost([ { weightPPM: 500000, authorAddress: account1 }, diff --git a/frontend/src/components/posts/ViewPostModal.jsx b/frontend/src/components/posts/ViewPostModal.jsx index 715ad58..7ffc898 100644 --- a/frontend/src/components/posts/ViewPostModal.jsx +++ b/frontend/src/components/posts/ViewPostModal.jsx @@ -8,7 +8,7 @@ function ViewPostModal({ }) { const handleClose = () => setShow(false); const { - content, authors, embeddedData, citations, + content, authors, embeddedData, references, } = post; const embeddedDataJson = JSON.stringify(embeddedData, null, 2); @@ -46,12 +46,12 @@ function ViewPostModal({ {embeddedDataJson} )} - {citations && citations.length > 0 && ( + {references && references.length > 0 && ( <>
-
Citations
+
References
- {citations.map(({ weightPPM, targetPostId }) => ( + {references.map(({ weightPPM, targetPostId }) => (
{targetPostId} {' '} diff --git a/frontend/src/utils/Post.js b/frontend/src/utils/Post.js index 035fcaa..9d6f279 100644 --- a/frontend/src/utils/Post.js +++ b/frontend/src/utils/Post.js @@ -8,7 +8,7 @@ window.Buffer = Buffer; class Post { constructor({ - sender, authors, content, signature, hash, embeddedData, citations, + sender, authors, content, signature, hash, embeddedData, references, }) { this.sender = sender; this.authors = authors; @@ -16,14 +16,14 @@ class Post { this.signature = signature; this.hash = hash; this.embeddedData = embeddedData ?? {}; - this.citations = citations ?? []; + this.references = references ?? []; } // Read from API static async read(hash) { const { data: { - sender, authors, content, signature, embeddedData, citations, + sender, authors, content, signature, embeddedData, references, }, } = await axios.get(`/api/read/${hash}`); // Verify hash @@ -46,7 +46,7 @@ class Post { } } return new Post({ - sender, authors, content, signature, hash, embeddedData, citations, + sender, authors, content, signature, hash, embeddedData, references, }); } @@ -73,7 +73,7 @@ class Post { content: this.content, signature: this.signature, embeddedData: this.embeddedData, - citations: this.citations, + references: this.references, }; const { data: hash } = await axios.post('/api/write', data); this.hash = hash; @@ -81,7 +81,7 @@ class Post { // Upload hash to blockchain async publish(DAO, account) { - await DAO.methods.addPost(this.authors, this.hash, this.citations ?? []).send({ + await DAO.methods.addPost(this.authors, this.hash, this.references ?? []).send({ from: account, gas: 1000000, }); diff --git a/specification/docs/requirements.md b/specification/docs/requirements.md index 1d3ddef..ad3c864 100644 --- a/specification/docs/requirements.md +++ b/specification/docs/requirements.md @@ -10,7 +10,7 @@ Validation Pools mint and award Reputation (REP). REP can be used for staking fo #### Reputation -1. The Reputation contract must be able to associate a non-negative numeric value with a given wallet address. +1. The Reputation contract must be able to associate a non-negative numeric value with any given wallet address. 1. The value associated with a given wallet address may be modified only by the results of a Validation Pool (explained below). The Validation Pool must be able to execute the following operations diff --git a/specification/docs/system-design.md b/specification/docs/system-design.md index 745a7f2..7e126ef 100644 --- a/specification/docs/system-design.md +++ b/specification/docs/system-design.md @@ -156,18 +156,30 @@ To achieve the Rollup requirements, the contract must do the following: 1. If this method is called to replace a batch worker who has failed to submit the next batch, a Validation Pool should be initiated and the batch worker's stakes submitted in favor of the VP. The DAO members may then stake against this VP, punishing the worker who failed to submit the batch. -## Backend +## Off-chain Operations -### API +As outlined in the Rollup section above, we need to define processes for handling off-chain Posts and Validation Pools. On-chain, Posts are represented by a unique identifier, but the Post content is always stored off-chain. So, every on-chain Post must have a corresponding off-chain Post. These off-chain posts should be visible to the public. To achieve this, we introduce a Forum API, that supports writing and reading off-chain Posts. -- Read -- Write +### Forum API -### Automated Staking +#### Write + +Parameters + +| Name | Type | Description | +| --- | --- | --- | +| sender | Wallet address | | +| authors | Array of tuples: Wallet address, author weight | +| content | String | +| cit + +#### Read + +## Automated Staking -#### Validation Pool +### Validation Pool -#### Rollup +### Rollup ## User Interface diff --git a/specification/docs/terminology.md b/specification/docs/terminology.md index 91e7c98..df448ee 100644 --- a/specification/docs/terminology.md +++ b/specification/docs/terminology.md @@ -14,7 +14,7 @@ | Customer | A user with a Blockchain Wallet. Pays to engage a work contract. | | Staking Client | An application that listens for validation pools, performs validation and stakes for/against the pool. | | Reputation (REP) | A non-transferable ERC20 token representing the results of validation pools within a given expertise. | -| Forum Post | A signed contribution with weighted citations | +| Forum Post | A signed contribution with weighted references | | Validation Pool (VP) | Takes CSPR/ETH and author stakes as input. Requires REP-weighted consensus. Mints and distributes REP; distributes CSPR/ETH. | | Work Evidence (WEV) | A forum post representing work that fulfills some work request(s) | | Work Smart Contract (WSC) | A contract that takes REP stakes from workers and CSPR/ETH from customers, and puts them toward a validation pool (VP) targeting work evidence (WEV). |