Pentagon Payment Processor

Centralized cross-chain payment stack for Pentagon Games ecosystem

Quick Links:

1. Overview

The Pentagon Payment Processor is a centralized smart contract + backend system that handles payments across multiple chains and payment methods. Products in the Pentagon ecosystem (EtherFantasy, Gunnies, Setsuko, etc.) use the same contracts and backend for payment processing, revenue tracking, and reward fulfillment.

Purpose: Why This Document Exists

Beyond "how to take a payment," this stack exists so that every dollar (or PC) a user spends anywhere in the Pentagon ecosystem lands in one place they can see, query, and prove. Think of it as a block explorer scoped to a single user's spending across all of Pentagon Games: NFT mints, mining fees, NPC credits, Stripe purchases, campaign claims, cross-chain bridges, everything.

That single source of truth powers two distinct audiences, each addressed by its own section below:

The long-term goal: monitor all of a user's on-chain contract spending on Pentagon Chain and recognize what each spend was for. Anything we can't recognize is a signal: it's a service that hasn't been wired into the payment stack yet. When that happens, point the owning team (or their session agent) at the Spend Capture section and ask them to describe their payment design pattern so the tracker can incorporate it.

Supported Payment Methods

MethodChainContract Function
PC (native) Pentagon 3344 purchaseWithNative(skuId)
USDC Ethereum purchaseWithToken(skuId, tokenAddress)
PC (ERC-20) Ethereum purchaseWithToken(skuId, tokenAddress)
Stripe (fiat) Off-chain Stripe Checkout → webhook

Payment Rails — How to Charge a User (Standard & Options)

Going forward, the standard way to charge for anything in the Pentagon ecosystem is the multichain Payment Processor (purchaseWithNative / purchaseWithToken). Its defining benefit is multichain: the same SKU can be sold for native PC on Pentagon Chain or USDC / PC-ERC-20 on Ethereum, through one contract and one settlement ledger (cross_chain_purchase_log in pg_payments_db). New products should default to it.

Other rails exist for specific cases — direct P2P pay with earn-back (CardSpendSplitter), in-game action spend (PayHub), fiat (Stripe), and custodial Points spend — and are documented below so every spend lands in the same ledger. But a SKU sale is the processor.

RailUse forChainsEntry pointStatus
Payment Processor STANDARD SKU sales — NFT mints, digital packs, NPC top-ups, anything with a price Pentagon (PC) + Ethereum (USDC / PC-ERC-20) purchaseWithNative(skuId) / purchaseWithToken(skuId, token) on 0x3930B34a… (PC) / 0xe6bde156… (ETH) LIVE
Custodial Points (spend_purchase) Let a logged-in user pay a processor SKU straight from their PG Points balance (AA wallet) — no wallet signing Pentagon (backend signs AA wallet → processor) POST /user/npc/spend_purchasepurchaseWithNative(skuId) TO BUILD
CardSpendSplitter (P2P direct + earn-back) Direct peer/creator payments that split to payee + platform + a held / earn-back bucket Pentagon (PC) POST /user/npc/card-spendCardSpendSplitter.pay(payee) LIVE (SKU 18)
PayHub (GamePayHub) In-game action spend (AR mining boosts, etc.), attributed per game id Pentagon (PC) POST /user/npc/spend_arGamePayHub.payPC(gameId, paymentId) LIVE
Stripe (fiat) Credit-card purchases (Foundation Heroes, NPC credits) Off-chain → webhook Stripe Checkout → /stripe/webhook LIVE

The Standard Flow (multichain processor)

One SKU, priced independently per chain/token, bought through one contract; one backend indexes all of it.

Buyer (own wallet OR custodial AA wallet) │ purchaseWithNative(skuId){value: price} ← Pentagon Chain, native PC │ purchaseWithToken(skuId, USDC|PC-ERC20) ← Ethereum ▼ PaymentProcessor 0x3930B34a… (PC) · 0xe6bde156… (ETH) ── emits Payment event ▼ event_tracker (workers) → cross_chain_purchase_log (pg_payments_db), purchase_status='confirmed' ▼ reward_service by item_type: nft → mint via minter token → send PC to AA wallet digital_item → settle, NO drop (vendor/project delivers off-chain) ▼ Read: /admin/payments/query · Vendor receiving: payments.pentagon.games (Projects)

Authoritative store: pg_payments_db (RDS) holds all payment info. The pg_identity_db copy of these tables is legacy/frozen — never read settlement from it. Identity touches payments only at the AA-wallet signing step.

Custodial Points — spend_purchase (TO BUILD)

RULE Resolve the canonical wallet from identity — never derive it. Every Points-consuming service MUST resolve the user's AA wallet from Pentagon Identity (user_wallet.address today, PGBalanceAccount post-AA2). Identity wallets are random per-user (keystore under WALLET_PASSWORD, no HD seed), so any independent or deterministic derivation yields a different address and spends the wrong balance. Cautionary example: NFT Mining derived wallets from its own NPC_MASTER_SEED and diverged from users' real Points wallets (now migrating to resolve-not-derive). Payments is aligned — resolve_aa_wallet returns identity's user_wallet.address, so SKU-17 Points credit the canonical wallet.

This is the one piece not yet built, and the reason "pay with Points" is blocked on storefronts today. It lets a logged-in user pay a processor SKU from their PG Points balance without signing: the backend derives the user's AA wallet key and calls purchaseWithNative(skuId) server-side. It is a sibling of the existing /user/npc/spend_ar — same custodial-signing infra, pointed at the processor instead of PayHub. Build it as a Django endpoint on the accounts backend.

Reference implementation: user/airdrop_confirm_view.py in pentagon-login-backend ran this exact sequence for the $CT airdrop (6,786 allocations, zero double-pays, zero nonce collisions). Copy its ordering — the order IS the point.

def spend_purchase(user, sku_id):
    # 1) IDEMPOTENCY FIRST — write the intent row BEFORE any chain action.
    #    UNIQUE(user_id, sku, intent_nonce) → retries/double-clicks collapse to IntegrityError.
    try:
        ledger = PayoutLedger.objects.create(user=user, sku=sku_id, status="pending")
    except IntegrityError:
        return existing_row_status(user, sku_id)            # never a second send

    # 2) NONCE SERIALIZATION — one Postgres advisory lock PER SENDER ADDRESS, held
    #    ACROSS get_nonce → sign → send (works across all gunicorn workers/boxes).
    with pg_advisory_lock(key=hash64(sender_address)):
        nonce   = w3.eth.get_transaction_count(sender_address)
        tx      = processor.functions.purchaseWithNative(sku_id).build_transaction(
                      {"nonce": nonce, "value": price_wei, ...})
        signed  = w3.eth.account.sign_transaction(tx, aa_key)
        tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
    # lock released only AFTER send — no other worker can grab the same nonce

    # 3) settle ledger from the receipt (async poll): pending → confirmed/failed
    ledger.mark(tx_hash)
