feat(dao-mcp): wire dao_proposal_create_unsigned + drop unused imports
This commit is contained in:
parent
3ac10f7f4b
commit
93edf0c9c3
3 changed files with 159 additions and 3 deletions
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
use pallas_primitives::PlutusData;
|
||||
|
||||
use crate::agora::plutus_data::{as_int, as_product, constr, int, product};
|
||||
use crate::agora::plutus_data::{as_int, as_product, int, product};
|
||||
use crate::agora::proposal::{ProposalThresholds, ProposalTimingConfig};
|
||||
use crate::error::{DaoError, DaoResult};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,10 +40,9 @@
|
|||
|
||||
use pallas_addresses::Address;
|
||||
use pallas_codec::minicbor;
|
||||
use pallas_codec::utils::Bytes;
|
||||
use pallas_crypto::hash::Hash;
|
||||
use pallas_primitives::PlutusData;
|
||||
use pallas_txbuilder::{BuildConway, ExUnits, Input, Output, ScriptKind, StagingTransaction};
|
||||
use pallas_txbuilder::{BuildConway, ExUnits, Input, Output, StagingTransaction};
|
||||
|
||||
use crate::agora::governor::GovernorDatum;
|
||||
use crate::agora::proposal::{
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ use std::sync::Arc;
|
|||
|
||||
use aldabra_chain::{ChainBackend, KoiosClient};
|
||||
use aldabra_dao::agora::stake::Credential as DaoCredential;
|
||||
use aldabra_dao::builder::proposal_create::{
|
||||
build_unsigned_proposal_create, GovernorUtxoIn, ProposalCreateArgs, ReferenceUtxo,
|
||||
WalletUtxo as DaoWalletUtxo,
|
||||
};
|
||||
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;
|
||||
|
|
@ -1486,6 +1490,135 @@ impl WalletService {
|
|||
Ok(serde_json::json!({ "dao": cfg.name, "stakes": arr }).to_string())
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "dao_proposal_create_unsigned",
|
||||
description = "Build (but DO NOT submit) an unsigned proposal-creation tx for the given DAO. Returns the CBOR-hex of the unsigned tx body + the new proposal_id. Currently supports InfoOnly proposals only — TreasuryWithdrawal effect path lands in Phase 4c. Caller signs via wallet_sign_partial then submits via wallet_submit_signed_tx. Args: dao (optional — defaults to active), fee_lovelace (suggested ~3_000_000 for v1; refine via koios tx_evaluate), starting_time_ms (POSIX millis to embed in ProposalDatum.starting_time; pass current chain tip's slot * 1000 + epoch start)."
|
||||
)]
|
||||
async fn dao_proposal_create_unsigned(
|
||||
&self,
|
||||
#[tool(aggr)] DaoProposalCreateArgs {
|
||||
dao,
|
||||
fee_lovelace,
|
||||
starting_time_ms,
|
||||
}: DaoProposalCreateArgs,
|
||||
) -> Result<String, String> {
|
||||
let cfg = self
|
||||
.inner
|
||||
.dao_store
|
||||
.resolve(dao.as_deref())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Read live governor state so we have the current next_proposal_id +
|
||||
// datum to copy.
|
||||
let (governor_utxo_ref, governor_datum) = self
|
||||
.inner
|
||||
.dao_reader
|
||||
.get_governor(&cfg)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let (gov_tx_hash, gov_idx) = parse_utxo_ref(&governor_utxo_ref)?;
|
||||
|
||||
// Pull governor lovelace + every wallet UTxO. We need both:
|
||||
// (a) governor lovelace for tx-balance calculation,
|
||||
// (b) wallet utxos for funding + collateral selection.
|
||||
let gov_lovelace = self
|
||||
.inner
|
||||
.chain
|
||||
.get_utxos(&cfg.governor_addr)
|
||||
.await
|
||||
.map_err(|e| format!("koios get governor utxos: {e}"))?
|
||||
.into_iter()
|
||||
.find(|u| u.tx_hash == gov_tx_hash && u.output_index == gov_idx)
|
||||
.ok_or_else(|| format!("governor utxo {governor_utxo_ref} no longer present on chain"))?
|
||||
.lovelace;
|
||||
|
||||
let wallet_utxos = self
|
||||
.inner
|
||||
.chain
|
||||
.get_utxos(&self.inner.address)
|
||||
.await
|
||||
.map_err(|e| format!("koios get wallet utxos: {e}"))?
|
||||
.into_iter()
|
||||
.map(|u| DaoWalletUtxo {
|
||||
tx_hash_hex: u.tx_hash,
|
||||
output_index: u.output_index,
|
||||
lovelace: u.lovelace,
|
||||
// Chain backend gives `assets: BTreeMap<concatenated_key, qty>`
|
||||
// where the key is `policy_id_hex || asset_name_hex` with the
|
||||
// policy taking the first 56 chars (28 bytes). Split for
|
||||
// pallas-txbuilder which wants the parts separate.
|
||||
assets: u
|
||||
.assets
|
||||
.into_iter()
|
||||
.filter_map(|(k, q)| {
|
||||
if k.len() >= 56 {
|
||||
let (p, n) = k.split_at(56);
|
||||
Some((p.to_string(), n.to_string(), q))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// ScriptRefs must be populated before this tool can build a tx.
|
||||
// For Sulkta the values are known from the audit; user must pass
|
||||
// them in via dao_register or hand-edit the json (until
|
||||
// dao_discover_scripts ships).
|
||||
let governor_validator_ref = ReferenceUtxo::from_str(
|
||||
cfg.script_refs
|
||||
.governor_validator
|
||||
.as_deref()
|
||||
.ok_or_else(|| {
|
||||
"DaoConfig.script_refs.governor_validator missing — populate before \
|
||||
calling dao_proposal_create_unsigned"
|
||||
.to_string()
|
||||
})?,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let proposal_st_policy_ref = ReferenceUtxo::from_str(
|
||||
cfg.script_refs
|
||||
.proposal_st_policy
|
||||
.as_deref()
|
||||
.ok_or_else(|| {
|
||||
"DaoConfig.script_refs.proposal_st_policy missing — populate first"
|
||||
.to_string()
|
||||
})?,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let proposer_pkh = self.wallet_pkh()?;
|
||||
|
||||
let unsigned = build_unsigned_proposal_create(ProposalCreateArgs {
|
||||
cfg: cfg.clone(),
|
||||
governor: GovernorUtxoIn {
|
||||
tx_hash_hex: gov_tx_hash,
|
||||
output_index: gov_idx,
|
||||
lovelace: gov_lovelace,
|
||||
datum: governor_datum,
|
||||
},
|
||||
proposer_pkh,
|
||||
change_address: self.inner.address.clone(),
|
||||
wallet_utxos,
|
||||
starting_time_ms,
|
||||
governor_validator_ref,
|
||||
proposal_st_policy_ref,
|
||||
fee_lovelace,
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"dao": cfg.name,
|
||||
"tx_cbor_hex": unsigned.tx_cbor_hex,
|
||||
"tx_hash_hex": unsigned.tx_hash_hex,
|
||||
"new_proposal_id": unsigned.new_proposal_id,
|
||||
"summary": unsigned.summary,
|
||||
"next_step": "review tx_cbor_hex (decode + audit), then sign via wallet_sign_partial + submit via wallet_submit_signed_tx",
|
||||
})
|
||||
.to_string())
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "dao_my_stake",
|
||||
description = "Filter dao_stake_list to just the stake owned by THIS wallet (by matching the wallet's payment-credential hash against StakeDatum.owner). Returns the same shape as dao_stake_list but with at most one entry. If the wallet has no stake yet, returns an empty stakes array. Args: dao (optional — defaults to active)."
|
||||
|
|
@ -1561,6 +1694,30 @@ pub struct DaoShowArgs {
|
|||
pub dao: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct DaoProposalCreateArgs {
|
||||
/// Named DAO. Falls through to active if omitted.
|
||||
#[serde(default)]
|
||||
pub dao: Option<String>,
|
||||
/// Estimated total fee in lovelace. v1 caller-supplied; future versions
|
||||
/// will derive from `koios /tx_evaluate`. ~3 ADA is a reasonable bound
|
||||
/// for an InfoOnly proposal-create on Sulkta-shape thresholds.
|
||||
pub fee_lovelace: u64,
|
||||
/// POSIX time in milliseconds to embed in the new ProposalDatum's
|
||||
/// `starting_time`. Should reflect current chain tip — pass
|
||||
/// `chain_tip.block_time * 1000` (Koios's `block_time` is in seconds).
|
||||
pub starting_time_ms: i64,
|
||||
}
|
||||
|
||||
/// Parse a `txhash#index` UTxO ref into its components.
|
||||
fn parse_utxo_ref(s: &str) -> Result<(String, u32), String> {
|
||||
let (h, i) = s
|
||||
.split_once('#')
|
||||
.ok_or_else(|| format!("utxo ref {s:?} not in 'txhash#index' form"))?;
|
||||
let idx: u32 = i.parse().map_err(|e| format!("utxo index {i:?} parse: {e}"))?;
|
||||
Ok((h.to_string(), idx))
|
||||
}
|
||||
|
||||
/// Render a [`aldabra_dao::reader::StakeUtxo`] as a JSON object for tool output.
|
||||
///
|
||||
/// Formatted as a free function rather than `impl Serialize for StakeUtxo` to
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue