OSKET
docs · read before you launch

How the royalty market holds.

The contracts, the four facts from Pons’s source they rest on, the tests, the deployment path and the security notes. Everything below is generated from the repository’s README and SECURITY files on every build.

factory 0x386B44bb8fbe812A60429F804ADf051B5DaA70EE · chain 4663 · sourcify

Every token launched on Pons V2, the largest launchpad on Robinhood Chain (4663), pays its creator a cut of every trade for as long as the token trades: 70% of the 1% hook fee, plus a creator tax of up to 10% set at launch. Pons writes that stream into its own contracts and pays it into an escrow the recipient claims.

OSKET turns one launch's stream into 1,000,000 transferable royalty shares. The vault that holds the stream is the launch's fee recipient and has no function that hands it on — Pons only lets the current recipient move the recipient — so the stream stays where it is. Creators sell shares for ETH today; holders harvest the fees and can take them as a stock token (NVDA, SPY, …) through a swap whose minimum they set.

Status: deployed on Robinhood Chain at 0x386B44bb8fbe812A60429F804ADf051B5DaA70EE. A share of someone else's trading revenue is almost certainly a security; not offered to persons in the United States or anywhere else it is not lawful. Nothing here is investment advice.

What is in here

pathwhat it is
contracts/src/RoyaltyVault.solthe shares (ERC-20, pull-based per-share accrual that travels with transfers), harvest(), claim(), claimAsStock(minOut), the creator's listing and 30-day vesting of sale proceeds. No owner.
contracts/src/RoyaltyFactory.sollaunchWithRoyalties (a new Pons launch with the vault as recipient from block one) and prepareAdoption / finishAdoption (an existing launch whose recipient moves the stream with Pons's own call)
contracts/src/interfaces/IPons.solthe slices of PonsV2LaunchFactory, the fee escrow, the meme hook and Uniswap's SwapRouter02 the vault uses, matching the verified contracts on 4663
contracts/test/RoyaltyVault.t.sol17 tests against mocks, including accrual travelling with transfers, the claimer-set swap minimum, re-entrancy, vesting, adoption order, the Pons owner override, a USDG launch and a fuzz over conservation
contracts/test/Fork.t.sol3 tests on a fork of chain 4663 through the real Pons factory, curve, escrow and SwapRouter02: a launch through Osket makes the vault the recipient and the creator cannot move it; a real curve buy accrues creator tax that harvest sweeps and holders claim in ETH and as NVDA; an existing launch is adopted in three steps
contracts/SECURITY.mdan internal review: the threats considered and what holds against each
research/local-rpc.cjsa local forwarder to the public RPC for the fork tests, resolving the host over DNS-over-HTTPS (this ISP hijacks it)
contracts/scripts/deploy-royalties.cjsthe only script that can send a transaction. Dry-run unless --yes; cross-checks Pons's feeEscrow() and memeHook() live before simulating
keeper/royalties-keeper.cjsharvests vaults whose pending credit (creator tax still on the curve plus the escrow balance) clears a floor, finishes adoptions once Pons pays the vault, raises an alarm on owner-override proposals. Dry-run unless --send
research/creator-fees-24h.cjsreads 24 hours of PoolFeesSwept from the meme hook: what Pons paid creators, by quote asset, and the top streams
research/stock-routes.cjswhich stock tokens a claim can be taken as: the official Robinhood asset list checked against symbol() on chain, and the best Uniswap v3 path from WETH and from USDG by live QuoterV2 quotes → evidence/stock-routes.json. The page ships only tokens with a route both ways
site/the page: template.html + build-site.cjsindex.html, royalties.html, public/. Reads the chain from the browser; transactions sign through the reader's own wallet and go only to the factory, a vault, or Pons. Its motion (masked headlines, the stream that draws on scroll, counters, the live block number, the tape of top streams) is transform and opacity only; the block number and every figure still trace to the chain or an evidence file
site/public/api/rpc.jsa read-only JSON-RPC relay (bounded eth_getLogs) for readers whose network cannot reach the public RPC host
site/deploy.html + site/serve.cjslocal only: deploy the factory with the wallet in your browser; the page verifies the mined factory and the local server writes the deploy record
contracts/scripts/verify-sourcify.cjspublishes the exact standard-JSON build to Sourcify for a deployed factory or vault
contracts/scripts/verify-vaults.cjslists every vault the factory has deployed and publishes the ones Sourcify does not know yet; part of the scheduled refresh
scripts/refresh-evidence.cjs + scripts/refresh.cmdre-measure fees and routes, verify new vaults, rebuild page and docs, test, deploy, commit; the .cmd is what Task Scheduler runs every 6 hours
keeper/Dockerfilethe keeper as a container (dry-run without a key)

The four facts it rests on

Read from PonsV2LaunchFactory (Sourcify exact match, 0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e):

  1. transferCreatorFeeRecipient(token, new) reverts unless msg.sender == launch.creatorFeeRecipient. The vault is the recipient and never calls it.
  2. setCreatorFeeRecipient is onlyOwner (Pons's 2-of-3 multisig) behind CREATOR_FEE_RECIPIENT_TIMELOCK = 3 days, with a public CreatorFeeRecipientChangeProposed event. That override is the one thing that can move a stream; overridePending() exposes it and the keeper alarms on it.
  3. _payOut credits the fee escrow (credit{value} / creditToken); the recipient calls claim() / claimToken(). harvest() does exactly that.
  4. sweepPoolFees and the curve's sweepFees accept the recipient as caller when no internal swap is needed; Pons's sweep operator does the rest, into the same escrow.

Tests

node research/local-rpc.cjs &                     # forwarder for the fork tests; without it they skip
cd contracts && npx hardhat test solidity        # 17 Solidity tests against mocks + 3 on a fork of the live chain
node --test keeper/royalties-logic.test.cjs      # 4 keeper tests
node --test site/site.test.cjs                   # page tests: figures match files, transaction targets, relay is read-only
node --test site/abi.test.cjs                    # the page's ABI strings match the compiled contracts (needs artifacts)

Reproducing the evidence

node research/creator-fees-24h.cjs               # -> evidence/creator-fees-24h.json
node site/build-site.cjs                         # rebuilds the page from it

Read-only. The RPC goes through curl --doh-url because the local ISP hijacks DNS for the RPC host; override with RH_RPC.

Deploying

Done on 23 Sep 2026: the factory is at 0x386B44bb8fbe812A60429F804ADf051B5DaA70EE on chain 4663 (see contracts/deploy/royalties-4663.deployed.json). To deploy again elsewhere, two ways, both run by a human:

With the wallet in your browser (no private key ever touches a file or a terminal):

cd contracts && npx hardhat compile && cd ..
node site/serve.cjs                       # then open http://localhost:4521/deploy

The page loads the compiled factory, runs the same live checks as the script (Pons answers, its escrow and hook match the pins, the router has code), lets you set the treasury, estimates gas, and hands one creation transaction to the wallet. When it is mined it reads the factory back, checks every immutable, and only then writes contracts/deploy/royalties-4663.deployed.json through the local server. It refuses to overwrite an existing record.

With a key file, if you prefer a terminal:

cd contracts
node scripts/deploy-royalties.cjs --chain 4663                        # dry run
node scripts/deploy-royalties.cjs --chain 4663 --treasury 0x… --yes   # signs with DEPLOYER_PRIVATE_KEY or --key-file

Afterwards, either way:

node contracts/scripts/verify-sourcify.cjs --address 0x… --tx 0x…    # publish the source on Sourcify (exact match)
node site/build-site.cjs && node --test site/site.test.cjs           # the page picks up the record and drops "not deployed"
node keeper/royalties-keeper.cjs --factory 0x… --once                # dry run of the keeper
node keeper/royalties-keeper.cjs --factory 0x… --send                # the keeper for real, with KEEPER_PRIVATE_KEY
docker build -f keeper/Dockerfile -t osket-keeper . && docker run -e FACTORY=0x… -e KEEPER_PRIVATE_KEY=0x… osket-keeper

site/abi.test.cjs checks that every function the page calls exists in the compiled contracts with the same selector and outputs, so the page and the bytecode cannot drift apart unnoticed.

Limits

Not affiliated with Pons, Robinhood Markets or Uniswap Labs.

Security notes

An internal review. Written 23 Sep 2026 against RoyaltyVault.sol and RoyaltyFactory.sol.

What the contracts are trusted with

Threats considered

threatwherewhat holds
Re-entrancy on claim / claimAsStock / buy / claimProceeds / harvestvaultone nonReentrant lock on every state-changing entry point; state is updated before any external call; the test suite includes an attacker that re-enters claim from receive
A share moved after accrual takes its accrual with itvaultpoints-per-share accumulator with signed per-holder corrections applied in _mint and _transfer (the ERC-2222 pattern); tested across transfer, claim and second transfer
Roundingvaultthe accumulator floors; at most a wei or two per harvest stays in the vault; the fuzz test bounds it and asserts the vault always holds what it owes
A harvester steals the swapvaultthere is no shared swap. harvest only claims from the escrow; the swap into a stock token happens per claimer, on their own claim, with a minimum they set
A creator re-points the stream after selling sharesPonstransferCreatorFeeRecipient reverts unless msg.sender is the current recipient; the vault is the recipient and has no code path that calls it
Pons's owner re-points the streamPonspossible: setCreatorFeeRecipient is onlyOwner behind a 3-day timelock with a public event. Not preventable by design; overridePending() and isRecipient() expose it; the keeper alarms on it. Already-harvested funds are unaffected
A creator wash-trades fees, sells shares, stopsmarketnot preventable on chain; sale proceeds vest over 30 days from the first sale; every sweep is a public event; the page shows the 24-hour history
A malicious payoutPath at launchvaultthe path is set once by the creator and only affects claimAsStock; a claimer can always use claim() instead; a path that routes through a bad pool can only hurt the claimer who sets a low minimum
Non-standard quote tokens (fee-on-transfer, no bool return)vaultPons only approves pair tokens it has checked; buy and _pay require a true return; a fee-on-transfer quote would break exact accounting — such tokens are not in Pons's approved list today
A vault bound to a token that is not a Pons launchfactorybind reads getLaunchedToken and requires exists and creatorFeeRecipient == vault
finishAdoption racing an unrelated transferfactorybinding requires Pons to already pay the vault; the adopter recorded at prepareAdoption is the only address the shares are minted to
Escrow claim reverts (escrow paused/upgraded)vaultharvest reverts; nothing is lost; funds stay in Pons's escrow under the vault's name
Sweep requires Pons's operatorvaultharvest wraps the sweep calls in try/catch; the operator's own sweeps land in the same escrow
Griefing harvest with dustvaultNothingHarvested reverts when the escrow holds nothing; there is no per-call fee to grief

Not covered