TrapRule
Double-pay on retryIdempotency row must be written BEFORE the send, deduped by a DB UNIQUE constraint (not an if-exists check — that races).
Nonce collision under click-stormsThe advisory lock must span get_nonce through send_raw_transaction. Lock only around get_nonce = two workers sign the same nonce; one tx silently replaces the other.
Timeout after sendDo NOT auto-retry the send (the tx may still land). Mark the row unknown and reconcile from the on-chain Payment event.

Build Status — what's live vs. what to build

CapabilityStatusOwner
SKU config + on-chain price (any SKU, any chain)LIVEpayments admin (/admin/payments)
Direct-wallet purchase (PC / USDC / PC-ERC-20)LIVEprocessor + hosted checkout
Record → settle → vendor dashboardLIVEevent_tracker + reward_service + payments.pg
Hosted checkout (any app links to it)LIVEpayments.pentagon.games/pay?project=<key>
Custodial Points payment (spend_purchase)TO BUILDaccounts backend (Django)
Project-managed delivery report (project_orders + allocation)TO BUILDintegration API + vendor

Worked Example — TCG / KEEPS packs (project-managed digital items)

The KEEPS card packs (tcg.etherfantasy.com) are live SKUs on the processor, sold for native PC:

PackSKUPricePointsitem_type
keeps_pack_x1220.025 PC25digital_item
keeps_pack_x5230.125 PC125digital_item
keeps_pack_x10240.25 PC250digital_item

Added via /admin/payments (register config row → owner signs setPrice on-chain → activate). A buyer purchases at payments.pentagon.games/pay?project=prj_tcg (PG login → "credited to @username" → pay PC). The processor records + settles it (digital_item = no on-chain drop); EtherFantasy delivers the packs off-chain and reports the pack/card allocation back via the delivery endpoint (TO BUILD). Points payment for these packs lights up when spend_purchase ships.

What This Is NOT

The payment processor handles payments and fulfillment. It does not own or gatekeep NFT metadata, images, or ownership data. Understanding the boundary helps you integrate correctly.

Not ThisUse This Instead
NFT metadata storage or image hosting NFT Data API — the centralized index for ownership, metadata, and images
The only way to mint Projects can self-mint using their own backend. Grant our payment processor moderator/minter access to mint in parallel, not instead of your own minting
The gatekeeper for NFT data updates Any authorized caller (your project backend, pfpvault admin panel, payment processor) can call the NFT Data API refresh endpoint directly
A metadata management system After fulfillment (mint, upgrade, etc.), the caller notifies NFT Data API to refresh. Payment processor is one such caller, not the only one
A replacement for your project's backend Your backend handles your product logic (upgrades, game state, etc.). Payment processor just detects payments and triggers fulfillment

How It Fits Together

Payment Processor NFT Data API Your Project Backend (payments + fulfillment) (metadata + ownership) (product logic) │ ▲ ▲ │ ┌─────────────────────┤ │ │ │ Any of these can │ │ │ │ call refresh: │ │ │ │ │ │ ├───┘ payment processor ─┤ │ │ project backend ──┤ │ │ pfpvault admin ──┘ │ │ user self-serve │ │ │ └── "fulfill this" webhook ─────────────────────────┘ (project does the work, returns proof)

The pattern: Payment processor detects payment, calls your project webhook to fulfill, your project does its thing (mint, upgrade, whatever), then whoever did the work notifies NFT Data API to refresh. For projects that self-mint without going through payments, they call NFT Data API directly. Same endpoint, same app key.

2. Smart Contracts

One PaymentProcessor contract deployed per chain. Same Solidity source, same interface.

Pentagon Chain (3344)

Contract0x3930B34a524170Cc8966859Da167DB7B5413A0ba
Owner0xB2e3e82a95f5c4c47E30A5b420Ac4f99d32EF61f
Payment TokenPC (native)
ExplorerView on Explorer

Ethereum Mainnet (1)

Contract0xe6bde156369d209c4d420e966541ee17093705b5
Owner0xB2e3e82a95f5c4c47E30A5b420Ac4f99d32EF61f
USDC0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
PC (ERC-20)0x6c3ea9036406852006290770bedfcaba0e23a0e8
ExplorerView on Etherscan

Contract Interface

// Read prices
mapping(uint256 => mapping(address => uint256)) public prices;
// skuConfigs[skuId] → (MintType, distributor)
mapping(uint256 => SkuConfig) public skuConfigs;

// Owner-only: configure SKUs
function setPrice(uint256 skuId, address token, uint256 price) external onlyOwner;
function setSkuConfig(uint256 skuId, MintType mintType, address distributor) external onlyOwner;

// User purchase functions
function purchaseWithNative(uint256 skuId) external payable;
function purchaseWithToken(uint256 skuId, address tokenAddress) external;

// Owner-only: withdraw collected funds
function withdrawNative() external onlyOwner;
function withdrawToken(address tokenAddress) external onlyOwner;

// Events
event PaymentReceived(uint256 indexed skuId, address indexed buyer, bool indexed minted);
event DirectMintSuccessful(uint256 indexed skuId, address indexed buyer, address distributor);
event DirectMintFailed(uint256 indexed skuId, address indexed buyer, address distributor);
    

3. Payment Flow

On-Chain (PC / USDC / ERC-20)

User → PaymentProcessor.purchaseWithNative(skuId) [Pentagon Chain] or PaymentProcessor.purchaseWithToken(skuId, token) [Ethereum] │ ├─ Contract checks: prices[skuId][token] > 0, amount matches exactly ├─ Contract transfers payment to itself ├─ If skuConfig = DIRECT → calls distributor.mintTo(buyer) on same chain ├─ If skuConfig = BACKEND → emits event only │ └─ Emits: PaymentReceived(skuId, buyer, minted) │ ▼ Backend Scanner (ef-scanner-pc / ef-scanner-eth) │ ├─ Picks up PaymentReceived event ├─ Creates cross_chain_purchase_log record ├─ Triggers reward fulfillment (mint NFT / send tokens) └─ Updates status: pending → processing → completed

Stripe (Fiat)

User → Stripe Checkout Session (pentagon.games/account) │ ├─ Stripe processes payment ├─ Sends webhook: checkout.session.completed │ └─ Backend receives webhook at /stripe/webhook │ ├─ Validates Stripe signature ├─ Creates cross_chain_purchase_log (payment_method: "stripe") ├─ Triggers reward fulfillment └─ Updates status: confirmed → completed

4. SKU Configuration

Current SKUs

