Skip to content

Latest commit

 

History

History
843 lines (697 loc) · 22.1 KB

File metadata and controls

843 lines (697 loc) · 22.1 KB

Liquid Democracy on Secret Network - Architecture Document

1. Executive Summary

This document outlines the architecture for implementing a Liquid Democracy platform inspired by LiquidFeedback, with a Next.js frontend and Secret Network backend. The solution leverages Secret Network's unique privacy-preserving smart contracts to protect vote confidentiality while maintaining transparency in governance outcomes.

Key Benefits of Secret Network for Liquid Democracy

  • Vote Privacy: Encrypted contract state ensures individual votes remain confidential
  • Delegation Privacy: Trust relationships can be kept private until voting weight is calculated
  • Secure Computation: Vote tallying and Schulze algorithm execution happen in the trusted execution environment
  • Verifiable Results: Final outcomes are transparent while individual choices remain private

2. Technology Stack

Backend - Secret Network

Component Technology
Smart Contracts Rust + CosmWasm
Contract Framework Secret CosmWasm
Chain Secret Network (mainnet: secret-4, testnet: pulsar-3)
Storage Encrypted contract state
Cross-contract calls Submessages

Frontend - Next.js

Component Technology
Framework Next.js 14+ (App Router)
Language TypeScript
Blockchain SDK SecretJS
State Management TanStack Query
Styling Tailwind CSS
Wallet Integration Keplr, MetaMask (via SecretJS)
WYSIWYG Editor TipTap or Slate.js

Infrastructure

Component Technology
API Layer Next.js API Routes + LCD/gRPC endpoints
Indexer Custom indexer or SubQuery
File Storage IPFS for attachments
Caching Redis (optional, for indexer)

3. System Architecture Overview

flowchart TB
    subgraph Frontend [Next.js Frontend]
        UI[React UI Components]
        SDK[SecretJS Client]
        WC[Wallet Connector]
        STATE[State Management]
    end
    
    subgraph Wallets [Web3 Wallets]
        KEPLR[Keplr Wallet]
        META[MetaMask]
    end
    
    subgraph SecretNetwork [Secret Network Blockchain]
        subgraph Contracts [Smart Contracts]
            REGISTRY[Registry Contract]
            UNIT[Unit Contract]
            AREA[Area Contract]
            ISSUE[Issue Contract]
            VOTE[Voting Contract]
            DELEG[Delegation Contract]
        end
        LCD[LCD/gRPC Endpoints]
    end
    
    subgraph Indexer [Indexer Service]
        IDX[Event Indexer]
        DB[(PostgreSQL)]
    end
    
    subgraph Storage [Decentralized Storage]
        IPFS[IPFS Node]
    end
    
    UI --> STATE
    STATE --> SDK
    SDK --> LCD
    WC --> KEPLR
    WC --> META
    LCD --> Contracts
    Contracts --> IDX
    IDX --> DB
    UI --> IPFS
Loading

4. Smart Contract Architecture

4.1 Contract Hierarchy

The system uses a factory pattern with multiple specialized contracts:

flowchart TD
    REGISTRY[Registry Contract<br/>Global Configuration]
    
    REGISTRY --> UNIT1[Unit Contract 1]
    REGISTRY --> UNIT2[Unit Contract 2]
    
    UNIT1 --> AREA1[Area Contract A]
    UNIT1 --> AREA2[Area Contract B]
    
    AREA1 --> ISSUE1[Issue Contract]
    AREA1 --> ISSUE2[Issue Contract]
    
    ISSUE1 --> VOTE1[Voting Instance]
    ISSUE1 --> DELEG1[Delegation Snapshot]
Loading

4.2 Contract Specifications

4.2.1 Registry Contract

Purpose: Global configuration, contract deployment, and admin management

// State
pub struct RegistryState {
    pub admin: Addr,
    pub unit_code_id: u64,
    pub area_code_id: u64,
    pub issue_code_id: u64,
    pub voting_code_id: u64,
    pub delegation_code_id: u64,
    pub units: Vec<Addr>,
    pub config: GlobalConfig,
}

