Use DDD

Use DDD_

DeFiLlama publishes stablecoin supply. FRED publishes U.S. M2. DDD normalizes both into one maintained, citeable benchmark — so you don't have to stitch raw feeds together yourself. The HTTP API and embed widget are the stable public surfaces; the Solana oracle notes are documented for teams that need experimental on-chain reads.

Program XJjnewyPHcfb2ogMN1uAZGyt25XbKN2DWnm1GfAwddd
GitHub package @snowplow1337/ddd-oracle-sdk github packages ↗
Why DDD API Embed widget Attribution AI agents SDK / Node Python Raw RPC Account layout
why ddd

A benchmark layer, not another raw feed

Raw stablecoin supply and U.S. M2 are both public, and both stay useful on their own. DDD's job is what sits above them: normalizing those public sources into one maintained number — the USD stablecoin-to-M2 benchmark for on-chain dollar penetration — with a fixed rounding/display rule, historical records, and Tape context behind it.

Stitching DeFiLlama and FRED together yourself means re-deriving the methodology and re-checking the rounding rule every time something changes upstream. DDD carries that maintenance burden so a product, market, widget, article, or dashboard can cite one number instead of re-deriving it.

Use DDD when you need a stable benchmark, not another raw data feed.
stable public endpoint

API usage

The DDD HTTP API returns the same normalized benchmark and Tape outputs used on the site, so you don't need to fetch raw stablecoin or M2 data and re-derive the ratio yourself. Use GET /api/ratio for the current benchmark, and use GET /api/tape for official Tape snapshots and movement windows.

Freshness boundary. The benchmark and Tape are citeable when they come from saved DDD records or the official API. Live provider checks are freshness signals, not a replacement for saved snapshots.
citation

Attribution guidance

When displaying DDD data, cite Digital Dollar Dominance and include the relevant timestamp when available. For benchmark methodology, link to /methodology. For Tape movement data, link to /tape and use expanded/contracted/snapshot-delta language unless event-level evidence is shown. Because the rounding rule and display format are fixed, a DDD citation stays consistent even as the raw source data underneath it updates.

ddd-oracle · live · solana mainnet
fetching summary account via JSON-RPC
Quick Start

install

Node.js / GitHub Packages

The @snowplow1337/ddd-oracle-sdk package handles account deserialization. Peer dependency on @solana/web3.js.

$ npm install @snowplow1337/ddd-oracle-sdk @solana/web3.js
javascript · node
// Read the DDD ratio from Solana mainnet
import('@snowplow1337/ddd-oracle-sdk').then(async ({ readDDD }) => {
  const { Connection } = await import('@solana/web3.js');
  const conn = new Connection('https://api.mainnet-beta.solana.com');

  const s = await readDDD(conn);

  console.log('DDD     = ', s.dddPercent.toFixed(4) + '%');
  console.log('Stables = $' + (Number(s.totalStablesUsd) / 1e9).toFixed(2) + 'B');
});
ESM only. The SDK ships as ES modules. Run with node --input-type=module or use dynamic import() as shown above.
agent http api

JSON for tools & bots

Versioned read-only endpoints backed by the same on-chain accounts as the SDK. Big integers are returned as strings. Send If-None-Match with the previous ETag to receive 304 Not Modified when the oracle sequence is unchanged.

Production: set SOLANA_RPC_URL in Netlify to your RPC provider. Optional ?debug=1 adds non-sensitive metadata.

zero dependencies

Python (stdlib only)

No packages required — uses only Python's standard library to query the Solana JSON-RPC and decode the binary account layout directly.

python 3 · stdlib only
import urllib.request, json, base64, struct
from datetime import datetime, timezone

# Base58 encoder (no deps)
ALPH = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def b58(b):
    n = int.from_bytes(b, 'big'); s = ''
    while n: n, r = divmod(n, 58); s = ALPH[r] + s
    return '1' * (len(b) - len(b.lstrip(b'\x00'))) + s

