Skip to content

Mirrors tracked repository source docs/premium-payments.md.

Premium Payments

This app supports Lux purchases from EVM stablecoin transfers and a Lux shop redemption flow for premium time.

Current pricing:

  • 1 USDC or USDT credits 100 Lux Coins
  • 500 Lux Coins redeems 31 days of premium

Treasury wallet for production:

  • 0x33b42Bad3E5ee28944aDA778E25f01679af38ebB

Required environment

These values must be set on any deployment that should accept premium payments:

bash
PREMIUM_TREASURY_ADDRESS=0x33b42Bad3E5ee28944aDA778E25f01679af38ebB

The premium service only enables a chain when that chain has an RPC URL and at least one token route.

RPC env keys:

bash
ETHEREUM_RPC_URL=...
POLYGON_RPC_URL=...
BASE_RPC_URL=...
OPTIMISM_RPC_URL=...
ARBITRUM_RPC_URL=...
BSC_RPC_URL=...
MEGAETH_RPC_URL=...
MONAD_RPC_URL=...

The default runtime path now tracks only submitted active orders directly by tx hash over RPC. It does not need the external finalized-payment watch service.

Optional legacy fallback if you explicitly want treasury-wide finalized-history scans:

bash
PREMIUM_FINALIZED_PAYMENT_WATCH_ENABLED=true
PREMIUM_PAYMENTS_SERVICE_URL=...
PREMIUM_PAYMENTS_SERVICE_TOKEN=...

Leave PREMIUM_FINALIZED_PAYMENT_WATCH_ENABLED unset in production unless you intentionally want that broader watch behavior. The direct active-order path is the recommended default and is the only mode suitable for low-volume/free RPC usage.

Built-in token routes

When the issuer documentation clearly publishes a mainnet contract address, the app ships with that address as the default. In those cases, production only needs the matching RPC URL plus the shared premium-payment env above.

Verified built-in defaults:

ChainTokenAddress source
EthereumUSDCCircle USDC contract addresses
EthereumUSDTTether supported protocols
PolygonUSDCCircle USDC contract addresses
PolygonUSDTVerified Polygon mainnet USDT contract
BaseUSDCCircle USDC contract addresses
BaseUSDTVerified Base bridged Tether USD contract
OptimismUSDCCircle USDC contract addresses
OptimismUSDT0Verified Optimism USDT0 contract
ArbitrumUSDCCircle USDC contract addresses
ArbitrumUSDTVerified Arbitrum mainnet USDT contract
BSCUSDCVerified BSC USDC contract
BSCUSDTVerified BSC USDT contract
MegaETHUSDT0Verified MegaETH USDT0 contract
MonadUSDCCircle USDC contract addresses
MonadUSDT0Verified Monad USDT0 contract

The app still does not hardcode MegaETH USDC, because I still do not have a verified canonical address for that route.

Routes that still require explicit token-address env vars if you want them enabled:

  • PREMIUM_MEGAETH_USDC_ADDRESS

Any built-in default can also be overridden explicitly with the same env naming pattern:

bash
PREMIUM_ETHEREUM_USDC_ADDRESS=...
PREMIUM_ETHEREUM_USDT_ADDRESS=...

If you override a route whose canonical symbol is not the plain USDT ticker, you can also override the player-facing symbol explicitly:

bash
PREMIUM_OPTIMISM_USDT_SYMBOL=USDT0
PREMIUM_MEGAETH_USDT_SYMBOL=USDT0
PREMIUM_MONAD_USDT_SYMBOL=USDT0

The premium modal now also shows the selected token contract address alongside the treasury address so manual senders can confirm they are transferring the exact token contract the app expects.

Production example

Example production env using the provided RPC endpoints and treasury wallet:

bash
PREMIUM_TREASURY_ADDRESS=0x33b42Bad3E5ee28944aDA778E25f01679af38ebB

ETHEREUM_RPC_URL=https://mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2
POLYGON_RPC_URL=https://polygon-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2
BASE_RPC_URL=https://base-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2
OPTIMISM_RPC_URL=https://optimism-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2
ARBITRUM_RPC_URL=https://arbitrum-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2
BSC_RPC_URL=https://bsc-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2
MEGAETH_RPC_URL=https://megaeth-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2
MONAD_RPC_URL=https://monad-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2

If you choose to keep the legacy finalized watch service available for emergency fallback, do not enable it by default:

bash
PREMIUM_FINALIZED_PAYMENT_WATCH_ENABLED=false
PREMIUM_PAYMENTS_SERVICE_URL=https://<stablecoin-payments-service>
PREMIUM_PAYMENTS_SERVICE_TOKEN=<token>

Add any missing token-address env vars for routes without a built-in default before expecting them to appear in the premium modal.

Local blockchain e2e

Automated local-chain coverage lives in:

  • /Users/worms/Documents/tibia-idle/tibia-wave-browser/server/test/premium-e2e.test.ts

It spins up a local anvil chain, deploys mock USDC and USDT contracts, registers the premium service against those local token addresses, transfers tokens into the treasury address, waits for confirmations, credits Lux, redeems premium, and verifies that premium-only outfit color editing unlocks.

Run it directly with:

bash
npx vitest run server/test/premium-e2e.test.ts

If anvil is not on your PATH, set ANVIL_BIN first:

bash
ANVIL_BIN=/absolute/path/to/anvil npx vitest run server/test/premium-e2e.test.ts

Safe prod-like local test

This is the safest way to test real mainnet behavior without mutating the production game database:

  1. Start a local app process with a local DATABASE_PATH, the real treasury address, and the production RPC URLs.
  2. Create a throwaway local account in that local app.
  3. Open the Premium modal, create an order on the target chain/token, and copy the treasury address.
  4. Send a real USDC or USDT transfer from your wallet to 0x33b42Bad3E5ee28944aDA778E25f01679af38ebB.
  5. Submit the sender address and transaction hash in the local app.
  6. Wait for confirmations, verify Lux appears, redeem 31 Days Premium, then verify premium-only color customization unlocks.

That proves the production chain/RPC/payment-service path, but the Lux and premium state live only in the local SQLite database you started for that test.

Example local run:

bash
npm run build
PORT=3001 \
HOST=0.0.0.0 \
NODE_ENV=production \
DATABASE_PATH=./data/premium-local-test.db \
PREMIUM_TREASURY_ADDRESS=0x33b42Bad3E5ee28944aDA778E25f01679af38ebB \
ETHEREUM_RPC_URL=https://mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2 \
POLYGON_RPC_URL=https://polygon-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2 \
BASE_RPC_URL=https://base-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2 \
OPTIMISM_RPC_URL=https://optimism-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2 \
ARBITRUM_RPC_URL=https://arbitrum-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2 \
MONAD_RPC_URL=https://monad-mainnet.infura.io/v3/fd378bcd1433463bac54c09a0f54e6f2 \
npm run start

Full live production test

If you want the Lux and premium purchase to hit the real production account state, you must test against the deployed production app after the branch is merged and deployed.

Recommended flow:

  1. Merge and deploy the premium branch to main.
  2. Log into the real production site with the production account you want to test.
  3. Create a premium order on the live site.
  4. Send USDC or USDT on a route that is enabled in production.
  5. Submit the tx hash on the live site and wait for Lux credit.
  6. Redeem 31 Days Premium.
  7. Verify:
    • account tier shows premium
    • Lux decreases by 500
    • AFK cap expands to 6h
    • Customize Colors opens instead of the premium upsell

Do not point a local app directly at the Fly volume or production SQLite file for this test.