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.
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
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
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 intoresolvePriceyet — 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.
toBaseUnits/fromBaseUnits.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.
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 var | Default | Meaning |
|---|---|---|
| NAQD_MIN_COLLATERAL_RATIO | 110 | Below this, reserves are considered unhealthy |
| NAQD_REBALANCE_THRESHOLD | 115 | Below 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.
| Operation | Fee | Chain call | Ledger event |
|---|---|---|---|
| Mint (fiat → NAQD) | none | ChainClient.Mint — increases supply | naqd.minted |
| Burn (NAQD → fiat) | NAQD_BURN_FEE_BPS, default 50bps (0.5%) | ChainClient.Burn — decreases supply | naqd.burned |
| Withdraw on-chain (self-custody) | none | ChainClient.Transfer — custody → user address, supply unchanged | naqd.burned (withdraw is folded into the same event type by design) |
| Env var | Default |
|---|---|
| NAQD_MINT_MIN_USD / NAQD_MINT_DAILY_LIMIT_USD | 10 / 10,000 |
| NAQD_BURN_MIN / NAQD_BURN_DAILY_LIMIT | 10 / 1,000 NAQD |
| NAQD_APPROVAL_THRESHOLD_USD | 5,000 (maker-checker gate) |
| NAQD_WORKER_POLL_INTERVAL / NAQD_WORKER_STUCK_AFTER | 2s / 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 asrequires_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.
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.