phase 3.1, 3.4, 3.5: native policy + mint path (no metadata yet)
new aldabra-core::mint module:
- PolicySpec enum: SingleSig, SingleSigTimelock, NofK
- SingleSig{pkh}: ScriptPubkey native script
- SingleSigTimelock{pkh, slot}: ScriptAll[ScriptPubkey, InvalidHereafter(slot)]
- NofK{n, [pkhs]}: ScriptNOfK
- PolicySpec::single_sig(payment) + single_sig_timelock(payment, slot)
convenience constructors that derive the pkh from a PaymentKey.
- policy_id() = pallas_traverse::ComputeHash<28>::compute_hash, which
is blake2b-224 of (0x00 || cbor) — the canonical native-script hash.
- to_cbor() for callers that want the script bytes raw.
build_signed_mint / build_unsigned_mint:
- two-pass fee like the send path, plus a few extras specific to mint:
staging.mint_asset(policy, name, qty), .script(Native, cbor),
.disclosed_signer(payment_pkh) — the disclosed_signer surfaces the
required signature in the tx body so the chain knows which witness
to verify against the script.
- positive qty mints (asset goes into dest output), negative qty burns
(asset comes out of input holdings, change preserves leftover).
- token-bearing change must hold ≥ min_utxo lovelace — same guard as
the send path.
mcp tools:
- wallet.policy.create — args: invalid_after_slot? — returns
{policy_id_hex, script_cbor_hex, type}.
- wallet.mint — args: dest_address, dest_lovelace (≥ 1 ADA),
asset_name_hex, quantity (i64), invalid_after_slot? — auto-generates
a single-sig policy bound to the wallet's payment key, builds, signs,
submits.
8 → 10 mcp tools. 48 → 56 unit tests.
3.2 (CIP-25 metadata) is BLOCKED on pallas-txbuilder 0.32/0.35 — both
hardcode `auxiliary_data: None` in the conway builder. options for next
session: (a) post-build CBOR injection, (b) assemble tx via
pallas-primitives directly, (c) wait for upstream. flagged in the
spec doc.
3.3 (CIP-68) depends on 3.2. 3.6 (MAP 2-of-2) needs the multi-key
signing flow on the build side; PolicySpec::NofK variant is ready but
build_signed_mint only sign with one key today.
This commit is contained in:
parent
a472695558
commit
532b3d3558
6 changed files with 867 additions and 3 deletions
|
|
@ -26,8 +26,8 @@ use std::sync::Arc;
|
|||
|
||||
use aldabra_chain::{ChainBackend, KoiosClient};
|
||||
use aldabra_core::{
|
||||
build_signed_payment_with_assets, build_unsigned_payment_with_assets, hex_decode, AssetSpec,
|
||||
InputUtxo, Network, PaymentKey, ProtocolParams,
|
||||
build_signed_mint, build_signed_payment_with_assets, build_unsigned_payment_with_assets,
|
||||
hex_decode, AssetSpec, InputUtxo, Network, PaymentKey, PolicySpec, ProtocolParams,
|
||||
};
|
||||
use rmcp::{model::ServerInfo, schemars, tool, ServerHandler};
|
||||
use serde::Deserialize;
|
||||
|
|
@ -129,6 +129,35 @@ pub struct SubmitSignedArgs {
|
|||
pub signed_cbor_hex: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct PolicyCreateArgs {
|
||||
/// Optional slot after which the policy becomes invalid. Use
|
||||
/// to lock supply (Cardano idiom: mint then expire). Omit for
|
||||
/// an open-ended policy that allows mint/burn forever.
|
||||
#[serde(default)]
|
||||
pub invalid_after_slot: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct MintArgs {
|
||||
/// Recipient bech32 address. Receives `dest_lovelace` ADA + the
|
||||
/// freshly-minted asset. Often the wallet's own address.
|
||||
pub dest_address: String,
|
||||
/// ADA to attach to the mint output (must be ≥ min_utxo —
|
||||
/// typically 1_500_000 lovelace for an asset-bearing output).
|
||||
pub dest_lovelace: u64,
|
||||
/// Hex-encoded asset name (raw bytes, 0-32 bytes).
|
||||
pub asset_name_hex: String,
|
||||
/// Mint quantity. Positive = mint, negative = burn (caller must
|
||||
/// hold the assets to burn).
|
||||
pub quantity: i64,
|
||||
/// Optional invalid-after slot for the auto-generated policy.
|
||||
/// If omitted, generates an open-ended single-sig policy bound
|
||||
/// to the wallet's payment key.
|
||||
#[serde(default)]
|
||||
pub invalid_after_slot: Option<u64>,
|
||||
}
|
||||
|
||||
#[tool(tool_box)]
|
||||
impl WalletService {
|
||||
#[tool(
|
||||
|
|
@ -331,6 +360,111 @@ impl WalletService {
|
|||
.await
|
||||
.map_err(|e| format!("submit: {e}"))
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.policy.create",
|
||||
description = "Generate a single-sig native policy bound to this wallet's payment key. Args: invalid_after_slot (optional u64 — omit for open-ended, supply for time-locked supply). Returns JSON {policy_id_hex, script_cbor_hex, type}."
|
||||
)]
|
||||
async fn wallet_policy_create(
|
||||
&self,
|
||||
#[tool(aggr)] PolicyCreateArgs { invalid_after_slot }: PolicyCreateArgs,
|
||||
) -> Result<String, String> {
|
||||
let policy = match invalid_after_slot {
|
||||
Some(slot) => PolicySpec::single_sig_timelock(&self.inner.payment_key, slot),
|
||||
None => PolicySpec::single_sig(&self.inner.payment_key),
|
||||
};
|
||||
let policy_id = policy.policy_id().map_err(|e| e.to_string())?;
|
||||
let cbor = policy.to_cbor().map_err(|e| e.to_string())?;
|
||||
let mut policy_id_hex = String::with_capacity(56);
|
||||
for b in policy_id.as_ref() {
|
||||
policy_id_hex.push_str(&format!("{:02x}", b));
|
||||
}
|
||||
let mut cbor_hex = String::with_capacity(cbor.len() * 2);
|
||||
for b in &cbor {
|
||||
cbor_hex.push_str(&format!("{:02x}", b));
|
||||
}
|
||||
let kind = match invalid_after_slot {
|
||||
Some(_) => "single_sig_timelock",
|
||||
None => "single_sig",
|
||||
};
|
||||
Ok(format!(
|
||||
"{{\"policy_id_hex\":\"{policy_id_hex}\",\"script_cbor_hex\":\"{cbor_hex}\",\"type\":\"{kind}\"}}"
|
||||
))
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.mint",
|
||||
description = "Mint or burn a native asset under a wallet-generated single-sig policy. Args: dest_address, dest_lovelace (ADA to attach to the mint output, ≥ ~1.5 ADA for an asset-bearing utxo), asset_name_hex, quantity (positive=mint, negative=burn), invalid_after_slot (optional). Returns the tx hash on success. NB: this version does not attach CIP-25 metadata — pallas-txbuilder 0.32 doesn't surface auxiliary_data yet."
|
||||
)]
|
||||
async fn wallet_mint(
|
||||
&self,
|
||||
#[tool(aggr)] MintArgs {
|
||||
dest_address,
|
||||
dest_lovelace,
|
||||
asset_name_hex,
|
||||
quantity,
|
||||
invalid_after_slot,
|
||||
}: MintArgs,
|
||||
) -> Result<String, String> {
|
||||
if quantity == 0 {
|
||||
return Err("quantity must be nonzero (positive=mint, negative=burn)".into());
|
||||
}
|
||||
if dest_lovelace < 1_000_000 {
|
||||
return Err(format!(
|
||||
"dest_lovelace {dest_lovelace} below 1 ADA min — token-bearing UTXO will be rejected"
|
||||
));
|
||||
}
|
||||
|
||||
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 the wallet 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 policy = match invalid_after_slot {
|
||||
Some(slot) => PolicySpec::single_sig_timelock(&self.inner.payment_key, slot),
|
||||
None => PolicySpec::single_sig(&self.inner.payment_key),
|
||||
};
|
||||
|
||||
let cbor = build_signed_mint(
|
||||
&self.inner.payment_key,
|
||||
self.inner.network,
|
||||
&inputs,
|
||||
&self.inner.address,
|
||||
&dest_address,
|
||||
dest_lovelace,
|
||||
&policy,
|
||||
&asset_name_hex,
|
||||
quantity,
|
||||
&ProtocolParams::default(),
|
||||
)
|
||||
.map_err(|e| format!("build/sign mint: {e}"))?;
|
||||
|
||||
let tx_hash = self
|
||||
.inner
|
||||
.chain
|
||||
.submit_tx(&cbor)
|
||||
.await
|
||||
.map_err(|e| format!("submit: {e}"))?;
|
||||
Ok(tx_hash)
|
||||
}
|
||||
}
|
||||
|
||||
#[tool(tool_box)]
|
||||
|
|
@ -338,7 +472,7 @@ impl ServerHandler for WalletService {
|
|||
fn get_info(&self) -> ServerInfo {
|
||||
ServerInfo {
|
||||
instructions: Some(
|
||||
"aldabra — Cardano lite wallet over MCP. Phase 1 (read): wallet.address, wallet.network, wallet.balance, wallet.utxos. Phase 2 (send): wallet.send (auto-sign), wallet.send.unsigned + wallet.submit_signed_tx (cold-sign flow), wallet.tx_status. Native-asset send + Plutus land in phase 3+.".into(),
|
||||
"aldabra — Cardano lite wallet over MCP. Phase 1 (read): wallet.address, wallet.network, wallet.balance, wallet.utxos. Phase 2 (send): wallet.send (with native-asset bundle support), wallet.send.unsigned + wallet.submit_signed_tx (cold-sign), wallet.tx_status. Phase 3 (mint): wallet.policy.create, wallet.mint. CIP-25 metadata + CIP-68 + Plutus land in follow-up.".into(),
|
||||
),
|
||||
..Default::default()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue