Stable Tape · developer reference

The interfaces Stable Tape publishes

Stable Tape maintains one number — USD-pegged stablecoin supply as a share of U.S. M2 — and the dated records behind it. This page documents every interface that number is published through, what each one returns, and which of them are supported public contracts rather than research or first-party surfaces.

No key, no registration and no account is required for any public endpoint. Every endpoint on this page is read-only: none of them accepts a write, and none of them takes a body. Documented behaviour here is verified against the implementation by npm run test:developer-api-docs.

Oracle program XJjnewyPHcfb2ogMN1uAZGyt25XbKN2DWnm1GfAwddd
SDK package @snowplow1337/ddd-oracle-sdk GitHub Packages

register

What is published, and on what terms

Not every interface below carries the same commitment. Three registers are used on this page, and each one is stated in words as well as by the rule beside the row, so the distinction survives print, greyscale and a cropped screenshot.

  • Public. A supported read-only contract. CORS-open to any origin, documented here, and covered by the documentation test. Field names and units are not changed without changing this page.
  • Research. Real and readable, but published as a research surface. The on-chain oracle is a second, independent publication of the same reading; it is not the citation surface and it carries no availability commitment.
  • Internal. Reachable, but not offered as a public contract. Restricted to first-party origins, and listed here only so that an interface visible in a network log is explained rather than mistaken for something to build on.
Every HTTP interface this page documents
Route Method Returns Freshness Register
/api/ratio GET Current DDD reading: unrounded and display share, 1-in-N, supply, U.S. M2, M2 observation date, sources Recomputed per request, behind a 5-minute shared cache Public
/api/tape?window=daily GET One frozen canonical Daily Tape record, in full Daily, cut at 12:00 UTC Public
/api/tape?window=daily-history GET Newest-first page of reduced Daily Tape records, plus declared gaps Daily, appends Public
/api/issuers GET Issuer supply and share snapshot behind /issuers Recomputed per request, behind a 5-minute shared cache Public
/api/chains GET Per-chain supply and 24h change behind /chains Recomputed per request, behind a 5-minute shared cache Public
/api/currencies-supply GET Supply by peg currency behind /currencies Recomputed per request, behind a 5-minute shared cache Public
/widget GET Embeddable HTML document that reads /api/ratio Reads on every load Public
/api/v1/oracle/summary GET Decoded on-chain summary account, big integers as strings Follows the on-chain write cycle Research
/api/v1/oracle/snapshot GET Summary plus the chain, issuer and token books, with a consistent flag Follows the on-chain write cycle Research
/api/jupiter-solana-route GET Read-only Jupiter quote proxy. Not an oracle read, not versioned Per request Internal
Which surface to cite. The dated Daily Tape record is the citation surface: /api/tape?window=daily, /api/tape?window=daily-history, or the dated /tape/YYYY-MM-DD permalink. Those return frozen canonical records. /api/ratio is a current reading, not a record, and the on-chain oracle is a research publication of the same reading. Neither is the citable surface.

what the benchmark is for

A maintained benchmark, not another raw feed

Stablecoin supply and U.S. M2 are both public, and both stay useful on their own. What DDD adds is the layer above them: one maintained number, computed from a fixed source selection with a fixed rounding and display rule, frozen into dated records, and carried forward unchanged when an upstream feed changes shape.

Stitching DeFiLlama and FRED together yourself means re-deriving that methodology and re-checking the rounding rule every time something moves upstream. The point of these endpoints is that a product, article, dashboard or agent can cite one number instead of re-deriving it. The full source selection and its caveats are on Methodology.

public · benchmark and records

Benchmark and record API

Three public read-only endpoints carry the benchmark itself. All are CORS-open to any origin, take no authentication, and return application/json.

GET/api/ratio

Current DDD reading

Recomputed on each request from the same two sources the benchmark always uses, then served behind Cache-Control: public, s-maxage=300, stale-while-revalidate=600. It takes no parameters. It is a current reading, not a frozen record, so successive calls can differ.

