feat(portfolio): fold Solana staking into get_portfolio_summary (roadmap #2) - #143
Merged
Merged
Conversation
…map #2 section 2) Follow-up to PR #141. Surfaces the SOL-equivalent subtotals from `getSolanaStakingPositions` (Marinade + Jito + native stakes) in the aggregate portfolio summary, paralleling how MarginFi lending and TRON staking are folded in today. ## New types (src/types/index.ts) - `SolanaStakingPositionSlice` — thin projection of the reader's consolidated shape (`marinade`, `jito`, `nativeStakes`, `totalSolEquivalent`). Drops per-reader wrapper metadata the portfolio JSON doesn't need. - `PortfolioCoverage.solanaStaking?: CoverageStatus` — tracks the staking-reader fetch independently of `coverage.solana` and `coverage.marginfi`. Mirror of the tron/tronStaking split: a staking failure doesn't mask a successful balance read. - `PortfolioSummary.solanaStakingUsd?: number` — top-level carve-out, already included in `totalUsd`. Parallels `solanaLendingUsd` and `tronStakingUsd`. - `SolanaPortfolioSlice.staking?` + `stakingNetUsd?` — per-wallet breakdown fields inside the Solana slice. ## Aggregator wiring (src/modules/portfolio/index.ts) - `getSolanaStakingPositions` added to the parallel Promise.all, gated on `solanaAddress` like the MarginFi reader. Graceful- degradation: `.catch(() => null)` flips `errors.solanaStaking = true` without aborting balance + MarginFi reads. - USD conversion uses the SAME SOL price that valued the native-SOL line in `solanaSlice`, via `solanaSlice.native.find(b => b.token === "native").priceUsd`. Guarantees the two USD numbers (SOL-in-wallet vs. staked-SOL) stay consistent + avoids a duplicate price fetch. - The `solanaStakingUsd` carve-out + `breakdown.solana.staking` entry only render when `totalSolEquivalent > 0` (no empty-stake dangling fields on wallets without staking). - `totalUsd` now includes `solanaStakingUsd` alongside `solanaLendingUsd` and `tronStakingUsd`. ## Tests 805/805 pass (was 802; +3 new on `test/solana-portfolio.test.ts`): 1. Happy-path — wallet with mSOL + jitoSOL folds into `breakdown.solana.staking`, `stakingNetUsd`, top-level `solanaStakingUsd`. Verifies totalSolEquivalent math (23 SOL) and USD math (× $100 SOL price = $2300). 2. Failure path — reader throws → `coverage.solanaStaking.errored: true`; `coverage.solana` stays `covered: true`; no `staking` key on `breakdown.solana`; no `solanaStakingUsd` at top level. 3. Zero-holdings path — reader succeeds but returns empty → `coverage.solanaStaking.covered: true`; no `staking` key; no `solanaStakingUsd` (avoids dangling empty fields). ## Reused - `getSolanaStakingPositions` — `src/modules/positions/solana-staking.ts` (shipped in PR #141) - SOL price — extracted from the existing `solanaSlice.native` entry (no new price fetch) ## What's next Closes roadmap item #2. Next per the plan: roadmap #3 (Solana staking writes — Marinade stake/unstake, Jito stake/unstake, native stake delegate/deactivate/withdraw). That's 2-3 PRs per the roadmap's budget. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced Apr 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes roadmap item #2 (section 2 of 2) from
claude-work/HIGH-solana-roadmap.md. Follow-up to PR #141 which shipped the standalone reader +get_solana_staking_positionstool. This PR folds the SOL-equivalent subtotals intoget_portfolio_summary, paralleling how MarginFi lending and TRON staking are surfaced today.Wire-up
SolanaStakingPositionSlice(thin projection of the reader),PortfolioCoverage.solanaStaking,PortfolioSummary.solanaStakingUsd,SolanaPortfolioSlice.staking/stakingNetUsdgetSolanaStakingPositionsadded to thesolanaAddress-gated Promise.all; graceful-degradation via.catch(() => null)mirrors the MarginFi reader's patternsolanaSlice.native.find(b => b.token === "native").priceUsd. No duplicate price fetch; guarantees SOL-in-wallet and staked-SOL use identical SOL pricetotalUsdsolanaStakingUsdalongsidesolanaLendingUsdandtronStakingUsdtotalSolEquivalent > 0— no dangling empty-stake fields on wallets without stakingTests
test/solana-portfolio.test.tsNew cases:
solanaStakingUsd. Verifies math end-to-end through the aggregator.coverage.solanaStaking.errored: true;coverage.solanastillcovered: true(independent split); nostakingkey on the breakdown; no top-level carve-out.coverage.solanaStaking.covered: true; no dangling staking fields anywhere.Test plan
npm run build— clean.npm test— 805/805 pass.get_portfolio_summary({ wallet: <EVM>, solanaAddress: <wallet with staking> })against a wallet that holds mSOL/jitoSOL/native stakes. Confirmbreakdown.solana.staking.totalSolEquivalentmatches cross-checks on explorers (Marinade dashboard for mSOL, Jito dashboard for jitoSOL, solscan for native). ConfirmsolanaStakingUsd+totalUsdinclude the staking USD.Roadmap status after this PR