Recipe: stablecoin monitor
This recipe combines three things: USD stablecoin supply from DefiLlama, reference prices from CoinGecko, and stored large stablecoin transfers. It prints a short report once per run.
#The data
| Question | Endpoint | Fields | Cache |
|---|---|---|---|
| Supply by asset, with 7 and 30 day change | Stablecoin supply | assets[] | 10 min |
| Supply by chain | Same | chains[] | 10 min |
| Distance from $1 | Same, or Asset prices | price or usd | 10 min, 60 s |
| Large mints and burns | List large transfers | Rows with the Null address label | One poll |
Supply covers the whole market by the source's method. Transfers cover curated tokens on indexed chains: USDC, USDT and DAI, USDbC on Base. These are different scopes, so do not divide one by the other.
#Code
Alert thresholds in this code are examples. Choose your own.
curl -s "https://farbeamlabs.com/api/v1/markets/stablecoins" \
| jq -r '.data.assets[:10][] | [.symbol, .price, .circulating_usd, .change_7d_pct] | @tsv'// stable.mjs: run with node stable.mjs
const API = "https://farbeamlabs.com/api/v1";
const get = async (path) => {
const { data, error } = await (await fetch(API + path)).json();
if (error) throw new Error(`${error.code}: ${error.message}`);
return data;
};
const money = (n) => `$${Math.round(n).toLocaleString("en-US")}`;
const { assets, chains } = await get("/markets/stablecoins");
for (const asset of assets.slice(0, 10)) {
const offPeg = asset.price === null ? null : Math.abs(asset.price - 1);
const flag = offPeg !== null && offPeg > 0.005 ? " price is more than 0.5% from $1" : "";
console.log(asset.symbol.padEnd(8), money(asset.circulating_usd).padStart(20), `${asset.change_7d_pct}% 7d`, flag);
}
console.log("Covered chains:", chains.filter((c) => c.covered).map((c) => `${c.name} ${money(c.circulating_usd)}`).join(", "));
const rows = await get("/transfers?min_usd=1000000&limit=100");
const stable = new Set(["USDC", "USDT", "DAI", "USDbC"]);
for (const row of rows.filter((r) => stable.has(r.token))) {
if (row.from_label === "Null address") console.log("mint", row.chain, row.token, money(row.usd), row.tx);
if (row.to_label === "Null address") console.log("burn", row.chain, row.token, money(row.usd), row.tx);
}# stable.py: run with python3 stable.py
import requests
API = "https://farbeamlabs.com/api/v1"
def get(path, **params):
body = requests.get(API + path, params=params, timeout=30).json()
if "error" in body:
raise RuntimeError(body["error"]["code"])
return body["data"]
data = get("/markets/stablecoins")
for asset in data["assets"][:10]:
price = asset["price"]
flag = " price is more than 0.5% from $1" if price is not None and abs(price - 1) > 0.005 else ""
print(f"{asset['symbol']:8} ${asset['circulating_usd']:>18,} {asset['change_7d_pct']}% 7d{flag}")
print("Covered chains:", [(c["name"], c["circulating_usd"]) for c in data["chains"] if c["covered"]])
for row in get("/transfers", min_usd=1000000, limit=100):
if row["token"] in ("USDC", "USDT", "DAI", "USDbC"):
if row["from_label"] == "Null address":
print("mint", row["chain"], row["token"], round(row["usd"]), row["tx"])
if row["to_label"] == "Null address":
print("burn", row["chain"], row["token"], round(row["usd"]), row["tx"])This sample of one asset row was captured on 2026-11-04 UTC. It is an example.
{
"name": "Tether",
"symbol": "USDT",
"mechanism": "fiat-backed",
"price": 0.99905,
"circulating_usd": 177773802949,
"change_7d_pct": -2.23,
"change_30d_pct": -3.32
}#Alerts for large moves
Save rules for the transfer side. Each rule fires at most once per cooldown. Rules cannot read supply or price, so keep those checks in a scheduled job.
token in ["USDC", "USDT", "DAI"] and usd >= 25m and not sampledfrom_label == "Null address" and token in ["USDC", "USDT", "DAI", "USDbC"]to_label == "Null address" and token in ["USDC", "USDT", "DAI", "USDbC"]#Reading the results
| Observation | What it can mean | What it does not prove |
|---|---|---|
| Supply fell over 7 days | The source counted less in circulation | A loss of trust. Chain migration and redemptions also reduce it. |
| Price off $1 by a small amount | Normal market noise or a source lag | A depeg. Check more than one source. |
Large row from Null address | A mint on that chain | New demand. Issuers also mint in advance, and bridges mint on arrival. |
| Large move between unlabeled wallets | A transfer | Any intent |
The price from DefiLlama and usd from /prices come from different sources and refresh at different rates. They will not match exactly, so do not build a check on a tiny difference between them.
Stablecoin valuation in Farbeam uses market prices, not a fixed $1. See USD valuation.
#Running on a schedule
Run this every 10 minutes at most for supply, since the source is cached for that long. Store the output yourself if you need a long series. Farbeam does not offer a history of these values, and a historical archive is Planned.
This recipe reports data. It makes no statement about the safety of any stablecoin, and it is not financial advice.