feat(dao-config): Phase 4 prerequisite fields + ScriptRefs

DaoConfig gains optional fields for Phase 4 (proposal_create) work:
- proposal_addr        — proposal validator address (bech32)
- stake_st_policy      — StakeST minting policy id (56 hex)
- proposal_st_policy   — ProposalST minting policy id (56 hex)
- script_refs          — cached reference UTxO refs for each Agora script
                         (governor / stake / proposal / treasury validators
                         + stake_st / proposal_st minting policies)

All fields optional with serde defaults so existing configs keep loading.
Will be populated by upcoming `dao_discover_scripts` MCP tool that audits
on-chain state under a known governor_addr.

Test fixture also corrected: stakes_addr now uses Sulkta's real per-DAO
parameterized stake-validator address (`addr1w8msu7p...`) instead of the
shared MLabs deployer (`addr1w9gexmeunzsy...`) — matches audit findings.

aldabra-mcp dao_register tool initializes new optionals to None so
DaoConfig construction stays explicit.
This commit is contained in:
Kayos 2026-05-05 15:42:59 -07:00
parent 4501700328
commit 3a7f536409
2 changed files with 60 additions and 2 deletions

View file

@ -107,6 +107,54 @@ pub struct DaoConfig {
/// Cardano network this DAO lives on.
#[serde(default)]
pub network: DaoNetwork,
// ─── Phase 4 prerequisites — populated by `dao_discover_scripts` ─────────
//
// All optional: existing configs registered before Phase 4 still load.
// The dao_discover_scripts MCP tool fills these in by inspecting on-chain
// state at the governor / stakes / treasury addresses.
/// Proposal validator address (bech32). Where new proposal UTxOs land.
/// Different from stakes_addr / governor_addr — separate parameterized
/// validator. Discoverable from any tx that created a proposal.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proposal_addr: Option<String>,
/// StakeST minting policy id (56 hex chars). Mints exactly one
/// "stake state thread" token per stake; the asset_name on the token
/// equals the stake validator's script hash.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stake_st_policy: Option<String>,
/// ProposalST minting policy id (56 hex chars). Mints one token per
/// proposal with empty asset name.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proposal_st_policy: Option<String>,
/// Reference UTxOs for each Agora script (so we don't re-discover on
/// every tx). Stored as `txhash#index` strings. Optional — falls back
/// to a lookup at use time when absent.
#[serde(default)]
pub script_refs: ScriptRefs,
}
/// Cached UTxO references for the parameterized Agora scripts. Populated by
/// [`dao_discover_scripts`], consumed by Phase 4 builders so each tx can
/// cite reference inputs without a fresh chain query.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ScriptRefs {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub governor_validator: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stake_validator: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proposal_validator: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub treasury_validator: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stake_st_policy: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proposal_st_policy: Option<String>,
}
impl DaoConfig {
@ -315,7 +363,7 @@ mod tests {
name: name.to_string(),
description: Some("test".into()),
governor_addr: "addr1w8v73wfrru7smn738k6c5xafqvl2tgsvct7dtztc4jwlf4c35jnmy".into(),
stakes_addr: "addr1w9gexmeunzsykesf42d4eqet5yvzeap6trjnflxqtkcf66g5740fw".into(),
stakes_addr: "addr1w8msu7psehjlcu5glzjgjtq53m4mk75c9d2p8hfjwyknmfqskkah8".into(),
treasury_addr: "addr1wx2xrpft9f97ggz4u5yrkev4u340fzfsrqama95kp8l2v6qll696y".into(),
gov_token_policy: "9c4bd4a90cdb73d9ff681215ecf7dea9fb183d916d30487d17098e05".into(),
gov_token_name_hex: "546572726170696e".into(),
@ -326,6 +374,10 @@ mod tests {
treasury_ref_config:
"d9a1cdac8d196ad9303e0faedba992ff31f5bc2186740c362686cfad".into(),
network: DaoNetwork::Mainnet,
proposal_addr: None,
stake_st_policy: None,
proposal_st_policy: None,
script_refs: ScriptRefs::default(),
}
}

View file

@ -30,7 +30,7 @@ use std::sync::Arc;
use aldabra_chain::{ChainBackend, KoiosClient};
use aldabra_dao::agora::stake::Credential as DaoCredential;
use aldabra_dao::config::{DaoConfig, DaoNetwork, DaoStore};
use aldabra_dao::config::{DaoConfig, DaoNetwork, DaoStore, ScriptRefs};
use aldabra_dao::reader::{DaoReader, KoiosDaoReader};
use aldabra_core::plutus_cost_models::PLUTUS_V3_COST_MODEL_PREPROD;
use aldabra_core::{
@ -1340,6 +1340,12 @@ impl WalletService {
Some("preview") => DaoNetwork::Preview,
_ => DaoNetwork::Mainnet,
},
// Optional discovery fields — populated by `dao_discover_scripts`
// after registration.
proposal_addr: None,
stake_st_policy: None,
proposal_st_policy: None,
script_refs: ScriptRefs::default(),
};
self.inner
.dao_store