Skip to content

feat(node): tipping subsystem — owner lookup + auto-tip engine + $DIG spend + tip ledger (#378)#25

Merged
MichaelTaylor3d merged 1 commit into
mainfrom
feat/tipping-subsystem
Jul 12, 2026
Merged

feat(node): tipping subsystem — owner lookup + auto-tip engine + $DIG spend + tip ledger (#378)#25
MichaelTaylor3d merged 1 commit into
mainfrom
feat/tipping-subsystem

Conversation

@MichaelTaylor3d

@MichaelTaylor3d MichaelTaylor3d commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

TLDR

Node-side tipping subsystem (#378, child of the auto-tip epic #377): the dig-node resolves a store's on-chain owner, runs an auto-tip policy engine (hard caps + crash-safe idempotency + fail-closed-on-unreadable-state, all fail-closed), builds+signs+broadcasts the $DIG CAT tip via the canonical Cat::spend_all path (never hand-rolled CLVM), records a persisted tip ledger (durable fsync writes), and exposes tip.* over the WS wallet/control transport (§4.8) with a {type:"tip"} push. Both creator and dev-account tips are default-ON — the dev tip pays the existing canonical DIG treasury shared contract (digstore_chain::dig::treasury_inner_puzzle_hash(), the per-capsule-payment recipient), sourced not re-hardcoded. No live mainnet broadcast in tests (simulator + MockBroadcaster).

Draft — not for merge until re-verified. Extension Tip tab/toggle (#379/#380) is a separate later branch.

What changed

  • crates/dig-wallet/src/sage/tipping.rs (new) — the engine + seams:
    • TippingConfig / AutoTipPolicy / TipMode (persisted tipping-config.json; creator + dev both default-ON).
    • Dev recipient = dig_treasury_ph_hex()digstore_chain::dig::treasury_inner_puzzle_hash() (the shared-contract treasury inner PH ec7c…6ce8; sourced, never re-hardcoded — no 4th copy to drift).
    • OwnerResolver seam + ChainOwnerResolver (reuses digstore_chain::singleton::sync_datastore(...).info.owner_puzzle_hash, cached per store).
    • TipSpender seam + NodeTipSpender.
    • TippingEngine: auto_tip_for_store / dev_daily_tip / manual_tip / get_config / set_config / get_ledger; TipEventBus for the WS push.
  • crates/dig-wallet/src/sage/rpc.rsWalletBackend: tipping wiring; build_and_broadcast_dig_tip ($DIG CAT via spend::build_cat_send, dig-clvm validate, node-custody sign, injected broadcaster); tip.* dispatch.
  • crates/dig-node-service/src/wallet_authz.rs — tip mutations paired-token gated (§7.12); reads open.
  • crates/dig-node-service/src/server.rs/ws forwards TipEvent as {type:"tip"} on a dedicated bus.
  • crates/dig-wallet/src/sage/service.rs — offline-safe bring-up attaches the engine.
  • SPEC.md — §18.23 (tipping) + §4.8 {type:"tip"} frame.
  • Version: minor 0.20.0 → 0.21.0 (workspace/binary) + dig-wallet 0.7.0 → 0.8.0 — additive feat.

Money-safety design (real mainnet $DIG)

  • Hard caps, fail-closed — per-site/day + a daily total cap spanning creator + dev. Reserved(Pending) + Confirmed + ambiguous-Failed all count → no double-count into over-spend. Over-cap → skip.
  • Crash-safe idempotency — a Pending ledger reservation is persisted immediately before the broadcast (the only money-moving step). A crash leaves ≤1 reserved entry per (kind, site, UTC-day); on restart the reloaded engine treats it as already-tipped and skips — never a double-spend. Pre-broadcast NotExecutable rolls back (retryable); an ambiguous broadcast error keeps Failed (never retried that day).
  • Fail-closed on unreadable persisted state (HIGH-review fix) — load distinguishes ABSENT (genuine first run: config→default-on, ledger→empty) from PRESENT-but-unreadable/unparseable. A present-but-unreadable ledger poisons the engine → every tip + config mutation refused (state-unreadable) until resolved, so a corrupt/locked/truncated ledger can NEVER reset caps+idempotency to "empty → tip freely". A present-but-unreadable config fails closed to DISABLED (never silently re-enables a disabled auto-tip). unwrap_or_default() on the persisted read is gone.
  • Durable writes — temp file → fsync → atomic rename → parent-dir fsync (best-effort), so a crash can't leave a truncated ledger.
  • No live broadcast in dev/tests — engine logic tested against a recording MockSpender; the $DIG CAT spend build tested on the chia-sdk-test simulator + MockBroadcaster. The shipped node ships no broadcaster yet (offline-safe bring-up) so a tip cleanly reports NotExecutable/wallet-unavailable until the wallet spend path's live sync/lineage lands (§18.12).

How verified

  • cargo test -p dig-wallet246 + 22 passed, 0 failed — incl. the money-safety regressions: corrupt/truncated ledger → fail-closed no re-tip; corrupt ledger after a real tip → no double-spend; corrupt config → not re-enabled; absent files → clean first run still tips; durable reload; per-site + shared daily caps; once-per-site/day idempotency; crash-retry; dev tip pays the treasury once/day; NotExecutable rollback; ambiguous-not-retried; manual bypasses caps/idempotency.
  • dig_tip_cat_send_records_via_mock_broadcaster_never_sends — the $DIG CAT tip builds+validates(dig-clvm)+signs, records via MockBroadcaster (not sent), and the same bundle is accepted by the real simulator.
  • cargo clippy --all-targets --all-features -- -D warnings clean; cargo fmt --all --check clean. Full gh pr checks state reported on the PR.

Blast radius

Additive: new tipping module + additive WalletBackend methods/fields + a tip.* dispatch prefix + a GATED_WALLET_MUTATIONS extension + one /ws select arm. No existing method / wire shape / Sage-parity surface changed (SUPPORTED_METHODS unchanged → Sage OpenAPI conformance untouched; tip events use a separate bus so GET /events is unchanged).

Recipients — canonical shared contracts (no new constants)

  • Creator tip → the on-chain-resolved store owner PH.
  • Dev-account tip → the existing DIG treasury inner PH (digstore_chain::dig::treasury_inner_puzzle_hash()), the same value that receives per-capsule payments. No placeholder, no pending user input.

Refs #377 · Closes #378

@MichaelTaylor3d MichaelTaylor3d changed the title feat(node): tipping subsystem — owner lookup + auto-tip policy engine + $DIG spend + tip ledger (#378) feat(node): tipping subsystem — owner lookup + auto-tip engine + $DIG spend + tip ledger (#378) Jul 12, 2026
@MichaelTaylor3d MichaelTaylor3d force-pushed the feat/tipping-subsystem branch 3 times, most recently from f3a1d9d to 2732cfa Compare July 12, 2026 07:18
… spend + tip ledger (#378)

Node-side tipping subsystem (#378, child of the auto-tip epic #377): the node
resolves a store's on-chain owner, runs the auto-tip policy engine (hard caps +
crash-safe idempotency, all fail-closed), builds+signs+broadcasts the $DIG CAT
tip via the canonical Cat::spend_all path, records a persisted tip ledger, and
exposes tip.* over the WS wallet/control transport with a {type:"tip"} push.

Creator auto-tip is default-on (real on-chain recipient); the DIG dev-account
daily tip is default-off and REFUSES while DIG_DEV_ACCOUNT_PH is the placeholder.
No live mainnet broadcast in tests (simulator + MockBroadcaster).

Refs #377
Closes #378

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d MichaelTaylor3d force-pushed the feat/tipping-subsystem branch from 5b778a0 to 94c7ef9 Compare July 12, 2026 07:29
@MichaelTaylor3d MichaelTaylor3d marked this pull request as ready for review July 12, 2026 07:30
@MichaelTaylor3d MichaelTaylor3d merged commit 63bb3e9 into main Jul 12, 2026
9 checks passed
@MichaelTaylor3d MichaelTaylor3d deleted the feat/tipping-subsystem branch July 12, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant