Skip to main content

Physical Notes (/vault withdraw)

Converts digital balance into redeemable physical paper items. Each note carries an HMAC-SHA256 signature that makes duplication via NBT / external plugins infeasible.

Withdrawing Notes

Required permission: vault.withdraw (default true). Flow:
  1. Validates positive amount and withdraws from wallet with TxType.NOTE_WITHDRAW
  2. Generates a random 16-char hex noteId (truncated UUID)
  3. Builds an ItemStack of Material.PAPER with display name + i18n lore
  4. Stores signed metadata and delivers the item to the player

Secure Storage — Dual Layer

Layer 1: PersistentDataContainer (1.14+)

If the NamespacedKey + PersistentDataContainer API is available at runtime, 6 keys are saved via reflection (PhysicalNoteService.java:89–132): Signed payload: noteId|cid|amount|issuerId|issuedAt

Layer 2: Legacy Lore HMAC (1.8 – 1.13)

If PDC does not exist, the signature written as the last lore line is validated:
Lore tags are extracted via dynamic parsers.

Dynamic i18n Label Extraction

For compatibility with 11 languages and pre-v2.1 legacy notes, extractFallback() detects prefixes such as “Nº serie”, “Importe”, “Moneda” using labelStarts() + hardcoded fallback lists (PhysicalNoteService.java:408–482): Serial — Detects: Nº serie / Serial / S/N / Número de série / Seriennummer / Numer seryjny / Серийный номер / 序列号 / 序號 / क्रम संख्या Amount — Detects: Importe / Amount / Valor / Montant / Betrag / Bedrag / Kwota / Сумма / 金额 / 金額 / राशि Currency — Detects: Moneda / Currency / Moeda / Devise / Währung / Valuta / Waluta / Валюта / 货币 / 貨幣 / मुद्रा Additionally parseAmountLoose() interprets decimal/thousand separators . and , based on position (last occurrence = decimal).

Redeeming Notes

Right-click while holding the note (air or block) triggers PlayerInteractEvent. Flow:
  1. extractNoteData() validates PDC first, then lore fallback
  2. Checks redeemed_notes.yml table for already-redeemed IDs
  3. If new: deposits the amount → TxType.NOTE_REDEEM → marks as redeemed
  4. Removes 1 unit from the stack (or setItemInHand(null) if quantity = 1)

Anti-dupe Window + redeemed_notes

  • redeemed_notes.yml stores the global list of redeemed IDs (persisted on disk)
  • ConcurrentHashMap in memory as a fast cache
  • If HMAC does not match or the ID was already used, returns note.redeem.invalid or note.redeem.already_redeemed

HMAC Secret

Generated via SecureRandom → 32 bytes and saved in .note-secret.dat (hidden with attrib +H on Windows). Without this file, no existing note can be validated.