Pentagon Identity API

Authentication, user profiles, wallet binding, NFT data, and social integrations for Pentagon Games ecosystem.

v2.0 — Live App Keys Active
⚠️ Migration Deadline: May 28, 2026 — All login/signup requests must include an X-PG-App-Key header. Currently in soft mode (warnings only). See migration guide →

🚀 Getting Started

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.

Step 1: Request an App Key

Contact the Pentagon Games team to get your key:

Step 2: Add the Header

Include your App Key in all login and signup requests:

X-PG-App-Key: pk_live_your_key

Step 3: Integrate

Use the endpoint reference below to integrate authentication, wallets, NFTs, and social connections into your app.

Current status: App Key enforcement is in soft mode (warnings only in server logs). Full enforcement begins May 28, 2026, after which requests without a valid key will receive HTTP 401. See migration timeline →
⚠️ Recommended: Popup Login Modal (not redirects)
Build a popup login modal in your app that calls the Pentagon Identity API directly. User clicks "Sign In", a modal overlay appears with email/password + wallet options, they authenticate, modal closes, done. Do NOT redirect users to pentagon.games to sign in.

This is how pentagon.games, gunnies.io, and mining.pentagon.games all work. See the Integration Patterns section for a drop-in React modal component, vanilla JS popup, and API-only examples.

Overview

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.

Infrastructure: This system runs on 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.

🔐 Authentication

Email/password, wallet signature, magic link, Ethermail SSO, social OAuth (Discord, Twitter, Telegram, LinkedIn)

👛 Multi-Chain Wallets

EVM (MetaMask, Rabby, Phantom), MultiversX, Tron, TON, Algorand wallet binding

🖼️ NFT Data

Cross-chain NFT ownership, metadata, collection info. Synced by backend oracle from on-chain data.

⭐ VIP & Referrals

Referral codes, VIP status, Discord role sync, friends system

Base URL

https://api.account.pentagon.games

All endpoints are prefixed with /user/ unless otherwise noted.

Authentication Pattern

Most endpoints require a JWT Bearer token obtained from login:

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Authentication

Login Methods

MethodEndpointDescription
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

Email/Password Login

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"
}

Wallet Signature Login

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.

Successful Login Response

{
  "status": true,
  "result": {
    "access_token": "eyJhbGciOiJIUzI1NiIs...",
    "refresh_token": "eyJhbGciOiJIUzI1NiIs..."
  }
}

PNS (Pentagon Name Service) Login

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.

Note: PNS names are lowercase only on-chain. When submitting a PNS name for login, use the lowercase form (e.g. king, not King). The backend normalizes input to lowercase before contract lookup.
PNS is now the primary username system. Legacy PG usernames (e.g. "nftprof1") still work for login but are being phased out. Once a user has a bound PNS name, that becomes their identity across all Pentagon services.

Login Methods Summary

MethodExampleStatus
Email + Passwordnftprof@pentagon.gamesActive
PNS Name + Passwordnftprof (on-chain bound)Active
Current Username + Passwordnftprof1Active
Wallet SignatureMetaMask / Rabby / PhantomActive
Magic Link (email)Sends login link to emailActive
Ethermail SSOEthermail tokenActive
Legacy Username + Passwordnftprof (as legacy of nftprof1)Removed
⚠️ Legacy username login removed (April 29, 2026) — Users can no longer log in with legacy/old usernames. Use your current username, email, or PNS name instead. Legacy usernames are retained in the database for PNS NFT airdrop mapping only.

Error Responses

ErrorMeaning
Unauthenticated userWrong credentials or wallet not registered
User not verifiedEmail not validated yet
Invalid SignatureWallet signature verification failed or timestamp expired

API Endpoints

User Info

MethodEndpointAuthDescription
GET/user/infoBearerCurrent user's full profile
GET/user/info/detailBearerLimited user info
GET/user/info/privateBearerPrivate user info
GET/user/info_by_username/public/<username>NonePublic profile lookup
GET/user/check_usernameAPI KeyCheck username availability
POST/user/edit_detailsBearerUpdate profile

Wallet Binding

