import { ERC1155 } from '../supporting/erc1155.js'; import { randomID } from '../../util/helpers.js'; import { EPSILON } from '../../util/constants.js'; class Lock { constructor(tokenAddress, tokenTypeId, amount, duration) { this.dateCreated = new Date(); this.tokenAddress = tokenAddress; this.amount = amount; this.duration = duration; this.tokenTypeId = tokenTypeId; } } export class ReputationTokenContract extends ERC1155 { constructor() { super('Reputation', 'REP'); this.histories = new Map(); // token address --> {tokenTypeId, increment, context (i.e. validation pool id)} this.values = new Map(); // token address --> token type id --> current value this.locks = new Set(); // {tokenAddress, tokenTypeId, amount, start, duration} } /** * * @param to Recipient address * @param values Object with reputation type id as key, and amount of reputation as value * @returns {string} */ mintBatch(to, tokenTypeIds, values) { const tokenAddress = `token_${randomID()}`; super.mintBatch(to, tokenAddress, tokenTypeIds, tokenTypeIds.map(() => 1)); const tokenMap = new Map(); for (let idx = 0; idx < tokenTypeIds.length; idx++) { const tokenTypeId = tokenTypeIds[idx]; const value = values[idx]; tokenMap.set(tokenTypeId, value); } this.values.set(tokenAddress, tokenMap); this.histories.set(tokenAddress, [{ operation: 'mintBatch', args: { to, tokenTypeIds, values } }]); return tokenAddress; } incrementValue(tokenAddress, tokenTypeId, increment, context) { const tokenTypeIds = this.values.get(tokenAddress); if (tokenTypeIds === undefined) { throw new Error(`Token not found: ${tokenAddress}`); } const value = tokenTypeIds?.get(tokenTypeId); const newValue = value + increment; const history = this.histories.get(tokenAddress) || []; if (newValue < -EPSILON) { throw new Error(`Token value can not become negative. Attempted to set value = ${newValue}`); } tokenTypeIds.set(tokenAddress, newValue); this.values.set(tokenAddress, tokenTypeIds); history.push({ tokenTypeId, increment, context }); this.histories.set(tokenAddress, history); } transferValueFrom(from, to, tokenTypeId, 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(from, tokenTypeId); if (sourceAvailable < amount - EPSILON) { throw new Error('Token value transfer: source has insufficient available value. ' + `Needs ${amount}; has ${sourceAvailable}.`); } this.incrementValue(from, tokenTypeId, -amount); this.incrementValue(to, tokenTypeId, amount); } batchTransferValueFrom(from, to, tokenTypeIds, amounts) { for (let idx = 0; idx < tokenTypeIds.length; idx++) { const tokenTypeId = tokenTypeIds[idx]; const amount = amounts[idx]; 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(from, tokenTypeId); if (sourceAvailable < amount - EPSILON) { throw new Error('Token value transfer: source has insufficient available value. ' + `Needs ${amount}; has ${sourceAvailable}.`); } this.incrementValue(from, tokenTypeId, -amount); this.incrementValue(to, tokenTypeId, amount); } } lock(tokenAddress, tokenTypeId, amount, duration) { const lock = new Lock(tokenAddress, tokenTypeId, amount, duration); this.locks.add(lock); } historyOf(tokenAddress) { return this.histories.get(tokenAddress); } valueOf(tokenAddress, tokenTypeId) { const tokenTypeIds = this.values.get(tokenAddress); if (tokenTypeIds === undefined) { throw new Error(`Token not found: ${tokenAddress}`); } return tokenTypeIds.get(tokenTypeId); } availableValueOf(tokenAddress, tokenTypeId) { const amountLocked = Array.from(this.locks.values()) .filter((lock) => lock.tokenAddress === tokenAddress && lock.tokenTypeId === tokenTypeId) .filter(({ dateCreated, duration }) => new Date() - dateCreated < duration) .reduce((total, { amount }) => total += amount, 0); return this.valueOf(tokenAddress, tokenTypeId) - amountLocked; } valueOwnedBy(ownerAddress, tokenTypeId) { return Array.from(this.owners.entries()) .filter(([__, owner]) => owner === ownerAddress) .map(([tokenAddress, __]) => this.valueOf(tokenAddress, tokenTypeId)) .reduce((total, value) => total += value, 0); } availableValueOwnedBy(ownerAddress, tokenTypeId) { return Array.from(this.owners.entries()) .filter(([__, owner]) => owner === ownerAddress) .map(([tokenAddress, __]) => this.availableValueOf(tokenAddress, tokenTypeId)) .reduce((total, value) => total += value, 0); } getTotal(tokenTypeId) { return Array.from(this.values.values()) .flatMap((tokens) => tokens.get(tokenTypeId)) .reduce((total, value) => total += value, 0); } // burn(tokenAddress, tokenTypeId, ) }