phase 4.1-4.3: plutus script spend
new aldabra-core::plutus module:
- PlutusVersion enum (V1, V2, V3) → maps to ScriptKind on the
pallas-txbuilder side.
- PlutusExUnits (mem, steps) — public mirror of pallas's so callers
don't drag pallas types in. From<> impl converts internally.
- DEFAULT_EX_UNITS = (14M mem, 10B steps) — generous budget that
validates trivial validators ("always succeeds", simple equality);
real validators tune via the ex_units arg.
- MIN_COLLATERAL_LOVELACE = 5_000_000 (Conway protocol floor).
- build_signed_plutus_spend(payment, network, locked, script, redeemer,
witness_datum?, available_utxos, change_addr, payout_addr,
payout_lovelace, ex_units, params) → signed cbor.
- picks the largest wallet UTXO ≥ 5 ADA as collateral, errors out
if none qualifies.
- happy path: locked + collateral as inputs, payout + change as
outputs, script + redeemer + (optional witness) datum as
witnesses, wallet's payment key signs the body.
- reference inputs (4.2 expansion) and live ExUnits estimation
(4.4) are follow-ups.
- looks_like_script_address(bech32) bool sanity helper for callers
that want to filter by address kind before constructing a spend.
mcp tool wallet.script.spend: full args surface for one-shot
spend. plutus_version is a string ("v1"|"v2"|"v3"). ex_units optional.
84 → 88 unit tests. 15 → 16 mcp tools.
phase 4 status:
- 4.1 ☑ inline datum (already supported via Output::set_inline_datum
used by cip-68 mint)
- 4.2 ◐ reference input (txbuilder has the API; not yet exposed in
build_signed_plutus_spend — followup)
- 4.3 ☑ wallet.script.spend
- 4.4 ☐ ExUnits estimation — needs uplc / aiken integration, defer
- 4.5 ☑ stake key derivation
- 4.6 ☑ wallet.stake.delegate
This commit is contained in:
parent
49986d2864
commit
5888d37df6
3 changed files with 564 additions and 3 deletions
|
|
@ -27,9 +27,10 @@ use std::sync::Arc;
|
|||
use aldabra_chain::{ChainBackend, KoiosClient};
|
||||
use aldabra_core::{
|
||||
add_witness, build_signed_cip68_nft_mint, build_signed_mint_with_metadata,
|
||||
build_signed_payment_with_assets, build_signed_stake_delegation, build_unsigned_mint,
|
||||
build_unsigned_payment_with_assets, hex_decode, AssetSpec, InputUtxo, Network, PaymentKey,
|
||||
PolicySpec, ProtocolParams, StakeKey,
|
||||
build_signed_payment_with_assets, build_signed_plutus_spend, build_signed_stake_delegation,
|
||||
build_unsigned_mint, build_unsigned_payment_with_assets, hex_decode, AssetSpec, InputUtxo,
|
||||
Network, PaymentKey, PlutusExUnits, PlutusInput, PlutusVersion, PolicySpec, ProtocolParams,
|
||||
StakeKey, DEFAULT_EX_UNITS,
|
||||
};
|
||||
use rmcp::{model::ServerInfo, schemars, tool, ServerHandler};
|
||||
use serde::Deserialize;
|
||||
|
|
@ -167,6 +168,41 @@ pub struct MintUnsignedArgs {
|
|||
pub disclosed_signer_pkh_hex: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct ScriptSpendArgs {
|
||||
/// Hex-encoded tx hash of the locked UTXO.
|
||||
pub locked_tx_hash: String,
|
||||
/// Output index of the locked UTXO at that tx.
|
||||
pub locked_output_index: u32,
|
||||
/// Lovelace at the locked UTXO.
|
||||
pub locked_lovelace: u64,
|
||||
/// Plutus version: "v1", "v2", or "v3".
|
||||
pub plutus_version: String,
|
||||
/// Hex-encoded Plutus script CBOR.
|
||||
pub script_cbor_hex: String,
|
||||
/// Hex-encoded redeemer (PlutusData CBOR).
|
||||
pub redeemer_cbor_hex: String,
|
||||
/// Optional witness datum hex. Omit if datum is inline on the
|
||||
/// locked UTXO.
|
||||
#[serde(default)]
|
||||
pub witness_datum_hex: Option<String>,
|
||||
/// Where the unlocked funds go.
|
||||
pub payout_address: String,
|
||||
/// Lovelace to send to payout address.
|
||||
pub payout_lovelace: u64,
|
||||
/// Optional ExUnits override `{"mem": ..., "steps": ...}`. Omit
|
||||
/// for the conservative default budget (works for trivial
|
||||
/// validators; tune for real ones).
|
||||
#[serde(default)]
|
||||
pub ex_units: Option<ExUnitsArg>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct ExUnitsArg {
|
||||
pub mem: u64,
|
||||
pub steps: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct StakeDelegateArgs {
|
||||
/// Stake pool bech32 ID (`pool1...`).
|
||||
|
|
@ -655,6 +691,102 @@ impl WalletService {
|
|||
Ok(tx_hash)
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.script.spend",
|
||||
description = "Spend a Plutus-locked UTXO. Args: locked_tx_hash, locked_output_index, locked_lovelace, plutus_version (v1|v2|v3), script_cbor_hex, redeemer_cbor_hex, witness_datum_hex (optional, omit if datum is inline on the locked utxo), payout_address, payout_lovelace, ex_units (optional {mem,steps} — defaults to a generous budget for trivial validators). Wallet picks its own collateral UTXO (≥ 5 ADA). Returns the tx hash."
|
||||
)]
|
||||
async fn wallet_script_spend(
|
||||
&self,
|
||||
#[tool(aggr)] ScriptSpendArgs {
|
||||
locked_tx_hash,
|
||||
locked_output_index,
|
||||
locked_lovelace,
|
||||
plutus_version,
|
||||
script_cbor_hex,
|
||||
redeemer_cbor_hex,
|
||||
witness_datum_hex,
|
||||
payout_address,
|
||||
payout_lovelace,
|
||||
ex_units,
|
||||
}: ScriptSpendArgs,
|
||||
) -> Result<String, String> {
|
||||
let version = match plutus_version.to_ascii_lowercase().as_str() {
|
||||
"v1" => PlutusVersion::V1,
|
||||
"v2" => PlutusVersion::V2,
|
||||
"v3" => PlutusVersion::V3,
|
||||
other => {
|
||||
return Err(format!(
|
||||
"plutus_version must be v1, v2, or v3 (got {other:?})"
|
||||
))
|
||||
}
|
||||
};
|
||||
let script_cbor = hex_decode(&script_cbor_hex).map_err(|e| format!("script: {e}"))?;
|
||||
let redeemer_cbor = hex_decode(&redeemer_cbor_hex).map_err(|e| format!("redeemer: {e}"))?;
|
||||
let datum_cbor: Option<Vec<u8>> = witness_datum_hex
|
||||
.as_deref()
|
||||
.map(|s| hex_decode(s).map_err(|e| format!("datum: {e}")))
|
||||
.transpose()?;
|
||||
let budget = ex_units
|
||||
.map(|e| PlutusExUnits {
|
||||
mem: e.mem,
|
||||
steps: e.steps,
|
||||
})
|
||||
.unwrap_or(DEFAULT_EX_UNITS);
|
||||
|
||||
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 {} — fund for collateral first",
|
||||
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 locked = PlutusInput {
|
||||
tx_hash_hex: locked_tx_hash,
|
||||
output_index: locked_output_index,
|
||||
lovelace: locked_lovelace,
|
||||
};
|
||||
|
||||
let cbor = build_signed_plutus_spend(
|
||||
&self.inner.payment_key,
|
||||
self.inner.network,
|
||||
&locked,
|
||||
version,
|
||||
&script_cbor,
|
||||
&redeemer_cbor,
|
||||
datum_cbor.as_deref(),
|
||||
&inputs,
|
||||
&self.inner.address,
|
||||
&payout_address,
|
||||
payout_lovelace,
|
||||
budget,
|
||||
&ProtocolParams::default(),
|
||||
)
|
||||
.map_err(|e| format!("build/sign plutus spend: {e}"))?;
|
||||
|
||||
let tx_hash = self
|
||||
.inner
|
||||
.chain
|
||||
.submit_tx(&cbor)
|
||||
.await
|
||||
.map_err(|e| format!("submit: {e}"))?;
|
||||
Ok(tx_hash)
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.stake.delegate",
|
||||
description = "Delegate this wallet's stake to a Cardano pool. Args: pool_id (bech32 'pool1...'), register_first (bool, defaults true — prepends a 2 ADA stake-registration cert; set false if the stake key is already registered). Signs with both the payment and stake keys, submits, returns the tx hash."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue