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.
- 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
| 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 |
| 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 |
| 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) |
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
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]
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 },
}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 },
}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 },
}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 {},
}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 {},
}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 },
}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
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]
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
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
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
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
| 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 |
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
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
}
},
});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
Phase transitions can be triggered by:
- Time-based: CosmWasm does not have native cron, requires external trigger
- Threshold-based: Quorum checks in contract logic
- Manual: Admin intervention for edge cases
Recommended approach: External relayer that monitors phase deadlines and submits AdvancePhase transactions.
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());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
- Deploy Registry Contract
- Store Unit, Area, Issue, Voting, Delegation contract codes
- Update Registry with code IDs
- Create initial Unit(s) via Registry
- Create Areas within Units
- System operational
// 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",
},
};- Registry Contract development
- Unit Contract with member management
- Basic Frontend with wallet connection
- Contract deployment scripts
- Area Contract with policy management
- Issue Contract with phase management
- Initiative creation and draft management
- Supporter system
- Voting Contract with Schulze method
- Delegation Contract and chain resolution
- Voting UI with ranked-choice interface
- Results visualization
- Delegation management UI
- Event indexer
- IPFS integration for content
- Phase transition relayer
- Security audit
- Performance optimization
- Documentation
- Mainnet deployment
-
Delegation Privacy Level: Should delegation relationships be fully private, or should there be options for public delegations for accountability?
-
Phase Transition Mechanism: Should we use an external cron service, incentivized keepers, or rely on user-triggered transitions?
-
Content Moderation: How should draft content be moderated? On-chain governance or off-chain reporting?
-
Contingent System Complexity: Should we implement the full contingent/rate-limiting system in V1, or start simpler?
-
Multi-Unit Support: Should a single deployment support multiple independent units, or separate deployments per organization?
-
Vote Weight Calculation: Should we calculate weights at vote time or snapshot at phase start?