Unified VIP tier resolution API for Pentagon Games.
Single endpoint replaces the old 3-API client-side pattern (api.account + api.service + api.bcsh.xyz) with one GET /user/vip_status call. Backend owns all tier logic. No more Math.max across three APIs on the frontend.
https://api.account.pentagon.games/user/vip_status
Hosted on pg-identity-be (AWS 13.212.154.41), port 9022, proxied through nginx alongside the identity API.
Currently public (v1.3). No API key required for reads.
The endpoint previously accepted X-VIP-API-Key or X-PG-App-Key headers. Auth check is now a no-op but headers are still accepted for backward compatibility.
Provide at least one identifier:
| Param | Type | Description |
|---|---|---|
wallet |
string | EVM address (0x…) |
username |
string | Pentagon Games username |
discord_id |
string | Discord user snowflake ID |
Multiple params can be combined. The resolver tries them in order: wallet, username, discord_id.
curl "https://api.account.pentagon.games/user/vip_status?username=nftprof"
{
"status": true,
"result": {
"effective_tier": 3,
"tier_name": "VIP3",
"on_chain_tier": 3,
"role_tier": 2,
"resolved_from": "on_chain",
"username": "nftprof",
"primary_wallet": "0x...",
"verified": true,
"referral_rate": 0.15,
"referral_rate_display": "15%",
"can_payout": true,
"pen_total": 11942834.39,
"pen_by_chain": {
"ethereum": 11741190.78,
"pentagon": 201643.61,
"arbitrum": 0.0
},
"zor_total": 52000.0,
"bcsh_count": 12,
"has_bcsh": true,
"has_ethan": false,
"ethan_count": 0,
"has_chain_hero": true,
"has_obelith": true,
"has_dark": true,
"nft_names": ["Nomad", "Dark Setsuko", "Obelith Setsuko", "BCSH CORE", "Lazuli"],
"discord_roles": ["VIP2", "BCSH Holder", "PG User"],
"sub_roles": ["200K PEN", "1M PEN", "BCSH", "Chain Hero"],
"wallets": ["0xabc...", "0xdef..."],
"progress": {
"current_tier": "VIP3",
"next_tier": null,
"message": "Maximum tier reached"
}
}
}
| Field | Type | Description |
|——-|——|————-|
| effective_tier | int | Final tier (0-3). max(on_chain_tier, role_tier) |
| tier_name | string | Human-readable: Newcomer, VIP1, VIP2, VIP3 |
| on_chain_tier | int | Tier from on-chain holdings (Source B) |
| role_tier | int | Tier from Discord role sync (Source A) |
| resolved_from | string | Which source won: on_chain, discord_role, or both_equal |
| Field | Type | Description |
|——-|——|————-|
| username | string | PG username |
| primary_wallet | string | Primary linked wallet |
| verified | bool | Account email verified |
| Field | Type | Description |
|——-|——|————-|
| referral_rate | float | Decimal rate (0.05 = 5%) |
| referral_rate_display | string | Human-readable (“5%”) |
| can_payout | bool | True if VIP2+ (eligible for referral payouts) |
| Field | Type | Description |
|——-|——|————-|
| pen_total | float | Total PEN across all chains and wallets |
| pen_by_chain | object | Breakdown: ethereum, pentagon, arbitrum |
| zor_total | float | Total ZOR balance |
| Field | Type | Description |
|——-|——|————-|
| bcsh_count | int | Total BCSH NFTs owned |
| has_bcsh | bool | Owns any BCSH NFT |
| has_ethan | bool | Owns Ethan NFT (Ethereum mainnet) |
| ethan_count | int | Number of Ethan NFTs |
| has_chain_hero | bool | Owns a Chain Hero NFT |
| has_obelith | bool | Owns Obelith Setsuko (on-chain tier 2) |
| has_dark | bool | Owns Dark Setsuko (on-chain tier 1) |
| nft_names | array | List of unique NFT collection names |
| Field | Type | Description |
|——-|——|————-|
| discord_roles | array | Discord VIP roles from last bot sync |
| sub_roles | array | Sub-roles (200K PEN, BCSH, Chain Hero, etc.) |
| Field | Type | Description |
|——-|——|————-|
| wallets | array | All bound EVM wallet addresses |
| Field | Type | Description |
|——-|——|————-|
| progress.current_tier | string | Current tier name |
| progress.next_tier | string or null | Next tier name, null if max |
| progress.upgrade_options | array | What the user needs for the next tier |
| Tier | Token Requirement | NFT Requirement | Referral Rate |
|---|---|---|---|
| VIP1 | 200K PEN or 10K ZOR | Any BCSH NFT | 0% (display only) |
| VIP2 | 1M PEN or 50K ZOR | Chain Hero NFT | 5% |
| VIP3 | 5M PEN | Ethan (ETH) or Obelith Setsuko | 15% |
Token OR NFT qualifies. Whichever gives the higher tier wins.
Chain Hero NFTs: Setsuko, Dark Setsuko, Obelith Setsuko, Lazuli, Avyaan, Launch, Nomad, BCSH CORE
Setsuko tiers (on-chain):
Note: The BCSH API returns variant names (“Dark Setsuko”, “Obelith Setsuko”) rather than plain “Setsuko”. The resolver uses substring matching to detect all Setsuko variants, then calls
tokenTier()on-chain with the full token ID (including5555chain prefix) to determine the exact tier.
Consumer
│
GET /user/vip_status?wallet=0x...
│
▼
pg-vip-api (port 9022, gunicorn)
│
├─ [1] User Lookup
│ └─ pg_identity_db (AWS RDS 172.31.x.x:5432)
│ Query by wallet / username / discord_id
│ Get all bound wallets from user_external_wallets
│
├─ [2] Source A: Discord Role Tier
│ └─ user_discord_roles table (synced by role bot every 4h)
│ Maps VIP1/VIP2/VIP3 roles → tier int
│
├─ [3] Source B: On-Chain Holdings (per wallet)
│ ├─ PEN balance
│ │ ├─ Service API (api.service.pentagon.games) → ETH + ARB
│ │ ├─ Web3 fallback → balanceOf on ETH mainnet (0x5ee3...8f66)
│ │ └─ Direct RPC → balanceOf on Pentagon Chain (0x02fa...8721)
│ │
│ ├─ ZOR balance
│ │ └─ Service API (api.service.pentagon.games)
│ │
│ ├─ BCSH NFTs
│ │ └─ api.bcsh.xyz/user/nfts (paginated, Pentagon Chain)
│ │
│ ├─ Ethan NFTs
│ │ └─ api.bcsh.xyz/user/nfts?chain_id=1 (Ethereum)
│ │
│ └─ Setsuko on-chain tier
│ └─ tokenTier() on Setsuko Distributor contract (Pentagon RPC)
│
└─ [4] Resolve
effective_tier = max(role_tier, chain_tier)
Return flat JSON with everything
The Service API (api.service.pentagon.games) for PEN balance is occasionally flaky, returning 0 for wallets that have millions of PEN. When the Service API returns 0 for Ethereum PEN, the resolver falls back to a direct balanceOf call on the PEN ERC-20 contract on Ethereum mainnet via public RPC. This prevents false tier downgrades during upstream API hiccups.
// Missing params
{ "status": false, "error": "Provide at least one: wallet, username, or discord_id" }
// 422
// User not found
{ "status": false, "error": "User not found" }
// 404
// Internal error
{ "status": false, "error": "Internal error during tier resolution" }
// 500
GET /health
→ { "status": "ok", "service": "pg-vip-api" }
13.212.154.41)./deploy.sh
Syncs code via rsync, sets up virtualenv, installs deps, copies supervisor config, restarts service.
Copy .env.example to .env on the server and fill in credentials:
| Variable | Description | Default |
|---|---|---|
DB_HOST |
PostgreSQL host | 172.31.46.190 |
DB_PORT |
PostgreSQL port | 5432 |
DB_NAME |
Database name | pg_identity_db |
DB_USER |
Database user | backend_pg_account |
DB_PASSWORD |
Database password | (required) |
SERVICE_API_KEY |
Key for api.service.pentagon.games | (required) |
PG_API_KEY |
Pentagon Games API key | (optional) |
PENTAGON_RPC |
Pentagon Chain RPC URL | https://rpc.pentagon.games |
ETH_RPC |
Ethereum mainnet RPC | https://eth.drpc.org |
PORT |
Server port | 9022 |
VIP_API_KEY |
API key for this service (legacy) | (optional) |
Routes /user/vip_status and /user/vip to port 9022 on the HTTPS server block for api.account.pentagon.games. All other routes continue to the identity backend on port 8031.
[program:pg-vip-api]
command=/var/www/services/pg-vip-api/venv/bin/gunicorn -w 2 -b 0.0.0.0:9022 --timeout 60 app:app
directory=/var/www/services/pg-vip-api
autostart=true
autorestart=true
stderr_logfile=/var/log/pg-vip-api.err.log
stdout_logfile=/var/log/pg-vip-api.out.log
flask==3.1.1
gunicorn==23.0.0
httpx==0.28.1
python-dotenv==1.1.0
psycopg2-binary==2.9.10
web3==7.12.0
| Version | Changes |
|---|---|
| v1.4 | Fixed Setsuko tier detection: substring name matching + full token ID for tokenTier() |
| v1.3 | Made endpoint public, no auth required |
| v1.2 | Web3 fallback for ETH PEN balance when Service API flakes |
| v1.1 | Renamed to /user/vip_status, flattened response, removed include= param |
| v1.0 | Initial release, unified VIP tier resolution |
For the Discord role bot: Call /user/vip_status?discord_id=X, read effective_tier, assign matching Discord role. All threshold logic lives here, not in the bot.
For frontend VIP pages: Call with wallet or username. The progress field gives upgrade paths. referral_rate_display is ready to show.
Multi-wallet: Users with multiple bound wallets get aggregated balances across all wallets. PEN on Ethereum + Pentagon Chain + Arbitrum are summed per wallet, then totaled.