// Messages
pub enum ExecuteMsg {
    CreateUnit { name: String, config: UnitConfig },
    UpdateCodeIds { ... },
    SetAdmin { new_admin: String },
    UpdateGlobalConfig { config: GlobalConfig },
}

pub enum QueryMsg {
    GetConfig {},
    GetUnits { start_after: Option<String>, limit: Option<u32> },
    GetUnit { address: String },
}

4.2.2 Unit Contract

Purpose: Organizational unit management, member privileges, sub-units

pub struct UnitState {
    pub registry: Addr,
    pub name: String,
    pub parent_unit: Option<Addr>,
    pub members: Map<Addr, MemberPrivileges>,
    pub areas: Vec<Addr>,
    pub sub_units: Vec<Addr>,
    pub config: UnitConfig,
}

pub struct MemberPrivileges {
    pub voting_right: bool,
    pub initiative_right: bool,
    pub polling_right: bool,
    pub weight: u64,
}

pub enum ExecuteMsg {
    CreateArea { name: String, policies: Vec<Policy> },
    AddMember { address: String, privileges: MemberPrivileges },
    UpdateMember { address: String, privileges: MemberPrivileges },
    RemoveMember { address: String },
    CreateSubUnit { name: String, config: UnitConfig },
    SetManager { role: ManagerRole, address: String },
}

pub enum QueryMsg {
    GetInfo {},
    GetMember { address: String },
    GetMembers { start_after: Option<String>, limit: Option<u32> },
    GetAreas {},
    CheckPrivilege { address: String, privilege: PrivilegeType },
}

4.2.3 Area Contract

Purpose: Policy-governed spaces for issue creation and delegation

pub struct AreaState {
    pub unit: Addr,
    pub name: String,
    pub policies: Vec<Policy>,
    pub issues: Vec<Addr>,
    pub delegations: Map<Addr, Addr>,  // delegator -> trustee
    pub default_policy: u64,
}

pub struct Policy {
    pub id: u64,
    pub name: String,
    pub min_admission_time: u64,
    pub max_admission_time: u64,
    pub discussion_time: u64,
    pub verification_time: u64,
    pub voting_time: u64,
    pub issue_quorum_num: u64,
    pub issue_quorum_den: u64,
    pub initiative_quorum_num: u64,
    pub initiative_quorum_den: u64,
    pub direct_majority_num: u64,
    pub direct_majority_den: u64,
    pub indirect_majority_positive: u64,
    pub indirect_majority_negative: u64,
    pub no_multistage_majority: bool,
}

pub enum ExecuteMsg {
    CreateIssue { policy_id: u64 },
    SetDelegation { trustee: String },
    RevokeDelegation {},
    UpdatePolicy { policy_id: u64, policy: Policy },
    AddPolicy { policy: Policy },
}

pub enum QueryMsg {
    GetInfo {},
    GetPolicies {},
    GetIssues { state_filter: Option<IssueState>, start_after: Option<String>, limit: Option<u32> },
    GetDelegation { delegator: String },
    GetDelegationChain { address: String },
}

4.2.4 Issue Contract

Purpose: Issue lifecycle, initiatives, drafts, and supporter management

pub struct IssueState {
    pub area: Addr,
    pub policy: Policy,
    pub state: IssuePhase,
    pub created_at: u64,
    pub phase_deadlines: PhaseDeadlines,
    pub initiatives: Vec<Initiative>,
    pub voting_contract: Option<Addr>,
}

pub enum IssuePhase {
    Admission,
    Discussion,
    Verification,
    Voting,
    Closed { winner: Option<u64> },
    Cancelled { reason: String },
}

pub struct Initiative {
    pub id: u64,
    pub initiator: Addr,
    pub name: String,
    pub drafts: Vec<Draft>,
    pub supporters: Vec<Addr>,  // Stored encrypted
    pub supporter_count: u64,
    pub revoked: bool,
}

