DEV Community

Cover image for Checking a stablecoin's reserves against Nigeria's new rules, in 30 lines of JavaScript
MenaChange
MenaChange

Posted on

Checking a stablecoin's reserves against Nigeria's new rules, in 30 lines of JavaScript

The rules in short

The Central Bank of Nigeria's regulations for non-security virtual assets took effect on 5 November 2026, according to The Block and Crypto Briefing. Among other things they:

  • split stablecoins into Class 1 (naira-referenced) and Class 2 (referenced to a foreign currency such as the US dollar),
  • set minimum capital at 10 billion naira for Class 1 issuers and 15 billion for Class 2 (Cointelegraph and Crypto Briefing),
  • require full reserve backing plus a buffer of at least 2% (Cointelegraph and Crypto Briefing),
  • require at least 40% of reserves in cash or highly liquid assets (reported by Cointelegraph),
  • ban stablecoins that hold their peg mainly through algorithms instead of eligible reserves (all three outlets),
  • give existing operators 180 days to apply for a licence (all three outlets).

The CBN's own document is a PDF on cbn.gov.ng. The press reports differ on some details, so treat the thresholds below as parameters, not as legal text.

Two checks that are easy to get wrong

The buffer check compares total reserves with circulating supply plus 2%. The liquidity check compares the liquid part with 40% of total reserves, not of supply. A token can pass the first and fail the second, which is the case worth testing.

export const RULES = { buffer: 0.02, liquidShare: 0.4 };

export function checkReserves({ supply, reserves }, rules = RULES) {
  const total = reserves.reduce((s, r) => s + r.amount, 0);
  const liquid = reserves.filter((r) => r.liquid).reduce((s, r) => s + r.amount, 0);
  const required = supply * (1 + rules.buffer);
  const findings = [];
  if (total < required) findings.push(`reserves ${fmt(total)} are below supply + buffer ${fmt(required)} (short ${fmt(required - total)})`);
  if (liquid < total * rules.liquidShare) {
    findings.push(`liquid share is ${pct(liquid / total)}, needs ${pct(rules.liquidShare)} (short ${fmt(total * rules.liquidShare - liquid)})`);
  }
  return { ok: findings.length === 0, coverage: total / supply, liquidShare: liquid / total, findings };
}

const fmt = (n) => Math.round(n).toLocaleString('en-US');
const pct = (x) => (x * 100).toFixed(1) + '%';
Enter fullscreen mode Exit fullscreen mode

Run it on two snapshots

Same supply, same total reserves (103% of supply), different mix:

const snapshots = {
  'buffer met, cash light': { supply: 50_000_000, reserves: [
    { type: 'cash at bank', amount: 12_000_000, liquid: true },
    { type: 'short-term government bills', amount: 39_500_000, liquid: false },
  ] },
  'buffer met, cash floor met': { supply: 50_000_000, reserves: [
    { type: 'cash at bank', amount: 21_000_000, liquid: true },
    { type: 'short-term government bills', amount: 30_500_000, liquid: false },
  ] },
};

for (const [name, s] of Object.entries(snapshots)) {
  const r = checkReserves(s);
  console.log(`${name}: ${r.ok ? 'meets both rules' : 'fails'} (coverage ${pct(r.coverage)}, liquid ${pct(r.liquidShare)})`);
  for (const f of r.findings) console.log('  - ' + f);
}
Enter fullscreen mode Exit fullscreen mode

Output with Node 18+:

buffer met, cash light: fails (coverage 103.0%, liquid 23.3%)
  - liquid share is 23.3%, needs 40.0% (short 8,600,000)
buffer met, cash floor met: meets both rules (coverage 103.0%, liquid 40.8%)
Enter fullscreen mode Exit fullscreen mode

Both tokens are over-collateralised. Only one could meet redemptions from cash under the reported floor.

Where this breaks in practice

Whether short-term government bills count as "highly liquid" is exactly the kind of detail the circular defines and the press reports don't, so liquid: false above is a choice you'd replace with the regulator's list. A real check would also price assets at market value on the snapshot date, read supply from the chain rather than a config, and keep the history so you can show the buffer held every day, not only on the day of the audit.

Sources

Top comments (0)