MyNEX Docs / Tokens, Keys & Chain Choice
chosen once, kept forever
Before anything is irreversible

Tokens, keys, and the chain — the decisions you don't get a second attempt at

NAQD and NAQD Minting & Reserves both document a running system that assumes a mint already exists. This page is about what happens before that: who is allowed to create NAQD supply, which Solana token program the mint is built on, and why Solana was picked as the primary chain at all. All three are one-way doors — get any of them wrong and the fix is a new mint, not a patch. docs/TOKEN_REGISTRY.md is the operational record for the first two; this page explains the reasoning behind all three and verifies every claim in it against the actual code that will run.

The token registry

docs/TOKEN_REGISTRY.md is not design documentation — it is the platform's living operational record of every NAQD deployment: the mint addresses that exist, the account that holds each authority, and where the keys controlling them physically live. It contains no secrets; it records where things live and who holds them, never key material itself. It is meant to be edited the same day something changes, not "eventually" — a registry that lags reality is worse than no registry, because it actively misleads whoever reads it next during an incident.

The rule that makes the registry trustworthy

An on-chain mint that exists but is absent from this table is not an undocumented deployment — it is an incident. Nothing should ever be able to mint NAQD supply that isn't also traceable in this file. Update it immediately when: a mint is created, an authority is transferred, a key is rotated or regenerated, or a compromise is even suspected — before remediation is finished, not after.

Current state — reproduced from docs/TOKEN_REGISTRY.md §1

NetworkMint addressDecimalsToken programMint authorityFreeze authorityCreatedStatus
solana-devnetnot yet created6SPL TokenBfEh8Cy…6kRzBfEh8Cy…6kRztreasury generated, awaiting faucet funding
solana-mainnetnot yet createddecide first (§token-program)blocked on §token-program decision + reserve
polygonnot yet createdERC-20secondary chain, not started

As of this page's generation date, zero mints exist anywhere. The devnet treasury below is a signer, not a mint — it's the keypair that will become the devnet mint/freeze authority the moment naqd-token create runs.

Devnet treasury (test only — holds no value)
Public key
BfEh8CyUomktmZE9nivH9YStPuxyfqryjrDSMNUM6kRz
Keypair file
~/.mynex/keys/naqd-devnet-treasury.json (mode 0600, outside the repo)
Purpose
proving the mint/issue path end-to-end
Reuse on mainnet?
Never. A key that has ever signed on devnet — where anyone with the file can request faucet SOL and experiment — should never also be the key that controls real silver-backed supply.

What gets written to §1, and when, follows directly from the two meanings of "minting": the registry tracks token creation events (one row per chain, ever), not the recurring issuance operations that stablecoin-service posts to the ledger millions of times over. Concretely:

  1. The moment naqd-token create succeeds. Record the mint address, decimals, token program, and both authorities in §1 — the command prints all of them on success.
  2. The moment an authority moves. spl-token authorize (see Operating & rotating) changes who can sign for mint or freeze — the new authority and who approved the change belong in the same row.
  3. The moment a key is rotated or a keypair file is regenerated, even if the on-chain authority hasn't moved yet — §2's custody table should never describe a file that no longer exists.
  4. The moment a compromise is even suspected. Don't wait for confirmation — see the incident ordering in Operating & rotating.

Key custody model

Three keys show up across this platform's NAQD path, and they have genuinely different blast radii — worth being exact about which is which, since the most common mistake is treating them as interchangeable "the crypto key."