pub struct Draft {
    pub id: u64,
    pub author: Addr,
    pub content_hash: String,  // IPFS hash
    pub created_at: u64,
}

pub struct Suggestion {
    pub id: u64,
    pub author: Addr,
    pub draft_id: u64,
    pub content_hash: String,
    pub opinions: Map<Addr, i8>,  // -2 to +2 scale
}

pub enum ExecuteMsg {
    CreateInitiative { name: String, draft_content_hash: String },
    AddDraft { initiative_id: u64, content_hash: String },
    AddSupport { initiative_id: u64 },
    RemoveSupport { initiative_id: u64 },
    CreateSuggestion { initiative_id: u64, draft_id: u64, content_hash: String },
    OpineOnSuggestion { suggestion_id: u64, opinion: i8 },
    AdvancePhase {},  // Called by timer or admin
    CancelIssue { reason: String },
}

pub enum QueryMsg {
    GetInfo {},
    GetInitiative { id: u64 },
    GetInitiatives {},
    GetDrafts { initiative_id: u64 },
    GetSupporterCount { initiative_id: u64 },
    AmISupporter { initiative_id: u64 },  // Permissioned query
    GetSuggestions { initiative_id: u64, draft_id: u64 },
    GetPhaseDeadlines {},
}

4.2.5 Voting Contract

Purpose: Schulze method ranked-choice voting with weighted delegations

pub struct VotingState {
    pub issue: Addr,
    pub initiatives: Vec<u64>,
    pub votes: Map<Addr, EncryptedBallot>,  // Encrypted storage
    pub delegation_snapshot: DelegationSnapshot,
    pub is_closed: bool,
    pub results: Option<VotingResults>,
}

pub struct EncryptedBallot {
    pub rankings: Vec<i8>,  // Grade for each initiative: -3 to +3
    pub comment_hash: Option<String>,
    pub timestamp: u64,
}

pub struct DelegationSnapshot {
    pub weights: Map<Addr, u64>,
    pub chains: Map<Addr, Vec<Addr>>,
}

pub struct VotingResults {
    pub battle_table: Vec<Vec<i64>>,  // Pairwise comparison matrix
    pub schulze_ranking: Vec<u64>,
    pub winner: Option<u64>,
    pub participation: u64,
    pub total_weight: u64,
}

pub enum ExecuteMsg {
    CastVote { rankings: Vec<i8>, comment_hash: Option<String> },
    UpdateVote { rankings: Vec<i8>, comment_hash: Option<String> },
    CloseVoting {},  // Calculate results
}

pub enum QueryMsg {
    GetStatus {},
    GetMyVote {},  // Permissioned query
    GetResults {},  // Only available after close
    GetParticipationStats {},
}

4.2.6 Delegation Contract

Purpose: Global delegation management and chain resolution

pub struct DelegationState {
    pub unit: Addr,
    // Stored as: scope -> delegator -> trustee
    pub unit_delegations: Map<Addr, Addr>,
    pub area_delegations: Map<(Addr, Addr), Addr>,  // (area, delegator) -> trustee
    pub issue_delegations: Map<(Addr, Addr), Addr>,  // (issue, delegator) -> trustee
    pub last_validation: Map<Addr, u64>,
}

pub enum ExecuteMsg {
    SetUnitDelegation { trustee: String },
    SetAreaDelegation { area: String, trustee: String },
    SetIssueDelegation { issue: String, trustee: String },
    RevokeDelegation { scope: DelegationScope },
    ValidateDelegation {},  // Periodic re-confirmation
}

pub enum QueryMsg {
    GetDelegation { delegator: String, scope: DelegationScope },
    ResolveDelegationChain { voter: String, issue: String },
    GetTrustees { delegator: String },
    GetDelegators { trustee: String },
    GetWeight { voter: String, issue: String },
}

5. Frontend Architecture

5.1 Application Structure

