diff --git a/crates/aldabra-dao/src/builder/proposal_create.rs b/crates/aldabra-dao/src/builder/proposal_create.rs index dd962dd..d569e8c 100644 --- a/crates/aldabra-dao/src/builder/proposal_create.rs +++ b/crates/aldabra-dao/src/builder/proposal_create.rs @@ -352,12 +352,21 @@ pub fn build_unsigned_proposal_create( starting_time: args.starting_time_ms, }; - // New stake datum: copy old, append Created lock for the new proposal. - let mut new_stake = args.stake_in.datum.clone(); - new_stake.locked_by.push(ProposalLock { + // New stake datum: copy old, PREPEND a Created lock for the new + // proposal. Order matters — the stake validator's ppermitVote uses + // `pcons NEW_LOCK old_locks` (head-cons, NOT append). If we append + // and the input had pre-existing locks, the chain rejects with + // CekError on the stake validator. Caught 2026-05-08 trying to + // create proposal #1 while the stake still held a Created lock + // from proposal #0; cosign + vote builders already prepend. + let mut new_locks = Vec::with_capacity(args.stake_in.datum.locked_by.len() + 1); + new_locks.push(ProposalLock { proposal_id: new_proposal_id, action: ProposalAction::Created, }); + new_locks.extend(args.stake_in.datum.locked_by.iter().cloned()); + let mut new_stake = args.stake_in.datum.clone(); + new_stake.locked_by = new_locks; let new_governor_datum_pd = new_governor.to_plutus_data()?; let new_proposal_datum_pd = new_proposal.to_plutus_data()?;