個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Structured credit on Solana: senior/junior tranches over a simulated lending pool, with an on-chain loss waterfall and operator-recorded loss events. Devnet demo.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Structured credit on Solana: a deposit into a lending pool is sliced into a senior
tranche with a capped, predictable yield and a junior tranche that takes the residual
yield and absorbs losses first. A separate loss-protection market will let anyone buy or
sell cover against a loss event in a specific pool, settled automatically on chain.
The lending pool in this repository is a simulation: yield accrues at a configured rate on a
scaled model clock, and loss events are recorded by the pool operator. Integration with
external lending protocols (Kamino, MarginFi, …) is out of scope.檢視完整 README ↗Structured credit on Solana: a deposit into a lending pool is sliced into a senior tranche with a capped, predictable yield and a junior tranche that takes the residual yield and absorbs losses first. A separate loss-protection market lets anyone buy or sell cover against a loss event in a specific pool, settled automatically on chain.
The lending pool in this repository is a simulation: yield accrues at a configured rate on a scaled model clock, and loss events are recorded by the pool operator. Integration with external lending protocols (Kamino, MarginFi, …) is out of scope.
Status — v0.2.0: pool, tranches, yield, loss waterfall and the protection market are
live on devnet with a web app and an operator sheet. The "what if" cabinet (v0.3.0) is
next; its screen in the web app still runs on mock data.
assets / supply, 1:1 while the tranche is empty).yield_rate. A performance fee goes to the operator's
treasury, senior gets at most senior_rate on its assets, junior takes everything left
(including rounding remainders).L hits junior first; senior loses only L − junior once junior is wiped out.
Every loss is written as an immutable LossEvent account (ts, model time, bps, amount,
junior/senior split, assets before) — the loss history is read from chain, no server.min_junior_bps of pool assets
(the junior floor); the first deposit into an empty pool must therefore be junior.assets == senior_assets + junior_assets == vault.amount.
Yield is minted into the vault, losses are burned from it.accrue. Time is a model clock:
model_time += elapsed × time_scale. The demo pool runs at 43 200×, so one minute on
chain is 30 model days — enough to watch NAV move during a demo.All amounts are integers in micro-units (u64 on chain, bigint in TypeScript); products go
through u128; everything is checked arithmetic.
| Site | https://trancheyield.github.io/Wash-App/ — landing page (GitHub Pages, built from main) |
| Web app | https://trancheyield.github.io/Wash-App/app/ |
| Program | 2Yq39tVgTH5e8be8YdssyhvM6339f2WG6QweNmxGpBbf (SBPFv0, upgradeable) |
| Pool 0 | yield 8 %/yr · senior 5 %/yr · junior floor 20 % · fee 10 % · clock 43 200× |
| Pool 0 cover | premium 2 %/yr of notional · trigger 1 % loss · fee 10 % of premium |
| Faucet | 1 000 WUSD per request from the pool page (cap per call in Config) |
fixtures/params.json is the single source of the demo parameters for demo:init, the
program tests and the design brief.
programs/washapp Anchor 1.2 program: init_config, faucet, create_pool, accrue, deposit,
redeem, record_loss, init_protection, provide_protection,
withdraw_protection, buy_protection, settle_protection,
expire_protection; pure waterfall and cover math in src/math.rs
packages/chain Codama-generated client on @solana/kit, PDAs, account readers,
instruction builders (each with a round-trip decode test)
packages/shared TypeScript mirror of the waterfall math, micro-unit formatting, Zod schemas
apps/web React + Vite SPA: pool page, deposit/redeem, loss sheet with operator
panel, protection market (live on devnet); position screen (mock data, M3)
apps/landing static landing page served at the site root (no build)
tools/demo devnet init and the scripted end-to-end scenario
fixtures/ evidence generated by the Rust tests and the scenario (see below)
scripts/ WSL build/deploy wrappers and the trace sweep run in CI
packageManager is pinned).1.97.1 (rust-toolchain.toml), Agave / cargo-build-sbf 3.1.10,
Anchor CLI 1.2.0 — only needed to emit the IDL. On Windows the on-chain toolchain runs in WSL.Copy .env.example to .env. SOLANA_RPC_URL and WASH_KEYS_DIR are read by the demo tools
and the deploy script; VITE_* by the web app. The operator, deployer and program keypairs live
in WASH_KEYS_DIR, outside the repository.
pnpm install
pnpm gate # biome + tsc + vitest across the workspace
pnpm dev # web on :5173
pnpm codama # IDL → packages/chain/src/generated (after wsl-build.sh idl)
pnpm demo:init # devnet: config, demo mint, treasury and pool 0 (idempotent)
pnpm demo:scenario # devnet: fresh buyer and seller → deposits → cover sold and bought → 60 s →
# loss → settle → expire → redeem → withdraw
On-chain, from PowerShell into WSL:
wsl.exe -e bash <repo>/scripts/wsl-build.sh gate # fmt-check, clippy, build-sbf, cargo test
wsl.exe -e bash <repo>/scripts/wsl-build.sh idl # anchor build for the IDL only
wsl.exe -e bash <repo>/scripts/wsl-build.sh fixtures # regenerate fixtures/*.json from the tests
wsl.exe -e bash <repo>/scripts/wsl-deploy.sh deploy # devnet deploy or upgrade, then verify
wsl.exe -e bash <repo>/scripts/wsl-deploy.sh verify # bytecode on chain == local .so, e_flags 0x0
The network artifact is built with cargo-build-sbf (SBPFv0 — Agave 3.1.10 on devnet does not
execute v3 bytecode). anchor build is used only to emit the IDL and overwrites the .so with a
v3 build, so build-sbf always follows it.
The site is static: there is no server, every number on it is read from chain in the browser.
.github/workflows/pages.yml runs on every push to main (or by hand from Actions → Pages →
Run workflow) and publishes one artifact of two folders:
https://<owner>.github.io/<repo>/ apps/landing — static landing page, copied as is
https://<owner>.github.io/<repo>/app/ apps/web — the app, built with base `/<repo>/app/`
apps/landing has no build and no dependencies (index.html, styles.css, assets/); every
figure on it comes from this README and fixtures/demo-run.json. Its og:image is an absolute
URL to the project site — change it together with the domain.
One-time setup in the repository:
Settings → Pages → Source: GitHub Actions (not "Deploy from a branch" — the branch root
has no index.html).
Optionally, Settings → Secrets and variables → Actions → Variables:
| Variable | Unset means |
|---|---|
VITE_SOLANA_RPC_URL | public https://api.devnet.solana.com |
VITE_WASH_DEFAULT_POOL | pool 0 |
VITE_SOLANA_CHAIN | solana:devnet (wallet-standard chain id) |
These are variables, not secrets: every VITE_* value is readable in the published
bundle. An RPC URL with a key is public once it is here. Helius Free (October 2026) cannot
lock a key to a domain, so use a key from a separate account that serves nothing else, and
test any provider's origin lock with curl -H "Origin: https://example.com" before relying
on it. Re-run the workflow after changing a variable.
Notes:
pnpm build: it reads the repo-root .env and would bake its RPC key
into the bundle. CI has no .env./Wash-App) comes from actions/configure-pages and the app is built under
<site path>/app/, so a custom domain needs no code change in the app. Set the domain in Settings → Pages only — a CNAME file in the repository is
ignored when Pages deploys from a workflow.index.html into every route of the default pool
(/app/pool/<id>/…, /app/me, /app/operator), so a direct link or a reload answers 200.
Any other path — another pool id, or a link from before the app moved under /app/ — renders
the app through the site's root 404.html with status 404; the app moves an old link below
/app/ before the router reads it.Run from PowerShell into WSL, with SOLANA_RPC_URL and WASH_KEYS_DIR set in .env:
wsl.exe -e bash <repo>/scripts/wsl-build.sh build-sbf # SBPFv0 artifact in target/deploy
wsl.exe -e bash <repo>/scripts/wsl-deploy.sh deploy # first deploy or upgrade, then verify
wsl.exe -e bash <repo>/scripts/wsl-deploy.sh status # program account and deployer balance
pnpm demo:init # Config, mint, treasury, pool 0, its cover market
washapp-keypair.json) fixes the address and must match declare_id!;
the deployer keypair is the upgrade authority. Both live in WASH_KEYS_DIR; the deploy
script creates the deployer if it is missing.deploy refuses to start until the deployer can pay: a first deploy needs rent for
ProgramData (.so × 1.5, room for upgrades) plus a buffer of the .so size; an upgrade needs
only the buffer. The buffer is refunded after the write. The script prints the exact amounts.deploy ends with verify: the bytecode on chain must equal target/deploy/washapp.so byte
for byte and be SBPFv0.wsl-build.sh idl, then build-sbf, then pnpm codama) and
push — Pages rebuilds from main.declare_id! (bytecode) and in the generated client (pnpm codama), which the site and the
tools both use. Set the new keypair, update declare_id! and WASH_PROGRAM_ID in .env,
then idl → build-sbf → pnpm codama → deploy, and push.Program tests run the real .so under mollusk-svm in cargo test: unit tests on
math.rs with proptest invariants, one test file per instruction, a compute-unit gate
(every instruction < 200 000 CU; current peak 40 713 for record_loss), and 100 seeded random
sequences of deposit/redeem/accrue/loss that check the vault invariant and the loss split byte
for byte after every step.
fixtures/ holds what the Rust side generated so the TypeScript side can be checked against
the program rather than against itself:
waterfall.json — 152 cases from math.rs; packages/shared must reproduce them exactly.pda.json — 30 PDA derivations at the edges of every numeric seed.accounts/m0.json — raw account bytes from the SVM before and after a 15 % loss; the readers
decode them in vitest.demo-run.json — the last demo:scenario run on devnet: per-step timings (signature →
confirmed → change visible in RPC), the loss waterfall checked against the on-chain event, and
the protection checks — premiums against the mirror, the buyer's balance after settle
(+notional × loss), the expired contract's released reservation, the seller's result.Guard tests fail cargo test when a fixture is stale, and vitest fails when a fixture breaks a
budget (scenario ≤ 180 s, each step ≤ 10 s) or records a failed check. The last run: 97.4 s end
to end over 16 transactions, slowest step 4.2 s; settle paid 150.00 on a 1,000 WUSD notional for a
15 % loss, to the unit.
CI (.github/workflows/ci.yml) runs the Rust gate on the SBPFv0 artifact, the Node gate, and a
sweep for keys and machine-specific traces over the whole history.
v0.1.0 — pool, tranches, yield, loss waterfall; pool page, deposit/redeem, operator loss
sheet; devnet scenario.v0.2.0 — protection market: sellers fund a cover pool, buyers pay a premium for cover
against a loss above a trigger, settlement pays out from the cover pool when a loss is
recorded; landing page at the site root. This release.v0.3.0 — position cabinet with a "what if the pool loses X %" forecast that must match the
program to one micro-unit.Licensed under the Apache License, Version 2.0.