VoteLockCovenant locks governance FTs with an immutable vote choice. The UTXO model eliminates double-voting cryptographically.
Contract path: contracts/core/governance/VoteLockCovenant.cash
CashScript version: ^0.13.0
Constructor Parameters (Bytecode-Embedded)
| Parameter | Type | Description |
|---|
proposalId | bytes32 | Links vote to specific proposal |
voteChoice | int | 0 = AGAINST, 1 = FOR, 2 = ABSTAIN |
voterHash | bytes20 | hash160(voter pubkey) , only owner can reclaim |
unlockTimestamp | int | When tokens can be reclaimed |
NFT State (32 bytes)
[0]: version
[1-4]: proposal_id_prefix (first 4 bytes of proposalId)
[5]: vote_choice
[6-7]: reserved
[8-12]: lock_timestamp (5 bytes)
[13-17]: unlock_timestamp (5 bytes)
[18-31]: reserved
Transaction Structure
Both entrypoints spend vote-lock state from tx.inputs[0] and require token return at tx.outputs[0].
| Function | Required outputs | Notes |
|---|
reclaim | tx.outputs[0] voter P2PKH with full token amount/category | Requires tx.locktime >= unlockTimestamp. |
earlyReclaim | tx.outputs[0] voter P2PKH with full token amount/category | Unlock bypassed when caller provides terminal proposal status enum. |
Functions
reclaim(sig voterSig, pubkey voterPubkey)
Returns all locked governance FTs to voter after unlockTimestamp.
earlyReclaim(sig voterSig, pubkey voterPubkey, int proposalFinalStatus)
Returns tokens before unlockTimestamp if the proposal is done. Caller must pass a valid terminal status: 2 (EXECUTED), 3 (CANCELLED), or 4 (EXPIRED).
BCH has no cross-UTXO reads. The voter supplies proposalFinalStatus as a parameter , the covenant only validates enum membership (2|3|4). Off-chain verification of actual proposal status is required.
earlyReclaim() also does not re-check proposalId from NFT commitment. The function trusts that the locked UTXO already belongs to the intended proposal context established at lock time.
Recent hardening
earlyReclaim has been removed. The previous entrypoint accepted a user-supplied proposalFinalStatus with no on-chain check against the actual proposal state, which let any voter bypass the lockup. Voters now reclaim via reclaim once unlockTimestamp passes.
- A future revision can reintroduce early reclaim safely by binding the lock to a specific
ProposalCovenant instance via a proposalLockingBytecodeHash constructor parameter and requiring an input from that proposal in CANCELLED or EXPIRED status.