feat(mcp): wallet_plutus_mint_unsigned MCP tool
Wires the new aldabra-core::plutus_mint module into MCP. Tool name
mirrors the unsigned-first DAO write convention (wallet_send_unsigned,
dao_proposal_*_unsigned, etc).
Args:
- policy_cbor_hex + policy_version (v1/v2/v3) + redeemer_cbor_hex
- mint_assets: array of {asset_name_hex, quantity}
- dest_address + dest_lovelace + dest_extra_assets + dest_inline_datum_cbor_hex
- required_input_refs: array of 'txhash#index' UTxOs that MUST be spent
- ex_units (mem + steps, optional — defaults to DEFAULT_EX_UNITS)
Returns the standard unsigned-payment shape ({cbor_hex, summary})
ready for wallet_sign_partial → wallet_submit_signed_tx.
Used for governor + stake + proposal bootstrap of any Plutus DAO.
For Agora preprod bringup, the typical call is:
- governor: required_input_refs=[gstOutRef], mint=[(GST,1)],
dest=governor_addr, datum=GovernorDatum
- stake: mint=[(StakeST,1)], dest_extras=[(tTRP,N)], dest=stakes_addr,
datum=StakeDatum
- proposal: spends GST input under the existing governor's spend
redeemer + mints ProposalST under the proposal policy
This commit is contained in:
parent
86bc4e45cd
commit
b50d45b5de
1 changed files with 202 additions and 2 deletions
|
|
@ -53,8 +53,9 @@ use aldabra_core::plutus_cost_models::PLUTUS_V3_COST_MODEL_PREPROD;
|
|||
use aldabra_core::{
|
||||
add_witness, build_signed_cip68_nft_mint, build_signed_mint_with_metadata,
|
||||
build_signed_payment_extras, build_signed_plutus_spend, build_signed_stake_delegation,
|
||||
build_unsigned_mint, build_unsigned_payment_extras, hex_decode, summarize_tx, AssetSpec,
|
||||
InputUtxo, Network, PaymentKey, PlutusExUnits, PlutusInput, PlutusVersion, PolicySpec,
|
||||
build_unsigned_mint, build_unsigned_payment_extras, build_unsigned_plutus_mint, hex_decode,
|
||||
summarize_tx, AssetSpec, ExtraDestAsset, InputUtxo, Network, PaymentKey, PlutusExUnits,
|
||||
PlutusInput, PlutusMintArgs as CorePlutusMintArgs, PlutusMintAsset, PlutusVersion, PolicySpec,
|
||||
ProtocolParams, ReferenceScriptSpec, ScriptKind, StakeKey, DEFAULT_EX_UNITS,
|
||||
};
|
||||
|
||||
|
|
@ -320,6 +321,62 @@ pub struct PolicyCreateArgs {
|
|||
pub invalid_after_slot: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct PlutusMintUnsignedArgs {
|
||||
/// Plutus minting policy script CBOR (hex). 28-byte blake2b
|
||||
/// hash with the version tag becomes the policy_id.
|
||||
pub policy_cbor_hex: String,
|
||||
/// Plutus version: "v1", "v2", or "v3".
|
||||
pub policy_version: String,
|
||||
/// PlutusData CBOR redeemer (hex) for the mint redeemer entry.
|
||||
pub redeemer_cbor_hex: String,
|
||||
/// Assets to mint under this policy. Each entry needs an
|
||||
/// `asset_name_hex` (hex of raw bytes, 0-64 chars) and a
|
||||
/// `quantity` (i64). Quantity > 0 mints, < 0 burns. Burning
|
||||
/// requires the wallet to already hold the asset.
|
||||
pub mint_assets: Vec<PlutusMintAssetArg>,
|
||||
/// Recipient address — typically a Plutus script address (e.g.
|
||||
/// governor / stakes / proposal address from the Agora linker).
|
||||
pub dest_address: String,
|
||||
/// ADA on the recipient output. Must be ≥ min-utxo for the
|
||||
/// shape (asset count + name length).
|
||||
pub dest_lovelace: u64,
|
||||
/// Non-mint native assets to forward from wallet inputs onto
|
||||
/// the recipient output. Used e.g. on stake bootstrap to send
|
||||
/// gov tokens (tTRP) into the stakes_addr alongside the freshly
|
||||
/// minted StakeST.
|
||||
#[serde(default)]
|
||||
pub dest_extra_assets: Vec<McpAssetSpec>,
|
||||
/// PlutusData CBOR (hex) for the recipient output's inline
|
||||
/// datum. REQUIRED when sending to a script address — the
|
||||
/// validator needs a datum to read on subsequent spends.
|
||||
#[serde(default)]
|
||||
pub dest_inline_datum_cbor_hex: Option<String>,
|
||||
/// UTxOs that MUST appear as regular tx inputs. Each is
|
||||
/// `txhash#index` referencing a UTxO at this wallet's address.
|
||||
/// Use this to spend the UTxO a parameterized minting policy
|
||||
/// is bound to (Agora's `gstOutRef` is the canonical case).
|
||||
#[serde(default)]
|
||||
pub required_input_refs: Vec<String>,
|
||||
/// ExUnits budget for the mint redeemer. Defaults to the
|
||||
/// generous DEFAULT_EX_UNITS if omitted. Tune for known
|
||||
/// validators to keep the fee tight.
|
||||
#[serde(default)]
|
||||
pub ex_units_mem: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub ex_units_steps: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct PlutusMintAssetArg {
|
||||
/// Hex of raw asset name bytes (0-64 chars). Empty string for
|
||||
/// policy-only / no-asset-name native assets.
|
||||
pub asset_name_hex: String,
|
||||
/// Positive = mint, negative = burn (caller must hold the
|
||||
/// assets to burn).
|
||||
pub quantity: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct MintUnsignedArgs {
|
||||
pub dest_address: String,
|
||||
|
|
@ -1478,6 +1535,149 @@ impl WalletService {
|
|||
serde_json::to_string(&unsigned).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet_plutus_mint_unsigned",
|
||||
description = "Build (no sign) a Plutus-policy mint with custom output. Distinct from wallet_mint (native script only). Args: policy_cbor_hex + policy_version (v1/v2/v3) + redeemer_cbor_hex + ex_units (mem+steps), mint_assets (array of {asset_name_hex, quantity}), dest_address + dest_lovelace + dest_extra_assets + dest_inline_datum_cbor_hex (req for script addrs), required_input_refs (array of 'txhash#index' UTxOs that MUST be spent — e.g. gstOutRef for parameterized policies). Returns JSON {cbor_hex, summary}. Pass through wallet_sign_partial → wallet_submit_signed_tx. Designed for Agora-style DAO bringup: governor bootstrap, stake bootstrap, proposal create."
|
||||
)]
|
||||
async fn wallet_plutus_mint_unsigned(
|
||||
&self,
|
||||
#[tool(aggr)] PlutusMintUnsignedArgs {
|
||||
policy_cbor_hex,
|
||||
policy_version,
|
||||
redeemer_cbor_hex,
|
||||
mint_assets,
|
||||
dest_address,
|
||||
dest_lovelace,
|
||||
dest_extra_assets,
|
||||
dest_inline_datum_cbor_hex,
|
||||
required_input_refs,
|
||||
ex_units_mem,
|
||||
ex_units_steps,
|
||||
}: PlutusMintUnsignedArgs,
|
||||
) -> Result<String, String> {
|
||||
if mint_assets.is_empty() {
|
||||
return Err("mint_assets must contain at least one entry".into());
|
||||
}
|
||||
if dest_lovelace < 1_000_000 {
|
||||
return Err(format!(
|
||||
"dest_lovelace {dest_lovelace} below 1 ADA min for asset-bearing UTXO"
|
||||
));
|
||||
}
|
||||
|
||||
let policy_cbor =
|
||||
hex_decode(&policy_cbor_hex).map_err(|e| format!("decode policy_cbor: {e}"))?;
|
||||
let redeemer_cbor =
|
||||
hex_decode(&redeemer_cbor_hex).map_err(|e| format!("decode redeemer: {e}"))?;
|
||||
let policy_ver = match policy_version.trim().to_ascii_lowercase().as_str() {
|
||||
"v1" | "plutusv1" => PlutusVersion::V1,
|
||||
"v2" | "plutusv2" => PlutusVersion::V2,
|
||||
"v3" | "plutusv3" => PlutusVersion::V3,
|
||||
other => {
|
||||
return Err(format!(
|
||||
"invalid policy_version '{other}'; expected v1/v2/v3"
|
||||
))
|
||||
}
|
||||
};
|
||||
let datum_bytes = match dest_inline_datum_cbor_hex.as_deref() {
|
||||
Some(s) => Some(hex_decode(s).map_err(|e| format!("decode datum: {e}"))?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let core_mints: Vec<PlutusMintAsset> = mint_assets
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
if m.quantity == 0 {
|
||||
return Err("mint_asset quantity must be nonzero".to_string());
|
||||
}
|
||||
Ok(PlutusMintAsset {
|
||||
asset_name_hex: m.asset_name_hex,
|
||||
quantity: m.quantity,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
let core_extras: Vec<ExtraDestAsset> = dest_extra_assets
|
||||
.into_iter()
|
||||
.map(|a| ExtraDestAsset {
|
||||
policy_id_hex: a.policy_id_hex,
|
||||
asset_name_hex: a.asset_name_hex,
|
||||
quantity: a.quantity,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Pull current UTxO set; resolve required_input_refs against it.
|
||||
let utxos = self
|
||||
.inner
|
||||
.chain
|
||||
.get_utxos(&self.inner.address)
|
||||
.await
|
||||
.map_err(|e| format!("fetch utxos: {e}"))?;
|
||||
if utxos.is_empty() {
|
||||
return Err(format!(
|
||||
"no utxos at wallet address {}",
|
||||
self.inner.address
|
||||
));
|
||||
}
|
||||
let inputs: Vec<InputUtxo> = utxos
|
||||
.into_iter()
|
||||
.map(|u| InputUtxo {
|
||||
tx_hash_hex: u.tx_hash,
|
||||
output_index: u.output_index,
|
||||
lovelace: u.lovelace,
|
||||
assets: u.assets,
|
||||
})
|
||||
.collect();
|
||||
let mut required: Vec<InputUtxo> = Vec::with_capacity(required_input_refs.len());
|
||||
for r in &required_input_refs {
|
||||
let (h, ix) = r
|
||||
.split_once('#')
|
||||
.ok_or_else(|| format!("required_input_ref '{r}' must be 'txhash#index'"))?;
|
||||
let ix: u32 = ix.parse().map_err(|e| format!("required_input_ref idx: {e}"))?;
|
||||
let found = inputs
|
||||
.iter()
|
||||
.find(|u| u.tx_hash_hex == h && u.output_index == ix)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"required_input {r} not found in this wallet's UTxOs — \
|
||||
either fund it first, or pass an existing UTxO ref"
|
||||
)
|
||||
})?;
|
||||
required.push(found.clone());
|
||||
}
|
||||
|
||||
let ex_units = match (ex_units_mem, ex_units_steps) {
|
||||
(Some(m), Some(s)) => PlutusExUnits { mem: m, steps: s },
|
||||
(None, None) => DEFAULT_EX_UNITS,
|
||||
_ => {
|
||||
return Err(
|
||||
"ex_units_mem and ex_units_steps must both be set or both omitted".into(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let core_args = CorePlutusMintArgs {
|
||||
required_inputs: &required,
|
||||
policy_cbor: &policy_cbor,
|
||||
policy_version: policy_ver,
|
||||
redeemer_cbor: &redeemer_cbor,
|
||||
ex_units,
|
||||
mint_assets: &core_mints,
|
||||
dest_address_bech32: &dest_address,
|
||||
dest_lovelace,
|
||||
dest_extra_assets: &core_extras,
|
||||
dest_inline_datum_cbor: datum_bytes.as_deref(),
|
||||
};
|
||||
|
||||
let unsigned = build_unsigned_plutus_mint(
|
||||
self.inner.network,
|
||||
&inputs,
|
||||
&self.inner.address,
|
||||
&core_args,
|
||||
&ProtocolParams::default(),
|
||||
)
|
||||
.map_err(|e| format!("build unsigned plutus mint: {e}"))?;
|
||||
serde_json::to_string(&unsigned).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet_tx_summary",
|
||||
description = "Decode a Conway-era tx CBOR (unsigned, partial, or signed) into a human-reviewable JSON summary: tx_hash, inputs count, outputs (address+lovelace+assets+inline_datum flag), fee, certificates, mint, witness count, aux-data presence. **Read-only — does not sign or submit.** Run this before `wallet_sign_partial` on any CBOR you didn't build yourself."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue