import { ERC721 } from '../supporting/erc721.js'; import { randomID } from '../../util/helpers.js'; import { EPSILON } from '../../util/constants.js'; class Lock { constructor(tokenId, amount, duration) { this.dateCreated = new Date(); this.tokenId = tokenId; this.amount = amount; this.duration = duration; } } export class ReputationTokenContract extends ERC721 { constructor() { super('Reputation', 'REP'); this.histories = new Map(); // token id --> {increment, context (i.e. validation pool id)} this.values = new Map(); // token id --> current value this.locks = new Set(); // {tokenId, amount, start, duration} } /** * * @param to * @param value * @param context * @returns {string} */ mint(to, value, context = {}) { const tokenId = `token_${randomID()}`; super.mint(to, tokenId); this.values.set(tokenId, value); this.histories.set(tokenId, [{ increment: value, context }]); return tokenId; } incrementValue(tokenId, increment, context) { const value = this.values.get(tokenId); if (value === undefined) { throw new Error(`Token not found: ${tokenId}`); } const newValue = value + increment; const history = this.histories.get(tokenId) || []; if (newValue < -EPSILON) { throw new Error(`Token value can not become negative. Attempted to set value = ${newValue}`); } this.values.set(tokenId, newValue); history.push({ increment, context }); this.histories.set(tokenId, history); } transferValueFrom(fromTokenId, toTokenId, amount) { if (amount === undefined) { throw new Error('Transfer value: amount is undefined!'); } if (amount === 0) { return; } if (amount < 0) { throw new Error('Transfer value: amount must be positive'); } const sourceAvailable = this.availableValueOf(fromTokenId); if (sourceAvailable < amount - EPSILON) { throw new Error('Token value transfer: source has insufficient available value. ' + `Needs ${amount}; has ${sourceAvailable}.`); } this.incrementValue(fromTokenId, -amount); this.incrementValue(toTokenId, amount); } lock(tokenId, amount, duration) { const lock = new Lock(tokenId, amount, duration); this.locks.add(lock); } historyOf(tokenId) { return this.histories.get(tokenId); } valueOf(tokenId) { const value = this.values.get(tokenId); if (value === undefined) { throw new Error(`Token not found: ${tokenId}`); } return value; } availableValueOf(tokenId) { const amountLocked = Array.from(this.locks.values()) .filter(({ tokenId: lockTokenId }) => lockTokenId === tokenId) .filter(({ dateCreated, duration }) => new Date() - dateCreated < duration) .reduce((total, { amount }) => total += amount, 0); return this.valueOf(tokenId) - amountLocked; } valueOwnedBy(ownerId) { return Array.from(this.owners.entries()) .filter(([__, owner]) => owner === ownerId) .map(([tokenId, __]) => this.valueOf(tokenId)) .reduce((total, value) => total += value, 0); } availableValueOwnedBy(ownerId) { return Array.from(this.owners.entries()) .filter(([__, owner]) => owner === ownerId) .map(([tokenId, __]) => this.availableValueOf(tokenId)) .reduce((total, value) => total += value, 0); } getTotal() { return Array.from(this.values.values()).reduce((total, value) => total += value, 0); } getTotalAvailable() { const amountLocked = Array.from(this.locks.values()) .filter(({ dateCreated, duration }) => new Date() - dateCreated < duration) .reduce((total, { amount }) => total += amount, 0); return this.getTotal() - amountLocked; } }