Skip to content

feat(payouts): confirm payouts on-chain with a view-only wallet (#381)#471

Merged
VijitSingh97 merged 3 commits into
develop-v1.5from
claude/v15-payout-confirm-381
Jul 11, 2026
Merged

feat(payouts): confirm payouts on-chain with a view-only wallet (#381)#471
VijitSingh97 merged 3 commits into
develop-v1.5from
claude/v15-payout-confirm-381

Conversation

@VijitSingh97

Copy link
Copy Markdown
Collaborator

Summary

Adds optional on-chain payout confirmation (v1.5). Every earnings figure the dashboard shows is an estimate; nothing checked that a P2Pool payout actually landed. Now, when the operator sets monero.view_key (the private view key for their payout address), the stack runs a view-only monero-wallet-rpc against the local node (scan-only — a view key cannot spend) and the dashboard confirms payouts from the chain (the coinbase outputs P2Pool pays you are the only ground truth).

  • Container: monero-wallet-rpc extracted from the already-verified CLI tarball (no new image/pin). New wallet-rpc compose service on a payout_confirm profile — gated on the view key being set + local mode, non-root (uid 1000), read-only rootfs + tmpfs, loopback-only publish 127.0.0.1:18082, depends on monerod healthy. The view key reaches it via env + a tmpfs file, never the command line.
  • pithead: validates/renders monero.view_key (refused on a remote node; 64-hex checked) + monero.payout_scan_height; generates & preserves the wallet-rpc login; masks view_key in the control config; keeps the secrets off the Stack config editor: change any setting from the dashboard, applied via pithead (incl. P2Pool mode hot-swap) #33 dashboard-editable allowlist (named deliberately in the comment).
  • Dashboard: MoneroWalletClient polls get_transfers on the slow (~5 min) cadence; new payouts table keyed (chain, txid) so the Tari sibling Confirm Tari payouts on-chain with a view-only minotari wallet (sibling of #381) #462 reuses this exact table with chain="tari" (this feature sets chain="monero"); confirmed 24h/7d/all-time totals beside the estimate; one payout_confirmed alert per new payout (idempotent storage + height seeding ⇒ no replay on restart; coinbase recorded on confirmation, not maturity).
  • Docs: configuration.md entries; a "Payout confirmation" section in dashboard.md stating the full view-key security model; CHANGELOG under [Unreleased].

Security model

  • A view key cannot spend — it can only scan — but it reveals every incoming payout amount and timing to anyone who reads config.json/.env. Handled exactly like node_password: owner-only .env, never logged/echoed, off the dashboard config editor, never on a container command line (red-then-green no-leak test).
  • The wallet-rpc stays on the box: loopback-only publish, non-root, read-only rootfs, password-authenticated; Phase 1 is local node only (a view key with monero.mode: remote is refused).
  • Empty view key (the default) = feature off, nothing new runs.

payouts table schema

payouts (chain TEXT, txid TEXT, height INTEGER, ts REAL, amount_atomic INTEGER, PRIMARY KEY (chain, txid)) — shared with #462.

Tests

make lint + make test green. Dashboard coverage 96.7% (≥80); patch coverage 97–98% (≥90). New: wallet client parsing + payouts round-trip/idempotency + alert edge (tier 1), fake wallet-rpc contract test (tier 2), config→.env render + view-key-never-leaked (tier-1 shell).

Closes #381

🤖 Generated with Claude Code

VijitSingh97 and others added 3 commits July 11, 2026 13:47
Add optional on-chain payout confirmation: when the operator supplies the
private VIEW key for monero.wallet_address, the stack runs a view-only
monero-wallet-rpc against the LOCAL node (scan-only, cannot spend) and the
dashboard confirms P2Pool coinbase payouts actually landed.

- build/monero: extract monero-wallet-rpc from the already-verified CLI tarball
  (no new image/pin); new wallet-entrypoint.sh (create-from-view-key, key via
  tmpfs file never argv) + wallet-healthcheck.sh.
- docker-compose: new wallet-rpc service on the payout_confirm profile, non-root
  uid 1000, read-only rootfs + tmpfs, loopback-only publish 127.0.0.1:18082,
  gated on the view key + local mode, depends on monerod healthy.
- pithead: validate/render monero.view_key (secret, handled like node_password;
  refused on a remote node; 64-hex checked) + payout_scan_height; generate +
  preserve the wallet-rpc login; add the payout_confirm profile; mask view_key
  in the control config; keep the secrets off the #33 editable allowlist.
- dashboard: MoneroWalletClient polls get_transfers on the 10th-poll cadence;
  new payouts table keyed (chain, txid) so the Tari sibling (#462) reuses it;
  confirmed 24h/7d/all-time totals beside the earnings estimate; one
  payout_confirmed alert per new txid (idempotent storage => no replay on
  restart; coinbase recorded on confirmation, not maturity).
- docs: configuration.md entries, a Payout confirmation section in dashboard.md
  stating the full view-key security model, CHANGELOG under [Unreleased].
- tests: wallet client parsing + payouts round-trip/idempotency + alert edge
  (tier 1); fake wallet-rpc contract test (tier 2); config->.env render +
  view-key-never-leaked (tier-1 shell).

Closes #381

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…yout-confirm-381

# Conflicts:
#	CHANGELOG.md
- gitleaks: generalize the curl-auth-user allowlist to ${VAR:-default} env
  expansions (wallet-healthcheck.sh uses ${WALLET_RPC_USERNAME:-wallet}); the
  matched 'secret' is shell interpolation, not a hardcoded credential.
- shfmt: reindent wallet-entrypoint.sh + wallet-healthcheck.sh to the repo -i 4.
- wallet client: skip a transfer whose amount/height/timestamp is non-numeric or
  out of signed-64 range instead of aborting the whole scan (would stall payout
  confirmation by never advancing min_height); the 64-bit bound also keeps the
  add_payouts INTEGER insert from OverflowError. + test for the skip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@VijitSingh97
VijitSingh97 merged commit e9d9f7e into develop-v1.5 Jul 11, 2026
16 checks passed
@VijitSingh97
VijitSingh97 deleted the claude/v15-payout-confirm-381 branch July 11, 2026 19:57
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