# thru > Pay gas in USDG on Robinhood Chain (chain id 4663). Immutable ERC-4337 paymasters with no owner, > no markup over what gas cost, and no API key. A second, optional paymaster gives gas away entirely, funded by the creator > fees of a token launched on Pons v2. Status as of 2026-09-08: contracts written and tested, not yet deployed. Every address below marked "predicted" is a CREATE2 prediction, not a live contract. ## The problem it solves Gas on Robinhood Chain is paid in ETH. Users hold USDG and tokenised stocks. The barrier is denomination, not cost: one transaction costs about 0.14 USDG. Robinhood subsidised gas for wallet swaps from mainnet launch until 2026-09-29; after that users must pay their own gas. ## Two paymasters | Contract | What it does | Who pays | |---|---|---| | UsdgGasPaymaster | Charges the user in USDG at the pool's 30-minute TWAP, no markup | the user | | ThruSponsorPaymaster | Pays the gas outright, up to a daily budget | the token's creator fees | A wallet chooses which one to name in `paymasterAndData`. When the sponsor's budget for the day is gone, the USDG paymaster still works, so nothing stops. ## UsdgGasPaymaster - Price source: Uniswap V3 ETH/USDG 0.01% pool, 30-minute TWAP. No Chainlink, no signer, no admin value. - Validation pulls `maxCost x cachedPrice x 1.05` USDG. postOp refunds down to the real cost. - If spot deviates from TWAP by more than 3%, the user is billed the cheaper of the two. - Price cache is valid 6 hours, enforced by the EntryPoint through `validUntil`. No TIMESTAMP in validation. - The one key in the system: `ThruFeeSplitter.GUARDIAN` may `proposeRecipient(to)`; anyone may `executeRecipient()` after `HANDOVER_DELAY` (3 days); the guardian may `cancelRecipient()` before. It calls the Pons factory's `transferCreatorFeeRecipient(token, to)` and nothing else — no access to the reserve, deposits, ratios or budget. `GUARDIAN_UNTIL` = deploy + 180 days; after that proposals revert `GuardianExpired` and the splitter is ownerless. Zero guardian disables the path. - Recovers about 99% of what it spends. The unbilled 1% is the EntryPoint's penalty on unused call gas. - Has no `owner`, `withdraw`, `unlockStake`, fee setter, oracle setter, proxy or pause. - Compatible with EntryPoint v0.7 and v0.8, one instance each. ### paymasterAndData layout ``` [0:20] paymaster address [20:36] paymasterVerificationGasLimit (uint128) recommend 120_000 [36:52] paymasterPostOpGasLimit (uint128) recommend 100_000 [52] mode: 0 = allowance, 1 = permit [53:85] permit value (uint256) mode 1, must be >= quoteMaxCharge(maxCost, maxFeePerGas) [85:117] permit deadline (uint256) mode 1 [117] v mode 1 [118:150] r mode 1 [150:182] s mode 1 ``` ### Integrating What it makes possible (each with the piece it leans on): - Payroll/remittance in USDG — recipients act without ETH (USDG paymaster, open tier). - Shop checkout — the shop `allow(customer)`s, first purchase costs the price on the tag (patron). - Recurring buys of tokenized stocks — app places the token for a larger share, sponsors users (patron, token bonus). - Games/social — wallet at signup, first actions free on the open tier, heavy users on the app's share. - Airdrop/mint pages — project vouches for its list; claims paid from its capped share (patron). - Tipping/donations — a $1 tip costs $1 on the open tier (open tier, USDG paymaster). 1. `quoteMaxCharge(maxCost, maxFeePerGas)` returns the USDG the sender must hold and permit. 2. The sender signs an EIP-2612 permit on USDG with the paymaster as spender. 3. Pack the fields above and send the userOp to any bundler serving chain 4663. No API key, no server, no signature from the project. Gas measured on a fork: validation about 35k in allowance mode, 97k in permit mode; postOp about 91k. ## ThruSponsorPaymaster - Each epoch may spend `reserve / RUNWAY_EPOCHS`. EPOCH = 1 day, RUNWAY_EPOCHS = 90. - One address may take at most a fiftieth of an epoch's budget. - Placing the project token with the paymaster (`depositToken`) raises an address's share of the day: one extra share per `HOLDING_UNIT` (250,000 tokens, which is 0.025% of a Pons launch's 1,000,000,000 supply), capped at `MAX_BONUS_SHARES` = 4, so the ceiling is five times the base and costs 0.1% of supply. The budget itself does not grow, so this is rationing rather than spending. `allowanceOf(address)` reports the result and `bonusShares(address)` the shares earned. A sponsor deployed with the token set to the zero address has the feature off. - An application may pay for its users: `allow(user)` offers the caller's allowance to that address, which then transacts holding nothing at all — no ETH, no USDG, no token. The offer alone spends nothing: an operation draws on it only by naming the patron in the last 20 bytes of paymasterAndData. Spending is charged to the patron and capped by the patron's own share, so patronage buys a bigger slice, never a bigger budget. Only the patron may offer or withdraw (`disallow`). The patron's share is read from `tokenDeposit` — the paymaster's own storage — so there is no snapshot to refresh and no expiry to enforce. The sponsor has to be staked for any of this: validation reads its own storage (budget, open-tier slice, offers), which ERC-7562 allows only to a staked entity. - Two tiers, one budget. Any address is sponsored with nothing placed — the OPEN TIER: a quarter of the base share per day (`OPEN_SHARE_DIVISOR` = 4), out of a slice of the day the open tier shares between all its addresses (`OPEN_POOL_BPS` = 3000, i.e. 30%; past it validation reverts `OpenPoolExhausted`). Placing 10 USDG (`depositUsdg`, `MIN_USDG_BALANCE`) or one `HOLDING_UNIT` of the token buys the FULL share. `onOpenTier(address)`, `openTier()` and `sponsorshipFor(address)` report the tier and what is left; `eligible()` is true on either tier while allowance remains. Holding was tried as the condition and is worth almost nothing: a balance can be walked from address to address, each arriving qualified. A deposit cannot be in two places at once, and `withdrawUsdg` reverts with `DepositUsedToday()` on a day it paid for gas, so it cannot be walked either. Open-tier operations do not set that mark — nothing placed qualified them — so a deposit placed through a sponsored operation can still leave the same day; the first full-tier operation locks it until tomorrow. It is never spent. Placing it is itself sponsored by the open tier. - An operation for the open tier has to fit its allowance, so a wallet sizes gas to the call and sets the fee from the chain (the site uses twice the base fee) rather than a padded ceiling. - `maxCost` is reserved at validation and released in postOp, so a bundle cannot overspend. - ERC-7562 forbids TIMESTAMP in validation, so validation reads the stored epoch and returns `validUntil = epochStart + EPOCH`. The epoch rolls in postOp, or via the permissionless `roll()`. - Because the budget is a fraction of the balance, falling income shrinks the budget instead of emptying the reserve. - No owner, no withdrawal, no upgrade path. ## ThruFeeSplitter Set as the creator wallet of a Pons v2 launch. Pons holds creator fees in an escrow and pays them to whoever calls `claim()`, so the splitter claims for itself. - Before a launch graduates its fees sit inside the bonding curve, not the escrow, and only Pons's fee-sweep operator or the launch's fee recipient may move them. The recipient is this contract, so `sweep(curve)` and `sweepClaimAndDistribute(curve)` exist; without them the money is ours, visible and unreachable until graduation. The curve is an argument because the token is launched after this contract is deployed, so the call carries no value and ignores failure. - `claimAndDistribute()` pulls the fees, then splits them on a ratio fixed at deployment. - The burn share buys the token on its Uniswap v4 pool and sends it straight to `0x...dEaD`. - A fixed share (`CREATOR_BPS`, capped by the contract at `MAX_CREATOR_BPS` = 2000) goes to the creator, who runs the keeper, the indexer and the site. It is zero while the sponsor's stake is short: nothing can be sponsored until the stake exists, so there is nothing to be paid for yet. Both the share and the recipient are immutable. - The rest funds gas, stake first: while the sponsor's stake is below `STAKE_TARGET` the gas share is sent to `addStake()`, and only the remainder becomes deposit. An unstaked sponsor sponsors nothing, so this is a prerequisite rather than a diversion. `stakeShortfall()` reports what is still owed; once it reaches zero the splitter never stakes again, including when someone else staked on the sponsor's behalf. - A single call may spend at most 20% of the balance, bounding what a sandwich can extract. - The caller reward is best-effort: a caller that cannot receive ETH forfeits it rather than reverting the distribution. Pons has a native buyback, but it vests bought tokens back to the creator over five years (`VESTING_DURATION` = 157,680,000 seconds) and has no burn function. The splitter burns instead. ## Upkeep: four permissionless calls | Call | Contract | Trigger | Consequence if nobody calls it | |---|---|---|---| | `poke()` | UsdgGasPaymaster | cache older than 5h | after 6h the EntryPoint rejects every operation | | `refill()` | UsdgGasPaymaster | deposit under 0.05 ETH | deposit runs dry while the contract holds USDG | | `roll()` | ThruSponsorPaymaster | at the day boundary | free gas stops until the budget is resized | | `claimAndDistribute()` | ThruFeeSplitter | fees have accrued | fees stay in the Pons escrow | `refill()` reimburses 250,000 gas worth of USDG but costs about 273,000 gas, so keeping it running is slightly unprofitable and no third-party bot will do it. A keeper script is provided, and the status page exposes all four as buttons. ## Addresses, Robinhood Chain (4663) | Contract | Address | |---|---| | UsdgGasPaymaster, EntryPoint v0.7 | 0x96A27b2909B661c4dAe9Aa785f4f519f928c42ee (predicted) | | UsdgGasPaymaster, EntryPoint v0.8 | 0x63700615c3Dc4935fb2145FdA9C194B3AcF9bE08 (predicted) | | ThruSponsorPaymaster | not deployed | | ThruFeeSplitter | not deployed | | EntryPoint v0.7 | 0x0000000071727De22E5E9d8BAf0edAc6f37da032 | | EntryPoint v0.8 | 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108 | | USDG (6 decimals, EIP-2612) | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 | | WETH | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 | | Uniswap V3 ETH/USDG 0.01% | 0x52e65B17fB6E5BA00Ed806f37Afcd2DaA50271Ca | | Uniswap V3 factory | 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA | | Uniswap V2 factory | 0x8bcEaA40B9AcdfAedF85AdF4FF01F5Ad6517937f | | Uniswap v4 PoolManager | 0x8366a39CC670B4001A1121B8F6A443A643e40951 | | Pons factory | 0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e | | Pons fee escrow | 0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e | | Pons buyback vault | 0x42df2a798f82289E177311362e8f5ccC45c1219c | CREATE2 through the keyless proxy 0x4e59b44847b379578588920cA78FbF26c0B4956C, salt keccak256("usdg-gas-paymaster-v1") for v0.7 and salt + 1 for v0.8. ## Constants TWAP window 30 min. Validation buffer 500 bps, fully refunded. Max spot/TWAP deviation 300 bps. Cache age 6 h. Refill threshold 0.05 ETH, target 0.5 ETH, minimum 5 USDG, slippage 50 bps, keeper allowance 250,000 gas. Stake delay 1 day, never unlocked; stake target and creator share set at deployment, creator share capped at 2000 bps. Sponsor epoch 1 day, runway 90 epochs, per-address share 1/50, raised up to 5x by holding the project token. Splitter maximum buy per call 2,000 bps, caller reward cap 100 bps. Pons v2 launch parameters, read from the factory at 0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e: launch config 0 mints 1,000,000,000 tokens with a 1% curve fee and graduates at 4.2 ETH; launchFee 0.0005 ETH; maxCreatorTaxBps 1000. The curve fee splits 70/30 creator/pons and the creator tax is paid to the creator in full, so a 1% creator tax puts 1.7% of volume in the splitter — measured at 0.051 ETH on 3 ETH of trading, against a real launch on the real factory. ## Adversarial testing test/Adversarial.t.sol is written from the attacker's side: each test is an attempt to take, break or deny something, and a passing test means the attempt failed. Four found real holes, which is why the contracts changed: - Naming a patron for somebody else replaced their own allowance, so a stranger could point a victim at an empty pool and switch off their free gas, with no way back for a wallet holding no ETH. Patronage now needs two consents: the app offers, and the operation names. - One operation could reserve a patron's entire day, because maxCost comes from gas limits and fee the sender writes. No operation may now exceed the base share. - A contract pretending to be a bonding curve could re-enter the distribution, taking the caller's reward and running the buyback twice in one transaction against a per-call impact ceiling. - A held balance qualified an address, and could then be walked to the next: fifty addresses, one bag of USDG, an entire day's budget, at four hundred to one over the operations it cost. The full share is now a deposit, which cannot be in two places at once; the open tier below it is bounded per address and by a shared slice, so draining it cannot touch depositors. - The patron's share was snapshotted from a balance, so a flash loan bought a fivefold pool for a day at no cost. The share now follows a deposit, and the snapshot is gone entirely. Attacks that were tried against the real chain and did not pay, with the numbers: - Moving the ETH/USDG pool to be billed for gas at a discount. A round trip costs 0.5955 ETH in fees and hands the price straight back, so the saving is zero. Holding the pool down instead saves 0.019 USDG per operation against a push costing 1,485 USDG: 77,845 operations to break even, each of which spends the same reserve the attacker is trying to farm. - Sandwiching one buyback. Against a real Pons v4 pool, 50 ETH of fees moved the price 4.86% — under the contract's own 5% ceiling — because the splitter spent 10 ETH and held 20 back rather than dumping into a pool that could not take it. - Sandwiching refill(). A real refill sold 1,228 USDG and lost 3 bps to the pool, against the 50 bps floor the contract refuses to sell below. - Lifting an EIP-2612 permit out of paymasterAndData and spending it first. The permit only ever approves this paymaster, so the thief gains nothing, and the victim is not stranded: the permit call is allowed to fail because the allowance it leaves behind is what the charge actually needs. One assumption the audit made explicit rather than fixed: the sponsor's postOp releases the unused part of a reservation, and running it twice for one operation would release it twice, erasing the charge. It is safe only because the EntryPoint calls postOp at most once — when postOp reverts, settlement is retried with mode postOpReverted and that branch is guarded by `if (mode != PostOpMode.postOpReverted)` before the paymaster is called. A test drives the double call directly so the dependency fails loudly if a future EntryPoint changes it. Making it idempotent would cost a storage write on every sponsored operation, out of the same budget that buys free gas. test/Invariants.t.sol drives the paymaster with random sequences (256 runs x 128,000 calls per property) and asserts what must never stop being true: deposits are never spent, the day is never overspent, and an allowance depends on nothing but its own deposit. Known and measured, not fixed: poke() is permissionless and writes the price validation bills at for up to six hours, so anyone may wait for an ordinary dip and record it. At a 5% dip that is 0.00625 USDG per operation, about 6.25 USDG over six hours at a thousand operations, absorbed by the reserve. USDG sent to the sponsor by plain transfer credits nobody and cannot be recovered. ## Known gaps - The local bundler now enforces the ERC-7562 storage rules by tracing paymaster validation, so an operation production would silently drop is rejected here instead. Opcode rules are still not checked: that needs an opcode-level tracer and anvil provides only state tracers. - Not deployed. A complete deployment — both paymasters, the launch, the sponsor, the splitter — is 12,704,174 gas, about 0.0025 ETH at the 0.197 gwei base fee on 2026-09-09, plus a 0.0005 ETH launch fee. The stake and the free-gas budget come from creator fees, not from the deployer. - Not audited. The contracts hold an ETH deposit that nobody can withdraw, and so nobody can rescue. - The USDG paymaster under-recovers by about 1%, absorbed by the reserve. - Permit mode is untested against a live bundler. USDG's permit reads `block.timestamp` during validation (ERC-7562 OP-011); Circle's USDC paymaster does the same and is accepted. - Validation now touches no other contract at all: qualification, allowances and accounting are all this paymaster's own storage. The ordinary path reads only slots keyed by the sender, which the rules allow even unstaked. The patron path reads slots keyed by the patron instead, which a staked paymaster may do and an unstaked one may not — so a stake is still required for that feature, and it is permanent: the contract has no unlockStake or withdrawStake. - Users need a smart account or an EIP-7702 delegation. Bundlers are active on this chain: roughly 520,000 userOps a day were measured across both EntryPoints, 99.7% of them using no paymaster. ## Swap execution Quotes cover Uniswap V2, V3 and v4. Execution goes through SwapRouter02 at 0xCaf681a66D020601342297493863E78C959E5cb2, which serves V2 and V3 on this chain, so: - USDG to ETH, and USDG to any token whose deepest pool is V3, are executable. - Uniswap v4 pools cannot be reached from an EOA at all: the PoolManager returns control through `unlockCallback`, so only a contract can drive it. Those pairs are quoted, not executed. - V2 destinations need a mixed-venue multicall (the USDG leg is V3) and are not wired yet. - Paying with any ERC-20 is routed: the path walks from the token paid to whichever of USDG or WETH its pool sits against, crosses between those two through the 0.01% pool when the sides differ, then out to the destination. One to three hops in a single `exactInput`. - Paying with native ETH is routed too: no approval, the ether travels with the call and the router wraps it. The app builds `[approve, swap]` as one ERC-5792 batch. The fill floor is the user's to set: Auto (3%, plus 2% for each hop beyond the first), or 0.5%, 1%, 3%, 5%. The chosen value is used exactly as given. ## Indexer Optional, and never a dependency. Robinhood Chain produces a block about every tenth of a second, so each page reading the chain per block would be ten round trips a second per visitor for figures that are identical between them. A small service reads once for everyone and pushes what changed over Server-Sent Events (`/stream`, `/snapshot`, `/holdings`, `/history`, `/health`). If it is absent or unreachable every screen falls back to reading the chain directly. The contracts do not know it exists, and integrating with the paymaster never involves it. ## Pages - / : the app. Send USDG, or swap it into any token, with gas paid in USDG. - /docs : full documentation. - /status : live state and the four permissionless upkeep buttons. License: MIT for contracts, site, indexer, service and simulation. Anyone may build on, fork, or run a business on top of it; nothing on chain can refuse a caller. The thru name and mark are not part of the grant.