NovaDAO Technical Whitepaper
v0.1 — March 2026 — Futarchy Governance on Stellar/Soroban
NovaDAO is a permissionless futarchy governance protocol built on Stellar's Soroban smart contract platform. It enables any project to create a DAO where treasury spending decisions are made by prediction markets instead of token votes. This document describes the technical mechanisms that power the system.
1. Conditional Vault
The Conditional Vault is the foundation of the futarchy mechanism. It takes a base token (e.g. NOVA) and splits it into two conditional tokens — pToken (pass) and fToken (fail) — each representing a claim on the underlying asset conditional on a specific outcome.
1.1 Split
A user deposits N base tokens into the vault and receives N pTokens and N fTokens. The base tokens are held in escrow by the vault contract.
split(depositor, amount):
require depositor.auth()
require vault.status == Active
transfer base_token from depositor to vault (amount)
pass_balance[depositor] += amount
fail_balance[depositor] += amount
total_deposited += amount1.2 Merge
The inverse of split. A user returns equal amounts of pTokens and fTokens to receive the underlying base tokens back. This enables arbitrage: if pToken + fToken < base token price, a trader can buy both conditional tokens, merge them, and pocket the difference.
merge(user, amount):
require user.auth()
require vault.status == Active
require pass_balance[user] >= amount
require fail_balance[user] >= amount
pass_balance[user] -= amount
fail_balance[user] -= amount
transfer base_token from vault to user (amount)
total_deposited -= amount1.3 Resolution & Redemption
After the proposal's voting period ends, the DAO contract (the vault's authority) calls either finalize() (pass wins) or revert() (fail wins). After resolution, holders of the winning conditional token can redeem 1:1 for the underlying base token. The losing token becomes worthless.
redeem(user):
require vault.status != Active // must be resolved
winning_token = pass_balance if Finalized, fail_balance if Reverted
amount = winning_token[user]
zero out both balances for user
transfer base_token from vault to user (amount)
total_deposited -= amounttransfer_pass() and transfer_fail() functions so the AMM can move conditional tokens between accounts.2. Futarchy AMM & Decision Token Pricing
Each proposal gets its own constant-product AMM (x × y = k, similar to Uniswap v1) that trades pTokens against fTokens. The ratio of reserves determines the market's implied probability of the proposal passing.
2.1 Price Discovery
Given reserves Rpass and Rfail, the implied prices are:
pass_price = R_fail / (R_pass + R_fail)
fail_price = R_pass / (R_pass + R_fail)
// Example: R_pass = 987, R_fail = 1015
// pass_price = 1015 / 2002 = 50.7%
// fail_price = 987 / 2002 = 49.3%When traders buy pTokens (expressing belief the proposal will pass), Rpass decreases and Rfail increases, pushing the pass price up. The opposite happens when traders buy fTokens.
2.2 Swap Mechanics
Swaps follow the constant product formula with a configurable fee (default: 0.3%):
swap(user, buy_pass, amount_in, min_out):
fee = amount_in * fee_bps / 10000
amount_in_net = amount_in - fee
// Constant product: (R_in + amount_in_net) * (R_out - amount_out) = R_in * R_out
amount_out = R_out * amount_in_net / (R_in + amount_in_net)
require amount_out >= min_out // slippage protection
update reserves
update TWAP oracle2.3 Liquidity Provision
Anyone can provide liquidity by depositing equal value of pTokens and fTokens. LP shares are calculated as:
- First provider: shares = √(pass_amount × fail_amount)
- Subsequent providers: shares = min(pass_amount / R_pass, fail_amount / R_fail) × total_shares
LP providers earn the swap fees proportional to their share of the pool. They can withdraw at any time by burning their LP shares.
3. TWAP Oracle & Manipulation Resistance
The AMM maintains a Time-Weighted Average Price (TWAP) oracle on-chain. This is critical: the proposal outcome is determined by the TWAP, not the spot price, making short-term price manipulation extremely expensive.
3.1 Cumulative Price Accumulator
On every trade, the contract updates a cumulative price sum:
update_twap(reserves):
time_delta = now - last_timestamp
if time_delta > 0 and reserves are non-zero:
cumulative_pass_price += (R_pass / R_fail) * time_delta
cumulative_fail_price += (R_fail / R_pass) * time_delta
last_timestamp = now
last_reserves = reservesThe TWAP over any period is simply:
TWAP = (cumulative_price_end - cumulative_price_start) / (time_end - time_start)3.2 Why TWAP Resists Manipulation
To move the TWAP by X%, an attacker must sustain that price distortion for the entire observation window. The constant-product AMM means moving the price further requires exponentially more capital. Meanwhile, every second the price is distorted creates an arbitrage opportunity for other traders to correct it and profit. The cost of manipulation grows quadratically with the desired distortion.
4. The Pass/Fail Decision System
When the voting period ends, anyone can call finalize_proposal(). The DAO contract reads the TWAP from the AMM and compares it against a configurable threshold:
finalize_proposal(proposal_id):
require now >= proposal.voting_ends_at
twap = amm.get_twap()
// pass_threshold_bps: 5000 = 50% (pass price must be > 50%)
threshold = SCALE * pass_threshold_bps / 10000
if twap >= threshold:
vault.finalize() // pass tokens win
proposal.state = Passed
else:
vault.revert() // fail tokens win
proposal.state = FailedThe threshold is configurable per DAO. A threshold of 5000 bps (50%) means the market must believe the proposal is more likely to increase token value than decrease it. DAOs can set higher thresholds for certain proposal types (e.g., team compensation proposals might require 55% to account for conflict of interest).
5. Treasury Escrow & ICO Fund Protection
NovaDAO's architecture provides built-in treasury protection for projects that raise capital through token sales. Here is how the escrow mechanism works:
5.1 Funds Flow Into On-Chain Treasury
When a project launches through NovaDAO, sale proceeds flow directly into the DAO's on-chain treasury (held as the base_token in the DAO contract). The team cannot access these funds directly — every withdrawal must go through a governance proposal.
5.2 Spend Allowance via Proposals
To spend treasury funds, the team submits a proposal specifying:
- The amount to withdraw
- The recipient address
- A description of what the funds will be used for
The proposal opens a prediction market. If the market believes the spending will increase the project's value, the proposal passes and funds are released. If not, funds stay in the treasury.
5.3 Why This Protects Investors
| Risk | Traditional ICO | NovaDAO |
|---|---|---|
| Rug pull | Team drains treasury and disappears | Impossible — funds only move via market-approved proposals |
| Wasteful spending | No oversight, team spends freely | Every allocation judged by the market on value-add basis |
| Slow rug | Team pays themselves excessive salaries | Higher pass threshold for team proposals (conflict of interest protection) |
| No accountability | Investors have no recourse | All spending is on-chain, transparent, and market-validated |
6. Staking Mechanism for Proposals
To prevent spam and ensure proposals have community support, NovaDAO requires a minimum liquidity deposit to create a proposal.
6.1 Proposal Creation & Initial Liquidity
When creating a proposal, the proposer must deposit base tokens as initial liquidity:
create_proposal(proposer, description_url, base_amount):
require base_amount >= dao.min_base_liquidity
// 1. Deploy a new Conditional Vault for this proposal
vault = deploy(vault_wasm)
vault.initialize(base_token, dao_address)
// 2. Deploy a new AMM for this proposal
amm = deploy(amm_wasm)
amm.initialize(vault, dao_address, fee_bps)
// 3. Transfer base tokens from proposer to DAO
base_token.transfer(proposer, dao, base_amount)
// 4. Split into conditional tokens
vault.split(dao, base_amount) // dao gets N pass + N fail tokens
// 5. Seed the AMM with equal liquidity
amm.add_liquidity(dao, base_amount, base_amount) // 50/50 ratio
// 6. Record proposal
proposal = { proposer, vault, amm, voting_ends_at: now + duration }
return proposal.id6.2 What Happens to Staked Liquidity
- The proposer's tokens are split into conditional tokens and deposited as AMM liquidity.
- The LP position is held by the DAO contract, not the proposer.
- After finalization, the liquidity can be withdrawn and the winning conditional tokens redeemed for the underlying base tokens.
- The proposer effectively "stakes" their tokens on the proposal being evaluated fairly by the market.
7. Contract Architecture
The protocol consists of four Soroban smart contracts that compose together:
| Contract | Role | Key Functions |
|---|---|---|
| Factory | Permissionless registry. Deploys new DAO instances. Lists all DAOs on-chain (no database needed). | create_dao, list_daos |
| DAO | Manages a single DAO's configuration and proposals. Deploys vault + AMM per proposal. Reads TWAP to finalize. | register_dao, create_proposal, finalize_proposal |
| Conditional Vault | Splits/merges/redeems conditional tokens. One vault per proposal. Tracks pass/fail balances internally. | split, merge, redeem, finalize, revert |
| Futarchy AMM | Constant-product market maker with TWAP oracle. One AMM per proposal. Trades pass vs fail tokens. | swap, add_liquidity, remove_liquidity, get_twap |
7.1 Deployment Flow
Factory
└── create_dao(config)
└── deploys DAO contract
└── create_proposal(...)
├── deploys Conditional Vault
└── deploys Futarchy AMM
└── linked to vault for token transfers7.2 Why Stellar / Soroban
- Sub-cent fees — critical for active market trading in governance
- 5-second finality — real-time price discovery
- Native USDC — no bridge risk for stablecoin pairs
- WASM-based contracts — Rust safety guarantees, resource-bounded execution
- Built-in Stellar DEX — potential for secondary market trading
8. Full Proposal Lifecycle
DAO Creation
A project calls factory.create_dao(config) specifying the governance token, proposal duration, pass threshold, minimum liquidity, and fee structure. A new DAO contract is deployed on-chain.
Proposal Submission
A proposer calls dao.create_proposal(description, base_amount). The DAO deploys a new vault and AMM, splits the proposer's tokens, and seeds the AMM with initial liquidity. A prediction market is now live.
Market Trading
Traders split base tokens into pass/fail pairs and trade on the AMM to express their views. The AMM continuously updates the TWAP oracle. Arbitrageurs keep prices honest via split/merge operations.
Finalization
After the voting period ends, anyone calls dao.finalize_proposal(id). The contract reads the TWAP, compares to the threshold, and calls vault.finalize() or vault.revert(). The outcome is permanent and on-chain.
Redemption
Holders of the winning conditional token call vault.redeem() to receive base tokens 1:1. Losing tokens become worthless. LP providers withdraw remaining liquidity.
9. Security Considerations
- Authority model: Only the DAO contract (vault authority) can call
finalize()orrevert()on the vault. No admin keys exist. - TWAP vs spot: Using TWAP for decisions prevents flash-loan style attacks where an attacker manipulates the price in a single transaction.
- Constant product invariant: The AMM enforces x × y = k on every swap, preventing the pool from being drained through malformed trades.
- Soroban resource bounds: All contract calls have bounded CPU and memory usage, preventing DoS through computational exhaustion.
- No external dependencies: The entire protocol runs on-chain with no off-chain oracles, keepers, or centralized components.
NovaDAO — Permissionless Futarchy on Stellar. Built with Soroban smart contracts. All code is open source.