# Fetch all 4 oracle accounts in one RPC call
req = urllib.request.Request(
    'https://api.mainnet-beta.solana.com',
    data=json.dumps({
        "jsonrpc": "2.0", "id": 1, "method": "getMultipleAccounts",
        "params": [[
            "8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA",  # summary
            "97g36T1PV3anxCwxV6ue5MmF2A9HQHMsLqevckrkqjzK", # chains
            "GznG6pxbLPrqeYDd5aFXJcXK5SQ4wXifBtqjep34DNe",  # issuers
            "7FRjdiN489qMU2R8G2RumBPxRjanY9683gnXttKP4Fhv",  # tokens
        ], {"encoding": "base64"}],
    }).encode(),
    headers={'Content-Type': 'application/json'},
)
r = json.loads(urllib.request.urlopen(req).read())['result']['value']
s, c, ib, t = (base64.b64decode(a['data'][0]) for a in r)
ts_utc = lambda u: datetime.fromtimestamp(u, tz=timezone.utc).isoformat()

# Summary account layout (offset 73): ddd, stables, m2, ts, slot, seq
ddd, stb, m2, ts, slot, seq = struct.unpack_from('<QQQqQQ', s, 73)

print(f"DDD ratio : {ddd/1e4:.4f}%")
print(f"Stables   : ${stb/1e9:,.4f}B")
print(f"U.S. M2   : ${m2/1e12:,.4f}T")
print(f"Updated   : {ts_utc(ts)}  (slot {slot:,})")
print(f"Sequence  : {seq}")
print(f"Admin     : {b58(s[8:40])}")

# Book accounts: chains, issuers, tokens
def read_book(name, d):
    cnt = d[25]
    print(f"\n--- {name}  (count={cnt}) ---")
    for k in range(cnt):
        o = 26 + k*29
        nm = d[o+1:o+1+d[o]].decode('utf-8', errors='replace')
        sup, pct = struct.unpack_from('<QI', d, o+17)
        print(f"  {k+1:<3} {nm:<16} ${sup/1e9:>10,.2f}B  {pct/1e6:.4f}%")

read_book('CHAINS',  c)
read_book('ISSUERS', ib)
read_book('TOKENS',  t)
On-Chain

accounts

Program & Account Addresses

The oracle writes to four accounts. Read any of them directly via getAccountInfo or getMultipleAccounts.

Account Role Address
program Oracle program ID XJjnewyPHcfb2ogMN1uAZGyt25XbKN2DWnm1GfAwddd
summary DDD ratio, totals, metadata 8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA
chains Supply breakdown by chain 97g36T1PV3anxCwxV6ue5MmF2A9HQHMsLqevckrkqjzK
issuers Supply breakdown by issuer GznG6pxbLPrqeYDd5aFXJcXK5SQ4wXifBtqjep34DNe
tokens Supply breakdown by token 7FRjdiN489qMU2R8G2RumBPxRjanY9683gnXttKP4Fhv
Permissionless reads. These accounts are publicly readable on Solana mainnet. No wallet, no transaction, no fees — a single RPC getAccountInfo call is all you need.
raw rpc

Direct JSON-RPC Call

No SDK? Fetch the summary account directly with a standard Solana JSON-RPC call. Works with any HTTP client in any language.

bash · curl
$ curl -sS https://api.mainnet-beta.solana.com \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getAccountInfo",
    "params": [
      "8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA",
      { "encoding": "base64", "commitment": "confirmed" }
    ]
  }'

A successful response is not meant to be human-readable at a glance. The RPC returns JSON; inside it, result.value.data[0] is a base64-encoded binary account (the oracle’s on-chain layout). Solana does not turn that into “DDD = 1.39%” for you — you decode base64, then read little-endian fields starting at byte 73 (see the layout table below). The giant string that looks like gibberish is normal.

If a python3 -c "…" example printed nothing: double quotes make bash/zsh expand $ inside Python f-strings before Python runs. Use the version below with single-quoted -c '…' so stdin from curl still reaches Python (do not use curl | python3 <<'PY' — that replaces stdin and breaks the pipe).