src/
├── app/                          # Next.js App Router
│   ├── (auth)/                   # Auth-required routes
│   │   ├── dashboard/
│   │   ├── units/[id]/
│   │   ├── areas/[id]/
│   │   ├── issues/[id]/
│   │   └── voting/[id]/
│   ├── (public)/                 # Public routes
│   │   ├── explore/
│   │   └── about/
│   ├── api/                      # API routes
│   │   ├── indexer/
│   │   └── ipfs/
│   ├── layout.tsx
│   └── page.tsx
├── components/
│   ├── ui/                       # Base UI components
│   ├── wallet/                   # Wallet connection
│   ├── units/                    # Unit management
│   ├── areas/                    # Area management
│   ├── issues/                   # Issue & initiative
│   ├── voting/                   # Voting interface
│   ├── delegation/               # Delegation management
│   └── editor/                   # WYSIWYG draft editor
├── hooks/
│   ├── useSecretClient.ts
│   ├── useWallet.ts
│   ├── useContracts.ts
│   └── useQueries.ts
├── lib/
│   ├── secret/
│   │   ├── client.ts
│   │   ├── contracts.ts
│   │   └── types.ts
│   ├── ipfs/
│   └── utils/
├── stores/
│   ├── wallet.ts
│   ├── units.ts
│   └── voting.ts
└── types/
    ├── contracts.ts
    └── models.ts

5.2 Key Frontend Components

flowchart TD
    subgraph Pages [Page Components]
        DASH[Dashboard]
        UNIT[Unit View]
        AREA[Area View]
        ISSUE[Issue View]
        VOTE[Voting View]
    end
    
    subgraph Features [Feature Components]
        WALLET[WalletProvider]
        INIT[InitiativeCard]
        DRAFT[DraftEditor]
        BALLOT[BallotForm]
        DELG[DelegationManager]
        TIMELINE[IssueTimeline]
    end
    
    subgraph Hooks [Custom Hooks]
        USC[useSecretClient]
        UW[useWallet]
        UC[useContracts]
        UQ[useQueries]
    end
    
    subgraph State [State Management]
        WS[WalletStore]
        DS[DataStore]
        CS[CacheStore]
    end
    
    Pages --> Features
    Features --> Hooks
    Hooks --> State
    Hooks --> SDK[SecretJS]
Loading

5.3 Wallet Integration Flow

sequenceDiagram
    participant User
    participant UI as Next.js UI
    participant WC as WalletConnector
    participant Keplr
    participant Secret as Secret Network
    
    User->>UI: Click Connect Wallet
    UI->>WC: initializeWallet
    WC->>Keplr: window.keplr.enable
    Keplr-->>User: Approve Connection
    User->>Keplr: Confirm
    Keplr-->>WC: Connected
    WC->>Keplr: getOfflineSigner
    Keplr-->>WC: OfflineSigner
    WC->>Secret: Create SecretNetworkClient
    Secret-->>WC: Client Ready
    WC-->>UI: Wallet Connected
    UI-->>User: Show Dashboard
Loading

6. Data Flow Diagrams

6.1 Initiative Creation Flow

sequenceDiagram
    participant User
    participant UI as Frontend
    participant IPFS
    participant Issue as Issue Contract
    participant Unit as Unit Contract
    
    User->>UI: Create Initiative
    UI->>Unit: Query: CheckPrivilege
    Unit-->>UI: Has initiative_right
    UI->>IPFS: Upload Draft Content
    IPFS-->>UI: Content Hash
    UI->>Issue: Execute: CreateInitiative
    Issue->>Unit: Query: GetMember
    Unit-->>Issue: Member Privileges
    Issue->>Issue: Validate & Create
    Issue-->>UI: Initiative Created
    UI-->>User: Show Initiative
Loading

6.2 Voting Flow with Delegations