bash · curl
$ curl -sS 'https://stabletape.com/api/ratio'
json · complete response
// Illustrative values. Every field the endpoint returns is shown.
{
  "dddPctRaw":           1.3308184918420052,   // unrounded PERCENT, not a 0-1 ratio
  "dddPctDisplay":       "1.33",                 // string, fixed 2dp
  "oneInN":              75,                     // round(100 / dddPctRaw)
  "stablecoinSupplyUsd": 308153683423,           // whole USD
  "usM2Usd":             23155200000000,         // whole USD
  "m2Date":              "2026-06-01",           // FRED M2SL observation date, always a month start
  "m2DateLabel":         "JUN 2026",
  "updatedAt":           "2026-08-06T12:04:11.318Z",
  "sources": {
    "stablecoins": "DeFiLlama",
    "m2":          "FRED M2SL"
  }
}
Degraded reads are labelled, not hidden. If the FRED fetch fails, the endpoint still answers 200 using a stored M2 snapshot. That response adds "status": "fallback-m2", sources.m2 becomes "bot/fallback.json" instead of "FRED M2SL", and updatedAt reports the stored snapshot's timestamp rather than the time of the request. Treat the presence of status as the signal that M2 is not fresh. If the stablecoin supply fetch fails instead, no reading is invented: the endpoint returns 502 with an error field.

GET/api/tape?window=daily

One frozen Daily Tape record

Returns the most recent frozen canonical Daily Tape record in full. Add &date=YYYY-MM-DD to fetch a specific date. A record carries the record date and window, supply before and after, expanded, contracted and net movement, per-row movement, the benchmark block, provenance and integrity fields, the selected headline and its evidence line under display.signal, and the record's canonical status.

bash · curl
$ curl -sS 'https://stabletape.com/api/tape?window=daily'
$ curl -sS 'https://stabletape.com/api/tape?window=daily&date=2026-06-25'
Citable and shareable. Records flagged canonical: true are the citable surface. Each dated record also has a share card at /daily-tape-card.png?date=YYYY-MM-DD, square variant at /daily-tape-card-square.png?date=YYYY-MM-DD. The canonical Daily Tape series starts 2026-06-20. Records describe snapshot-to-snapshot supply movement, not transaction-level mint or burn events.

GET/api/tape?window=daily-history

Recent frozen Daily Tape records

Returns the newest frozen Daily Tape records in newest-first order. The default response contains up to 7 records. A caller that needs a longer run of history asks for a larger bounded page with ?limit=N, up to a maximum of 90. The archive decides the rest: if fewer eligible records are stored than the number requested, fewer are returned, so limit=90 returns 90 records only once 90 records exist.

Each entry is a reduced projection of the frozen record, not the complete stored record. It carries the record date, the snapshot window, supply before and after, net, expanded and contracted movement, the largest expansion, contraction and absolute supply delta, the tracked row count, the selected headline, the benchmark block, and the record's canonical status. Full per-row movement, provenance and integrity fields stay on the dated record at /api/tape?window=daily&date=YYYY-MM-DD.

Parameters
Parameter Default Accepted Behaviour
limit 7 1–90 Maximum number of records in the response. A value above 90 is clamped to 90, so the response stays bounded however long the archive grows. Any value that is not a plain positive integer, for example an empty, negative, fractional, exponential or non-numeric value, normalises to the default of 7 and still returns HTTP 200 rather than an error. A repeated limit uses the first occurrence.

Omitting limit is the long-standing behaviour and is unchanged: an existing caller that sends no parameter keeps the same seven-record response it received before the parameter existed. Responses are CORS-open and sent with Cache-Control: public, max-age=300, stale-while-revalidate=600.

bash · curl · default, up to 7 records
$ curl -sS 'https://stabletape.com/api/tape?window=daily-history'
bash · curl · larger bounded page, up to 90 records
$ curl -sS 'https://stabletape.com/api/tape?window=daily-history&limit=90'
json · response (shape, abridged)
// illustrative values; each record is a reduced projection, newest first
{
  "status":  "ok",
  "count":   41,          // whole stored archive, not the returned page
  "records": [
    {
      "date":                 "2026-07-29",
      "from":                 "2026-07-28T12:00:00.000Z",
      "to":                   "2026-07-29T12:00:00.000Z",
      "netIssuerMovementUsd": -250060285,
      "expandedUsd":          426210093,
      "contractedUsd":        676270378,
      "benchmark": {
        "totalSupplyUsd": 308022276470,
        "m2Used":         23155200000000,
        "m2AsOf":         "2026-06-01",   // FRED M2SL reference month
        "sharePctRaw":    1.33025098,
        "sharePct":       1.33,
        "oneInN":         75
      },
      "canonical":    true,
      "recordStatus": "canonical"
    }
  ],
  "gaps": [
    { "date": "2026-07-30", "type": "gap", "reason": "noon_anchor_unavailable" }
  ]
}
Gaps are declared, never filled. A date with no frozen record is listed in gaps with a reason, and is never fabricated into a record. Never assume a missing day equals zero movement. count reports the whole stored archive, so it can be larger than the number of records returned; the returned page is always the newest slice of the same ordering, and a smaller limit gives a strict prefix of a larger one.
What a record resolves, and what it does not. The benchmark block carries the inputs to the published calculation: total stablecoin supply, the U.S. M2 figure used, the M2 reference month, the raw and display-rounded share, and the 1-in-N value. Every canonical record therefore resolves back to the division it came from. The window does not provide M2 release dates, M2 vintage history, or revision history; the M2 figure is the monthly observation held constant until the next reference month. Early records stored before the canonical series began are still returned, flagged canonical: false with a recordStatus, rather than silently dropped.

