Arcadia Auction House

Arcadia Auction House adds a cross-player marketplace with item listings, a mailbox for delivered purchases, and Numismatics currency support. Requires Arcadia Lib.

File Details

arcadia-ah-1.3.1

  • R
  • Jun 6, 2026
  • 135.53 KB
  • 15
  • 1.21.1
  • NeoForge

File Name

arcadia-ah-1.3.1.jar

Supported Versions

  • 1.21.1

Curse Maven Snippet

NeoForge

implementation "curse.maven:arcadia-ah-1493501:8204440"
Curse Maven does not yet support mods that have disabled 3rd party sharing

Learn more about Curse Maven

## [1.3.1] - 2026-06-05 (latest)

### Fixed

- **Sellers receiving less than expected on a sale (root cause + clarity)** — The reported "fee charged twice" was traced end-to-end: the listing fee is deducted exactly once, upfront, in `listItem`, and the seller is paid 100% of the price at sale time (`buyListing`) and 100% of the winning bid at auction finalisation (`BidManager`). There is no second fee deduction. The confusion came from (a) a misleading `BidManager` javadoc claiming the seller receives "the bid amount minus 1% fee" — a fee the code never applied — now corrected to state the seller receives the full bid because the fee was paid upfront, and (b) real payout-loss paths under DB faults, now closed (see below).
- **Critical coin duplication via concurrent mailbox claim** — `claimMailboxEntry` performed check-then-consume non-atomically: two rapid clicks on the same entry could both pass the recipient check and both credit the coins/item before either delete ran. Now an atomic conditional delete (`DELETE ... WHERE entry_id = ? AND recipient_uuid = ?`) is the ownership token — the payload is granted only to the single click that wins the delete. Item payloads are validated *before* the claim so a corrupt row is preserved for admin inspection.
- **Critical coin duplication via outbid refund** — `BidManager.placeBid` refunded an online previous bidder through *two* channels at once: a direct credit *and* a refund mailbox row that a racing async task tried to delete. A delete-before-insert-commit race left a persistent refund row the bidder could claim a second time. The refund now goes through exactly one channel: a direct credit if online, otherwise a single mailbox row — never both.
- **Cross-server / stale-cache double-buy and pay-for-nothing** — `buyListing` charged the buyer and minted the item from cached NBT before any authoritative DB check, so a listing already sold on another server (or consumed by the expiry sweep) could be bought again, duplicating the item and double-paying the seller. The buy now claims the DB row atomically *before* any item delivery or payout; a lost race fully refunds the buyer and delivers nothing. Degraded cross-server NBT (empty shulker) refunds the buyer and pays the seller nothing instead of charging for an empty item.
- **Item duplication: expiry sweep racing a buy** — The expiry sweep (`processExpired`) shared no lock with `buyListing` and never consulted `pendingSales`, so a buy landing at a listing's expiry instant could deliver the item to the buyer *and* return it to the seller's mailbox. The sweep now skips in-flight sales and consumes each listing through the same atomic claim, and a re-entrancy guard prevents two overlapping sweeps from double-processing the same listing.
- **No database transactions on multi-step money/item handoffs** — Buy, bid finalisation, outbid refund and expiry were sequences of independent auto-commit statements with swallowed exceptions, so a mid-sequence fault could lose an item or strand a bidder's escrowed coins. Won-auction finalisation (`finalizeWonAuctionTx`) and expired-listing return (`returnExpiredToMailboxTx`) now run as real all-or-nothing JDBC transactions (delete + deliver + pay + log), rolling back cleanly on any fault with `autoCommit` restored before the connection is returned to the pool.
- **Listing fee charged with no listing persisted** — `insertListing` was fire-and-forget with a swallowed `SQLException`: on insert failure the fee was taken and the item destroyed with nothing to show. It now reports success; on failure the fee is refunded and the item returned to the seller's mailbox.
- **Payouts/refunds on stale player references** — Every disbursement that completes after an async DB round-trip (buyer refund, seller payout, item delivery, mailbox claim, featured race-refund, cancel return) now re-resolves the player by UUID on the server thread via the new `payOrMail` / `giveOrMail` helpers, re-mailing the payload when the player has disconnected — so a logout mid-handoff can no longer lose or duplicate coins or items.
- **Integer-overflow listing-fee bypass** — `(price * percent)` could overflow to a negative value for a client-controlled astronomical price, collapsing the fee to 1 coin and poisoning revenue aggregations. Added a configurable `max_listing_price` cap (default 1e9) enforced at both the packet edge and the manager, plus an overflow-safe divide-first fee computation.
- **Auction House kill-switch not enforced on trade paths** — `AH_ENABLED` was checked only when opening the dashboard. Raw packets, a pre-opened dashboard and `/arcadia_ah sell|bid` bypassed the emergency freeze. A `tradingBlocked` guard (operators exempt) now gates listing, buying, cancelling, featuring, claiming and bidding.
- **Expiry sweep could latch off permanently** — If the async pool rejected the sweep submission (server shutting down), the re-entrancy guard was never released and every subsequent sweep early-returned forever. The submission is now wrapped so a rejected submit releases the guard.

