Futarchy
✅ Ready to Dev
This contract specification has passed business logic audit, cross-program integration check, and authority model verification. A developer can implement from this document.
Key Concepts
8 Instructions
Initialize, create/approve/cancel/execute proposal, claim OT governance, authority transfer
2 State Accounts
FutarchyConfig, Proposal
2 PDA Seeds
futarchy_config, proposal
What Holders Can Govern
🪙 Mint OT
Create new OT tokens for the project.CPI →
ownership_token::mint_otUsed for: raising capital, rewarding contributors, expanding supply.💰 Spend Treasury
Transfer any token from OT Treasury to any destination.CPI →
ownership_token::spend_treasuryUsed for: funding operations, paying expenses, investing.📊 Update Revenue Split
Change how revenue is distributed between holders, treasury, and Nexus.CPI →
ownership_token::batch_update_destinationsUsed for: adjusting protocol economics.All OT projects are equal. ARL (Areal Finance) uses the same Futarchy contract as RCP (Rental Car Pool) or any other OT project. No special treatment. The only difference is what the OT represents — the governance mechanics are identical.
V1 vs V2
V1: Authority Approval
Current implementation.
- Authority (team wallet) creates and approves proposals
- Single signer = instant approval
- Simple and fast for early stage
- Authority transferred to Team Multisig after bootstrap
V2: Prediction Markets (future)
Planned upgrade.
- OT holders create proposals
- Futarchy prediction market resolves: “Will this proposal increase OT value?”
- Market-based decision making
- No single authority — decentralized governance
Instructions
Initialization
initialize_futarchy
initialize_futarchy
Create governance for an OT project. Called once per OT.
Caller: Deployer (one-time per OT)Accounts:
| Parameter | Type | Description |
|---|---|---|
ot_mint | Pubkey | The OT this governance serves |
deployer(signer, mut) — pays for creationot_mint— the OT token mintconfig(init) — PDA seed:["futarchy_config", ot_mint]system_program
FutarchyConfigPDA — governance config for this OT
authority = deployer(transferred to Team Multisig after bootstrap)ot_mint = ot_mintnext_proposal_id = 0is_active = true
Proposals
create_proposal
create_proposal
Create a new governance proposal. V1: authority creates. V2: any OT holder above threshold.
Caller: Authority (V1) / OT holder above threshold (V2)Accounts:
| Parameter | Type | Description |
|---|---|---|
proposal_type | ProposalType | MintOt, SpendTreasury, or UpdateDestinations |
amount | u64 | Amount for mint/spend (0 for UpdateDestinations) |
destination | Pubkey | Recipient for mint/spend, or ignored for UpdateDestinations |
token_mint | Pubkey | Token mint for SpendTreasury (which token to spend) |
params_hash | [u8; 32] | Hash of full params (for UpdateDestinations: hash of new destinations array) |
proposer(signer, mut) — pays for Proposal accountconfig— validates is_activeproposal(init) — PDA seed:["proposal", config, proposal_id]system_program
- Config must be active
- V1: proposer == config.authority
- V2 (future): proposer holds >= threshold OT tokens (threshold TBD — likely configurable in FutarchyConfig, e.g.,
min_proposal_ot_balance: u64, set by authority) - For
MintOt/SpendTreasury:amount > 0(no zero-amount proposals) - For
SpendTreasury/MintOt:destination ≠ Pubkey::default()(no zero-address recipients) - For
UpdateDestinations:params_hash ≠ [0; 32](sanity check against empty hash)
ProposalCreated.V1 rate limiting: Not needed — only authority can create proposals, providing implicit rate control. V2 rate limiting (future): When any holder can propose, add cooldown per proposer (e.g., 1 proposal per 24h per wallet) and proposal deposit (refundable on execution/cancel) to prevent spam. This should be added during V2 implementation.
approve_proposal
approve_proposal
Approve a proposal. V1: authority signs. V2: prediction market resolves.Caller: Authority (V1) / Automatic via market resolution (V2)Accounts:
authority(signer) — must matchconfig.authority(V1)configproposal(mut) — must be Active status
proposal.status == Active
proposal.status = Approved. Emits ProposalApproved.cancel_proposal
cancel_proposal
Cancel an active proposal. Only the authority can cancel. Cannot cancel already approved or executed proposals.Caller: Authority (V1)Accounts:
authority(signer) — must matchconfig.authorityconfigproposal(mut) — must be Active status
proposal.status == Active
proposal.status = Cancelled. Emits ProposalCancelled.execute_proposal
execute_proposal
Execute an approved proposal. Permissionless — anyone can trigger execution once approved. CPIs to OT contract based on proposal_type.Caller: PermissionlessAccounts:Logic by ProposalType:MintOt:
Hash construction: Validation:
executor(signer, mut) — pays for CPI feesconfigproposal(mut) — must be Approved status- OT contract accounts (varies by proposal_type — order must match OT instruction layout):
- MintOt:
ot_governance(OT governance PDA["ot_governance", ot_mint]),ot_config(mut,["ot_config", ot_mint]),ot_mint(mut),recipient_token_account(mut, ATA ofproposal.destinationfor OT mint),recipient(proposal.destination),payer(signer, pays ATA init if needed — can be executor) - SpendTreasury:
ot_governance(["ot_governance", ot_mint]),ot_treasury(["ot_treasury", ot_mint]),treasury_token_account(mut, Treasury ATA forproposal.token_mint),destination_token_account(mut, ATA atproposal.destination— must exist),token_mint(proposal.token_mint) - UpdateDestinations:
ot_governance(["ot_governance", ot_mint]),revenue_config(mut,["revenue_config", ot_mint]),ot_mint, plusremaining_accounts[0](serializedVec<BatchDestination>)
- MintOt:
ot_program— constraint:key == OT_PROGRAM_IDtoken_program,system_program
Executor guide: All OT PDA addresses are derivable from
config.ot_mint. Executor reads proposal fields (amount, destination, token_mint, proposal_type) and derives required accounts. For UpdateDestinations, executor must also fetch serialized destinations data matching proposal.params_hash (from off-chain source published by proposer).- CPI →
ownership_token::mint_ot(proposal.amount) - Futarchy config PDA signs as OT governance authority
- New OT tokens minted to
proposal.destination
- CPI →
ownership_token::spend_treasury(proposal.amount) - Futarchy config PDA signs as OT governance authority
- Tokens transferred from OT Treasury to
proposal.destination proposal.token_mintdetermines which token to spend
- Executor passes full destinations array via
remaining_accounts - Deserialization:
- Hash verification:
sha256(remaining_accounts[0].data) == proposal.params_hash— ensures executor passes the exact destinations that were proposed - CPI →
ownership_token::batch_update_destinations(destinations)with destinations as instruction parameter - Futarchy config PDA signs as OT governance authority via seeds
[b"futarchy_config", config.ot_mint.as_ref(), &[config.bump]]
BatchDestination struct (defined in OT contract, used here for serialization):| Field | Type | Description |
|---|---|---|
address | Pubkey | Target USDC token account |
allocation_bps | u16 | Allocation in basis points (1-10,000) |
label | [u8; 32] | Human-readable label |
params_hash = sha256(borsh_serialize(destinations_vec)) where destinations_vec: Vec<BatchDestination>. Proposer computes hash off-chain when creating proposal; executor must pass identical serialized data in remaining_accounts[0].Executor workflow: Proposer publishes
params_hash and full destinations data off-chain (e.g., IPFS or API). Executor downloads data, passes as remaining_accounts[0], contract verifies hash match before CPI. If hash mismatch → revert.proposal.status == Approved- Proposal not already executed
proposal.status = Executed, executed_ts = now. Emits ProposalExecuted.OT Governance Claim
claim_ot_governance
claim_ot_governance
Accept OT governance authority on behalf of Futarchy config PDA. Called during deployment after OT proposes authority transfer to this Futarchy instance. Permissionless — anyone can trigger once OT has proposed.Caller: PermissionlessAccounts:
executor(signer, mut)config— Futarchy config PDA (signs CPI as new OT authority)ot_governance(mut) — OT governance account (pending_authority must == config PDA)ot_mint— for PDA derivationot_program— constraint:key == OT_PROGRAM_ID
ot_governance.pending_authority == config.key()(OT must have proposed this Futarchy)
ownership_token::accept_authority_transfer. Futarchy config PDA signs as new_authority. OT governance authority now = Futarchy config PDA. Emits OtGovernanceClaimed.Authority Transfer
Two-step governance authority handoff. Standard pattern across all contracts.propose_authority_transfer
propose_authority_transfer
accept_authority_transfer
accept_authority_transfer
State Accounts
FutarchyConfig
Per-OT governance configuration. One per OT project.| Field | Type | Description |
|---|---|---|
ot_mint | Pubkey | The OT this governance serves |
authority | Pubkey | Current governance authority (team wallet V1, market PDA V2) |
pending_authority | Option<Pubkey> | Pending authority transfer target |
next_proposal_id | u64 | Auto-incrementing proposal counter (also = total proposals created) |
is_active | bool | Governance active flag |
bump | u8 | PDA bump seed |
["futarchy_config", ot_mint]
FutarchyConfig PDA is set as
ot_governance.authority in the OT contract. This is how Futarchy controls OT operations — by signing as the OT governance authority via CPI.Proposal
Individual governance proposal. Created per action.| Field | Type | Description |
|---|---|---|
proposal_id | u64 | Unique ID within this Futarchy instance |
ot_mint | Pubkey | The OT project |
proposer | Pubkey | Who created this proposal |
proposal_type | ProposalType | MintOt, SpendTreasury, or UpdateDestinations |
amount | u64 | Amount for mint/spend (context-dependent) |
destination | Pubkey | Recipient for mint/spend |
token_mint | Pubkey | Token mint for SpendTreasury |
params_hash | [u8; 32] | Hash of serialized params (for UpdateDestinations) |
status | ProposalStatus | Active, Approved, Executed, Cancelled |
created_ts | i64 | Creation timestamp |
executed_ts | i64 | Execution timestamp (0 if not executed) |
bump | u8 | PDA bump seed |
["proposal", futarchy_config, proposal_id]
ProposalType enum:
MintOt— mint new OT tokensSpendTreasury— spend from OT TreasuryUpdateDestinations— change revenue split
Active— created, awaiting approvalApproved— approved, ready for executionExecuted— executed successfullyCancelled— cancelled by authority viacancel_proposal(V1), or market resolves against (V2)
PDA Seeds
| Account | Seeds | Description |
|---|---|---|
| FutarchyConfig | "futarchy_config", ot_mint | Per-OT governance config |
| Proposal | "proposal", futarchy_config, proposal_id | Individual proposal |
Constants
| Constant | Value | Description |
|---|---|---|
OT_PROGRAM_ID | hardcoded | Ownership Token program ID (validated in execute_proposal) |
Events
| Event | Fields | When |
|---|---|---|
FutarchyInitialized | ot_mint, authority, timestamp | Governance created |
ProposalCreated | proposal_id, ot_mint, proposer, proposal_type, amount, destination, timestamp | Proposal created |
ProposalApproved | proposal_id, approver, timestamp | Proposal approved |
ProposalCancelled | proposal_id, authority, timestamp | Proposal cancelled |
ProposalExecuted | proposal_id, proposal_type, executor, timestamp | Proposal executed via CPI |
OtGovernanceClaimed | ot_mint, futarchy_config, timestamp | OT governance authority accepted |
AuthorityTransferProposed | current_authority, pending_authority, timestamp | Transfer proposed |
AuthorityTransferAccepted | old_authority, new_authority, timestamp | Transfer accepted |
Error Codes
| Error | Description |
|---|---|
Unauthorized | Signer is not the authority |
GovernancePaused | Config is_active = false |
ProposalNotActive | Proposal status ≠ Active (for approve) |
ProposalNotApproved | Proposal status ≠ Approved (for execute) |
AlreadyExecuted | Proposal already executed |
ProposalCancelled | Proposal was cancelled (for execute) |
InvalidProposalType | Unknown proposal type |
MathOverflow | Arithmetic overflow |
SelfTransfer | Cannot transfer authority to yourself |
NoPendingAuthority | No pending transfer |
InvalidPendingAuthority | Signer ≠ pending_authority |
Architecture & Integration Guide
Cross-Program Integration
→ Ownership Token (execute proposals via CPI)
→ Ownership Token (execute proposals via CPI)
- Futarchy config PDA =
ot_governance.authorityin OT contract execute_proposalCPIs to OT:mint_ot,spend_treasury,batch_update_destinations- Config PDA signs as OT governance authority
OT_PROGRAM_IDhardcoded and validated
How Futarchy Connects to OT
execute_proposal CPIs to OT, the Futarchy config PDA signs via seeds — OT contract validates signer == ot_governance.authority. ✓
Authority Model
🔧 Program Upgrade
Team Multisig (Squads)Can deploy new contract versions.
🏛️ Governance Authority
Team wallet (V1) / Prediction market (V2)
create_proposalapprove_proposalpropose_authority_transfer
execute_proposal is permissionless — once approved, anyone can trigger execution. This ensures proposals cannot be blocked after approval.What Team Controls Directly (NOT via Futarchy)
These protocol operations are managed by team wallets directly, NOT through Futarchy proposals:- DEX: config, pool creators, rebalancer — via
dex_config.authority(Team Multisig) - RWT Engine: manager, distribution config, admin mint, capital adjustments — via
vault.authority(Team Multisig) - Yield Distribution: config, publish authority, distributors — via
config.authority(Team Multisig) - Liquidity Nexus: managed directly by team (Nexus manager wallet)
- Pool Rebalancer: config — via
config.authority(Team Multisig) - Emergency pause: all contracts — via
pause_authority(Team Multisig)
Futarchy governs per-project OT decisions (mint, spend, split). Protocol-level infrastructure is managed by the team until decentralized governance (V2) is mature enough to handle it.
Deployment Checklist
- Deploy OT contract and
initialize_otfor the project - Call
initialize_futarchyfor the OT - Transfer OT governance to Futarchy config PDA:
- OT:
propose_authority_transfer(futarchy_config_pda)(deployer signs) - Futarchy:
claim_ot_governance(permissionless — Futarchy config PDA accepts via CPI)
- OT:
- Transfer Futarchy authority to Team Multisig via
propose_authority_transfer+accept_authority_transfer
Token Flow Summary
| From | To | Mechanism | Who triggers |
|---|---|---|---|
| — | OT mint → recipient | execute_proposal (MintOt CPI) | Anyone (permissionless) |
| OT Treasury | destination | execute_proposal (SpendTreasury CPI) | Anyone (permissionless) |
| — | OT RevenueConfig | execute_proposal (UpdateDestinations CPI) | Anyone (permissionless) |