MethodEndpointAuthDescription
POST/user/bind_metamaskBearerConnect EVM wallet
POST/user/bind_multiversxBearerConnect MultiversX wallet
POST/user/bind_tronBearerConnect Tron wallet
POST/user/bind_tonBearerConnect TON wallet
POST/user/updateAlgorandBearerConnect Algorand wallet
GET/user/walletinfoBearerGet connected wallets

NFT Data

MethodEndpointAuthDescription
GET/user/nftsBearerUser's NFTs across all chains
GET/user/nfts?collection=GunniesBearerFilter by collection name
GET/user/nfts?order_by=recently_receivedBearerSort by recent
GET/user/penxr/nftsBearerPenXR NFT display data

Social Auth & Connections

MethodEndpointAuthDescription
POST/user/auth/discordBearerConnect Discord
POST/user/auth/twitterBearerConnect Twitter/X
POST/user/auth/telegramBearerConnect Telegram
POST/user/auth/linkedinBearerConnect LinkedIn
POST/user/social/oauth/<platform>/connectBearerGeneric OAuth connect
POST/user/social/<platform>/disconnectBearerDisconnect platform
PUT/user/social/<platform>/privacyBearerUpdate privacy setting

Email & Password

MethodEndpointAuthDescription
POST/user/verify-emailBearerSend verification email
POST/user/validate-emailTokenValidate email from link
POST/user/validate_email/v2TokenValidate email v2
POST/user/password/forgotNoneForgot password
POST/user/password/resetTokenReset password

Friends System

MethodEndpointAuthDescription
GET/user/friendsBearerGet friends list
POST/user/friends/connectBearerSend friend request
GET/user/friends/pendingBearerPending requests
POST/user/friends/updateBearerAccept/reject request

VIP & Referrals

MethodEndpointAuthDescription
GET/user/referrals/<code>NoneReferral info
GET/user/referral_code/validate/<code>NoneValidate referral code
GET/user/discord_rolesBearerUser's Discord roles
GET/user/lookup_vipAPI KeyVIP status by email

SSO (Single Sign-On)

MethodEndpointAuthDescription
POST/sso/authorizeBearerGenerate authorization code
POST/sso/tokenNoneExchange code for token
POST/sso/validateNoneValidate SSO token
GET/sso/user_rolesBearerGet user roles via SSO

2FA & Security

MethodEndpointAuthDescription
POST/user/2fa/generateBearerGenerate 2FA QR code
POST/user/2fa/verifyBearerVerify 2FA code
POST/user/check_echovault_tokenBearerCheck EchoVault token
POST/user/register_echovault_tokenBearerRegister EchoVault token

Signup

MethodEndpointAuthDescription
POST/user/signup/ethermailNoneSignup via Ethermail
POST/api/v3/user/signupNoneSignup v3
POST/api/v4/user/signupNoneSignup v4 (latest)

🖼️ NFT Data Platform

Build on top of Pentagon's cross-chain NFT data. Once a user logs in via the Identity API, your app gets access to their full profile, wallet addresses, social connections, AND their complete NFT portfolio across 15+ blockchains. No indexer setup, no RPC calls, no Alchemy/Moralis subscription needed.

What You Get After User Login

A single login gives your application access to the user's entire Pentagon ecosystem data:

👤 User Identity

Username, email, PNS name, profile picture, about me, interests. The user's Pentagon account is their universal identity.

👛 Wallet Addresses

All connected wallets: EVM (MetaMask, Rabby, Phantom), MultiversX, Tron, TON, Algorand. Plus Pentagon-generated managed wallets.

🖼️ Cross-Chain NFTs

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.

🔗 Social Connections

Discord, Twitter/X, Telegram, LinkedIn accounts. Discord role data. Friend lists and follow relationships.

The Developer Flow

// 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}` }
});

NFT API Endpoints

MethodEndpointAuthDescription
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)

NFT Response Format

// 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
  }
}

Supported Collections & Chains

The Pentagon oracle syncs NFT ownership data from 15+ blockchains. Below are the active collections with NFTs currently tracked:

