@hubra-labs/rasol-max
v0.5.4
Published
TypeScript SDK for the raSOL max leveraged-staking adapter on Solana mainnet — read NAV / leverage / APY, build crank + deposit + withdraw instructions.
Maintainers
Readme
@hubra-labs/rasol-max
TypeScript SDK for the raSOL max leveraged-staking adapter on Solana mainnet.
Reads NAV / leverage / borrow APR / strategy APY, builds the
permissionless crank instruction, and builds deposit + withdraw
instructions for the Voltr /
Save Finance / Sanctum
stack the adapter sits on top of.
Pilot scope
0.3.xtracks the v2 mainnet pilot. The on-chain program is live but the upgrade-authority is still a single hot key (Squads multisig rotation pending). Do not point production deposits at this SDK beyond the pilot TVL cap without coordinating with the Hubra team.
Install
npm install @hubra-labs/rasol-max
# peer deps:
npm install @solana/kit @solana-program/token @voltr/vault-sdkQuickstart
import { createSolanaRpc } from "@solana/kit";
import { RasolMaxClient } from "@hubra-labs/rasol-max";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const client = new RasolMaxClient({ rpc });
// 1. Read strategy state
const data = await client.getStrategyData();
console.log({
tvlRasol: data.tvlRasol,
leverageBps: data.leverageBps,
borrowAprBps: data.borrowAprBps,
strategyApyBps: data.strategyApyBps,
paused: data.paused,
});
// 2. Crank (permissionless NAV refresh)
const crankIx = await client.buildCrankIx();
// sign + send via your tx transport
// 3. Deposit (raSOL → raSOLmax LP)
const depositIxs = await client.buildDeposit({
owner: yourSigner, // TransactionSigner from @solana/kit
amount: 100_000_000n, // 0.1 raSOL
});
// 4. Withdraw — dual-path router
const { route, instructions } = await client.buildWithdraw({
owner: yourSigner,
lpAmount: 50_000_000n,
});
console.log(route.path); // "vault-idle" | "flash-bracket"
// 5. Manager — lever up
const lever = await client.manager.depositLeveraged({
manager: managerSigner,
targetLeverageBps: 27_000, // 2.7×
// rasolIn defaults to 100% of *incidental* vault idle (pending user
// deposits). v2 keeps no buffer — drain the inbox fully.
});
// 6. Manager — delever
const delever = await client.manager.rebalanceDown({
manager: managerSigner,
// targetLeverageBps omitted → full unwind
});
// 7. crank + Voltr-cache resync (manager-signed)
const resyncIxs = await client.crankAndResync({ manager: managerSigner });Status
| Surface | Status | Notes |
|---|---|---|
| getStrategyData() | ✅ | NAV, leverage, borrow APR, supply APR, simple APY. |
| buildCrankIx() | ✅ | Permissionless. Live on mainnet program LST3EutZgtbsLp3RqagSfjTB6kB8BwEQjexEkqBZiRr since 2026-05-24. |
| buildDeposit() | ✅ | Voltr.depositVault with an optional crank prefix. |
| quoteWithdraw() | ✅ | Returns the routing decision without building the tx. |
| buildWithdraw() — vault-idle path | ✅ | When vault idle covers the payout, returns 1 ix (Voltr.instantWithdrawVault). |
| buildWithdraw() — flash-bracket path | ✅ | 8 ixs: cb_limit + cb_price + create_wsol + Save.flash_borrow + adapter.prepare_withdraw + Save.flash_repay + Voltr.instantWithdrawStrategy + close_wsol. Sanctum INF exact-out refund swap sized to satisfy C1 cap. Returns the adapter ALT to compress with. |
| crankAndResync({ manager }) | ✅ | Manager-signed bundle: crank + Voltr.depositStrategy(0). Refreshes Save state AND Voltr's cached strategy.positionValue in one tx. |
| client.manager.depositLeveraged(...) | ✅ | Manager-signed lever-up cycle. Auto-quotes Sanctum router SOL→raSOL, builds the 5- or 6-ix flash bracket, returns adapter ALT. |
| client.manager.rebalanceDown(...) | ✅ | Manager-signed deleverage. Full or partial (via targetLeverageBps). raSOL→SOL routes through Sanctum INF (unbounded, deep liquidity) — no reserve cap. |
Architecture
The adapter (Rust, Anchor) lives at
Hubra-labs/rasol-max-adapter.
This SDK wraps the on-chain surface in three pieces:
- Decoders (
src/decode/) — byte-offset readers for Save reserve / Save obligation / SPL stake pool /StrategyPDA accounts. Mirrors the Rustutils/nav.rsoffsets exactly; if Save / SPL stake-pool upgrades shift layout, bump the SDK. - Read path (
src/read/) — an off-chain mirror ofcompute_nav_from_amounts+ a simple APY model. Five fixture tests pin the NAV math against hand-computed values. - Ix builders (
src/ix/) — assembleInstructions for crank, deposit, and withdraw (dual-path). Account orderings mirror the on-chainAccountsstructs and theraSOLmax-new.jsonlive binding.
Dual-path withdraw
On-chain, the redemption is a single 4-ix flash bracket. The SDK opportunistically routes around it when incidental vault idle is enough to cover the payout:
client.buildWithdraw({ owner, lpAmount }):
1. payout = lpAmount × NAV / lpSupply (off-chain compute)
2. vault_idle = vault_asset_idle_ata.amount (on-chain read)
3. if payout ≤ vault_idle:
→ Voltr.instantWithdrawVault (1 ix, no flash fee)
else:
→ 8-ix flash bracket (Save flash + adapter.prepare_withdraw +
Sanctum INF exact-out refund swap + Voltr.instantWithdrawStrategy)"Incidental" because v2's design is no-idle-buffer — between a user
deposit and the manager bot's next deposit_leveraged cycle, vault
idle is non-zero. Once the lever cycle runs, vault idle → 0 and
every withdraw falls through to the bracket path.
ALT compression
The flash-bracket path returns the adapter's address-lookup-table
address in result.lookupTable. Compress your transaction message
with it before signing:
import {
compressTransactionMessageUsingAddressLookupTables,
fetchAddressLookupTable,
} from "@solana/kit";
const { instructions, lookupTable } = await client.buildWithdraw({ owner, lpAmount });
const alt = await fetchAddressLookupTable(rpc, lookupTable!);
const message = compressTransactionMessageUsingAddressLookupTables(
baseMessage,
{ [lookupTable!]: alt.data.addresses },
);Without ALT compression the tx exceeds the 1232-byte transaction size
limit. The SDK does not auto-extend the ALT — if you've changed
the adapter binding or added accounts, the on-chain ALT may need an
admin-signed extend before bracket withdrawals work. See
scripts/src/withdraw.ts
for the reference extend flow.
NAV mirror
The off-chain computeNav() is a literal port of nav.rs::compute_nav_from_amounts:
NAV (raSOL) = strategy_collateral_ata.amount
+ strategy_ctoken_ata.amount × cToken→raSOL
+ obligation.deposited_amount × cToken→raSOL
− obligation.borrowed_sol × SOL→raSOLvault_strategy_asset_ata is deliberately excluded (Voltr-side
transit ATA; counting it double-counts in-flight withdraw payouts).
Throws InsolventError when debt > assets (mirrors the on-chain
H-C guard).
Versioning
| SDK | Adapter program | Notes |
|---|---|---|
| 0.3.x | LST3EutZgtbsLp3RqagSfjTB6kB8BwEQjexEkqBZiRr (crank-upgrade binary) | v2 pilot |
Major version bumps on any adapter program-ID rotation. Minor bumps for new ixs / new SDK surface. Patch for read-path / typecode fixes.
License
Apache-2.0.
