tutus-chain/docs/ADR-008-UI-Implementation-G...

7.7 KiB

ADR-008 UI Implementation Guide

This document provides guidance for front-end developers implementing UI for the ADR-008 security features.

1. Commit-Reveal Investment System

Overview

Investments now use a two-phase commit-reveal pattern to prevent front-running attacks. Users must first commit a hash of their investment, wait for a delay period, then reveal the actual amount.

Flow

1. User decides to invest amount X in opportunity Y
2. Frontend generates random nonce (32 bytes)
3. Frontend computes commitment = SHA256(amount || nonce || investorAddress)
4. User calls commitInvestment(opportunityID, commitment, vitaID)
5. Wait 10+ blocks (configurable: CommitRevealDelay)
6. User calls revealInvestment(commitmentID, amount, nonce)
7. If valid, investment is executed

Contract Methods

commitInvestment(opportunityID: uint64, commitment: bytes32, vitaID: uint64) -> uint64

  • Returns: commitmentID
  • Event: CommitmentCreated(commitmentID, opportunityID, vitaID, revealDeadline)

revealInvestment(commitmentID: uint64, amount: uint64, nonce: bytes) -> bool

  • Must be called after CommitRevealDelay blocks (default: 10)
  • Must be called before CommitRevealWindow expires (default: 1000 blocks)
  • Event: CommitmentRevealed(commitmentID, amount, investmentID)

cancelCommitment(commitmentID: uint64) -> bool

  • Can cancel pending commitments
  • Event: CommitmentCanceled(commitmentID)

getCommitment(commitmentID: uint64) -> CommitmentInfo | null

  • Returns commitment details including status

Frontend Implementation Notes

// Generate commitment
function createCommitment(amount: bigint, investorAddress: string): { commitment: string, nonce: string } {
  const nonce = crypto.randomBytes(32);
  const preimage = Buffer.concat([
    Buffer.alloc(8), // amount as uint64 big-endian
    nonce,
    Buffer.from(investorAddress, 'hex')
  ]);
  // Write amount as big-endian uint64
  preimage.writeBigUInt64BE(amount, 0);

  const commitment = sha256(preimage);
  return {
    commitment: commitment.toString('hex'),
    nonce: nonce.toString('hex')
  };
}

// IMPORTANT: Store nonce securely until reveal!
// If user loses nonce, they cannot reveal and funds are locked until expiry

UI Considerations

  • Nonce Storage: Store nonce in localStorage/sessionStorage with commitmentID as key
  • Progress Indicator: Show blocks remaining until reveal is allowed
  • Deadline Warning: Alert user if reveal deadline is approaching
  • Retry Logic: If reveal fails, nonce is still valid for retry

2. Whale Concentration Limits

Overview

No single investor can hold more than 5% (configurable) of any investment opportunity's total pool.

How It Works

  • When invest() is called, the contract checks:
    (existingInvestment + newAmount) / (totalPool + newAmount) <= WealthConcentration
    
  • Default limit: 500 basis points (5%)
  • Enforced at investment time, not at commitment time

UI Considerations

  • Pre-check: Before committing, query user's existing investment in opportunity
  • Warning: Show warning if user is approaching concentration limit
  • Error Handling: Handle "investment would exceed whale concentration limit" error gracefully

Querying Current Investment

// Get all investments by an investor
const investments = await contract.getInvestmentsByInvestor(vitaID);
const investmentInOpp = investments.find(inv => inv.opportunityID === targetOppID);
const currentAmount = investmentInOpp?.amount || 0n;

// Get opportunity details
const opp = await contract.getOpportunity(oppID);
const totalPool = opp.totalPool;

// Calculate max additional investment
const maxConcentration = config.wealthConcentration; // e.g., 500 = 5%
const maxAllowed = (totalPool * BigInt(maxConcentration)) / 10000n;
const maxAdditional = maxAllowed - currentAmount;

3. Sybil Resistance Vesting

Overview

New Vita tokens have a 30-day vesting period before gaining full rights. This prevents mass creation of fake identities for manipulation.

Default Vesting Period

  • 2,592,000 blocks (~30 days at 1 second per block)
  • Committee can adjust vesting for individual tokens

Contract Methods

isFullyVested(tokenID: uint64) -> bool

  • Returns true if current block >= vestedUntil

getVestingInfo(tokenID: uint64) -> [vestedUntil, isFullyVested, remainingBlocks] | null

  • vestedUntil: Block height when vesting completes
  • isFullyVested: Current vesting status
  • remainingBlocks: Blocks until fully vested (0 if already vested)

setVesting(tokenID: uint64, vestedUntil: uint32) -> bool (Committee only)

  • Can accelerate vesting for verified identities
  • Can extend vesting for suspicious accounts

UI Considerations

Registration Flow

// After Vita registration, show vesting status
const vestingInfo = await vita.getVestingInfo(tokenID);
if (!vestingInfo[1]) { // not fully vested
  const remainingBlocks = vestingInfo[2];
  const estimatedTime = remainingBlocks * 1; // 1 second per block
  showVestingBanner(`Your identity will be fully verified in ~${formatDuration(estimatedTime)}`);
}

Feature Gating

Some features may require fully vested Vita:

  • Voting: May require full vesting to prevent vote manipulation
  • Large Investments: Higher limits may require vesting
  • Attestation: May need vesting to attest others
// Check before allowing sensitive actions
async function canPerformAction(vitaID: uint64, action: string): Promise<boolean> {
  const isVested = await vita.isFullyVested(vitaID);

  switch(action) {
    case 'vote':
    case 'large_investment':
    case 'attest_others':
      return isVested;
    default:
      return true; // Basic actions allowed during vesting
  }
}

Vesting Progress Display

// Calculate and display vesting progress
const vestingInfo = await vita.getVestingInfo(tokenID);
const vestedUntil = vestingInfo[0];
const token = await vita.getTokenByID(tokenID);
const createdAt = token.createdAt;

const totalVestingBlocks = vestedUntil - createdAt;
const elapsedBlocks = currentBlock - createdAt;
const progressPercent = Math.min(100, (elapsedBlocks / totalVestingBlocks) * 100);

Configuration Values

These can be queried from the contracts:

Collocatio Config

const config = await collocatio.getConfig();
// config.commitRevealDelay = 10 (blocks)
// config.commitRevealWindow = 1000 (blocks)
// config.wealthConcentration = 500 (basis points = 5%)

Vita Vesting

// Default vesting period: 2,592,000 blocks (~30 days)
// Query individual token vesting via getVestingInfo()

Events to Subscribe

Collocatio Events

  • CommitmentCreated(commitmentID, opportunityID, vitaID, revealDeadline)
  • CommitmentRevealed(commitmentID, amount, investmentID)
  • CommitmentCanceled(commitmentID)

Vita Events

  • VestingUpdated(tokenID, vestedUntil, updatedBy)

Error Messages

Error Meaning UI Action
commitment already exists Duplicate commitment Show existing commitment
commitment not found Invalid commitmentID Check ID is correct
reveal too early Before delay period Show countdown
reveal window expired Past deadline Commitment forfeited
commitment verification failed Wrong amount/nonce Check stored values
investment would exceed whale concentration limit Too much in one opp Show max allowed
vita is not fully vested Vesting incomplete Show remaining time