Everything you need to integrate with Pentagon Name Service
Pentagon Name Service (PNS) is an on-chain username registry on Pentagon Chain. Each name is an ERC-721 NFT with additional features: spatial address binding, URL forwarding, tier-based access control, and SBT-like locking when bound.
Names follow the format @username and resolve to EVM addresses on-chain. Off-chain, names map to profile pages at peg.gg/username and username.peg.gg.
| Name | Pentagon Name Service |
| Symbol | PEGNAMES |
| Proxy | 0xf97EB9f8293D1FD5587a809Eb74518c300738d07 |
| Implementation (V5) | 0x5a084503746745e58e6baA6Cd5778438459919F1 |
| Chain | Pentagon Chain (ID: 3344) |
| RPC | https://rpc.pentagon.games |
| Explorer | https://explorer.pentagon.games |
| Standard | ERC-721 + AccessControl + UUPS Upgradeable |
| Token IDs | Start at 1 (0 is never valid) |
| Gas Token | PC (Pentagon Chain native) |
Every name goes through these states:
MINT (by Moderator)
│
├─ spatialAddr = 0x0 → UNBOUND (tradable NFT)
│ │
│ ├─ User calls rebindSpatial() → BOUND (locked, SBT-like)
│ │ │
│ │ ├─ Can set forwardUrl()
│ │ ├─ Can use as PG login
│ │ ├─ Name resolves on-chain
│ │ ├─ CANNOT transfer or sell
│ │ │
│ │ └─ Moderator calls moderatorUnbind() → UNBOUND (tradable again)
│ │
│ └─ User transfers/sells → New owner (UNBOUND)
│
└─ spatialAddr != 0x0 → BOUND at mint (locked immediately)
└─ (same as bound state above)
| Role | Identifier | Permissions |
|---|---|---|
| Owner (DEFAULT_ADMIN_ROLE) | 0x00 | Rename any name. Rebind Super-High SBT addresses. Upgrade contract. Manage roles. |
| Moderator | keccak256("MODERATOR_ROLE") | Mint new names. Batch mint. Claim mint. Unbind names. Bind to any address (airdrops). |
| NFT Holder | (token owner) | Bind to own address only. Set forward URL (only when bound). Transfer (only when unbound). |
All read functions are free (no gas). Use these to resolve names in your dApp.
function tokenOfName(string name) → uint256
// Returns the tokenId for a name. Reverts if name doesn't exist.
function nameExists(string name) → bool
// Check if a name is registered. Use before tokenOfName to avoid reverts.
function nameOf(uint256 tokenId) → string
function tokenName(uint256 tokenId) → string // same, public mapping
function spatialBinding(uint256 tokenId) → address
function spatialOf(uint256 tokenId) → address // alias
// Returns the wallet address the name resolves to.
// Returns address(0) if unbound.
function forwardUrl(uint256 tokenId) → string
// Returns the forward URL for the name.
// Empty string if not set. Can only be set when bound.
function tokenTier(uint256 tokenId) → Tier // 0=SuperHigh, 1=High, 2=Medium
function tierOf(uint256 tokenId) → Tier // alias
function boundLocked(uint256 tokenId) → bool // true if bound (locked)
function sbtUnlocked(uint256 tokenId) → bool // true if Super-High is unlocked for transfer
function ownerOf(uint256 tokenId) → address // standard ERC-721
function balanceOf(address owner) → uint256 // standard ERC-721
function totalMinted() → uint256 // total names ever minted
// Resolve @king to a wallet address:
uint256 tokenId = pns.tokenOfName("king");
address wallet = pns.spatialBinding(tokenId);
// Resolve @king to a forward URL:
uint256 tokenId = pns.tokenOfName("king");
string memory url = pns.forwardUrl(tokenId);
function mint(address to, string name, Tier tier, address spatialAddr) → uint256 tokenId
// Mint a single name. spatialAddr=address(0) for unbound mint.
function mintBatch(address[] tos, string[] names, Tier[] tiers, address[] spatialAddrs)
// Batch mint. Arrays must be same length.
function claimMint(address to, string name, Tier tier, address spatialAddr) → uint256 tokenId
// Mint with one-per-address enforcement. Reverts if address already claimed.
function rebindSpatial(uint256 tokenId, address newAddr)
// Bind name to an address. Regular users: newAddr must equal msg.sender.
// Moderator: can bind to any address.
// Owner: can rebind Super-High SBTs.
// Binding sets boundLocked=true (locks the NFT).
function moderatorUnbind(uint256 tokenId)
// Clears spatial binding, forward URL, and unlocks the NFT for transfer.
// Called when user submits support ticket to sell their name.
function setForwardUrl(uint256 tokenId, string url)
// Set forward URL. Requires: ownerOf(tokenId)==msg.sender AND boundLocked==true.
// Must bind first before setting forward URL.
function rename(uint256 tokenId, string newName)
// Change the name string on any token. For security/IP/compliance only.
function setSbtUnlocked(uint256 tokenId, bool unlocked)
// Unlock a Super-High SBT for one transfer. Re-locks after transfer.
function transferFrom(address from, address to, uint256 tokenId)
function safeTransferFrom(address from, address to, uint256 tokenId)
// Standard transfers. Blocked if boundLocked==true or SBT locked.
// Clears binding + forward URL on successful transfer.
event NameMinted(uint256 indexed tokenId, string name, Tier tier, address indexed to, address spatialAddr)
event NameRenamed(uint256 indexed tokenId, string oldName, string newName)
event SpatialBound(uint256 indexed tokenId, address indexed spatialAddr)
event NameUnbound(uint256 indexed tokenId)
event SbtLockChanged(uint256 indexed tokenId, bool unlocked)
event ForwardUrlSet(uint256 indexed tokenId, string url)
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) // ERC-721
| Tier | Enum Value | Token Type | Tradable | Price Range | Binding |
|---|---|---|---|---|---|
| Super-High | 0 | SBT | Never (unless Owner unlocks) | 20-50 PC | Owner only rebind |
| High | 1 | NFT | When unbound | 5-20 PC | User self-bind |
| Medium | 2 | NFT | When unbound | 1-5 PC | User self-bind |
| Low | N/A | Off-chain | N/A | Free | N/A |
Tiers are set at mint and cannot be changed. The AI classification API determines the tier based on name rarity, cultural significance, and web presence.
The AI classification service runs on the PNS backend and scores names in real time.
Base URL: https://id.peg.gg/api
POST /api/check
Content-Type: application/json
{ "name": "king", "user_key": "optional_rate_limit_key" }
Response:
{
"name": "king",
"score": 74,
"tier": "high",
"action": "reserve_premium",
"price_pc": 16,
"reasons": ["intrinsic score", "cultural/IP match", "web popularity"],
"signals": {
"length": 4,
"is_dictionary_word": true,
"search_result_bin": "10M+",
"domain": { "com": "taken", "net": "taken", ... },
"social": { "x": "taken", "instagram": "taken", ... },
"cultural_ip_hits": [{ "kb": "Wikipedia", "id": "King", "label": "Monarch" }]
},
"cached": false,
"checked_by": "venice-v1"
}
POST /api/suggest
{ "name": "king" }
Response:
{
"original": "king",
"suggestions": [
{ "name": "kingx", "tier": "low", "price_pc": 0, "available": true },
{ "name": "theking", "tier": "medium", "price_pc": 3, "available": true }
]
}
GET /api/stats
{ "total_classified": 68, "today_checks": 5, "daily_limit": 1000, "tiers": [...] }
30 seconds between new name checks per user. 1,000 AI checks per day globally. Cached lookups are unlimited and instant.
PNS names can be used as Pentagon login credentials. The resolution chain:
PNS Username → tokenOfName() → tokenId → spatialBinding() → wallet address → PG user by mm_address
nameExists(username) on the PNS contracttokenOfName(username) → tokenIdspatialBinding(tokenId) → wallet addressSELECT * FROM user WHERE mm_address = walletimport { ethers } from 'ethers';
const RPC = 'https://rpc.pentagon.games';
const PNS = '0xf97EB9f8293D1FD5587a809Eb74518c300738d07';
const ABI = [
'function nameExists(string) view returns (bool)',
'function tokenOfName(string) view returns (uint256)',
'function spatialBinding(uint256) view returns (address)',
'function forwardUrl(uint256) view returns (string)',
'function nameOf(uint256) view returns (string)',
'function ownerOf(uint256) view returns (address)',
'function boundLocked(uint256) view returns (bool)',
'function tierOf(uint256) view returns (uint8)',
'function totalMinted() view returns (uint256)',
'function rebindSpatial(uint256,address)',
'function setForwardUrl(uint256,string)',
];
const provider = new ethers.JsonRpcProvider(RPC);
const pns = new ethers.Contract(PNS, ABI, provider);
// Resolve a name to a wallet
async function resolve(name) {
if (!await pns.nameExists(name)) return null;
const tokenId = await pns.tokenOfName(name);
return await pns.spatialBinding(tokenId);
}
// Get all info about a name
async function getNameInfo(name) {
if (!await pns.nameExists(name)) return null;
const tokenId = await pns.tokenOfName(name);
return {
tokenId: tokenId.toString(),
owner: await pns.ownerOf(tokenId),
spatial: await pns.spatialBinding(tokenId),
url: await pns.forwardUrl(tokenId),
tier: await pns.tierOf(tokenId), // 0=SuperHigh, 1=High, 2=Medium
bound: await pns.boundLocked(tokenId),
};
}
// Bind your name (requires signer)
async function bindName(tokenId, signer) {
const pnsWrite = pns.connect(signer);
const tx = await pnsWrite.rebindSpatial(tokenId, await signer.getAddress());
return tx.wait();
}
// Set forward URL (must be bound first)
async function setUrl(tokenId, url, signer) {
const pnsWrite = pns.connect(signer);
const tx = await pnsWrite.setForwardUrl(tokenId, url);
return tx.wait();
}
from web3 import Web3
RPC = 'https://rpc.pentagon.games'
PNS = '0xf97EB9f8293D1FD5587a809Eb74518c300738d07'
ABI = [...] # Full ABI from contract
w3 = Web3(Web3.HTTPProvider(RPC))
pns = w3.eth.contract(address=PNS, abi=ABI)
# Resolve name to wallet
def resolve(name):
if not pns.functions.nameExists(name).call():
return None
token_id = pns.functions.tokenOfName(name).call()
return pns.functions.spatialBinding(token_id).call()
# Check if bound
def is_bound(name):
token_id = pns.functions.tokenOfName(name).call()
return pns.functions.boundLocked(token_id).call()
# Check if name exists
cast call 0xf97EB9f8293D1FD5587a809Eb74518c300738d07 \
"nameExists(string)(bool)" "king" \
--rpc-url https://rpc.pentagon.games
# Get token ID for a name
cast call 0xf97EB9f8293D1FD5587a809Eb74518c300738d07 \
"tokenOfName(string)(uint256)" "king" \
--rpc-url https://rpc.pentagon.games
# Get spatial binding
cast call 0xf97EB9f8293D1FD5587a809Eb74518c300738d07 \
"spatialBinding(uint256)(address)" 1 \
--rpc-url https://rpc.pentagon.games
Contract source code available on request. Full ABI in the Pentagon Explorer.