phase 3.2: cip-25 metadata via the pallas fork

unblocks named mints. wallet.mint now accepts an optional `metadata`
arg (json object); explorers + wallets render the asset with name/image
instead of <asset1xyz...>.

new aldabra-core::metadata module:
- json_to_metadatum: serde_json::Value → Metadatum (recursive). numbers
  must fit i64 (cardano metadata Int width). strings >64 bytes split
  into Array<Text> chunks at utf-8 char boundaries (CIP-25 v2
  long-string convention). null is rejected.
- build_cip25_aux_data(policy_id_hex, asset_name_hex, json_value):
  builds the label-721 wrapper (Map { 721: Map { policy_bytes:
  Map { name_bytes: attrs }, "version": "2.0" } }), wraps in
  AuxiliaryData::PostAlonzo, returns cbor bytes.

mint module:
- new build_signed_mint_with_metadata + build_unsigned_mint now take
  optional cip25_metadata. backward-compat: build_signed_mint is a
  thin no-metadata wrapper.
- prepare_mint + build_mint_staging plumb aux_data_cbor through.
  staging.auxiliary_data(bytes) is the new fork API surface — when
  set, conway::build_conway_raw decodes + computes
  auxiliary_data_hash automatically.
- regression test build_signed_mint_with_metadata_produces_aux_hash:
  decodes the resulting signed cbor, asserts both
  body.auxiliary_data_hash is Some and tx.auxiliary_data is present.
  catches the failure mode where metadata is silently dropped.

mcp wallet.mint gains a `metadata` arg field surfaced via schemars
JsonSchema. tools/list shape correctly carries the optional json
object.

depends on Sulkta-Coop/pallas@feat-aux-data — vendored via
[patch.crates-io] in the workspace Cargo.toml. PR upstream pending.

56 → 65 unit tests. 8 → 8 mcp tools (count unchanged, wallet.mint
gained an arg).
This commit is contained in:
Sulkta 2026-05-04 12:11:11 -07:00
parent 532b3d3558
commit 218d1ac94b
7 changed files with 450 additions and 20 deletions

View file

@ -26,8 +26,9 @@ use std::sync::Arc;
use aldabra_chain::{ChainBackend, KoiosClient};
use aldabra_core::{
build_signed_mint, build_signed_payment_with_assets, build_unsigned_payment_with_assets,
hex_decode, AssetSpec, InputUtxo, Network, PaymentKey, PolicySpec, ProtocolParams,
build_signed_mint_with_metadata, 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;
@ -156,6 +157,12 @@ pub struct MintArgs {
/// to the wallet's payment key.
#[serde(default)]
pub invalid_after_slot: Option<u64>,
/// Optional CIP-25 v2 metadata: a JSON object with the asset's
/// per-attributes (`name`, `image`, `description`, `mediaType`,
/// `files`, etc.). Wallets and explorers display this when
/// rendering the asset.
#[serde(default)]
pub metadata: Option<serde_json::Value>,
}
#[tool(tool_box)]
@ -394,7 +401,7 @@ impl WalletService {
#[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."
description = "Mint or burn a native asset under a wallet-generated single-sig policy, optionally with CIP-25 v2 metadata. Args: dest_address, dest_lovelace (≥ 1.5 ADA for asset-bearing UTXO), asset_name_hex, quantity (positive=mint, negative=burn), invalid_after_slot (optional), metadata (optional CIP-25 JSON object: {name, image, description, mediaType, files, ...}). Returns the tx hash on success."
)]
async fn wallet_mint(
&self,
@ -404,6 +411,7 @@ impl WalletService {
asset_name_hex,
quantity,
invalid_after_slot,
metadata,
}: MintArgs,
) -> Result<String, String> {
if quantity == 0 {
@ -443,7 +451,7 @@ impl WalletService {
None => PolicySpec::single_sig(&self.inner.payment_key),
};
let cbor = build_signed_mint(
let cbor = build_signed_mint_with_metadata(
&self.inner.payment_key,
self.inner.network,
&inputs,
@ -453,6 +461,7 @@ impl WalletService {
&policy,
&asset_name_hex,
quantity,
metadata.as_ref(),
&ProtocolParams::default(),
)
.map_err(|e| format!("build/sign mint: {e}"))?;