Owed

Integrate Owed

Tokenized equities rebase on-chain. The multiplier stored in a mint's scaledUiAmountConfig is not always the one the runtime applies, so a client that reads that field reports a number the chain disagrees with. Here is the fix in whatever your stack already speaks. No key, no account, no signup.

The rulenow >= newMultiplierEffectiveTimestamp ? newMultiplier : multiplier
Read the mint accountNo extra account or RPC call: it rides along with the mint you already load
MIT licensedVendor it, fork it. No signup and no key to rotate

1. JavaScript and TypeScript zero dependencies, no key

sdk/owed.mjs reads the mint account from any Solana RPC and applies the rule above. It is a file, not a package: copy it in or vendor it, and it needs no install step.

// sdk/owed.mjs - one file, no dependencies, no API key.
import { getScaledState, getEffectiveMultiplier, toDisplayAmount } from "./sdk/owed.mjs";

// The short form, when you only want the number the chain applies:
const eff = await getEffectiveMultiplier(mint);   // 10 for PPLTx, while the field reads 1

// The form that also tells you whether you were wrong:
const { stored, effective, stale, daysStale, factor } = await getScaledState(mint);
if (stale) {
  console.warn(
    `${mint}: field says ${stored}x, chain applies ${effective}x - ${factor}x off`,
  );
}

// Balances: convert raw base units the way the runtime does, not the way the field does.
const shown = toDisplayAmount(rawBalance, eff);

Run it live before you write any code: node sdk/example.mjs AAPLx prints the stored field, the effective multiplier, how long ago they diverged, and the corrected display balance.

2. On-chain, from another program Rust, CPI-callable

A program cannot call a JavaScript SDK, so the same rule ships as a library your program imports. It walks the account's TLV extension region rather than assuming a fixed offset, which matters: the scaled config sits at byte 275 on one issuer's mints and at byte 575 on another's, and a fixed-offset reader passes every test on the first issuer while silently misreading the second.

// core/src/multiplier.rs - no extra account, no extra RPC call.
use owed_core::multiplier::read_multiplier;

// `data` is the mint account you already load to validate the mint.
let now = Clock::get()?.unix_timestamp;
match read_multiplier(&mint.data.borrow(), now)? {
    // Not a scaled mint: nothing to correct.
    None => {}
    // The runtime applies `effective`. `stored` is the field that traps readers.
    Some(r) if r.stale => {
        let units = r.effective * raw_balance as f64;
        let understated_by_pct = r.gap_pct();
    }
    Some(_) => {}
}

35 tests, run against five real mainnet accounts committed as fixtures, including one that replays every truncation of every fixture - because on-chain a panic aborts the whole transaction.

3. Anything else the published feed

If your stack is neither JS nor Rust, read the feed. It is a plain JSON document over HTTPS with CORS open, a five-minute edge cache, and a published schema that is the contract. No key.

# Every mint whose stored field is not what the runtime applies, worst first.
curl -s https://owed.sithunyein.com/feed/owed-risk.json \
  | jq '.tokens[] | select(.trap.stale) | {symbol, stored: .scaled.state.multiplier,
        applied: .scaled.effectiveMultiplier, error_pct: .trap.gapPct}'
// One entry, as published. The full schema is at /feed/schema.json.
{
  "symbol": "PPLTx",
  "mint": "Xst6eFD4YT6sz9RLMysN9SyvaZWtraSdVJQGu5ZkAme",
  "scaled": {
    "state": { "multiplier": "1", "newMultiplier": "10",
               "newMultiplierEffectiveTimestamp": 1778985000 },
    "effectiveMultiplier": 10
  },
  "trap": { "stale": true, "gapPct": 900, "daysStale": 130 }
}

Machine index: /feed/index.json points at the feed and its schema, so a consumer never has to read documentation to find it. Alerts, when the lane fires, are at /feed/alerts.json.

4. Check your own list before you ship CLI, offline-friendly

The fastest way to know whether this affects you is to measure your own set. Every command below reads public mainnet state and needs no key.

git clone https://github.com/thesithunyein/owed && cd owed

