agentsclimarketplace

Scallop portfolio analytics

Skill scallop-io/scallop-skills/skills/scallop-portfolio-analytics

Aggregate user positions and protocol TVL via the Scallop TS SDK portfolio query. Use when user says "portfolio", "TVL", "total value locked", "lendings", "user dashboard", "all obligations", "net worth", "aggregate positions", "user portfolio", "protocol stats", or asks for a single view across supplies + obligations + veSCA.From its SKILL.md

Install
npx -y skills add scallop-io/scallop-skills --skill scallop-portfolio-analytics

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.2 KB, as published. Nobody here has run it

Portfolio Analytics

Aggregate every Scallop position a user holds — supplied assets, all obligations (with debts + collaterals + USD values), spool stakes, veSCA — into a single view. Plus protocol-level TVL.

Overview

ScallopQuery exposes three composite read methods that fan out across all of a user's positions, plus a protocol-wide TVL view. The assembly logic lives in buildUserPortfolio in src/services/query/portfolioCalculations.ts; the I/O entry points are on src/models/scallopQuery/index.ts — call them on the query instance, not via internal subpaths.

ScallopQuery methodReturns
getUserPortfolio({ walletAddress?, indexer? })Lendings + obligation accounts (as borrowings) + pending rewards + veSCAs, all priced
getLendings(poolCoinNames?, ownerAddress?, args?)Just the supplied positions (cheaper)
getTvl({ indexer? })Protocol-wide TVL (supplyValue, borrowValue, totalValue, plus the lending/collateral split)

The Python SDK (sui-scallop-sdk >= 0.3.0a1) wraps the same views: query.get_user_portfolio(address), query.get_lendings(address), query.get_tvl() — the examples below are TypeScript, but you are not locked to it. All TS shapes on this page were verified against @scallop-io/sui-scallop-sdk v4.3.0.

Full User Portfolio

import { Scallop } from '@scallop-io/sui-scallop-sdk';

const scallopSDK = new Scallop({ networkType: 'mainnet' });
await scallopSDK.init();
const query = scallopSDK.client.query;

const portfolio = await query.getUserPortfolio({ walletAddress: '0xUSER' });

// Real shape (abridged — see buildUserPortfolio in
// src/services/query/portfolioCalculations.ts for the full structure):
// {
//   ...totals,
//   lendings: [
//     {
//       coinName, symbol, coinType, coinPrice, coinDecimals,
//       suppliedCoin, suppliedValue, stakedCoin,
//       supplyApr, supplyApy, incentiveApr,
//     }
//   ],
//   borrowings: [                       // note: NOT "obligations"
//     {
//       obligationId,
//       totalDebtsInUsd, totalCollateralInUsd, riskLevel,
//       availableCollateralInUsd, totalUnhealthyCollateralInUsd,
//       collaterals:    [ { coinName, depositedCoin, depositedValueInUsd, ... } ],
//       borrowedPools:  [ { coinName, borrowedCoin, borrowedValueInUsd,
//                           borrowApr, borrowApy, incentiveInfos, ... } ],
//     }
//   ],
//   pendingRewards: { lendings: [...], borrowIncentives: [...] },
//   veScas: [ { veScaKey, lockedScaInCoin, lockedScaInUsd, currentVeScaBalance,
//               remainingLockPeriodInDays, unlockAt /* epoch ms */ } ],
// }

Render a Dashboard

console.log('=== Lending Positions ===');
for (const p of portfolio.lendings) {
  if (!p.suppliedCoin) continue;
  console.log(
    `${p.symbol.padEnd(8)} supplied=${p.suppliedCoin}` +
    ` ($${p.suppliedValue.toFixed(2)})  APY=${(p.supplyApy * 100).toFixed(2)}%`
  );
}

console.log('\n=== Obligations ===');
for (const ob of portfolio.borrowings) {
  const flag = ob.riskLevel >= 1 ? 'LIQUIDATABLE'
            : ob.riskLevel >= 0.8 ? 'AT RISK'
            : 'OK';
  console.log(
    `${ob.obligationId.slice(0, 10)}...  risk=${ob.riskLevel.toFixed(3)} ` +
    `debt=$${ob.totalDebtsInUsd.toFixed(2)} ` +
    `coll=$${ob.totalCollateralInUsd.toFixed(2)} ${flag}`
  );
}

for (const v of portfolio.veScas ?? []) {
  console.log('\n=== veSCA ===');
  console.log(`Locked: ${v.lockedScaInCoin} SCA`);
  console.log(`Voting power: ${v.currentVeScaBalance}`);
  // unlockAt is already in milliseconds — do not multiply by 1000
  console.log(`Unlocks: ${new Date(v.unlockAt).toISOString()}`);
}

Lightweight: Lendings Only

When you don't need obligation/veSCA data (e.g. a yield aggregator), skip the expensive parts:

// First positional arg is poolCoinNames (omit for "all"), second is owner address.
const lendings = await query.getLendings(undefined, '0xUSER');
const totalSupplied = Object.values(lendings)
  .filter((l): l is NonNullable<typeof l> => !!l)
  .reduce((sum, l) => sum + l.suppliedValue, 0);
console.log(`Total supplied: $${totalSupplied.toFixed(2)}`);

Protocol TVL

const tvl = await query.getTvl();
console.log(`Supply (lending + collateral): $${tvl.supplyValue}`);
console.log(`  - lending:    $${tvl.supplyLendingValue}`);
console.log(`  - collateral: $${tvl.supplyCollateralValue}`);
console.log(`Borrow: $${tvl.borrowValue}`);
console.log(`TVL:    $${tvl.totalValue}`);

When you pass { indexer: true } the response is augmented with *ChangeRatio fields (e.g. supplyValueChangeRatio) sourced from the Scallop indexer. Useful as a denominator when comparing a user's exposure against protocol size, or for an indexer that snapshots TVL on a schedule.

Composition with Other Skills

NeedSkill
Borrow/supply caps per assetmarket-limits
Per-obligation health, liquidation thresholdsobligation-manager
Find liquidatable positions in the returned obligations listliquidation-helper
veSCA detail (rewards, lock extensions)vesca

Performance Notes

getUserPortfolio is a fan-out: market data + all owned obligations + all market coins + veSCA, each priced. Expect several seconds on a cold call. For dashboards:

  • Cache the response per address with a TTL (30–60s for read-only UIs).
  • Stream individual sections (getLendings then per-obligation reads) when you only need part of the data.
  • For server-side monitors, prefer a single getUserPortfolio per tick over many discrete calls — fewer RPC round-trips.
  • Pass { indexer: true } to getUserPortfolio / getTvl to take the indexer fast-path; it falls back to on-chain reads automatically if the indexer is unavailable.

Error Handling

  • The veScas array is empty when the user holds no veSCA — not an error.
  • Empty positions are filtered out (lendings only includes entries with positive withdrawable amounts; borrowings only includes obligations with debt or collateral).
  • RPC failures bubble up as the underlying error; wrap in a retry with exponential backoff for keeper code.

References

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.