sequenceDiagram
    participant Voter
    participant UI as Frontend
    participant Vote as Voting Contract
    participant Deleg as Delegation Contract
    participant Issue as Issue Contract
    
    Voter->>UI: Open Voting
    UI->>Vote: Query: GetStatus
    Vote-->>UI: Voting Open
    UI->>Deleg: Query: ResolveDelegationChain
    Deleg-->>UI: Weight & Delegators
    UI-->>Voter: Show Ballot + Weight
    
    Voter->>UI: Submit Rankings
    UI->>Vote: Execute: CastVote
    Vote->>Vote: Validate & Store Encrypted
    Vote-->>UI: Vote Recorded
    UI-->>Voter: Confirmation
    
    Note over Vote: After Deadline
    Vote->>Vote: Execute: CloseVoting
    Vote->>Vote: Calculate Schulze Rankings
    Vote->>Issue: Update Winner
Loading

6.3 Delegation Resolution Algorithm

flowchart TD
    START[Resolve Delegation]
    CHECK_ISSUE[Check Issue-level Delegation]
    CHECK_AREA[Check Area-level Delegation]
    CHECK_UNIT[Check Unit-level Delegation]
    DIRECT[Direct Vote]
    FOLLOW[Follow Trustee]
    CYCLE[Cycle Detection]
    
    START --> CHECK_ISSUE
    CHECK_ISSUE -->|Has Delegation| FOLLOW
    CHECK_ISSUE -->|No Delegation| CHECK_AREA
    CHECK_AREA -->|Has Delegation| FOLLOW
    CHECK_AREA -->|No Delegation| CHECK_UNIT
    CHECK_UNIT -->|Has Delegation| FOLLOW
    CHECK_UNIT -->|No Delegation| DIRECT
    FOLLOW --> CYCLE
    CYCLE -->|No Cycle| CHECK_ISSUE
    CYCLE -->|Cycle Found| DIRECT
Loading

7. Security Considerations

7.1 Privacy Model

Data Visibility Storage
Vote content Private Encrypted state
Delegation relationships Private by default Encrypted state
Supporter list Private until verification Encrypted state
Vote results Public after close Published to state
Draft content Public IPFS
Member privileges Public within unit Contract state

7.2 Access Control

flowchart TD
    subgraph Permissions [Permission Checks]
        VOTE_P[Voting Right]
        INIT_P[Initiative Right]
        POLL_P[Polling Right]
        ADMIN_P[Admin Access]
    end
    
    subgraph Actions [Protected Actions]
        CAST[Cast Vote]
        CREATE[Create Initiative]
        POLL[Create Poll]
        MANAGE[Manage Unit]
    end
    
    VOTE_P --> CAST
    INIT_P --> CREATE
    POLL_P --> POLL
    ADMIN_P --> MANAGE
Loading

7.3 Viewing Keys for Private Data

Secret Network requires viewing keys for accessing encrypted data:

// Generate viewing key
const viewingKey = await secretjs.tx.compute.executeContract({
  contract_address: contractAddress,
  code_hash: codeHash,
  msg: { create_viewing_key: { entropy: "random_string" } },
  sender: walletAddress,
});

// Query with viewing key
const myVote = await secretjs.query.compute.queryContract({
  contract_address: votingContract,
  code_hash: codeHash,
  query: { 
    get_my_vote: { 
      address: walletAddress,
      key: viewingKey 
    } 
  },
});

8. Phase Timeline & State Machine

8.1 Issue Lifecycle State Machine

stateDiagram-v2
    [*] --> Admission: Issue Created
    
    Admission --> Discussion: Quorum Met + Time Elapsed
    Admission --> Cancelled: No Quorum + Deadline
    
    Discussion --> Verification: Discussion Time Elapsed
    
    Verification --> Voting: Verification Time Elapsed
    Verification --> Cancelled: Supporters Lost
    
    Voting --> Closed: Voting Time Elapsed
    
    Closed --> [*]
    Cancelled --> [*]
    
    note right of Admission: Gathering Supporters
    note right of Discussion: Draft Refinement
    note right of Verification: Final Confirmation
    note right of Voting: Ranked Choice
