Decision log
The canonical register is doc/DECISIONS.md. This page summarises the turning points.
2026-08-25 — Internal security review
One high finding: partial fills stranded ETH and USDC in the routers, fully taxed. Fixed by refusing partial fills rather than handling them.
2026-08-27 — The fee goes from 4 % to 5 %
Creator share doubled, from 1 % to 2 %. $STOCKFUN schedule from 2 / 1.5 / 0.5 to
2 / 2.5 / 0.5. The treasury share stays at 2 %, and that is the number that never moves.
Accepted consequence: the round trip goes from 7.84 % to 9.75 %. Since 2026-10-05 the rate and its split are owner settings, these values being the defaults.
2026-08-27 — The creator buyback
Lifts the "no exit" exclusion on a single path, which always ends at the burn. The Treasury Ratio can now fall at a creator's discretion. Removed on 2026-09-27, replaced by the airdrop.
2026-08-29 — Emergency mode
The owner can move a treasury's assets after 48 public hours. Replaces the promise "nobody can touch the treasury". The protocol stops being described as trustless. Immediate since 2026-10-05: the delay is gone.
2026-09-11 — The rail moves to Robinhood Chain
The blocker was a vault contract's eligibility with a primary-mint issuer, never documented publicly. Robinhood Chain's secondary pools do not require it. Price paid: an accepted cross-chain dependency.
2026-09-12 — The bonding curve goes away
Replaced by two single-sided Uniswap v4 positions, locked from creation. No graduation, no migration, no graduation fee.
The launch FDV does not change: it stays the one the curve opened at, 3.409 ETH. A first 2 ETH ticket takes 36.06 % of supply against 36.99 % before — the launch behaves as it did, to within a point.
Ruled out along the way: a $5,000 launch with a $50,000 ceiling, which gave only 3.25 ETH of depth and let the first 2 ETH buyer take 57 % of supply. And the idea that a lower ceiling would calm the market — it hands over sooner to the unbounded band, which is thin, and doubles the FDV reached after 50 ETH of spending.
2026-09-12 — Three-block anti-snipe
Creator only in block 1, 99 % to the buyback in block 2 except the creator and the owner whitelist, normal thereafter. Replaced on 2026-09-28 by the decaying anti-snipe.
Taken in full knowledge of the objection: a 99 % tax only produces anything if someone loses their money, and closing block 2 would have had the same deterrent effect with no victim. The whitelist sits at the protocol level rather than the creator level, precisely so it cannot become an insider advantage market by market.
2026-09-12 — No burn on sells
The idea of sending 10 % of incoming tokens to the burn on every sell was dropped. Paid by the seller, it pushed the round trip to 18.78 %. Paid by the LP, it made "liquidity locked forever" false and handed anyone a roughly 1:1 lever to burn the float and pump their own bag.
2026-09-27 — The airdrop replaces the creator buyback
A market's treasury now has one use: 100 % of the stocks it buys are airdropped to the
holders of its token, pro rata, in kind, on Robinhood Chain. The creator buyback is
removed, and with it the bridge's return path. The $STOCKFUN buyback-and-burn stays.
Ruled out along the way: keeping the creator buyback, and a split keeping 60 % in the treasury and distributing 40 %, the former V2 plan. Accepted consequences: no treasury accumulates any more, the Treasury Ratio loses its meaning, and the slogan and tagline are to be revisited. The mechanism was coded on 2026-10-04: see The airdrop.
2026-09-27 — A cycle every 24 hours
The airdrop's cadence is set the same day. Once every 24 hours, for each market whose treasury has accumulated at least 0.1 ETH, the keeper converts, bridges, buys the stocks, then airdrops them. Below the threshold, nothing happens that day; the ETH waits.
Stocks can only be bought while the US stock market is open, so there is no cycle at weekends or on NYSE holidays. In the keeper's code, until 2026-10-06 the conversion ran during the session, at the first pass where the vault held the threshold; since then it follows this decision, once per window, at the keeper's first pass after the window closes and only if the vault holds the threshold then (see 2026-10-06 below); since the sixth audit loop, the same day, the USDC a vault holds follows this cadence too. The send to the airdrop runs once per window since 2026-10-05, after the day's session: see The keeper.
2026-09-27 — Stuck funds and holding measurement
Leftovers of an interrupted cycle are finished at the next cycle, threshold reached or not. Airdrop funds stuck for 24 hours in a mirror vault can be moved by the owner without notice: the one exception to the public 48-hour delay, chosen knowingly. Holdings are measured as the average balance over the 24 hours before the cycle, Mondays included. The stuck-funds window became 48 hours on 2026-09-28, and the rule was closed on 2026-10-05, never coded.
2026-09-28 — Decaying anti-snipe
80 % in the opening block, minus 8 points per block, 5 % from the 11th block, on buys and
sells. The surplus goes to the market's treasury, or to the team's claimable fees on
$STOCKFUN. Exempt: the creator's launch buy and a whitelist set by the creator, fixed in
the creation transaction, public, immutable and capped at 20 addresses; on $STOCKFUN, set
by the owner at launch. Since 2026-10-05 these numbers are owner settings, the values
above being the defaults.
Ruled out along the way: tagging the wallets that buy in the first blocks and taxing their later sales, even after a transfer. A v4 pool does not see the wallet, a tax taken by the token makes the sale fail, and blocking tagged wallets outside the StockFun router would get the token flagged by scanners. Accepted consequence: the whitelist moves from the protocol to the creator, which the 2026-09-12 decision had ruled out to prevent an insider advantage.
2026-09-28 — Stuck airdrop funds: 48 hours
Airdrop funds that have stayed 48 hours in a mirror vault, instead of 24, can be moved by the owner without notice. At 24 hours the window equalled the cycle period, so an ordinary one-cycle carry-over became movable without notice; at 48 hours it no longer does. A Friday failure still becomes movable on Sunday, before Monday's cycle. Decided the same day: the clock never stops, not during a pause nor at weekends. Closed on 2026-10-05, never coded: the emergency transfer is immediate everywhere.
2026-09-28 — Holdings recorded onchain
Each market's token records every wallet's holdings over time, so a contract computes the average over the 24 hours before a cycle and every share; the keeper never supplies them. Ruled out: a split computed offchain by the keeper and published with a challenge delay. Accepted cost: every transfer of the token pays extra gas.
2026-09-28 — The airdrop in wrapped stocks, on Ethereum
The stocks bought on Robinhood Chain are locked there in LayerZero adapters deployed by StockFun, one per stock, and sent to Ethereum as wrapped stocks. The distribution happens on Ethereum, where the token and its holdings live, and each holder claims their share at their own cost. Accepted consequences: the wrapped stocks are only worth the locked stocks and the LayerZero configuration, they have no market on Ethereum, and a freeze of the stock tokens by their issuer would block the locked stocks. Decided the same day: the owner holds the adapters' LayerZero configuration, with no delay and no cap on withdrawals, as Paxos does for USDG. Ruled out: a configuration frozen after deployment, and changes under a 48-hour notice.
2026-09-28 — The official router stays changeable
The owner can change the official swap router at any time, and the hook recognises the anti-snipe whitelist through that router. Accepted consequence: by naming another router, the owner can exempt anyone from the anti-snipe.
2026-09-28 — A creation fee of 0.001 ETH
The creation fee becomes 0.001 ETH, fixed in the code, instead of about $3: no price feed, no owner setting. Its dollar value now follows the price of ETH. An owner setting since 2026-10-05, 0.001 ETH by default.
2026-09-28 — The PoolManager left out of the holding record
The token does not record the Uniswap PoolManager, a side of every swap that holds the
tokens of every pool and never receives an airdrop: its history reads zero, while the
trader's is still recorded. Measured saving: about 24,000 gas per swap, on buys and sells
alike.
2026-09-29 — No LP fee on StockFun pools
The pools are created with a Uniswap LP fee of zero instead of 0.30 %: the hook's tax is the whole cost of a trade, 5 % per direction and 9.75 % for a round trip before price impact. Accepted cost: the treasury, the team and the creator lose their share of the LP fees, and the burn of the token side of those fees disappears. Since 2026-10-05 the LP fee is a launch setting, zero by default; each pool keeps the fee it was created with.
2026-10-01 — Fixes from the 2026-09-29 security audit
Applied on 2026-09-30 and 2026-10-01, after every finding was reviewed and its fix chosen. Each fixed finding has regression tests that fail on the audited code.
Only the liquidity lock can add liquidity to a StockFun pool: a third-party position traded against taxed swaps without paying the tax or the anti-snipe. The new hook permission changed the hook's address, which was re-mined.
Only the treasury share is sent during a trade. The team and buyback shares are credited on the hook and paid by claims anyone can call, to the factory's current wallets; the escrow is gone. The router and the lock pay the input before the swap. Until then, a fee wallet that called back into Uniswap could halt trading on every pool.
Each conversion step spends an amount the keeper names, so a vault larger than its venue converts in slices. The cash is reserved per basket stock as it arrives, and a stock the keeper skips keeps its reservation; the mirror vault works the same way.
Each token records its supply outside the Uniswap PoolManager over time, the denominator
of the airdrop's pro-rata share. Of the audit's three options, this one costs one more
write per swap; recording the PoolManager again would have cost about 24,000 gas per
swap. Accepted consequence: the airdrop's windows start and end on an hour boundary.
On the Robinhood rail, a refused basket no longer blocks the other markets of a batch, one remote token takes one identifier, a partial v3 fill fails its route, a paused remote hub still applies role changes, and canonical records are paid in the order they landed.
Measured: a router buy costs 113,529 gas and a sell 137,250, against 125,862 and 150,416 before. Still open, awaiting the owner's decision: the bridge adapter's rotation (M-2, and L-8 with it), Ondo attestations (M-7), and routers accepting themselves as recipient (L-4). Since 2026-10-05 M-7 and L-4 are fixed, and M-2 stays as it is by the owner's decision, L-8 with it.
2026-10-01 — The security pipeline completes the fixes
The same day, a second audit round, by two independent auditors, completed several fixes. Each has regression tests; none changes a design decision or an immutable parameter.
On Robinhood Chain, a v3 route runs one pool at a time, and each pool must take its whole input: the earlier check saw only the first pool, and a partial fill further along left the intermediate token where anyone could take it. A canonical accounting ticket pays at most as many records as it added to the queue, oldest first, so a backlog no longer pushes it past the gas of its automatic execution; the sweep pays the rest.
An executed emergency that takes a vault's cash below its reservations zeroes them all, in both vaults: what is left, and every later inflow, is split afresh at the weights. Until then the reset existed only in the reading, and the old reservations came back with the next inflow.
The keeper holds back n−1 units on a basket's last stock at each purchase: the rounding
remainder goes to that stock, and a dust transfer could make the purchase revert. It also
halves a burn slice the $STOCKFUN pool cannot fill whole. Each token records its first
hourly mark at deployment, which only mattered on a clock that starts in hour 0, such as a
test chain's. The Ethereum stock router syncs before paying in ETH, and the Lens refuses
an empty page.
Measured: a token's first swap of each hour costs about 28,000 to 30,000 gas more as its own transaction, not about 23,000.
Awaiting the owner's decision, not decided: an emergency on the remote hub's recorded cash,
whose records are then paid out of other markets' cash (R2H-1); a cash token that refuses
one mirror vault, which halts the canonical queue and reverts the batches bundled with that
market (R2H-2); the contract side of the last stock's margin, which changes the documented
allocation rule (R2T-1); taking the buy tax as PoolManager claims, so that any router can
buy (R2F-1, option b); having the factory deploy the $STOCKFUN vault itself (R2F-2); and
three informational points (R2H-4, R2H-6, R2H-7). A stock that can never be bought again
keeps receiving its weight of every inflow: that is by design, since retiring a leg would
change the basket's fixed weights. Since 2026-10-05 R2H-1 is covered by the settlement
tools of that day's audit loops plus a procedure, the owner pausing the remote hub before the
transfer; R2H-2 is closed by the fourth loop's non-blocking deliveries; R2F-1's option b is
declined, the tax staying as it is taken; and a vault that refuses ETH no longer halts its
market, the halt R2F-2 described: see the entries of 2026-10-05 below.
2026-10-02 — Every module upgradeable
Until then, no contract of the protocol could be upgraded. Every module but the tokens and the liquidity lock becomes upgradeable by StockFun's owner, with immediate effect: no timelock. Each is a proxy upgraded through UUPS, and asks its upgrade authority who may upgrade it: the factory on Ethereum, the remote hub on Robinhood Chain. The vaults, on both chains, are one proxy per market, upgraded market by market. The hook's proxy is mined with all 14 v4 permissions, so a later implementation can use any callback without moving the address.
The tokens carry no logic of their own: a plain ERC-20 with a fixed supply and one setter,
setRecorder, for the owner. The holding record leaves them for a new module, the holding
recorder, which every token calls on each transfer; if the call fails, the transfer fails.
Since 2026-10-05 the tokens also have rescues, for what is sent to their own address,
and that blocking call is the one deliberate exception to the founder's isolation rule (see
below).
The liquidity lock stays non-upgradeable and gains an end mode: the owner announces it, and
30 days later can recover the whole liquidity of every pool, $STOCKFUN's included, to any
address. The owner can cancel; trading goes on during the 30 days, and no new market can
launch while an end is pending. It exists for the case where the project shuts down; the
30 days are the holders' notice. The remote vault deployer on Robinhood Chain cannot be
upgraded either: every mirror vault's address derives from it.
What it changes: an upgrade does not wait for the 48 hours of emergency mode, which stays; the rules this book calls fixed, from the vaults' bounds to the burn and the 5 % rate, hold as long as the owner does not upgrade the module that carries them; the factory is deployed first and wired by the owner, which changes the deployment order. A swap costs about 27,000 more gas, measured in isolation: a buy 237,190 gas instead of 210,105, a sell 285,031 instead of 258,278 — the price of the proxies on its path and of the call to the recorder. Since 2026-10-05 emergency mode has no delay either, and the vaults' bounds and the 5 % rate are owner settings.
2026-10-04 — The airdrop contract
Coded and tested, not deployed. AirdropDistributor, an upgradeable module on Ethereum
bound to the factory, distributes each market's stocks by daily cycle. A cycle's window
closes at 13:00 UTC, before the US open all year; its purchases and sends run in the
session that follows. A cycle opens with its first send, or ahead of it, and freezes its
numbers: the holding recorder, the exclusion list in force when the window closed, and the
eligible holdings. Two paths feed it, sendToAirdrop on the mirror vault, through each
stock's LayerZero adapter, and on the Ethereum vault for the local rail; nothing else
credits a cycle.
Settled the same day: no keeper push, only the holder claims, at their own cost (TBD 5); no
cap per wallet, no minimum, no expiry (TBD 7); an exclusion list per token, set by the
owner, at most 16 addresses, with the burn address always and the vesting contract for
$STOCKFUN (TBD 8). A send for a window without eligible holdings is held aside for the
first window that has some, whoever calls and whenever, as long as the cycle hour does not
change and the token's holding recorder is not replaced in between.
Accepted and documented: the keeper chooses when, so a send that arrives after the next
13:00 UTC is measured over the next day's window; moving the cycle hour re-cuts the windows
not opened yet, those that held-aside stocks are still to look at included; a token's
holding recorder is upgraded in place, never replaced on a live token. Not in this change:
the 48-hour rule for stuck funds, the stock adapters, the keeper's airdrop step and the
dapp's claim screen. Since 2026-10-05 there is no vesting contract to exclude, the
48-hour rule is closed, and the cycle hour belongs to a schedule the owner sets, the
windows' length and closing time (setCycleSchedule); a recorder named on a live token
starts from the token's supply outside the PoolManager (see the audit loop's entry below).
2026-10-05 — No team allocation, an immediate emergency mode, every number a setting
Three decisions, coded and tested the same day, not deployed.
The team allocation goes. The team vesting contract, TeamVesting, is deleted, and no
$STOCKFUN is reserved for the team: the whole supply, 1,000,000,000 tokens by default,
goes into the single-sided locked position, as a market's whole supply goes into its two
positions. Until then 10 % went to the vesting contract over 12 months, and the airdrop
excluded that contract from $STOCKFUN's cycles; only the burn address is excluded now,
with, since that day's third audit loop, the launch operator (see below).
The emergency mode becomes immediate. emergencyTransfer moves an asset out of a vault, a
bridge hub or the airdrop contract, to any address, at once; each transfer has a sequential
id and a public event. The 48-hour schedule of 2026-08-29 is gone, with its cancellation
window, and the delay is not a parameter: an emergency acts as soon as the owner uses it,
never after a wait. Replacing the bridge adapter, changeAdapter, is immediate too, under
the same pins. The rule for stuck airdrop funds, decided on 2026-09-27 and 2026-09-28 and
never coded, is closed: the immediate transfer covers it, on any contract, at any time.
Every number becomes a setting. The protocol's numbers are now onchain settings of the owner, on both chains, today's values being the defaults: the tax, its split and the anti-snipe; the creation fee and the length limits of a new market's strings; the shape of new markets, LP fee and tick spacing included, and the shares of what the locked positions collect; the conversion threshold and the vaults' price bounds; the airdrop's schedule and limits; the oracles' heartbeats; the bridge's gas and slippage. Each takes effect at once and refuses impossible values. A change of the tax applies from the next swap on every pool, one still in its anti-snipe blocks included; a change of shape applies to the markets created afterwards, and each pool keeps the LP fee and tick spacing it was created with.
Kept fixed: the end mode's 30-day notice, END_DELAY, in the lock, which cannot be
upgraded; the units and encodings, from the basis point to the hour of the holding record;
and the wiring, from the price feeds to the bridge's endpoints, which only an upgrade can
point elsewhere.
Accepted consequence: the figures this book gives for the tax, the launch, the vaults and the airdrop are defaults, not guarantees; the owner can change each of them at any time, without notice. Measured: reading the tax settings costs a swap about 1,200 more gas, warm. See Trust model.
2026-10-05 — The audit loop's fixes
The same day, a review loop over the whole code led to contract fixes, each with a regression test, not deployed. Four of them change behaviour.
After an emergency transfer, the books are settled, never paid out of another market. The airdrop contract and the remote hub hold assets that several markets are owed. After a transfer out of either, the payouts that depend on what is missing wait — the airdrop's claims of that stock, the remote hub's payouts on the USDG rail — until the assets come back or StockFun's owner writes the loss off the cycle or the market that suffered it; on the canonical rail, the owner drops the record whose cash the transfer took before the next deposit lands. A cycle can be written down in part only while nobody has claimed the stock from it, every holder then losing the same share; once some holders have been paid, only by its whole remainder, which the holders not yet paid lose. The transfer itself stays immediate and unconditional. See Emergency mode.
Only the keeper bridges. Until then anyone could send a bridge batch: a third party could slip one in at the adapter's loosest minimum between two trades of its own on the Curve pool, or make the keeper's batch fail by bridging one of its vaults first.
Only the keeper and StockFun's owner pre-deploy a mirror vault on Robinhood Chain. Until then anyone could, even for a market that did not exist yet, tied to that day's wiring. A pre-deployed vault now takes the remote hub's stock router and oracle at its first batch.
A holding recorder named on a live token starts from the token's supply outside the
PoolManager, and a recorder that recorded the token before refuses it. Until then a fresh
recorder started from zero: every sell failed, and the airdrop shares of the cycles opened
afterwards were broken for good. Now trading continues, and the holders the new recorder has
not seen move read as having held nothing until their next move. Upgrading the recorder in
place stays the rule.
Smaller fixes: the vaults hold the keeper's own minimum on what actually arrives, not only
their own bound; the canonical bridge's accounting ticket pays for the batch's size; the
settings refuse a launch tick no pool can open at, an empty name bound, and a hook and a lock
on different PoolManagers; a change to longer airdrop windows no longer blocks a market
with stocks held aside. See Trust model.
2026-10-05 — The audit loop's second pass
The same day, a second review loop over the fixes led to more contract fixes, each with a test, not deployed, and to keeper changes.
The books count what backs them, never the balance. The balance of the airdrop contract, and
of the remote hub on the USDG rail, also holds tokens that have arrived but are not credited
yet: a delivery whose last step has not run. After an emergency, such tokens could reopen a
drained cycle's claims and pay them with another market's stocks. Both contracts now count
what backs their books themselves. An emergency transfer takes from that first; what it takes
beyond it came from tokens not credited yet, and the next deliveries pay it off before they
back anything, the remote hub recording those batches instead of delivering them. Assets come
back through restore, which anyone can call; a plain transfer backs nothing, so stray tokens
are rescued only to be restored. See Emergency mode.
After a write-off of a cycle's whole remainder, which follows a claim, whatever reaches that cycle afterwards is shared pro rata among all its holders, as if the written-off amount had never been there; until then it went first to the holders not yet paid, in the order they claimed. A write-off that leaves nothing held aside for a market ends its search for a window.
A token refuses a holding recorder bound to another Uniswap PoolManager than its pool's,
which would count the pool as a holder and pay every holder a fraction of their share. And a
recorder named on a live token keeps a recorded supply at least equal to the holders' total,
not equal to it, until every holder has moved.
On the canonical bridge, the fee is quoted at a base fee the keeper names, twice the latest one, with the excess refunded: a quote read without a gas price saw a zero base fee, and a long batch failed. The remote hub learns the keeper only from a batch: before the first one, StockFun's owner pre-deploys the first market's mirror vault, and a market whose vault the keeper cannot pre-deploy is left out of its batch, with an alert. The keeper counts a vault's failures on Ethereum and on Robinhood Chain separately.
Of the second round of 2026-10-01: R2H-1 is covered by the settlement tools plus a procedure, the owner pausing the remote hub before the transfer; R2H-2 is mitigated, still open: a frozen mirror vault no longer halts the canonical queue for good, and only the keeper builds a batch, but a delivery to that vault still fails whole, so the keeper must leave that market out. See Trust model. Closed the same day by the fourth pass: see below.
2026-10-05 — One failure never blocks the rest
The founder's design rule, set the same day: when something makes one function fail, the other functions must not pay for it; everything must keep working, and everything must have a lever to recover lost funds and take the fix. It has two parts. Isolation: a failure in one function, market, stock, cycle, record or recipient never blocks the others; the item is skipped, kept as owed or deferred, with an event, and the rest goes on. Levers: every contract that can hold ETH or tokens, even in passing or by mistake, has a lever to move out what is stuck, and every module can take a fix, an upgrade or a setter that swaps it. From the fourth audit loop on, the loops count any breach as a defect.
One deliberate exception: a token's report to its holding recorder stays blocking, a failing
report failing the transfer. A report allowed to fail would let a holder starve it of gas on
their own transfer, so that the record skips the move and their airdrop share grows. The
lever is immediate: setRecorder(0) on the token, or an upgrade of the recorder in place,
one transaction each.
Accepted consequences: a refused share waits for its recipient instead of stopping the rest; an emergency transfer charged to no market makes the payouts of that asset wait until it is settled; and while a token has no recorder, the airdrop opens no cycle of it and holds its stocks aside. See Architecture.
2026-10-05 — The audit loop's third pass
The same day, a third loop, run over sequences: operations correct alone that go wrong in some order or timing. Each fix has a test, not deployed.
restore pays back first what an emergency transfer took beyond what backs the books, on the
airdrop contract and on the remote hub's USDG rail, and backs the books with the rest: giving
back a waiting delivery's tokens no longer pays the drained market with them. A stock written
off a cycle whole before anyone claimed it leaves that cycle's list.
A holding recorder named on a live token starts a new record at the switch: the airdrop measures no window that started before it, whose stocks wait, held aside, for the first window it covers in full, and the token reports the burn address's balance at the switch. Until then such a window, measured from the switch only, overpaid whoever moved after it. Procedure: open the cycles of the windows already closed first, then switch right after a window ends; upgrading the recorder in place stays the rule.
The $STOCKFUN launch script excludes the launch operator from the airdrop, whose first
cycle paid that holding until then. The bridge scripts stop before deploying when the
factory already names another hub, and map the stocks before naming the hub; setBridgeHub
refuses a hub that cannot carry a registered basket. Offchain: a transaction whose receipt
cannot be read is followed by its hash and never sent twice; the emergency watch reads its
events in chunks, and alerts once when it fails and once when it recovers; a market whose
batch entry cannot be built is left out, with one alert. In the app, an unconfirmed
transaction keeps its hash (since the tenth audit loop it is followed to what is mined at its
nonce, a cancel in the wallet never read as done: below), and a pool the end mode recovered
shows neither price nor trading.
2026-10-05 — The audit loop's fourth pass: the rule in the code
The same day, the fourth loop put the founder's rule in the code. Each fix has a regression test, not deployed.
Bridge deliveries never block another market, the owner's decision on R2H-2: a share the
cash token refuses to a mirror vault stays on the remote hub, owed to its market, and the
others are paid; on the canonical rail it leaves the queue, so the records behind it are
paid. Anyone pays it later with sweep. An emergency transfer on the remote hub can be
charged to one market, whose books are settled in the same call. Claims pay what they can:
a stock the books are short of, or whose transfer is refused, is deferred and stays due, and
claimMany skips a cycle that excludes the caller. Each purchase leg, each stock of a send
to the airdrop and each market of a bridge batch goes on its own. A vault that refuses ETH
no longer halts its market: the hook keeps what it could not pay as owed to that vault, the
lock keeps a refused share of a fee collection for its recipient, and anyone pays them; a
creator contract that cannot take ETH claims to another address. The Lens reads one vault at
a time.
Levers everywhere: a rescue of what a module holds by mistake on every module that keeps nothing of anyone's, on the hook for strays only, on the lock never for the shares it keeps, on the burner for its ETH only once no burn could spend it, and on both tokens; the vaults, the hubs and the airdrop contract keep emergency mode.
Also: the Ondo router serves only its factory's vaults (M-7), and every router refuses
itself as the recipient (L-4); the canonical rail's deposit ticket is priced at the base
fee; the $STOCKFUN launch script lists the operator before the mint; the storage check
compares every depth of every struct. Decided the same day: M-2 and R2F-1 stay as they are.
See Emergency mode and Trust model.
2026-10-05 — The keeper's airdrop step and the claim screen
Written the same day, at the founder's request, with no new decision; not deployed. The keeper sends each market's stocks once per window, after the US session by default: held-aside stocks placed first, then the cycle opened, then the send, each stock and each market on its own, and each delivery from Robinhood Chain followed until it is credited, with an alert that carries the command to run a late one again. The dapp's claim screen lists what a wallet can claim, window by window, and claims up to ten windows per transaction, about 3.4 to 3.5 million gas measured cold. Left out: running a failed delivery's last step automatically; the alert gives the command instead.
The fourth loop's offchain pass then gave the keeper its new duties: paying the debts the hook and the lock keep, alerting on failed legs, repeatedly skipped markets and refused deliveries, planning each stock leg on its own, a state file across restarts, and the daily collection of LP fees. The app shows "Figures unavailable" for a vault that cannot answer. See The keeper and The dapp.
2026-10-05 — The audit loop's fifth pass
The same day, a fifth loop found seven low-severity issues, each fixed with a test, not deployed. The remote hub's transfer charged to a canonical record is for a record whose deposit has landed: it refuses an amount beyond the cash not held for refused shares, and a record whose deposit is lost is written off. A stock whose balance cannot be read no longer stops a send to the airdrop, nor its quote, and neither does an adapter without code in the quote. The implementation contracts of the factory and the remote hub, which keep their admin in the proxy's storage, take their deployer as the lever for what is sent to them. The lock and both tokens gain rescues for v4 claims and NFTs sent to them by mistake; the lock still has no generic call. A bridge batch leaves out a market whose vault has no code yet. A comment that misstated how a purchase moves funds was corrected. See Emergency mode.
2026-10-06 — The fifth loop's offchain pass
The fifth loop's review of the offchain code found three medium-severity and sixteen low-severity issues, each fixed the same day with a test, not deployed. The keeper now holds to the cadence of 2026-09-27: a vault's ETH converts once per airdrop window, if the vault holds the threshold at the keeper's first pass after the window closes; below it, the ETH waits for the next window. Until then the keeper converted it at any pass of the session once the vault held the threshold. Every transaction the keeper sends is written to its state file, with its nonce, before its receipt is awaited, so a keeper killed while it waits does not send it again; one that no node knows any more is let go after a bound; one keeper at a time uses a state folder. On the Ondo rail, a purchase Ondo prices under the vault's bound is no longer sent to fail, nor paid an attestation. A stock whose adapter does not answer stays out of an airdrop send on its own.
A basket holds at most five stocks, a technical bound StockFun's owner can change by an
upgrade: every cost that grows with a basket is measured up to that size, and the three
launch baskets hold three, three and two. The Lens carries each vault's state and what the
hook and the lock owe it, and its upgrade goes live before the Worker and the app that read
it. The $STOCKFUN launch script resumes a run that stopped before the protocol market was
named, instead of minting a second token. The app counts the ETH owed to a vault in its
treasury, leaves a claim room for a stock credited before it is mined, remembers a stock its
token refused, and never shows a figure it could not read as nothing. See
The keeper, Baskets and The dapp.
2026-10-06 — The sixth audit loop
The sixth loop found one high-severity and five low-severity issues, each fixed the same day with a test, not deployed. On the canonical bridge, used on the testnet and as a fallback, the keeper stopped replaying a batch's tickets once its markets were credited, and a market could be credited with another batch's cash, so a deposit that missed its automatic execution could expire. The keeper now watches every ticket until it knows it redeemed, whatever its transfer's credit, replays one still live and alerts when one is not known redeemed after six hours by default, or is lost; it credits a canonical transfer by the remote hub's own events only, never by a balance.
One decision came with the fixes, taken by the audit as its recommended default: the cadence of 2026-09-27 reaches the USDC. A vault's USDC, bought on Ethereum or sent across the bridge, goes once after each conversion of its ETH, and at most once per window otherwise; until then a few units of USDC sent to a vault made the keeper send a transaction, or a whole bridge batch, at every pass. The purchases on Robinhood Chain still run at every pass: a mirror vault's balance cannot tell a delivery from a gift, and the caps on each purchase spread a large delivery over several passes on purpose. Local and testnet runs turn both cadences off with the same switch.
Also fixed: a search for held-aside stocks that would place nothing, for a vault with nothing
to send, is no longer sent every day; a vault under the threshold is checked on a balance read
after the window closes; earlier the same day, the local deployment file is checked against
the chain before use; the app
marks the $STOCKFUN price "(last read)" while the Lens cannot read its treasury, and the
worker records no price for it meanwhile. The next loop, started the same day, found that the
keeper quoted every vault on one router while each vault keeps the router it was created
with: each vault is now quoted on its own. See The keeper.
Since the seventh loop, below, "nothing to send" means nothing worth sending, and the keeper reads each ticket from the receipt of its creation first.
2026-10-06 — The seventh audit loop
The seventh loop found one medium-severity and fourteen low-severity issues, each fixed the same day with a test, not deployed; the contracts were clean. On the canonical bridge, the keeper read whether a ticket still existed before reading the receipt of its creation: a deposit created between the two reads, or seen by two nodes a block apart, could pass for executed while it was still live, and expire with no replay and no alert. It now reads them in the order the Arbitrum SDK does: the receipt first, then the automatic execution, then the ticket at a block no earlier than its creation.
One decision came with the fixes, taken by the audit as its recommended default: the keeper
sends to the airdrop only what is worth what sending it costs. A stock above the dust the
LayerZero bridge cannot carry, valued with its vault's own oracle at its price feed's last
answer, must be worth its own LayerZero fee, or its share of the send's gas on Ethereum, and
the stocks that pass must together be worth the whole send plus the opening of the window's
cycle when it is not open yet; otherwise they wait in the vault for a later window, with what
accumulates. A value the keeper cannot read lets the send go, as before. The multiple is a
keeper setting, KEEPER_AIRDROP_MIN_VALUE_BPS: once the cost by default, 0 turning the rule off.
It decides only when a stock goes, never how much, what or where. Until then a gift of stock
just above the dust to the vault of a market nobody trades made the keeper open that window's
cycle and pay a LayerZero message every day; and the same dust counted as "something to send",
so the limit on held-aside searches of the sixth loop did not hold on the bridge.
Also fixed: every log search of the keeper stops a few blocks below the chain's latest block, so
an event is never missed behind a node a block or two late; the keeper checks that each RPC
serves the chain its configuration names, and refuses to start otherwise; its alert webhook has a
time limit and its answer is read, an alert it does not take being kept and sent again; an
empty setting takes its default. The app's data Worker reads each upgradeable contract on its
own, so one that fails after a faulty upgrade no longer blanks the $STOCKFUN token's figures;
it shows the 24-hour volume as unknown while its reads of the trade logs keep failing, checks
that each of its endpoints serves its chain, and the price chart places each point at its time.
The Lens and the Worker's data schema do not change. See The keeper and
The dapp.
Since the eighth loop, below, a stock whose own quote is zero is asked about on its adapter, the opening of the window is counted once on the local rail, the ticket is read a few blocks below the latest one, and the keeper no longer assumes a chain identifier when none is set.
2026-10-06 — Price-feed protections on Robinhood Chain
The launch plan listed two protections the oracle did not have yet, both recommended by Robinhood's documentation. They are built, not deployed, as settings of StockFun's owner that start off; each can only hold a price back, never change one.
- A corporate action. While a stock goes through one, its token says its oracle is paused, and its price feed holds its last value, which can still look fresh while the token's multiplier changes. The oracle now holds that stock's price back: its purchase leg fails alone, its cash kept for it, and the basket's other stocks are bought. The check is on for every stock of the Robinhood rail, per stock, so that a token whose answer cannot be used, or whose flag stays set, can be switched off alone. A token that does not answer counts as not paused: Robinhood calls the flag advisory, and the feed's own staleness check stays the main guard.
- The sequencer. Robinhood Chain is an Arbitrum chain with a single sequencer. Given Chainlink's L2 sequencer uptime feed, the oracle holds every price back while the sequencer is down, came back up within the grace period (an hour by default), or the feed cannot be read. No such feed exists for Robinhood Chain, and Chainlink is no longer adding them to new networks: the rail is deployed with this check off, by an explicit choice the deployment script requires, and StockFun's owner sets the feed if one is ever published.
On Ethereum both stay off. See The Robinhood rail.
2026-10-06 — The eighth audit loop
The eighth loop found one medium-severity and eight low-severity issues, each fixed the same day with a test, not deployed; the contracts and the price-feed protections were clean. Before the app had read the chain's basket registry, its launch form offered the configured baskets under their configured numbers: on a deployment numbering its baskets otherwise, a creator could launch a market, irreversibly, on another basket than the one shown. The form now offers only the baskets read from the chain, and reads the chosen one again from the factory just before sending, which it refuses if the name or the stocks differ.
Also fixed: the keeper follows a bridge batch only once the block that holds it is a few blocks deep, so a reorganization of Ethereum's newest blocks can no longer leave it watching identifiers that never exist; it tells the dust the bridge cannot carry from a stock whose own LayerZero fee cannot be quoted, which it now leaves out with an alert instead of dropping it in silence; it keeps one waiting alert per distinct alert, with how many times and when it was raised, so alerts repeated at every cycle no longer push a one-off alert out; it no longer sets aside a state file written for another chain before checking which chain its RPC serves, and requires both chain identifiers; it reads a ticket a few blocks below the latest one, which every node of an endpoint has; and on the local rail it counts the opening of the window once. The keeper, its preflight and its inspector learnt the price-feed protections: the purchases on Robinhood Chain wait while the oracle holds prices back for the sequencer, with one alert, and a stock in a corporate action waits alone, never counted as a failure. The app's data Worker never moves its trade window back, so a node a few blocks behind no longer makes the 24-hour volume count blocks twice; its RPC proxy refuses any address that is not one; it publishes why a stock's price is missing (data schema 8), which the app shows; and the trade panel charges a whitelisted wallet the normal tax during the anti-snipe window, as the hook does. The Lens does not change, and the Worker and the app still deploy in either order. See The keeper and The dapp.
2026-10-06 — The airdrop delivery's gas under Glamsterdam
Sepolia activated Ethereum's Glamsterdam upgrade on 2026-10-06, during the LayerZero testnet
run (below); Hoodi and mainnet had no date yet. It reprices the growth of state: a new storage
slot written cold costs 110,020 gas, against 22,100 before. Every fixed gas figure of the
contracts and the scripts was measured again on Sepolia, and all hold but one pair, the gas an
airdrop delivery gets on Ethereum: the mirror vault's send carried a single compose figure,
600,000, and the stock OFT's lzReceive got only what that OFT enforces. Every delivery of the
run's first cycle ran out of gas at LayerZero's executor and was run by hand.
The tenth audit loop found two more afterwards: the deployment scripts' own gas and the
bridge compose's for a batch (below).
The founder's decision, the same day: the keeper simulates each delivery and chooses its gas, clamped into bounds StockFun's owner sets on the remote hub; a delivery still stuck is re-executed by the keeper, and alerted on a second failure. In the code, not deployed on mainnet:
- The remote hub holds two gas policies, each a default, a floor and a ceiling: the
lzReceivegas on top of what the stock's OFT enforces, 650,000 between 200,000 and 1,500,000 (setAirdropReceiveGas), and the compose gas, 1,250,000 between 600,000 and 4,000,000 (setAirdropComposeGas). The mirror vault'ssendToAirdrop(stocks, receiveGas, composeGas)sends what the hub grants for what the keeper asks, zero taking the default; the call with the stocks alone takes both defaults. A hub upgraded from before the policies refuses every send until both are set - The keeper simulates each stock's
lzReceiveand compose on Ethereum, from the endpoint's address, and asks for the need plus 25 %; when a simulation cannot run, it takes what the market's last deliveries used, then the hub's defaults. A delivery that stays stored on the endpoint, once LayerZero's executor has failed it or after ten minutes, is run again from the keeper's own key, with its simulated need plus 25 %, at most 4,000,000 gas by default; one that cannot go is alerted with the command to run it by hand, and a second failure is alerted and never sent again - The app claims five windows per transaction instead of ten, leaves 400,000 gas per stock a window may still receive, and added 150,000 gas to the estimate of every write, which the ninth audit loop replaced with each write's own limit (below)
The fix was applied to the testnet run's remote hub and mirror vault by upgrade, and carried its later cycles. See The keeper and The Robinhood rail.
2026-10-06 — The LayerZero testnet run
The protocol was deployed on Sepolia and on Robinhood Chain's testnet, by the production scripts or testnet wrappers that keep their body, in 167 transactions, all successful, with test stocks, test adapters, a test USDG and mock feeds, and run from 13:50 to 19:07 UTC: seven hourly windows, each one's ETH converted, bridged over LayerZero and spent on the test stocks; six airdrop cycles sent back over LayerZero, the first four claimed in full by five holders, each payout exactly the share computed from the holding recorder; the incident drills played on live messages and recovered as documented, but for two halves. It proves StockFun's code over LayerZero's real endpoints, DVN and executor. It does not prove Paxos's USDG pair, Robinhood's stocks and their adapters, real feeds or liquidity, nor mainnet's gas, fees and finality: a mainnet trial remains. What it found in the production code is the Glamsterdam repricing (above) and part of the ninth audit loop (below). See The Robinhood rail.
2026-10-06 — The ninth audit loop, and the verifier gate
The ninth loop reviewed the eighth loop's fixes, and took the findings of the LayerZero testnet run's operator. It found two medium-severity issues, the airdrop delivery's gas (above) and the app's data Worker rebuilding its RPC failover at every eviction of its Cloudflare object, which, during an outage of the primary RPC, sent it 37 requests in under seven minutes where 8 now go; and low-severity issues in the keeper, the Worker and a testnet script, each fixed the same day with a test. For a minute after one of its own transactions, the keeper reads what it changed, and estimates the gas of what it sends next, at that transaction's block, so a node a block behind no longer turns the bridge batch sent right after a conversion into a false failure; each of its transactions goes with its estimated gas plus 25 %; the alerts it could not deliver go in the order they were last raised; a ticket its own redeem deleted is no longer alerted as live; a chain time no date can hold reads "an unknown time"; it decodes the oracle's errors; and its preflight runs on the testnet run's deployment. The Worker records Robinhood Chain's own block number.
Since this loop a second agent reviews each fix's change before it is pushed, against the classes of defect the earlier loops found, most of them in the fixes of the loop before: the verifier gate. What it finds is fixed like a review finding, and that fix goes through the gate again. In this loop it found low-severity defects in several fixes, among them a re-execution that believed an alert anyone can emit, now believed only from LayerZero's executor, and one that decided a failed run at that run's own block, which a reorganization of the block could have turned into a false second failure: it now waits until the block is a few blocks deep. All are fixed.
The loop's review of the Worker and the app then found one more medium-severity issue, the app's 150,000 gas over a write's estimate falling short when a trade meets more new storage than the hour's supply mark, fixed the same day: every write now gets its estimate plus 50,000 gas, and a trade also the gas of each write its estimate cannot see that can still happen, read at the estimate's block; a write whose estimate fails goes with a fixed limit, and a launch then waits. Its two low-severity findings were fixed too: a pot that is an estimate is marked "+" wherever it shows, and an approval that cannot be read is never taken for none. The loop is not clean, and the count of clean loops stays at zero. See The keeper and The dapp.
2026-10-06 — The tenth audit loop
The tenth loop reviewed the ninth loop's fixes and the work on Ethereum's Glamsterdam upgrade, each area from an angle of its own: the contracts under the new gas prices, every cross-chain message failing at its destination, the keeper over months, the app with real wallets, and the documents against the code. It found three medium-severity issues and six low-severity ones, each fixed the same day with a test, every fix reviewed by the verifier gate, not deployed on mainnet. The contracts were clean on the gas angle under both price schedules.
- The deployment procedure under Glamsterdam. The documented Ethereum deployment could not succeed: the deployment tool gave each transaction the gas of its own local simulation, at the old prices, where a contract creation now needs four to seven times that. Every broadcast now asks the node for each transaction's gas once the previous one is mined; played on Sepolia
- A bridge batch's compose gas. A bridge batch's last step on Robinhood Chain got one fixed gas figure, 1,200,000, whatever the batch held: four new five-stock markets ran it out of gas, their USDG then sat on the remote hub under no record, and every later batch with the same markets failed the same way. Now the gas is a base plus a part per market, 200,000 and 400,000 by default, both settings of StockFun's owner, and a batch carries at most 17 markets, which keeps its message under LayerZero's size limit and its gas under Robinhood Chain's limit per transaction; the keeper sends at most that many and leaves the rest for its next pass, the markets waiting longest first. The extra gas costs little: LayerZero's executor charges it at Robinhood Chain's gas price, 0.00001 ETH per million gas on the testnet. The keeper's alert for a stalled transfer now says where the batch's message stands. The testnet's adapter was upgraded the same day and its next batch went through at the new gas
- The emergency watch. The keeper named every watched vault in one log request, which an endpoint refuses past its limit: once the registry outgrew it, no emergency transfer would have been relayed again. It now names them in groups, bounded by a new setting
- Smaller fixes. The keeper's error texts keep an RPC address's host only; its reads of every market go through Multicall3, a hundred markets a call, instead of one by one at every pass; the app follows a transaction the wallet cancels or replaces, never showing a cancel as done, and for a minute after its own transaction reads at no earlier than that transaction's block, so a sale right after its approval is no longer refused by a node a block behind; and two documents were brought up to the code
With its default of sending after the US session, the keeper now reads a vault with nothing to send after the close only at the next session. A consequence, of that saving and not a new rule: a stock given to a vault after the close, outside any purchase, goes with the next session's send, into a later window; what the vault's own purchases bring still goes in the window that ended that day.
The audit also weighed a recommendation, not a defect: claims that leave one wei in each of the hook's fee accruals, so that the next trade never pays to write those storage slots from zero. It is not adopted now. The extra gas falls on the first trade after each claim, about 104,000 a slot, and the app's margin for it raises a trade's limit, not what it pays; the change would touch claim code whose formal rules could not be proved again without a prover run; and the hook and the burner can take it later by upgrade, with no migration. The loop is not clean, and the count of clean loops stays at zero. See The keeper, The Robinhood rail and The dapp.