MyNEX Docs / NAQD
1 NAQD = 1g silver
The silver coin

NAQD — a store of value outside both MVR and USD

NAQD is a silver-backed stablecoin: 1 NAQD = 1 gram of silver, priced continuously off a live XAG/USD oracle. It exists to give Maldivians somewhere to hold value that isn't exposed to MVR peg risk and isn't gated by the USD shortage described on the Overview page.

Going deeper

This page covers the product-level story. For the custody/key-handling model, the full mint/burn/withdraw code paths with the compensating-reversal logic, the oracle's aggregation internals, and — the most commonly conflated part — the real economics of creating the NAQD token versus issuing units of it, see NAQD Minting & Reserves. For the key-custody model in full, the irreversible SPL Token vs. Token-2022 decision, and why Solana is the primary chain, see Tokens, Keys & Chain Choice.

Peg math

Gram price formula
silverGramPriceUSD = silverPriceOzUSD / 31.1034768   // grams per troy ounce

// worked example: silver at $32.00/oz
silverGramPriceUSD = 32.00 / 31.1034768 = $1.0288 / gram
mint 100 NAQD = 100 × 1.0288 = $102.88 USD

1 NAQD always equals 1 gram of silver at the platform's resolved price — never a fixed dollar amount. When silver moves, NAQD's USD value moves with it; that's the point, it tracks a real commodity rather than a currency peg the Maldives itself can't guarantee.

Oracle: sources, aggregation, staleness fallback

CoinGecko always active, weight 0.8 Metals-API active if METALS_API_KEY set, weight 1.0 Median aggregator default method — resists 1 bad source Live price ✓ used if fetch succeeds Last known-good if fetch fails, age ≤ 15m (ORACLE_STALENESS_THRESHOLD) Admin manual rate most recent OracleManualRate row Nothing available → ORACLE_STALE error surfaced to mint/burn callers Every degraded step publishes system.alert
resolvePrice() in oracle-service/service.go — live → last-known-good → manual → stale, in that exact order.
  • Kitco was deliberately removed. The pre-revamp code had a fully-simulated KitcoSource (no real public API exists) — deleted rather than kept as a third fake source alongside two genuinely real ones.
  • Median, not weighted-average, is the default. Switched during the NAQD wave specifically for resistance to a single bad/compromised source — a weighted average lets one outlier skew the result; a median with only two sources doesn't, and the aggregator's own test suite verifies a $999 outlier gets ignored.
  • Outlier detection and confidence scoring exist (DetectOutliers, IQR-based; GetPriceConfidence, coefficient-of-variation) but aren't wired into resolvePrice yet — available for a future admin dashboard signal, not load-bearing today.

Custodial-first design

A user's NAQD balance is a ledger entry, full stop, until they explicitly withdraw on-chain. The pre-revamp codebase generated and stored a private key per user per chain — that entire model was deleted. ChainClient now operates against exactly one platform-owned custody address per chain.

ChainClient Mint · Burn · Transfer BalanceOf · TxStatus CustodyAddress() sandboxChain (default) in-memory map, deterministic txRef, idempotent per (kind, reference) solanaChain (CHAIN_ENV=live) SPL Token, 6 decimals, Burn restricted to treasury address polygonChain (CHAIN_ENV=live) ERC-20, 18 decimals (different base units than Solana) No per-user chain wallets anywhere. Treasury private keys are AES-256-GCM encrypted at rest, derived from ENCRYPTION_KEY.
Solana is 6-decimal, Polygon is 18-decimal — the same nominal NAQD amount converts independently per chain via toBaseUnits/fromBaseUnits.
What was removed

Cross-chain bridging (BridgeTokens, lock-and-mint between Solana/Polygon) was deleted along with the per-user-wallet model it depended on — out of scope for the current design. A future bridging feature would be built against ChainClient fresh rather than reviving the old code.

Reserve accounting & collateral ratio

GET /naqd/reserves and the admin overview both read live ledger balances of the naqd_reserve system account — there is no separate snapshot table, no cached figure that can drift from reality.

internal/stablecoin-service/reserves.go
circulatingSupply = -1 × naqd_reserve.NAQD_balance   // reserve is credited on burn, debited on mint
collateralRatio   = totalReserveUSD / naqdValueUSD × 100

isHealthy            = collateralRatio >= MIN_COLLATERAL_RATIO      // default 110
rebalancingRequired   = naqdValueUSD > 0 && collateralRatio < REBALANCE_THRESHOLD  // default 115

// if the oracle is down: collateralRatio = 0, rebalancingRequired = true — fails safe
Env varDefaultMeaning
NAQD_MIN_COLLATERAL_RATIO110Below this, reserves are considered unhealthy
NAQD_REBALANCE_THRESHOLD115Below this, a rebalance flag is raised even if still "healthy"

This formula in the wider context of the platform's other integrity checks: Reconciliation → NAQD reserve accounting. Proof-of-reserve economics and a worked supply-vs-silver table: NAQD Minting → Economics.

Mint, burn, withdraw lifecycles

All three are async — an API call only enqueues a NAQDOperation; the worker does the real work. Full sequence diagrams: Data Flows → NAQD mint, → NAQD on-chain withdrawal. Exact ledger entries for all three: Money Flows → NAQD mint / burn / withdraw. The orchestration code around each — claiming, the compensating reversal on chain failure, custody/key handling — is on NAQD Minting & Reserves.

OperationFeeChain callLedger event
Mint (fiat → NAQD)noneChainClient.Mint — increases supplynaqd.minted
Burn (NAQD → fiat)NAQD_BURN_FEE_BPS, default 50bps (0.5%)ChainClient.Burn — decreases supplynaqd.burned
Withdraw on-chain (self-custody)noneChainClient.Transfer — custody → user address, supply unchangednaqd.burned (withdraw is folded into the same event type by design)
Env varDefault
NAQD_MINT_MIN_USD / NAQD_MINT_DAILY_LIMIT_USD10 / 10,000
NAQD_BURN_MIN / NAQD_BURN_DAILY_LIMIT10 / 1,000 NAQD
NAQD_APPROVAL_THRESHOLD_USD5,000 (maker-checker gate)
NAQD_WORKER_POLL_INTERVAL / NAQD_WORKER_STUCK_AFTER2s / 2m

Treasury admin & maker-checker

  • GET /admin/naqd/overview — supply vs. reserve, collateral ratio, oracle status, all admin-role gated.
  • POST /admin/naqd/operations/:id/approve|reject — the maker-checker decision for anything created as requires_approval. A reject costs nothing to undo since no ledger posting happened yet.
  • POST /admin/naqd/oracle/refresh — force a live price refetch (contract documents this at /admin/oracle/refresh; the actually-mounted path is /admin/naqd/oracle/refresh).
  • PUT /admin/naqd/oracle/manual-rate — set the fallback rate used when both live and last-known-good are unavailable. Not mentioned in API_CONTRACT.md at all.
Superseded model, kept for safety

models.NAQDToken (the old mint/burn/transfer history table) is no longer written to — models.NAQDOperation is the source of truth now — but it's still in AutoMigrate rather than dropped, a deliberate one-way-door avoidance until any historical rows are confirmed migrated or archived.