node sdk/example.mjs AAPLx           # any official ticker, one line per fact
node scripts/conformance.mjs --all   # our reader against the runtime, every mint
node scripts/verify-rpc-mechanism.mjs PPLTx # two RPC calls, and they disagree by 10x
node scripts/verify-trap.mjs         # settle the thesis against getTokenSupply
node scripts/collateral-scenario.mjs # what the error does to a loan, in dollars

conformance.mjs is the one to run first. getTokenSupply returns the runtime's own effective scaled amount, so comparing our prediction against it is a direct test of the reader rather than of our arithmetic. Sampling is stratified: the mints with real gaps are all included, so a pass cannot be earned by testing only the easy ones.

verify-rpc-mechanism.mjs is the one to run when you want to be convinced rather than told. It makes two calls to the same node for the same mint - the account, which stores both fields and says nothing about which applies, and the supply, which the runtime has already scaled - and prints the factor by which the two disagree. It exits non-zero when a mint does not diverge, so a clean result counts as a result.

What this does and does not claim

The runtime is correct. It applies the effective multiplier exactly as the SPL specification says, including the case where an activation timestamp has already passed and nothing has folded the new value into the stored field. We do not claim an issuer acted improperly, and we do not know why any particular field lags. We claim one thing, which is falsifiable in one command: the two fields disagree, and a client that reads the stored one reports a different number than the chain will apply. Reading the right field is avoidable.

Reproduce it: node scripts/verify-trap.mjs. It exists to try to falsify this page, and it will say so if the assumption behind it ever stops holding.

The registry: what runs where

Two things live in this project and they are scoped differently on purpose, so neither is dressed up as more than it is.

Measurement reads mainnet

Every published number is read from Solana mainnet: 933 official mints across two independent issuers, their real Token-2022 accounts, and the real runtime response. Reading costs nothing and moves nothing, so the finding is mainnet evidence even though the settlement program is not deployed there.

Settlement devnet, unaudited

The Anchor registry that settles corporate actions - snapshot holders, pay a distribution from escrow, mint a split delta against a published Merkle root - is deployed to devnet and has not been audited. It is not pointed at real assets, and it is not claimed to be.

The program

Program42WwVtPQzKiQRtDvaiGM7yjMw8jPSN1hxam24FcFFCLV
Deploy transaction49haW2jxU32L4XonwB7LtBv…PJS3pd6
Deployed2026-09-22, from workflow run 35718143099
Size / rent350,208 bytes / 1.77993548 SOL

Every step, with a signature

A 4:1 split and then a cash distribution, settled against the program above: a green end-to-end run whose signatures are public explorer links. Two paths must refuse and are listed as such. The identical 15-step lifecycle also runs on a throwaway validator on every push, so this is not a one-off anyone has to take on faith - and the settlement has genuinely executed on a network the team does not control.

stepdevnet transaction
initialize_assetG92HsCsjee1cr7vsuS…
arm_split_authority2ivzcAChnksjhkPbxM…
declare_action(split 4:1)3iNVHCARumMzFPZU1a…
snapshot_holders(short register)rejected the program's own SupplyMismatch: a register that does not sum to supply cannot be recorded
snapshot_holders(action 4zqNC9…)3nbPfG7RmwL1eTtdHv…
claim[2Km2Hu…]GDHpmmFxmNEZvxMLKm…
claim[8bHPWZ…]xzcZJWMFE7Do9Wngp4…
claim[9svT6y…]3qZcC9PMyZHQuXhfUN…
claim(replay)rejected the receipt PDA already exists, so a settled claim can never be paid twice
settle_action(split)5JPbGxFbi1iosL5zRK…
declare_action(dividend)2uoiQQsihuHFA3JxcM…
snapshot_holders(action HV7jHU…)4mn7zkrBu8ncSFRxht…
claim[2Km2Hu…]4CWHUejtzLTw4KTUby…
claim[8bHPWZ…]54JQPuRgX2UcJ6Uzik…
settle_action(dividend)5oisftAyUtWY9iphCJ…

Generated from docs/devnet-settlement-2026-09-22.json by scripts/gen-webdata.mjs; a test fails the build if this table and that record ever disagree.