Authentication, user profiles, wallet binding, NFT data, and social integrations for Pentagon Games ecosystem.
X-PG-App-Key header. Currently in soft mode (warnings only). See migration guide →
Before you can use the Pentagon Identity API, you must have an App Key. Requests without a valid key will be rejected starting May 28, 2026.
Contact the Pentagon Games team to get your key:
Include your App Key in all login and signup requests:
X-PG-App-Key: pk_live_your_key
Use the endpoint reference below to integrate authentication, wallets, NFTs, and social connections into your app.
The Pentagon Identity API provides unified authentication and user management for all Pentagon Games products. One account works across pentagon.games, Gunnies, EtherFantasy, PentaSwap, and all partner apps.
pg_identity_db, a dedicated PostgreSQL identity database. Fully migrated from the legacy chainguardians database in May 2026. Currently serving 729,291+ registered users across the Pentagon ecosystem.
Email/password, wallet signature, magic link, Ethermail SSO, social OAuth (Discord, Twitter, Telegram, LinkedIn)
EVM (MetaMask, Rabby, Phantom), MultiversX, Tron, TON, Algorand wallet binding
Cross-chain NFT ownership, metadata, collection info. Synced by backend oracle from on-chain data.
Referral codes, VIP status, Discord role sync, friends system
https://api.account.pentagon.games
All endpoints are prefixed with /user/ unless otherwise noted.
Most endpoints require a JWT Bearer token obtained from login:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
| Method | Endpoint | Description |
|---|---|---|
| POST | /user/login | Email/password or wallet signature login |
| POST | /user/login/email | Magic link login (sends email) |
| POST | /user/login/ethermail | Ethermail SSO login |
| POST | /user/token/refresh | Refresh expired JWT token |
POST /user/login
Content-Type: application/json
X-PG-App-Key: pk_live_your_key
{
"type": "email",
"username": "player@example.com",
"password": "securepassword",
"login_from": "your_app_name"
}
For web3 wallet login, the client signs a message and the backend verifies the signature matches the wallet address.
POST /user/login
Content-Type: application/json
X-PG-App-Key: pk_live_your_key
{
"type": "wallet",
"address": "0x1234...abcd",
"signature": "0xsigned...",
"message": "Logging into Pentagon Games,1745812345",
"login_from": "your_app_name"
}
Message format: Logging into Pentagon Games,{unix_timestamp_seconds}
Timestamp window: Must be within 5 minutes of server time.
Prerequisite: Wallet must be previously bound to an account via /user/bind_metamask.
{
"status": true,
"result": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}
}
Users with a PNS username NFT can log in using their on-chain name instead of their email or PG username. The backend resolves the PNS name to a wallet address via the on-chain contract, then matches it to the PG account with that wallet bound.
POST /user/login
Content-Type: application/json
X-PG-App-Key: pk_live_your_key
{
"type": "email",
"username": "king",
"password": "userpassword",
"login_from": "your_app_name"
}
Resolution flow: PNS name → on-chain contract → wallet address → PG account lookup → password verification → JWT token
Contract: 0xf97EB9f8293D1FD5587a809Eb74518c300738d07 on Pentagon Chain (3344)
Requirements: The PNS name must be minted AND spatially bound to a wallet that is connected to a PG account.
king, not King). The backend normalizes input to lowercase before contract lookup.
| Method | Example | Status |
|---|---|---|
| Email + Password | nftprof@pentagon.games | Active |
| PNS Name + Password | nftprof (on-chain bound) | Active |
| Current Username + Password | nftprof1 | Active |
| Wallet Signature | MetaMask / Rabby / Phantom | Active |
| Magic Link (email) | Sends login link to email | Active |
| Ethermail SSO | Ethermail token | Active |
nftprof (as legacy of nftprof1) | Removed |
| Error | Meaning |
|---|---|
Unauthenticated user | Wrong credentials or wallet not registered |
User not verified | Email not validated yet |
Invalid Signature | Wallet signature verification failed or timestamp expired |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /user/info | Bearer | Current user's full profile |
| GET | /user/info/detail | Bearer | Limited user info |
| GET | /user/info/private | Bearer | Private user info |
| GET | /user/info_by_username/public/<username> | None | Public profile lookup |
| GET | /user/check_username | API Key | Check username availability |
| POST | /user/edit_details | Bearer | Update profile |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /user/bind_metamask | Bearer | Connect EVM wallet |
| POST | /user/bind_multiversx | Bearer | Connect MultiversX wallet |
| POST | /user/bind_tron | Bearer | Connect Tron wallet |
| POST | /user/bind_ton | Bearer | Connect TON wallet |
| POST | /user/updateAlgorand | Bearer | Connect Algorand wallet |
| GET | /user/walletinfo | Bearer | Get connected wallets |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /user/nfts | Bearer | User's NFTs across all chains |
| GET | /user/nfts?collection=Gunnies | Bearer | Filter by collection name |
| GET | /user/nfts?order_by=recently_received | Bearer | Sort by recent |
| GET | /user/penxr/nfts | Bearer | PenXR NFT display data |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /user/auth/discord | Bearer | Connect Discord |
| POST | /user/auth/twitter | Bearer | Connect Twitter/X |
| POST | /user/auth/telegram | Bearer | Connect Telegram |
| POST | /user/auth/linkedin | Bearer | Connect LinkedIn |
| POST | /user/social/oauth/<platform>/connect | Bearer | Generic OAuth connect |
| POST | /user/social/<platform>/disconnect | Bearer | Disconnect platform |
| PUT | /user/social/<platform>/privacy | Bearer | Update privacy setting |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /user/verify-email | Bearer | Send verification email |
| POST | /user/validate-email | Token | Validate email from link |
| POST | /user/validate_email/v2 | Token | Validate email v2 |
| POST | /user/password/forgot | None | Forgot password |
| POST | /user/password/reset | Token | Reset password |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /user/friends | Bearer | Get friends list |
| POST | /user/friends/connect | Bearer | Send friend request |
| GET | /user/friends/pending | Bearer | Pending requests |
| POST | /user/friends/update | Bearer | Accept/reject request |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /user/referrals/<code> | None | Referral info |
| GET | /user/referral_code/validate/<code> | None | Validate referral code |
| GET | /user/discord_roles | Bearer | User's Discord roles |
| GET | /user/lookup_vip | API Key | VIP status by email |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /sso/authorize | Bearer | Generate authorization code |
| POST | /sso/token | None | Exchange code for token |
| POST | /sso/validate | None | Validate SSO token |
| GET | /sso/user_roles | Bearer | Get user roles via SSO |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /user/2fa/generate | Bearer | Generate 2FA QR code |
| POST | /user/2fa/verify | Bearer | Verify 2FA code |
| POST | /user/check_echovault_token | Bearer | Check EchoVault token |
| POST | /user/register_echovault_token | Bearer | Register EchoVault token |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /user/signup/ethermail | None | Signup via Ethermail |
| POST | /api/v3/user/signup | None | Signup v3 |
| POST | /api/v4/user/signup | None | Signup v4 (latest) |
A single login gives your application access to the user's entire Pentagon ecosystem data:
Username, email, PNS name, profile picture, about me, interests. The user's Pentagon account is their universal identity.
All connected wallets: EVM (MetaMask, Rabby, Phantom), MultiversX, Tron, TON, Algorand. Plus Pentagon-generated managed wallets.
Every NFT the user owns across Ethereum, Polygon, BSC, Pentagon Chain, SKALE, Oasys, Core, Avalanche, Arbitrum, TON, Base, Tron, and more. Synced from on-chain data by Pentagon's oracle backend.
Discord, Twitter/X, Telegram, LinkedIn accounts. Discord role data. Friend lists and follow relationships.
// 1. User logs in via your app (using Pentagon Identity)
const loginRes = await fetch('https://api.account.pentagon.games/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-PG-App-Key': 'pk_live_your_key'
},
body: JSON.stringify({
type: 'email',
username: 'player@example.com', // or PNS name like "nftprof"
password: '***'
})
});
const { access_token } = (await loginRes.json()).result;
// 2. Get user profile + wallet addresses
const userInfo = await fetch('https://api.account.pentagon.games/user/info', {
headers: { 'Authorization': `Bearer ${access_token}` }
});
// Returns: username, email, mm_address, social accounts, profile picture...
// 3. Get ALL their NFTs across every supported chain
const nfts = await fetch('https://api.account.pentagon.games/user/nfts', {
headers: { 'Authorization': `Bearer ${access_token}` }
});
// Returns: paginated list of every NFT the user owns
// 4. Filter by collection
const gunnies = await fetch('https://api.account.pentagon.games/user/nfts?collection=Gunnies%20PFP', {
headers: { 'Authorization': `Bearer ${access_token}` }
});
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /user/nfts | Bearer | All NFTs owned by the logged-in user, across all chains |
| GET | /user/nfts?collection=<name> | Bearer | Filter by collection name (comma-separated for multiple) |
| GET | /user/nfts?name=<search> | Bearer | Search NFTs by name |
| GET | /user/nfts?order_by=recently_received | Bearer | Sort by most recently acquired |
| GET | /user/nfts?order_by=recently_created | Bearer | Sort by creation date |
| GET | /user/penxr/nfts | Bearer | PenXR metaverse NFT display data (frame positions, likes) |
| GET | /user/info_by_username/public/<username> | None | Public profile lookup (includes EchoVault token info) |
// GET /user/nfts response
{
"success": true,
"result": {
"items": [
{
"id": 12345,
"token_id": "4521",
"name": "Gunnies PFP #4521",
"type": "ERC-721",
"image": "https://storage.googleapis.com/...",
"desc": "A unique Gunnies PFP character"
},
{
"id": 67890,
"token_id": "1337",
"name": "BCSH #1337",
"type": "ERC-721",
"image": "https://...",
"desc": ""
}
],
"total_item": 47,
"total_page": 5
}
}
The Pentagon oracle syncs NFT ownership data from 15+ blockchains. Below are the active collections with NFTs currently tracked:
| Collection | Chain | Chain ID | NFTs Tracked |
|---|---|---|---|
| ChainGunnies | Oasys | 5555 | 97,868 |
| Azuki | Ethereum | 1 | 10,000 |
| Bored Ape Yacht Club | Ethereum | 1 | 10,000 |
| Gunnies PFP | Pentagon Chain | 3344 | 9,093 |
| Chainbunnies | Ethereum | 1 | 6,052 |
| BCSH (TON) | TON | 181818 | 2,500 |
| BCSH (Monad) | Monad | 143 | 2,498 |
| Zombunnies | Ethereum | 1 | 1,225 |
| BCSH (Oasys/PC) | Pentagon Chain | 3344 | 1,124 |
| BCSH (Polygon) | Polygon | 137 | 1,051 |
| BCSH (SKALE) | SKALE | 1482601649 | 687+ |
| BCSH (Arbitrum) | Arbitrum | 42161 | 426 |
| BCSH (Ethereum) | Ethereum | 1 | 294 |
| BCSH (Core) | Core | 1116 | 227 |
| Chainguardians (Oasys) | Oasys | 5555 | 150 |
| POW Bull Badge | Ethereum | 1 | 142 |
| PEGNAMES (PNS) | Pentagon Chain | 3344 | 91 |
| BCSH (BSC) | BSC | 56 | 37 |
| Missing Onez | Core | 1116 | 32 |
| BCSH (Avalanche) | Avalanche | 43114 | 15 |
| BCSH (Tron) | Tron | 728126428 | 6 |
Total: 368,522 NFTs tracked across 2.1M+ metadata records and 353,712 ownership records. 36 collections, 112 contracts, 15+ chains.
Ethereum, Polygon, BSC, Pentagon Chain (3344), Oasys, SKALE, Core, Avalanche, Arbitrum, TON, Monad, Tron, Base
On-chain ownership synced by the nft-sync-cron oracle. Ownership changes reflected within minutes of on-chain transfer.
Let users log in and instantly see their game assets, PFPs, and collectibles. No separate NFT indexer needed. Build loot systems, marketplaces, and inventories on top of Pentagon's data.
Build NFT portfolio trackers, collection dashboards, or analytics tools. One login gives you the user's complete cross-chain holdings.
Build tools that show creators who holds their NFTs, enable token-gated content, or create holder-only experiences.
Target airdrops or rewards based on what a user holds. Verify NFT ownership for eligibility checks, Discord role assignment, or loyalty programs.
api.metadata.pentagon.games which reads from pg_nft_db. Contact the team for server-type app keys with the appropriate access level. Unauthenticated bulk access to NFT data is not available through the Identity API.
Pentagon Identity supports multiple integration patterns depending on what your app needs. Some apps need full identity + wallet verification, others just need login for data access, and some work wallet-first without requiring login at all. Choose the pattern that fits your use case.
POST /user/login with your X-PG-App-Key, and receives a JWT token. The user never leaves your site.The best UX is a popup modal that opens over your page (like pentagon.games does). User clicks "Sign In", a modal appears with email/password + wallet options, they log in, modal closes, they're back on your page. No redirect, no new tab.
Login form opens as overlay on your page. User never leaves. This is how pentagon.games, gunnies.io, and mining.pentagon.games work.
Login fields embedded directly in your page (e.g., sidebar or dedicated section). Good for simple apps. Still uses the API directly.
Sending users to pentagon.games to log in, then redirecting back. Breaks flow, loses context, confusing UX. Only use if you have no frontend.
Drop-in React login modal with email/password and wallet tabs. Uses your own App Key, no redirect needed.
// PentagonLoginModal.tsx — Drop-in popup login for any React/Next.js app
import { useState } from 'react';
import { useAccount, useSignMessage } from 'wagmi';
import { useConnectModal } from '@rainbow-me/rainbowkit';
const PG_API = 'https://api.account.pentagon.games';
const APP_KEY = process.env.NEXT_PUBLIC_PG_APP_KEY;
export function PentagonLoginModal({ isOpen, onClose, onLogin }) {
const [tab, setTab] = useState('email'); // 'email' | 'wallet'
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const { address, isConnected } = useAccount();
const { signMessageAsync } = useSignMessage();
const { openConnectModal } = useConnectModal();
// Email + Password login (also works with PNS name or username)
const handleEmailLogin = async (e) => {
e.preventDefault();
setLoading(true); setError('');
try {
const res = await fetch(`${PG_API}/user/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-PG-App-Key': APP_KEY
},
body: JSON.stringify({
type: 'email',
username: email, // email, PNS name, or username all work
password,
login_from: 'your_app'
})
});
const data = await res.json();
if (data.status) {
onLogin(data.result); // { access_token, refresh_token }
onClose();
} else {
setError(data.message || 'Login failed');
}
} catch (err) { setError('Network error'); }
finally { setLoading(false); }
};
// Wallet signature login
const handleWalletLogin = async () => {
if (!isConnected) { openConnectModal?.(); return; }
setLoading(true); setError('');
try {
const message = `Logging into Pentagon Games,${Math.floor(Date.now()/1000)}`;
const signature = await signMessageAsync({ message });
const res = await fetch(`${PG_API}/user/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-PG-App-Key': APP_KEY
},
body: JSON.stringify({
type: 'wallet',
signature,
address: address.toLowerCase(),
message,
login_from: 'your_app'
})
});
const data = await res.json();
if (data.status) {
onLogin(data.result);
onClose();
} else {
setError(data.message || 'Wallet not linked to an account');
}
} catch (err) { setError('Signature rejected or failed'); }
finally { setLoading(false); }
};
if (!isOpen) return null;
return (
<div style={{
position:'fixed', inset:0, zIndex:9999,
background:'rgba(0,0,0,0.6)', backdropFilter:'blur(4px)',
display:'flex', alignItems:'center', justifyContent:'center'
}} onClick={onClose}>
<div onClick={e => e.stopPropagation()} style={{
background:'#12121a', border:'1px solid #2a2a3a', borderRadius:16,
padding:32, width:400, maxWidth:'90vw', color:'#e0e0e8'
}}>
<h2 style={{margin:'0 0 20px', fontSize:20}}>⛠ Sign In</h2>
{/* Tabs */}
<div style={{display:'flex', gap:0, borderBottom:'1px solid #2a2a3a', marginBottom:20}}>
<button onClick={() => setTab('email')} style={{
padding:'8px 20px', background:'none', border:'none', cursor:'pointer',
color: tab==='email' ? '#7c5cff' : '#8888a0',
borderBottom: tab==='email' ? '2px solid #7c5cff' : '2px solid transparent'
}}>Email</button>
<button onClick={() => setTab('wallet')} style={{
padding:'8px 20px', background:'none', border:'none', cursor:'pointer',
color: tab==='wallet' ? '#7c5cff' : '#8888a0',
borderBottom: tab==='wallet' ? '2px solid #7c5cff' : '2px solid transparent'
}}>Wallet</button>
</div>
{error && <div style={{color:'#ff4c6a', fontSize:14, marginBottom:12}}>{error}</div>}
{tab === 'email' ? (
<form onSubmit={handleEmailLogin}>
<input placeholder="Email, PNS name, or username" value={email}
onChange={e => setEmail(e.target.value)} style={inputStyle} />
<input type="password" placeholder="Password" value={password}
onChange={e => setPassword(e.target.value)} style={inputStyle} />
<button type="submit" disabled={loading} style={btnStyle}>
{loading ? 'Signing in...' : 'Sign In'}
</button>
</form>
) : (
<button onClick={handleWalletLogin} disabled={loading} style={btnStyle}>
{loading ? 'Confirming...' : isConnected
? `Sign with ${address.slice(0,6)}...${address.slice(-4)}`
: 'Connect Wallet'}
</button>
)}
<button onClick={onClose} style={{
marginTop:16, background:'none', border:'none',
color:'#8888a0', cursor:'pointer', width:'100%', textAlign:'center'
}}>Cancel</button>
</div>
</div>
);
}
const inputStyle = {
width:'100%', padding:'10px 14px', marginBottom:12, borderRadius:8,
border:'1px solid #2a2a3a', background:'#0a0a0f', color:'#e0e0e8',
fontSize:14, outline:'none'
};
const btnStyle = {
width:'100%', padding:'12px', borderRadius:8, border:'none',
background:'#7c5cff', color:'white', fontSize:15,
fontWeight:600, cursor:'pointer'
};
// Usage in your app:
// const [showLogin, setShowLogin] = useState(false);
// <button onClick={() => setShowLogin(true)}>Sign In</button>
// <PentagonLoginModal
// isOpen={showLogin}
// onClose={() => setShowLogin(false)}
// onLogin={({ access_token }) => { saveToken(access_token); loadUser(); }}
// />
// Vanilla JS popup modal — no framework needed
// Add to any HTML page
const PG_API = 'https://api.account.pentagon.games';
const APP_KEY = 'pk_live_your_key';
function showPentagonLogin(onSuccess) {
// Create overlay
const overlay = document.createElement('div');
overlay.style.cssText = `
position:fixed; inset:0; z-index:9999;
background:rgba(0,0,0,0.6); backdrop-filter:blur(4px);
display:flex; align-items:center; justify-content:center;
`;
overlay.innerHTML = `
<div style="background:#12121a; border:1px solid #2a2a3a; border-radius:16px;
padding:32px; width:400px; max-width:90vw; color:#e0e0e8;">
<h2 style="margin:0 0 20px; font-size:20px;"⛠ Sign In</h2>
<input id="pg-email" placeholder="Email, PNS name, or username"
style="width:100%; padding:10px 14px; margin-bottom:12px; border-radius:8px;
border:1px solid #2a2a3a; background:#0a0a0f; color:#e0e0e8;" />
<input id="pg-pass" type="password" placeholder="Password"
style="width:100%; padding:10px 14px; margin-bottom:12px; border-radius:8px;
border:1px solid #2a2a3a; background:#0a0a0f; color:#e0e0e8;" />
<div id="pg-error" style="color:#ff4c6a; font-size:14px; margin-bottom:8px;"></div>
<button id="pg-submit" style="width:100%; padding:12px; border-radius:8px;
border:none; background:#7c5cff; color:white; font-size:15px;
font-weight:600; cursor:pointer;">Sign In</button>
<button id="pg-cancel" style="margin-top:12px; width:100%; background:none;
border:none; color:#8888a0; cursor:pointer;">Cancel</button>
</div>
`;
overlay.querySelector('#pg-cancel').onclick = () => overlay.remove();
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
overlay.querySelector('#pg-submit').onclick = async () => {
const email = overlay.querySelector('#pg-email').value;
const pass = overlay.querySelector('#pg-pass').value;
const errEl = overlay.querySelector('#pg-error');
try {
const res = await fetch(`${PG_API}/user/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-PG-App-Key': APP_KEY },
body: JSON.stringify({ type:'email', username:email, password:pass, login_from:'your_app' })
});
const data = await res.json();
if (data.status) {
overlay.remove();
onSuccess(data.result); // { access_token, refresh_token }
} else {
errEl.textContent = data.message || 'Login failed';
}
} catch(e) { errEl.textContent = 'Network error'; }
};
document.body.appendChild(overlay);
}
// Usage:
// document.getElementById('login-btn').onclick = () => {
// showPentagonLogin(({ access_token }) => {
// localStorage.setItem('pg_token', access_token);
// loadUserData();
// });
// };
// Server-side or API-only login (no popup needed)
// Good for backends, CLI tools, or server-rendered apps
async function pgLogin(email, password) {
const res = await fetch('https://api.account.pentagon.games/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-PG-App-Key': 'pk_live_your_key'
},
body: JSON.stringify({
type: 'email',
username: email, // works with email, PNS name, or username
password: password,
login_from: 'your_app'
})
});
const data = await res.json();
if (data.status) {
return data.result; // { access_token, refresh_token }
}
throw new Error(data.message || 'Login failed');
}
Before choosing a pattern, understand the available login methods. All use POST /user/login with X-PG-App-Key header:
| Method | type field | Username / Data Fields | Description |
|---|---|---|---|
| Email + Password | type="email" |
username="user@example.com", password="***" |
Standard email login |
| PNS Name + Password | type="email" |
username="nftprof" (lowercase PNS name), password="***" |
On-chain Pentagon Name Service lookup. Names are lowercase only. |
| Current Username + Password | type="email" |
username="nftprof1", password="***" |
Direct PG username |
| Wallet Signature | type="wallet" |
signature, address, message="Logging into Pentagon Games,{unix_timestamp}" |
Web3 wallet sign-in. Wallet must be pre-bound to account. |
| Magic Link (Email) | POST /user/login/email with email="user@example.com" |
Sends a login link to email, no password needed | |
| Ethermail SSO | POST /user/login/ethermail with ethermail_token="..." |
Ethermail integration | |
| SSO (Widget) | POST /sso/authorize + /sso/token with client_id, redirect_uri |
Embeddable login widget, returns auth code → token | |
User logs in via PG Identity, then connects their browser wallet. The app verifies the connected wallet matches the wallet registered to their PG account. This ensures the person controlling the wallet is the verified account holder.
Flow:
access_tokenGET /user/info with Bearer token → gets mm_address (registered wallet)connected.toLowerCase() !== mm_address.toLowerCase() → block, show mismatch warning// After PG login, verify wallet match
const userInfo = await fetch('/user/info', {
headers: { Authorization: `Bearer ${token}` }
});
const { mm_address } = (await userInfo.json()).result;
// Compare with connected wallet (from wagmi useAccount)
if (connectedAddress.toLowerCase() !== mm_address.toLowerCase()) {
showError("Please connect the wallet registered to your Pentagon account");
return;
}
// Wallet verified — proceed with transactions
When to use: DeFi apps, staking, token swaps, any app where on-chain actions must be tied to verified identity. Transactions go directly to smart contracts from user's wallet.
Transaction handling: Direct contract calls from user's wallet. No backend fulfillment needed. Frontend uses wagmi/viem writeContract. User pays gas from their own wallet.
User logs in via PG Identity to pull their NFT data and account info, but can connect ANY wallet for mining operations. This is intentional — users shouldn't have to use their sensitive NFT-holding wallet for mining.
Flow:
GET /user/nftsmm_address)When to use: Apps where NFT ownership verification is needed but the operational wallet can differ. Protects users from exposing high-value wallets.
Transaction handling: Backend-driven fulfillment. User submits mining action, backend verifies NFT ownership from PG data, and either:
Pure PG Identity login with no wallet connection needed. The app uses the user's internal PG wallet (centralised_wallet_address) for any on-chain interactions, using NPC points as gas.
Flow:
GET /user/info → uses centralised_wallet_address for any chain interactionsWhen to use: Social features, points systems, profile management, any feature that doesn't require user-controlled wallet signing.
Transaction handling: Fully backend-managed. User never signs transactions. Backend uses managed wallet with NPC points as gas abstraction.
User connects wallet first via RainbowKit (no PG login required). The app checks if the connected wallet belongs to a verified PG account. This allows NFT purchases without requiring full PG login — just wallet.
Flow:
POST /user/login with type="wallet"mm_address match// Wallet-first login
const message = `Logging into Pentagon Games,${Math.floor(Date.now() / 1000)}`;
const signature = await signMessageAsync({ message });
const result = await signIn('wallet-login', {
signature,
address: address.toLowerCase(),
message,
redirect: false
});
// If user has PG account → logged in
// If not → show signup or continue in wallet-only mode
When to use: NFT marketplaces, purchase flows, game clients where wallet presence matters more than full identity verification. Lowers friction for buyers.
Transaction handling: Direct contract calls from user's connected wallet. For purchases that need backend fulfillment (minting, reward distribution), backend watches for the tx hash or catches wallet + nonce to verify payment and fulfill.
User connects any wallet with zero PG account required. Their NFTs are loaded directly from the NFT data API by wallet address. They can use the app immediately. If they later create a PG account (or already have one), the experience upgrades automatically.
How it differs from Pattern 4: Pattern 4 (Gunnies.io) connects wallet then silently checks if it's a PG account and auto-logs in. Pattern 5 doesn't require or check for a PG account at all. The wallet IS the identity. PG login is purely an optional enhancement layer.
GET nft-data.pentagon.games/api/v1/nft/owner/{address}// Pattern 5: Wallet-first, PG optional
import { useAccount } from 'wagmi';
const { address, isConnected } = useAccount();
// Load NFTs directly by wallet address (no PG login needed)
const nfts = await fetch(`https://nft-data.pentagon.games/api/v1/nft/owner/${address}`);
// Check if they have a PG account (optional enhancement)
const pgCheck = await fetch(`https://api.account.pentagon.games/user/info_by_username/public/${address}`);
const hasPGAccount = pgCheck.status === true;
if (hasPGAccount) {
// Unlock enhanced features: offchain mining, faster data, free gas
} else {
// Show CTA to create PG account
}
When to use: Apps targeting web3-native users who may not have a Pentagon account. Eliminates signup friction entirely. The PG account becomes a value-add, not a gate.
Transaction handling: User wallet handles all transactions directly (on-chain). For PG-enhanced features (offchain mining), backend fulfillment via managed wallet.
| Pattern | PG Login | Wallet Connect | Wallet Must Match | Transaction Source | Example Apps |
|---|---|---|---|---|---|
| 1: PG + Wallet Match | Required | Required | Yes (strict) | User wallet (direct contract) | PenDeFi, Staking |
| 2: PG + Any Wallet | Required | Required | No (any wallet) | Backend fulfillment | NFT Mining |
| 3: PG Only | Required | No | N/A | Backend (managed wallet) | Profile, NPC Points |
| 4: Wallet First | Optional (auto) | Required | N/A | User wallet (direct contract) | Gunnies.io, NFT Buy |
| 5: Wallet + PG Optional | Optional (enhancement) | Required | N/A | User wallet / backend if PG | NFT Mining v2 |
Pentagon ecosystem uses RainbowKit v2 + wagmi v2 + viem for wallet connections.
// Web3Provider.tsx
import { getDefaultConfig } from '@rainbow-me/rainbowkit';
import { skaleNebula, coreDao, mainnet } from 'wagmi/chains';
const PentagonChain = {
id: 3344, name: 'Pentagon Chain',
nativeCurrency: { name: 'PC', symbol: 'PC', decimals: 18 },
rpcUrls: { default: { http: ['https://rpc.pentagon.games'] } },
blockExplorers: { default: { name: 'Explorer', url: 'https://explorer.pentagon.games' } }
};
export const config = getDefaultConfig({
appName: 'Your App Name',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [mainnet, PentagonChain, skaleNebula, coreDao],
});
Dependencies:
{
"@rainbow-me/rainbowkit": "2",
"wagmi": "^2.15.3",
"viem": "2.x",
"@wagmi/core": "^2.17.1"
}
Two models depending on who fulfills:
Direct Contract (Patterns 1 & 4):
User wallet calls contract directly. Frontend uses wagmi writeContract or sendTransaction. No backend tracking needed — contract events handle fulfillment. User pays gas.
import { useWriteContract } from 'wagmi';
const { writeContract } = useWriteContract();
await writeContract({
address: '0xContractAddress',
abi: contractABI,
functionName: 'mint',
args: [tokenId],
value: parseEther('0.01')
});
Backend Fulfillment (Patterns 2 & 3):
Backend needs to know when user has paid or taken action. Two approaches:
Backend then fulfills (mints reward, updates state, distributes tokens) using a service wallet.
// Frontend: send tx hash to backend for tracking
const tx = await writeContract({ ... });
await fetch('/api/submit-mining', {
method: 'POST',
headers: { Authorization: `Bearer ${pgToken}` },
body: JSON.stringify({ txHash: tx, walletAddress: connectedAddress })
});
// Backend watches chain for tx confirmation, then fulfills
For apps that want to embed Pentagon login without building their own login UI:
<!-- Add to your HTML -->
<script src="https://login.pentagon.games/static/js/pgwidget-sdk-v1.js"></script>
<pentagon-widget clientid="YOUR_SSO_CLIENT_ID"></pentagon-widget>
<script>
window.PentagonWidget.mount();
window.addEventListener('PentagonGamesSignInOnSuccess', (e) => {
const token = e.detail.token;
// User is logged in, use token for API calls
});
</script>
The widget opens a popup to login.pentagon.games, handles auth, and fires a PentagonGamesSignInOnSuccess event with the JWT token. Currently 5 SSO clients registered.
Every application that calls the Pentagon Identity API must include an App Key header to identify itself.
X-PG-App-Key: pk_live_your_key_here
Add this header to all requests to the Identity API. Keys are issued per-application and tracked for analytics and security.
| Type | Prefix | Use Case | Restrictions |
|---|---|---|---|
| Web App | pk_live_ | Browser-based apps | Origin-locked to registered domains |
| Native App | pk_live_ | Unity games, mobile apps | No origin restriction |
| Server | pk_live_ | Backend services | IP-locked to registered servers |
| Development | pk_test_ | Local development | localhost only |
Contact the Pentagon Games team to register your application and receive a key:
You'll need to provide:
The Pentagon Identity system is backed by pg_identity_db, a dedicated PostgreSQL database. Below is a reference of all tables organized by domain.
Primary user records, wallets, login tracking, and profile data.
| Table | Description |
|---|---|
user | Primary user accounts (email, username, password hash, created_at, referral info) |
user_wallet | Primary EVM wallet binding (one per user, used for login and PNS resolution) |
user_external_wallets | Additional wallets: MultiversX, Tron, TON, Algorand, and secondary EVM wallets |
user_legacy_username | Old/renamed usernames retained for PNS NFT airdrop mapping |
user_login_history | Login event log (timestamp, IP, method, app key used) |
user_data | Extended user metadata and preferences |
user_profile_picture | Avatar/profile image references |
user_old_usernames | Username change history |
Social platform connections, OAuth tokens, Discord role sync, and social graph.
| Table | Description |
|---|---|
user_social_accounts | Connected social platforms (Discord, Twitter, Telegram, LinkedIn) |
user_discord_roles | Synced Discord roles for gating and VIP status |
user_friendship | Friend connections (pending, accepted, blocked) |
user_follows | Follow relationships between users |
social_platforms | Platform definitions and OAuth configuration |
user_security_accounts | 2FA and security key registrations |
Application registration, key tracking, and usage analytics.
| Table | Description |
|---|---|
app_registrations | Registered applications with key, type, allowed origins/IPs |
app_key_logs | Per-request app key usage logs for analytics and abuse detection |
external_app | External/partner app metadata and integration config |
Cross-chain NFT ownership and metadata, synced by backend oracle.
| Table | Description |
|---|---|
nfts | Individual NFT records (token ID, chain, contract, owner) |
nft_collection | Collection definitions (name, chain, type) |
nft_contract | Smart contract addresses per collection per chain |
nft_metadata | Token metadata (name, image, attributes, rarity) |
nft_owners | Current ownership records synced from on-chain data |
Badge campaigns, referral tracking, and reward distribution.
| Table | Description |
|---|---|
user_campaign_badges | Earned campaign badges |
user_campaign_points | Accumulated campaign points |
user_campaign_sub_badges | Sub-badge progress within campaigns |
user_campaign_ascended_badges | Ascended (evolved) badge records |
user_campaign_royal_badges | Royal tier badge records |
user_referral_details | Referral code ownership and usage tracking |
referral_rewards | Reward payouts for successful referrals |
Metaverse user data, spaces, inventory, and virtual currency.
| Table | Description |
|---|---|
penxr_user_data | PenXR user profile and settings |
penxr_user_space | User-owned virtual spaces |
penxr_user_inventory | User's in-world item inventory |
penxr_inventories | Global inventory item definitions |
penxr_user_nfts | NFTs displayed or used within PenXR |
penxr_currency | Virtual currency definitions |
penxr_currency_balance | User currency balances |
Chain definitions, token lists, bridge logs, and supply tracking.
| Table | Description |
|---|---|
pgw_chains | Supported blockchain definitions (chain ID, RPC, explorer) |
pgw_chain_tokens | Token contracts per chain |
pgw_swap_chains | Chains enabled for swap/DEX features |
pgw_swap_chain_tokens | Tokens available for swap per chain |
pen_bridge_logs | Cross-chain bridge transaction logs |
pen_supply | Token supply tracking records |
| Table | Description |
|---|---|
sso_client | Registered SSO client applications (client ID, secret, redirect URIs) |
Security keys, KYC, anti-sybil, and access control.
| Table | Description |
|---|---|
security_user_keys | Hardware security key / passkey registrations |
user_kyc_status | KYC verification status per user |
sybil_flags | Anti-sybil detection flags and scores |
captcha_data | Captcha challenge and verification records |
blocked_email_domains | Blocked disposable/spam email domains |
On-chain identity vault contract interactions.
| Table | Description |
|---|---|
user_echovault_contract | User's EchoVault contract deployment records |
user_echovault_events | EchoVault on-chain event log |
X-PG-App-Key header will be rejected with HTTP 401 on all login/signup endpoints.
Contact Pentagon Games team or check if a key has already been generated for your app.
Add X-PG-App-Key to every request that hits login/signup endpoints.
Affected endpoints:
POST /user/loginPOST /user/login/emailPOST /user/login/ethermailPOST /user/signup/ethermailPOST /api/v3/user/signupPOST /api/v4/user/signupPOST /sso/authorizePOST /sso/token// Add to your login API call
const response = await fetch("https://api.account.pentagon.games/user/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-PG-App-Key": process.env.PG_APP_KEY, // ← Add this
},
body: JSON.stringify({
type: "email",
username: email,
password: password,
login_from: "your_app",
}),
});
const data = await response.json();
if (data.status) {
const token = data.result.access_token;
// Store token, use as Bearer auth for subsequent requests
}
using UnityEngine.Networking;
using System.Text;
string json = JsonUtility.ToJson(new LoginRequest {
type = "email",
username = email,
password = password,
login_from = "gunnies_pc"
});
UnityWebRequest request = new UnityWebRequest(
"https://api.account.pentagon.games/user/login", "POST");
byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("X-PG-App-Key", appKey); // ← Add this
yield return request.SendWebRequest();
import requests
import os
response = requests.post(
"https://api.account.pentagon.games/user/login",
headers={
"Content-Type": "application/json",
"X-PG-App-Key": os.environ["PG_APP_KEY"], # ← Add this
},
json={
"type": "email",
"username": username,
"password": password,
"login_from": "backend_service",
},
)
data = response.json()
if data["status"]:
token = data["result"]["access_token"]
curl -X POST https://api.account.pentagon.games/user/login \
-H "Content-Type: application/json" \
-H "X-PG-App-Key: pk_test_your_key" \
-d '{
"type": "email",
"username": "test@example.com",
"password": "password123",
"login_from": "testing"
}'
| Date | What Happens |
|---|---|
| Apr 28, 2026 | Now CORS locked. App Keys live in soft mode (warnings only) |
| May 12, 2026 | Check-in. Review logs for apps still missing keys |
| May 21, 2026 | Final warning to any app without a key |
| May 28, 2026 | Enforcement Requests without keys rejected with 401 |
Pentagon VIP members can generate a personal API key to access their own data and their friends' public data programmatically.
Access your data at: vip.pentagon.games/mydata
| Scope | Endpoints | Description |
|---|---|---|
self:read | /user/info, /user/nfts, /user/walletinfo | Your profile, NFTs, and wallet data |
friends:read | /user/friends, /user/friends/<username>/info | Your friends list and their public profiles |
nfts:read | /user/friends/<username>/nfts | Your friends' NFT collections |
| Key Type | Rate Limit |
|---|---|
VIP User Key (uk_live_) | 60 requests / minute |
App Key (pk_live_) | 100 requests / minute |
| No key (deprecated) | Will be rejected after May 28 |
# Use your personal API key
curl -H "X-PG-User-Key: uk_live_your_key" \
https://api.account.pentagon.games/user/info
# Get your NFTs
curl -H "X-PG-User-Key: uk_live_your_key" \
https://api.account.pentagon.games/user/nfts
# Get a friend's public info
curl -H "X-PG-User-Key: uk_live_your_key" \
https://api.account.pentagon.games/user/friends/friendname/info