### Changed

- **`arcadia-lib` dependency jar refreshed to 1.2.12** — `libs/` carried an outdated `arcadia-lib-1.2.5.jar` while the project targets the documented 1.2.10+ floor. Replaced with `arcadia-lib-1.2.12.jar`; full build verified.

### Correctifs

- **Vendeurs recevant moins que prévu lors d'une vente (cause racine + clarté)** — Le « frais prélevés deux fois » signalé a été tracé de bout en bout : les frais de mise en vente sont prélevés une seule fois, en amont, dans `listItem`, et le vendeur reçoit 100 % du prix à la vente (`buyListing`) et 100 % de l'enchère gagnante à la finalisation (`BidManager`). Il n'y a aucune seconde déduction. La confusion venait (a) d'un javadoc `BidManager` mensonger affirmant que le vendeur reçoit « le montant de l'enchère moins 1 % de frais » — un frais que le code n'applique jamais — désormais corrigé pour indiquer que le vendeur reçoit l'enchère complète car les frais ont été payés en amont, et (b) de vrais chemins de perte de paiement sous fautes DB, désormais corrigés (voir ci-dessous).
- **Duplication de pièces critique via réclamation concurrente de la boîte aux lettres** — `claimMailboxEntry` effectuait une vérification-puis-consommation non atomique : deux clics rapides sur la même entrée pouvaient tous deux passer le contrôle de destinataire et créditer les pièces/objet avant qu'aucune suppression ne s'exécute. Désormais, une suppression conditionnelle atomique (`DELETE ... WHERE entry_id = ? AND recipient_uuid = ?`) sert de jeton de propriété — le contenu n'est accordé qu'au seul clic qui gagne la suppression. Les objets sont validés *avant* la réclamation pour préserver une ligne corrompue à des fins d'inspection admin.
- **Duplication de pièces critique via remboursement de surenchère** — `BidManager.placeBid` remboursait un enchérisseur précédent en ligne par *deux* canaux à la fois : un crédit direct *et* une ligne de remboursement en boîte aux lettres qu'une tâche async concurrente tentait de supprimer. Une course suppression-avant-insertion laissait une ligne de remboursement persistante que l'enchérisseur pouvait réclamer une seconde fois. Le remboursement passe désormais par un seul canal : crédit direct si en ligne, sinon une seule ligne de boîte aux lettres — jamais les deux.
- **Double-achat cross-serveur / cache périmé et achat sans contrepartie** — `buyListing` débitait l'acheteur et créait l'objet depuis le NBT en cache avant tout contrôle DB autoritaire, donc une annonce déjà vendue sur un autre serveur (ou consommée par le balayage d'expiration) pouvait être rachetée, dupliquant l'objet et payant le vendeur deux fois. L'achat réclame désormais la ligne DB atomiquement *avant* toute livraison ou paiement ; une course perdue rembourse intégralement l'acheteur et ne livre rien. Un NBT cross-serveur dégradé (shulker vide) rembourse l'acheteur et ne paie rien au vendeur au lieu de facturer un objet vide.
- **Duplication d'objet : balayage d'expiration en course avec un achat** — Le balayage d'expiration (`processExpired`) ne partageait aucun verrou avec `buyListing` et ne consultait jamais `pendingSales`, donc un achat survenant à l'instant d'expiration pouvait livrer l'objet à l'acheteur *et* le renvoyer dans la boîte du vendeur. Le balayage ignore désormais les ventes en cours et consomme chaque annonce via la même réclamation atomique, et une garde de ré-entrance empêche deux balayages chevauchants de traiter deux fois la même annonce.
- **Aucune transaction de base de données sur les transferts argent/objet multi-étapes** — Achat, finalisation d'enchère, remboursement de surenchère et expiration étaient des séquences d'instructions auto-commit indépendantes avec exceptions avalées, donc une faute en milieu de séquence pouvait perdre un objet ou bloquer les pièces séquestrées d'un enchérisseur. La finalisation d'enchère gagnée (`finalizeWonAuctionTx`) et le retour d'annonce expirée (`returnExpiredToMailboxTx`) s'exécutent désormais comme de vraies transactions JDBC tout-ou-rien (suppression + livraison + paiement + journal), avec rollback propre sur toute faute et `autoCommit` restauré avant le retour de la connexion au pool.
- **Frais de mise en vente prélevés sans annonce persistée** — `insertListing` était « fire-and-forget » avec une `SQLException` avalée : en cas d'échec d'insertion, les frais étaient pris et l'objet détruit sans contrepartie. Il signale désormais le succès ; en cas d'échec, les frais sont remboursés et l'objet renvoyé dans la boîte du vendeur.
- **Paiements/remboursements sur références de joueur périmées** — Chaque versement qui se termine après un aller-retour DB async (remboursement acheteur, paiement vendeur, livraison d'objet, réclamation boîte aux lettres, remboursement de course featured, retour d'annulation) re-résout désormais le joueur par UUID sur le thread serveur via les nouveaux helpers `payOrMail` / `giveOrMail`, renvoyant le contenu en boîte aux lettres si le joueur s'est déconnecté — un logout en cours de transfert ne peut donc plus perdre ni dupliquer pièces ou objets.
- **Contournement des frais par dépassement d'entier** — `(prix * pourcentage)` pouvait déborder vers une valeur négative pour un prix astronomique contrôlé par le client, réduisant les frais à 1 pièce et corrompant les agrégations de revenus. Ajout d'un plafond configurable `max_listing_price` (défaut 1e9) appliqué au niveau du paquet et du manager, plus un calcul de frais sûr (division d'abord).
- **Coupe-circuit de l'Hôtel des Ventes non appliqué sur les chemins de transaction** — `AH_ENABLED` n'était vérifié qu'à l'ouverture du tableau de bord. Les paquets bruts, un tableau de bord déjà ouvert et `/arcadia_ah sell|bid` contournaient le gel d'urgence. Une garde `tradingBlocked` (opérateurs exemptés) contrôle désormais mise en vente, achat, annulation, mise en avant, réclamation et enchère.
- **Le balayage d'expiration pouvait se bloquer définitivement** — Si le pool async rejetait la soumission du balayage (serveur en arrêt), la garde de ré-entrance n'était jamais relâchée et tout balayage suivant retournait immédiatement à jamais. La soumission est désormais encapsulée pour qu'un rejet relâche la garde.

### Modifications

- **Jar de dépendance `arcadia-lib` mis à jour en 1.2.12** — `libs/` contenait un `arcadia-lib-1.2.5.jar` obsolète alors que le projet vise le plancher documenté 1.2.10+. Remplacé par `arcadia-lib-1.2.12.jar` ; build complet vérifié.