Devnet treasury keypair generated via naqd-token keygen ~/.mynex/keys/ — dev machine outside the repo, mode 0600 Blast radius: low test-only; devnet SOL and devnet NAQD hold no value; may be held by any developer Mainnet mint authority not yet created — see §registry Multisig (recommended) e.g. Squads — ≥2 signers required Blast radius: total controls issuance — i.e. creating claims on real silver the reserve must actually hold ENCRYPTION_KEY-wrapped SOLANA_/POLYGON_WALLET_ PRIVATE_KEY (both chains) Service environment secret manager / env vars, never in git Blast radius: total, at runtime decrypted only by the running service; same ENCRYPTION_KEY that also guards card PANs CHAIN_ENV only picks sandboxChain vs. solanaChain/polygonChain — it never touches key decryption. ENCRYPTION_KEY does that, identically for both chains.
Three keys, three custody locations, three very different consequences if leaked. Verified against internal/blockchain-service/keys.go and registry.go.
Devnet — low stakes, disposable
Mainnet mint authority — total, permanent
Live chain keys — total, but runtime-scoped

How the service actually loads and decrypts keys

Treasury private keys for both live chains are AES-256-GCM encrypted at rest. The easy mistake is assuming CHAIN_ENV is somehow part of key derivation — it isn't. It only selects which ChainClient implementation gets constructed:

internal/blockchain-service/registry.go — NewRegistryFromConfig
func NewRegistryFromConfig(cfg *config.Config) (*Registry, error) {
    mode := strings.ToLower(strings.TrimSpace(os.Getenv("CHAIN_ENV")))
    if mode == "" { mode = ChainEnvSandbox }

    switch mode {
    case ChainEnvLive:
        solanaC, err := newSolanaChain(cfg)   // hard-fails if keys/addresses unset
        polygonC, err := newPolygonChain(cfg) // same — never silently falls back
        return NewRegistry([]ChainClient{solanaC, polygonC}, "solana")
    case ChainEnvSandbox:
        return NewRegistry([]ChainClient{NewSandboxChain("solana"), NewSandboxChain("polygon")}, "solana")
    }
}

CHAIN_ENV=live hard-fails construction at startup if either chain's keys or addresses are missing — it deliberately never degrades to sandbox quietly, because a live deployment must not end up minting into an in-memory map without anyone noticing.

internal/blockchain-service/keys.go
func deriveAESKey(passphrase string) []byte {
    sum := sha256.Sum256([]byte(passphrase))
    return sum[:] // ENCRYPTION_KEY, whatever length an operator sets, becomes exactly 32 bytes
}

func decryptTreasuryKey(passphrase, ciphertextB64 string) (string, error) {
    key := deriveAESKey(passphrase)
    raw, _ := base64.StdEncoding.DecodeString(ciphertextB64)
    block, _ := aes.NewCipher(key)
    gcm, _ := cipher.NewGCM(block)
    nonce, ciphertext := raw[:gcm.NonceSize()], raw[gcm.NonceSize():]
    plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    return string(plaintext), err
}