SKUProductChainPrice (USD)FulfillmentStatus
2TamagoPC (3344)$10Centralized mint✅ Live
3VaelionPC (3344)$10Centralized mint✅ Live
4No5PC (3344)$10Centralized mint✅ Live
5MusklSKALE Nebula$50Centralized mint + gas bonus (0.5 PC)✅ Live
7Monad HeroMonad (143)$25Centralized mint + gas bonus✅ Live
9EtherFantasy CharacterPC (3344)$0.10–$10Project-managed (see below)⚠️ Project
11ARB Hero — LazuliArbitrum$260Centralized mint + gas bonus✅ Live
12Core HeroCore (1116)$10Centralized mint + gas bonus✅ Live
13Polygon Hero — AvyaanPolygon$200Centralized mint + gas bonus✅ Live
14Avax Hero — LaunchAvalanche$50Centralized mint + gas bonus✅ Live
15BNB Hero — SentinelBSC$400Centralized mint + gas bonus✅ Live
171000 NPC CreditsPC (3344)$35Centralized (token send)✅ Live
22KEEPS Pack x1 (TCG)PC (3344)~$1 (0.025 PC)Project-managed (digital_item, EF delivers)✅ Live
23KEEPS Pack x5 (TCG)PC (3344)~$5 (0.125 PC)Project-managed (digital_item, EF delivers)✅ Live
24KEEPS Pack x10 (TCG)PC (3344)~$10 (0.25 PC)Project-managed (digital_item, EF delivers)✅ Live

Fulfillment Modes

The payment processor supports two fulfillment modes per SKU:

Mode 1: Centralized Fulfillment (default)

The payment processor backend mints the NFT directly using a shared minter wallet. Project grants MINTER_ROLE to the payment processor's moderator address. No project backend needed.

User pays → Scanner captures event → Fulfillment service mints NFT → Done

Requirements:
  1. Deploy your NFT contract with a mintTo(address) or mintNextToken(address) function
  2. Grant minter role to: 0x9A7c1e986e49C2Ace0e85Ccc6eeeb262D46b7521
  3. Register SKU with contract address + mint ABI in cross_chain_payment_config
  4. Set price on PaymentProcessor contract

Used by: Tamago, Vaelion, No5, Muskl, all Chain Heroes, NPC Credits
        

Mode 2: Project-Managed Fulfillment

The payment processor captures the payment event for revenue tracking and referral commissions, but the project's own backend handles delivery. Use this when fulfillment requires game-specific logic (character assignment, inventory, etc.) that the centralized processor can't handle.

User pays → Scanner captures event → Record stored in pg_payments_db
                                      (revenue tracked, referrals calculated)
         → Project's own scanner also picks up event
         → Project backend handles mint/delivery from its own DB

Requirements:
  1. Project runs its own scanner watching the PaymentProcessor contract
  2. Project handles minting/delivery independently
  3. SKU is NOT added to cross_chain_payment_config (prevents centralized fulfillment)
  4. Payment data still flows through pg_payments_db for referral/revenue tracking

Used by: EtherFantasy (SKU 9)
        

Example: EtherFantasy (Project-Managed, SKU 9)

EtherFantasy runs its own Node.js/TypeScript backend with:

The centralized scanner also captures SKU 9 events into pg_payments_db for revenue reporting and referral commission calculation. But it does not attempt to fulfill — SKU 9 has no entry in cross_chain_payment_config, so the centralized fulfillment service skips it.

This pattern works for any project with complex delivery logic. The centralized stack handles money, the project handles product.

Adding a New SKU

Owner calls on each chain's contract:

// 1. Set price (per token, per chain)
setPrice(skuId, tokenAddress, priceInSmallestUnit)
// Use address(0) for native token

// 2. Configure fulfillment
setSkuConfig(skuId, mintType, distributorAddress)
// mintType: 0 = BACKEND (cross-chain, backend fulfills)
//           1 = DIRECT (same-chain, contract calls distributor.mintTo)

// 3. Add to cross_chain_payment_config table in backend DB
INSERT INTO cross_chain_payment_config
  (sku_id, sku_name, item_type, item_name, amount, contract_chain_id, ...)
    

5. Authentication

All internal API endpoints require an app key passed via the X-PG-App-Key header. The same keys issued for the NFT Data API work here. One key, multiple Pentagon services.

App Key Header

curl -H "X-PG-App-Key: pk_live_your_key" \
  "https://api.account.pentagon.games/api/v1/payments?sku_id=9&status=completed"
        

Rate Limits

Auth LevelLimitScope
No key (public)BlockedInternal endpoints require a key
App key (web)60–100 req/minPer key
App key (server)100–500 req/minPer key

Rate limits are configurable per app key. When exceeded, the API returns 429 Too Many Requests with a retry_after field. The Stripe webhook endpoint does not require an app key (authenticated by Stripe signature instead).

Access Scopes

App keys can be issued with different access scopes:

ScopeAccessUse Case
payments:readRead own payments only (wallet-bound)User-facing frontends, Payment Explorer (user view)
payments:read:allRead all payments across all buyersAdmin dashboards, internal tools, Payment Explorer (admin view)
payments:writeTrigger retries, update statusAdmin fulfillment tools

User-scoped keys (payments:read) can only query payments matching the wallet address bound to their key or authenticated session. Admin keys (payments:read:all) can query any buyer.

6. API Endpoints