public · structure

Structure endpoints

Three further public endpoints expose the structure layer that Methodology points here for. Each one backs the corresponding Structure route, is CORS-open, and is served with Cache-Control: public, s-maxage=300, stale-while-revalidate=600. Like /api/ratio, they are current snapshots rather than frozen records, so they are not a citation surface.

Structure endpoints and their top-level response keys
Route Backs Top-level keys
GET /api/issuers /issuers The issuer snapshot object, including issuers and history. Each issuer row carries its symbol, supply, share and the static metadata Stable Tape maintains for it.
GET /api/chains /chains Per-chain totals summed across every USD-pegged asset, with the previous-day figure alongside the current one so a 24h change can be derived.
GET /api/currencies-supply /currencies currencies, totals, summary, trackingStartedAt, annualAvailable, annualNote, generatedAt. Note that annualAvailable is false: there is no 1Y currency series behind this endpoint yet, and the response says so rather than implying one.
These are derived, not canonical. Structure endpoints recompute from the upstream supply feed on each request. They carry no frozen record, no record hash and no dated permalink. When an upstream fetch fails they return 502 with an error field rather than a stale or partial figure.

citation

Attribution guidance

Please attribute Stable Tape and link to the relevant dated Daily Tape record or to the methodology page when citing the benchmark.

  • Benchmark: cite Digital Dollar Dominance (DDD) with a link to stabletape.com/methodology. DDD measures USD stablecoin supply as a share of U.S. M2. It is a supply-scale benchmark, not a complete adoption metric.
  • Daily Tape records: dated, frozen records are the citable and shareable surface, and each carries a selected headline and its own dated card. Link to stabletape.com/tape and include the record date. Current readings from /api/ratio, from the widget or from the homepage are not citable until frozen as a dated record.
  • Movement language: use expanded supply, contracted supply and snapshot-to-snapshot movement. Daily Tape is not a transaction-level mint or burn tracker unless on-chain event evidence is shown.
  • Sources and caveats: stablecoin supply from DeFiLlama; U.S. M2 from FRED M2SL. M2 observations are revised by the Federal Reserve, and DDD uses the latest available. Data can be delayed or revised upstream. No SLA or uptime guarantee is implied.

public · embed

Embed widget

The widget is a single self-contained HTML document served at /widget. Drop it into any page as an iframe. It loads no script bundle, no web font and no stylesheet from anywhere, and it makes exactly one network request of its own: a read of /api/ratio when it loads. It is free to use and needs no key.

Preview

Embed code

<iframe src="https://stabletape.com/widget" width="380" height="100" frameborder="0"></iframe>
  • DataReads GET /api/ratio once per load and renders 1-in-N, stablecoin supply, U.S. M2 and the display share. It does not poll, so an embed is as current as the page around it.
  • StandaloneOne HTML document with its CSS and its single fetch inline. No script bundle, no web font, no external stylesheet, no build step.
  • No key, no trackingNo API key, no registration, and no analytics or tracking script inside the widget itself.
  • ThemesDark by default. Append ?theme=light to the widget URL for the light variant.
  • If the read failsThe widget leaves its values as -- rather than showing a stale or invented number. It never renders a figure it did not just fetch.
  • Embedding policyThe widget sets frame-ancestors *, so it can be framed from any origin.

When embedding, please attribute Stable Tape with a visible link to stabletape.com. The widget already carries a branded link. For methodology context, link to stabletape.com/methodology.

research · solana mainnet

The on-chain oracle

The DDD reading is also written to four accounts on Solana mainnet, so the number can be verified and consumed without trusting this site's HTTP layer. This is a research surface. It is a second publication of the same reading, not a separate measurement, and it carries no availability commitment: an on-chain read can be mid-cycle, and the accounts are updated by a single authority.