CollectionChainChain IDNFTs Tracked
ChainGunniesOasys555597,868
AzukiEthereum110,000
Bored Ape Yacht ClubEthereum110,000
Gunnies PFPPentagon Chain33449,093
ChainbunniesEthereum16,052
BCSH (TON)TON1818182,500
BCSH (Monad)Monad1432,498
ZombunniesEthereum11,225
BCSH (Oasys/PC)Pentagon Chain33441,124
BCSH (Polygon)Polygon1371,051
BCSH (SKALE)SKALE1482601649687+
BCSH (Arbitrum)Arbitrum42161426
BCSH (Ethereum)Ethereum1294
BCSH (Core)Core1116227
Chainguardians (Oasys)Oasys5555150
POW Bull BadgeEthereum1142
PEGNAMES (PNS)Pentagon Chain334491
BCSH (BSC)BSC5637
Missing OnezCore111632
BCSH (Avalanche)Avalanche4311415
BCSH (Tron)Tron7281264286

Total: 368,522 NFTs tracked across 2.1M+ metadata records and 353,712 ownership records. 36 collections, 112 contracts, 15+ chains.

⛓️ Supported Chains

Ethereum, Polygon, BSC, Pentagon Chain (3344), Oasys, SKALE, Core, Avalanche, Arbitrum, TON, Monad, Tron, Base

🔄 Sync Frequency

On-chain ownership synced by the nft-sync-cron oracle. Ownership changes reflected within minutes of on-chain transfer.

Use Cases

🎮 Game Integration

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.

📊 Portfolio / Analytics

Build NFT portfolio trackers, collection dashboards, or analytics tools. One login gives you the user's complete cross-chain holdings.

🎨 Creator Tools

Build tools that show creators who holds their NFTs, enable token-gated content, or create holder-only experiences.

🏆 Rewards & Airdrops

Target airdrops or rewards based on what a user holds. Verify NFT ownership for eligibility checks, Discord role assignment, or loyalty programs.

Internal / Approved Application Access

For internal Pentagon services and approved partners: Backend services that need to query NFT data across all users (not just a logged-in user) should use the NFT Metadata API at 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.

🔗 Integration Patterns

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.

✅ Default Approach: Embed login directly in your app. Build your own login form (email/password fields, wallet connect button) and call the Pentagon Identity API directly. Your app collects credentials, sends them to POST /user/login with your X-PG-App-Key, and receives a JWT token. The user never leaves your site.

Do NOT redirect users to pentagon.games for authentication. The API is designed for direct integration. Every pattern below assumes your app handles the login UI and makes API calls server-side or from the frontend. The SSO Widget is the only exception (drop-in popup overlay, not a redirect).

Recommended: Popup Login Modal

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.

✅ Popup Modal (Best)

Login form opens as overlay on your page. User never leaves. This is how pentagon.games, gunnies.io, and mining.pentagon.games work.

⚠️ Inline Form (OK)

Login fields embedded directly in your page (e.g., sidebar or dedicated section). Good for simple apps. Still uses the API directly.

🚫 Redirect (Avoid)

Sending users to pentagon.games to log in, then redirecting back. Breaks flow, loses context, confusing UX. Only use if you have no frontend.

Popup Modal Reference Implementation

Drop-in React login modal with email/password and wallet tabs. Uses your own App Key, no redirect needed.

React (Modal)
Vanilla JS (Modal)
API Only
// 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');
}

Login Methods (All Patterns)

Before choosing a pattern, understand the available login methods. All use POST /user/login with X-PG-App-Key header:

Methodtype fieldUsername / Data FieldsDescription
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

Pattern 1: PG Login + Wallet Match (Strict Identity)

Used by: PenDeFi (pendefi.com), Pentagon Website staking, any app where the user must transact with their registered wallet.

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:

  1. User logs in via any PG login method → gets JWT access_token
  2. App calls GET /user/info with Bearer token → gets mm_address (registered wallet)
  3. User connects browser wallet via RainbowKit/WalletConnect
  4. Frontend compares: if connected.toLowerCase() !== mm_address.toLowerCase() → block, show mismatch warning
  5. All transactions must come from the matched wallet
// 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.

Pattern 2: PG Login + Any Wallet (Flexible Wallet)

Used by: NFT Mining (mining.pentagon.games)

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:

  1. User logs in via PG Identity → gets JWT → app fetches their NFTs via GET /user/nfts
  2. User connects any wallet for mining (does NOT need to match mm_address)
  3. Mining rewards are tracked by the connected mining wallet, not the PG wallet
  4. NFT ownership is verified from PG account data, not from the connected wallet

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:

