class Lock { constructor(tokens, duration) { this.dateCreated = new Date(); this.tokens = tokens; this.duration = duration; } } class Reputation { constructor() { this.tokens = 0; this.locks = new Set(); } addTokens(tokens) { if (this.tokens + tokens < 0) { throw new Error(`Token balance can not become negative`); } this.tokens += tokens; } lockTokens(tokens, duration) { if (tokens > this.getAvailableTokens()) { throw new Error("Can not lock more tokens than are available") } const lock = new Lock(tokens, duration); this.locks.add(lock); // TODO: Prune locks once expired } getTokens() { return this.tokens; } getAvailableTokens() { const now = new Date(); const tokensLocked = Array.from(this.locks.values()) .filter(({dateCreated, duration}) => now - dateCreated < duration) .reduce((acc, cur) => acc += cur.tokens, 0); if (tokensLocked > this.tokens) { throw new Error("Assertion failure. tokensLocked > tokens"); } return this.tokens - tokensLocked; } } export class Reputations extends Map { getTokens(reputationPublicKey) { const reputation = this.get(reputationPublicKey); if (!reputation) { return 0; } return reputation.getTokens(); } getAvailableTokens(reputationPublicKey) { const reputation = this.get(reputationPublicKey); if (!reputation) { return 0; } return reputation.getAvailableTokens(); } addTokens(reputationPublicKey, tokens) { const reputation = this.get(reputationPublicKey) ?? new Reputation(); reputation.addTokens(tokens); this.set(reputationPublicKey, reputation); } lockTokens(reputationPublicKey, tokens, duration) { if (!tokens || !duration) { return; } const reputation = this.get(reputationPublicKey); if (!reputation) { throw new Error(`${reputationPublicKey} has no tokens to lock`); } reputation.lockTokens(tokens, duration); } getTotal() { return Array.from(this.values()).reduce((acc, cur) => acc += cur.getTokens(), 0); } getTotalAvailable() { return Array.from(this.values()).reduce((acc, cur) => acc += cur.getAvailableTokens(), 0); } }