Reads are permissionless. No wallet, no transaction and no fee is involved; a single RPC getAccountInfo or getMultipleAccounts call is enough.

Program and account addresses, mainnet-beta
Account Seed Role Address
program Oracle program ID XJjnewyPHcfb2ogMN1uAZGyt25XbKN2DWnm1GfAwddd
summary "oracle" DDD value, totals, concentration and metadata 8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA
chains "chains" Supply breakdown by chain, up to 20 rows 97g36T1PV3anxCwxV6ue5MmF2A9HQHMsLqevckrkqjzK
issuers "issuers" Supply breakdown by issuer, up to 25 rows GznG6pxbLPrqeYDd5aFXJcXK5SQ4wXifBtqjep34DNe
tokens "tokens" Supply breakdown by token, up to 25 rows 7FRjdiN489qMU2R8G2RumBPxRjanY9683gnXttKP4Fhv

Each account is a program-derived address from its seed and the program ID, so any of them can be re-derived rather than trusted from this page.

Reading across accounts. The summary and the three books each stamp the same sequence when they are written together. If a book's sequence does not equal the summary's, the read landed mid-cycle or one book write failed. Compare them and retry rather than mixing figures from two cycles.

Node.js · @snowplow1337/ddd-oracle-sdk

SDK

The SDK handles PDA derivation and account deserialisation. It has a peer dependency on @solana/web3.js and is published to GitHub Packages, so an install needs a registry entry for the @snowplow1337 scope.

$ npm install @snowplow1337/ddd-oracle-sdk @solana/web3.js
javascript · node
// Read the DDD summary account 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);

  // dddPercent is precomputed; totalStablesUsd is a bigint in whole USD.
  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.

readDDD(connection) fetches the summary account only. readAll(connection) fetches the summary and all three books in one getMultipleAccounts call and adds a consistent flag; readAllConsistent(connection) is the same read with a built-in retry when that flag is false. readChains, readIssuers and readTokens fetch one book each.

readDDD() return value — every field
Field Type Meaning
adminPublicKeyAdmin key; can rotate the authority and the admin.
authorityPublicKeyKey that signs updates.
bumpnumberBump seed for the summary PDA.
dddbigintRaw on-chain value, (stables / m2) × 106.
totalStablesUsdbigintTotal US-stablecoin circulating supply, in whole USD. Divide by 1e9 for billions.
m2UsdbigintU.S. M2 money stock, in whole USD. Divide by 1e12 for trillions.
timestampbigintUnix seconds for the underlying data point.
slotbigintSolana slot when the account was last written.
sequencebigintMonotonic write counter. Books copy it each cycle; compare to detect a mid-cycle read.
chainCountnumberValid rows in the chain book.
issuerCountnumberValid rows in the issuer book.
tokenCountnumberValid rows in the token book.
issuerHhinumberIssuer Herfindahl-Hirschman index on a 0–10,000 scale: the sum of squared market-share percentages.
effectiveIssuerScalednumberEffective issuer count × 106. 2.5 issuers is 2500000.
topIssuerShareScalednumberLargest issuer share as percent × 106, the same encoding book rows use.
dddRationumberConvenience: DDD as a decimal ratio, ddd / 1e6. 1.4138% is 0.014138.
dddPercentnumberConvenience: DDD as a percentage, dddRatio × 100. Ready to display.
effectiveIssuerCountnumberConvenience: effectiveIssuerScaled / 1e6.
topIssuerSharePercentnumberConvenience: topIssuerShareScaled / 1e6, e.g. 58.97.

Python 3 · standard library only

Decoding without an SDK

No packages required. This queries the Solana JSON-RPC and decodes the binary account layout directly, using the offsets documented below.

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, offset 73 onward: ddd, total_stables_usd, m2_usd, timestamp, slot, sequence
ddd, stb, m2, ts, slot, seq = struct.unpack_from('<QQQqQQ', s, 73)
# Concentration fields follow the three u8 counts at 121/122/123.
hhi, eff, top = struct.unpack_from('<III', s, 124)

print(f"DDD ratio : {ddd/1e4:.4f}%")          # raw / 1e4 == percent
print(f"Stables   : ${stb/1e9:,.4f}B")        # whole USD, so /1e9 == billions
print(f"U.S. M2   : ${m2/1e12:,.4f}T")
print(f"Issuer HHI: {hhi}  (effective {eff/1e6:.2f}, top {top/1e6:.2f}%)")
print(f"Updated   : {ts_utc(ts)}  (slot {slot:,})")
print(f"Sequence  : {seq}")
print(f"Admin     : {b58(s[8:40])}")

