124 lines
3.7 KiB
JavaScript
124 lines
3.7 KiB
JavaScript
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;
|
|
}
|
|
}
|