HEADS UP Built vs. documented: the /api/v1/payments/* read endpoints below are a design target, not yet built (calling /api/v1/payments/by-buyer/… returns 404 today). What actually exists on api.account.pentagon.games: POST /payment/register, POST /payment/status, /stripe/webhook, /stripe/payment_status, and GET /admin/payments/query (the canonical read, auth via a dedicated PAYMENTS_READ_KEY, not the god key). All read the live pg_payments_db via the PAYMENTS_DB alias. Build new integrations against those; treat /api/v1/* as roadmap until marked LIVE.

Infrastructure Note

Payment API endpoints currently live on api.account.pentagon.games, which is the Pentagon Identity API (login, user profiles, wallets, social auth, VIP). The "account" in the domain name covers both meanings: user accounts (identity) and financial accounting (payments).

This is intentional for now. The payment endpoints (/stripe/webhook, /stripe/payment_status) share the same nginx, SSL, and server infrastructure as the identity API on pg-identity (13.212.154.41). The payment processing workers (on-chain scanner + NFT fulfillment + minter key) run on a separate internal-only server pg-payment-workers (18.143.93.137) with no public HTTP ports.

ComponentServerDomainFacing
Payment API (webhook, status) pg-identity (13.212.154.41) api.account.pentagon.games 3rd-party (Stripe, frontends)
Identity API (login, wallets, VIP) pg-identity (13.212.154.41) api.account.pentagon.games 3rd-party (frontends, SSO clients)
On-chain scanner pg-payment-workers (18.143.93.137) None (internal) Internal only
NFT fulfillment + minter key pg-payment-workers (18.143.93.137) None (internal) Internal only

When payment volume warrants its own domain, the API endpoints will move to api.payment.pentagon.games on a dedicated server. The workers server with the minter key stays isolated either way.

LIVE

Base URL: https://api.account.pentagon.games (shared with Identity API)

Future Base URL: https://api.payment.pentagon.games (when split)

REQUIRES APP KEY

All /api/v1/payments/* endpoints require a valid X-PG-App-Key header. Same keys as the NFT Data API.

GET /api/v1/payments LIVE

List payments with optional filters. Admin keys return all records. User keys return only payments matching their bound wallet.

ParamTypeDescription
buyeraddressFilter by buyer wallet
sku_idintFilter by SKU
chain_idintFilter by chain
statusstringpending | confirmed | completed | failed
fromdateStart date (YYYY-MM-DD)
todateEnd date (YYYY-MM-DD)
pageintPage number (default 1)
limitintPer page (default 50, max 200)
GET /api/v1/payments/summary

Monthly revenue summary grouped by chain, token, and SKU.

{
  "total_revenue_usd": 12450.00,
  "months": [
    {
      "month": "2026-03",
      "payments": 6283,
      "revenue": {
        "pc_pentagon": { "amount": 1570.75, "count": 6275 },
        "usdc_eth": { "amount": 64.00, "count": 8 }
      }
    }
  ],
  "withdrawable": {
    "pentagon": { "pc": 614.925 },
    "ethereum": { "usdc": 183.17, "pc_erc20": 0 }
  }
}
        
GET /api/v1/payments/{payment_id} LIVE

Get full detail for a single payment including buyer identity, on-chain proof, and delivery status. The buyer_identity block is resolved from the Pentagon Identity system (admin keys only; user keys see their own identity only).

{
  "payment": {
    "id": 8412,
    "tx_hash": "0x7fa2cf58a278b2df6d74...",
    "chain_id": 3344,
    "block_number": 3350142,
    "buyer": "0xcfb0aee8f7445140d49b...",
    "sku_id": 9,
    "sku_name": "EtherFantasy Character",
    "payment_method": "native",
    "payment_token": "PC",
    "amount": "250000000000000000",
    "amount_usd": 0.25,
    "purchase_status": "confirmed",
    "created_at": "2026-03-30T14:22:11+00:00"
  },
  "buyer_identity": {
    "pg_user_id": 41892,
    "username": "cryptoking",
    "pns_name": "king",
    "email": "king@example.com",
    "wallets": [
      "0xcfb0aee8f7445140d49bf589e30db43fd2ac05e9",
      "0xab12...7890"
    ],
    "social": {
      "discord": { "id": "123456789", "username": "cryptoking#1234" },
      "twitter": { "handle": "@cryptoking" },
      "telegram": { "id": "987654321", "username": "cryptoking" }
    },
    "vip_tier": "VIP2",
    "registered_at": "2025-06-12T08:30:00+00:00",
    "last_login": "2026-05-20T14:15:22+00:00",
    "resolution": "mm_address"
  },
  "delivery": {
    "status": "completed",
    "reward_tx_hash": "0x99d815d2af3e7b82...",
    "reward_chain_id": 3344,
    "token_id": 4201,
    "contract": "0x8F83c6122Dd4d275B53a...",
    "delivered_at": "2026-03-30T14:27:45+00:00"
  },
  "on_chain_proof": {
    "payment_event_found": true,
    "payment_block": 3350142,
    "delivery_verified": true,
    "current_nft_owner": "0xcfb0aee8f7445140d49b..."
  }
}
        

Buyer Identity Resolution

The buyer_identity block triangulates the on-chain wallet address against the Pentagon Identity database to resolve the full user profile. Resolution checks three sources in order:

SourceDB FieldMatch
Primary EVM walletuser.mm_addressCase-insensitive match against buyer address
Additional walletsuser_wallet tableSecondary bound wallets (MetaMask, Rabby, etc.)
External walletsuser_external_wallets tableMultiversX, Tron, TON, Algorand addresses

The resolution field indicates which source matched: mm_address (primary), user_wallet (secondary), or external_wallet. If the wallet is not linked to any PG account, buyer_identity returns null with resolution: "unregistered".

Social Contact Fields (Admin Only)

The social block includes all connected accounts from the Pentagon Identity system. Use these to contact buyers for support:

PlatformFieldsContact Method
EmailemailDirect email (always present if registered)
Discordsocial.discord.id, social.discord.usernameDM via bot or manual
Twittersocial.twitter.handleDM or @mention
Telegramsocial.telegram.id, social.telegram.usernameTG DM

Social fields are only populated if the user connected that platform to their PG account. Admin keys see all fields. User keys see only their own profile (no other buyers' contact info).

Delivery Status Values

StatusMeaning
pendingPayment confirmed, awaiting fulfillment
processingMint transaction submitted, waiting for confirmation
completedNFT minted and delivered. reward_tx_hash + token_id present.
failedFulfillment failed (contract error, gas, minter overloaded)
stuckProcessing started but never completed (server crash, timeout)
GET /api/v1/payments/by-buyer/{address} LIVE

All payments for a specific wallet with delivery status for each, plus the resolved Pentagon user identity. User keys can only query their own bound wallet. Admin keys can query any address.

{
  "buyer": "0xcfb0aee8f7445140d49bf589e30db43fd2ac05e9",
  "buyer_identity": {
    "pg_user_id": 41892,
    "username": "cryptoking",
    "pns_name": "king",
    "email": "king@example.com",
    "social": {
      "discord": { "id": "123456789", "username": "cryptoking#1234" },
      "twitter": { "handle": "@cryptoking" },
      "telegram": { "id": "987654321", "username": "cryptoking" }
    },
    "vip_tier": "VIP2"
  },
  "total": 50,
  "delivered": 44,
  "pending": 0,
  "failed": 6,
  "page": 1,
  "payments": [
    {
      "id": 8412,
      "sku_id": 9,
      "tx_hash": "0x7fa2cf58...",
      "chain_id": 3344,
      "amount_usd": 0.25,
      "delivery_status": "completed",
      "token_id": 4201,
      "created_at": "2026-03-30T14:22:11+00:00"
    }
  ]
}
        
GET /api/v1/payments/by-collection/{contract} LIVE

All payments for a specific NFT collection (by contract address). Returns payment + delivery records for every purchase targeting this collection. Admin keys only.

ParamTypeDescription
contractaddress (path)NFT contract address
statusstring (query)Filter: pending | completed | failed | stuck
fromdate (query)Start date (YYYY-MM-DD)
todate (query)End date (YYYY-MM-DD)
pageint (query)Page number (default 1)
limitint (query)Per page (default 50, max 200)
GET /api/v1/payments/stats LIVE

Aggregate payment and delivery statistics. Admin keys only.

{
  "total_payments": 12228,
  "by_status": {
    "completed": 6440,
    "failed": 2571,
    "stuck": 424,
    "pending": 4
  },
  "by_sku": {
    "9": { "total": 12204, "completed": 6440, "failed": 2571, "stuck": 424 },
    "2": { "total": 1, "completed": 1 }
  },
  "on_chain_audit": {
    "last_run": "2026-05-24T23:50:00Z",
    "payments_on_chain": 12228,
    "nfts_minted_on_chain": 6536,
    "deficit": 5682,
    "discrepancies": 12
  }
}
        
POST /api/v1/payments/resolve-buyers LIVE

Bulk resolve wallet addresses to Pentagon user identities. Admin keys only. Max 200 addresses per request. Used by the Payment Explorer for batch identity resolution.

// Request
POST /api/v1/payments/resolve-buyers
X-PG-App-Key: pk_live_admin_key

{
  "addresses": [
    "0xcfb0aee8f7445140d49bf589e30db43fd2ac05e9",
    "0x5133657725225202a8817b8cab14aa77fd7758ef",
    "0xea3339a9aac6e3c7efecd9e9c9993f3a0163d7be"
  ]
}

// Response
{
  "resolved": 2,
  "unregistered": 1,
  "results": {
    "0xcfb0aee8f7445140d49bf589e30db43fd2ac05e9": {
      "pg_user_id": 41892,
      "username": "cryptoking",
      "pns_name": "king",
      "email": "king@example.com",
      "social": {
        "discord": { "id": "123456789", "username": "cryptoking#1234" },
        "telegram": { "username": "cryptoking" }
      },
      "resolution": "mm_address"
    },
    "0xea3339a9aac6e3c7efecd9e9c9993f3a0163d7be": {
      "pg_user_id": 55210,
      "username": "germax",
      "pns_name": null,
      "email": "germax@proton.me",
      "social": {},
      "resolution": "user_wallet"
    },
    "0x5133657725225202a8817b8cab14aa77fd7758ef": null
  }
}
        

Addresses that don't match any PG account return null. The Explorer uses this to populate identity columns in the admin table and flag unregistered wallets for follow-up.

POST /api/v1/webhooks/subscribe

Subscribe to payment events. Receive callbacks when payments are confirmed.

{
  "url": "https://vip.pentagon.games/api/payment-callback",
  "events": ["payment.confirmed", "payment.completed"],
  "sku_ids": [9, 17]
}
        
POST /stripe/webhook LIVE

Centralized Stripe webhook endpoint. Receives checkout.session.completed events from multiple Stripe accounts.

Endpoint: https://api.account.pentagon.games/stripe/webhook

Supports multiple Stripe accounts (e.g., NPC Collections + BCSH Collections). The backend verifies the webhook signature against each configured secret until one matches. All payments write to pg_payments_db regardless of which Stripe account processed them.

Stripe Checkout Metadata (required)

When creating a Stripe Checkout Session, include these fields in metadata:

KeyTypeDescription
skuIdintSKU identifier (must match a registered SKU)
walletaddressBuyer's EVM wallet address (NFT will be minted here)

Webhook Response

// Success
{"status": true, "message": "payment_completed"}

// Missing metadata
{"status": false, "message": "missing_metadata"}  // 400

// Invalid signature (doesn't match any configured account)
{"status": false, "message": "Invalid signature"}  // 400
        

Adding a New Stripe Account

To onboard a new project's Stripe account to the centralized webhook:

  1. Get the project's Stripe webhook secret (whsec_...)
  2. Add it to the backend .env as STRIPE_WEBHOOK_SECRET_<PROJECT>
  3. Add it to the STRIPE_WEBHOOK_SECRETS list in settings.py
  4. Restart pentagon-login-backend
  5. Project updates their Stripe dashboard webhook URL to https://api.account.pentagon.games/stripe/webhook

Current Stripe Accounts

AccountSKUsStatus
NPC CollectionsSKU 17 (NPC Credits)✅ Live
BCSH CollectionsSKUs 2, 3, 4 (Tamago, Vaelion, No5)✅ Live
POST /stripe/payment_status LIVE

Check payment status for a Stripe purchase. Reads from pg_payments_db. Works for any SKU regardless of which Stripe account processed it.

ParamTypeDescription
sku_idintSKU identifier
order_idstringStripe checkout session ID (cs_live_...)
buyeraddressBuyer's wallet address

Response

// Found
{
  "success": true,
  "result": {
    "id": 12257,
    "tx_hash": "cs_live_a1ccPzX7...",
    "chain_id": 8888,
    "block_number": 0,
    "buyer": "0x1fdf1637...",
    "sku_id": 3,
    "reward_status": "completed",       // pending | processing | completed | failed
    "payment_method": "stripe",
    "purchase_status": "confirmed",
    "created_at": "2026-05-18T08:04:47.561900+00:00",
    "updated_at": "2026-05-20T03:22:11.291272+00:00",
    "reward_tx_hash": "0x99d815d2...",  // mint transaction hash (null if not yet fulfilled)
    "reward_chain_id": 3344             // chain where NFT was minted
  }
}

// Not found
{"status": false, "message": "Data Not found"}
        

Reward Status Values

StatusMeaning
pendingPayment confirmed, awaiting fulfillment (within 5 minutes)
processingFulfillment in progress (minting transaction sent)
completedNFT minted and delivered. reward_tx_hash contains the mint transaction.
failedFulfillment failed (contract error, gas issue, etc.)

7. Payment Explorer

A web-based payment lookup tool for buyers and admins. Gated by Pentagon login (same auth as nftmining.com). No public access, no crawling.

URL

https://payments.pentagon.games LIVE

Access Tiers

RoleAccessAuth
User (payer) View own payments and delivery status only. Filtered by connected wallet. Pentagon login (wallet connect or social login)
Admin View all payments, all buyers, all SKUs. Export, retry failed, bulk actions. Pentagon login + admin whitelist (initially: nftprof)

User View

After logging in, the user sees all payments from wallets linked to their Pentagon account:

My Payments

Payment IDDateSKUAmountDeliveryTX
#8412 Mar 30, 2026 EtherFantasy Character 0.25 PC ✅ Delivered View
#8413 Mar 30, 2026 EtherFantasy Character 0.25 PC ❌ Failed View

Clicking a payment opens the detail view showing:

Admin View

Admins see everything the user sees, plus full buyer identity resolution and contact info:

Admin Payment Explorer

IDDateBuyerPG UserContactSKUDeliveryOn-Chain
#8412 Mar 30 0xcfb0...05e9 cryptoking (ID: 41892) 📧 💬 🐦 SKU 9
#9001 Mar 30 0x5133...8ef Unregistered SKU 9 ❌ Failed

Admin Capabilities

Buyer Identity Triangulation

For every payment, the Explorer resolves the buyer wallet to a Pentagon user account by querying the Identity API. This gives admins a complete picture of who paid and how to reach them:

On-chain buyer wallet: 0xcfb0aee8f7445140d49bf589e30db43fd2ac05e9 │ ├─ Check user.mm_address (primary EVM wallet) ├─ Check user_wallet table (secondary bound wallets) └─ Check user_external_wallets (MultiversX, Tron, TON, Algo) │ ▼ PG User ID: 41892 Username: cryptoking PNS Name: king Email: king@example.com Discord: cryptoking#1234 (ID: 123456789) Twitter: @cryptoking Telegram: @cryptoking (ID: 987654321) VIP Tier: VIP2 Registered: 2025-06-12 Last Login: 2026-05-20

If the wallet is not linked to any Pentagon account, the identity block shows "Unregistered". The admin can still see the wallet address and on-chain payment proof, but has no contact info. For bulk outreach, export the unregistered wallet list and consider on-chain messaging or explorer-based contact.

On-Chain Verification

The explorer cross-references DB records against on-chain truth, similar to the NFT Data API's four-layer sync engine:

Layer 1: Payment Event Verification └─ Confirm PaymentReceived event exists on-chain for each DB record └─ Flag: DB record with no matching on-chain event = suspicious Layer 2: Delivery Verification └─ For "completed" records: confirm mint TX exists on-chain └─ For "failed"/"stuck": confirm no mint was actually delivered └─ Flag: DB says completed but no on-chain mint = discrepancy Layer 3: Deficit Detection └─ Cross-reference: total on-chain payments vs total on-chain mints per buyer └─ Report: X buyers paid but never received, Y total NFTs owed

Anti-Crawl Protection

Tech Stack

ComponentChoiceReason
FrontendNext.js (React)Same stack as pentagon.games, nftmining.com
AuthPentagon Identity APISame SSO as nftmining.com, wallet-linked sessions
DataPayment API (/api/v1/payments/*)All data served through the authenticated API, not direct DB
Hostingpg-fe (18.139.44.212) or dedicatedSame infra pattern as other Pentagon frontends

8. Integration Guide

For New Products (Centralized Fulfillment)

  1. Request a SKU ID from admin
  2. Deploy your NFT contract with a mintTo(address) or mintNextToken(address) function
  3. Grant MINTER_ROLE to moderator address: 0x9A7c1e986e49C2Ace0e85Ccc6eeeb262D46b7521
  4. Admin sets price on each chain's contract via setPrice()
  5. Admin configures fulfillment via setSkuConfig()
  6. Add SKU config to cross_chain_payment_config table (contract address, chain, mint ABI)
  7. Frontend calls purchaseWithNative() or purchaseWithToken()
  8. Fulfillment service automatically mints NFT to buyer within 5 minutes

For New Products (Project-Managed Fulfillment)

  1. Request a SKU ID from admin
  2. Admin sets price on each chain's contract via setPrice()
  3. Admin configures as BACKEND mode via setSkuConfig()
  4. Do NOT add to cross_chain_payment_config (prevents centralized fulfillment)
  5. Run your own scanner to watch for PaymentReceived events with your SKU ID
  6. Handle delivery in your own backend
  7. Payment data still tracked in pg_payments_db for revenue and referral calculations

For Stripe Integration

  1. Create a Stripe Checkout Session with metadata: { skuId, wallet }
  2. Configure your Stripe webhook URL to: https://api.account.pentagon.games/stripe/webhook
  3. Provide your webhook secret to the platform team for multi-account configuration
  4. Use POST /stripe/payment_status to poll fulfillment status from your frontend

Frontend Example (viem/wagmi)

// Purchase with native PC on Pentagon Chain
const { writeContract } = useWriteContract();

await writeContract({
  address: '0x3930B34a524170Cc8966859Da167DB7B5413A0ba',
  abi: paymentProcessorAbi,
  functionName: 'purchaseWithNative',
  args: [9n], // SKU ID
  value: parseEther('0.25'), // Must match contract price exactly
  chain: pentagonChain,
});

// Purchase with USDC on Ethereum
// Step 1: Approve USDC spend
await writeContract({
  address: USDC_ADDRESS,
  abi: erc20Abi,
  functionName: 'approve',
  args: [PAYMENT_PROCESSOR_ETH, 8000000n], // $8 USDC
});

// Step 2: Purchase
await writeContract({
  address: PAYMENT_PROCESSOR_ETH,
  abi: paymentProcessorAbi,
  functionName: 'purchaseWithToken',
  args: [9n, USDC_ADDRESS],
});
    

9. Referral & Revenue

VIP Referral Integration

When a payment is confirmed, the payment processor notifies the VIP referral system. If the buyer was referred by a VIP member, a commission is calculated based on the referrer's tier.

VIP TierCommission Rate
VIP10% (view only)
VIP25%
VIP315%
Partner20% (custom)

Commissionable Events

EventCommissionable
ETH chain NFT purchases✅ Yes
Stripe/PayPal payments✅ Yes
ETH → PC Bridge deposits✅ Yes
Cross-chain purchases (USDC)✅ Yes
PC-on-PC purchases❌ No (internal)

Revenue Reporting

The /api/v1/payments/summary endpoint provides monthly revenue breakdowns. Funds can be withdrawn by the contract owner via withdrawNative() and withdrawToken().

Current Balances (Withdrawable)

Pentagon Chain Contract614.925 PC
ETH Contract (USDC)$183.17
ETH Contract (ETH)0
ETH Contract (PC ERC-20)0

Last updated: 2026-05-02

10. Campaign Rewards & Offers

The Campaign Rewards system incentivizes user activity by compensating purchases with on-chain token rewards. Think of it as an on-ramp: users buy NPC credits (fiat via Stripe), and the system rewards them with real tokens on Pentagon Chain and Ethereum mainnet. Campaigns are time-limited, one-per-user, and publicly visible on the Offers tab at payments.pentagon.games.

Key Concept: NPC = PC

NPC PG Points are PC tokens held in the user's AA (Account Abstraction) internal wallet. 1000 NPC = 1 PC. When a user purchases NPC credits via Stripe on pentagon.games/account, they're buying PC into their platform wallet.

How Campaigns Work

User buys 1000 NPC credits on pentagon.games/account (Stripe, $35) │ ▼ pentagton-login-backend detects threshold met │ ▼ POST /api/campaigns/qualify → cross-chain-payment service │ ├─ Check: has this user already claimed this campaign? (DB unique constraint) ├─ If yes → 409 Conflict, skip ├─ If no → create campaign_claim (status: qualified) │ ▼ campaign_reward_processor picks up (every 5 min) │ ├─ Reward 1: Send 1 PC on Pentagon Chain (3344) via send_gas() ├─ Reward 2: Call CampaignReward.distribute(recipient) on ETH mainnet │ (contract enforces 1-per-address on-chain) │ ▼ All rewards delivered → claim.status = completed │ ▼ User sees TX hashes + explorer links on payments.pentagon.games/offers

Campaign Reward Contract (ETH Mainnet)

Each campaign that distributes tokens on ETH mainnet gets a dedicated smart contract. The contract enforces the 1-per-address rule on-chain, preventing double claims even if the backend has a bug. After the campaign ends, remaining funds can only be withdrawn by the designated recovery address.

CampaignReward.sol

Owner0xC486efE8F196Ed9956b54965988D3307e08cBde0 (Agent deployer)
Recovery Address0xB2e3e82a95f5c4c47E30A5b420Ac4f99d32EF61f (Idon/dev)
ChainEthereum Mainnet (1)
1-per-addressmapping(address => bool) hasClaimed

Contract Interface

// Owner-only: send reward to qualified user (1 per address enforced)
function distribute(address recipient) external onlyOwner;

// Recovery: withdraw remaining funds after campaign ends
// Only callable by recoveryAddress after campaignEnd timestamp
function recoverFunds() external;

// Pause/unpause distributions
function setPaused(bool _paused) external onlyOwner;

// View functions
function hasClaimed(address user) external view returns (bool);
function remainingFunds() external view returns (uint256);
function totalClaimed() external view returns (uint256);
function campaignEnd() external view returns (uint256);
function rewardAmount() external view returns (uint256);

// Events
event Claimed(address indexed recipient, uint256 amount, uint256 timestamp);
event FundsRecovered(address indexed to, uint256 amount);
event CampaignPaused(bool paused);
    

Database Schema (Campaign Tables)

Four new tables in pg_payments_db alongside the existing payment tables:

TablePurposeKey Fields
campaign Campaign definitions (admin/agent-created) slug, title, trigger_type, trigger_threshold, starts_at, ends_at, max_claims (per user), is_active
campaign_reward What each campaign pays out (supports multiple rewards per campaign) campaign_id, reward_type (native_token/erc20/nft), chain_id, amount, token_symbol, is_bonus, contract_address
campaign_claim Who claimed what (1-per-user enforced via unique constraint) campaign_id, user_id, wallet_address, status, qualification_proof (JSONB), UNIQUE(campaign_id, user_id)
campaign_claim_reward Individual TX tracking per reward delivery claim_id, campaign_reward_id, chain_id, amount, to_address, tx_hash, status

API Endpoints (Campaign)

GET /api/campaigns PUBLIC

List active campaigns. No auth required. Powers the Offers tab on payments.pentagon.games.

[
  {
    "slug": "npc-1000-pc-bonus-june2026",
    "title": "Earn 1000 NPC Points → Get PC on Ethereum",
    "description": "Purchase 1000 NPC PG Points and receive...",
    "image_url": "https://pentagon.games/assets/campaigns/npc-pc-bonus.png",
    "starts_at": "2026-06-01T00:00:00Z",
    "ends_at": "2026-06-30T23:59:59Z",
    "is_featured": true,
    "rewards": [
      { "label": "1 PC on Pentagon Chain", "chain_id": 3344, "amount": 1.0, "is_bonus": false },
      { "label": "1 PC on Ethereum", "chain_id": 1, "amount": 1.0, "is_bonus": true }
    ],
    "trigger": { "type": "npc_credit", "threshold": 1000 },
    "total_claims": 42
  }
]
        
GET /api/campaigns/{slug}/status?user_id={user_id} AUTH

Check a user's eligibility and reward status for a specific campaign.

{
  "eligible": true,
  "claimed": true,
  "claim": {
    "status": "completed",
    "qualified_at": "2026-06-05T14:30:00Z",
    "rewards": [
      {
        "label": "1 PC on Pentagon Chain",
        "chain_id": 3344,
        "amount": 1.0,
        "status": "success",
        "tx_hash": "0xabc...",
        "explorer_url": "https://explorer.pentagon.games/tx/0xabc..."
      },
      {
        "label": "1 PC on Ethereum",
        "chain_id": 1,
        "amount": 1.0,
        "status": "success",
        "tx_hash": "0xdef...",
        "explorer_url": "https://etherscan.io/tx/0xdef..."
      }
    ]
  }
}
        
POST /api/campaigns/qualify INTERNAL

Called when a user meets a campaign threshold. Creates a claim entry. Returns 409 if already claimed.

// Request
{
  "campaign_slug": "npc-1000-pc-bonus-june2026",
  "user_id": "user179298482",
  "wallet_address": "0x1234...",
  "proof": { "npc_points": 1000, "stripe_pi": "pi_xxx" }
}

// Success: 201 Created
{ "claim_id": 42, "status": "qualified" }

// Already claimed: 409 Conflict
{ "error": "already_claimed", "claimed_at": "2026-06-05T14:30:00Z" }
        

Offers Tab (payments.pentagon.games)

The Offers tab on payments.pentagon.games shows all active and past campaigns. Users can:

First Campaign: NPC 1000 → PC Bonus (June 2026)

CampaignEarn 1000 NPC Points → Get PC on Ethereum
TriggerAcquire 1000 NPC PG Points (= 1 PC in AA wallet)
Reward 11 PC on Pentagon Chain (3344)
Reward 2 (Bonus)1 PC on Ethereum mainnet (via CampaignReward contract)
Limit1 per user (enforced in DB + on-chain)
PeriodJune 1 – June 30, 2026
Post-campaignUnclaimed ETH funds recoverable only by 0xB2e3...EF61f

Creating New Campaigns

Campaigns are created by the agent directly (no admin panel). After the first campaign, an agent guide will be written for standardized campaign creation. The process:

  1. Define campaign parameters (trigger, rewards, period, limits)
  2. If ETH mainnet rewards: deploy a new CampaignReward contract with appropriate parameters
  3. Fund the contract with the reward token amount
  4. Insert campaign + campaign_reward rows into pg_payments_db
  5. Campaign appears on Offers tab automatically

11. Spend Capture for Custom Integrations

This section is written to be handed directly to another agent or developer who runs a service that takes money from users but does not go through the standard payment processor or Stripe. The goal is simple: get your spend events into pg_payments_db so the user's unified spending history (and their AI agent) can see and prove every payment they made.

Why your payment data needs to be provided. Pentagon is building a single per-user spending ledger — effectively a block explorer of a user's spending across the whole ecosystem. If your service collects fees and we don't capture them, the user sees a gap: they paid, but their history doesn't show it, and their agent can't explain it. Every spend that isn't recognized is treated as an unconnected service. Connecting yours closes the gap.

Case Study: NFT Mining (custom contract, no payment processor, no Stripe)

The NFT mining product is the reference case. It does most things itself: a custom contract takes a fee from the user to initiate mining and distributes rewards (mega pool, omega pool, treasury split). It is fully self-contained — it does not use the payment processor contract and it does not use Stripe. That's fine. We don't need to take over its fee logic. We only need to capture the spend events so the user has one place to track and prove their mining payments alongside everything else.

What You Need to Provide (the design-pattern checklist)

When asked to integrate, describe your payment design pattern using the items below. This is the exact information the tracker needs to ingest and correctly label your spend events:

#What to provideWhy the tracker needs it
1 Contract address(es) + chain ID that receive user funds So the scanner knows what to watch. One row per (address, chain).
2 The fee/payment event signature emitted on a successful spend (name + indexed args) The scanner subscribes to this event. Ideally it carries the payer address and the amount kept. e.g. FeePaid(address indexed payer, uint256 amount, uint8 tier)
3 Which field is the payer/user address This is the key the ledger groups by, and what resolves to a Pentagon identity (see buyer resolution in Section 6).
4 Amount semantics — token, decimals, and whether the event amount is the amount kept vs amount sent (refunds?) So the recorded spend equals what the user actually paid, not an over- or under-count. (Mining had exactly this kept-vs-sent nuance.)
5 What the spend is for — a human label + a stable type/category (e.g. mining_fee, boost_7day) So the explorer can show "Mining boost (7-day)" instead of an unknown transfer, and so unrecognized spends can be flagged.
6 Any sub-type / tier dimension (e.g. 24h vs 7-day, mega vs omega) Lets the ledger and revenue reporting attribute by tier. Emitting a distinct event or a tier arg makes this clean.
7 Outcome/fulfillment reference, if any (reward tx, token id, raffle entry) So "what happened after I paid" can be answered end-to-end, not just "you paid X".

Design-pattern guidance: emit a clean, attributable event

The cheapest way to be trackable is to emit one explicit event per paid action, carrying the payer, the amount actually kept, and a tier/type. Computing on the amount kept (after any refund of excess) keeps the ledger consistent with on-chain reality.

// Recommended: distinct, attributable spend event
event FeePaid(
    address indexed payer,   // (3) user address -> resolves to PG identity
    uint256 amount,          // (4) amount KEPT, in token's smallest unit
    uint8   tier             // (6) e.g. 0 = 24h, 1 = 7-day
);

// Emit AFTER refunding excess, so amount == what the user actually paid:
// uint256 kept = boostFee;
// if (msg.value > kept) payable(msg.sender).transfer(msg.value - kept);
// emit FeePaid(msg.sender, kept, tier);
        

If retrofitting an existing contract isn't possible, you can instead provide a backend feed (a webhook or a list of tx hashes + payer + amount + type) and we'll ingest that. On-chain events are preferred because they're self-proving.

How Capture Works (cron / scanner pattern)

Once you provide the checklist above, the spend tracker watches your contract the same way the payment processor's own scanners (ef-scanner-pc / ef-scanner-eth) watch the processor contract:

Your contract emits FeePaid(payer, amount, tier) │ ▼ Spend Scanner (cron, polls every ~15s per watched contract) │ ├─ Matches event signature you registered ├─ Reads payer, amount(kept), tier, tx_hash, block, chain_id ├─ Labels it via the type/category you provided ├─ Resolves payer -> Pentagon identity (Section 6 buyer resolution) └─ Writes a row to pg_payments_db (unified user spend ledger) │ ▼ Visible in: payments.pentagon.games (user portal) and via /api/v1/payments?buyer=... (user/admin keys) and to the user's AI agent via /mydata (Section 12)

Unrecognized-spend detection: the tracker can also watch a user's general contract activity on Pentagon Chain. Any spend that doesn't match a registered service surfaces as "unknown." That's the trigger to find the owning team and walk them through this checklist.

12. User AI Agent Access (mydata)

This section explains how a user's own AI agent reads payment information to help that user understand and debug their spending. When a user runs a Pentagon AI agent, the agent is granted access to the user's mydata endpoint, which exposes everything a logged-in user could themselves access across the Pentagon ecosystem.

Endpoint: https://vip.pentagon.games/mydata
Scope: one authenticated user's own data only — profile, NFTs, internal (AA) wallet, and payment/spend history. The agent never sees other users' data through this path.

What mydata Exposes

DomainContentsBacked By
Profile / identityusername, PNS name, email, linked socials, VIP tier, walletsPentagon Identity API
NFT dataowned NFTs, metadata, imagesNFT Data API
Internal walletAA wallet address, balances, NPC creditsIdentity / wallet service
Payment / spend historyevery captured spend: processor payments, Stripe, mining fees, campaign claims, custom-integration spendsThis payment stack (pg_payments_db)

How the Agent Reads Payment Information

The payment portion of mydata is the user-scoped view of the same data described in Section 6, bound to the authenticated user's wallet(s). Conceptually it returns (or links through to) the payments:read view: only payments matching that user's bound wallet(s) or session.

// The user's agent calls mydata on the user's behalf (session-authenticated)
GET https://vip.pentagon.games/mydata

// Payment section (user-scoped; equivalent to /api/v1/payments/by-buyer/{own wallet})
{
  "payments": {
    "total": 12,
    "delivered": 11,
    "pending": 0,
    "failed": 1,
    "items": [
      {
        "id": 8412,
        "type": "mining_fee",            // label from the capturing integration
        "label": "Mining boost (7-day)",
        "chain_id": 3344,
        "amount": "100000000000000000",   // amount actually kept
        "amount_usd": 0.25,
        "tx_hash": "0x7fa2cf58...",
        "explorer_url": "https://explorer.pentagon.games/tx/0x7fa2cf58...",
        "status": "completed",
        "outcome": { "reward_tx_hash": "0x99d8...", "tier": "7day" },
        "created_at": "2026-06-02T03:34:50Z"
      }
    ]
  }
}
    

Answering "I made this spend, what happened?"

A typical debugging flow the agent runs for its user:

  1. User asks: "I paid for a mining boost earlier, did it go through?"
  2. Agent calls GET /mydata and reads the payments section (user-scoped, no admin key).
  3. Agent finds the matching spend by recency / type / amount, reads its status and outcome.
  4. Agent answers with the on-chain proof: the tx hash + explorer link, the recognized label, and whether the reward/fulfillment completed.
  5. If the spend shows as unknown (captured on-chain but unlabeled), that's the signal a service isn't wired into the stack yet — the agent can flag it, and a builder should follow Section 11 to register it.

Security boundary: mydata is always scoped to the authenticated user. The agent inherits the user's own visibility — it can see that user's contact fields and full spend history, but never another buyer's. Admin-wide queries (payments:read:all) are a separate, non-user path covered in Section 5.