Pattern 3: PG Login Only (No Wallet)

Used by: Pentagon.games profile pages, social features, NPC points system, internal tools

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:

  1. User logs in via PG Identity → gets JWT
  2. App calls GET /user/info → uses centralised_wallet_address for any chain interactions
  3. Backend handles all transactions using the user's managed wallet
  4. NPC points balance serves as internal gas

When 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.

Pattern 4: Wallet-First (PG Account Check)

Used by: Gunnies.io (gunnies.io), NFT purchase pages

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:

  1. User connects wallet via RainbowKit modal
  2. App sends wallet signature to POST /user/login with type="wallet"
  3. If wallet is bound to a PG account → auto-login, load user data
  4. If wallet is NOT bound → user can still buy NFTs (purchase-only mode) or prompted to create account
  5. After successful wallet login, app checks 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.

Pattern 5: Wallet-First + PG Optional (Zero Friction)

Used by: NFT Mining v2 (web3 mode), any app targeting existing web3 users who don't have a Pentagon account yet.

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.

  1. User connects wallet via RainbowKit (no signup, no PG account needed)
  2. App loads NFTs from NFT data API: GET nft-data.pentagon.games/api/v1/nft/owner/{address}
  3. User can start using the app immediately (e.g., mining with on-chain mode)
  4. App shows CTA to create PG account for enhanced features
  5. If user creates account or logs in, experience upgrades: faster indexed NFT loading, offchain modes, free gas, PNS identity
// 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.

⚠️ Required: PG Account Upsell CTA
Every app implementing Pattern 5 MUST include a visible call-to-action explaining why users should create a Pentagon Games account. This is the conversion funnel from web3 users to the PG ecosystem. Without it, wallet-only users have no reason to join.

Recommended CTA content:
🚀 Upgrade to Pentagon Games
• Faster NFT loading from indexed database (372K+ NFTs, instant)
• Offchain mining with no gas needed
• Mine from a separate wallet (keep NFT holdings safe)
• Free starting gas ($PC) on Pentagon Chain
• PNS name as your cross-ecosystem identity
[Create Account] [Learn about PNS]

Pattern Comparison

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

Wallet Connection Toolkit

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"
}

Transaction Handling

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:

  1. TX Hash tracking: User submits tx, frontend sends tx hash to backend, backend watches for confirmation
  2. Wallet + Nonce tracking: Backend knows the user's wallet and expected nonce, watches for matching tx on-chain

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

Login Widget (SSO)

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.

App Keys

Every application that calls the Pentagon Identity API must include an App Key header to identify itself.

How It Works

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.

Key Types

TypePrefixUse CaseRestrictions
Web Apppk_live_Browser-based appsOrigin-locked to registered domains
Native Apppk_live_Unity games, mobile appsNo origin restriction
Serverpk_live_Backend servicesIP-locked to registered servers
Developmentpk_test_Local developmentlocalhost only

Getting a Key

Contact the Pentagon Games team to register your application and receive a key:

You'll need to provide:

Currently 24 apps registered including Pentagon Website, Gunnies, PenDeFi, BCSH TCG, EtherFantasy, and partner integrations. Turnaround is typically within 24 hours.
Note: App keys are visible in browser dev tools for web apps. That's by design. They're origin-locked, so they can't be used from unauthorized domains. For native apps and servers, treat keys as secrets.

Database Reference

The Pentagon Identity system is backed by pg_identity_db, a dedicated PostgreSQL database. Below is a reference of all tables organized by domain.

Core Identity

Primary user records, wallets, login tracking, and profile data.

TableDescription
userPrimary user accounts (email, username, password hash, created_at, referral info)
user_walletPrimary EVM wallet binding (one per user, used for login and PNS resolution)
user_external_walletsAdditional wallets: MultiversX, Tron, TON, Algorand, and secondary EVM wallets
user_legacy_usernameOld/renamed usernames retained for PNS NFT airdrop mapping
user_login_historyLogin event log (timestamp, IP, method, app key used)
user_dataExtended user metadata and preferences
user_profile_pictureAvatar/profile image references
user_old_usernamesUsername change history

Social & Auth