bash · same curl, piped into python (readable numbers)
$ curl -sS 'https://api.mainnet-beta.solana.com' -X POST \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA",{"encoding":"base64","commitment":"confirmed"}]}' \
| python3 -c '
import sys, json, base64, struct
from datetime import datetime, timezone
j = json.load(sys.stdin)
b64 = j["result"]["value"]["data"][0]
raw = base64.b64decode(b64)
ddd, stb, m2, ts, slot, seq = struct.unpack_from("<QQQqQQ", raw, 73)
print("DDD %     ", round(ddd / 1e4, 4))
print("Stables   ", "${:.2f}B".format(stb / 1e9))
print("U.S. M2   ", "${:.2f}T".format(m2 / 1e12))
print("Updated   ", datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d %H:%M UTC"))
print("Slot      ", slot)
print("Sequence  ", seq)
'
Dedicated RPC (QuickNode, etc.) — Replace the URL with yours. Include "commitment": "confirmed" in the params object; many setups need it. If nothing comes back: try the URL without a trailing slash; check QuickNode for an IP allowlist (your home IP must be allowed, or the call is rejected); run curl -sS (shown above) or add -v to see HTTP errors. On Windows, run the same command in Git Bash or WSL — PowerShell often breaks multiline JSON in single quotes.
Reference

binary layout

Summary Account Layout

The summary account is a packed binary struct. All integers are little-endian. The DDD ratio and supply figures start at byte offset 73.

Field Offset Type Description
discriminator 0 [u8; 8] Anchor account discriminator
admin 8 [u8; 32] Admin pubkey (base58 encoded)
authority 40 [u8; 32] Update authority pubkey
ddd_ratio 73 u64 LE DDD ratio × 10⁶. Divide by 10,000 for percentage.
e.g. 13900 → 1.3900%
total_stables_usd 81 u64 LE Total stablecoin supply in USD (raw cents × 10⁰).
e.g. 319000000000 → $319B
m2_usd 89 u64 LE U.S. M2 broad money in USD.
e.g. 21700000000000 → $21.7T
data_timestamp 97 i64 LE Unix timestamp (UTC) of last data update
slot 105 u64 LE Solana slot of last write
sequence 113 u64 LE Monotonically increasing write counter. Use to detect stale reads.
chain_count 121 u8 Number of chain entries in the chains book
issuer_count 122 u8 Number of issuer entries in the issuers book
token_count 123 u8 Number of token entries in the tokens book
sdk return value

readDDD() Return Fields

The GitHub Packages SDK returns a parsed object. All bigint fields are raw on-chain values.

Field JS Type Description
dddPercent number DDD ratio as a percentage (e.g. 1.3900). Ready to display.
totalStablesUsd bigint Total stablecoin supply in USD (raw). Divide by 1e9 for billions.
m2Usd bigint U.S. M2 in USD (raw). Divide by 1e12 for trillions.
dataTimestamp number Unix timestamp of last oracle update (UTC seconds).
slot bigint Solana slot when the data was last written.
sequence bigint Write sequence counter. Compare across reads to detect stale data.
good to know

Usage Notes

agent workflows

Using DDD with AI Agents

Humans often choose stablecoins based on preference, habit, brand trust or chain loyalty. Agents should use live data. DDD Oracle gives agents machine readable stablecoin signals, including the USD stablecoin-to-M2 benchmark, issuer concentration and chain distribution.

Treasury agents

Monitor issuer concentration before recommending stablecoin allocation.

Risk monitoring agents

Alert when TCD, HHI or top issuer share rises too high.

Payment planning agents

Use chain distribution now, and future velocity data later, to help choose where stablecoins should move.

Research agents

Read stablecoin data directly instead of scraping dashboards.

DDD is the decision layer. Jupiter and LI.FI are execution layers. DDD helps agents decide which stablecoin and chain make sense. Execution providers handle swaps, bridges, quotes and transactions.

DDD decides. Execution providers execute.

Website embed

iframe

Embed the live stablecoin tracker

Add the live DDD Stablecoin-to-M2 Ratio to any page. Real-time data from the DDD API on each load, no API key, free to use.

Preview

Embed code

<iframe src="https://digitaldollardominance.com/widget" width="380" height="100" frameborder="0"></iframe>
Live data
Pulls from the DDD API on every load. Always current.
No auth required
No API key, no rate limits, no tracking.
Lightweight
Single iframe, no dependencies. Under 2KB.
Dark & light themes
Works on dark dashboards and light sites. Add ?theme=light to the widget URL for a light background.

Questions? @dddtracker

Daily Tape

Get Tape updates.

The latest official Daily Tape window, daily card, and citeable stablecoin supply movement updates.