Loading

8.2 Automatic Phase Transitions

Phase transitions can be triggered by:

  1. Time-based: CosmWasm does not have native cron, requires external trigger
  2. Threshold-based: Quorum checks in contract logic
  3. Manual: Admin intervention for edge cases

Recommended approach: External relayer that monitors phase deadlines and submits AdvancePhase transactions.


9. Indexer Architecture

9.1 Event Indexing

Secret Network contracts emit events that can be indexed:

// Example events in contract
let response = Response::new()
    .add_attribute("action", "create_initiative")
    .add_attribute("issue_id", issue_id.to_string())
    .add_attribute("initiative_id", initiative_id.to_string())
    .add_attribute("initiator", info.sender.to_string());

9.2 Indexer Data Model

erDiagram
    UNIT {
        string address PK
        string name
        string parent_unit FK
        timestamp created_at
    }
    
    AREA {
        string address PK
        string unit_address FK
        string name
        timestamp created_at
    }
    
    ISSUE {
        string address PK
        string area_address FK
        string state
        timestamp created_at
        timestamp phase_deadline
    }
    
    INITIATIVE {
        string issue_address FK
        int id PK
        string initiator
        string name
        int supporter_count
    }
    
    VOTE_EVENT {
        string voting_address FK
        string voter
        timestamp voted_at
    }
    
    UNIT ||--o{ UNIT : has_sub_units
    UNIT ||--o{ AREA : contains
    AREA ||--o{ ISSUE : contains
    ISSUE ||--o{ INITIATIVE : contains
    ISSUE ||--o| VOTE_EVENT : has_votes
Loading

10. Deployment Architecture

10.1 Contract Deployment Order

  1. Deploy Registry Contract
  2. Store Unit, Area, Issue, Voting, Delegation contract codes
  3. Update Registry with code IDs
  4. Create initial Unit(s) via Registry
  5. Create Areas within Units
  6. System operational

10.2 Environment Configuration

// config/networks.ts
export const networks = {
  mainnet: {
    chainId: "secret-4",
    rpcUrl: "https://lcd.mainnet.secretsaturn.net",
    registryAddress: "secret1...",
    explorerUrl: "https://www.mintscan.io/secret",
  },
  testnet: {
    chainId: "pulsar-3",
    rpcUrl: "https://pulsar.lcd.secretnodes.com",
    registryAddress: "secret1...",
    explorerUrl: "https://testnet.ping.pub/secret",
  },
};

11. Implementation Roadmap

Phase 1: Core Infrastructure

  • Registry Contract development
  • Unit Contract with member management
  • Basic Frontend with wallet connection
  • Contract deployment scripts

Phase 2: Governance Core

  • Area Contract with policy management
  • Issue Contract with phase management
  • Initiative creation and draft management
  • Supporter system

Phase 3: Voting System

  • Voting Contract with Schulze method
  • Delegation Contract and chain resolution
  • Voting UI with ranked-choice interface
  • Results visualization

Phase 4: Advanced Features

  • Delegation management UI
  • Event indexer
  • IPFS integration for content
  • Phase transition relayer

Phase 5: Polish & Launch

  • Security audit
  • Performance optimization
  • Documentation
  • Mainnet deployment

12. Open Questions for Discussion

  1. Delegation Privacy Level: Should delegation relationships be fully private, or should there be options for public delegations for accountability?

  2. Phase Transition Mechanism: Should we use an external cron service, incentivized keepers, or rely on user-triggered transitions?

  3. Content Moderation: How should draft content be moderated? On-chain governance or off-chain reporting?

  4. Contingent System Complexity: Should we implement the full contingent/rate-limiting system in V1, or start simpler?

  5. Multi-Unit Support: Should a single deployment support multiple independent units, or separate deployments per organization?

  6. Vote Weight Calculation: Should we calculate weights at vote time or snapshot at phase start?


13. References