Social platform connections, OAuth tokens, Discord role sync, and social graph.

TableDescription
user_social_accountsConnected social platforms (Discord, Twitter, Telegram, LinkedIn)
user_discord_rolesSynced Discord roles for gating and VIP status
user_friendshipFriend connections (pending, accepted, blocked)
user_followsFollow relationships between users
social_platformsPlatform definitions and OAuth configuration
user_security_accounts2FA and security key registrations

App Key System

Application registration, key tracking, and usage analytics.

TableDescription
app_registrationsRegistered applications with key, type, allowed origins/IPs
app_key_logsPer-request app key usage logs for analytics and abuse detection
external_appExternal/partner app metadata and integration config

NFT Data

Cross-chain NFT ownership and metadata, synced by backend oracle.

TableDescription
nftsIndividual NFT records (token ID, chain, contract, owner)
nft_collectionCollection definitions (name, chain, type)
nft_contractSmart contract addresses per collection per chain
nft_metadataToken metadata (name, image, attributes, rarity)
nft_ownersCurrent ownership records synced from on-chain data

Campaign & Rewards

Badge campaigns, referral tracking, and reward distribution.

TableDescription
user_campaign_badgesEarned campaign badges
user_campaign_pointsAccumulated campaign points
user_campaign_sub_badgesSub-badge progress within campaigns
user_campaign_ascended_badgesAscended (evolved) badge records
user_campaign_royal_badgesRoyal tier badge records
user_referral_detailsReferral code ownership and usage tracking
referral_rewardsReward payouts for successful referrals

PenXR / Metaverse

Metaverse user data, spaces, inventory, and virtual currency.

TableDescription
penxr_user_dataPenXR user profile and settings
penxr_user_spaceUser-owned virtual spaces
penxr_user_inventoryUser's in-world item inventory
penxr_inventoriesGlobal inventory item definitions
penxr_user_nftsNFTs displayed or used within PenXR
penxr_currencyVirtual currency definitions
penxr_currency_balanceUser currency balances

Wallet Infrastructure

Chain definitions, token lists, bridge logs, and supply tracking.

TableDescription
pgw_chainsSupported blockchain definitions (chain ID, RPC, explorer)
pgw_chain_tokensToken contracts per chain
pgw_swap_chainsChains enabled for swap/DEX features
pgw_swap_chain_tokensTokens available for swap per chain
pen_bridge_logsCross-chain bridge transaction logs
pen_supplyToken supply tracking records

SSO

TableDescription
sso_clientRegistered SSO client applications (client ID, secret, redirect URIs)

Security

Security keys, KYC, anti-sybil, and access control.

TableDescription
security_user_keysHardware security key / passkey registrations
user_kyc_statusKYC verification status per user
sybil_flagsAnti-sybil detection flags and scores
captcha_dataCaptcha challenge and verification records
blocked_email_domainsBlocked disposable/spam email domains

EchoVault

On-chain identity vault contract interactions.

TableDescription
user_echovault_contractUser's EchoVault contract deployment records
user_echovault_eventsEchoVault on-chain event log

Migration Guide

Deadline: May 28, 2026 — After this date, requests without a valid X-PG-App-Key header will be rejected with HTTP 401 on all login/signup endpoints.

Step 1: Get Your Key

Contact Pentagon Games team or check if a key has already been generated for your app.

Step 2: Add the Header

Add X-PG-App-Key to every request that hits login/signup endpoints.

Affected endpoints:

Code Examples

JavaScript
Unity / C#
Python
cURL
// 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"
  }'

Timeline

DateWhat Happens
Apr 28, 2026Now CORS locked. App Keys live in soft mode (warnings only)
May 12, 2026Check-in. Review logs for apps still missing keys
May 21, 2026Final warning to any app without a key
May 28, 2026Enforcement Requests without keys rejected with 401

VIP — My Data Access

⭐ Your Data, Your Key

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

What You Get

ScopeEndpointsDescription
self:read/user/info, /user/nfts, /user/walletinfoYour profile, NFTs, and wallet data
friends:read/user/friends, /user/friends/<username>/infoYour friends list and their public profiles
nfts:read/user/friends/<username>/nftsYour friends' NFT collections

Rate Limits

Key TypeRate 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

Usage

# 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