{"_id":"@assayhq/erc8056","_rev":"4-474eb4e2665cf91e3caf94deeb5229b6","name":"@assayhq/erc8056","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@assayhq/erc8056","version":"0.1.0","keywords":["erc8056","erc-8056","scaled-ui-amount","ethereum","erc20","tokenized-equities","stock-tokens","corporate-actions","rebasing","robinhood-chain"],"license":"MIT","_id":"@assayhq/erc8056@0.1.0","maintainers":[{"name":"wraithioner","email":"muslimoskanov@gmail.com"}],"homepage":"https://github.com/Ransenfun/Robinhood-#readme","bugs":{"url":"https://github.com/Ransenfun/Robinhood-/issues"},"dist":{"shasum":"51c835cf46916c37d5af26ceb69f426c17086bfb","tarball":"https://registry.npmjs.org/@assayhq/erc8056/-/erc8056-0.1.0.tgz","fileCount":24,"integrity":"sha512-rP/CwDlpxJb4yYHUpbUksKJ9HYPLpNooIfK1q0sg+bEjsqL4uu7tE8MGqr6KHRb3QZvLoot7ZS/ldNWOHkQj/A==","signatures":[{"sig":"MEUCIQC7clAHnL+ZRpAowBCsw8tMld7xUrqFP+/75um5/uArLQIgA1dnIT0PCqC7+5jeslsi4MliHX2C1xl8poQ1u1+x73I=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":46677},"main":"./src/index.ts","type":"module","types":"./src/index.ts","engines":{"node":">=18"},"exports":{".":"./src/index.ts","./constants":"./src/constants.ts","./package.json":"./package.json"},"gitHead":"1255d6c037c8da86066708f07cd98190b7d5260d","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build && npm run test"},"_npmUser":{"name":"wraithioner","email":"muslimoskanov@gmail.com"},"deprecated":"Broken entry points (main/exports pointed at unpublished src). Use 0.1.1+.","repository":{"url":"git+https://github.com/Ransenfun/Robinhood-.git","type":"git","directory":"packages/erc8056"},"_npmVersion":"11.13.0","description":"ERC-8056 (Scaled UI Amount) reference math: raw <-> underlying-share conversion, a point-in-time corporate-action multiplier history, and multiplier-independent valuation. Zero dependencies.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"main":"./dist/index.js","types":"./dist/index.d.ts","access":"public","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./constants":{"types":"./dist/constants.d.ts","import":"./dist/constants.js","default":"./dist/constants.js"},"./package.json":"./package.json"}},"_hasShrinkwrap":false,"devDependencies":{"vitest":"2.1.8","fast-check":"3.23.1","typescript":"5.6.3","@vitest/coverage-v8":"2.1.8"},"_npmOperationalInternal":{"tmp":"tmp/erc8056_0.1.0_1788349322791_0.16851022504854263","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@assayhq/erc8056","version":"0.1.1","keywords":["erc8056","erc-8056","scaled-ui-amount","ethereum","erc20","tokenized-equities","stock-tokens","corporate-actions","rebasing","robinhood-chain"],"license":"MIT","_id":"@assayhq/erc8056@0.1.1","maintainers":[{"name":"wraithioner","email":"muslimoskanov@gmail.com"}],"homepage":"https://github.com/Ransenfun/Robinhood-#readme","bugs":{"url":"https://github.com/Ransenfun/Robinhood-/issues"},"dist":{"shasum":"3eec1e63f927861ac9790b8b46cea1168287695c","tarball":"https://registry.npmjs.org/@assayhq/erc8056/-/erc8056-0.1.1.tgz","fileCount":24,"integrity":"sha512-3mPlaOGOmj4W2CsyscCtaIMiXHLgq2vS9TWvaskiJGjblKf1g8EcAf2LreMmGgyOrlXiF0vsyRs4Ll+wwMxD6Q==","signatures":[{"sig":"MEUCIA06QKlTQQpIvx5vSU7RwpAkz4w2UJyzVws2X0pRbQ4kAiEAoRGQA8NwVzlkZNUVZb5OUDlc7X7/mVEQmVB8dZ0if10=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":46489},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./constants":{"types":"./dist/constants.d.ts","import":"./dist/constants.js","default":"./dist/constants.js"},"./package.json":"./package.json"},"gitHead":"f0a563da8bd262309569acf0434250c0b00146c9","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","prepare":"npm run build","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build && npm run test"},"_npmUser":{"name":"wraithioner","email":"muslimoskanov@gmail.com"},"repository":{"url":"git+https://github.com/Ransenfun/Robinhood-.git","type":"git","directory":"packages/erc8056"},"_npmVersion":"11.13.0","description":"ERC-8056 (Scaled UI Amount) reference math: raw <-> underlying-share conversion, a point-in-time corporate-action multiplier history, and multiplier-independent valuation. Zero dependencies.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"2.1.8","fast-check":"3.23.1","typescript":"5.6.3","@vitest/coverage-v8":"2.1.8"},"_npmOperationalInternal":{"tmp":"tmp/erc8056_0.1.1_1788349636030_0.19428828025136502","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@assayhq/erc8056","version":"0.1.2","description":"ERC-8056 (Scaled UI Amount) reference math: raw <-> underlying-share conversion, a point-in-time corporate-action multiplier history, and multiplier-independent valuation. Zero dependencies.","keywords":["erc8056","erc-8056","scaled-ui-amount","ethereum","erc20","tokenized-equities","stock-tokens","corporate-actions","rebasing","robinhood-chain"],"license":"MIT","type":"module","sideEffects":false,"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./constants":{"types":"./dist/constants.d.ts","import":"./dist/constants.js","default":"./dist/constants.js"},"./package.json":"./package.json"},"engines":{"node":">=18"},"repository":{"type":"git","url":"git+https://github.com/wraithioner/assayhq.git","directory":"packages/erc8056"},"scripts":{"build":"tsc -p tsconfig.build.json","test":"vitest run","test:watch":"vitest","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm run test","prepare":"npm run build"},"devDependencies":{"@vitest/coverage-v8":"2.1.8","fast-check":"3.23.1","typescript":"5.6.3","vitest":"2.1.8"},"publishConfig":{"access":"public"},"gitHead":"3174afb5e5b3e68c88bdd2999f65e34232d1756e","_id":"@assayhq/erc8056@0.1.2","bugs":{"url":"https://github.com/wraithioner/assayhq/issues"},"homepage":"https://github.com/wraithioner/assayhq#readme","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-ETUNRrbYx/YM4JP+5u9lyiS/4mijM+Smy5rr3DhV+xp1euLZuO26c7ki2iggP+cilcjyictwzDhMtk1PHsF3ZQ==","shasum":"2bfd40ad9a550fca8421571bf86f819f6d85931e","tarball":"https://registry.npmjs.org/@assayhq/erc8056/-/erc8056-0.1.2.tgz","fileCount":24,"unpackedSize":52806,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBE6C8Q5b+06NXl6st7AIAFINQVB3jjK0I7FLNH2qoUEAiAnhphIV55JoTAseRoAS0+bANpt0lFZUWn3mLIQRqMiMw=="}]},"_npmUser":{"name":"wraithioner","email":"muslimoskanov@gmail.com"},"directories":{},"maintainers":[{"name":"wraithioner","email":"muslimoskanov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/erc8056_0.1.2_1788353757657_0.3017609361977325"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-02T11:42:02.597Z","modified":"2026-09-02T12:55:58.007Z","0.1.0":"2026-09-02T11:42:02.962Z","0.1.1":"2026-09-02T11:47:16.160Z","0.1.2":"2026-09-02T12:55:57.790Z"},"bugs":{"url":"https://github.com/wraithioner/assayhq/issues"},"license":"MIT","homepage":"https://github.com/wraithioner/assayhq#readme","keywords":["erc8056","erc-8056","scaled-ui-amount","ethereum","erc20","tokenized-equities","stock-tokens","corporate-actions","rebasing","robinhood-chain"],"repository":{"type":"git","url":"git+https://github.com/wraithioner/assayhq.git","directory":"packages/erc8056"},"description":"ERC-8056 (Scaled UI Amount) reference math: raw <-> underlying-share conversion, a point-in-time corporate-action multiplier history, and multiplier-independent valuation. Zero dependencies.","maintainers":[{"name":"wraithioner","email":"muslimoskanov@gmail.com"}],"readme":"# erc8056\r\n\r\nReference math for **ERC-8056 (“Scaled UI Amount”)** tokens: raw ↔ underlying-share\r\nconversion, a point-in-time corporate-action multiplier history, and valuation that is\r\nprovably independent of the multiplier.\r\n\r\n**Zero runtime dependencies. MIT licensed.** Ships TypeScript types and a matching Solidity\r\nlibrary so on-chain and off-chain accounting agree to the wei.\r\n\r\n```bash\r\nnpm install @assayhq/erc8056\r\n```\r\n\r\n---\r\n\r\n## Headline finding: corporate actions are far less dangerous to AMMs than expected\r\n\r\nThe common warning about ERC-8056 is that when a dividend or split fires, every\r\nconstant-product pool holding that token is instantly mispriced and LPs get arbitraged. We\r\nmeasured it against every corporate action ever emitted on Robinhood Chain. The warning is\r\noverstated, and the arithmetic says so:\r\n\r\n- **A 1% reinvested dividend costs LPs ≈ 0.124 bps of pool value.** Roughly one part in\r\n  80,000. The largest distribution ever emitted on this chain (CCL, +2.15%) costs 0.571 bps.\r\n- **A compensated split — 10:1, 4:1, any ratio — costs exactly zero.** Because raw balances\r\n  never rebase and the feed is total-return, the pool doesn't move and neither does fair\r\n  value. The split is invisible to the pool.\r\n\r\nThe real hazard of ERC-8056 is **not** the AMM. It is **share accounting**: reading\r\n`balanceOf()` as a share count, or applying the multiplier twice. Those are silent, they are\r\noff by whole multiples rather than fractions of a basis point, and they are what this library\r\nexists to prevent. [Skip to the arithmetic](#what-a-multiplier-step-actually-does-to-a-constant-product-amm).\r\n\r\n---\r\n\r\n## The mechanic\r\n\r\nAn ERC-8056 token carries a second quantity alongside the ordinary ERC-20 balance: an\r\n18-decimal fixed-point **UI multiplier**.\r\n\r\n```\r\nunderlying shares = raw amount × uiMultiplier ÷ 1e18\r\n```\r\n\r\nA corporate action — a reinvested dividend, a stock split — does not move anyone’s tokens.\r\nIt updates one number on the contract and emits:\r\n\r\n```solidity\r\nevent UIMultiplierUpdated(uint256 oldMultiplier, uint256 newMultiplier, uint256 effectiveAtTimestamp);\r\n```\r\n\r\nThe multiplier may be scheduled ahead of time (`newUIMultiplier()` / `effectiveAt()`), so the\r\nvalue in force at a given moment is “the last update whose `effectiveAt` ≤ now” — never the\r\nlatest one you happen to have fetched.\r\n\r\n## Why `balanceOf()` misleads\r\n\r\n**The token never rebases.** After a 4:1 split your `balanceOf` is *the same integer it was\r\nbefore* — but each of those raw units now represents four underlying shares.\r\n\r\n```ts\r\nimport { toUnderlyingShares, WAD } from \"@assayhq/erc8056\";\r\n\r\nconst raw = WAD;                       // balanceOf() -> 1.0, before AND after the split\r\ntoUnderlyingShares(raw, WAD);          // 1e18  → 1 share   (multiplier 1.0)\r\ntoUnderlyingShares(raw, 4n * WAD);     // 4e18  → 4 shares   (multiplier 4.0, real: CRWD)\r\n```\r\n\r\nSo any protocol that reads a raw balance and calls it “shares” is wrong by a factor of the\r\nmultiplier — silently, and only for the assets that have had a corporate action. It will not\r\nthrow. It will just be wrong, and only sometimes, which is worse.\r\n\r\nThree concrete traps:\r\n\r\n1. **Collateral valuation.** Treating raw as shares under-counts a post-split position 4×.\r\n2. **`totalSupply` accounting.** Supply looks flat across a split that quadrupled the claim.\r\n3. **Double-counting the multiplier.** If your price feed is already *total-return* (as\r\n   Robinhood Chain’s Chainlink feeds are — the answer is the price of one **raw token**, with\r\n   the multiplier already applied), then converting to shares *and* using that feed\r\n   over-values the position by the multiplier again. This library makes that mistake\r\n   structurally impossible: `rawBalanceValueUsd()` takes no multiplier argument.\r\n\r\n```ts\r\nimport { rawBalanceValueUsd } from \"@assayhq/erc8056\";\r\n// value a raw balance with a total-return answer — no multiplier term, by design\r\nrawBalanceValueUsd(WAD, 31_563_860_540n); // 315.6386054e18  ($315.6386054)\r\n```\r\n\r\nA useful corollary for anyone building a portfolio tracker: **a multiplier step alone does\r\nnot move NAV.** Share count jumps, per-share price divides, USD value is continuous.\r\n\r\n## What a multiplier step actually does to a constant-product AMM\r\n\r\nA Uniswap-style pool holds *raw* tokens and consults no oracle. Its price is `y/x`. The fair\r\nprice of a raw token is the total-return feed, `underlying_price × multiplier ÷ 1e18`.\r\n\r\nThe intuition that a multiplier step must wreck the pool assumes the pool's fair price jumps\r\nwhen the multiplier does. Usually it doesn't. What matters is not the size of the multiplier\r\nstep but whether the underlying price moves to offset it **at the same instant**:\r\n\r\n- **A split (e.g. 10:1).** Multiplier ×10, underlying share price ÷10. Fair per-raw-token\r\n  price is *unchanged*, and since raw balances never rebase, the pool’s reserves don’t move\r\n  either. **The pool is structurally immune: `r = 1`, LP loss ≈ 0.** This is the whole point\r\n  of a non-rebasing multiplier.\r\n- **A reinvested dividend.** The multiplier steps up at the on-chain `effectiveAt`, but the\r\n  market’s ex-dividend price drop happened on the ex-date and the feed only re-ticks on its\r\n  heartbeat/deviation. In that gap the fair price is genuinely above the pool price with\r\n  nothing on the pool having changed — real, uncompensated mispricing that arbitrageurs take\r\n  from LPs.\r\n\r\nFor a fair-price jump of ratio `r`, arbitrageurs move a constant-product pool to the new\r\nprice via reserves `x' = x/√r`, `y' = y·√r`. Marking both sides at the new price, the value\r\nthey extract is:\r\n\r\n```\r\nLP loss / pool value = (√r − 1)² / 2\r\n```\r\n\r\n### Worked numbers — from real on-chain history\r\n\r\nEvery figure below is computed from an actual `UIMultiplierUpdated` event on Robinhood Chain\r\nmainnet and is **pinned by tests** (`test/onchain-fixture.test.ts`), so this table cannot\r\ndrift from the chain.\r\n\r\n| Event | `r` | LP loss (fraction) | in bps |\r\n|---|---|---|---|\r\n| **Canonical 1% dividend** | 1.01 | `1.2438 × 10⁻⁵` | **0.124 bps** |\r\n| CCL distribution — real, block 50,955,407 | 1.021486444855206408 | `5.7097 × 10⁻⁵` | 0.571 bps |\r\n| SGOV distribution — real, block 51,269,236 | 1.002113947879 | `5.580 × 10⁻⁷` | 0.006 bps |\r\n| AAPL dividend — real, block 36,345,344 | 1.000566080061092436 | `4.00 × 10⁻⁸` | 0.0004 bps |\r\n| **10:1 split, compensated (reality)** | **1.0** | **0** | **0 bps** |\r\n| 10:1 split, *uncompensated* — a bound, not an expectation | 10 | `2.3377` | — |\r\n| CRWD 4:1 split, *uncompensated* bound — real, block 978,630 | 4 | `0.5000` | — |\r\n\r\nRead that table carefully, because it contradicts the usual warning:\r\n\r\n- **Dividends are the only real effect, and they are tiny.** A 1% reinvested dividend costs\r\n  LPs about **0.12 bps** of pool value — smaller than a single Uniswap fee tier by two orders\r\n  of magnitude, and smaller than the spread almost any LP already tolerates. The largest\r\n  distribution ever emitted on this chain (CCL, +2.15%) costs **0.57 bps**.\r\n- **Splits are a non-event**, provided the feed and the multiplier move together — which is\r\n  the designed behaviour, and what the on-chain history shows.\r\n- **The `r = 10` row is a bound on desynchronisation, not a prediction.** It answers \"what if\r\n  the multiplier and the feed came apart completely?\" — `(√10 − 1)²/2 ≈ 2.34`, meaning the\r\n  token side of the pool is emptied into the arbitrageur. It is included for completeness and\r\n  has never happened. Quoting it as an expected loss would be wrong.\r\n- Because `effectiveAt` is **known in advance**, even that residual risk is schedulable: a\r\n  pool or LP can pause, widen, or oracle-gate across the step.\r\n\r\nThe practical conclusion for an integrator: **do not build multiplier-aware AMM machinery to\r\navoid a 0.12 bps effect.** Spend the effort on share accounting instead, where the errors are\r\nwhole multiples.\r\n\r\n### Every corporate action ever emitted on Robinhood Chain\r\n\r\nScanned `block 0 → 52,428,883` — **17 `UIMultiplierUpdated` logs across 10 tokens**. That is\r\nthe complete set, committed as the test fixture (`test/fixtures/multiplier-history.json`).\r\n\r\nThe table below is the 11 logs on currently-listed tokens. The other six are five logs from one\r\ntoken that carries no ticker in the current 194-asset list (`0xc93a8c44…`, delisted or\r\npre-listing) and one re-emission of the CRWD split — see [the note below](#re-emitted-updates).\r\n\r\n| Token | Block | Multiplier | Kind |\r\n|---|---|---|---|\r\n| CRWD | 978,630 | 1.0 → 4.0 | 4:1 split |\r\n| SGOV | 4,629,631 | 1.0 → 1.000957519891 | distribution |\r\n| MU | 18,239,875 | 1.0 → 1.000074823219 | dividend |\r\n| ORCL | 20,823,272 | 1.0 → 1.002210914971 | dividend |\r\n| DELL | 26,853,518 | 1.0 → 1.000063708620 | dividend |\r\n| ASML | 29,439,914 | 1.0 → 1.000101323251 | dividend |\r\n| SGOV | 30,302,195 | 1.000957519891 → 1.002981519347 | distribution |\r\n| COST | 32,889,913 | 1.0 → 1.000612040296 | dividend |\r\n| AAPL | 36,345,344 | 1.0 → 1.000566080061 | dividend |\r\n| CCL | 50,955,407 | 1.0 → 1.021486444855 | distribution |\r\n| SGOV | 51,269,236 | 1.002981519347 → 1.005101770003 | distribution |\r\n\r\nSGOV’s three-step chain is the cleanest illustration of the model: an ETF accruing\r\ndistributions purely through the multiplier, with the raw balance never once changing.\r\n\r\n#### Re-emitted updates\r\n\r\n`UIMultiplierUpdated` is not emitted exactly once per corporate action on this chain. CRWD’s\r\n4:1 split is logged twice — blocks 978,630 and 1,231,096, identical `oldMultiplier`,\r\n`newMultiplier` and `effectiveAt` — and the unlisted token above repeats an update the same\r\nway. Two of the 17 logs are therefore repeats rather than distinct actions.\r\n\r\nThis matters for anyone feeding raw logs straight in: `fromEvents()` validates that each\r\nevent’s `oldMultiplier` matches the running value, so a repeat fails that check rather than\r\nbeing ignored.\r\n\r\n```\r\nUIMultiplierUpdated chain broken at effectiveAt=1782999000: oldMultiplier=1000000000000000000\r\nbut running multiplier=4000000000000000000. If these are raw logs, Robinhood Chain re-emits\r\nsome updates — run them through dedupeMultiplierEvents() first.\r\n```\r\n\r\nThe check stays strict, because silently accepting a mismatched chain is how a position gets\r\nmis-valued by a whole multiple. Run one token's logs through the dedupe pass first:\r\n\r\n```ts\r\nimport { MultiplierHistory, dedupeMultiplierEvents } from \"@assayhq/erc8056\";\r\n\r\nMultiplierHistory.fromEvents(dedupeMultiplierEvents(logsForOneToken));\r\n```\r\n\r\nA log is a repeat only when **`oldMultiplier`, `newMultiplier` and `effectiveAt` all match**\r\none already seen, so two genuinely different actions are never merged — including a corrected\r\nschedule that reuses an `effectiveAt` with a different `newMultiplier`, which\r\n`MultiplierHistory` resolves on its own. Pass one token at a time; multipliers are per-token.\r\n\r\nEvery token on the chain builds a clean point-in-time history after this pass, and the test\r\nsuite pins both halves: the raw stream throws for exactly the two repeating tokens, the\r\ndeduped stream throws for none.\r\n\r\n## API\r\n\r\n```ts\r\nimport {\r\n  toUnderlyingShares, fromUnderlyingShares,  // conversion (floors, matches Solidity)\r\n  rawBalanceValueUsd, rawBalanceValueUsdExact, // multiplier-free valuation\r\n  MultiplierHistory,                          // point-in-time multiplier lookup\r\n  dedupeMultiplierEvents,                     // drop this chain's re-emitted updates\r\n  multiplierToFloat,                          // display only\r\n  WAD, TOKEN_DECIMALS, FEED_DECIMALS,\r\n  TOPIC, SELECTOR, SCALED_UI_ABI,             // verified event/selector constants\r\n} from \"@assayhq/erc8056\";\r\n```\r\n\r\n**Point-in-time history**, built straight from decoded events. `multiplierAt()` never\r\nconsults an update whose `effectiveAt` is after the query time, and `fromEvents()` validates\r\nthat each event’s `oldMultiplier` matches the running value — a broken chain throws instead\r\nof silently mis-valuing:\r\n\r\n```ts\r\nconst h = MultiplierHistory.fromEvents(events);\r\nh.multiplierAt(1_788_220_825n); // the value in force at that second\r\nh.current();\r\nh.changes();\r\n```\r\n\r\n### A note on event naming\r\n\r\nThe deployed contracts emit **`TransferWithScaledUI`**\r\n(`0x37e7f0db430edc9dd31bc66f25f8449353aa0818f503b906747dd8f286cd3802`), **not** the\r\nEIP-8056 draft’s canonical `TransferWithUIAmount`. Indexers must filter on the former; the\r\nlatter topic does not appear on chain. Both are exported so you can assert the distinction:\r\n\r\n```ts\r\nTOPIC.TransferWithScaledUI !== EIP_CANONICAL_TRANSFER_WITH_UI_AMOUNT_TOPIC; // true\r\n```\r\n\r\nEvery raw `Transfer` is paired 1:1 with a `TransferWithScaledUI` carrying `uiValue`, so an\r\nindexer can read the underlying-share amount directly rather than recomputing it — which\r\nalso removes the off-by-one risk at the exact block a multiplier changes.\r\n\r\n## Solidity\r\n\r\n[`solidity/ScaledUIMath.sol`](./solidity/ScaledUIMath.sol) is the on-chain twin. It floors\r\nidentically (`mulDiv` truncation), so a contract and an off-chain indexer agree exactly. The\r\nTypeScript property tests are the shared specification for both.\r\n\r\n## Testing\r\n\r\n```bash\r\nnpm test          # 52 tests\r\nnpm run coverage  # 100% statements / branches / functions / lines\r\n```\r\n\r\nCoverage is 100% on the conversion path and every other module. The suite includes\r\nproperty-based tests (`fast-check`) for the invariants that matter:\r\n\r\n- a multiplier update **never** changes the raw balance;\r\n- conversion round-trips to within 1–2 wei (bounded double-flooring);\r\n- underlying shares are monotonic in the multiplier;\r\n- valuation is **independent of the multiplier**, and double-applying it demonstrably\r\n  overstates NAV (4× on a real CRWD-style multiplier);\r\n- the point-in-time history never looks ahead;\r\n- every token's real on-chain log stream replays cleanly once re-emissions are dropped.\r\n\r\n## Licence\r\n\r\nMIT.\r\n","readmeFilename":"README.md"}