# Book accounts: count at byte 25, then fixed 29-byte entries from byte 26.
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)
text · example output, illustrative not live
# Static example. This page runs no browser-side RPC call of its own:
# third-party RPC endpoints set their own CORS and CSP rules, so a
# documentation page is the wrong place to read from one. Run the
# snippet above from a server or a shell for real values.
DDD ratio : 1.3500%
Stables   : $305.0000B
U.S. M2   : $22.5900T
Issuer HHI: 3120  (effective 3.21, top 61.40%)
Updated   : 2026-08-06T12:00:00+00:00  (slot ...)
Sequence  : ...

Any language · Solana JSON-RPC

Direct RPC call

No SDK? Fetch the summary account directly with a standard Solana JSON-RPC call. This 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" }
    ]
  }'

The response is not meant to be readable at a glance. result.value.data[0] is the base64-encoded raw account. Solana does not turn that into DDD = 1.33% for you: decode the base64, then read little-endian fields at the offsets in the layout table below. The long string that looks like noise is the expected result.

If a python3 -c "..." one-liner printed nothing, the cause is usually quoting: double quotes let the shell expand $ inside Python f-strings before Python runs. Use single-quoted -c '...' as below so stdin from curl still reaches Python. Do not use curl | python3 <<'PY'; that replaces stdin and breaks the pipe.

bash · same call, piped into python
$ 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)
raw = base64.b64decode(j["result"]["value"]["data"][0])
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)
'
Using a dedicated RPC provider. Replace the URL with your own. Keep "commitment": "confirmed" in the params object; many setups need it. If nothing comes back: try the URL without a trailing slash, check whether the provider enforces an IP allowlist, and add -v to see HTTP errors. On Windows, run the command in Git Bash or WSL, as PowerShell handles multiline single-quoted JSON differently.

Binary layout · OracleState

Summary account layout

The summary account is a packed Anchor struct of exactly 252 bytes. Every integer is little-endian and there is no padding between fields, so each offset below is the sum of the widths before it. A read that returns any other length is not this account.

OracleState, seed "oracle", 252 bytes
Field Offset Type Meaning
discriminator 0 [u8; 8] Anchor account discriminator
admin 8 [u8; 32] Admin pubkey; base58-encode to display
authority 40 [u8; 32] Update authority pubkey
bump 72 u8 Bump seed for this PDA
ddd 73 u64 LE DDD ratio × 106. Divide by 10,000 for percentage.
e.g. 13900 → 1.3900%
total_stables_usd 81 u64 LE Total US-stablecoin circulating supply, in whole USD. Not cents, and not scaled.
e.g. 319000000000 → $319B
m2_usd 89 u64 LE U.S. M2 money stock, in whole USD.
e.g. 21700000000000 → $21.7T
timestamp 97 i64 LE Unix seconds (UTC) of the underlying data point. Signed, not unsigned.
slot 105 u64 LE Solana slot of the last write
sequence 113 u64 LE Monotonic write counter. Books copy it; compare to detect a mid-cycle read.
chain_count 121 u8 Valid rows in the chain book
issuer_count 122 u8 Valid rows in the issuer book
token_count 123 u8 Valid rows in the token book
issuer_hhi 124 u32 LE Issuer HHI on a 0–10,000 scale: the sum of squared market-share percentages.
e.g. a 56% issuer contributes 3136
effective_issuer_scaled 128 u32 LE Effective issuer count × 106.
e.g. 2500000 → 2.5 issuers
top_issuer_share_scaled 132 u32 LE Largest issuer share, percent × 106.
e.g. 58970000 → 58.97%
reserved 136 [u8; 116] Reserved for future fields. Do not read; do not assume it stays zero.
Two different scales share one factor. ddd is the decimal ratio × 106, so percent is ddd / 10,000. The *_scaled concentration fields and every book row's pct_scaled are a percentage × 106, so percent is value / 1,000,000. Both use 106; they do not mean the same thing.

Book account layout

The three book accounts share one layout. Only the row capacity, and therefore the total size, differs: chains holds up to 20 rows (638 bytes), issuers and tokens up to 25 (783 bytes each). Rows past count are present in the account but not meaningful.

