Appearance
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:
1USDC or USDT credits100Lux Coins500Lux Coins redeems31days 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=0x33b42Bad3E5ee28944aDA778E25f01679af38ebBThe 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:
| Chain | Token | Address source |
|---|---|---|
| Ethereum | USDC | Circle USDC contract addresses |
| Ethereum | USDT | Tether supported protocols |
| Polygon | USDC | Circle USDC contract addresses |
| Polygon | USDT | Verified Polygon mainnet USDT contract |
| Base | USDC | Circle USDC contract addresses |
| Base | USDT | Verified Base bridged Tether USD contract |
| Optimism | USDC | Circle USDC contract addresses |
| Optimism | USDT0 | Verified Optimism USDT0 contract |
| Arbitrum | USDC | Circle USDC contract addresses |
| Arbitrum | USDT | Verified Arbitrum mainnet USDT contract |
| BSC | USDC | Verified BSC USDC contract |
| BSC | USDT | Verified BSC USDT contract |
| MegaETH | USDT0 | Verified MegaETH USDT0 contract |
| Monad | USDC | Circle USDC contract addresses |
| Monad | USDT0 | Verified 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=USDT0The 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/fd378bcd1433463bac54c09a0f54e6f2If 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.tsIf 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.tsSafe prod-like local test
This is the safest way to test real mainnet behavior without mutating the production game database:
- Start a local app process with a local
DATABASE_PATH, the real treasury address, and the production RPC URLs. - Create a throwaway local account in that local app.
- Open the Premium modal, create an order on the target chain/token, and copy the treasury address.
- Send a real USDC or USDT transfer from your wallet to
0x33b42Bad3E5ee28944aDA778E25f01679af38ebB. - Submit the sender address and transaction hash in the local app.
- 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 startFull 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:
- Merge and deploy the premium branch to
main. - Log into the real production site with the production account you want to test.
- Create a premium order on the live site.
- Send USDC or USDT on a route that is enabled in production.
- Submit the tx hash on the live site and wait for Lux credit.
- Redeem
31 Days Premium. - Verify:
- account tier shows premium
- Lux decreases by
500 - AFK cap expands to
6h Customize Colorsopens instead of the premium upsell
Do not point a local app directly at the Fly volume or production SQLite file for this test.