Paradaux
IssuesPAR-158Backlog
0

ChestShop sales route through the "ChestShop System" clearing account (2 txns/sale) instead of clean buyer→seller transfers

Finding (prod data)

Every ChestShop sale is recorded as two ledger transactions bridged by a SYSTEM clearing account #4 "ChestShop System", not as one buyer→seller transfer. All 1.26M ChestShop ledger txns are 2-posting, and account #4 carries ~1.26M postings (it's the single busiest account in the DB — the "whale" behind the slow account pages).

Example — one sale "siven01 sold x8 Feather to GovInterior" is a pair:

txnaccountamount
1782332GovInterior (buyer)−3.47
1782332ChestShop System+3.47
1782333ChestShop System−3.47
1782333siven01 (seller)+3.47

i.e. buyer → ChestShop System then ChestShop System → seller.

Root cause (code)

ChestShop moves money as two single-sided operations (subtract from buyer, add to seller) via the legacy Vault economy model — com/Acrobot/ChestShop/Economy/Economy.java + Events/Economy/CurrencySubtractEvent etc., bridged by TreasuryListener. The Vault API has no atomic transfer(from,to), so Treasury's VaultEconomyAdapter books each single-sided op against the plugin's SYSTEM account:

  • treasury .../services/LedgerService.java — explicit: Direct value transfer (no plugin SYSTEM postings) (the clean path, used by rest-api/business) vs Vault-style: withdraw … into plugin SYSTEM (burn) / deposit from plugin SYSTEM (mint).
  • AccountServiceImpl.java:210 — "Vault bridge SYSTEM accounts are faucets/sinks. credit_limit = -1". One per plugin (Exams, nightcore, … all owned by the sentinel UUID 00…01); ChestShop's is just the highest-volume.

The system account nets ~0 over time — it's a pass-through, not money creation.

Impact

  • Doubles ChestShop txn/posting volume (each sale = 2 txns).
  • Account #4 = 1.26M postings → the perf hotspot behind slow /accounts/[id], money-flow, etc. (mitigated but not removed by PAR-156/157).
  • Distorts analytics: the large SYSTEM↔PERSONAL flows in money-flow are mostly shop sales passing through the clearing account.
  • Non-atomic: withdraw-then-deposit can strand money in the system account if it fails between legs (vs a native transfer which is atomic).

Proposed fix (we own the ChestShop-3 fork)

For player shop sales, have ChestShop-3 call Treasury's native transfer (TreasuryApi.transfer(buyer→seller) / the "Direct value transfer, no SYSTEM postings" path) instead of the Vault subtract+deposit — collapsing each sale into one clean 2-leg buyer→seller transaction, no system account.

Caveats / scope

  • Admin/server shops genuinely need the sink — infinite-stock server shops have no real counterparty (see ServerAccountCorrector), so those keep a faucet/sink leg. The fix must distinguish player shops from server/admin shops.
  • ChestShop's event model is single-sided (CurrencySubtractEvent / hold / return-on-failure), so a transfer-based path is a real integration change in the fork, not a config flip — likely a new bridge route in TreasuryListener + possibly a transfer-shaped path that still honours ChestShop's hold/refund semantics.
  • No historical backfill — leave the existing two-hop rows as legacy; document the cutover so analytics treat pre-fix ChestShop volume as doubled.

Acceptance

  • A player-to-player ChestShop purchase produces one ledger transaction (buyer −X, seller +X), no ChestShop System posting.
  • Admin/server-shop purchases still book against the system/server account (no real seller).
  • Failure path leaves no money stranded (atomic).
  • Verify post-cutover that ChestShop System (#4) stops accruing new postings for player sales.

Surfaced from an economy-data review; relates to the account-detail perf work (PAR-157) and the /pay attribution work (PAR-145).

Comments

No comments yet.

Activity

  • tesks created the issueJun 14, 2026, 1:56 PM