ChainBook / IssuerBook / TokenBook
Field Offset Type Meaning
discriminator0[u8; 8]Anchor account discriminator
bump8u8Bump seed for this PDA
sequence9u64 LESequence stamped when this book was written. Must equal the summary's for an atomic read.
timestamp17i64 LEUnix seconds of the cycle this book belongs to
count25u8Number of valid rows
entries[i]26 + 29i29 bytesFixed-width row: name_len u8, name [u8; 16], supply_usd u64 LE at +17 (whole USD), pct_scaled u32 LE at +25 (percent × 106).
reserved26 + 29·capacity[u8; 32]Reserved for future fields

Research · /api/v1/oracle/*

Oracle over HTTP

If you want the on-chain values without running an RPC client, two versioned read-only endpoints return the decoded accounts as JSON. They read the same four accounts the SDK reads, so they are a convenience over the oracle, not a separate source. They carry the same research register as the oracle itself.

  • GET /api/v1/oracle/summary — the decoded summary account only. The cheapest read.
  • GET /api/v1/oracle/snapshot — the summary plus the chain, issuer and token books from one multi-account read, with a consistent boolean. When consistent is false, a note field explains that the read landed mid-cycle; retry shortly rather than using the books.

Both wrap their payload in apiVersion, kind, programId and cluster, and both return every 64-bit value as a string so no precision is lost in JSON. Alongside the raw fields they include the same convenience values the SDK computes: dddRatio, dddPercent, effectiveIssuerCount, topIssuerSharePercent, plus updatedAt as an ISO timestamp derived from timestamp.

Both are CORS-open, answer OPTIONS, and reject any method other than GET with 405. Each response carries an ETag keyed on the oracle sequence and Cache-Control: public, max-age=15, s-maxage=45, stale-while-revalidate=120. Send If-None-Match with the previous ETag to get 304 Not Modified while the sequence is unchanged. If the account cannot be decoded, both return 503 with error: "oracle_unavailable"; any other failure is 500. Adding ?debug=1 appends a small non-sensitive debug block with the fetch time and the RPC hostname.

One interface here is not public. GET /api/jupiter-solana-route also exists and may appear in a network log. It is a read-only proxy for a Jupiter swap quote — not an oracle read, not versioned, and not part of the benchmark. Its CORS policy admits only Stable Tape's own origins, so a cross-origin browser call from another site will fail. It is listed here so it is explained, not so it is built on.

agent workflows

Using DDD with agents

People often pick a stablecoin by habit, brand or chain loyalty. An agent can read the structure instead. The endpoints above expose the benchmark, issuer concentration and chain distribution in machine-readable form, which is the input an allocation or routing decision actually needs.

  • Treasury

    Check issuer concentration — HHI, effective issuer count, top issuer share — before recommending a stablecoin allocation.

  • Risk monitoring

    Watch for concentration rising: the same three fields, sampled over time from /api/v1/oracle/summary or /api/issuers.

  • Payment planning

    Use chain distribution to decide where stablecoins should move. Velocity data is research-stage and is not exposed here.

  • Research

    Read the dated records directly from /api/tape instead of scraping a dashboard that may re-render.

DDD is a decision input, not an execution layer. Jupiter and LI.FI execute. These endpoints help an agent decide which stablecoin and which chain make sense; swaps, bridges, quotes and transactions are somebody else's contract.

DDD decides. Execution providers execute.

limits and guarantees

What is and is not promised

  • No SLA. These endpoints are published without an uptime, latency or availability guarantee. Cache what you need and degrade gracefully. Every public endpoint here is CDN-cached, so treat the cache window as the real freshness bound.
  • Upstream revision. DeFiLlama restates supply and the Federal Reserve revises M2. A current reading can change under you; a frozen dated record cannot. That is the reason to cite records rather than readings.
  • Update cadence. The Daily Tape record is cut at 12:00 UTC. /api/ratio and the structure endpoints recompute per request behind a five-minute cache. The oracle follows its own write cycle and stamps timestamp, slot and sequence so you can see exactly how old a read is.
  • No fees on chain. Reading Solana accounts costs nothing. You only pay lamports to write, which reading the ratio never requires.
  • RPC endpoints. Every snippet here uses the public cluster api.mainnet-beta.solana.com, which is rate-limited and not intended for production. Use your own provider. This page itself makes no RPC call: the example output above is a static illustration, not a live read.
  • Change policy. If a documented field name, unit or scale changes, this page changes with it, and npm run test:developer-api-docs fails until it does. Interfaces in the research and internal registers carry no such commitment.

Questions? @stabletape

The weekly email.