Skip to main content

seedelf_core/
build.rs

1//! Network-free transaction building, shared by the CLI and the web wallet.
2//!
3//! Each builder takes chain data the caller has already fetched (protocol
4//! parameters, and UTxOs as Koios returns them) and returns an unsigned
5//! transaction. Callers make the network calls and sign: the CLI with its
6//! file-stored key, the web wallet inside WebAssembly with its HD keys.
7
8use std::collections::{BTreeMap, BTreeSet};
9use std::fmt;
10
11use anyhow::{Context, Result, bail};
12use pallas_addresses::Address;
13use pallas_codec::minicbor;
14use pallas_crypto::hash::{Hash, Hasher};
15use pallas_crypto::key::ed25519::{PublicKey, SecretKey, Signature};
16use pallas_primitives::{Fragment, NonEmptySet, conway};
17use pallas_txbuilder::{
18    BuildConway, BuiltTransaction, ExUnits, Input, Output, ScriptKind, StagingTransaction,
19};
20use pallas_wallet::PrivateKey;
21use rand_core::OsRng;
22use seedelf_crypto::register::Register;
23use seedelf_koios::koios::{ProtocolParameters, UtxoResponse, extract_bytes_with_logging};
24use serde_json::Value;
25
26use crate::address::{collateral_address, is_not_a_script, is_on_correct_network, wallet_contract};
27use crate::assets::{Asset, Assets};
28use crate::constants::{COLLATERAL_HASH, COLLATERAL_PUBLIC_KEY, Config, MAXIMUM_TOKENS_PER_UTXO};
29use crate::data_structures::{create_mint_redeemer, create_spend_redeemer};
30use crate::note::Note;
31use crate::staking::Staking;
32use crate::transaction::{
33    address_minimum_lovelace_with_assets, checked_lovelace, collateral_input, computation_fee,
34    decode_tx_hash, reference_utxo, seedelf_minimum_lovelace, seedelf_token_name,
35    wallet_minimum_lovelace_with_assets,
36};
37use crate::utxos::{assets_of, fitting, reference_script_bytes, reference_script_size};
38
39/// A throwaway ed25519 key, used to sign a draft so its size includes a
40/// realistic witness. The signature is discarded.
41pub fn fake_signer() -> PrivateKey {
42    PrivateKey::from(SecretKey::new(OsRng))
43}
44
45/// The linear (size-based) fee for a transaction of `tx_size` bytes:
46/// `min_fee_a × size + min_fee_b`, as the ledger charges it. (Pallas's
47/// `fees::PolicyParams::default()` is Byron's policy, 43.946 lovelace a byte,
48/// which falls a few dozen lovelace short on a Seedelf spend.)
49pub fn linear_fee(params: &ProtocolParameters, tx_size: u64) -> u64 {
50    params
51        .min_fee_a
52        .saturating_mul(tx_size)
53        .saturating_add(params.min_fee_b)
54}
55
56/// The most reference-script bytes one transaction may use, over its spent
57/// and reference inputs together: the ledger's `maxRefScriptSizePerTx`,
58/// 200 KiB.
59pub const MAX_REFERENCE_SCRIPT_BYTES: u64 = 204_800;
60
61/// How many bytes of reference script each of Conway's price tiers holds.
62const REFERENCE_SCRIPT_TIER: u64 = 25_600;
63
64/// Conway's fee for `bytes` of reference scripts, which the ledger adds for
65/// every script on a spent or reference input: the first 25,600 bytes at
66/// `min_fee_ref_script_cost_per_byte`, each next 25,600 at 1.2 times the
67/// price of the ones before, and the total rounded down (the ledger's
68/// `tierRefScriptFee`, in exact fractions as it works). More than
69/// [`MAX_REFERENCE_SCRIPT_BYTES`] is refused, in words, as the ledger would.
70pub fn reference_script_fee(params: &ProtocolParameters, bytes: u64) -> Result<u64> {
71    if bytes > MAX_REFERENCE_SCRIPT_BYTES {
72        bail!(
73            "This transaction would use {bytes} bytes of reference scripts, over the network's limit of {MAX_REFERENCE_SCRIPT_BYTES}. Spend fewer UTxOs holding one at once"
74        );
75    }
76    let price = params.min_fee_ref_script_cost_per_byte;
77    if price.denominator == 0 {
78        bail!("The network's reference script price isn't a number");
79    }
80    // Tier k costs price × (6/5)^k a byte. Over the common denominator
81    // 5^tiers, each full tier adds TIER × 6^k × 5^(tiers−k), and the bytes
82    // past the last full tier add rest × 6^tiers. With at most 8 tiers, every
83    // step fits a u128.
84    let tiers = (bytes / REFERENCE_SCRIPT_TIER) as u32;
85    let rest = u128::from(bytes % REFERENCE_SCRIPT_TIER);
86    let tier = u128::from(REFERENCE_SCRIPT_TIER);
87    let scaled: u128 = (0..tiers)
88        .map(|k| tier * 6u128.pow(k) * 5u128.pow(tiers - k))
89        .sum::<u128>()
90        + rest * 6u128.pow(tiers);
91    let fee =
92        scaled * u128::from(price.numerator) / (u128::from(price.denominator) * 5u128.pow(tiers));
93    u64::try_from(fee).context("The reference scripts' fee doesn't fit a number")
94}
95
96/// The most a transaction the wallet builds may pay in fees. Real ones pay
97/// under 1 ₳, and even a 16 KiB transaction running scripts to the ledger's
98/// limit pays about 2.6 ₳. More comes from wrong protocol parameters, and the
99/// ledger would take it all.
100pub const MAX_FEE: u64 = 10_000_000;
101
102/// Refuses a fee over [`MAX_FEE`], in words.
103pub fn check_fee(fee: u64) -> Result<()> {
104    if fee > MAX_FEE {
105        bail!(
106            "This transaction's fee would be {} ADA, over the wallet's limit of {} ADA, so it wasn't built. The network's fee settings look wrong: try again later",
107            ada(fee),
108            ada(MAX_FEE)
109        );
110    }
111    Ok(())
112}
113
114/// Settles the size fee of a key-signed transaction. `build(fee)` stages the
115/// transaction for a given fee; each draft is signed with `signers` throwaway
116/// keys and priced, until the fee covers the transaction it produces.
117/// Returns the fee and the transaction staged with it.
118pub fn settle_fee(
119    params: &ProtocolParameters,
120    signers: usize,
121    build: impl FnMut(u64) -> Result<StagingTransaction>,
122) -> Result<(u64, StagingTransaction)> {
123    settle(
124        signers,
125        Patches::staking(&Staking::none()),
126        |size| linear_fee(params, size),
127        build,
128    )
129}
130
131/// The largest transaction the ledger takes, signed: `max_tx_size`, 16 KiB on
132/// mainnet and preprod. A bigger one is refused at submit.
133pub const MAX_TX_SIZE: u64 = 16_384;
134
135/// What's patched into a built transaction, since Pallas can stage neither:
136/// staking (certificates, a withdrawal; see [`Staking::patch`]) and a note
137/// ([`Note::patch`]).
138#[derive(Clone, Copy)]
139struct Patches<'a> {
140    staking: &'a Staking,
141    note: Option<&'a Note>,
142}
143
144impl<'a> Patches<'a> {
145    /// Only `staking`: every transaction but a send with a note.
146    fn staking(staking: &'a Staking) -> Self {
147        Patches {
148            staking,
149            note: None,
150        }
151    }
152
153    fn apply(&self, built: BuiltTransaction) -> Result<BuiltTransaction> {
154        let built = self.staking.patch(built)?;
155        match self.note {
156            Some(note) => note.patch(built),
157            None => Ok(built),
158        }
159    }
160}
161
162/// [`settle_fee`] with any pricing: `price(size)` is the fee a transaction of
163/// `size` signed bytes needs. `patches` go into each draft before it's
164/// priced; `signers` doesn't count the stake key a staking patch needs.
165/// A draft over [`MAX_TX_SIZE`], or a fee over [`MAX_FEE`], is refused here,
166/// in words.
167fn settle(
168    signers: usize,
169    patches: Patches,
170    price: impl Fn(u64) -> u64,
171    mut build: impl FnMut(u64) -> Result<StagingTransaction>,
172) -> Result<(u64, StagingTransaction)> {
173    let mut fee: u64 = 200_000;
174    for _ in 0..5 {
175        let staged = build(fee)?;
176        let size = signed_size(&staged, signers, patches)?;
177        if size > MAX_TX_SIZE {
178            bail!(
179                "This transaction would be {size} bytes, over the network's limit of {MAX_TX_SIZE}. Send fewer tokens, or pay fewer recipients, at once"
180            );
181        }
182        let needed = price(size);
183        check_fee(needed)?;
184        if needed <= fee && fee - needed < 1_000 {
185            return Ok((fee, staged));
186        }
187        // Too low, or far above what's needed (the first guess): price again.
188        fee = needed;
189    }
190    bail!("The transaction fee did not settle")
191}
192
193fn signed_size(staged: &StagingTransaction, signers: usize, patches: Patches) -> Result<u64> {
194    let mut built = built_with(staged.clone(), patches)?;
195    for _ in 0..signers.max(1) + patches.staking.signers() {
196        built = built
197            .sign(fake_signer())
198            .context("Failed To Sign The Draft Transaction")?;
199    }
200    Ok(built.tx_bytes.0.len() as u64)
201}
202
203/// Builds a staged transaction, then patches `patches` into it.
204fn built_with(staged: StagingTransaction, patches: Patches) -> Result<BuiltTransaction> {
205    patches.apply(
206        staged
207            .build_conway_raw()
208            .context("Failed To Build The Transaction")?,
209    )
210}
211
212/// `tx`, valid until slot `invalid_hereafter` when one is given: the ledger
213/// refuses it from that slot on, so one that never landed can't land later.
214/// Staged, not patched, so every draft is priced with it and the staking and
215/// note patches hash a body that holds it.
216fn expiring(tx: StagingTransaction, invalid_hereafter: Option<u64>) -> StagingTransaction {
217    match invalid_hereafter {
218        Some(slot) => tx.invalid_from_slot(slot),
219        None => tx,
220    }
221}
222
223/// The transaction input for a Koios UTxO.
224pub fn input_of(utxo: &UtxoResponse) -> Result<Input> {
225    Ok(Input::new(
226        Hash::new(decode_tx_hash(&utxo.tx_hash)?),
227        utxo.tx_index,
228    ))
229}
230
231/// A wallet-contract output holding `lovelace` and `tokens` under a fresh
232/// re-randomization of `owner` (the same `d` on both points).
233fn deposit_output(
234    wallet_addr: &Address,
235    owner: &Register,
236    lovelace: u64,
237    tokens: &Assets,
238) -> Result<Output> {
239    let datum: Vec<u8> = owner
240        .clone()
241        .rerandomize()
242        .context("Failed To Randomize Points")?
243        .to_vec()?;
244    let mut output = Output::new(wallet_addr.clone(), lovelace).set_inline_datum(datum);
245    for asset in &tokens.items {
246        output = output
247            .add_asset(asset.policy_id, asset.token_name.clone(), asset.amount)
248            .context("Failed To Add An Asset")?;
249    }
250    Ok(output)
251}
252
253/// Outputs that deposit `lovelace` and `tokens` into the wallet contract,
254/// each under a fresh re-randomization of `owner`. Tokens go
255/// `MAXIMUM_TOKENS_PER_UTXO` to an output; every output but the last carries
256/// its minimum, and the last carries the rest.
257pub fn deposit_outputs(
258    params: &ProtocolParameters,
259    wallet_addr: &Address,
260    owner: &Register,
261    lovelace: u64,
262    tokens: &Assets,
263) -> Result<Vec<Output>> {
264    let chunks: Vec<Assets> = if tokens.items.is_empty() {
265        vec![Assets::new()]
266    } else {
267        tokens.split(MAXIMUM_TOKENS_PER_UTXO as usize)
268    };
269    let last = chunks.len() - 1;
270    let mut remaining = lovelace;
271    let mut outputs = Vec::with_capacity(chunks.len());
272    for (i, chunk) in chunks.iter().enumerate() {
273        let minimum = wallet_minimum_lovelace_with_assets(params, chunk.clone())?;
274        let amount = if i == last { remaining } else { minimum };
275        if amount < minimum || remaining < amount {
276            bail!(TooLittle {
277                what: "Seedelf",
278                needed: minimum_deposit(params, tokens)?,
279            });
280        }
281        remaining -= amount;
282        outputs.push(deposit_output(wallet_addr, owner, amount, chunk)?);
283    }
284    Ok(outputs)
285}
286
287/// The least lovelace a deposit of `tokens` needs: the sum of each deposit
288/// output's minimum.
289pub fn minimum_deposit(params: &ProtocolParameters, tokens: &Assets) -> Result<u64> {
290    if tokens.items.is_empty() {
291        return wallet_minimum_lovelace_with_assets(params, Assets::new());
292    }
293    tokens
294        .split(MAXIMUM_TOKENS_PER_UTXO as usize)
295        .into_iter()
296        .map(|chunk| wallet_minimum_lovelace_with_assets(params, chunk))
297        .sum()
298}
299
300/// Change outputs to a key address: `tokens` split `MAXIMUM_TOKENS_PER_UTXO`
301/// to an output, the last output carrying the rest of `lovelace`. No output at
302/// all when there's nothing to return. `short` is the error when the
303/// lovelace can't carry the tokens.
304fn change_outputs(
305    params: &ProtocolParameters,
306    change_addr: &Address,
307    lovelace: u64,
308    tokens: &Assets,
309    short: NotEnough,
310) -> Result<Vec<Output>> {
311    if lovelace == 0 && tokens.items.is_empty() {
312        return Ok(Vec::new());
313    }
314    let bech32 = change_addr
315        .to_bech32()
316        .map_err(|e| anyhow::anyhow!("Failed to encode the change address: {e}"))?;
317    let chunks: Vec<Assets> = if tokens.items.is_empty() {
318        vec![Assets::new()]
319    } else {
320        tokens.split(MAXIMUM_TOKENS_PER_UTXO as usize)
321    };
322    let last = chunks.len() - 1;
323    let mut remaining = lovelace;
324    let mut outputs = Vec::with_capacity(chunks.len());
325    for (i, chunk) in chunks.iter().enumerate() {
326        let minimum = address_minimum_lovelace_with_assets(params, &bech32, chunk.clone())?;
327        let amount = if i == last { remaining } else { minimum };
328        if amount < minimum || remaining < amount {
329            bail!(short);
330        }
331        remaining -= amount;
332        let mut output = Output::new(change_addr.clone(), amount);
333        for asset in &chunk.items {
334            output = output
335                .add_asset(asset.policy_id, asset.token_name.clone(), asset.amount)
336                .context("Failed To Add An Asset")?;
337        }
338        outputs.push(output);
339    }
340    Ok(outputs)
341}
342
343/// The least lovelace change outputs holding `tokens` need.
344fn minimum_change(
345    params: &ProtocolParameters,
346    change_addr: &Address,
347    tokens: &Assets,
348) -> Result<u64> {
349    if tokens.items.is_empty() {
350        return Ok(0);
351    }
352    let bech32 = change_addr
353        .to_bech32()
354        .map_err(|e| anyhow::anyhow!("Failed to encode the change address: {e}"))?;
355    tokens
356        .split(MAXIMUM_TOKENS_PER_UTXO as usize)
357        .into_iter()
358        .map(|chunk| address_minimum_lovelace_with_assets(params, &bech32, chunk))
359        .sum()
360}
361
362/// The chosen inputs can't pay for the transaction; more inputs might.
363#[derive(Debug, Clone, Copy)]
364struct NotEnough(&'static str);
365
366/// A move-in's inputs can't pay for it.
367const MOVE_IN_SHORT: NotEnough =
368    NotEnough("Not enough ADA in the Cardano account for this move, its fee and the change");
369
370/// An account payment's inputs can't pay for it.
371const SEND_SHORT: NotEnough =
372    NotEnough("Not enough ADA in the Cardano account for this payment, its fee and the change");
373
374/// A staking transaction's inputs can't pay for it.
375const STAKE_SHORT: NotEnough =
376    NotEnough("Not enough ADA in the Cardano account for this, its fee and the change");
377
378/// An account-paid mint's inputs can't pay for it.
379const ACCOUNT_MINT_SHORT: NotEnough =
380    NotEnough("Not enough ADA in the Cardano account for the Seedelf, its fee and the change");
381
382/// A script spend's inputs can't pay for it.
383const SEEDELF_SHORT: NotEnough =
384    NotEnough("Not enough ADA in the Seedelf balance for this, its fee and the change");
385
386impl fmt::Display for NotEnough {
387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388        f.write_str(self.0)
389    }
390}
391
392impl std::error::Error for NotEnough {}
393
394/// The amount asked for is below what the outputs must carry.
395#[derive(Debug)]
396struct TooLittle {
397    what: &'static str,
398    needed: u64,
399}
400
401impl fmt::Display for TooLittle {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        write!(
404            f,
405            "A {} deposit of these tokens needs at least {} ADA",
406            self.what,
407            ada(self.needed)
408        )
409    }
410}
411
412impl std::error::Error for TooLittle {}
413
414/// `external sweep`: every UTxO at the CLI's dApp address back into the
415/// wallet contract, under fresh re-randomizations of `owner`. `signer` is the
416/// dApp key's hash, disclosed as a required signer. The fee pays for any
417/// reference script they hold. Returns the unsigned transaction and its fee.
418pub fn external_sweep(
419    params: &ProtocolParameters,
420    utxos: &[UtxoResponse],
421    owner: &Register,
422    wallet_addr: &Address,
423    signer: Hash<28>,
424) -> Result<(BuiltTransaction, u64)> {
425    if utxos.is_empty() {
426        bail!("Not Enough Lovelace/Tokens");
427    }
428    let (total, tokens) = assets_of(utxos.to_vec())?;
429    let inputs: Vec<Input> = utxos.iter().map(input_of).collect::<Result<_>>()?;
430    // Conway charges for a reference script on a spent input too.
431    let script_fee = reference_script_fee(params, reference_script_bytes(utxos)?)?;
432    let price = |size| linear_fee(params, size).saturating_add(script_fee);
433    let (fee, staged) = settle(1, Patches::staking(&Staking::none()), price, |fee| {
434        let lovelace = checked_lovelace(total, &[fee])?;
435        let mut tx = StagingTransaction::new();
436        for input in &inputs {
437            tx = tx.input(input.clone());
438        }
439        for output in deposit_outputs(params, wallet_addr, owner, lovelace, &tokens)? {
440            tx = tx.output(output);
441        }
442        Ok(tx.fee(fee).disclosed_signer(signer))
443    })?;
444    let tx = staged
445        .build_conway_raw()
446        .context("Failed To Build The Transaction")?;
447    Ok((tx, fee))
448}
449
450/// How much ADA the Cardano account pays, into Seedelf or to an address.
451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
452pub enum AccountAmount {
453    Lovelace(u64),
454    /// Everything that can go: all ADA but the fee and the minimum the
455    /// change needs to carry the tokens that stay.
456    Max,
457}
458
459/// Where the Cardano account pays.
460#[derive(Clone, Copy)]
461pub enum Payee<'a> {
462    /// Into the wallet contract, under fresh re-randomizations of `owner`:
463    /// the user's base register (a move-in), or the register of the contract
464    /// UTxO holding someone's seedelf, which must be [`is_payable`]: only its
465    /// owner can spend the payment, and nothing on chain ties it to their
466    /// seedelf. Tokens go `MAXIMUM_TOKENS_PER_UTXO` to an output.
467    Seedelf {
468        owner: &'a Register,
469        wallet_addr: &'a Address,
470    },
471    /// A key address, in one output.
472    Address(&'a Address),
473    /// No one: the transaction only does something with the stake key
474    /// (certificates, a withdrawal), and everything comes back as change.
475    Nobody,
476}
477
478impl Payee<'_> {
479    /// The least lovelace a payment of `tokens` here must carry.
480    pub fn minimum(&self, params: &ProtocolParameters, tokens: &Assets) -> Result<u64> {
481        match self {
482            Payee::Seedelf { .. } => minimum_deposit(params, tokens),
483            Payee::Address(to) => minimum_address_payment(params, to, tokens),
484            Payee::Nobody => Ok(0),
485        }
486    }
487
488    fn outputs(
489        &self,
490        params: &ProtocolParameters,
491        lovelace: u64,
492        tokens: &Assets,
493    ) -> Result<Vec<Output>> {
494        match self {
495            Payee::Seedelf { owner, wallet_addr } => {
496                deposit_outputs(params, wallet_addr, owner, lovelace, tokens)
497            }
498            Payee::Address(to) => Ok(vec![pay_address(params, to, lovelace, tokens)?]),
499            Payee::Nobody if lovelace == 0 && tokens.items.is_empty() => Ok(Vec::new()),
500            Payee::Nobody => bail!("A transaction that pays no one pays nothing"),
501        }
502    }
503}
504
505/// One recipient of a payment from the Cardano account: where it goes, how
506/// much ADA, and which tokens, as `(policy, name, quantity)` in hex.
507#[derive(Clone)]
508pub struct AccountPay<'a> {
509    pub payee: Payee<'a>,
510    pub amount: AccountAmount,
511    pub picked: Vec<(String, String, u64)>,
512}
513
514impl<'a> AccountPay<'a> {
515    pub fn new(payee: Payee<'a>, amount: AccountAmount, picked: &[(String, String, u64)]) -> Self {
516        AccountPay {
517            payee,
518            amount,
519            picked: picked.to_vec(),
520        }
521    }
522}
523
524/// A built payment from the Cardano account (a move-in, a send, or a staking
525/// transaction): the unsigned transaction and what it does.
526pub struct AccountPayment {
527    /// With its staking patched in: needs the stake key's signature too when
528    /// there was any.
529    pub tx: BuiltTransaction,
530    /// The spent UTxOs, in input order; each needs its payment key's signature.
531    pub inputs: Vec<UtxoResponse>,
532    pub fee: u64,
533    /// Lovelace and tokens paid, all recipients together.
534    pub lovelace: u64,
535    pub tokens: Assets,
536    /// The lovelace each recipient got, in order.
537    pub paid: Vec<u64>,
538    /// How many outputs hold them.
539    pub outputs: usize,
540    /// What goes back to the Cardano account.
541    pub change_lovelace: u64,
542    pub change_tokens: Assets,
543    /// What Max couldn't spend with the rest, in the order given: see
544    /// [`fitting`] and [`reference_script_size`]. Empty for an amount.
545    pub left_out: Vec<UtxoResponse>,
546}
547
548/// Move-in: the Cardano account pays into the wallet contract under fresh
549/// re-randomizations of `owner`, the user's base register. No script runs.
550///
551/// - `available` is what may be spent: key addresses the caller can sign
552///   for. Every one of them can be; the caller leaves out what it keeps (the
553///   web wallet's collateral and the UTxOs its user locked).
554/// - `picked` tokens go in the quantities asked, as `(policy, name,
555///   quantity)` in hex. Every UTxO holding one is spent, and what's left of
556///   it goes back with the change.
557/// - Otherwise pure-ADA UTxOs are spent first, largest first, then other
558///   token UTxOs, until the amount, the fee and valid change are covered.
559///   Tokens that aren't picked go back with the change.
560/// - A UTxO whose tokens would push a total past what one output holds is
561///   never spent with the rest ([`fitting`]), nor is one holding a reference
562///   script that can't be measured ([`reference_script_size`]); Max says
563///   which it left out. The fee pays for every spent input's reference
564///   script, as Conway charges.
565/// - Change goes to `change_addr`. There is no change output when nothing is left.
566/// - `staking` rides along: a reward withdrawal adds to what pays
567///   ([`Staking::withdraw`]), and it's patched into the transaction.
568/// - `invalid_hereafter` is the slot the transaction stops being valid at
569///   ([`crate::eval::slot_at`] gives one from the time): past it, one that
570///   never landed can't land any more, so a payment made again can't pay
571///   twice. `None` leaves it valid for as long as its inputs are unspent.
572#[allow(clippy::too_many_arguments)]
573pub fn move_in(
574    params: &ProtocolParameters,
575    available: &[UtxoResponse],
576    amount: AccountAmount,
577    picked: &[(String, String, u64)],
578    owner: &Register,
579    wallet_addr: &Address,
580    change_addr: &Address,
581    staking: &Staking,
582    invalid_hereafter: Option<u64>,
583) -> Result<AccountPayment> {
584    let payee = Payee::Seedelf { owner, wallet_addr };
585    account_payment(
586        params,
587        available,
588        &[AccountPay::new(payee, amount, picked)],
589        Patches::staking(staking),
590        change_addr,
591        MOVE_IN_SHORT,
592        invalid_hereafter,
593    )
594}
595
596/// Send: the Cardano account pays `to`, a key address on this network
597/// (`network_flag`: `true` is preprod), in one output. The UTxOs are chosen,
598/// the change made, and `staking` and `invalid_hereafter` carried, as for
599/// [`move_in`].
600#[allow(clippy::too_many_arguments)]
601pub fn account_send(
602    params: &ProtocolParameters,
603    available: &[UtxoResponse],
604    amount: AccountAmount,
605    picked: &[(String, String, u64)],
606    to: &Address,
607    network_flag: bool,
608    change_addr: &Address,
609    staking: &Staking,
610    invalid_hereafter: Option<u64>,
611) -> Result<AccountPayment> {
612    let payee = Payee::Address(to);
613    account_send_many(
614        params,
615        available,
616        &[AccountPay::new(payee, amount, picked)],
617        network_flag,
618        change_addr,
619        staking,
620        None,
621        invalid_hereafter,
622    )
623}
624
625/// Send to several: the Cardano account pays each of `recipients`, in the
626/// order given, then the change. Each is a key address on this network or a
627/// Seedelf (see [`Payee::Seedelf`]), with its own amount and tokens. Max pays a
628/// single recipient. The UTxOs are chosen, the change made, and `staking`
629/// and `invalid_hereafter` carried, as for [`move_in`]. A `note` goes on the
630/// transaction as CIP-20's message, which anyone can read; the fee pays for
631/// its bytes.
632#[allow(clippy::too_many_arguments)]
633pub fn account_send_many(
634    params: &ProtocolParameters,
635    available: &[UtxoResponse],
636    recipients: &[AccountPay],
637    network_flag: bool,
638    change_addr: &Address,
639    staking: &Staking,
640    note: Option<&Note>,
641    invalid_hereafter: Option<u64>,
642) -> Result<AccountPayment> {
643    if recipients.is_empty() {
644        bail!("A payment needs someone to pay");
645    }
646    for pay in recipients {
647        match pay.payee {
648            Payee::Address(to) => check_payable(to, network_flag)?,
649            Payee::Seedelf { owner, .. } => check_register(owner)?,
650            Payee::Nobody => bail!("A payment needs someone to pay"),
651        }
652    }
653    account_payment(
654        params,
655        available,
656        recipients,
657        Patches { staking, note },
658        change_addr,
659        SEND_SHORT,
660        invalid_hereafter,
661    )
662}
663
664/// Refuses a register a payment would be lost under ([`is_payable`]).
665fn check_register(register: &Register) -> Result<()> {
666    if !is_payable(register) {
667        bail!(
668            "That Seedelf's register isn't valid: a payment to it could be locked for good, or taken by anyone"
669        );
670    }
671    Ok(())
672}
673
674/// A staking transaction from the Cardano account: `staking`'s certificates
675/// and withdrawal, and nothing paid to anyone. The inputs pay the fee and any
676/// deposit, and everything else comes back to `change_addr`; UTxOs are
677/// chosen as for [`move_in`], as few as pay. A withdrawal or a refund counts
678/// towards the fee, but a transaction always spends at least one UTxO.
679/// `invalid_hereafter` is as for [`move_in`].
680pub fn account_staking(
681    params: &ProtocolParameters,
682    available: &[UtxoResponse],
683    staking: &Staking,
684    change_addr: &Address,
685    invalid_hereafter: Option<u64>,
686) -> Result<AccountPayment> {
687    if staking.is_empty() {
688        bail!("A staking transaction needs a certificate, a withdrawal or a vote");
689    }
690    account_payment(
691        params,
692        available,
693        &[AccountPay::new(
694            Payee::Nobody,
695            AccountAmount::Lovelace(0),
696            &[],
697        )],
698        Patches::staking(staking),
699        change_addr,
700        STAKE_SHORT,
701        invalid_hereafter,
702    )
703}
704
705fn account_payment(
706    params: &ProtocolParameters,
707    available: &[UtxoResponse],
708    pays: &[AccountPay],
709    patches: Patches,
710    change_addr: &Address,
711    short: NotEnough,
712    invalid_hereafter: Option<u64>,
713) -> Result<AccountPayment> {
714    let max = pays.iter().any(|p| p.amount == AccountAmount::Max);
715    if max && pays.len() > 1 {
716        bail!("Max pays a single recipient: give each of several an amount");
717    }
718    // Every recipient's tokens together: what the inputs must hold.
719    let mut picked: BTreeMap<(&str, &str), u64> = BTreeMap::new();
720    for (policy, name, quantity) in pays.iter().flat_map(|p| &p.picked) {
721        if *quantity == 0 {
722            bail!("Choose more than none of the token {policy}.{name}, or leave it out");
723        }
724        let total = picked.entry((policy, name)).or_default();
725        *total = total.saturating_add(*quantity);
726    }
727    let holds_picked = |u: &UtxoResponse| {
728        u.asset_list.as_ref().is_some_and(|assets| {
729            assets
730                .iter()
731                .any(|a| picked.contains_key(&(a.policy_id.as_str(), a.asset_name.as_str())))
732        })
733    };
734    // A UTxO holding a reference script that can't be measured can't be
735    // priced, so it's never spent. Of the rest, what one transaction can hold
736    // together, the UTxOs with picked tokens first.
737    let priced: Vec<UtxoResponse> = available
738        .iter()
739        .filter(|u| reference_script_size(u).is_ok())
740        .cloned()
741        .collect();
742    let (eligible, _) = fitting(&priced, &Assets::new(), holds_picked)?;
743    let left_out: Vec<UtxoResponse> = available
744        .iter()
745        .filter(|u| !eligible.iter().any(|e| same_utxo(e, u)))
746        .cloned()
747        .collect();
748    for ((policy, name), quantity) in &picked {
749        let held: u64 = eligible
750            .iter()
751            .flat_map(|u| u.asset_list.iter().flatten())
752            .filter(|a| a.policy_id == *policy && a.asset_name == *name)
753            .map(|a| a.quantity.parse::<u64>().unwrap_or(0))
754            .sum();
755        if held == 0 {
756            bail!("The Cardano account doesn't hold the token {policy}.{name}");
757        }
758        if *quantity > held {
759            bail!("The Cardano account holds only {held} of the token {policy}.{name}");
760        }
761    }
762
763    let (mandatory, mut rest): (Vec<UtxoResponse>, Vec<UtxoResponse>) =
764        eligible.into_iter().partition(|u| holds_picked(u));
765    // Pure ADA first, then token UTxOs; within each, those holding a
766    // reference script (which costs more to spend) after the rest, and the
767    // largest first.
768    rest.sort_by_key(|u| {
769        let tokens = u.asset_list.as_ref().is_some_and(|a| !a.is_empty());
770        let lovelace = u.value.parse::<u64>().unwrap_or(0);
771        (
772            tokens,
773            u.reference_script.is_some(),
774            std::cmp::Reverse(lovelace),
775        )
776    });
777
778    let attempt = |selected: &[UtxoResponse]| {
779        build_account_payment(
780            params,
781            selected,
782            pays,
783            patches,
784            change_addr,
785            short,
786            invalid_hereafter,
787        )
788    };
789
790    if max {
791        let all: Vec<UtxoResponse> = mandatory.into_iter().chain(rest).collect();
792        if all.is_empty() {
793            bail!("There is nothing in the Cardano account to spend");
794        }
795        let mut built = attempt(&all)?;
796        built.left_out = left_out;
797        return Ok(built);
798    }
799
800    let mut last_error = None;
801    for k in 0..=rest.len() {
802        let selected: Vec<UtxoResponse> = mandatory
803            .iter()
804            .chain(rest.iter().take(k))
805            .cloned()
806            .collect();
807        if selected.is_empty() {
808            continue;
809        }
810        match attempt(&selected) {
811            Ok(built) => return Ok(built),
812            Err(e) if e.downcast_ref::<NotEnough>().is_some() => last_error = Some(e),
813            Err(e) => return Err(e),
814        }
815    }
816    Err(last_error.unwrap_or_else(|| short.into()))
817}
818
819fn build_account_payment(
820    params: &ProtocolParameters,
821    selected: &[UtxoResponse],
822    pays: &[AccountPay],
823    patches: Patches,
824    change_addr: &Address,
825    short: NotEnough,
826    invalid_hereafter: Option<u64>,
827) -> Result<AccountPayment> {
828    let (inputs_total, all_tokens) = assets_of(selected.to_vec())?;
829    // What pays: the inputs, plus rewards and a refund, less a deposit.
830    let total = patches.staking.net(inputs_total).ok_or(short)?;
831    // How much of a held token `picked` asks for (0 for tokens not picked).
832    let asked_in = |picked: &[(String, String, u64)], a: &Asset| -> u64 {
833        let policy = hex::encode(a.policy_id);
834        let name = hex::encode(&a.token_name);
835        picked
836            .iter()
837            .filter(|(p, n, _)| *p == policy && *n == name)
838            .map(|(_, _, q)| *q)
839            .fold(0u64, u64::saturating_add)
840    };
841    let tokens_for = |picked: &[(String, String, u64)]| Assets {
842        items: all_tokens
843            .items
844            .iter()
845            .filter(|a| asked_in(picked, a) > 0)
846            .map(|a| Asset {
847                amount: asked_in(picked, a).min(a.amount),
848                ..a.clone()
849            })
850            .collect(),
851    };
852    let each_tokens: Vec<Assets> = pays.iter().map(|p| tokens_for(&p.picked)).collect();
853    let all_picked: Vec<(String, String, u64)> =
854        pays.iter().flat_map(|p| p.picked.iter().cloned()).collect();
855    let asked = |a: &Asset| asked_in(&all_picked, a).min(a.amount);
856    let paying = tokens_for(&all_picked);
857    let staying = Assets {
858        items: all_tokens
859            .items
860            .iter()
861            .filter(|a| a.amount > asked(a))
862            .map(|a| Asset {
863                amount: a.amount - asked(a),
864                ..a.clone()
865            })
866            .collect(),
867    };
868    let inputs: Vec<Input> = selected.iter().map(input_of).collect::<Result<_>>()?;
869    let signers = selected
870        .iter()
871        .map(|u| u.payment_cred.as_str())
872        .collect::<BTreeSet<_>>()
873        .len();
874    let change_floor = minimum_change(params, change_addr, &staying)?;
875    // Conway charges for a reference script on a spent input too.
876    let script_fee = reference_script_fee(params, reference_script_bytes(selected)?)?;
877
878    let mut paid: Vec<u64> = Vec::new();
879    let mut change = 0;
880    let mut outputs = 0;
881    let price = |size| linear_fee(params, size).saturating_add(script_fee);
882    let (fee, staged) = settle(signers, patches, price, |fee| {
883        (paid, change) = match pays {
884            [only] if only.amount == AccountAmount::Max => {
885                let all = total
886                    .checked_sub(fee)
887                    .and_then(|rest| rest.checked_sub(change_floor))
888                    .ok_or(short)?;
889                (vec![all], change_floor)
890            }
891            _ => {
892                let each: Vec<u64> = pays
893                    .iter()
894                    .map(|p| match p.amount {
895                        AccountAmount::Lovelace(lovelace) => lovelace,
896                        AccountAmount::Max => 0,
897                    })
898                    .collect();
899                let change = each
900                    .iter()
901                    .try_fold(total, |rest, l| rest.checked_sub(*l))
902                    .and_then(|rest| rest.checked_sub(fee))
903                    .ok_or(short)?;
904                (each, change)
905            }
906        };
907        let mut tx = StagingTransaction::new();
908        for input in &inputs {
909            tx = tx.input(input.clone());
910        }
911        outputs = 0;
912        for ((pay, lovelace), tokens) in pays.iter().zip(&paid).zip(&each_tokens) {
913            for output in pay.payee.outputs(params, *lovelace, tokens)? {
914                outputs += 1;
915                tx = tx.output(output);
916            }
917        }
918        for output in change_outputs(params, change_addr, change, &staying, short)? {
919            tx = tx.output(output);
920        }
921        Ok(expiring(tx.fee(fee), invalid_hereafter))
922    })?;
923
924    Ok(AccountPayment {
925        tx: built_with(staged, patches)?,
926        inputs: selected.to_vec(),
927        fee,
928        lovelace: paid.iter().sum(),
929        tokens: paying,
930        paid,
931        outputs,
932        change_lovelace: change,
933        change_tokens: staying,
934        left_out: Vec::new(),
935    })
936}
937
938// ---------------------------------------------------------------------------
939// Seedelf script spends
940//
941// Every spend of owned wallet-contract UTxOs has the same shape: each input is
942// unlocked by a Schnorr proof bound to a one-time key, collateral comes from
943// giveme.my, and the scripts are read from reference inputs. It's built in two
944// phases, because the scripts' execution budgets are only known after Ogmios
945// has evaluated a draft:
946//
947//   ScriptSpend::new → .output(…) / .mint(…) → .proven(prove)
948//     .draft()             placeholder budgets, for Ogmios to evaluate
949//     .finalize(budgets)   the real budgets and fee: the unsigned transaction
950//
951// The caller signs it with the one-time key and giveme.my's witness. Mint,
952// transfer, sweep and remove are built on it here.
953// ---------------------------------------------------------------------------
954
955/// What one script may use: memory units and CPU steps.
956#[derive(Debug, Clone, Copy, PartialEq, Eq)]
957pub struct Budget {
958    pub mem: u64,
959    pub steps: u64,
960}
961
962/// Each redeemer's budget in a draft: the most a transaction may use, so
963/// evaluation never runs short. Ogmios reports what each script really used.
964pub const DRAFT_BUDGET: Budget = Budget {
965    mem: 14_000_000,
966    steps: 10_000_000_000,
967};
968
969/// The most execution a transaction may use: `max_tx_ex_mem` and
970/// `max_tx_ex_steps` as of epoch 290 on preprod. They have only gone up since
971/// (17.5M memory at epoch 315), so this errs on the safe side.
972pub const MAX_TX_BUDGET: Budget = Budget {
973    mem: 16_500_000,
974    steps: 10_000_000_000,
975};
976
977/// A guess at one wallet-contract spend's budget, used only to pick enough
978/// inputs before Ogmios has measured the real one. Ogmios measured 76,043
979/// memory and 337,845,799 steps on preprod (epoch 315); this is about 1.3×.
980pub const SPEND_BUDGET_GUESS: Budget = Budget {
981    mem: 100_000,
982    steps: 450_000_000,
983};
984
985/// A guess at the seedelf policy's budget, like [`SPEND_BUDGET_GUESS`].
986/// Measured: 72,836 memory and 21,396,182 steps.
987pub const MINT_BUDGET_GUESS: Budget = Budget {
988    mem: 100_000,
989    steps: 30_000_000,
990};
991
992/// The collateral giveme.my lends: one 5 ADA UTxO.
993pub const COLLATERAL_LOVELACE: u64 = 5_000_000;
994
995/// Rounds a fee up to an even number of lovelace, so the collateral's
996/// `3/2 × fee` is whole.
997pub fn even(fee: u64) -> u64 {
998    fee + fee % 2
999}
1000
1001/// The collateral return: 5 ADA − 3/2 × `fee` back to giveme.my. `fee` must
1002/// be even (see [`even`]). Errors when the fee is too large for the 5 ADA
1003/// collateral to cover.
1004pub fn collateral_output(addr: Address, fee: u64) -> Result<Output> {
1005    let collateral_return: u64 = COLLATERAL_LOVELACE
1006        .checked_sub(fee * 3 / 2)
1007        .context("transaction fee is too large for the 5 ADA collateral to cover")?;
1008    Ok(Output::new(addr, collateral_return))
1009}
1010
1011/// The collateral key's signature in giveme.my's answer, `{ "witness": hex }`:
1012/// the witness's last 64 bytes.
1013pub fn collateral_signature(answer: &Value) -> Result<[u8; 64]> {
1014    let witness = answer
1015        .get("witness")
1016        .and_then(Value::as_str)
1017        .filter(|w| w.len() >= 128)
1018        .with_context(|| format!("The collateral service answered unexpectedly: {answer}"))?
1019        .as_bytes();
1020    hex::decode(&witness[witness.len() - 128..])
1021        .context("The collateral service's witness isn't hex")?
1022        .try_into()
1023        .map_err(|_| anyhow::anyhow!("The collateral service's signature isn't 64 bytes"))
1024}
1025
1026/// Whether `signature` is giveme.my's collateral key signing `tx_hash`.
1027pub fn is_collateral_signature(tx_hash: &[u8], signature: &[u8; 64]) -> bool {
1028    PublicKey::from(COLLATERAL_PUBLIC_KEY).verify(tx_hash, &Signature::from(*signature))
1029}
1030
1031/// Adds vkey witnesses to a transaction kept as CBOR, leaving its body, and so
1032/// its hash, byte for byte as it was. For signing a script spend built at
1033/// review and sent later.
1034pub fn add_witnesses(tx_cbor: &[u8], witnesses: &[(PublicKey, Signature)]) -> Result<Vec<u8>> {
1035    let hash = tx_id(tx_cbor)?;
1036    let mut tx = conway::Tx::decode_fragment(tx_cbor)
1037        .map_err(|e| anyhow::anyhow!("The transaction to sign isn't a Conway transaction: {e}"))?;
1038    let mut vkeys: Vec<conway::VKeyWitness> = tx
1039        .transaction_witness_set
1040        .vkeywitness
1041        .map(|w| w.to_vec())
1042        .unwrap_or_default();
1043    for (key, signature) in witnesses {
1044        vkeys.push(conway::VKeyWitness {
1045            vkey: key.as_ref().to_vec().into(),
1046            signature: signature.as_ref().to_vec().into(),
1047        });
1048    }
1049    tx.transaction_witness_set.vkeywitness = NonEmptySet::from_vec(vkeys);
1050    let signed = tx
1051        .encode_fragment()
1052        .map_err(|e| anyhow::anyhow!("Failed to encode the signed transaction: {e}"))?;
1053    if tx_id(&signed)? != hash {
1054        bail!("Signing changed the transaction");
1055    }
1056    Ok(signed)
1057}
1058
1059/// A transaction's id: the hash of its body exactly as encoded (the first
1060/// element of the transaction's CBOR array).
1061pub fn tx_id(tx_cbor: &[u8]) -> Result<Hash<32>> {
1062    let mut decoder = minicbor::Decoder::new(tx_cbor);
1063    let body = decoder.array().and_then(|_| {
1064        let start = decoder.position();
1065        decoder.skip()?;
1066        Ok(start..decoder.position())
1067    });
1068    let body = body.map_err(|e| anyhow::anyhow!("The transaction isn't valid CBOR: {e}"))?;
1069    Ok(Hasher::<256>::hash(&tx_cbor[body]))
1070}
1071
1072/// The key hashes a Conway transaction needs signatures from.
1073pub fn required_signers(tx_cbor: &[u8]) -> Result<Vec<Hash<28>>> {
1074    let tx = conway::Tx::decode_fragment(tx_cbor)
1075        .map_err(|e| anyhow::anyhow!("The transaction isn't a Conway transaction: {e}"))?;
1076    Ok(tx
1077        .transaction_body
1078        .required_signers
1079        .map(|s| s.to_vec())
1080        .unwrap_or_default())
1081}
1082
1083/// The execution budgets measured by redeemer, by Ogmios or in the wallet
1084/// ([`crate::eval`]).
1085#[derive(Debug, Clone, Default, PartialEq, Eq)]
1086pub struct Budgets {
1087    spend: BTreeMap<u64, Budget>,
1088    mint: BTreeMap<u64, Budget>,
1089    withdraw: BTreeMap<u64, Budget>,
1090}
1091
1092impl Budgets {
1093    /// Reads Ogmios's answer to `evaluateTransaction` (JSON-RPC, Ogmios v6):
1094    /// a `result` list of `{ validator: { purpose, index }, budget: { memory,
1095    /// cpu } }`. Budgets are matched to redeemers by purpose and index, never
1096    /// by their order in the answer. An `error` answer becomes an error that
1097    /// says which script failed and why.
1098    pub fn from_ogmios(answer: &Value) -> Result<Self> {
1099        if let Some(error) = answer.get("error") {
1100            bail!(ogmios_failure(error));
1101        }
1102        let rows = answer
1103            .get("result")
1104            .and_then(Value::as_array)
1105            .with_context(|| format!("Ogmios answered without budgets: {answer}"))?;
1106        let mut budgets = Budgets::default();
1107        for row in rows {
1108            let purpose = row.pointer("/validator/purpose").and_then(Value::as_str);
1109            let index = row.pointer("/validator/index").and_then(Value::as_u64);
1110            let mem = row.pointer("/budget/memory").and_then(Value::as_u64);
1111            let steps = row.pointer("/budget/cpu").and_then(Value::as_u64);
1112            let (Some(purpose), Some(index), Some(mem), Some(steps)) = (purpose, index, mem, steps)
1113            else {
1114                bail!("Ogmios returned a budget the wallet can't read: {row}");
1115            };
1116            let budget = Budget { mem, steps };
1117            match purpose {
1118                "spend" => budgets.spend.insert(index, budget),
1119                "mint" => budgets.mint.insert(index, budget),
1120                "withdraw" => budgets.withdraw.insert(index, budget),
1121                other => {
1122                    bail!("Ogmios measured a {other} script, which this transaction doesn't have")
1123                }
1124            };
1125        }
1126        Ok(budgets)
1127    }
1128
1129    /// A guess for `spends` wallet-contract inputs and `mints` seedelf
1130    /// policies, before Ogmios has measured anything.
1131    fn guess(spends: &[u64], mints: usize) -> Self {
1132        Budgets {
1133            spend: spends.iter().map(|i| (*i, SPEND_BUDGET_GUESS)).collect(),
1134            mint: (0..mints as u64).map(|i| (i, MINT_BUDGET_GUESS)).collect(),
1135            withdraw: BTreeMap::new(),
1136        }
1137    }
1138
1139    pub fn spend(&self, index: u64) -> Option<Budget> {
1140        self.spend.get(&index).copied()
1141    }
1142
1143    pub fn mint(&self, index: u64) -> Option<Budget> {
1144        self.mint.get(&index).copied()
1145    }
1146
1147    /// A script withdrawal's budget (a withdraw-zero, such as Lovejoin's).
1148    pub fn withdraw(&self, index: u64) -> Option<Budget> {
1149        self.withdraw.get(&index).copied()
1150    }
1151
1152    /// Every budget raised by `percent`, rounded up: what to declare when the
1153    /// transaction measured will change a little before it's sent (a proof
1154    /// bound to the fee costs a hair more or less each time it's made).
1155    pub fn with_margin(&self, percent: u64) -> Self {
1156        let raise = |m: &BTreeMap<u64, Budget>| {
1157            m.iter()
1158                .map(|(i, b)| {
1159                    let up = |x: u64| x + (x * percent).div_ceil(100);
1160                    (
1161                        *i,
1162                        Budget {
1163                            mem: up(b.mem),
1164                            steps: up(b.steps),
1165                        },
1166                    )
1167                })
1168                .collect()
1169        };
1170        Budgets {
1171            spend: raise(&self.spend),
1172            mint: raise(&self.mint),
1173            withdraw: raise(&self.withdraw),
1174        }
1175    }
1176
1177    /// Whether every budget here is at least `used`'s, redeemer by redeemer.
1178    pub fn covers(&self, used: &Budgets) -> bool {
1179        let within = |ours: &BTreeMap<u64, Budget>, theirs: &BTreeMap<u64, Budget>| {
1180            theirs.iter().all(|(i, b)| {
1181                ours.get(i)
1182                    .is_some_and(|o| o.mem >= b.mem && o.steps >= b.steps)
1183            })
1184        };
1185        within(&self.spend, &used.spend)
1186            && within(&self.mint, &used.mint)
1187            && within(&self.withdraw, &used.withdraw)
1188    }
1189}
1190
1191/// Ogmios's `error` object in plain words. Script failures name the script
1192/// and keep the machine's last words and any traces; inputs Ogmios can't find
1193/// mean the wallet's view of the chain is out of date.
1194fn ogmios_failure(error: &Value) -> String {
1195    let message = error
1196        .get("message")
1197        .and_then(Value::as_str)
1198        .unwrap_or("no reason given");
1199    let items: Vec<&Value> = error
1200        .get("data")
1201        .and_then(Value::as_array)
1202        .into_iter()
1203        .flatten()
1204        .collect();
1205    // 3110: redeemers pointing at inputs Ogmios can't resolve; 3117: unknown inputs.
1206    let stale = error.get("code").and_then(Value::as_i64) == Some(3117)
1207        || items.iter().any(|item| {
1208            matches!(
1209                item.pointer("/error/code").and_then(Value::as_i64),
1210                Some(3110 | 3117)
1211            )
1212        });
1213    if stale {
1214        return "Some of the UTxOs this spends aren't on chain anymore. Refresh the balance and try again.".to_string();
1215    }
1216    let failures: Vec<String> = items
1217        .iter()
1218        .filter_map(|item| {
1219            let purpose = item.pointer("/validator/purpose").and_then(Value::as_str)?;
1220            let index = item.pointer("/validator/index").and_then(Value::as_u64)?;
1221            let what = match purpose {
1222                "spend" => format!("spending input {index}"),
1223                "mint" => "the Seedelf policy".to_string(),
1224                other => format!("{other} {index}"),
1225            };
1226            let reason = item
1227                .pointer("/error/data/validationError")
1228                .and_then(Value::as_str)
1229                .and_then(|e| e.lines().map(str::trim).rfind(|l| !l.is_empty()))
1230                .or_else(|| item.pointer("/error/message").and_then(Value::as_str))
1231                .unwrap_or("failed");
1232            let traces: Vec<&str> = item
1233                .pointer("/error/data/traces")
1234                .and_then(Value::as_array)
1235                .into_iter()
1236                .flatten()
1237                .filter_map(Value::as_str)
1238                .collect();
1239            Some(if traces.is_empty() {
1240                format!("{what}: {reason}")
1241            } else {
1242                format!("{what}: {reason}, traces: {}", traces.join("; "))
1243            })
1244        })
1245        .collect();
1246    // Seedelf's scripts only spend and mint; a withdraw-zero is Lovejoin's.
1247    let foreign = items.iter().any(|item| {
1248        !matches!(
1249            item.pointer("/validator/purpose").and_then(Value::as_str),
1250            Some("spend" | "mint") | None
1251        )
1252    });
1253    if failures.is_empty() {
1254        format!("Ogmios couldn't evaluate the transaction: {message}")
1255    } else if foreign {
1256        format!(
1257            "A script refused this transaction ({})",
1258            failures.join("; ")
1259        )
1260    } else {
1261        format!(
1262            "The Seedelf contract refused this transaction ({})",
1263            failures.join("; ")
1264        )
1265    }
1266}
1267
1268/// The fee of a script spend, by part. `total` is what the transaction pays:
1269/// the sum, rounded up to even.
1270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1271pub struct ScriptFee {
1272    /// The linear fee for the signed size.
1273    pub size: u64,
1274    /// Execution units, at the protocol's prices.
1275    pub compute: u64,
1276    /// Reference scripts: those read from reference inputs, and any on a
1277    /// spent input.
1278    pub script_reference: u64,
1279    pub total: u64,
1280}
1281
1282/// A finished script spend: the unsigned transaction and what it does.
1283pub struct FinalSpend {
1284    /// Needs the one-time key's signature and giveme.my's witness.
1285    pub tx: BuiltTransaction,
1286    pub fee: ScriptFee,
1287    /// Back into the wallet contract, under fresh registers.
1288    pub change_lovelace: u64,
1289    pub change_tokens: Assets,
1290    pub change_outputs: usize,
1291}
1292
1293/// What every script spend is built against.
1294#[derive(Clone)]
1295pub struct Chain {
1296    pub params: ProtocolParameters,
1297    /// The CLI's convention: `true` is preprod.
1298    pub network_flag: bool,
1299    /// The contract variant's hashes and reference UTxOs (`get_config`).
1300    pub config: Config,
1301}
1302
1303/// An owned wallet-contract UTxO being spent.
1304#[derive(Clone)]
1305struct OwnedInput {
1306    utxo: UtxoResponse,
1307    input: Input,
1308    register: Register,
1309}
1310
1311/// A token minted (or burned, when negative) under the seedelf policy.
1312#[derive(Clone)]
1313struct PolicyMint {
1314    policy: Hash<28>,
1315    token_name: Vec<u8>,
1316    amount: i64,
1317    redeemer: Vec<u8>,
1318}
1319
1320/// A key account whose UTxOs a Seedelf spend takes too, and whose own
1321/// collateral it puts up instead of giveme.my's: a private session bringing
1322/// its money back into the Seedelf UTxO its funding made. The account's key
1323/// signs for its UTxOs; the proofs stay bound to the spend's own one-time key
1324/// (privacy rule 1), which signs too. The account's key may sign what others
1325/// build (a site's session does), so a proof bound to it could be replayed
1326/// in a transaction someone else put in front of it.
1327#[derive(Clone)]
1328struct AccountPart {
1329    inputs: Vec<(UtxoResponse, Input)>,
1330    collateral: Input,
1331    collateral_lovelace: u64,
1332    collateral_addr: Address,
1333}
1334
1335/// A Seedelf script spend, before its execution budgets are known. See the
1336/// section comment above.
1337#[derive(Clone)]
1338pub struct ScriptSpend {
1339    chain: Chain,
1340    inputs: Vec<OwnedInput>,
1341    /// One spend redeemer per input, once proven.
1342    redeemers: Option<Vec<Vec<u8>>>,
1343    outputs: Vec<Output>,
1344    paid_lovelace: u64,
1345    paid_tokens: Assets,
1346    mint: Option<PolicyMint>,
1347    change_owner: Register,
1348    /// Where the change goes instead of the contract, if anywhere.
1349    change_addr: Option<Address>,
1350    signer: Hash<28>,
1351    /// A key account spent alongside, with its collateral ([`Self::with_account`]).
1352    account: Option<AccountPart>,
1353}
1354
1355impl ScriptSpend {
1356    /// Spends `inputs`, owned wallet-contract UTxOs (each datum is a
1357    /// register). What's left after the outputs and the fee goes back into
1358    /// the contract under fresh re-randomizations of `change_owner`. `signer`
1359    /// is the one-time key's hash: every proof is bound to it, and it must
1360    /// sign.
1361    pub fn new(
1362        chain: &Chain,
1363        inputs: &[UtxoResponse],
1364        change_owner: &Register,
1365        signer: Hash<28>,
1366    ) -> Result<Self> {
1367        if inputs.is_empty() {
1368            bail!("A Seedelf spend needs at least one UTxO to spend");
1369        }
1370        let inputs = inputs
1371            .iter()
1372            .map(|utxo| {
1373                let register =
1374                    extract_bytes_with_logging(&utxo.inline_datum).with_context(|| {
1375                        format!("UTxO {}#{} holds no register", utxo.tx_hash, utxo.tx_index)
1376                    })?;
1377                Ok(OwnedInput {
1378                    input: input_of(utxo)?,
1379                    utxo: utxo.clone(),
1380                    register,
1381                })
1382            })
1383            .collect::<Result<Vec<_>>>()?;
1384        Ok(ScriptSpend {
1385            chain: chain.clone(),
1386            inputs,
1387            redeemers: None,
1388            outputs: Vec::new(),
1389            paid_lovelace: 0,
1390            paid_tokens: Assets::new(),
1391            mint: None,
1392            change_owner: change_owner.clone(),
1393            change_addr: None,
1394            signer,
1395            account: None,
1396        })
1397    }
1398
1399    /// Spends `inputs` too, UTxOs under the key account's key `account`, and
1400    /// puts up `collateral`, an ADA-only UTxO of the same account, in place of
1401    /// giveme.my's (it may be one of `inputs`: the ledger takes a UTxO as
1402    /// both). The account's key and the one-time key sign; giveme.my doesn't.
1403    /// What the account holds goes with the change. `account` can't be the
1404    /// one-time key: the proofs must stay bound to a key used once.
1405    pub fn with_account(
1406        mut self,
1407        account: Hash<28>,
1408        inputs: &[UtxoResponse],
1409        collateral: &UtxoResponse,
1410    ) -> Result<Self> {
1411        if account == self.signer {
1412            bail!("The account's key can't also be the spend's one-time key");
1413        }
1414        let network_flag = self.chain.network_flag;
1415        let at_signer = |utxo: &UtxoResponse| -> Result<Address> {
1416            let addr = Address::from_bech32(&utxo.address).with_context(|| {
1417                format!(
1418                    "UTxO {}#{} has an unreadable address",
1419                    utxo.tx_hash, utxo.tx_index
1420                )
1421            })?;
1422            let key = match &addr {
1423                Address::Shelley(s) if is_on_correct_network(addr.clone(), network_flag) => {
1424                    match s.payment() {
1425                        pallas_addresses::ShelleyPaymentPart::Key(k) => Some(*k),
1426                        pallas_addresses::ShelleyPaymentPart::Script(_) => None,
1427                    }
1428                }
1429                _ => None,
1430            };
1431            if key != Some(account) {
1432                bail!(
1433                    "UTxO {}#{} isn't under the account's key",
1434                    utxo.tx_hash,
1435                    utxo.tx_index
1436                );
1437            }
1438            Ok(addr)
1439        };
1440        let mut taken = Vec::with_capacity(inputs.len());
1441        for utxo in inputs {
1442            at_signer(utxo)?;
1443            let input = input_of(utxo)?;
1444            if self.inputs.iter().any(|o| o.input == input)
1445                || taken
1446                    .iter()
1447                    .any(|(_, i): &(UtxoResponse, Input)| *i == input)
1448            {
1449                bail!("UTxO {}#{} is spent twice", utxo.tx_hash, utxo.tx_index);
1450            }
1451            taken.push((utxo.clone(), input));
1452        }
1453        let collateral_addr = at_signer(collateral)?;
1454        if collateral
1455            .asset_list
1456            .as_ref()
1457            .is_some_and(|a| !a.is_empty())
1458        {
1459            bail!("A collateral must hold ADA alone");
1460        }
1461        self.account = Some(AccountPart {
1462            inputs: taken,
1463            collateral: input_of(collateral)?,
1464            collateral_lovelace: collateral
1465                .value
1466                .parse()
1467                .context("The collateral's value isn't a number")?,
1468            collateral_addr,
1469        });
1470        Ok(self)
1471    }
1472
1473    /// Sends what's left after the outputs and the fee to `addr`, a key
1474    /// address, instead of back into the contract: everything, for sending
1475    /// the lot, or a removed seedelf's ADA. Tokens go 20 to an output.
1476    pub fn change_to(mut self, addr: &Address) -> Self {
1477        self.change_addr = Some(addr.clone());
1478        self
1479    }
1480
1481    /// Adds an output. `tokens` must be what the output holds.
1482    pub fn output(mut self, output: Output, tokens: &Assets) -> Result<Self> {
1483        self.paid_lovelace = self
1484            .paid_lovelace
1485            .checked_add(output.lovelace)
1486            .context("output lovelace overflow")?;
1487        self.paid_tokens = self.paid_tokens.merge(tokens.clone())?;
1488        self.outputs.push(output);
1489        Ok(self)
1490    }
1491
1492    /// Mints `amount` of `token_name` under the seedelf policy (burns it
1493    /// when negative), with the policy's `redeemer`.
1494    pub fn mint(mut self, token_name: Vec<u8>, amount: i64, redeemer: Vec<u8>) -> Result<Self> {
1495        self.mint = Some(PolicyMint {
1496            policy: policy_hash(&self.chain.config)?,
1497            token_name,
1498            amount,
1499            redeemer,
1500        });
1501        Ok(self)
1502    }
1503
1504    /// Proves every input: `prove(register, vkh)` returns the Schnorr proof
1505    /// `(z, g_r)` for a register, bound to `vkh`, the one-time key's hash in
1506    /// hex. The caller holds the secret; this never sees it.
1507    pub fn proven(
1508        mut self,
1509        mut prove: impl FnMut(&Register, &str) -> Result<(String, String)>,
1510    ) -> Result<Self> {
1511        let vkh = hex::encode(self.signer);
1512        let redeemers = self
1513            .inputs
1514            .iter()
1515            .map(|owned| {
1516                let (z, g_r) = prove(&owned.register, &vkh)?;
1517                create_spend_redeemer(z, g_r, vkh.clone())
1518            })
1519            .collect::<Result<Vec<_>>>()?;
1520        self.redeemers = Some(redeemers);
1521        Ok(self)
1522    }
1523
1524    /// The UTxOs spent, in the order given.
1525    pub fn inputs(&self) -> Vec<UtxoResponse> {
1526        self.inputs.iter().map(|o| o.utxo.clone()).collect()
1527    }
1528
1529    /// The draft for Ogmios to evaluate: every redeemer carries
1530    /// [`DRAFT_BUDGET`]. Needs [`Self::proven`] first, or the scripts fail.
1531    pub fn draft(&self) -> Result<BuiltTransaction> {
1532        let redeemers = self.proofs()?;
1533        // Ogmios doesn't check the fee. Staging with the estimated one keeps
1534        // the draft's change valid whenever the finished transaction's is.
1535        let fee = self.estimate()?.fee.total;
1536        self.stage(fee, None, redeemers)?
1537            .build_conway_raw()
1538            .context("Failed To Build The Draft Transaction")
1539    }
1540
1541    /// The unsigned transaction, with the budgets Ogmios measured on the
1542    /// draft and the fee they and the signed size need.
1543    pub fn finalize(&self, budgets: &Budgets) -> Result<FinalSpend> {
1544        let redeemers = self.proofs()?;
1545        self.settle(budgets, redeemers)
1546    }
1547
1548    /// [`Self::finalize`] with guessed budgets and stand-in proofs: whether
1549    /// these inputs can pay, before anything is proven or measured.
1550    pub fn estimate(&self) -> Result<FinalSpend> {
1551        let placeholder = create_spend_redeemer(
1552            "00".repeat(32),
1553            Register::create(blstrs::Scalar::from(1u64))?.generator,
1554            hex::encode(self.signer),
1555        )?;
1556        let redeemers = vec![placeholder; self.inputs.len()];
1557        let budgets = Budgets::guess(&self.positions()?, usize::from(self.mint.is_some()));
1558        self.settle(&budgets, &redeemers)
1559    }
1560
1561    /// Measures the scripts in the wallet ([`crate::eval`]) rather than
1562    /// through Ogmios, and finishes the spend: the draft, then the finished
1563    /// transaction measured again as it will be sent. `known` are UTxOs it
1564    /// spends that aren't on chain yet (a chain's own outputs); the rest
1565    /// come from the rows given, and the Seedelf references are bundled.
1566    pub fn measure_locally(&self, known: &[crate::eval::Resolved]) -> Result<FinalSpend> {
1567        let Chain {
1568            params,
1569            network_flag,
1570            ..
1571        } = &self.chain;
1572        let mut resolved: Vec<crate::eval::Resolved> = known.to_vec();
1573        let rows = self.inputs.iter().map(|o| &o.utxo).chain(
1574            self.account
1575                .iter()
1576                .flat_map(|a| a.inputs.iter().map(|(u, _)| u)),
1577        );
1578        for row in rows {
1579            let hash = decode_tx_hash(&row.tx_hash)?;
1580            if !resolved
1581                .iter()
1582                .any(|r| r.tx_hash == hash && r.index == row.tx_index)
1583            {
1584                resolved.push(crate::eval::resolve_row(row)?);
1585            }
1586        }
1587        resolved.extend(crate::eval::seedelf_references(*network_flag)?);
1588        let measure = |tx: &BuiltTransaction| -> Result<Budgets> {
1589            let answer = crate::eval::evaluate(
1590                &tx.tx_bytes.0,
1591                &resolved,
1592                &params.cost_model_v3,
1593                *network_flag,
1594            )?;
1595            Budgets::from_ogmios(&answer)
1596        };
1597        let budgets = measure(&self.draft()?)?;
1598        let finished = self.finalize(&budgets)?;
1599        let again = measure(&finished.tx)?;
1600        if budgets.covers(&again) {
1601            return Ok(finished);
1602        }
1603        // The finished transaction's scripts used a hair more than the draft's.
1604        let finished = self.finalize(&again.with_margin(1))?;
1605        if !again.with_margin(1).covers(&measure(&finished.tx)?) {
1606            bail!("The scripts' budgets did not settle");
1607        }
1608        Ok(finished)
1609    }
1610
1611    fn proofs(&self) -> Result<&[Vec<u8>]> {
1612        self.redeemers
1613            .as_deref()
1614            .context("The Seedelf spend isn't proven yet")
1615    }
1616
1617    /// Every input, the account's too, in the ledger's order: by
1618    /// transaction id, then index. Redeemers point into it.
1619    fn ledger_order(&self) -> Vec<Input> {
1620        let mut order: Vec<Input> = self
1621            .inputs
1622            .iter()
1623            .map(|o| o.input.clone())
1624            .chain(
1625                self.account
1626                    .iter()
1627                    .flat_map(|a| a.inputs.iter().map(|(_, i)| i.clone())),
1628            )
1629            .collect();
1630        order.sort_by_key(|i| (i.tx_hash.0, i.txo_index));
1631        order
1632    }
1633
1634    /// Where each owned input sits in the ledger's order, in the order given:
1635    /// the index its spend redeemer carries.
1636    fn positions(&self) -> Result<Vec<u64>> {
1637        let order = self.ledger_order();
1638        self.inputs
1639            .iter()
1640            .map(|owned| {
1641                order
1642                    .iter()
1643                    .position(|i| *i == owned.input)
1644                    .map(|p| p as u64)
1645                    .context("An input is missing from the transaction")
1646            })
1647            .collect()
1648    }
1649
1650    fn settle(&self, budgets: &Budgets, redeemers: &[Vec<u8>]) -> Result<FinalSpend> {
1651        // Every redeemer needs a budget, and together they must fit a transaction.
1652        let mut used = Vec::with_capacity(self.inputs.len() + 1);
1653        let mut positions = self.positions()?;
1654        positions.sort_unstable();
1655        for index in positions {
1656            used.push(budgets.spend(index).with_context(|| {
1657                format!("Ogmios measured no budget for spending input {index}")
1658            })?);
1659        }
1660        if self.mint.is_some() {
1661            used.push(
1662                budgets
1663                    .mint(0)
1664                    .context("Ogmios measured no budget for the Seedelf policy")?,
1665            );
1666        }
1667        let total = used
1668            .iter()
1669            .fold(Budget { mem: 0, steps: 0 }, |a, b| Budget {
1670                mem: a.mem + b.mem,
1671                steps: a.steps + b.steps,
1672            });
1673        if total.mem > MAX_TX_BUDGET.mem || total.steps > MAX_TX_BUDGET.steps {
1674            bail!(
1675                "Spending {} UTxOs at once needs more computation than a transaction may use",
1676                self.inputs.len()
1677            );
1678        }
1679
1680        let compute: u64 = used
1681            .iter()
1682            .map(|b| computation_fee(&self.chain.params, b.mem, b.steps))
1683            .sum();
1684        // The Seedelf scripts, read from reference inputs, and any reference
1685        // script on a spent input, which Conway charges for too.
1686        let contract = &self.chain.config.contract;
1687        let mut script_bytes = contract.wallet_contract_size;
1688        if self.mint.is_some() {
1689            script_bytes += contract.seedelf_contract_size;
1690        }
1691        let mut spent = self.inputs();
1692        if let Some(account) = &self.account {
1693            spent.extend(account.inputs.iter().map(|(u, _)| u.clone()));
1694        }
1695        script_bytes += reference_script_bytes(&spent)?;
1696        let script_reference = reference_script_fee(&self.chain.params, script_bytes)?;
1697
1698        // Two signatures: the one-time key and giveme.my's collateral key, or
1699        // the account's key when an account puts up the collateral.
1700        let (fee, staged) = settle(
1701            2,
1702            Patches::staking(&Staking::none()),
1703            |size| even(linear_fee(&self.chain.params, size) + compute + script_reference),
1704            |fee| self.stage(fee, Some(budgets), redeemers),
1705        )?;
1706        let (change_lovelace, change_tokens) = self.remainder(fee)?;
1707        let change_outputs = staged.outputs.as_ref().map_or(0, Vec::len) - self.outputs.len();
1708        Ok(FinalSpend {
1709            tx: staged
1710                .build_conway_raw()
1711                .context("Failed To Build The Transaction")?,
1712            fee: ScriptFee {
1713                size: fee - compute - script_reference,
1714                compute,
1715                script_reference,
1716                total: fee,
1717            },
1718            change_lovelace,
1719            change_tokens,
1720            change_outputs,
1721        })
1722    }
1723
1724    /// What goes back into the contract when the fee is `fee`.
1725    fn remainder(&self, fee: u64) -> Result<(u64, Assets)> {
1726        let mut spent = self.inputs();
1727        if let Some(account) = &self.account {
1728            spent.extend(account.inputs.iter().map(|(u, _)| u.clone()));
1729        }
1730        let (total, mut tokens) = assets_of(spent)?;
1731        if let Some(m) = &self.mint {
1732            let asset = Asset::new(
1733                hex::encode(m.policy),
1734                hex::encode(&m.token_name),
1735                m.amount.unsigned_abs(),
1736            )?;
1737            tokens = if m.amount >= 0 {
1738                tokens.add(asset)?
1739            } else {
1740                tokens
1741                    .sub(asset)
1742                    .context("The UTxOs spent don't hold the token to burn")?
1743            };
1744        }
1745        let lovelace = total
1746            .checked_sub(self.paid_lovelace)
1747            .and_then(|rest| rest.checked_sub(fee))
1748            .ok_or(SEEDELF_SHORT)?;
1749        let tokens = tokens
1750            .separate(self.paid_tokens.clone())
1751            .context("The UTxOs spent don't hold the tokens being sent")?;
1752        Ok((lovelace, tokens))
1753    }
1754
1755    fn stage(
1756        &self,
1757        fee: u64,
1758        budgets: Option<&Budgets>,
1759        redeemers: &[Vec<u8>],
1760    ) -> Result<StagingTransaction> {
1761        let Chain {
1762            params,
1763            network_flag,
1764            config,
1765        } = &self.chain;
1766        let (change_lovelace, change_tokens) = self.remainder(fee)?;
1767        let wallet_addr = wallet_contract(*network_flag, config.contract.wallet_contract_hash);
1768        let change = if change_lovelace == 0 && change_tokens.is_empty() {
1769            Vec::new()
1770        } else if let Some(addr) = &self.change_addr {
1771            change_outputs(params, addr, change_lovelace, &change_tokens, SEEDELF_SHORT)?
1772        } else {
1773            if change_lovelace < minimum_deposit(params, &change_tokens)? {
1774                bail!(SEEDELF_SHORT);
1775            }
1776            deposit_outputs(
1777                params,
1778                &wallet_addr,
1779                &self.change_owner,
1780                change_lovelace,
1781                &change_tokens,
1782            )?
1783        };
1784
1785        let mut tx = StagingTransaction::new();
1786        for owned in &self.inputs {
1787            tx = tx.input(owned.input.clone());
1788        }
1789        for (_, input) in self.account.iter().flat_map(|a| &a.inputs) {
1790            tx = tx.input(input.clone());
1791        }
1792        // The payments and the change in a random order, drawn afresh each
1793        // time: in a transfer both are contract outputs under fresh
1794        // registers, so a fixed order was all that said which one is the
1795        // change (privacy review §3.7). No redeemer points at an output, and
1796        // the order changes neither the size nor the fee.
1797        let mut outputs: Vec<Output> = self.outputs.iter().cloned().chain(change).collect();
1798        crate::lovejoin::shuffle(&mut outputs);
1799        for output in outputs {
1800            tx = tx.output(output);
1801        }
1802        tx = match &self.account {
1803            None => tx
1804                .collateral_input(collateral_input(*network_flag))
1805                .collateral_output(collateral_output(collateral_address(*network_flag), fee)?)
1806                .disclosed_signer(self.signer)
1807                .disclosed_signer(Hash::new(COLLATERAL_HASH)),
1808            Some(account) => {
1809                // The fee is even, so 3/2 of it is whole.
1810                let back = account
1811                    .collateral_lovelace
1812                    .checked_sub(fee * 3 / 2)
1813                    .context("The collateral can't cover this transaction's fee")?;
1814                tx.collateral_input(account.collateral.clone())
1815                    .collateral_output(Output::new(account.collateral_addr.clone(), back))
1816                    .disclosed_signer(self.signer)
1817            }
1818        };
1819        tx = tx
1820            .fee(fee)
1821            .reference_input(reference_utxo(config.reference.wallet_reference_utxo))
1822            .language_view(ScriptKind::PlutusV3, params.cost_model_v3.clone());
1823
1824        // Redeemers point at inputs in the ledger's order: sorted by tx id, then index.
1825        let order = self.ledger_order();
1826        let units = |budget: Option<Budget>| {
1827            let b = budget.unwrap_or(DRAFT_BUDGET);
1828            ExUnits {
1829                mem: b.mem,
1830                steps: b.steps,
1831            }
1832        };
1833        for (owned, redeemer) in self.inputs.iter().zip(redeemers) {
1834            let index = order
1835                .iter()
1836                .position(|i| *i == owned.input)
1837                .context("An input is missing from the transaction")?;
1838            let budget = budgets.and_then(|b| b.spend(index as u64));
1839            tx = tx.add_spend_redeemer(owned.input.clone(), redeemer.clone(), Some(units(budget)));
1840        }
1841        if let Some(m) = &self.mint {
1842            tx = tx
1843                .reference_input(reference_utxo(config.reference.seedelf_reference_utxo))
1844                .mint_asset(m.policy, m.token_name.clone(), m.amount)
1845                .context("Failed To Mint The Seedelf")?
1846                .add_mint_redeemer(
1847                    m.policy,
1848                    m.redeemer.clone(),
1849                    Some(units(budgets.and_then(|b| b.mint(0)))),
1850                );
1851        }
1852        Ok(tx)
1853    }
1854}
1855
1856fn policy_hash(config: &Config) -> Result<Hash<28>> {
1857    let bytes: [u8; 28] = hex::decode(&config.contract.seedelf_policy_id)
1858        .context("The Seedelf policy id is not hex")?
1859        .try_into()
1860        .map_err(|_| anyhow::anyhow!("The Seedelf policy id is not 28 bytes"))?;
1861    Ok(Hash::new(bytes))
1862}
1863
1864// ---------------------------------------------------------------------------
1865// Keeping histories apart
1866//
1867// Two contract UTxOs spent in one transaction can be taken to share an owner,
1868// so a spend ties together the histories of everything it spends. Two boxes
1869// back from Lovejoin spent together undo much of the mixing; the change a
1870// session's funding left, spent in another session's funding, ties the two
1871// sessions; money made private from the public account, spent on a stealth
1872// mint, ties the new Seedelf to the account (privacy review §2.3).
1873//
1874// The web wallet knows where each of its UTxOs came from (its sealed
1875// history), and gives each a class. Selection keeps classes apart where one
1876// of the choices it tries without merging them pays, and otherwise merges
1877// them, never refusing what the CLI's order would pay (independent review
1878// M6): the review then says what was merged. The CLI
1879// knows nothing of the sort: every UTxO is Unknown, and it picks as it
1880// always has.
1881// ---------------------------------------------------------------------------
1882
1883/// Where a UTxO's money came from, as far as the wallet knows.
1884#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1885pub enum Origin {
1886    /// Nothing is known: the CLI's UTxOs, a restored wallet's.
1887    #[default]
1888    Unknown,
1889    /// Made private from the wallet's own public account, or its change.
1890    Own,
1891    /// Paid by someone else.
1892    Received,
1893    /// What a private session's funding left, or its return.
1894    Session,
1895    /// A box back from Lovejoin: each is a class of its own.
1896    Lovejoin,
1897}
1898
1899/// The class every UTxO with no history is in.
1900pub const UNKNOWN_CLASS: &str = "unknown";
1901
1902/// A UTxO's history: UTxOs of one class share it, and spending them together
1903/// ties nothing new. `id` names it; a class whose histories were merged
1904/// before names each of them, joined by `+`.
1905#[derive(Debug, Clone, PartialEq, Eq)]
1906pub struct Class {
1907    pub id: String,
1908    pub origin: Origin,
1909}
1910
1911impl Default for Class {
1912    fn default() -> Self {
1913        Class {
1914            id: UNKNOWN_CLASS.to_string(),
1915            origin: Origin::Unknown,
1916        }
1917    }
1918}
1919
1920impl Class {
1921    /// Whether this class's history includes `part`'s.
1922    pub fn has(&self, part: &str) -> bool {
1923        self.id.split('+').any(|p| p == part)
1924    }
1925}
1926
1927/// What a spend is for, which orders the classes it would rather take.
1928#[derive(Debug, Clone, PartialEq, Eq, Default)]
1929pub enum Purpose {
1930    /// Send or Make public: own money first, a box back from Lovejoin last.
1931    #[default]
1932    Pay,
1933    /// A stealth mint: received money first, since the new Seedelf is tied to
1934    /// whatever pays for it.
1935    Mint,
1936    /// A private session's funding or top-up: received money, or a box that
1937    /// pays alone, first; another session's money last. `session` is the
1938    /// session's own class, whose money a top-up may take first.
1939    Fund { session: Option<String> },
1940}
1941
1942impl Purpose {
1943    /// How much this spend would rather take money of `class`: lowest first.
1944    fn rank(&self, class: &Class) -> u8 {
1945        use Origin::*;
1946        match self {
1947            Purpose::Pay => match class.origin {
1948                Own => 0,
1949                Session => 1,
1950                Received => 2,
1951                Unknown => 3,
1952                Lovejoin => 4,
1953            },
1954            Purpose::Mint => match class.origin {
1955                Received => 0,
1956                Lovejoin => 1,
1957                Unknown => 2,
1958                Own => 3,
1959                Session => 4,
1960            },
1961            Purpose::Fund { session } => {
1962                if session.as_deref().is_some_and(|s| class.has(s)) {
1963                    return 0;
1964                }
1965                match class.origin {
1966                    Received => 1,
1967                    Lovejoin => 2,
1968                    Unknown => 3,
1969                    Own => 4,
1970                    Session => 5,
1971                }
1972            }
1973        }
1974    }
1975
1976    /// When merging: 0 for what's taken first, 1 for what's taken only when
1977    /// nothing else pays (another session's money, for a funding), 2 for a
1978    /// box, taken last and one at a time.
1979    fn tier(&self, class: &Class) -> u8 {
1980        match (self, class.origin) {
1981            (_, Origin::Lovejoin) => 2,
1982            (Purpose::Fund { .. }, Origin::Session) if self.rank(class) > 0 => 1,
1983            _ => 0,
1984        }
1985    }
1986}
1987
1988/// What selection knows of the UTxOs it picks from: each one's class, by
1989/// outpoint, and what the spend is for. [`Histories::default`] knows
1990/// nothing, and picks as the CLI always has.
1991#[derive(Debug, Clone, Default)]
1992pub struct Histories {
1993    classes: BTreeMap<(String, u64), Class>,
1994    purpose: Purpose,
1995}
1996
1997impl Histories {
1998    /// Nothing known yet, for a spend that's for `purpose`.
1999    pub fn new(purpose: Purpose) -> Self {
2000        Histories {
2001            classes: BTreeMap::new(),
2002            purpose,
2003        }
2004    }
2005
2006    /// The UTxO `tx_hash#tx_index` is of `class`.
2007    pub fn with(mut self, tx_hash: &str, tx_index: u64, class: Class) -> Self {
2008        self.classes.insert((tx_hash.to_string(), tx_index), class);
2009        self
2010    }
2011
2012    /// `utxo`'s class: Unknown when none was given.
2013    pub fn class_of(&self, utxo: &UtxoResponse) -> Class {
2014        self.classes
2015            .get(&(utxo.tx_hash.clone(), utxo.tx_index))
2016            .cloned()
2017            .unwrap_or_default()
2018    }
2019
2020    /// The classes `inputs` merge: each one once, in the order first spent,
2021    /// when there's more than one; none when they share a history.
2022    pub fn merged(&self, inputs: &[UtxoResponse]) -> Vec<Class> {
2023        let mut classes: Vec<Class> = Vec::new();
2024        for class in inputs.iter().map(|u| self.class_of(u)) {
2025            if !classes.iter().any(|c| c.id == class.id) {
2026                classes.push(class);
2027            }
2028        }
2029        if classes.len() > 1 {
2030            classes
2031        } else {
2032            Vec::new()
2033        }
2034    }
2035
2036    /// Whether nothing is known of any of `utxos`.
2037    fn blind(&self, utxos: &[UtxoResponse]) -> bool {
2038        utxos
2039            .iter()
2040            .all(|u| self.class_of(u).origin == Origin::Unknown)
2041    }
2042}
2043
2044fn lovelace_of(utxo: &UtxoResponse) -> u64 {
2045    utxo.value.parse::<u64>().unwrap_or(0)
2046}
2047
2048fn holds_tokens(utxo: &UtxoResponse) -> bool {
2049    utxo.asset_list.as_ref().is_some_and(|a| !a.is_empty())
2050}
2051
2052/// Picks as few of `available` as it can, keeping different histories apart
2053/// when it can (see the section comment above), and hands each choice to
2054/// `attempt` until one succeeds. Only "not enough" failures move on. Never
2055/// picked: a UTxO the wallet's evaluator can't take
2056/// ([`crate::eval::refusal`]: one holding a reference script, which anyone
2057/// can pay a Seedelf), and one whose tokens would push a total past what one
2058/// output holds ([`fitting`]). `floor` is the lovelace the spend pays out:
2059/// inputs holding no more can't pay, and aren't tried.
2060///
2061/// First the UTxOs holding the tokens in `needed` ([`holding`]). When nothing
2062/// is known of any UTxO's history, then pure-ADA UTxOs, largest first, then
2063/// other token UTxOs, adding one at a time: the CLI's way. Otherwise, in turn:
2064///
2065/// 1. one UTxO that pays on its own (a token-bearing one counts), by the
2066///    purpose's order, the smallest first;
2067/// 2. UTxOs of one class, by the purpose's order, the largest first;
2068/// 3. merging classes: with tokens sent, their UTxOs and one of another
2069///    class first; then by the purpose's order, its last resorts after the
2070///    rest, and boxes back from Lovejoin last of all, one at a time, so two
2071///    are spent together only when nothing else pays. When that runs into
2072///    what a transaction can spend (too many inputs), the same without the
2073///    purpose's order; then as few boxes as pay, none first, then one more
2074///    at a time, each with the fewest other inputs (the largest first,
2075///    tokens or not), or with UTxOs holding tokens last, for when their
2076///    tokens make it too large; and at the last the CLI's own order, so
2077///    nothing the CLI pays is refused.
2078fn select_script_inputs<T>(
2079    available: &[UtxoResponse],
2080    needed: &Assets,
2081    floor: u64,
2082    histories: &Histories,
2083    mut attempt: impl FnMut(&[UtxoResponse]) -> Result<T>,
2084) -> Result<T> {
2085    let holds_needed = |u: &UtxoResponse| {
2086        needed.items.iter().any(|want| {
2087            quantity_in(
2088                u,
2089                &hex::encode(want.policy_id),
2090                &hex::encode(&want.token_name),
2091            ) > 0
2092        })
2093    };
2094    let evaluable: Vec<UtxoResponse> = available
2095        .iter()
2096        .filter(|u| crate::eval::refusal(u).is_none())
2097        .cloned()
2098        .collect();
2099    let (available, _) = fitting(&evaluable, &Assets::new(), holds_needed)?;
2100    let available = available.as_slice();
2101    let mandatory = holding(available, needed)?;
2102    let mut rest: Vec<UtxoResponse> = available
2103        .iter()
2104        .filter(|u| !mandatory.iter().any(|m| same_utxo(m, u)))
2105        .cloned()
2106        .collect();
2107    if !histories.blind(available) {
2108        return apart(mandatory, rest, floor, histories, attempt);
2109    }
2110    rest.sort_by_key(|u| (holds_tokens(u), std::cmp::Reverse(lovelace_of(u))));
2111    let mut last_error = None;
2112    for k in 0..=rest.len() {
2113        let selected: Vec<UtxoResponse> = mandatory.iter().chain(&rest[..k]).cloned().collect();
2114        if selected.is_empty() {
2115            continue;
2116        }
2117        match attempt(&selected) {
2118            Ok(built) => return Ok(built),
2119            Err(e) if e.downcast_ref::<NotEnough>().is_some() => last_error = Some(e),
2120            Err(e) => return Err(e),
2121        }
2122    }
2123    Err(last_error.unwrap_or_else(|| SEEDELF_SHORT.into()))
2124}
2125
2126/// How many UTxOs holding tokens [`apart`] tries alone. Each is a question
2127/// of its own (its change must carry its tokens), and a wallet can hold
2128/// hundreds; past these, the later steps still find what pays.
2129const MAX_TOKEN_SINGLES: usize = 12;
2130
2131/// One choice [`apart`] hands to `attempt`.
2132enum Tried<T> {
2133    Built(T),
2134    /// These inputs don't hold enough.
2135    Short,
2136    /// They failed otherwise (too much computation, say).
2137    Failed,
2138    /// Not tried: they can't pay what the spend pays out.
2139    Skipped,
2140}
2141
2142/// [`apart`] with one more UTxO: each of `candidates` in turn, by `rank`,
2143/// the smallest first. One of ADA alone that was short says no smaller one
2144/// of ADA alone will do, and only [`MAX_TOKEN_SINGLES`] holding tokens are
2145/// tried.
2146fn one_more<'a, T>(
2147    mut candidates: Vec<&'a UtxoResponse>,
2148    rank: impl Fn(&UtxoResponse) -> u8,
2149    try_with: &mut impl FnMut(&[&'a UtxoResponse]) -> Tried<T>,
2150) -> Option<T> {
2151    candidates.sort_by_key(|u| (rank(u), lovelace_of(u)));
2152    let mut short_at: Option<u64> = None;
2153    let mut token_tries = 0;
2154    for u in candidates {
2155        let ada_only = !holds_tokens(u);
2156        if ada_only && short_at.is_some_and(|at| lovelace_of(u) <= at) {
2157            continue;
2158        }
2159        if !ada_only {
2160            if token_tries == MAX_TOKEN_SINGLES {
2161                continue;
2162            }
2163            token_tries += 1;
2164        }
2165        match try_with(&[u]) {
2166            Tried::Built(built) => return Some(built),
2167            Tried::Short if ada_only => short_at = short_at.max(Some(lovelace_of(u))),
2168            _ => {}
2169        }
2170    }
2171    None
2172}
2173
2174/// How growing one of [`apart`]'s merging orders ended.
2175enum Grown<T> {
2176    Built(T),
2177    /// Everything in it together is short.
2178    Short,
2179    /// The choice of its first `.0` failed otherwise (too much computation,
2180    /// say): more inputs won't mend it.
2181    Failed(usize),
2182}
2183
2184/// `order[..k]` for k = `from`, `from` + 1, … until one builds or fails
2185/// otherwise.
2186fn grow<'a, T>(
2187    order: &[&'a UtxoResponse],
2188    from: usize,
2189    try_with: &mut impl FnMut(&[&'a UtxoResponse]) -> Tried<T>,
2190) -> Grown<T> {
2191    for k in from.max(1)..=order.len() {
2192        match try_with(&order[..k]) {
2193            Tried::Built(built) => return Grown::Built(built),
2194            Tried::Failed => return Grown::Failed(k),
2195            _ => {}
2196        }
2197    }
2198    Grown::Short
2199}
2200
2201/// [`select_script_inputs`] with histories known: `base` are the UTxOs
2202/// holding the tokens sent, which every choice spends, and `rest` the others.
2203fn apart<T>(
2204    base: Vec<UtxoResponse>,
2205    rest: Vec<UtxoResponse>,
2206    floor: u64,
2207    histories: &Histories,
2208    mut attempt: impl FnMut(&[UtxoResponse]) -> Result<T>,
2209) -> Result<T> {
2210    let purpose = &histories.purpose;
2211    let class = |u: &UtxoResponse| histories.class_of(u);
2212    let base_lovelace: u64 = base.iter().map(lovelace_of).sum();
2213    let base_classes: Vec<String> = base.iter().map(|u| class(u).id).collect();
2214    // Money that adds no history to what the tokens sent bring.
2215    let joins_base = |u: &UtxoResponse| base.is_empty() || base_classes.contains(&class(u).id);
2216
2217    let mut not_enough: Option<anyhow::Error> = None;
2218    let mut other: Option<anyhow::Error> = None;
2219    let mut try_with = |extra: &[&UtxoResponse]| -> Tried<T> {
2220        let lovelace = base_lovelace + extra.iter().map(|u| lovelace_of(u)).sum::<u64>();
2221        if lovelace <= floor || (base.is_empty() && extra.is_empty()) {
2222            return Tried::Skipped;
2223        }
2224        let selected: Vec<UtxoResponse> = base
2225            .iter()
2226            .cloned()
2227            .chain(extra.iter().map(|u| (*u).clone()))
2228            .collect();
2229        match attempt(&selected) {
2230            Ok(built) => Tried::Built(built),
2231            Err(e) if e.downcast_ref::<NotEnough>().is_some() => {
2232                not_enough = Some(e);
2233                Tried::Short
2234            }
2235            Err(e) => {
2236                other = Some(e);
2237                Tried::Failed
2238            }
2239        }
2240    };
2241
2242    // The tokens' own UTxOs, alone.
2243    if let Tried::Built(built) = try_with(&[]) {
2244        return Ok(built);
2245    }
2246
2247    // 1. One more UTxO: by the purpose's order, the smallest first.
2248    let singles: Vec<&UtxoResponse> = rest.iter().filter(|u| joins_base(u)).collect();
2249    if let Some(built) = one_more(singles, |u| purpose.rank(&class(u)), &mut try_with) {
2250        return Ok(built);
2251    }
2252
2253    // 2. UTxOs of one class: the classes by the purpose's order (the most
2254    // money first among equals), each one's largest first, pure ADA before
2255    // tokens.
2256    let mut classes: Vec<(Class, Vec<&UtxoResponse>)> = Vec::new();
2257    for u in rest.iter().filter(|u| joins_base(u)) {
2258        let c = class(u);
2259        match classes.iter_mut().find(|(k, _)| k.id == c.id) {
2260            Some((_, rows)) => rows.push(u),
2261            None => classes.push((c, vec![u])),
2262        }
2263    }
2264    classes.sort_by_key(|(c, rows)| {
2265        let total: u64 = rows.iter().map(|u| lovelace_of(u)).sum();
2266        (purpose.rank(c), std::cmp::Reverse(total), c.id.clone())
2267    });
2268    for (_, rows) in &mut classes {
2269        rows.sort_by_key(|u| (holds_tokens(u), std::cmp::Reverse(lovelace_of(u))));
2270        for k in 2..=rows.len() {
2271            match try_with(&rows[..k]) {
2272                Tried::Built(built) => return Ok(built),
2273                // More of this class won't mend it.
2274                Tried::Failed => break,
2275                _ => {}
2276            }
2277        }
2278    }
2279
2280    // 3. Merging. With tokens sent, first their UTxOs and one UTxO of another
2281    // class, as in 1: two histories before more (independent review L39).
2282    // Boxes and the purpose's last resorts wait for the merging below.
2283    if !base.is_empty() {
2284        let others: Vec<&UtxoResponse> = rest
2285            .iter()
2286            .filter(|u| !joins_base(u) && purpose.tier(&class(u)) == 0)
2287            .collect();
2288        if let Some(built) = one_more(others, |u| purpose.rank(&class(u)), &mut try_with) {
2289            return Ok(built);
2290        }
2291    }
2292
2293    // Then orders tried in turn, each grown one UTxO at a time. One that runs
2294    // into what more inputs can't mend (too much computation, too large) is
2295    // left for the next; short with everything is short in any order
2296    // (independent review M6).
2297    //
2298    // a. As the CLI would, but the purpose's last resorts after the rest,
2299    //    and boxes after everything, one at a time; within each, by the
2300    //    purpose's order, the largest first (independent review L39).
2301    // b. The same without the purpose's order, pure ADA before tokens within
2302    //    each: what merging paid before a did is paid the same way.
2303    // c. As few boxes as pay: the largest j of them, then the rest without
2304    //    the other boxes (the last resorts after the others), for j = 0, 1,
2305    //    2, …, so no more boxes are spent together than pay. For each j, the
2306    //    rest the largest first, tokens or not, for the fewest inputs; then
2307    //    pure ADA before tokens, for when a UTxO's tokens make it too large.
2308    //    Boxes that fail alone fail with more.
2309    // d. The CLI's own order, boxes among the rest by size: never refused
2310    //    what the CLI would pay.
2311    let tier = |u: &UtxoResponse| purpose.tier(&class(u));
2312    let mut by_history: Vec<&UtxoResponse> = rest.iter().collect();
2313    by_history.sort_by_key(|u| {
2314        (
2315            tier(u),
2316            !joins_base(u),
2317            purpose.rank(&class(u)),
2318            holds_tokens(u),
2319            std::cmp::Reverse(lovelace_of(u)),
2320        )
2321    });
2322    let mut by_tier: Vec<&UtxoResponse> = rest.iter().collect();
2323    by_tier.sort_by_key(|u| {
2324        (
2325            tier(u),
2326            !joins_base(u),
2327            holds_tokens(u),
2328            std::cmp::Reverse(lovelace_of(u)),
2329        )
2330    });
2331    let is_box = |u: &UtxoResponse| tier(u) == 2 && !holds_tokens(u);
2332    let mut boxes: Vec<&UtxoResponse> = rest.iter().filter(|u| is_box(u)).collect();
2333    boxes.sort_by_key(|u| (!joins_base(u), std::cmp::Reverse(lovelace_of(u))));
2334    let mut largest: Vec<&UtxoResponse> = rest.iter().filter(|u| !is_box(u)).collect();
2335    largest.sort_by_key(|u| (tier(u), !joins_base(u), std::cmp::Reverse(lovelace_of(u))));
2336    let mut tokens_last: Vec<&UtxoResponse> = rest.iter().filter(|u| !is_box(u)).collect();
2337    tokens_last.sort_by_key(|u| {
2338        (
2339            holds_tokens(u),
2340            tier(u),
2341            !joins_base(u),
2342            std::cmp::Reverse(lovelace_of(u)),
2343        )
2344    });
2345    let same = |a: &[&UtxoResponse], b: &[&UtxoResponse]| {
2346        a.len() == b.len() && a.iter().zip(b).all(|(x, y)| std::ptr::eq(*x, *y))
2347    };
2348    let mut rests = vec![&largest];
2349    if !same(&largest, &tokens_last) {
2350        rests.push(&tokens_last);
2351    }
2352    let (boxes, rests) = (&boxes, &rests);
2353    // With nothing but boxes, a grew these already.
2354    let fewest_boxes = (0..=boxes.len())
2355        .filter(|_| !largest.is_empty())
2356        .flat_map(|j| {
2357            rests
2358                .iter()
2359                .map(move |others| (boxes[..j].iter().chain(others.iter()).copied().collect(), j))
2360        });
2361    let mut blind: Vec<&UtxoResponse> = rest.iter().collect();
2362    blind.sort_by_key(|u| (holds_tokens(u), std::cmp::Reverse(lovelace_of(u))));
2363    let mut failed = false;
2364    let mut boxes_fail = false;
2365    let mut grown: Vec<Vec<&UtxoResponse>> = Vec::new();
2366    // Each order, and how many boxes it brings forward (none for a, b, d).
2367    let orders = [(by_history, 0), (by_tier, 0)]
2368        .into_iter()
2369        .chain(fewest_boxes)
2370        .chain([(blind, 0)]);
2371    for (order, j) in orders {
2372        // The same order again, or the start of one grown whole before (c's
2373        // without boxes, often), would fail the same way.
2374        let grown_before =
2375            |g: &Vec<&UtxoResponse>| g.len() >= order.len() && same(&g[..order.len()], &order);
2376        if (j > 0 && boxes_fail) || grown.iter().any(grown_before) {
2377            continue;
2378        }
2379        let whole = order.len() == rest.len();
2380        match grow(&order, j, &mut try_with) {
2381            Grown::Built(built) => return Ok(built),
2382            Grown::Short if whole => {
2383                failed = false;
2384                break;
2385            }
2386            // Short without the other boxes: one more may pay.
2387            Grown::Short => {}
2388            Grown::Failed(at) => {
2389                failed = true;
2390                boxes_fail |= j > 0 && at == j;
2391            }
2392        }
2393        if whole {
2394            grown.push(order);
2395        }
2396    }
2397    Err(match (failed, other, not_enough) {
2398        (true, Some(e), _) => e,
2399        (_, _, Some(e)) => e,
2400        (_, Some(e), None) => e,
2401        _ => SEEDELF_SHORT.into(),
2402    })
2403}
2404
2405fn same_utxo(a: &UtxoResponse, b: &UtxoResponse) -> bool {
2406    a.tx_hash == b.tx_hash && a.tx_index == b.tx_index
2407}
2408
2409/// How much of a token a UTxO holds.
2410fn quantity_in(utxo: &UtxoResponse, policy: &str, name: &str) -> u64 {
2411    utxo.asset_list
2412        .iter()
2413        .flatten()
2414        .filter(|a| a.policy_id == policy && a.asset_name == name)
2415        .map(|a| a.quantity.parse::<u64>().unwrap_or(0))
2416        .sum()
2417}
2418
2419/// As few of `available` as together hold `needed`: for each token in turn,
2420/// the UTxOs holding the most of it, until there's enough. Errors naming the
2421/// token when `available` doesn't hold enough of it.
2422fn holding(available: &[UtxoResponse], needed: &Assets) -> Result<Vec<UtxoResponse>> {
2423    let mut picked: Vec<UtxoResponse> = Vec::new();
2424    for want in &needed.items {
2425        let policy = hex::encode(want.policy_id);
2426        let name = hex::encode(&want.token_name);
2427        let mut have: u64 = picked.iter().map(|u| quantity_in(u, &policy, &name)).sum();
2428        let mut candidates: Vec<&UtxoResponse> = available
2429            .iter()
2430            .filter(|u| quantity_in(u, &policy, &name) > 0)
2431            .filter(|u| !picked.iter().any(|p| same_utxo(p, u)))
2432            .collect();
2433        candidates.sort_by_key(|u| std::cmp::Reverse(quantity_in(u, &policy, &name)));
2434        for utxo in candidates {
2435            if have >= want.amount {
2436                break;
2437            }
2438            have = have.saturating_add(quantity_in(utxo, &policy, &name));
2439            picked.push(utxo.clone());
2440        }
2441        if have < want.amount {
2442            if have == 0 {
2443                bail!("The Seedelf balance doesn't hold the token {policy}.{name}");
2444            }
2445            bail!(
2446                "The Seedelf balance holds only {have} of the token {policy}.{name}, not {}",
2447                want.amount
2448            );
2449        }
2450    }
2451    Ok(picked)
2452}
2453
2454/// A seedelf mint, ready to prove, draft and finalize.
2455pub struct SeedelfMint {
2456    pub spend: ScriptSpend,
2457    /// The new token's name: prefix, label, and the smallest input.
2458    pub token_name: Vec<u8>,
2459    /// Locked with the token; only removing the seedelf gets it back.
2460    pub lovelace: u64,
2461}
2462
2463/// `util mint`: a new seedelf paid from owned wallet-contract UTxOs. Picks as
2464/// few of `available` as can pay (see [`mint_from`]).
2465pub fn mint(
2466    chain: &Chain,
2467    available: &[UtxoResponse],
2468    label: &str,
2469    seedelf: &Register,
2470    change_owner: &Register,
2471    signer: Hash<28>,
2472) -> Result<SeedelfMint> {
2473    mint_apart(
2474        chain,
2475        available,
2476        &Histories::default(),
2477        label,
2478        seedelf,
2479        change_owner,
2480        signer,
2481    )
2482}
2483
2484/// [`mint`], keeping the `histories` of `available` apart where it can.
2485pub fn mint_apart(
2486    chain: &Chain,
2487    available: &[UtxoResponse],
2488    histories: &Histories,
2489    label: &str,
2490    seedelf: &Register,
2491    change_owner: &Register,
2492    signer: Hash<28>,
2493) -> Result<SeedelfMint> {
2494    let floor = seedelf_minimum_lovelace(&chain.params)?;
2495    select_script_inputs(available, &Assets::new(), floor, histories, |inputs| {
2496        let built = mint_from(chain, inputs, label, seedelf, change_owner, signer)?;
2497        built.spend.estimate()?;
2498        Ok(built)
2499    })
2500}
2501
2502/// A seedelf mint spending exactly `inputs`.
2503///
2504/// - The token is named after the smallest input (`seedelf_token_name`), as
2505///   the policy checks, with `label` cut to 15 bytes.
2506/// - It sits in the contract with the minimum ADA, under `seedelf`, which is
2507///   used as given: pass a fresh re-randomization.
2508/// - The rest goes back into the contract under fresh re-randomizations of
2509///   `change_owner`.
2510pub fn mint_from(
2511    chain: &Chain,
2512    inputs: &[UtxoResponse],
2513    label: &str,
2514    seedelf: &Register,
2515    change_owner: &Register,
2516    signer: Hash<28>,
2517) -> Result<SeedelfMint> {
2518    // Points that don't decode, aren't in the prime-order subgroup, or are the
2519    // identity would lose the seedelf and anything sent to it.
2520    if !is_payable(seedelf) {
2521        bail!("The Seedelf's register isn't made of valid points");
2522    }
2523    let spend = ScriptSpend::new(chain, inputs, change_owner, signer)?;
2524    let Chain {
2525        params,
2526        network_flag,
2527        config,
2528    } = chain;
2529    let policy = policy_hash(config)?;
2530    let owned: Vec<Input> = spend.inputs.iter().map(|o| o.input.clone()).collect();
2531    let token_name = seedelf_token_name(label.to_string(), Some(&owned))?;
2532    let lovelace = seedelf_minimum_lovelace(params)?;
2533    let wallet_addr = wallet_contract(*network_flag, config.contract.wallet_contract_hash);
2534    let output = Output::new(wallet_addr, lovelace)
2535        .set_inline_datum(seedelf.to_vec()?)
2536        .add_asset(policy, token_name.clone(), 1)
2537        .context("Failed To Add The Seedelf")?;
2538    let token = Asset::new(
2539        config.contract.seedelf_policy_id.clone(),
2540        hex::encode(&token_name),
2541        1,
2542    )?;
2543    let spend = spend.output(output, &Assets::new().add(token)?)?.mint(
2544        token_name.clone(),
2545        1,
2546        create_mint_redeemer(label.to_string())?,
2547    )?;
2548    Ok(SeedelfMint {
2549        spend,
2550        token_name,
2551        lovelace,
2552    })
2553}
2554
2555// ---------------------------------------------------------------------------
2556// Transfer: paying seedelfs from the Seedelf balance
2557//
2558// Each payment goes into the contract under a fresh re-randomization of the
2559// recipient's register, as found on chain with their seedelf. The register
2560// itself is never written back: re-randomizing is what keeps payments to the
2561// same seedelf unlinkable, and it refuses points outside the prime-order
2562// subgroup. The rest goes back under fresh re-randomizations of the payer's.
2563// ---------------------------------------------------------------------------
2564
2565/// A payment to a seedelf.
2566#[derive(Debug, Clone)]
2567pub struct Payment {
2568    /// The recipient's register: the datum of the UTxO holding their seedelf.
2569    pub register: Register,
2570    pub lovelace: u64,
2571    pub tokens: Assets,
2572}
2573
2574/// Whether anything paid to `register` is safe: both points decode, lie in
2575/// the prime-order subgroup, and neither is the identity. An identity public
2576/// value is `g^0`, so anyone could prove the key and take the payment; an
2577/// identity generator locks it for good, as does a torsion point.
2578pub fn is_payable(register: &Register) -> bool {
2579    let identity = |point: &str| {
2580        hex::decode(point)
2581            .is_ok_and(|b| b.len() == 48 && b[0] == 0xc0 && b[1..].iter().all(|&x| x == 0))
2582    };
2583    register.is_valid().unwrap_or(false)
2584        && !identity(&register.generator)
2585        && !identity(&register.public_value)
2586}
2587
2588/// `transfer`: pays each of `payments` from owned wallet-contract UTxOs.
2589/// Picks as few of `available` as can pay: the UTxOs holding the tokens
2590/// being sent first, then as in [`mint`].
2591pub fn transfer(
2592    chain: &Chain,
2593    available: &[UtxoResponse],
2594    payments: &[Payment],
2595    change_owner: &Register,
2596    signer: Hash<28>,
2597) -> Result<ScriptSpend> {
2598    transfer_apart(
2599        chain,
2600        available,
2601        &Histories::default(),
2602        payments,
2603        change_owner,
2604        signer,
2605    )
2606}
2607
2608/// [`transfer`], keeping the `histories` of `available` apart where it can.
2609pub fn transfer_apart(
2610    chain: &Chain,
2611    available: &[UtxoResponse],
2612    histories: &Histories,
2613    payments: &[Payment],
2614    change_owner: &Register,
2615    signer: Hash<28>,
2616) -> Result<ScriptSpend> {
2617    let outputs = payment_outputs(chain, payments)?;
2618    let needed = payments
2619        .iter()
2620        .try_fold(Assets::new(), |all, p| all.merge(p.tokens.clone()))?;
2621    select_script_inputs(
2622        available,
2623        &needed,
2624        paid_out(&outputs),
2625        histories,
2626        |inputs| {
2627            let spend = paying(chain, inputs, &outputs, change_owner, signer)?;
2628            spend.estimate()?;
2629            Ok(spend)
2630        },
2631    )
2632}
2633
2634/// The lovelace `outputs` carry.
2635fn paid_out(outputs: &[(Output, Assets)]) -> u64 {
2636    outputs.iter().map(|(o, _)| o.lovelace).sum()
2637}
2638
2639/// A transfer spending exactly `inputs`. They must hold the tokens being
2640/// sent.
2641pub fn transfer_from(
2642    chain: &Chain,
2643    inputs: &[UtxoResponse],
2644    payments: &[Payment],
2645    change_owner: &Register,
2646    signer: Hash<28>,
2647) -> Result<ScriptSpend> {
2648    let outputs = payment_outputs(chain, payments)?;
2649    paying(chain, inputs, &outputs, change_owner, signer)
2650}
2651
2652fn paying(
2653    chain: &Chain,
2654    inputs: &[UtxoResponse],
2655    outputs: &[(Output, Assets)],
2656    change_owner: &Register,
2657    signer: Hash<28>,
2658) -> Result<ScriptSpend> {
2659    outputs.iter().try_fold(
2660        ScriptSpend::new(chain, inputs, change_owner, signer)?,
2661        |spend, (output, tokens)| spend.output(output.clone(), tokens),
2662    )
2663}
2664
2665/// One output per payment, each under a fresh re-randomization of the
2666/// recipient's register and above its minimum.
2667fn payment_outputs(chain: &Chain, payments: &[Payment]) -> Result<Vec<(Output, Assets)>> {
2668    if payments.is_empty() {
2669        bail!("A transfer pays at least one Seedelf");
2670    }
2671    let wallet_addr = wallet_contract(
2672        chain.network_flag,
2673        chain.config.contract.wallet_contract_hash,
2674    );
2675    payments
2676        .iter()
2677        .map(|payment| {
2678            if !is_payable(&payment.register) {
2679                bail!(
2680                    "That Seedelf's register isn't valid: a payment to it could be locked for good, or taken by anyone"
2681                );
2682            }
2683            if payment.tokens.items.iter().any(|a| a.amount == 0) {
2684                bail!("A payment can't send none of a token");
2685            }
2686            let minimum = minimum_seedelf_payment(&chain.params, &payment.tokens)?;
2687            if payment.lovelace < minimum {
2688                bail!(
2689                    "A payment to a Seedelf{} needs at least {} ADA",
2690                    if payment.tokens.is_empty() { "" } else { " with these tokens" },
2691                    ada(minimum)
2692                );
2693            }
2694            let output = deposit_output(&wallet_addr, &payment.register, payment.lovelace, &payment.tokens)?;
2695            Ok((output, payment.tokens.clone()))
2696        })
2697        .collect()
2698}
2699
2700/// The least lovelace a payment to a seedelf with `tokens` must carry: one
2701/// contract output under a register.
2702pub fn minimum_seedelf_payment(params: &ProtocolParameters, tokens: &Assets) -> Result<u64> {
2703    wallet_minimum_lovelace_with_assets(params, tokens.clone())
2704}
2705
2706/// Lovelace as ADA, with all six decimals.
2707fn ada(lovelace: u64) -> String {
2708    format!("{}.{:06}", lovelace / 1_000_000, lovelace % 1_000_000)
2709}
2710
2711// ---------------------------------------------------------------------------
2712// Withdraw: paying an address from the Seedelf balance, or removing a seedelf
2713//
2714// `sweep` pays an address a fixed amount, and the change goes back into the
2715// contract; `sweep_all` sends everything the inputs hold, less the fee.
2716// `remove` burns a seedelf, and what its UTxO held goes back into the
2717// contract, or to an address with `ScriptSpend::change_to`.
2718// ---------------------------------------------------------------------------
2719
2720/// Whether a withdrawal can pay `addr`: a Shelley address on this network
2721/// with no script in it. A script output would carry no datum, which locks
2722/// it at most scripts.
2723pub fn is_payable_address(addr: &Address, network_flag: bool) -> bool {
2724    matches!(addr, Address::Shelley(_))
2725        && is_not_a_script(addr.clone())
2726        && is_on_correct_network(addr.clone(), network_flag)
2727}
2728
2729fn check_address(chain: &Chain, addr: &Address) -> Result<()> {
2730    check_payable(addr, chain.network_flag)
2731}
2732
2733fn check_payable(addr: &Address, network_flag: bool) -> Result<()> {
2734    if !is_payable_address(addr, network_flag) {
2735        let network = if network_flag { "preprod" } else { "mainnet" };
2736        bail!(
2737            "Payments go to a normal {network} address: not a script, stake or other network's address"
2738        );
2739    }
2740    Ok(())
2741}
2742
2743/// The least lovelace an output paying `to` exactly `tokens` must carry.
2744pub fn minimum_address_payment(
2745    params: &ProtocolParameters,
2746    to: &Address,
2747    tokens: &Assets,
2748) -> Result<u64> {
2749    let bech32 = to
2750        .to_bech32()
2751        .map_err(|e| anyhow::anyhow!("Failed to encode the address: {e}"))?;
2752    address_minimum_lovelace_with_assets(params, &bech32, tokens.clone())
2753}
2754
2755/// The output paying `to` exactly `lovelace` and `tokens`, above its minimum.
2756fn address_output(chain: &Chain, to: &Address, lovelace: u64, tokens: &Assets) -> Result<Output> {
2757    check_address(chain, to)?;
2758    pay_address(&chain.params, to, lovelace, tokens)
2759}
2760
2761/// [`address_output`] for an address already checked.
2762fn pay_address(
2763    params: &ProtocolParameters,
2764    to: &Address,
2765    lovelace: u64,
2766    tokens: &Assets,
2767) -> Result<Output> {
2768    if tokens.items.iter().any(|a| a.amount == 0) {
2769        bail!("A payment can't send none of a token");
2770    }
2771    let minimum = minimum_address_payment(params, to, tokens)?;
2772    if lovelace < minimum {
2773        bail!(
2774            "A payment to that address{} needs at least {} ADA",
2775            if tokens.is_empty() {
2776                ""
2777            } else {
2778                " with these tokens"
2779            },
2780            ada(minimum)
2781        );
2782    }
2783    let mut output = Output::new(to.clone(), lovelace);
2784    for asset in &tokens.items {
2785        output = output
2786            .add_asset(asset.policy_id, asset.token_name.clone(), asset.amount)
2787            .context("Failed To Add An Asset")?;
2788    }
2789    Ok(output)
2790}
2791
2792/// `sweep`: pays `to` exactly `lovelace` and `tokens` from owned
2793/// wallet-contract UTxOs, as few as can pay (the ones holding the tokens
2794/// first, as for a transfer). The change goes back into the contract under
2795/// fresh re-randomizations of `change_owner`.
2796pub fn sweep(
2797    chain: &Chain,
2798    available: &[UtxoResponse],
2799    to: &Address,
2800    lovelace: u64,
2801    tokens: &Assets,
2802    change_owner: &Register,
2803    signer: Hash<28>,
2804) -> Result<ScriptSpend> {
2805    let payment = AddressPayment {
2806        to: to.clone(),
2807        lovelace,
2808        tokens: tokens.clone(),
2809    };
2810    sweep_many(chain, available, &[payment], change_owner, signer)
2811}
2812
2813/// A payment to a key address from the Seedelf balance.
2814#[derive(Debug, Clone)]
2815pub struct AddressPayment {
2816    pub to: Address,
2817    pub lovelace: u64,
2818    pub tokens: Assets,
2819}
2820
2821/// [`sweep`] to several: pays each of `payments` from as few owned UTxOs as
2822/// can pay them all. The outputs, change included, go in a random order
2823/// ([`ScriptSpend`]).
2824pub fn sweep_many(
2825    chain: &Chain,
2826    available: &[UtxoResponse],
2827    payments: &[AddressPayment],
2828    change_owner: &Register,
2829    signer: Hash<28>,
2830) -> Result<ScriptSpend> {
2831    sweep_many_apart(
2832        chain,
2833        available,
2834        &Histories::default(),
2835        payments,
2836        change_owner,
2837        signer,
2838    )
2839}
2840
2841/// [`sweep_many`], keeping the `histories` of `available` apart where it can.
2842pub fn sweep_many_apart(
2843    chain: &Chain,
2844    available: &[UtxoResponse],
2845    histories: &Histories,
2846    payments: &[AddressPayment],
2847    change_owner: &Register,
2848    signer: Hash<28>,
2849) -> Result<ScriptSpend> {
2850    if payments.is_empty() {
2851        bail!("A withdrawal pays at least one address");
2852    }
2853    let outputs: Vec<(Output, Assets)> = payments
2854        .iter()
2855        .map(|p| {
2856            Ok((
2857                address_output(chain, &p.to, p.lovelace, &p.tokens)?,
2858                p.tokens.clone(),
2859            ))
2860        })
2861        .collect::<Result<_>>()?;
2862    let needed = payments
2863        .iter()
2864        .try_fold(Assets::new(), |all, p| all.merge(p.tokens.clone()))?;
2865    select_script_inputs(
2866        available,
2867        &needed,
2868        paid_out(&outputs),
2869        histories,
2870        |inputs| {
2871            let spend = paying(chain, inputs, &outputs, change_owner, signer)?;
2872            spend.estimate()?;
2873            Ok(spend)
2874        },
2875    )
2876}
2877
2878/// A sweep spending exactly `inputs`. They must hold the tokens being sent.
2879pub fn sweep_from(
2880    chain: &Chain,
2881    inputs: &[UtxoResponse],
2882    to: &Address,
2883    lovelace: u64,
2884    tokens: &Assets,
2885    change_owner: &Register,
2886    signer: Hash<28>,
2887) -> Result<ScriptSpend> {
2888    let output = address_output(chain, to, lovelace, tokens)?;
2889    ScriptSpend::new(chain, inputs, change_owner, signer)?.output(output, tokens)
2890}
2891
2892/// `sweep --all`: everything `inputs` hold, less the fee, to `to`. Tokens go
2893/// 20 to an output.
2894pub fn sweep_all(
2895    chain: &Chain,
2896    inputs: &[UtxoResponse],
2897    to: &Address,
2898    change_owner: &Register,
2899    signer: Hash<28>,
2900) -> Result<ScriptSpend> {
2901    check_address(chain, to)?;
2902    Ok(ScriptSpend::new(chain, inputs, change_owner, signer)?.change_to(to))
2903}
2904
2905/// The seedelf token in `utxo`, by name: it must hold exactly one.
2906pub fn seedelf_in(chain: &Chain, utxo: &UtxoResponse) -> Result<Vec<u8>> {
2907    let policy = &chain.config.contract.seedelf_policy_id;
2908    let held: Vec<&seedelf_koios::koios::Asset> = utxo
2909        .asset_list
2910        .iter()
2911        .flatten()
2912        .filter(|a| &a.policy_id == policy)
2913        .collect();
2914    match held.as_slice() {
2915        [one] if one.quantity == "1" => {
2916            hex::decode(&one.asset_name).context("The Seedelf's name isn't hex")
2917        }
2918        _ => bail!(
2919            "UTxO {}#{} doesn't hold exactly one Seedelf",
2920            utxo.tx_hash,
2921            utxo.tx_index
2922        ),
2923    }
2924}
2925
2926/// `remove`: spends the UTxO holding a seedelf and burns the token. What
2927/// it held, less the fee, goes back into the contract under a fresh
2928/// re-randomization of `change_owner`, or to an address with
2929/// [`ScriptSpend::change_to`].
2930pub fn remove(
2931    chain: &Chain,
2932    seedelf_utxo: &UtxoResponse,
2933    change_owner: &Register,
2934    signer: Hash<28>,
2935) -> Result<ScriptSpend> {
2936    let token_name = seedelf_in(chain, seedelf_utxo)?;
2937    ScriptSpend::new(
2938        chain,
2939        std::slice::from_ref(seedelf_utxo),
2940        change_owner,
2941        signer,
2942    )?
2943    .mint(token_name, -1, create_mint_redeemer(String::new())?)
2944}
2945
2946// ---------------------------------------------------------------------------
2947// A seedelf mint paid by the Cardano account
2948//
2949// The first seedelf is minted before any move-in: the account pays, so the
2950// seedelf is linked to it openly, but money moved in afterwards looks the
2951// same as paying anyone's seedelf. (A stealth mint from moved-in money would
2952// link the account, the seedelf and the change in one chain; see the web
2953// wallet's privacy.md.) This is the CLI's `create`, with the account's keys
2954// instead of a browser wallet.
2955//
2956// Nothing in the contract is spent, so there are no proofs and no one-time
2957// key. The collateral is one of the account's own UTxOs, as in the CLI (the
2958// web wallet's set-aside one, when it has one): this transaction names the
2959// account anyway. It needn't be ADA-only: the collateral return gives back
2960// its tokens, which Babbage and later allow.
2961// ---------------------------------------------------------------------------
2962
2963/// The least an ADA-only account UTxO should hold to be put up as collateral:
2964/// 3/2 of any fee a mint pays, plus the minimum the collateral return needs.
2965const ACCOUNT_COLLATERAL_MINIMUM: u64 = 2_000_000;
2966
2967/// A seedelf mint paid by the Cardano account, ready to draft and finalize.
2968#[derive(Clone)]
2969pub struct AccountMint {
2970    chain: Chain,
2971    inputs: Vec<UtxoResponse>,
2972    collateral: UtxoResponse,
2973    seedelf: Output,
2974    redeemer: Vec<u8>,
2975    change_addr: Address,
2976    /// A reward withdrawal riding along, patched into every draft.
2977    staking: Staking,
2978    /// The slot it stops being valid at, staged in every draft.
2979    invalid_hereafter: Option<u64>,
2980    /// The new token's name: prefix, label, and the smallest input.
2981    pub token_name: Vec<u8>,
2982    /// Locked with the token; only removing the seedelf gets it back.
2983    pub lovelace: u64,
2984}
2985
2986/// A finished account-paid mint: the unsigned transaction and what it does.
2987pub struct FinalAccountMint {
2988    /// Needs a signature from each input's key and the collateral's, and the
2989    /// stake key's when a withdrawal rides along.
2990    pub tx: BuiltTransaction,
2991    pub fee: ScriptFee,
2992    /// Back to the Cardano account.
2993    pub change_lovelace: u64,
2994    pub change_tokens: Assets,
2995    pub change_outputs: usize,
2996}
2997
2998impl AccountMint {
2999    /// The UTxOs spent, in the order given.
3000    pub fn inputs(&self) -> &[UtxoResponse] {
3001        &self.inputs
3002    }
3003
3004    /// The UTxO put up as collateral. It's only taken if the policy fails,
3005    /// which evaluation rules out.
3006    pub fn collateral(&self) -> &UtxoResponse {
3007        &self.collateral
3008    }
3009
3010    /// The draft for Ogmios to evaluate: the policy's redeemer carries
3011    /// [`DRAFT_BUDGET`], and the fee is the estimate.
3012    pub fn draft(&self) -> Result<BuiltTransaction> {
3013        let fee = self.estimate()?.fee.total;
3014        built_with(self.stage(fee, None)?, Patches::staking(&self.staking))
3015    }
3016
3017    /// The unsigned transaction, with the policy's measured budget and the
3018    /// fee it and the signed size need.
3019    pub fn finalize(&self, budgets: &Budgets) -> Result<FinalAccountMint> {
3020        self.settle(budgets)
3021    }
3022
3023    /// [`Self::finalize`] with a guessed budget: whether these inputs pay.
3024    fn estimate(&self) -> Result<FinalAccountMint> {
3025        self.settle(&Budgets::guess(&[], 1))
3026    }
3027
3028    /// How many payment keys sign: each distinct one among the inputs and
3029    /// the collateral. (A withdrawal's stake key is counted by `settle`.)
3030    fn signers(&self) -> usize {
3031        self.inputs
3032            .iter()
3033            .chain(std::iter::once(&self.collateral))
3034            .map(|u| u.payment_cred.as_str())
3035            .collect::<BTreeSet<_>>()
3036            .len()
3037    }
3038
3039    fn settle(&self, budgets: &Budgets) -> Result<FinalAccountMint> {
3040        let budget = budgets
3041            .mint(0)
3042            .context("Ogmios measured no budget for the Seedelf policy")?;
3043        let compute = computation_fee(&self.chain.params, budget.mem, budget.steps);
3044        // The policy, by reference, and any reference script on an input
3045        // (never the collateral's: the ledger doesn't count it).
3046        let script_reference = reference_script_fee(
3047            &self.chain.params,
3048            self.chain.config.contract.seedelf_contract_size
3049                + reference_script_bytes(&self.inputs)?,
3050        )?;
3051        let (fee, staged) = settle(
3052            self.signers(),
3053            Patches::staking(&self.staking),
3054            |size| even(linear_fee(&self.chain.params, size) + compute + script_reference),
3055            |fee| self.stage(fee, Some(budgets)),
3056        )?;
3057        let (change_lovelace, change_tokens) = self.change(fee)?;
3058        let change_outputs = staged.outputs.as_ref().map_or(0, Vec::len) - 1;
3059        Ok(FinalAccountMint {
3060            tx: built_with(staged, Patches::staking(&self.staking))?,
3061            fee: ScriptFee {
3062                size: fee - compute - script_reference,
3063                compute,
3064                script_reference,
3065                total: fee,
3066            },
3067            change_lovelace,
3068            change_tokens,
3069            change_outputs,
3070        })
3071    }
3072
3073    /// Everything the inputs hold, and any rewards withdrawn, less the
3074    /// seedelf's ADA and the fee.
3075    fn change(&self, fee: u64) -> Result<(u64, Assets)> {
3076        let (inputs, tokens) = assets_of(self.inputs.clone())?;
3077        let lovelace = self
3078            .staking
3079            .net(inputs)
3080            .and_then(|total| total.checked_sub(self.lovelace))
3081            .and_then(|rest| rest.checked_sub(fee))
3082            .ok_or(ACCOUNT_MINT_SHORT)?;
3083        Ok((lovelace, tokens))
3084    }
3085
3086    fn stage(&self, fee: u64, budgets: Option<&Budgets>) -> Result<StagingTransaction> {
3087        let Chain { params, config, .. } = &self.chain;
3088        let (change_lovelace, change_tokens) = self.change(fee)?;
3089        let change = change_outputs(
3090            params,
3091            &self.change_addr,
3092            change_lovelace,
3093            &change_tokens,
3094            ACCOUNT_MINT_SHORT,
3095        )?;
3096
3097        // The collateral comes back, less 3/2 of the fee, to where it was,
3098        // with any tokens it holds.
3099        let collateral_addr = Address::from_bech32(&self.collateral.address)
3100            .map_err(|e| anyhow::anyhow!("The collateral UTxO's address is invalid: {e}"))?;
3101        let (collateral_lovelace, collateral_tokens) = assets_of(vec![self.collateral.clone()])?;
3102        let collateral_back = collateral_lovelace.saturating_sub(fee * 3 / 2);
3103        if collateral_back
3104            < address_minimum_lovelace_with_assets(
3105                params,
3106                &self.collateral.address,
3107                collateral_tokens.clone(),
3108            )?
3109        {
3110            bail!("The Cardano account's UTxOs are too small to put up as collateral");
3111        }
3112        let mut collateral_return = Output::new(collateral_addr, collateral_back);
3113        for asset in &collateral_tokens.items {
3114            collateral_return = collateral_return
3115                .add_asset(asset.policy_id, asset.token_name.clone(), asset.amount)
3116                .context("Failed To Add An Asset")?;
3117        }
3118
3119        let policy = policy_hash(config)?;
3120        let budget = budgets.and_then(|b| b.mint(0)).unwrap_or(DRAFT_BUDGET);
3121        let mut tx = StagingTransaction::new();
3122        for utxo in &self.inputs {
3123            tx = tx.input(input_of(utxo)?);
3124        }
3125        for output in std::iter::once(self.seedelf.clone()).chain(change) {
3126            tx = tx.output(output);
3127        }
3128        tx = expiring(tx, self.invalid_hereafter);
3129        Ok(tx
3130            .collateral_input(input_of(&self.collateral)?)
3131            .collateral_output(collateral_return)
3132            .fee(fee)
3133            .reference_input(reference_utxo(config.reference.seedelf_reference_utxo))
3134            .mint_asset(policy, self.token_name.clone(), 1)
3135            .context("Failed To Mint The Seedelf")?
3136            .add_mint_redeemer(
3137                policy,
3138                self.redeemer.clone(),
3139                Some(ExUnits {
3140                    mem: budget.mem,
3141                    steps: budget.steps,
3142                }),
3143            )
3144            .language_view(ScriptKind::PlutusV3, params.cost_model_v3.clone()))
3145    }
3146}
3147
3148/// Mints a seedelf paid by the Cardano account. `available` is what may be
3149/// spent (key addresses the caller can sign for), as for [`move_in`]:
3150///
3151/// - Inputs: pure ADA first, largest first, then token UTxOs, as few as pay
3152///   for the seedelf, the fee and valid change.
3153/// - Collateral: `collateral` when given (the web wallet's, set aside: it's
3154///   never an input, even if `available` lists it). Otherwise any of
3155///   `available`, preferring an ADA-only one, then one of at least 2 ADA (3/2
3156///   of the fee plus the return), then one that isn't spent, then a 5 ADA
3157///   one, then the largest. Its tokens, if any, come back with the
3158///   collateral return.
3159/// - The token is named after the smallest input, with `label` cut to 15
3160///   bytes; it sits in the contract under `seedelf`, used as given (pass a
3161///   fresh re-randomization). Tokens in the inputs go back with the change,
3162///   to `change_addr`.
3163/// - `staking` rides along, as for [`move_in`]: a withdrawal of the rewards.
3164/// - `invalid_hereafter` is as for [`move_in`]; the draft Ogmios measures
3165///   holds it too.
3166#[allow(clippy::too_many_arguments)]
3167pub fn account_mint(
3168    chain: &Chain,
3169    available: &[UtxoResponse],
3170    collateral: Option<&UtxoResponse>,
3171    label: &str,
3172    seedelf: &Register,
3173    change_addr: &Address,
3174    staking: &Staking,
3175    invalid_hereafter: Option<u64>,
3176) -> Result<AccountMint> {
3177    if !is_payable(seedelf) {
3178        bail!("The Seedelf's register isn't made of valid points");
3179    }
3180    let pure_ada = |u: &UtxoResponse| u.asset_list.as_ref().is_none_or(|a| a.is_empty());
3181    let lovelace_of = |u: &UtxoResponse| u.value.parse::<u64>().unwrap_or(0);
3182    let same =
3183        |a: &UtxoResponse, b: &UtxoResponse| a.tx_hash == b.tx_hash && a.tx_index == b.tx_index;
3184
3185    // Never one holding a reference script that can't be measured, so can't
3186    // be priced.
3187    let spendable: Vec<UtxoResponse> = available
3188        .iter()
3189        .filter(|u| collateral.is_none_or(|c| !same(u, c)))
3190        .filter(|u| reference_script_size(u).is_ok())
3191        .cloned()
3192        .collect();
3193    let (mut spendable, _) = fitting(&spendable, &Assets::new(), |_| false)?;
3194    if spendable.is_empty() {
3195        bail!("There is nothing in the Cardano account to pay for a Seedelf");
3196    }
3197    // A reference script costs more to spend: those after the rest.
3198    spendable.sort_by_key(|u| {
3199        (
3200            !pure_ada(u),
3201            u.reference_script.is_some(),
3202            std::cmp::Reverse(lovelace_of(u)),
3203        )
3204    });
3205
3206    let config = &chain.config;
3207    let policy = policy_hash(config)?;
3208    let lovelace = seedelf_minimum_lovelace(&chain.params)?;
3209    let wallet_addr = wallet_contract(chain.network_flag, config.contract.wallet_contract_hash);
3210    let redeemer = create_mint_redeemer(label.to_string())?;
3211
3212    let mut last_error = None;
3213    for k in 1..=spendable.len() {
3214        let inputs = &spendable[..k];
3215        let spent = |u: &UtxoResponse| inputs.iter().any(|i| same(i, u));
3216        let mut candidates: Vec<&UtxoResponse> = available.iter().collect();
3217        candidates.sort_by_key(|u| {
3218            (
3219                !pure_ada(u),
3220                lovelace_of(u) < ACCOUNT_COLLATERAL_MINIMUM,
3221                spent(u),
3222                lovelace_of(u) != COLLATERAL_LOVELACE,
3223                std::cmp::Reverse(lovelace_of(u)),
3224            )
3225        });
3226        let Some(collateral) = collateral.or(candidates.first().copied()) else {
3227            bail!("There is nothing in the Cardano account to pay for a Seedelf");
3228        };
3229
3230        let owned: Vec<Input> = inputs.iter().map(input_of).collect::<Result<_>>()?;
3231        let token_name = seedelf_token_name(label.to_string(), Some(&owned))?;
3232        let seedelf_output = Output::new(wallet_addr.clone(), lovelace)
3233            .set_inline_datum(seedelf.to_vec()?)
3234            .add_asset(policy, token_name.clone(), 1)
3235            .context("Failed To Add The Seedelf")?;
3236        let mint = AccountMint {
3237            chain: chain.clone(),
3238            inputs: inputs.to_vec(),
3239            collateral: collateral.clone(),
3240            seedelf: seedelf_output,
3241            redeemer: redeemer.clone(),
3242            change_addr: change_addr.clone(),
3243            staking: staking.clone(),
3244            invalid_hereafter,
3245            token_name,
3246            lovelace,
3247        };
3248        match mint.estimate() {
3249            Ok(_) => return Ok(mint),
3250            Err(e) if e.downcast_ref::<NotEnough>().is_some() => last_error = Some(e),
3251            Err(e) => return Err(e),
3252        }
3253    }
3254    Err(last_error.unwrap_or_else(|| ACCOUNT_MINT_SHORT.into()))
3255}