Skip to content

built Built. This is a decision record, not documentation.

What is still current: sGHO is a wallet-tracked variable-rate USD asset with a full profile, as specified. The route it calls /asset-coverage is now /asset-profiles.

Landed: migration 053 (v0.14.0)

Header updated 2026-09-14. The body below is frozen history. All plans.

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:

  1. a wallet-tracked variable-rate asset in /portfolio (bare holdings chart into the USD book with yield-bearing valuation, category variable_rate_asset), and
  2. 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) ​

FactValue
TokensGho proxy 0xE1753F2e00940cC31213dd92013cF019DFE4ca1d, Ethereum mainnet only
Deployed2026-05-05 (block-verified via Herd); product live ~2026-05-17
StandardERC-4626 vault; asset() = GHO 0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f
Decimals18 (share) / 18 (GHO) — convertToAssets divisor = 18 + 18 − 18 = 18
On-chain symbolsGho (display ticker everywhere in our code: sGHO)
Yield mechanicsinternal RAY yieldIndex, linear-per-update accrual at ratePerSecond, compounds across updates; convertToAssets(1e18) = 1.007482e18 on 2026-07-19
RatetargetRate() = 425 bps (4.25% APR), set by YIELD_MANAGER_ROLE, hard cap MAX_SAFE_RATE = 50%
Exitsatomic 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)
PausePAUSE_GUARDIAN_ROLE pause blocks transfers and exits (_update is whenNotPaused, maxWithdraw → 0)
CapssupplyCap() in asset terms, yield-manager-settable
ScaletotalAssets() ≈ 134.5M GHO (2026-07-19)
DefiLlamapool 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:

sql
(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 in UNRATED_YIELD_ACCOUNTING_ASSETS → redemptionRateKind routes it down rate-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_snapshots has 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:

ts
{ 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 (targetRate initialised 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 (readDbShareRate history 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:

ts
{ 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/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_tokens seed note (053) + assets ticker list.

4 Ops runbook (staging first; prod only rides the next release, with explicit permission) ​

  1. Branch from origin/staging → PR (code + docs + migration + backfill script together).
  2. Merge → wait out the Actions deploy fully (npm ci drops tsx; premature run-cron fails).
  3. migrate.sh <db> 053-sgho-token.sql (rows only — safe after deploy; no prerender deadlock).
  4. run-cron.sh backfill-sgho.ts (share_rate history 2026-05-05 → now).
  5. Next 6h token-yields tick (or manual run) starts the live series; refresh-assets tick populates the assets row → /asset-coverage shows sGHO within its ISR hour (revalidate 3600; force re-render if needed for the feel-check).
  6. backfill-portfolio-wallet.ts for 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 pool 19d024ed… (4.25%) and targetRate() = 425.
  • Backfill: series monotonic in share_rate, genuine 0% head, no gaps at 6h cadence; spot-check one mid-June window against an archive convertToAssets read.
  • 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_asset with 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_tokens is 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.

Private documentation. creddit.xyz