Skip to content

Commit af730a6

Browse files
authored
Merge pull request #117 from ctmotox2/feat/usdai-monitoring
Feat/usdai monitoring
1 parent ac21f90 commit af730a6

File tree

6 files changed

+1156
-925
lines changed

6 files changed

+1156
-925
lines changed

.github/workflows/hourly.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ env:
4949
TELEGRAM_CHAT_ID_ETHENA: ${{ secrets.TELEGRAM_CHAT_ID_ETHENA }}
5050
TELEGRAM_CHAT_ID_EULER: ${{ secrets.TELEGRAM_CHAT_ID_EULER }}
5151
TELEGRAM_CHAT_ID_FLUID: ${{ secrets.TELEGRAM_CHAT_ID_FLUID }}
52+
TELEGRAM_CHAT_ID_USDAI: ${{ secrets.TELEGRAM_CHAT_ID_USDAI }}
5253
TELEGRAM_BOT_TOKEN_DEFAULT: ${{ secrets.TELEGRAM_BOT_TOKEN_DEFAULT }}
5354
GRAPH_API_KEY: ${{ secrets.GRAPH_API_KEY }}
5455
TALLY_API_KEY: ${{ secrets.TALLY_API_KEY }}
@@ -124,6 +125,8 @@ jobs:
124125
run: uv run resolv/resolv.py
125126
- name: Run Silo Utilization script
126127
run: uv run silo/ur_sniff.py
128+
- name: Run USDai script
129+
run: uv run usdai/main.py
127130

128131
# Check final hash
129132
- name: Check final hash

.github/workflows/multisig-checker.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ env:
2727
TELEGRAM_CHAT_ID_PENDLE: ${{ secrets.TELEGRAM_CHAT_ID_PENDLE }}
2828
TELEGRAM_BOT_TOKEN_EULER: ${{ secrets.TELEGRAM_BOT_TOKEN_EULER }}
2929
TELEGRAM_CHAT_ID_EULER: ${{ secrets.TELEGRAM_CHAT_ID_EULER }}
30+
TELEGRAM_CHAT_ID_USDAI: ${{ secrets.TELEGRAM_CHAT_ID_USDAI }}
3031
TELEGRAM_CHAT_ID_USD0: ${{ secrets.TELEGRAM_CHAT_ID_USD0 }}
3132
TELEGRAM_CHAT_ID_MOONWELL: ${{ secrets.TELEGRAM_CHAT_ID_MOONWELL }}
3233
TELEGRAM_CHAT_ID_LRT: ${{ secrets.TELEGRAM_CHAT_ID_LRT }}

safe/main.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,18 @@
130130
"0x2536f2ef78b0df34299cad6e59300f8f83fe1ec4",
131131
"thBILL minter role. Markets used on Morpho Arbitrum",
132132
], # thBILL minter role
133+
[
134+
"USDAI",
135+
"arbitrum-main",
136+
"0xF223F8d92465CfC303B3395fA3A25bfaE02AED51",
137+
"USDai Admin Safe",
138+
],
139+
[
140+
"USDAI",
141+
"arbitrum-main",
142+
"0x783B08aA21DE056717173f72E04Be0E91328A07b",
143+
"sUSDai Admin Safe",
144+
],
133145
# [
134146
# "USD0",
135147
# "mainnet",

