The authoritative contract an independent reimplementation of dig-tips could be built against. Layering: this
SPEC.mdis the repo's own contract;dig-cat'sSPEC.mdis the underlying CAT contract dig-tips delegates to; the ecosystemSYSTEM.mdmaps the cross-repo interactions. They must agree.
dig-tips is the DIG Network canonical tipping expert crate: a pure, key-free, network-free
builder that constructs the exact Chia CoinSpends to tip a CAT (including $DIG) to a recipient,
and defines the honest auto-tip policy logic (dig_ecosystem#377).
A tip is a single-payment CAT send: dig-tips does not re-derive any CAT spend bytes — it wraps
dig-cat's build_cat_spend with exactly one payment and adds
only the tipping semantics (the recipient, the honesty policy, the pure cap decisions). dig-tips has
no read path and therefore cannot gate or slow content consumption.
- dig-tips never holds a secret key, never signs, never performs I/O or network access.
- Builders take only public inputs and append unsigned
CoinSpends to a caller-ownedSpendContext. - A
required_signatures-style function reports the exact signatures the caller must produce; the caller signs, assembles theSpendBundle, and broadcasts. - dig-tips never signs an unbound / caller-controllable message (the custody-oracle rule).
- The auto-tip is default-on but transparent, capped, one-click-off, and dismissible.
- Tipping never gates or slows a content read.
- The default auto-tip recipient is the canonical
dig-constants::DIG_TREASURY_INNER_PUZZLE_HASH(reused, never hand-copied). - The tip coin memo is minimal on-chain (NC-8).
default_recipient() -> Bytes32— the default auto-tip recipient. MUST equaldig_constants::DIG_TREASURY_INNER_PUZZLE_HASHbyte-for-byte (reused, never copied).TREASURY_ADDRESS: &str— the bech32mxch1…form, re-exported fromdig_constants::DIG_TREASURY_ADDRESS.
enum TipMode { Auto, Manual }— whether tips fire automatically or require explicit approval.struct AutoTipPolicy { enabled: bool, mode: TipMode, asset_id: Bytes32, recipient: Bytes32, tip_amount: u64, threshold: u64, max_tips_per_day: u32, max_amount_per_day: u64 }— the honest, capped configuration. Every field is public (a settings UI renders + persists it directly).AutoTipPolicy::dig_default(asset_id) -> Self— enabled,Auto,recipient == default_recipient(), with the default caps (DEFAULT_TIP_AMOUNT= 1000,DEFAULT_THRESHOLD= 0,DEFAULT_MAX_TIPS_PER_DAY= 50,DEFAULT_MAX_AMOUNT_PER_DAY= 50000).AutoTipPolicy::disabled(asset_id) -> Self—enabled = false(the one-click-off), retaining the canonical recipient + caps.
struct LedgerSnapshot { tips_today: u32, amount_today: u64 }— today's caller-owned counters.enum CapReason { FrequencyCap, AmountCap }.enum TipDecision { Tip { amount }, SkipDisabled, SkipBelowThreshold, SkipManualNotApproved, SkipCapReached { reason } }.decide_auto_tip(policy, primary_send_amount, ledger) -> TipDecision— checks, in order: enabled →Automode →primary_send_amount >= threshold→ caps. Tipspolicy.tip_amount.decide_daily_tip(policy, ledger) -> TipDecision— as above minus the threshold check.decide_manual_tip(policy, requested_amount, approved, ledger) -> TipDecision— checks enabled →approved→requested_amount != 0→ caps. Independent ofmode(an explicit user action). Tipsrequested_amount.apply_tip(ledger, amount) -> LedgerSnapshot— advances both counters withsaturating_add.is_new_utc_day(last_tip_unix, now_unix) -> bool— pure day-rollover predicate for cap resets (div_euclidby 86400; correct for negative timestamps).
Cap enforcement (custody-critical, MUST hold): both cap checks use checked_add — the frequency
check on tips_today + 1, the amount check on amount_today + amount. An arithmetic overflow is
treated as cap reached, never as a wrap that slips past the ceiling. A u64::MAX running amount
therefore yields SkipCapReached { AmountCap }, never a Tip.
enum TipEvent { TipPlanned { amount, recipient }, TipDeferredCap { reason }, TipDeclined, TipSkippedDisabled, TipSkippedBelowThreshold }.TipEvent::from_decision(&TipDecision, recipient) -> Option<TipEvent>— mapsTip → TipPlanned,SkipCapReached → TipDeferredCap,SkipManualNotApproved → TipDeclined,SkipDisabled → TipSkippedDisabled,SkipBelowThreshold → TipSkippedBelowThreshold. Every decision maps toSome(theOptionreserves room for a future silent decision).
struct TipRequest { cats: Vec<Cat>, owner_pk: PublicKey, asset_id: Bytes32, recipient: Bytes32, amount: u64, change_p2_puzzle_hash: Bytes32 }.build_tip(req) -> Result<UnsignedCatSpend>— wrapsdig_cat::build_cat_spendwith oneCatPayment::new(recipient, amount)(minimal memo). Value is conserved (plan.delta == 0). It NEVER returns a degenerate empty spend — a zero amount or a shortfall is anErr.build_tip_if_allowed(policy, primary_send_amount, ledger, cats, owner_pk, change_p2_puzzle_hash) -> Result<(TipDecision, Option<UnsignedCatSpend>)>— the canonical GUARDED path: it decides first and builds the spend ONLY onTipDecision::Tip; every skip returns(decision, None)with no spend constructed, so a capped/declined tip can never be built.
required_signatures(&[CoinSpend], &Network) -> Result<Vec<RequiredSignature>>andenum Networkare re-exported verbatim fromdig-cat. dig-tips adds no signing logic. The caller signs the reported messages, aggregates, and broadcasts.
Bytes32, Coin, Cat, CatInfo, CoinSpend, PublicKey, UnsignedCatSpend (via dig-cat), plus
VERSION / version().
- Treasury pin (KAT).
default_recipient() == dig_constants::DIG_TREASURY_INNER_PUZZLE_HASHandTREASURY_ADDRESS == dig_constants::DIG_TREASURY_ADDRESS. - Cap overflow (KAT). With
amount_today = u64::MAX,decide_auto_tipreturnsSkipCapReached { AmountCap }(no panic, no wrap); withtips_today = u32::MAX, it returnsSkipCapReached { FrequencyCap }. - On-chain round-trip. A tip built by
build_tip, signed for the reported signatures, validates on the Chia simulator (TESTNET11); value routes exactly (amountto the recipient, remainder to change); the owner key is a required signer. - Minimal memo (NC-8). The recipient coin's
CREATE_COINmemo list is exactly[recipient]— the recipient hint only, no identity, no extra memos. - Golden vector. For a fixed owner seed + fixed input, the recipient CAT coin id is stable
(
fixed_input_tip_is_a_stable_golden), pinning the built spend against drift.
enum Error { Cat(dig_cat::CatError) }— the sole fallible surface is the on-chain spend construction, surfaced verbatim fromdig-cat(ZeroAmountfor a zero tip,InsufficientFunds/TooManyInputsfrom coin selection). A policy skip is aTipDecision, NOT an error. dig-tips never collapses a build failure into a silent empty spend.