sGHO (Aave Savings GHO) — portfolio + asset-profile support plan
Written 2026-07-19 against origin/staging = 61eca76 (v0.13.0). ⚠ The local checkout on this machine is a stale v0.9.0 tree — cut the feature branch from origin/staging, not from the on-disk staging branch.
1 Product requirement
Support sGHO only — the savings GHO vault Aave launched in May 2026 — as:
- a wallet-tracked variable-rate asset in /portfolio (bare holdings chart into the USD book with yield-bearing valuation, category
variable_rate_asset), and - a full row + memo in Asset Profiles (/asset-coverage).
Explicitly not supported (product decision, 2026-07-19):
- Legacy Safety Module stkGHO (
0x1a88df1cfe15af22b3c4c783d4e6f7f9e0c1885d) — deprecated, rewards wound to zero over seven weeks from the May 2026 sGHO launch. DefiLlama tags its pool "Legacy". - Umbrella stkGHO (the June 2025 slashing-bearing staking) — out of scope by the same "latest version only" decision.
- Merit / incentive rewards on top of the savings rate — only the native on-chain rate charts.
2 The asset (facts verified on-chain 2026-07-19)
| Fact | Value |
|---|---|
| Token | sGho proxy 0xE1753F2e00940cC31213dd92013cF019DFE4ca1d, Ethereum mainnet only |
| Deployed | 2026-05-05 (block-verified via Herd); product live ~2026-05-17 |
| Standard | ERC-4626 vault; asset() = GHO 0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f |
| Decimals | 18 (share) / 18 (GHO) — convertToAssets divisor = 18 + 18 − 18 = 18 |
| On-chain symbol | sGho (display ticker everywhere in our code: sGHO) |
| Yield mechanics | internal RAY yieldIndex, linear-per-update accrual at ratePerSecond, compounds across updates; convertToAssets(1e18) = 1.007482e18 on 2026-07-19 |
| Rate | targetRate() = 425 bps (4.25% APR), set by YIELD_MANAGER_ROLE, hard cap MAX_SAFE_RATE = 50% |
| Exits | atomic redeem, no cooldown, no slashing — but maxWithdraw/maxRedeem are capped by the vault's actual GHO balance (totalAssets() is accounting: totalSupply × yieldIndex; the accrual is funded by Aave DAO GHO top-ups) |
| Pause | PAUSE_GUARDIAN_ROLE pause blocks transfers and exits (_update is whenNotPaused, maxWithdraw → 0) |
| Caps | supplyCap() in asset terms, yield-manager-settable |
| Scale | totalAssets() ≈ 134.5M GHO (2026-07-19) |
| DefiLlama | pool 19d024ed-9c70-4323-9a81-fe873ea5f0e0 (aave-v3 / SGHO), daily APY+TVL since 2026-05-17. Do NOT use ff2a68af-… (poolMeta "Legacy" = old stkGHO) |
Key integration consequence: sGHO is a clean erc4626 share-rate token — it slots into the existing rate-source machinery exactly like sUSDe/sUSDS. No new adapter kind, no new classifier logic, no schema change.
3 Design — one small PR, nine touchpoints
3.1 Registry row — scripts/sql/053-sgho-token.sql (new migration)
One INSERT … ON CONFLICT DO NOTHING into onchain_credit.portfolio_tokens, following the 049 asset-profile row pattern:
(1, '0xe1753f2e00940cc31213dd92013cf019dfe4ca1d', 'sGHO', 'Savings GHO', 18,
'USD', 'variable_rate', true, '0xe1753f2e00940cc31213dd92013cf019dfe4ca1d', 'asset-profile')Additive rows only, no new columns → no prerender/migration deadlock exposure. Idempotent, forward-only, GRANTs already exist on the table. Run via migrate.sh (bare DB name) as the postgres owner, ledger entry as usual.
Note: sync-portfolio-tokens.ts --approve is deliberately NOT the path — it refuses known accruers by design; accruers enter via manual migration + profile (this plan).
3.2 Book seed + rate path — src/lib/portfolio/buckets.ts, src/lib/portfolio/valuation-sources.ts
ACCOUNTING_ASSET_BOOKS(buckets.ts): add"0xe1753f2e00940cc31213dd92013cf019dfe4ca1d": "USD"in the USD block (seed + pure-context fallback; runtime bookMap comes from 3.1).- Not in
PAR_ACCOUNTING_ASSETS(it accrues; par would under-report — M9) and not inUNRATED_YIELD_ACCOUNTING_ASSETS→redemptionRateKindroutes it downrate-source. JIT_RATE_GETTERS(valuation-sources.ts): same-shape entry as sUSDe:{ to: <sGHO>, data: SEL_CONVERT_TO_ASSETS + pad32(SHARES_1E18), divisorPow10: 18 }.- Per the buckets.ts header rule (issue #385): the rate path (this + 3.3) lands in the same change as the map entry, or holdings disappear silently instead of charting.
- R6 check at ship time: confirm
portfolio_position_snapshotshas zero pre-existing rows with accounting asset = sGHO (expected — it was never swept and is not a venue reserve), so no destructive re-book repair is needed.
3.3 Yield adapter — scripts/refreshers/token-yields.ts
One BASE_YIELD_TOKENS entry, existing kind:
{ address: "0xe1753f2e00940cc31213dd92013cf019dfe4ca1d", symbol: "sGHO", kind: "erc4626" },The 6h refresher then writes token_yield_apy (share_rate, supply_apy annualised on the trailing 24h window, apy_30d, total_supply, block anchor). Expected steady-state: share_rate ≈ 1.0075 and rising; supply_apy ≈ 4.25%.
3.4 History backfill — scripts/backfill-sgho.ts (new, clone of backfill-susds.ts)
Archive eth_call convertToAssets(1e18) at each 6h boundary from 2026-05-05 (deployment) to now via blockByTimestamp + ethCallAt; store share_rate + 24h-annualised APY into token_yield_apy. ~300 windows, trivial run. Notes:
- Early windows will show share_rate = 1.0 and 0% APY (
targetRateinitialised to 0 before governance set it) — genuine zeros, keep them (same policy as the flat reUSD days). - No DefiLlama dependency needed for the primary series; the pool above is the cross-check.
- Ordering matters for wallet history: historical snapshot valuation resolves share_rate from the DB (
readDbShareRatehistory mode), so this backfill must run before 3.5.
3.5 Wallet history — scripts/backfill-portfolio-wallet.ts
For tracked wallets already holding sGHO (bought May–July), run the existing wallet backfill so Transfer flows, entry basis (#433) and history snapshots materialise. Standard ops caveats apply: drain cron, advisory lock, repair re-runs are destructive full re-derivations.
3.6 Asset Profiles data row — scripts/refreshers/yield-token-assets.ts
ASSETS[] entry:
{ ticker: "sGHO", name: "Savings GHO", issuer: "Aave",
href: "/asset-coverage?asset=sGHO",
tokenAddress: "0xe1753f2e00940cc31213dd92013cf019dfe4ca1d" }plus the per-asset productive/underlying branch: productive = sGho.totalAssets()/1e18 (GHO earning the savings rate), underlying = GHO.totalSupply()/1e18.
3.7 Asset-coverage page — src/app/asset-coverage/page.tsx
ASSET_DENOMINATION: add sGHO: "USD" (explicit, though USD is the fallback).
3.8 Narrative — src/data/asset-narratives.ts (ASSET_NARRATIVES.sGHO)
Follow the file-header rules exactly (three fixed sections, strictly factual, no point-in-time figures, no em-dashes; PST is the tone reference). Draft skeleton to refine in the PR:
- Lede: sGHO is Aave's savings token for the GHO stablecoin. Deposit GHO, receive sGHO; yield accrues as a rising GHO redemption price per sGHO at a rate set through Aave governance.
- Where does the yield come from? ERC-4626 vault; redemption price follows an on-chain index that grows at a target annual rate set by a yield-manager role appointed by Aave governance (contract-capped at 50%). The yield is not generated inside the vault: the GHO that honors it is contributed by the Aave DAO, funded from protocol revenue such as interest paid by GHO borrowers. One layer down, GHO itself is an overcollateralised stablecoin minted against collateral supplied to Aave.
- Risks and the loss absorption waterfall: no slashing (sGHO is not protocol insurance — the contrast with the retired Safety Module staking and with Umbrella staking); rate is administratively adjustable at any time; the accrual is an accounting promise — withdrawals are capped by the GHO actually held by the vault, so exits depend on the DAO keeping it funded; a pause guardian can freeze transfers and exits; upgradeable proxy under Aave governance; ultimate exposure is the GHO peg. Waterfall: borrower collateral and liquidations (first) → Aave's staking backstop and treasury (second) → GHO holders, including sGHO (last).
- Redemptions and liquidity: atomic on-chain redemption into GHO, no cooldown or fee, bounded by vault GHO balance and blocked while paused; deposits bounded by an adjustable supply cap; secondary liquidity is GHO's own (DEX pools, GHO Stability Modules into USDC/USDT); GhoRouter zaps other stables in; Ethereum mainnet only.
3.9 Icon — src/components/icons/token-marks.tsx + public/token-icons/
IMAGE_MARK entry SGHO: "/token-icons/sgho.svg". Source the mark from Aave's official asset set (interface repo / bgd-labs web3 icons) — never hand-drawn. Fallback if no distinct official sGHO mark exists: reuse gho.svg deliberately, noted in the PR. Update token-marks.test.tsx.
Category / classifier
No code change: class = 'variable_rate' → categoryForInclusion yields variable_rate_asset for bare wallet legs automatically.
Docs (same PR — vitepress build as dead-link check)
docs/portfolio.md— add sGHO to the wallet-tracked variable-rate universe.docs/metrics.md— token-yield coverage list (+ the sGHO rate semantics: admin-set target rate, not market-derived).docs/data-pipeline.md— token-yields entry +backfill-sgho.ts.docs/database.md—portfolio_tokensseed note (053) +assetsticker list.
4 Ops runbook (staging first; prod only rides the next release, with explicit permission)
- Branch from
origin/staging→ PR (code + docs + migration + backfill script together). - Merge → wait out the Actions deploy fully (npm ci drops tsx; premature run-cron fails).
migrate.sh <db> 053-sgho-token.sql(rows only — safe after deploy; no prerender deadlock).run-cron.sh backfill-sgho.ts(share_rate history 2026-05-05 → now).- Next 6h token-yields tick (or manual run) starts the live series;
refresh-assetstick populates theassetsrow → /asset-coverage shows sGHO within its ISR hour (revalidate 3600; force re-render if needed for the feel-check). backfill-portfolio-wallet.tsfor tracked wallets holding sGHO, then POST portfolio refresh before any GET feel-check (cold cache lies).
5 Verification bar
- Adapter: local token-yields run stores share_rate matching live
convertToAssets(1e18)(1.0075 area, monotonic) and supply_apy ≈ 4.25% ± noise; cross-check DefiLlama pool19d024ed…(4.25%) andtargetRate()= 425. - Backfill: series monotonic in share_rate, genuine 0% head, no gaps at 6h cadence; spot-check one mid-June window against an archive
convertToAssetsread. - Portfolio: seed a real sGHO-holding wallet (pick from current top holders) on staging (SSH tunnel + minted session cookie), POST refresh, then verify the leg charts in the USD book as
variable_rate_assetwith entry basis populated, and survives the sync tool's check 3 (variable_rate row resolves a redemption rate) without a WS8 alert. - Profiles: /asset-coverage row renders with APY/returns + productive/underlying; memo passes the narrative header rules (no em-dashes, three sections); vitepress build clean.
- Tests: token-marks test updated; any new render asserts use magnitude-unique fixtures.
6 Decision log & open items
- 2026-07-19 (Fred): latest staked GHO only → sGHO. Legacy stkGHO and Umbrella stkGHO stay unsupported.
- Merit rewards excluded from APY (native savings rate only) — consistent with how other assets' off-chain incentives are treated.
- Cross-chain sGHO (governance ARFC pending): out of scope;
portfolio_tokensis chain_id-keyed, so a later Base/Arbitrum deployment is a new row, not a redesign. - If Aave later lists sGHO as a v3 reserve / e-mode collateral, venue readers pick it up via the book map automatically — the rate path shipped here already covers it.
- Open: none at planning time.