usdai/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# USDai Monitoring
2+
3+
This script monitors the USDai protocol on Arbitrum One, specifically the relationship between its supply and collateral backing.
4+
5+
## Protocol Overview
6+
7+
- **Docs**: [Proof of Reserves Guide](https://docs.usd.ai/app-guide/proof-of-reserves)
8+
- **Claimed Backing**: [99.8% by TBills](https://app.usd.ai/reserves)
9+
- **Mechanism**: USDai is backed by `wM` (Wrapped M) tokens. `M` is a token representing T-Bill yields from the M^0 protocol.
10+
- **Backing Source**: The M^0 protocol backs `M` tokens with off-chain T-Bills held in custody. `wM` is a wrapper that enables `M` to be used on other chains like Arbitrum, rebasing or accumulating value to reflect T-Bill yield.
11+
- **Minting**: Minting involves depositing `wM` into the USDai Vault, which ensures 1:1 backing for the minted `USDai`.
12+
13+
## Metrics & Monitoring
14+
15+
We track the following key metrics to ensure solvency and stability:
16+
17+
- **USDai Supply**: The calculated supply based on the `wM` balance held by the USDai Vault on-chain.
18+
- **Mint Ratio**: The collateralization ratio retrieved from the protocol API (0.995).
19+
- **Collateral**: Calculated as `USDai Supply / Mint Ratio`.
20+
- **Buffer**: `Implied Collateral` - `USDai Supply`. A positive buffer indicates the system is functioning within the expected Mint Ratio parameters.
21+
22+
## Alerts
23+
24+
- **Buffer Drop**: A Telegram alert is triggered if the **Buffer** value decreases by more than **$10,000** from the last cached value. Since the buffer represents accumulated yield, it is expected to grow or remain stable. A significant drop could indicate:
25+
- A loss of backing value (depegging of underlying asset).
26+
- An issue with the yield accrual mechanism.
27+
- An unexpected withdrawal or rebalancing event.
28+
- **Mint Ratio Change**: A Telegram alert is triggered if the protocol's Mint Ratio changes from its previous value. This is a critical parameter that determines backing requirements.
29+
- **Governance Events**: We monitor for queued governance actions on the USDai Admin Safe and sUSDai Admin Safe contracts.
30+
31+
## Contracts (Arbitrum One)
32+
33+
- **USDai Token (Vault)**: `0x0A1a1A107E45b7Ced86833863f482BC5f4ed82EF`
34+
- **wM Token**: `0x437cc33344a0b27a429f795ff6b469c72698b291`
35+
- **sUSDai**: `0x0B2b2B2076d95dda7817e785989fE353fe955ef9`
36+
37+
## Governance & Security
38+
39+
- **Access Control**: The USDai token contract implements standard Access Control roles:
40+
- `DEFAULT_ADMIN_ROLE` (`0x00...`): Can grant and revoke other roles.
41+
- `hasRole(role, account)`: Used to verify permissions.
42+
- `getRoleAdmin(role)`: Determines who manages specific roles.
43+
- **Upgradeability**:
44+
- **USDai Token**: Is an upgradeable contract (ERC1967Proxy). We monitor for `Upgraded` events.
45+
- **wM Token**: Is an upgradeable contract (ERC1967Proxy). We monitor for `Upgraded` events.
46+
- **Functionality**:
47+
- **Supply Control**: Includes `supplyCap` and `totalSupply`.
48+
- **Bridging**: Includes `bridgedSupply` and `eip712Domain` (supports cross-chain/permit).
49+
- **Swap Adapter**: Contains a `swapAdapter` address for integrating swaps or redemptions.
50+
51+
## sUSDai FAQ
52+
53+
> **How does it work?**
54+
55+
**sUSDai** is a yield-bearing ERC-4626 vault token. It earns yield from M token emissions and by lending USDai to AI infrastructure pools (MetaStreet). It is not a stablecoin but a floating-price token representing a share of the lending portfolio and unallocated cash.
56+
57+
> **How to redeem it? Is there a queue?**
58+
59+
Redemption is done via the [app](https://app.usd.ai/unstake) or directly on-chain. It involves an **asynchronous request with a 30-day queue** (average wait expected to drop to 15 days). Redemptions are processed periodically by the protocol admin. Users wanting instant exit must use secondary markets (DEXs like Fluid/Curve), which currently hold ~$20M liquidity on Arbitrum.
60+
61+
> **Can it have losses?**
62+
63+
**Yes.** Unlike USDai (backed by T-Bills), sUSDai carries credit risk from its GPU-backed loans. If loans default and collateral liquidation is insufficient, the share price will drop, leading to principal loss. Redemptions use a "Conservative NAV" (Principal Only) to protect remaining stakers.
64+
65+
> **How is Price Per Share (PPS) defined? On-chain or Off-chain?**
66+
67+
**On-chain calculation using off-chain data.**
68+
The contract calculates PPS on-chain (see `redemptionSharePrice`), but the underlying Net Asset Value (NAV) relies on off-chain loan health data. The system uses a dual-NAV model:
69+
70+
- **Optimistic NAV** (Principal + Interest) for Deposits.
71+
- **Conservative NAV** (Principal Only) for Redemptions.
72+
A Chainlink oracle is used to convert pool positions into USDai value. Thus, while you can read the price on-chain, the inputs depend on the strategy's off-chain reporting.
73+
74+
## Usage
75+
76+
Collateral/Supply Monitoring:
77+
78+
```bash
79+
uv run usdai/main.py
80+
```
81+
82+
Governance Monitoring:
83+
84+
We monitor the following Safes for queued transactions using the shared Safe monitoring script:
85+
86+
- **USDai Admin Safe**: [`0xF223F...`](https://arbiscan.io/address/0xF223F8d92465CfC303B3395fA3A25bfaE02AED51) (2/4 multisig) - Admin of wM Token.
87+
- **sUSDai Admin Safe**: [`0x783B...`](https://arbiscan.io/address/0x783B08aA21DE056717173f72E04Be0E91328A07b) (3/3 multisig) - Admin of USDai Token (Vault) and sUSDai.
88+
89+
This runs every 10 minutes via GitHub Actions.
90+
91+
```bash
92+
uv run safe/main.py
93+
```

usdai/main.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import requests
2+
from dotenv import load_dotenv
3+
from web3 import Web3
4+
5+
from utils.cache import cache_filename, get_last_value_for_key_from_file, write_last_value_to_file
6+
from utils.chains import Chain
7+
from utils.telegram import send_telegram_message
8+
from utils.web3_wrapper import ChainManager
9+
10+
load_dotenv()
11+
12+
# Constants
13+
PROTOCOL = "usdai"
14+
VAULT_ADDR = Web3.to_checksum_address("0x0A1a1A107E45b7Ced86833863f482BC5f4ed82EF")
15+
WM_TOKEN = Web3.to_checksum_address("0x437cc33344a0b27a429f795ff6b469c72698b291")
16+
SUSDAI_ADDR = Web3.to_checksum_address("0x0B2b2B2076d95dda7817e785989fE353fe955ef9")
17+
GRAPHQL_URL = "https://protocol-api.m0.org/graphql"
18+
19+
20+
def main():
21+
client = ChainManager.get_client(Chain.ARBITRUM)
22+
23+
# Common ABI
24+
from utils.abi import load_abi
25+
26+
erc20_abi = load_abi("common-abi/ERC20.json")
27+
28+
wm = client.get_contract(WM_TOKEN, erc20_abi)
29+
30+
try:
31+
# --- On-Chain Supply ---
32+
# USDai Supply (wM held by Vault)
33+
vault_shares = wm.functions.balanceOf(VAULT_ADDR).call()
34+
# Decimals will always be the same = 6
35+
wm_decimals = 6
36+
usdai_supply_fmt = vault_shares / (10**wm_decimals)
37+
38+
# 2. Get Mint Ratio via API
39+
query = """
40+
query {
41+
mintRatio: protocolConfigs(
42+
where: {key: "mint_ratio"}
43+
orderBy: blockTimestamp
44+
orderDirection: desc
45+
first: 1
46+
) {
47+
value
48+
blockTimestamp
49+
}
50+
}
51+
"""
52+
53+
mint_ratio = 10000 # Default to 1:1 (scaled 1e4 for bps) if not found
54+
55+
try:
56+
res = requests.post(GRAPHQL_URL, json={"query": query}, timeout=10)
57+
if res.status_code == 200:
58+
data = res.json().get("data", {})
59+
60+
# --- Mint Ratio ---
61+
configs = data.get("mintRatio", [])
62+
if configs:
63+
# Mint ratio is scaled by 1e4 (e.g. 9950 = 99.5%)
64+
mint_ratio_raw = int(configs[0].get("value", 10000))
65+
mint_ratio = mint_ratio_raw
66+
67+
except Exception as e:
68+
print(f"API Error: {e}")
69+
70+
# Derived Collateral from Mint Ratio
71+
# Scaling: mint_ratio is in bps (1e4).
72+
# So mint_ratio_fmt = mint_ratio / 10000.
73+
mint_ratio_fmt = mint_ratio / 10000
74+
75+
# Avoid division by zero
76+
required_collateral = 0
77+
if mint_ratio_fmt > 0:
78+
required_collateral = usdai_supply_fmt / mint_ratio_fmt
79+
80+
print("\n--- USDai Stats ---")
81+
print(f"USDai Supply: ${usdai_supply_fmt:,.2f}")
82+
print(f"Mint Ratio: {mint_ratio_fmt:.4f}")
83+
84+
collateral_metric = required_collateral
85+
# Buffer = Collateral - Supply
86+
buffer = collateral_metric - usdai_supply_fmt
87+
88+
print(f"Collateral: ${collateral_metric:,.2f}")
89+
print(f"Buffer: ${buffer:,.2f}")
90+
91+
if collateral_metric > 0:
92+
# 1. Check for Mint Ratio Change (Critical)
93+
cache_key_ratio = f"{PROTOCOL}_mint_ratio"
94+
last_ratio = int(get_last_value_for_key_from_file(cache_filename, cache_key_ratio))
95+
96+
if last_ratio != 0 and last_ratio != mint_ratio:
97+
msg = f"⚠️ *USDai Mint Ratio Changed*\n\nOld: {last_ratio / 10000:.4f}\nNew: {mint_ratio / 10000:.4f}"
98+
send_telegram_message(msg, PROTOCOL)
99+
100+
# Always update ratio cache
101+
write_last_value_to_file(cache_filename, cache_key_ratio, mint_ratio)
102+
103+
# 2. Check for Buffer Drop (Activity)
104+
cache_key_buffer = f"{PROTOCOL}_buffer"
105+
last_buffer = float(get_last_value_for_key_from_file(cache_filename, cache_key_buffer))
106+
107+
if last_buffer != 0:
108+
change = buffer - last_buffer
109+
# Alert if buffer drops by more than $10,000
110+
if change < -10000:
111+
msg = f"📉 *USDai Buffer Drop Alert*\n\nBuffer dropped by ${abs(change):,.2f}!\nOld Buffer: ${last_buffer:,.2f}\nNew Buffer: ${buffer:,.2f}\n(Collateral: ${collateral_metric:,.2f})"
112+
send_telegram_message(msg, PROTOCOL)
113+
114+
write_last_value_to_file(cache_filename, cache_key_buffer, buffer)
115+
116+
except Exception as e:
117+
print(f"Error: {e}")
118+
send_telegram_message(f"⚠️ USDai monitoring failed: {e}", PROTOCOL, False, True)
119+
120+
121+
if __name__ == "__main__":
122+
main()

0 commit comments

Comments
 (0)