npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lodz/seam-router

v0.2.0

Published

Deterministic capital routing across BTC yield seams on Solana. Enforces per-posture ceilings on counterparty-funded, emission-funded and uncorrelated LP exposure, routes on trailing rates rather than spot, and never treats an unmeasured impermanent loss

Readme

@lodz/seam-router

Deterministic capital routing across BTC yield seams on Solana.

A seam is one place yield comes from: a lending market, an LP position, a market-making vault. This package decides how much capital belongs in each of them, when it is worth trading to correct a drifted book, and how much realized yield came from fees, from token emissions, or from the losing side of somebody else's trade.

Every function is pure. No network, no filesystem, no clock, no random source. Timestamps arrive as arguments, so the same input always produces the same plan. That is what lets an off-chain planner and an on-chain program reach the same answer.

Install

npm install @lodz/seam-router

Node 18 or newer. Ships ESM and CommonJS builds with type declarations for both.

What the measured market looks like

The ceilings and gates below are not generic risk hygiene. Each one exists because a survey of every BTC-related pool on Solana turned up a specific case that would otherwise produce a dishonest plan:

| Measured fact | What the router does about it | | --- | --- | | A vault advertising 104 percent has 10,927 USD of capacity | Hard liquidity floor, default 100,000 USD | | One pool's history contains a day reading 74,187 percent | Spot rates are never routed on; only trailing windows are | | The only real yield is LP fees, and nobody has measured the impermanent loss against them | Unmeasured loss is disclosed, never assumed to be zero | | The highest rate on the market, 214 percent, is funded by trader losses | Third yield source, counterparty, with its own ceiling | | Lending rates are effectively zero because nothing is being borrowed | Zero-rate seams are excluded regardless of TVL | | The largest BTC representation is bridged through a bridge that was exploited | Custody hop limit per posture | | One data source can be stale while another is current | A seam two sources disagree about is withheld, not published |

Why a router rather than a sort

Ranking seams by headline rate produces a book that fills up with whatever is currently printing the largest number, and on this market that number is funded by other traders losing money. So the ceilings are hard limits, expressed in basis points of total capital and varying by posture:

| Ceiling | conservative | balanced | aggressive | | --- | --- | --- | --- | | Counterparty-funded yield | 0 | 0 | 3000 | | Uncorrelated LP exposure | 1000 | 3500 | 6000 | | Emission-funded yield | 2000 | 5000 | 10000 | | Single venue | 3000 | 4000 | 5000 | | Single asset | 4000 | 6000 | 7000 | | Custody hops | 1 | 2 | 3 | | Risk tier low / medium / high | 10000 / 3500 / 0 | 10000 / 7000 / 2000 | 10000 / 10000 / 5000 | | Holds unmeasured impermanent loss | no | yes | yes | | Liquidity buffer | 500 | 250 | 0 |

The emissions ceiling is kept even though the measured market currently has zero emission programmes. It has had them before and will again, and a ceiling that only appears once the exposure exists is a ceiling nobody reviewed.

The first two rows are not this package's argument. They are transcribed from RiskProfile::max_emissions_bps and RiskProfile::max_counterparty_bps in the lodz-vault program, which is deployed on devnet and rejects an allocation past either one with EmissionsAllocationExceeded or CounterpartyAllocationExceeded. Read the aggressive emissions row as written: that posture permits a book funded entirely by emissions. Earlier versions of this package published 1500 / 3500 / 6000 there, which understated what a stope can actually hold, and understating a ceiling is not caution. The remaining rows are defaults and can be overridden per call; overriding the first two upward only produces allocations the program refuses.

Usage

Plan an allocation

import { planAllocation } from "@lodz/seam-router";
import type { Seam } from "@lodz/seam-router";

// The catalogue is injected. This package does not ship one: it comes from the indexer.
const seams: Seam[] = [
  {
    id: "S2",
    name: "Orca cbBTC-USDC",
    venue: "Orca",
    asset: "cbBTC",
    assetMint: "cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij",
    kind: "lp",
    yieldKind: "sustainable",
    apyBps: 1_500,          // spot, never routed on
    apy7dBps: 1_546,
    apy30dBps: 2_201,
    apy90dMedianBps: 2_850,
    tvlUsd: 6_319_470,
    allocationBps: 0,
    emissionToken: null,
    emissionEndsAt: null,
    riskTier: "medium",
    belowLiquidityFloor: false,
    sourceDivergence: false,
    ilEstimateBps: null,    // unmeasured, which is not the same as zero
    pairVolatilityClass: "uncorrelated",
    wrapHops: 1,
    sourceUrl: "https://yields.llama.fi/chart/2651188f-6b05-473e-9cfb-977a4ad094ba",
    updatedAt: "2026-08-15T00:00:00.000Z",
  },
  // ...more seams
];

const plan = planAllocation({
  seams,
  stope: "balanced",
  capitalBtc: 12.5,
  btcPriceUsd: 100_000,
  now: "2026-08-15T00:00:00.000Z",
});

btcPriceUsd is required. The liquidity floor and the share-of-pool ceiling are denominated in USD and cannot be enforced without it.

sum(plan.allocations[].allocationBps) + plan.idleBps is exactly 10000 for every input. When the ceilings make full deployment impossible, the surplus stays in idleBps rather than being forced into a seam already at its limit. idleBps is never smaller than the posture's liquidity buffer. Satoshis are split exactly too: the per-seam capitalSats values plus idleSats add up to capitalSats with no remainder lost.

Routing the measured catalogue for three postures produces three genuinely different books:

profile        counterparty  ceiling   status
conservative       0 bps        0   X1 refused outright
balanced           0 bps        0   X1 refused outright
aggressive      1367 bps     3000   under ceiling

