Rust API Reference
use mesmo::{Mesmo, Network, MesmoError, Result};Most methods return mesmo::Result<String> where the string is either a JSON document (parse with serde_json) or a bare hex/bech32 value — noted per method below.
impl Mesmo { pub fn new() -> Result<Self>; pub fn version(&self) -> Result<String>;
pub fn accounts(&self) -> AccountsApi<'_>; pub fn address(&self) -> AddressApi<'_>; pub fn crypto(&self) -> CryptoApi<'_>; pub fn tx(&self) -> TxApi<'_>; pub fn plutus(&self) -> PlutusApi<'_>; pub fn script(&self) -> ScriptApi<'_>; pub fn quicktx(&self) -> QuickTxApi<'_>;}Mesmo::new() creates a GraalVM isolate and verifies the native library version matches the crate.
Lifecycle. Teardown is RAII: Drop tears down the isolate. The namespace handles (AddressApi<'_> etc.) borrow Mesmo, so the borrow checker statically prevents use-after-free; managed Accounts are owned values that a Mesmo drop hard-invalidates (typed -11 errors).
Threading. Mesmo is !Send and !Sync — moving it to another thread is a compile error. The GraalVM isolate thread is bound to the OS thread that created it; create one Mesmo per thread.
Networks
Section titled “Networks”#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub enum Network { Mainnet, Testnet }
impl Network { pub fn as_i32(self) -> i32 } // Mainnet=0, Testnet=1Gotcha: the ordinals are CCL enum values, not Cardano’s on-chain network id — the two are inverted for mainnet/testnet (
Mainnet= 0, but a mainnet address’s on-chainnetwork_idis1). Thenetwork_idfield returned byaddress().info()is the genuine on-chain value; never map it back to aNetwork.
Errors
Section titled “Errors”pub struct MesmoError { pub code: i32, pub message: String } // Display: "Mesmo error {code}: {message}"pub type Result<T> = std::result::Result<T, MesmoError>;Error codes (mesmo::error_codes):
| Constant | Code | Meaning |
|---|---|---|
MESMO_ERROR_GENERAL | -1 | Unspecified failure (also: version mismatch, HTTP provider errors) |
MESMO_ERROR_INVALID_ARGUMENT | -2 | Bad argument (also: interior NUL in a string) |
MESMO_ERROR_SERIALIZATION | -3 | (De)serialization failure |
MESMO_ERROR_CRYPTO | -4 | Cryptographic failure |
MESMO_ERROR_INVALID_NETWORK | -5 | Bad network value |
MESMO_ERROR_INVALID_MNEMONIC | -6 | Bad mnemonic |
MESMO_ERROR_INVALID_ADDRESS | -7 | Bad address |
MESMO_ERROR_INSUFFICIENT_FUNDS | -8 | UTXOs can’t cover outputs + fee |
MESMO_ERROR_INVALID_TRANSACTION | -9 | Bad transaction |
MESMO_ERROR_TX_BUILD | -10 | TxPlan build failure (most common quicktx().build error — usually a malformed plan) |
MESMO_ERROR_INVALID_HANDLE | -11 | Unknown or closed account handle, or a Mesmo that was dropped |
Predicate methods (validate, validate_mnemonic, verify) return bool and never error.
lib.accounts() — managed accounts
Section titled “lib.accounts() — managed accounts”Handle-based accounts (ADR-0016): open once, then operate without the mnemonic — the only account API.
use mesmo::accounts::SigningRole;
let acct = lib.accounts().from_mnemonic(&mnemonic, Network::Testnet, 0, 0)?;let info = acct.info()?; // serde_json::Value — never the mnemoniclet signed = acct.sign_tx(&tx_cbor, SigningRole::PAYMENT | SigningRole::STAKE)?;// Drop closes the handle; acct.close() is the explicit, idempotent form- The
Accountis an owned value, not a borrow: it can live in the same struct as itsMesmo. Dropping theMesmohard-invalidates outstanding accounts — their calls fail withMESMO_ERROR_INVALID_HANDLE(-11), never by touching a dead isolate. Like theMesmo, anAccountis!Send. create(network)— fresh 24-word account; no secret in the result. Retrieve the phrase once, deliberately, withexport_recovery_phrase()— a second call fails, as does export on a mnemonic-opened account.sign_tx(&tx_cbor, roles)— typedSigningRolecombined with|; witnesses apply in canonical order. An empty mask is rejected.- The
Debugrepresentation shows only the handle. info()returns public data only: the base/enterprise/stake/change addresses, network and derivation indices,drep_id, and the committee identifiers (committee_cold_id/committee_hot_id, bech32, pluscommittee_cold_credential/committee_hot_credential— hex blake2b-224 verification-key hashes, as used in committee certificates).
An account is bound to one CIP-1852 payment leaf (m/1852'/1815'/account'/0/address_index): one handle, one payment address — open further accounts for further address indices. The stake/DRep/committee keys sit at their standard role indices independent of address_index, so accounts at different address indices of one account index share a single stake/DRep identity.
lib.address()
Section titled “lib.address()”pub fn info(&self, bech32: &str) -> Result<String>; // JSONpub fn validate(&self, bech32: &str) -> bool;pub fn to_bytes(&self, bech32: &str) -> Result<String>; // hexpub fn from_bytes(&self, hex_bytes: &str) -> Result<String>; // bech32info JSON fields: type ("Base", "Enterprise", "Pointer", "Reward"), network_id (on-chain: 1 = mainnet), payment_credential_hash, delegation_credential_hash, is_pubkey_payment, is_script_payment.
lib.crypto()
Section titled “lib.crypto()”pub fn blake2b_256(&self, data_hex: &str) -> Result<String>;pub fn blake2b_224(&self, data_hex: &str) -> Result<String>;pub fn generate_mnemonic(&self, word_count: i32) -> Result<String>; // 12 or 24pub fn validate_mnemonic(&self, mnemonic: &str) -> bool;pub fn sign(&self, message_hex: &str, sk_hex: &str) -> Result<String>; // Ed25519; 32-byte seed or 64-byte extended key (by length)pub fn verify(&self, signature_hex: &str, message_hex: &str, pk_hex: &str) -> bool;pub fn derive_key(&self, mnemonic: &str, account_index: i32, address_index: i32, role: &str) -> Result<String>;derive_key is the stateless CIP-1852 “raw key material” utility — role is one of "payment",
"change", "stake", "drep", "committee_cold", "committee_hot"; it returns the JSON
{"path","private_key","public_key","public_key_hash"}, plus — for the governance roles — the
CIP-105 bech32 encodings bech32_verification_key/bech32_verification_key_hash (what
cardano-cli and GovTool accept for registration). Key derivation is network-independent.
Prefer managed accounts for signing — handles never expose key bytes.
let digest = lib.crypto().blake2b_256("48656c6c6f")?; // "Hello"let key: serde_json::Value = serde_json::from_str(&lib.crypto().derive_key(&mnemonic, 0, 0, "payment")?)?;let sk = key["private_key"].as_str().unwrap();let sig = lib.crypto().sign(msg_hex, &sk)?; // pass the extended key wholelib.tx()
Section titled “lib.tx()”pub fn hash(&self, tx_cbor_hex: &str) -> Result<String>; // 64-hex tx idpub fn sign_with_secret_key(&self, tx_cbor_hex: &str, sk_cbor_hex: &str) -> Result<String>;pub fn to_json(&self, tx_cbor_hex: &str) -> Result<String>; // JSONpub fn from_json(&self, tx_json: &str) -> Result<String>; // CBOR hexpub fn deserialize(&self, tx_cbor_hex: &str) -> Result<String>; // JSONto_json/deserialize return JSON with a body object (inputs/outputs/fee). sign_with_secret_key expects a CBOR-encoded secret key, not raw key hex — for mnemonic-based accounts prefer account().sign_tx.
lib.plutus()
Section titled “lib.plutus()”pub fn data_hash(&self, datum_cbor_hex: &str) -> Result<String>; // 64 hex charspub fn data_to_json(&self, cbor_hex: &str) -> Result<String>;pub fn data_from_json(&self, json: &str) -> Result<String>; // CBOR hexlet hash = lib.plutus().data_hash("182a")?; // hash of PlutusData int 42lib.script()
Section titled “lib.script()”pub fn native_from_json(&self, json: &str) -> Result<String>; // JSON: { policy_id, script_hash, cbor_hex }pub fn hash(&self, script_cbor_hex: &str, script_type: i32) -> Result<String>; // 56 hex charsscript_type: 0 native, 1 PlutusV1, 2 PlutusV2, 3 PlutusV3.
let script_json = format!(r#"{{"type":"sig","keyHash":"{key_hash}"}}"#);let parsed: serde_json::Value = serde_json::from_str(&lib.script().native_from_json(&script_json)?)?;// parsed["policy_id"], parsed["script_hash"], parsed["cbor_hex"]Governance identity and HD-wallet flows
Section titled “Governance identity and HD-wallet flows”There is no separate gov/wallet API. Governance identity (DRep id, committee ids and
credentials) is public data on acct.info(); governance signing uses sign_tx with the
DREP/COMMITTEE_* roles; raw governance key material comes from crypto().derive_key.
An HD wallet is one recovery phrase with one managed handle per CIP-1852 payment leaf — pass
address_index to accounts().from_mnemonic to enumerate addresses.
lib.quicktx()
Section titled “lib.quicktx()”#[derive(Debug, serde::Deserialize)]pub struct TxResult { pub tx_cbor: String, pub tx_hash: String, pub fee: String }
pub fn build(&self, yaml: &str, utxos: &serde_json::Value, protocol_params: &serde_json::Value, exec_units: Option<&serde_json::Value>, additional_signers: u32) -> Result<TxResult>;
// with `--features providers`:pub fn build_with(&self, yaml: &str, provider: &dyn ChainDataProvider, senders: &[&str], additional_signers: u32, evaluator: Option<&dyn TransactionEvaluator>) -> Result<TxResult>;buildis fully offline: you describe the transaction as TxPlan YAML and supply the chain data yourself asserde_json::Values. UTXO selection, fee calculation, and change handling happen inside the native library. It never submits — sign the returnedtx_cborand submit with any HTTP client.utxosis a JSON array of CCLUtxoobjects:{tx_hash, output_index, address, amount: [{unit, quantity}]}.unitis"lovelace"orpolicyId + assetNameHex. Quantities are best passed as strings ("quantity": "5000000"), matching the canonical CCL model.protocol_paramsis the CCLProtocolParamsJSON model; unknown fields are ignored.exec_units— for Plutus transactions,Some(&json!([{"mem": ..., "steps": ...}])), one entry per redeemer in transaction order. PassNoneto let the native library compute them offline with the embedded Scalus evaluator.additional_signersbudgets vkey witnesses for fee estimation, beyond those the input UTXOs imply (one per sender). You know how many keys will sign:0for a plain payment,1for a stake or DRep certificate,2for both in one tx, the number ofsigkeys for a native-script spend, plus one per plan-level required signer. Undercounting yields a fee the node rejects withFeeTooSmallUTxO; overcounting only overpays (~4,400 lovelace per extra witness).build_withfetches each sender’s UTXOs from a provider — merged and de-duplicated by(tx_hash, output_index)— plus protocol parameters, then builds. With multiple senders, TxPlan’scontext.fee_payerdecides who pays the fee. With an evaluator it runs two passes: draft build → remote evaluation → rebuild with the returned units.
use serde_json::json;
let result = lib.quicktx().build(&yaml, &utxos, ¶ms, None)?;
let plutus = lib.quicktx().build(&yaml, &utxos, ¶ms, Some(&json!([{"mem": 2000000, "steps": 500000000}])))?;