Called as decryptTreasuryKey(cfg.Security.EncryptionKey, encrypted) — identically for SOLANA_WALLET_PRIVATE_KEY and POLYGON_WALLET_PRIVATE_KEY inside newSolanaChain/newPolygonChain — the same ENCRYPTION_KEY that also wraps card PANs (Security → Key management). In CHAIN_ENV=sandbox none of this runs at all; no keys are needed. Custody address = treasury address in both live implementations — there is no separate "mint authority" identity distinct from the treasury signer anywhere in this codebase today; that separation only exists once a multisig becomes the mint authority (§chain-choice does not change this — it's orthogonal to which chain is primary).

The .gitignore protections, and what a leak actually means

.gitignore
# --- Chain keypairs: NEVER commit these (they are mint authorities) ---
*treasury*.json
*keypair*.json
!package*.json

The negation on the last line matters: without it, the two globs above would also swallow every package.json/package-lock.json in the admin dashboard and mobile tooling, which is not the intent.

A committed keypair file is the mint authority

Not "a credential that grants access to" — is. Solana/Ethereum treasury keys are bearer secrets: whoever holds the file can sign as that address, full stop, with no additional password or MFA step. naqd-token keygen prints this outright the moment it generates one: "This file IS the mint authority. Anyone holding it can issue NAQD." If one is ever committed — even to a private repo, even briefly, even deleted in a later commit — treat it as fully compromised: create a new mint and move the reserve narrative forward from there. Do not merely rotate the authority on the same mint; git history doesn't forget.

SPL Token vs. Token-2022

The single irreversible decision that has to happen before any mainnet mint exists. A Solana mint is created under exactly one token program and cannot be migrated to the other afterwards; Token-2022's extensions can only be enabled at mint creation, never bolted on later. Getting this wrong doesn't mean a config change — it means creating a second mint and migrating every holder's balance to it.

CapabilitySPL Token (original)Token-2022Why it matters for NAQD
Freeze / thaw accountsPut a compliance hold on one holder's account without touching anyone else's
Permanent delegate (clawback / court-ordered seizure)✅ — set at creation, permanentMoves or burns any holder's tokens without their signature — see the callout below
Confidential transfersBalance/amount privacy — not an AML requirement here, and it adds real engineering surface
Transfer hooks (e.g. KYC gating on every transfer)A program runs on every transfer and can block non-KYC'd counterparties
On-chain metadata pointervia Metaplex (external program)✅ nativeAffects how wallets resolve NAQD's name/icon — cosmetic, but a real dependency either way
Wallet / exchange supportuniversalbroad by 2026, still marginally behindWorth checking the specific wallets/exchanges in the platform's actual reach before committing
Migrating between programs laternot possible, either directionThe decision below is made exactly once, at mint creation, forever
Permanent delegate is a policy decision, not a default

Setting a permanent delegate grants that authority the power to move or burn any holder's tokens without their signature — the on-chain equivalent of a bank's ability to reverse a fraudulent transfer, except unilateral and irreversible once set. For an asset-backed instrument facing AML obligations, that's exactly the primitive regulators tend to ask for, and counsel may require it. It is also the extension most likely to draw scrutiny from holders who chose NAQD's on-chain withdrawal path specifically for self-custody guarantees — a permanent delegate means "self-custody" still has an asterisk. Decide this consciously, on its own merits, and record the decision itself — not just the resulting technical config — in docs/TOKEN_REGISTRY.md §4 once made.

Why cmd/tools/naqd-token deliberately doesn't implement this

The CLI documented on this platform creates mints under the original SPL Token program only — on purpose, not as an oversight:

cmd/tools/naqd-token/main.go — cmdCreate, comment in full
// This tool creates a mint under the ORIGINAL SPL Token program. That is a
// permanent choice: a mint cannot be migrated to Token-2022 afterwards, and
// Token-2022 extensions (permanent delegate / clawback, confidential
// transfers, transfer hooks for KYC gating) can only be enabled at creation.
// solana-go ships no instruction bindings for those extensions, and
// hand-rolling TLV encoding for a one-time irreversible mainnet action is a
// bad trade — use the official `spl-token` CLI if you want them.
// See docs/TOKEN_REGISTRY.md § "SPL Token vs Token-2022".

Concretely: solana-go (the Go SDK naqd-token is built on) ships the Token-2022 program ID as a constant, and full instruction-builder bindings for the original token program — which is what cmdCreate actually calls, via token.NewInitializeMint2InstructionBuilder — but no equivalent builders for Token-2022's extension instructions. Enabling permanent delegate or a transfer hook means hand-encoding TLV (type-length-value) extension data by hand against the raw program, with no library to check the encoding against. That's a reasonable thing to build and test carefully for a feature you'll exercise repeatedly; it's the wrong thing to trust for a mainnet mint creation you get to run exactly once. The official Rust-based spl-token CLI has first-class, tested support for every Token-2022 extension — use it instead.

Official spl-token CLI — creating a Token-2022 mint
# freeze authority only (the conservative, recommended default)
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  create-token --decimals 6 --enable-freeze

# add clawback as well — only once the policy question above is actually decided
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  create-token --decimals 6 --enable-freeze --enable-permanent-delegate

Decimals must stay ≥ 4 — the platform's NAQD settlement exponent (Ledger → On rounding) — or ledger amounts become unrepresentable on chain. 6 is the recommended value and what naqd-token's defaultDecimals constant and every worked example on this site assume, matching USDC's convention.

Why Solana, and when you'd choose otherwise

MyNEX is custodial and ledger-first: a user's NAQD balance is a row in the ledger, not a wallet UTXO or account, until they explicitly withdraw on-chain (NAQD → Custodial-first design). Every mint, burn, and transfer inside the platform posts to the ledger first and calls the chain second (NAQD Minting → Mint lifecycle) — which means the chain is not this product's hot path. It's a settlement/proof/self-custody-exit rail: it exists so circulating supply is publicly checkable against the reserve, and so a user can leave custodial holding entirely by withdrawing to an address only they control. That materially lowers the stakes of which chain is primary compared to a wallet-native token where every user interaction touches the chain directly — and it's exactly why ChainClient (NAQD Minting → Custody & the ChainClient interface) is an interface with three interchangeable implementations rather than Solana-specific code sprinkled through stablecoin-service.

What this section is, honestly

Everything above this callout on this page is verified line-by-line against the code that runs. This section is different: it's a reasoned comparison of chains for a product still deciding, using published facts about each network as of 2026 plus the platform's own declared priorities. Treat it as the current best answer, not something grep can confirm the way the rest of this site is confirmed.

CriterionSolanaStellarBase & EVM L2sPolygon
Cost / finality per withdrawalSub-cent fees, sub-second finality~5s finality, very low fees, native issuance (no smart contract needed)Low fees, ~2s finality, inherits Ethereum settlement securityLow fees, ~2s finality
Wallet + exchange reach for self-custody exitBroad — largest stablecoin chain outside Ethereum; major wallets and every major exchangeStrong specifically in remittance rails: Circle USDC/EURC issuance, MoneyGram integrationStrong and growing, Coinbase-anchored reachBroad but has lost mindshare since 2024
Compliance primitivesToken-2022: native freeze, permanent delegate, transfer hooks — being adopted for institutional stablecoinsNative asset controls (authorize/clawback flags) built into the base ledger, not an extensionStandard ERC-20 + optional proxy patterns; nothing as integrated as Token-2022Same ERC-20 model as Base/EVM L2s
Malaysia-corridor relevanceSolid regional exchange/wallet liquidity across Southeast AsiaGenuinely strong — MoneyGram/remittance rails are widely used in the same migrant-worker corridors MyNEX's Touch 'n Go integration targetsWeaker regional (US/EU-centric) presence todayWas relevant, has faded
Operational maturity (2026)22+ months without a major outage; Firedancer shipped to mainnet Dec 2025, ending single-client risk, 20%+ validator adoption by Q2 2026Mature, low-drama, narrower ecosystemMaturing quickly, shorter track record than Solana or StellarMature but not where new stablecoin activity concentrates
Verdict

Solana as primary is the right call, as the product is scoped today. It's the largest stablecoin venue outside Ethereum, its cost/finality profile is a good match for a self-custody-exit rail rather than a payments hot path, Token-2022 gives the compliance primitives an asset-backed instrument plausibly needs without switching chains, and 2026's operational picture (22+ months incident-free, Firedancer ending single-client risk) closes the biggest historical objection to Solana specifically. Stellar is the alternative genuinely worth respecting here — not Base, not Polygon. Its remittance-native design and existing MoneyGram/Circle rails line up unusually well with the Touch 'n Go (Malaysia) corridor this platform has already committed to as its first partner rail (Data Flows → TnG reload).

What would change this answer: if MyNEX's center of gravity shifted from "store of value plus a self-custody exit" toward "payments/remittance rail as the primary use case" — NAQD moving through corridors like Malaysia more than it sits in a wallet as saved value — Stellar's remittance-native issuance and MoneyGram/Circle integrations would deserve a serious, product-led re-evaluation, not just a technical one. Separately, a major Solana outage or a client-diversity regression would have been a real reason to hesitate as recently as late 2025, before Firedancer shipped; that specific risk is materially smaller now. Polygon's position is worth naming plainly: it's still the platform's declared secondary chain in configuration, but polygon.Client.DeployERC20Token is a literal not-implemented stub today (NAQD Minting → The two meanings of "minting") — the gap between "declared secondary chain" and "chain with any working deployment path" is real, not a rounding error.

Operating & rotating

cmd/tools/naqd-token is the operational surface for token creation, inspection, and break-glass issuance — not the platform's normal issuance path.

CommandDoes
naqd-token keygen --keypair <path>Generates a treasury keypair file (mode 0600); refuses to overwrite an existing file
naqd-token airdrop --keypair <path> --network devnet --sol <n>Requests devnet/testnet SOL; refuses on mainnet (no faucet exists there)
naqd-token create --keypair <path> --network <net>Creates the mint under the original SPL Token program; optional --supply for an initial issuance to the treasury itself
naqd-token info --mint <addr> --network <net>Reads decimals, supply, mint/freeze authority straight from the mint account — works against any SPL mint
naqd-token issue --keypair <path> --mint <addr> --to <addr> --amount <n>Mints units directly on-chain — see the warning below before ever running this against mainnet

Every command except info refuses to touch mainnet without --yes-really-mainnet, checked in netFlags.endpoint() — a deliberate second confirmation on top of whatever shell history or muscle memory got the operator there.

Normal issuance does not go through this CLI

In ordinary operation, the platform issues NAQD through stablecoin-service — the mint worker path in full at NAQD Minting → Mint lifecycle, which posts a ledger transaction before ever calling ChainClient.Mint. naqd-token issue calls the SPL MintTo instruction directly against the chain — it never creates a NAQDOperation row and never touches the ledger. Anything minted this way is invisible to ledger.Reconcile (Reconciliation → The reconcile tool), because that check only compares each wallet's cached balance against its own ledger entries — a CLI-issued mint never touches a wallet or the ledger at all, so there's nothing there to disagree. The only way it surfaces is the manual check docs/TOKEN_REGISTRY.md §6 already prescribes: run naqd-token info for the real on-chain supply and compare it by hand against the admin console's NAQD overview (SumCompletedOperations-derived, per NAQD → Reserve accounting) — the two should match, and a gap means something minted outside the ledger's view. This CLI exists for mint creation, inspection, and break-glass situations only.

Authority rotation

ActionCommandEffect
Rotate mint authorityspl-token authorize <mint> mint <new-authority>Old key loses issuance power immediately — record the change in the registry's §1 and §2 the same day
Renounce mint authority permanentlyspl-token authorize <mint> mint --disableFixes total supply forever. Irreversible — no key can ever mint against this address again

Incident response — suspected key compromise, in order

  1. Rotate the mint authority first. spl-token authorize <mint> mint <new-authority> — minutes matter here, since a compromised authority can issue unbacked supply for as long as it stays valid. Do this before investigating, not after.
  2. Freeze suspicious accounts, if a freeze authority exists and holder accounts look affected — a mitigation, not a fix, for the underlying key exposure.
  3. Audit naqd_operations against on-chain supply. Every legitimate mint has a corresponding completed NAQDOperation row; anything minted through the compromised key directly (bypassing stablecoin-service, same mechanism as CLI issuance above) will not.
  4. Reconcile the ledger's naqd_reserve account against both figures — the full procedure is documented at Reconciliation → NAQD reserve accounting.

Which key is compromised changes what's actually at risk — cross-reference against the blast-radius column in the custody diagram above before deciding how urgently to escalate.