dig-did 0.7.0

The DIG Network canonical Chia DID expert crate: a pure, key-free, network-free SpendBundle-builder for Chia Decentralized Identifiers. Builds the exact CoinSpends for every DID lifecycle operation and reports the exact signatures a caller must produce — never holds a key, never signs, never broadcasts.
Documentation
//! Internal spend-construction helpers shared by every DID operation (SPEC §2).
//!
//! These are the two primitives every operation module builds on: turning an [`Owner`] into the
//! concrete inner [`Spend`] that authorizes a DID spend, and draining a [`SpendContext`] into the
//! flat `Vec<CoinSpend>` that [`crate::DidSpend`] exposes. Keeping them here means each operation
//! module reads as pure DID logic — the p2/context mechanics live in one place.

use chia_wallet_sdk::driver::{Spend, SpendContext, SpendWithConditions};
use chia_wallet_sdk::types::Conditions;

use crate::types::Owner;
use crate::DidResult;

/// Builds the inner (p2) [`Spend`] that authorizes a DID spend, given the DID's [`Owner`].
///
/// - [`Owner::Standard`] curries the standard single-key p2 layer over the owner key and emits the
///   supplied `conditions` from it (the usual path — one `AGG_SIG_ME` results).
/// - [`Owner::Custom`] returns the caller's pre-built inner spend unchanged, **dropping
///   `conditions`** — a pre-built `(puzzle, solution)` pair emits one fixed condition set and cannot
///   be extended after the fact.
///
/// # The drop is only sound where the caller could have known the conditions
///
/// Passing a non-empty `conditions` alongside an [`Owner::Custom`] silently produces a spend that
/// emits none of them, so every call site MUST either pass `Conditions::new()` for that variant or
/// refuse it. Conditions computed *inside* the call — launcher create/announcement conditions, a
/// singleton's recreation condition — are by construction unknowable to the caller, so any call site
/// that builds them refuses [`Owner::Custom`] with [`crate::DidError::UnsupportedOwner`] rather than
/// returning a well-formed bundle that creates none of the coins it reports.
///
/// Consumed by every DID operation module (create, update, hydrate follow-ups, and beyond).
pub(crate) fn inner_spend(
    ctx: &mut SpendContext,
    owner: Owner,
    conditions: Conditions,
) -> DidResult<Spend> {
    match owner {
        Owner::Standard(public_key) => {
            let layer = chia_wallet_sdk::driver::StandardLayer::new(public_key);
            Ok(layer.spend_with_conditions(ctx, conditions)?)
        }
        Owner::Custom(spend) => Ok(spend),
    }
}

/// Drains every coin spend accumulated in the [`SpendContext`] into a flat vector, in spend order.
///
/// A thin wrapper over [`SpendContext::take`] that names the intent at the DID call sites.
/// Consumed by every DID operation module that returns a [`crate::DidSpend`].
pub(crate) fn drain_coin_spends(ctx: &mut SpendContext) -> Vec<chia_protocol::CoinSpend> {
    ctx.take()
}

#[cfg(test)]
mod tests {
    use super::*;
    use chia_wallet_sdk::driver::Spend;
    use chia_wallet_sdk::prelude::PublicKey;

    #[test]
    fn standard_owner_builds_an_inner_spend() {
        let mut ctx = SpendContext::new();
        let owner = Owner::Standard(PublicKey::default());

        let spend = inner_spend(&mut ctx, owner, Conditions::new())
            .expect("standard layer should curry a spend");

        // A real puzzle + solution were allocated (they are distinct node pointers).
        assert_ne!(spend.puzzle, spend.solution);
        // Building the p2 puzzle staged CLVM into the context but no coin spend yet.
        assert!(drain_coin_spends(&mut ctx).is_empty());
    }

    #[test]
    fn custom_owner_passes_the_inner_spend_through_unchanged() {
        let mut ctx = SpendContext::new();
        // Two distinct pointers so we can prove they survive the passthrough byte-for-byte.
        let puzzle = ctx.one();
        let solution = ctx.nil();
        let prebuilt = Spend::new(puzzle, solution);

        let spend = inner_spend(&mut ctx, Owner::Custom(prebuilt), Conditions::new())
            .expect("custom passthrough never fails");

        assert_eq!(spend.puzzle, puzzle);
        assert_eq!(spend.solution, solution);
    }
}