Each allocation carries a bindingCap naming the ceiling that stopped it from growing, and every seam that received nothing appears in plan.excluded with a reason.

Rates: trailing windows only

planAllocation never routes on apyBps. It picks the lowest available trailing window and reports which one in apySource.

Windows disagree by a lot here. One pool reads 15.46 percent over seven days, 22.01 over thirty and 28.50 as a ninety day median. Picking the highest would let the planner justify a position with a rate the pool is no longer paying, so the lowest is used: it cannot overstate, and no single inflated window can carry a seam into the book alone.

A seam with no trailing figure at all is excluded (no-durable-apy) rather than routed on its spot rate. A seam carrying sourceDivergence is excluded too: when two independent feeds disagree about a rate, at least one of them is stale, and publishing the disputed number as settled is how a stale feed becomes a promise. belowLiquidityFloor excludes a seam on the indexer's own judgement even when its reported TVL clears the numeric floor. When the spot reading exceeds the trailing week by more than spotRejectMultiple (default 5), spotRejected is set on the allocation for disclosure. Feeding a seam the documented 74,187 percent artifact day moves its allocation by exactly zero basis points.

Impermanent loss: disclosed, never invented

For an LP seam, ilEstimateBps of null means nobody has measured the loss. It does not mean zero, and this package will not pretend otherwise:

  • netApyBps is grossApyBps - ilEstimateBps when the loss is measured, and null when it is not;
  • plan.blendedNetApyBps is null if any held seam has an unmeasured loss, because a net figure that silently treats an unmeasured cost as zero is the overstatement this package exists to prevent;
  • plan.ilUnknownBps reports how much capital sits in such seams;
  • ranking applies a discount for unmeasured loss, which is a scoring penalty and not a substitute figure.

conservative refuses these seams outright. On the measured market that removes every LP position, which is the honest answer: the only real yield available is LP fees, and the cost that offsets them has not been measured.

Plan a rebalance

import { planRebalance } from "@lodz/seam-router";

const rebalance = planRebalance({
  current,
  target,
  minDeltaBps: 250,
  gasCostBps: 6,
  apyBpsBySeamId,
  horizonDays: 90,
  forcedExitSeamIds: ["X1"],
});

Two filters run before anything executes. A per-seam threshold drops deltas too small to bother with. Then, when APY data and a horizon are supplied, a spread test drops moves whose expected gain does not clear the execution cost over that horizon:

required spread (bps) = gasCostBps * 365 / horizonDays

forcedExitSeamIds overrides that test. A position that breached a ceiling has to come down whether or not the move is accretive, and it almost never is: the seam that breached is usually the highest-yielding one in the book. Without the override, the cost test would quietly make every risk ceiling advisory. The move is still reported with its real, negative expectedGainBps rather than dressed up.

Everything filtered out appears in skipped with a reason: below-min-delta, cost-exceeds-gain, or unmatched. sum(resulting) always equals sum(current); a rebalance moves capital, it never creates or destroys it.

Aggregate realized yield

import { aggregateRealizedYield } from "@lodz/seam-router";

const report = aggregateRealizedYield(entries, { from, to });

report.sustainableShareBps;   // 4906
report.emissionsShareBps;     // 412
report.counterpartyShareBps;  // 4682

The report never collapses the three sources into one headline number. bySeam and byVenue carry all three at every level, and the shares always close to exactly 10000. A combined total says nothing about whether the yield survives the end of an emission schedule or the month the traders on the other side stop losing.

Size a keeper bond

import { keeperBondRequirement } from "@lodz/seam-router";

const bond = keeperBondRequirement(12.5, {
  btcPriceUsd: 100_000,
  lodzPriceUsd: 0.25,
  bondRateBps: 200,
  minBondUsd: 5_000,
  maxBondUsd: 2_000_000,
  stope: "balanced",
  profileMultiplierBps: { conservative: 8_000, balanced: 10_000, aggressive: 15_000 },
  slashConditions: [
    { code: "allocation-cap-breach", description: "Submitted an allocation that breaches a vault ceiling", slashBps: 2_500 },
    { code: "spot-rate-routing", description: "Routed capital on a spot rate the planner had rejected", slashBps: 2_000 },
  ],
});

Slash conditions are supplied rather than defined here. They have to match the on-chain program exactly, and the program is the authority.

Risk

Capital routed through this package is exposed to bridge risk, custody risk and the protocol risk of every venue it touches. Emission-funded yield ends on a schedule. Counterparty-funded yield is paid by somebody else's losses and stops when they stop losing. Impermanent loss on an LP position can exceed the fees it collects, and on this market it is largely unmeasured. Nothing here is free of risk or assured, and no computation in this package should be read as a promise about future returns. Wrapped and bridged representations of BTC are not bitcoin itself and carry the risk of whatever issues them.

API

| Export | Purpose | | --- | --- | | planAllocation(input) | Target allocation per seam under the posture's ceilings | | planRebalance(input) | Moves to execute, plus the ones deliberately skipped | | aggregateRealizedYield(entries, window?) | Realized yield split by source, seam, venue and token | | keeperBondRequirement(capitalBtc, params) | Bond size in USD and LODZ, with priced slash conditions | | resolveConstraints(input?) | Merge overrides onto the defaults and validate | | DEFAULT_CONSTRAINTS | The default ceiling set | | SeamRouterError | Thrown for every rejected input, with a code |

Domain types (Seam, YieldKind, RiskTier, StopeProfile, PairVolatilityClass) are declared here and kept shape-identical to @lodz/assay-engine, which owns the canonical declarations.

Development

npm run typecheck   # tsc --noEmit
npm run build       # ESM and CommonJS into dist/
npm test            # node --test
npm run demo        # the worked example in examples/scenario.ts

License

MIT