Releases: mkrz-x/robinhood-chain-kit
Release list
robinhood-chain-kit v0.9.0
The kit meets viem where developers are, and every fail-closed door gets an
on-ramp. 0.8.x shipped three flagship fail-closed designs — Oracle Guard,
ERC-8056 reads, transaction preflight — and, an audit found, no way to feed
any of them: call descriptors were exported but nothing executed them,
assessOracleHealth demanded sequencer and pause state the kit gave you no
way to obtain, and the preflight adapter's ten methods had no shipped binding.
This release closes those gaps without breaking anything: a pure minor, and
the core still has zero runtime dependencies.
Added
robinhood-chain-kit/viem— the one entry that speaks viem, with viem
imported as types only (the emitted JS contains no viem import, so the
new optional peer stays truly optional).robinhoodChainActions()for
.extend():getScaledUiMultiplierState,getStockDirectory,
getOracleHealth, andgetEquityQuote, which composes registry → feed →
round → multiplier → pause → session into one quote.
createViemPreflightAdapter(client)is the shipped
TransactionPreflightAdapterbinding; it deliberately omits
resolveContract/resolveAssetrather than fabricateverified: true
evidence an RPC cannot know.- Subpath exports
./viem,./erc8056,./oracle,./preflight, each
with types-first ESM and CJS conditions, so guarding a price no longer
drags the preflight engine into a bundle. The root keeps re-exporting
everything else, unchanged. MinimalPublicClient(src/client.ts) — the structural client the
executed readers take. Any viemPublicClientsatisfies it without the kit
importing viem;executeContractCallsbatches throughmulticallwhen
present (chunked at 300 calls) and falls back to sequential reads when the
method is absent, rejects wholesale, or returns a misaligned result —
misalignment being the silent-wrong-answer case, since slot N would read as
call N+1's value.- Executed readers:
readScaledUiMultiplierState(s)run the three
authoritative ERC-8056 reads and return typed bigints plus the resolved
schedule;readOraclePauseStatereadsoraclePaused()on the token —
documented on the official oracles page, verified byeth_callon chain
4663, and mapped fail-closed to"unknown"on any revert (a plain ERC-20
reverts on the selector, and USDG did);readOracleRound(s)execute and
normalizelatestRoundData();readSequencerUptimereads an AggregatorV3
uptime feed (0 = up, 1 = down, anything else or a revert ="unknown");
andcheckOracleHealthgathers round + pause + sequencer in one call and
hands them to the unchanged fail-closed assessment. No sequencer uptime
feed address ships: none for chain 4663 could be verified in the live
Chainlink directory, and an unverified address baked into a package is the
exact class of silent wrong answer this kit exists to prevent. - The stock-token registry (
src/registry.ts):loadStockTokenDirectory
parses Robinhood's ownapi.robinhood.com/rhj/assetswith the same trust
boundary as the Chainlink loader — atomic parsing where one malformed row
rejects the document, optional byte-levelexpectedSha256pinning,
injectable fetch — with the schema taken from the live payload (194 assets
read and checked), not from documentation that does not exist.
findStockTokenlooks up by ticker or address;RHJ_ASSETS_URLis
exported fromchain.USDG_ADDRESS/USDG_DECIMALSship verified:
eth_callon chain 4663 answered name "Global Dollar", symbol "USDG",
decimals 6 at0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168, and the chain's
Blockscout lists that deployment as verified with live market data while
the several same-name copycats are unverified with round 1e27 supplies. - DvP leg builders:
settlementLegFromScaledUiLogand
settlementLegFromTransferLogconvert decoded logs intoSettlementLegs —
the boilerplate everypairDeliveryVersusPaymentcaller hand-wrote, with
its two recurring mistakes designed out (uiValue where the raw value
belongs; uiValue dropped and lost to UI pricing). Strict validation throws
on malformed input, because a leg that silently fails to pair is worse than
one that errors. - Premium math where people can find it (
src/premium.ts, also on
./oracle):underlyingSharePriceencodes the official docs' direction —
the feed prices the TOKEN, multiplier included, and the share price is
feedPrice * 1e18 / uiMultiplier(), quoted from the docs in the source —
plusformatUnderlyingSharePrice, signedpremiumBps, and
computePriceDeviationBps/computeSignedPriceDeviationBps. The deviation
arithmetic moved to a shared pure module so./oracleoffers it without
the preflight engine; preflight'scalculatePriceDeviationBpsnow
delegates to it, byte-for-byte the same results. nextUsEquitySessionChange(ts)— the exact epoch of the next scheduled
session boundary and which way it flips, holiday- and early-close-aware,
using the same calendar asgetUsEquityMarketSession.oraclePausedjoinedERC8056_ABIwith adeployed-callverification
entry, evidence documented in the member's note.- Formerly private utilities, exported:
sha256Hex(compute your own
directory pins),DEFAULT_RANGE_TOO_LARGE_PATTERNS,defaultIsRateLimit
anddefaultIsRangeTooLarge(extend the classifiers instead of replacing
them),KNOWN_ERC20_SELECTORS, andMAX_UINT256. llms.txtin the package root: every export, one line each, grouped by
module and marked[pure]/[network], for token-budgeted contexts.- Examples:
equity-quote.mts,oracle-health-live.mts,
viem-preflight.mts, and a rewritten learning-order table.
Changed
- viem becomes an optional peer (
peerDependenciesMeta.optional), used
by the/viementry and only for types. Consumers without viem see no
install change and no runtime change. getChainlinkRoundDataCallsacceptsPick<ChainlinkFeed, "proxyAddress">,
a safe widening so executed readers can pass a bare address.- The build is multi-entry (tsup) and
attwruns the node16 profile: the
package requires Node ≥ 20.3, and the node10 resolution mode it drops was
the only thing standing between the subpaths and a green check. verify-livenow exercises the whole new surface against mainnet: parses
the live registry, reads AAPL's multiplier and pause flag, confirms USDG's
decimals on-chain, runscheckOracleHealth, and extends a real viem client
throughgetEquityQuote.- README: the five-line viem quickstart leads, followed by a
which-functions-touch-the-network table and the on-ramp sections. The
failure-mode prose is unchanged.
Fixed
- Nothing existing broke, and nothing existing changed behaviour. Every 0.8.x
export, including both deprecated functions, is intact — this is a pure
minor.
robinhood-chain-kit v0.8.1
- Release process: CI attests, it no longer publishes. Two attempts to
publish over OIDC were rejected with a 404 on the PUT while provenance signed
fine, and the second was unambiguous — the version was not on the registry at
all, so nothing but the token could have been refused. Rather than leave a
failed run on every tag,npm run releasepublishes from a laptop and the
tag workflow proves the result: it rebuilds the tag's tree and fails unless
the tarball on npm is byte-identical to it. That is a stronger check than the
old flow had, because it catches the failure a manual release actually
introduces — publishing from a dirty tree, a staledist/, or the wrong
branch.publishConfig.provenanceis removed: with no OIDC provider outside
CI it made every hand publish fail withEUSAGE, and no released version has
ever carried an attestation. The workflow header documents exactly how to
restore CI publishing if the trusted publisher is ever configured. npm run releaserefuses in under a second instead of after thirty.
scripts/preflight-release.mjschecks the three cheap things first: the
version is not already on the registry, the working tree is clean, and the
CHANGELOG has a section for this version. The working-tree check is the one
that matters — publishing is irreversible and npm does not allow replacing a
version, so the tag workflow's checksum comparison can only report a bad
publish after it is already public. This prevents it.- No consumer-facing code changed in this release; it is release tooling and
the README section describing it.
robinhood-chain-kit v0.8.0
robinhood-chain-kit v0.8.0
Correctness release. Two figures in 0.7.0 were presented as exact and were not.
- multiplier recovery from a transfer is bounded, not exact: uiValue is
truncated by the contract before the event exists - settlement pricing now states whether it is per raw ERC-20 unit or per
displayed share - every ERC-8056 member verified against live contracts on chain 4663
- UIMultiplierUpdated announces a scheduled change, not a completed one
- optional SHA-256 pinning for the Chainlink feed directory
robinhood-chain-kit v0.7.0
0.7.0: the event name in the spec matches nothing on this chain
Robinhood Chain exists for tokenized equities, and this kit had nothing
about the one thing that makes a tokenized equity different from an
ERC-20. Corporate actions here do not mint or burn. The contract carries
a multiplier, raw balances stay exactly where they were, and the amount
a holder is shown changes underneath them.
The trap, and the reason to ship this at all: EIP-8056 names the event
TransferWithUIAmount. The deployed contracts emit TransferWithScaledUI.
Those hash to different topics —
0x37e7f0db430edc9dd31bc66f25f8449353aa0818f503b906747dd8f286cd3802
0x0226a2f5c1ae0e071aeec3d4ebafcefdc5c549be11f40ed27e76e802acccf374
— so a filter written from the spec matches zero logs, forever. It does
not throw and it does not look like a bug. It looks like a chain with no
tokenized-equity activity on it. Both constants are exported, and the
tests hash the signatures rather than trusting what I typed.
Multipliers are bigint end to end. A number cannot hold a uint256 ratio
exactly, and a figure that is quietly slightly wrong is the entire
hazard this module exists for. readScaledUiMultiplier fails closed: a
zero raw value makes the ratio undefined rather than enormous, and "we
cannot tell" is a different answer from "the multiplier is 1" — callers
that flatten those two are the ones who publish a split-adjusted figure
as if it were unadjusted.
compareScaledUiMultiplier reports direction and refuses to name a cause.
A split and a dividend adjustment both move it up; the event carries
nothing that separates them, and naming one would be a guess wearing an
API's clothes.
Also adds peer-to-peer settlement. A stock leg and a cash leg opposed
inside one transaction is a trade by construction, and no pool was
involved, which is why a swap-based index cannot see any of it. It fails
closed hard: exactly one leg of each, between the same two addresses, or
the transaction is left unclassified. The whole value of the output is
that a reported settlement really is one.
inferSettlementPrice carries inferred: true in the result type, where
a call site has to read it. No venue quoted that price. It is the ratio
of two amounts, and the parties may have agreed it hours earlier
off-chain at a level unrelated to any market. The count is a hard fact;
the level is a reconstruction.
The changelog now also marks 0.4.0 and 0.5.0 as developed but never
published. A reader on npm sees 0.3.2 jump to 0.6.0 and deserves to know
the middle two were real work that never shipped, not missing entries.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com