個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Fixed income for onchain treasuries on Solana: deposit stablecoins once, get a dated schedule of guaranteed inflows instead of a floating APY. Ladder across dates in one signature, roll policy, runway report.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Fixed income for onchain treasuries on Solana. A treasury deposits stablecoins once
and gets a dated schedule of guaranteed inflows instead of a floating APY.
The novelty isn't splitting principal from yield — Exponent already does that — it's
the treasury layer on top: laddering one amount across several dates in a single
signature, a roll policy, and a runway report showing how far the inflows reach.檢視完整 README ↗Fixed income for onchain treasuries on Solana. A treasury deposits stablecoins once and gets a dated schedule of guaranteed inflows instead of a floating APY.
The novelty isn't splitting principal from yield — Exponent already does that — it's the treasury layer on top: laddering one amount across several dates in a single signature, a roll policy, and a runway report showing how far the inflows reach.
Project page: https://runwayladder.github.io/RunwayLadder/ — what the product does,
the terms it comes with, and the figures measured so far.
Live prototype: https://runwayladder.github.io/RunwayLadder/app/ — the dashboard on
mock data, deployed from main by GitHub Actions. It reads no network and says so in
its status bar: the program is not deployed anywhere public yet. Pointed at a validator
(see Build and run), the same dashboard reads the chain and signs.
The treasurer enters an amount and a horizon. The form shows the rate, the operator, and when that rate was set — before signing, not after. One signature opens the ladder and lays the amount across rungs, where each rung is a promise to pay a specific amount on a specific date. The dashboard then reads the ladder back from the chain and draws the inflow chart.
A protocol fee (Market.fee_bps) is taken from the principal at deposit time and
goes to the protocol buffer, so the promise on each rung is made on the working
amount, not the nominal — the form shows both.
When a date matures, the epoch is settled once (settle_epoch): what the yield
source actually returned is compared with what that date promised. A shortfall is
covered from the protocol buffer; whatever the buffer cannot cover becomes a
pro-rata haircut, recorded on the epoch as SettledWithDeficit. A surplus goes to
the same buffer. Then each rung of that date either:
redeem_rung) — at par, or as
RedeemedWithDeficit { amount, promised } with both numbers on the rung; orroll_rung) into the market's furthest date, if the
ladder's roll policy is Roll. Nothing is paid out; the amount goes to work again
in a new rung of the same ladder.The owner switches the roll policy from the dashboard (set_roll_policy); the switch
is read at the moment of a roll. A keeper (apps/keeper) settles matured dates and
rolls opted-in rungs on a timer. It holds no operator key and opens no dates: if the
operator has not opened a later date, the rung waits or its owner redeems it.
The dashboard shows a deficit before it is paid — in the forecast and on the ladder
screen, with the actual amount instead of the promised one — and builds the
redemption history from the closing transactions. Every state change emits an event
(LadderOpened, RungIssued, LadderFunded, EpochSettled, RungRedeemed,
RungRolled, RollPolicySet).
v0.2.0)The ladder lives on its own: rungs mature, get settled, and are either redeemed or
rolled forward without the treasurer — and a shortfall is visible on screen, not in
the logs. Measured on a local validator (pnpm e2e:us1, pnpm e2e:us2):
| Criterion | Budget | Measured |
|---|---|---|
| Ladder across 4 rungs, chart updated | ≤ 15 s | 1 signature, 0.04 s |
| Ladder survives full cycles with no treasurer action | ≥ 2 cycles | 2 cycles, 8 rolls by the keeper, 0 treasurer signatures |
| Network fees of a full cycle (4 rungs + 4 redemptions) | < $0.10 | 45,000 lamports = $0.011 at $250/SOL |
| Charted total = sum of rungs actually redeemed | drift 0 | 0 base units (399,000.169032 both) |
| Principal at par or the deficit marked, on simulated rate paths | 100% of ≥ 200 | 400/400: 303 at par, 97 marked |
| Rungs that fit in one signature | — | 11 (1195 of 1232 bytes) |
Account rent is separate from the fee figure and is not returned today: 0.0093 SOL per 4-date ladder, 0.0019 SOL per roll.
Tests: 341 TypeScript, 104 Rust.
What's not here yet. The yield source is a deterministic adapter, not Kamino (M3), so the 400 rate paths above are simulated: they show the cover rule holds, not that a real Kamino shortfall looks like this. There is no early exit — money is locked until its date. Rent is not returned: closing a finished ladder is planned for M3. No public network deployment yet: the devnet link arrives with M3, and until then the hosted dashboard runs on mock data. Nothing has been audited.
Rung.promised_amount. This is the absence of a path, not a flag
check.RedeemedWithDeficit { amount, promised } and
RolledWithDeficit { amount, promised, into } are separate rung variants, and the
epoch carries SettledWithDeficit { paid, deficit }.settle_epoch), one order. A surplus goes symmetrically into the
same buffer: the rate risk sits with the protocol, not with a third party.realized is principal plus
min(accrued, source reserve), not a formula: the vault is shared by all dates, and
income without tokens behind it would be paid out of someone else's principal.settle_epoch is permissionless; accruing up to now would let whoever picks the
moment of sending decide what each rung gets.roll_rung from a stranger's signer and try to divert it into a rung, or a
ladder, of the stranger's own.u128 for intermediate products, overflow-checks
enabled even in release: an overflow here is a wrong amount, not a panic.fixtures/vectors.json, read by both test suites — the
settlement vectors included.programs/runway-ladder Anchor: market, epochs, ladder, rungs, settlement
packages/math ladder and settlement math — pure, no network, no Solana types
packages/sdk PDAs, instruction builders, account and event decoders, IDL
apps/web treasurer dashboard (served at /app/)
apps/landing project page — static, the Pages root
apps/keeper permissionless settle-and-roll crank
scripts/ WSL build, local validator stand, end-to-end runs
Requires pnpm 9.15 and the Node version in .nvmrc; the program additionally needs a
Rust toolchain inside WSL (anchor-cli 0.32.1, solana-cli 4.2.0).
pnpm install
pnpm gate # lint + typecheck + test
bash scripts/program.sh build # anchor build in WSL, .so copied to target/deploy
bash scripts/program.sh test # program tests on mollusk-svm
pnpm e2e:stand # local validator with the program, a mint, a treasury
pnpm e2e:us1 # end-to-end ladder run, with numbers
pnpm e2e:us2 # two full roll cycles with the keeper (~11 min)
pnpm --filter @runway-ladder/keeper start [--once] # the keeper on its own
Without VITE_RPC_URL and VITE_MARKET the dashboard stays a prototype on mock data
and says so in its status bar — it never shows invented numbers dressed up as onchain
ones. The keeper reads RPC_URL, PROGRAM_ID, KEEPER_MARKET and its own keypair;
it needs SOL for fees and for the rent of each rung it rolls, and no access to the
treasury's funds. All variables are documented in
.env.example.
Rust only in WSL, TypeScript only on Windows: a single node_modules cannot serve
both platforms, so pnpm install is never run inside WSL.
anchor-cli / anchor-lang / @coral-xyz/anchor — all three 0.32.1 · rustc 1.97.1 ·
solana-cli 4.2.0 · mollusk-svm 0.15.0 · pnpm 9.15 + Turborepo 2.10.11 ·
TypeScript 7.0.2 strict · Biome 2.5.9 · Vitest 4.1.11 · Zod 4.4.3 ·
@solana/web3.js 1.98.4.
Work in progress. Nothing is deployed to mainnet and nothing has been audited. Demos do not run against mainnet: today the stand is a plain local validator with the program loaded; a mainnet fork arrives with the Kamino adapter (M3), devnet for a public link.