thru pays your gas in ETH and bills you in USDG.

One immutable ERC-4337 paymaster on Robinhood Chain. Users hold only USDG. The price is the Uniswap V3 ETH/USDG pool’s own 30-minute average. There is no owner, no API key, and no markup over what the gas cost, and no function that can change any of that. Fees charged by a Uniswap pool when you swap are the pool’s, not this contract’s.

Not deployed yet. Addresses below are CREATE2 predictions.

thru is for two kinds of people:

It works with any ERC-4337 account, including an EOA delegated to Simple7702Account under EIP-7702, against EntryPoint v0.7 and v0.8. One paymaster instance per EntryPoint.

Status

As of 2026-09-10:

How billing works

Every user operation is settled in three steps by the EntryPoint, plus one that anyone can trigger later.

Your walletholds USDGUSDGPaymastercharges at TWAPETHEntryPointpays the networkYour callrunsrefill(): the collected USDG is sold back into ETHThe user never needs ETH. The paymaster never keeps a margin.
validateThe paymaster reads its cached price and pulls maxCost × price × 1.05 USDG from the sender, either through an existing allowance or through an EIP-2612 permit carried in paymasterAndData. It returns validUntil = cachedAt + 6h so the EntryPoint enforces freshness; no TIMESTAMP opcode is used in validation.
executeYour call runs. Gas is drawn from the paymaster’s ETH deposit in the EntryPoint.
postOpThe paymaster reads a fresh 30-minute TWAP and the spot price, stores the TWAP in its cache, converts the real cost (actualGasCost plus its own measured postOp gas) into USDG, and refunds the difference. If the refund transfer fails it is recorded as a claimable balance rather than reverting the op.
refillOnce the ETH deposit falls under 0.05 ETH, anyone can call refill(). The contract sells the USDG it holds through the same pool, unwraps to ETH, deposits it into the EntryPoint up to 0.5 ETH, and pays the caller a fixed gas reimbursement in USDG.

The 5% buffer exists only to absorb price movement between validation and postOp within one block. It is refunded in the same transaction. If the price moves by more than the buffer inside a single op, the paymaster eats the difference rather than charging more than it reserved.

What you actually pay

cost = (actualGasCost + postOpGas × actualUserOpFeePerGas) × price / 1e18, in USDG base units (6 decimals). postOpGas is measured with gasleft() inside postOp plus a 15k tail for the part it cannot measure. The one thing never billed is the EntryPoint’s 10% penalty on unused callGasLimit; the reserve absorbs it.

Price and cache

Pool depth on 2026-09-08 was roughly 6,400 WETH against 16.5M USDG with an observation cardinality of 2,500, which makes a 30-minute TWAP expensive to move.

Permissionless upkeep

FunctionWhoWhat it does
refill()anyoneUSDG held by the contract → ETH in the EntryPoint deposit, when the deposit is under 0.05 ETH. Caller receives 250_000 × tx.gasprice worth of USDG at the TWAP. Reverts if the deposit is already sufficient, if less than 5 USDG plus the reward is available, or if spot deviates from TWAP.
poke()anyoneRefreshes the cached TWAP. Needed only after 6 hours without any op.
addStake()anyone, payableStakes ETH in the EntryPoint for this paymaster with a 1-day unstake delay. There is no function to unlock or withdraw stake, ever.
claimRefund(to)the owed senderPulls a refund that could not be pushed during postOp.
receive()anyoneAny ETH sent to the contract goes straight into the EntryPoint deposit. This is how the reserve is seeded and how donations work.

Trust model

Everything that could be an owner-only knob is either an immutable constant or a permissionless function. Concretely, the contract has no:

What you are trusting: the Solidity code as deployed, the EntryPoint, the USDG token contract (a proxy operated by its issuer), and the liquidity of one Uniswap pool. What you are not trusting: the authors, after deployment.

Immutability cuts both ways. A bug cannot be patched and a stuck deposit cannot be rescued. That is why the reserve should stay small until an audit exists.

Where free gas comes from

Paying in USDG solves the real problem, which is holding the wrong asset, not the size of the fee. Making gas free is a separate and much more expensive thing, so it is funded separately: by the creator fees of a token launched on Pons.

People tradethe token on PonsCreator fees1.7% of volumeFee splitterratios fixed at deployKeeperwhoever called itCreator 20%only once stakedBurned 40%gone for goodGas 40%stake, then budgetStaketo the target, onceReserve1/90 a dayGas on usfor everyonecheaper to transact, so more people doAn unstaked paymaster sponsors nothing, so the stake is filled before any fee becomes budget.The reserve then spends a ninetieth a day, so income falling shrinks the budget instead of emptying it.

Pons pays a launch’s creator fees into an escrow. Naming an immutable splitter as the creator wallet is what makes the loop trustless: nobody, including whoever deployed it, can redirect the money or change the ratio. The splitter buys the token on its Uniswap v4 pool and sends it to the burn address. A fixed share goes to whoever keeps the keeper, the indexer and the site running, and the rest funds gas in the only order that works: it fills the sponsor’s stake first, because an unstaked paymaster sponsors nothing, and every fee after that becomes budget.

Placing the token buys a larger share of the same budget: one extra share perHOLDING_UNIT deposited, capped at MAX_BONUS_SHARES. Placing rather than holding, because a balance read for a patron had to be snapshotted outside validation, and a snapshot can be taken while holding tokens handed straight back — a flash loan bought a fivefold share for a day at no cost. A deposit cannot be borrowed for one block, and it removed the snapshot and its expiry along with the attack. This is rationing, not spending — the day’s budget does not grow, so a larger share for a holder is a smaller one for everybody else. That is a real trade against free gas being equal for all, and the ceiling is what keeps it from becoming a private tap. It also means the token has a use, not only a supply curve.

The threshold is fixed in tokens and cannot be changed, so what it costs moves with the price: cheap for whoever holds early, dearer later. That is the aligned direction, and the alternative — reading a price oracle inside validation — would put a dependency and an attack surface in the one place that can least afford either. One more thing worth saying plainly: the bonus follows the deposit, and a deposit can be withdrawn tomorrow and placed elsewhere. What limits that is the deposit itself — it cannot be in two places at once, and it cannot leave on a day it paid for gas — not the bonus.

Paying for somebody else

A brand new wallet is on the open tier: a quarter share, from a slice of the day. That is enough to start with and deliberately too small to farm — but an application may want its users to have more than that from their first minute. So it can put its own allowance behind them. It calls allow(user), and from then on that address can transact holding nothing, naming the application in its operation, while everything it spends comes out of the application’s share of the day. The share is capped like everyone else’s, so nobody buys their way past the rationing — an application with more users than its cap covers can sponsor the first transaction, which is the one that fails today, and let the rest pay in USDG.

Only the patron may offer; if users could name their own, anyone could spend any holder’s allowance. Validation cannot read a third party’s token balance — ERC-7562 allows only storage associated with the sender — but it can read the paymaster’s own, which is where a placed deposit lives. So the patron’s share is computed from tokenDeposit at validation time, with no snapshot to keep fresh and no expiry to enforce. It does mean the sponsor has to be staked, which is what the splitter’s first fees buy.

Before a launch graduates its fees sit inside the bonding curve, not the escrow, and only Pons’s own operator or the launch’s fee recipient may move them. The recipient is the splitter, so sweepClaimAndDistribute(curve) does the whole journey in one permissionless call: out of the curve, out of the escrow, and into the burn and the reserve. Measured on a real launch against the real factory, 3 ETH of volume credited 0.051 ETH — the 1% curve fee at the 70/30 split plus the whole 1% creator tax, which is the 1.7% the numbers above assume.

Between launch and graduation there is no pool to buy back on, so the burn share simply accumulates and the gas share flows as usual — nothing reverts and nothing is lost. The deployment order follows from the same circularity: the splitter needs the token’s address and the launch needs the splitter’s, so a launch names its creator first and hands the fees over afterwards with transferCreatorFeeRecipient.

The creator’s share is fixed at deployment, capped in the contract atMAX_CREATOR_BPS = 20%, and paid to an address that cannot be changed. It is also earned rather than assumed: while the sponsor’s stake is short the share is zero, because until the stake exists no operation can be sponsored and there is nothing to be paid for. Someone has to run the keeper and the indexer; a project that pretends otherwise is either lying or about to stop.

Pons has a buyback of its own, but it vests the tokens it buys back to the creator over five years rather than burning them, and it takes its slice before the money is visible. Doing it in the splitter is what makes the burn permanent and the ratio yours.

Free gas is a budget, not a promise. When it runs out for the day, operations fall back to the USDG paymaster and simply cost what they cost. Nothing stops working.

An operationfrom any accountSponsor paymasterfree, while today’s budget lastsUSDG paymasteryou pay, always availablefunded by creator feesfunded by you, no subsidyThe wallet picks. When the budget is gone the second one still works, so nothing ever stops.

The daily budget

Reserveeverything deposited÷ 90Today's budgetone day's spending÷ 50Base shareone address, one dayOpen tier ÷ 4anyone, nothing placedFull share10 USDG or 250k placed× up to 5, placing more of the tokenbounded together by 30% of the dayNothing here enlarges the reserve or the day. Each step only divides what already exists, so a larger share for oneaddress is a smaller one for everybody else — which is why the multiplier has a ceiling and the open tier has a slice.Income falling shrinks the budget rather than emptying the reserve: the day is always a ninetieth of whatever is there.

Each day the sponsor may spend reserve / 90. That single rule does the work of a treasury policy. Revenue rises and the budget rises with it. Revenue stops and the budget shrinks in proportion, so the reserve decays towards zero without ever arriving. Nothing is forecast, nothing is voted on, and there is no owner to decide otherwise.

RuleValueWhy
Epoch1 dayHow often the budget is resized.
Runway90 epochsA day may use a ninetieth of the reserve.
Holder allowanceup to 5×Holding the token raises an address’s share of the day, never the day’s size.
Per-address share1/50thOne address cannot take a day’s budget.
ReservationmaxCostBooked at validation, released in postOp, so a bundle cannot overspend.
Open tier¼ shareWhat any address gets with nothing placed. OPEN_SHARE_DIVISOR = 4.
Open slice30%The most the open tier may take of a day between all its addresses (OPEN_POOL_BPS = 3,000). Past it the tier is full; depositors are untouched.
Full share10 USDG placedOr one HOLDING_UNIT of the token. Never spent; withdrawable on any day it has not paid for gas.
HOLDING_UNIT250,000 tokensheld per extra share of the daily allowance; 0.025% of a Pons launch’s supply
MAX_BONUS_SHARES4ceiling on those extra shares, so the largest allowance is five times the base

Nobody is refused. Any address — created five seconds ago, holding nothing — is on theopen tier: a quarter of the base share each day, out of a slice of the budget the open tier shares between all its addresses. That is the promise taken literally: a wallet with nothing can transact. It is also, on purpose, too small to be worth farming, and the slice is what keeps a wave of throwaway addresses from crowding out everybody else — past 30% of the day the open tier is simply full, and the rest of the budget still belongs to the addresses that placed something.

Placing 10 USDG with the paymaster — or one holding unit of the token — buys thefull share. Free gas for anyone with an address is a faucet, and a faucet is drained by whoever can generate addresses fastest; a deposit puts a capital cost on every address a squatter wants to run at once. An earlier version asked what an address was holding when it was validated, which was worth almost nothing: one bag of USDG could be walked from address to address, each arriving qualified — fifty addresses, one bag, the whole day. A deposit cannot be in two places at once, and one that has paid for gas today cannot leave until tomorrow, so it cannot be walked either. Only an operation the deposit actually qualified sets that lock — the open tier does not — so placing it through a sponsored operation leaves it free to come back the same day. It is never spent. Placing it costs nothing: the open tier sponsors the deposit itself.

ERC-7562 forbids reading the clock during validation, so validation never does. It works from the stored epoch and hands the EntryPoint validUntil = epochStart + 1 day, which makes the EntryPoint itself enforce the boundary. The epoch actually rolls in postOp, where the restriction does not apply.

Keeping it running

Four calls keep the system alive. All four are permissionless, none of them is optional, and none of them happens by itself. A keeper normally makes them; the status page exposes the same calls as buttons for when it stalls.

CallWhenWhat breaks without it
poke()cache older than 5 hoursAfter six hours the EntryPoint rejects every operation. This is the one that takes the paymaster offline.
refill()deposit under 0.05 ETHThe deposit runs dry while the contract sits on plenty of USDG.
roll()at the day boundaryFree gas stops until the next budget is sized.
claimAndDistribute()whenever fees have accruedCreator fees sit in the Pons escrow and never reach the reserve.

refill() reimburses its caller a fixed 250,000 gas allowance in USDG. Measured on a fork the call actually costs about 273,000 gas, so a keeper runs at a small loss and no third-party bot will do it for profit. Run your own.

What you can build

Two things change once gas is paid in USDG or not paid at all. A wallet that holds only a stablecoin can act — no bridge, no faucet, no “buy 0.01 ETH first” — and an application can pay for its users without running a paymaster server, keeping an API key, or asking anybody’s permission. Some shapes that fall out of that, each with the one piece of thru it leans on:

Payroll and remittance in USDG

Pay a team or send money home in USDG. Whoever receives it can move it the same minute — the gas comes out of the USDG itself, or out of the open tier, never out of ETH they were never given.

USDG paymasteropen tierquoteMaxCharge

Checkout for a shop

A customer pays in USDG and the shop covers the gas, so the first purchase from a fresh wallet costs exactly the price on the tag. The shop calls allow(customer) once; the spend comes out of its own capped share.

patronallowsponsorshipFor

Recurring buys of tokenized stocks

A savings app that buys a little of a stock token every week — many small operations that would each cost gas. The app places $THRU for a larger share and sponsors its users out of it; the users only ever see USDG leave.

patrontoken bonusdepositToken

A game or social app

Create the wallet at signup and let the first dozen actions happen on the open tier, free and unconditional. Nobody is sent to an exchange before their first move; heavy users are lifted onto the app’s share.

open tierpatrononOpenTier

An airdrop or mint page

Claimants hold nothing, which is what makes a claim page hard. The project vouches for its list and the claims are paid from its share, capped, so a long list cannot empty anybody else’s day.

patronallowpatronAllowance

Tipping and donations

Tips of a dollar or two die when gas costs more than the tip. On the open tier a tip costs the tip. A streaming or creator platform adds one paymaster address and nothing else.

open tierUSDG paymaster

A wallet can offer all of this to every app at once by pointing at the ERC-7677 service below — today for paying in USDG; the sponsor is not served that way yet (see known gaps).

Integrate in three steps

You do not need an API key, a server, or a signature from us. Your dapp talks to the paymaster contract directly: ask it how much USDG the operation costs, have the user sign a USDG permit for that amount, and pack both into paymasterAndData. Any ERC-4337 account works, including an EOA delegated with EIP-7702. The app on the home page is built on exactly these calls.

  1. Quote. Call quoteMaxCharge(maxCost, maxFeePerGas) on the paymaster. It returns the USDG the sender must hold and permit, buffer included. Anything unused is refunded on chain.
  2. Permit. The sender signs an EIP-2612 permit on USDG with the paymaster as spender and that value. No approval transaction, no ETH.
  3. Pack. Encode paymaster address, gas limits, mode byte 1, permit value, deadline and signature, and send the userOp to any bundler serving chain 4663.

The same three steps in viem:

import { encodePacked } from "viem";

const PM   = "0x96A27b2909B661c4dAe9Aa785f4f519f928c42ee"; // EntryPoint v0.7 instance
const USDG = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168";

// 1. how much USDG the sender must permit (6 decimals)
const maxCost = (verificationGasLimit + callGasLimit + 120_000n + 100_000n + preVerificationGas) * maxFeePerGas;
const value = await client.readContract({
  address: PM, abi, functionName: "quoteMaxCharge", args: [maxCost, maxFeePerGas],
});

// 2. EIP-2612 permit signed by the sender (EOAs incl. 7702-delegated ones)
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
const sig = await walletClient.signTypedData({
  /* Permit(owner, spender = PM, value, nonce, deadline) on USDG */
});

// 3. paymasterAndData
const paymasterAndData = encodePacked(
  ["address", "uint128", "uint128", "uint8", "uint256", "uint256", "uint8", "bytes32", "bytes32"],
  [PM, 120_000n, 100_000n, 1, value, deadline, sig.v, sig.r, sig.s],
);

Allowance mode

If the sender has already approved the paymaster for USDG, set the mode byte to 0 and stop there: paymasterAndData = PM ‖ verificationGasLimit ‖ postOpGasLimit ‖ 0x00. Validation is about 60k gas cheaper.

Permit front-running

A permit signature can be submitted by anyone who sees it. The paymaster wraps permit() in try/catch: if someone already consumed it, the allowance is in place and the following transferFrom still succeeds.

paymasterAndData layout

BytesFieldNotes
[0:20]paymaster address
[20:36]paymasterVerificationGasLimit uint128120_000 recommended
[36:52]paymasterPostOpGasLimit uint128100_000 recommended
[52]mode0 = allowance, 1 = permit
[53:85]permit value uint256mode 1 · must be ≥ quoteMaxCharge(maxCost, maxFeePerGas)
[85:117]permit deadline uint256mode 1
[117]vmode 1
[118:150]rmode 1
[150:182]smode 1

Pay for your users

Your applicationholds the tokenallowIts share of the daycapped, like everyone'sA new walletholds nothing at allA new walletno ETH, no USDGA new walletno token eitherEveryone elseopen tier, or a share of their ownOnly the application may offer. Its users spend from its share, never from anybody else’s — and without the offer they still have the open tier.

A wallet created five seconds ago already has the open tier. If your application wants its users to have the full share from their first minute — without asking them to place anything — it can put its own allowance behind them:

// once per user, from the address that is paying for them
await sponsor.write.allow([userAddress]);

// they now transact holding nothing at all: no ETH, no USDG, no token
// the operation names you in paymasterAndData, and everything it spends
// comes out of your share of the day

// what is left to you today, and who pays for an address
const [ok, payer, remaining] = await sponsor.read.sponsorshipFor([userAddress, yourAddress]);

// a larger share of the day: place the token with the paymaster
await sponsor.write.depositToken([amount]);

// stop paying for them
await sponsor.write.disallow([userAddress]);

Only you can offer — if users could name their own patron, anyone could spend anyone’s allowance. The offer alone spends nothing: an operation has to name you in its paymasterAndData to draw on it, and disallow ends it. Your pool is capped like everybody else’s, so this buys a larger slice of the day and never a larger day. If you have more users than the cap covers, sponsor the first transaction — the one that fails today — and let the rest pay their own gas in USDG, which needs no subsidy and no arrangement with anyone.

ERC-7677 service

For wallets and SDKs that speak ERC-7677, thru exposes a stateless paymaster service. It holds no keys and signs nothing: it only tells the caller which paymaster to use, how much USDG the sender must hold or permit, and when the price cache expires. The endpoint URL is published together with the deployment.

Methods over JSON-RPC POST: pm_getPaymasterStubData, pm_getPaymasterData, pm_supportedEntryPoints. Pass the permit in context to use mode 1:

{ "permit": { "value": "0x..", "deadline": "0x..", "v": 27, "r": "0x..", "s": "0x.." } }

The final response includes usdgMaxCharge and priceCacheExpiresAt, plus a hint when the cache is about to expire and poke() should be called first. Because the contract needs no signature from the service, you can also skip it entirely and build paymasterAndData yourself as shown above.

Gas limits

PhaseMeasured on forkSet
validate, mode 0 (allowance)≈ 35k60_000
validate, mode 1 (permit through the USDG proxy)≈ 97k120_000
postOp≈ 91k100_000

Set the limits above and let your bundler tighten them. Over-estimating is cheap: postOp bills measured gas, not the limit.

Addresses

Robinhood Chain mainnet, chain id 4663.

ContractAddress
thru paymaster · EntryPoint v0.7 (predicted)0x96A27b2909B661c4dAe9Aa785f4f519f928c42ee
thru paymaster · EntryPoint v0.8 (predicted)0x63700615c3Dc4935fb2145FdA9C194B3AcF9bE08
EntryPoint v0.70x0000000071727De22E5E9d8BAf0edAc6f37da032
EntryPoint v0.80x4337084D9E255Ff0702461CF8895CE9E3b5Ff108
USDG0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168
WETH0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73
Uniswap V3 ETH/USDG 0.01%0x52e65B17fB6E5BA00Ed806f37Afcd2DaA50271Ca
Sponsor paymaster (not deployed)pending launch
Fee splitter (not deployed)pending launch
Pons fee escrow0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e
Pons factory0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e
Uniswap v4 PoolManager0x8366a39CC670B4001A1121B8F6A443A643e40951

Paymaster addresses are deterministic: CREATE2 through the keyless proxy 0x4e59b44847b379578588920cA78FbF26c0B4956C with salt keccak256("usdg-gas-paymaster-v1") for v0.7 and salt + 1 for v0.8.

Constants

NameValueMeaning
TWAP_WINDOW30 minwindow for postOp and refill pricing
BUFFER_BPS5005% over-reserve at validation, refunded
MAX_DEVIATION_BPS300spot vs TWAP guard
MAX_CACHE_AGE6 hhow long a cached price validates ops
POST_OP_GAS100_000headroom quoted at validation
POST_OP_TAIL_GAS15_000unmeasurable tail added to the bill
REFILL_THRESHOLD0.05 ETHrefill allowed below this deposit
REFILL_TARGET0.5 ETHrefill tops up to this
MIN_REFILL_USDG5 USDGsmallest swap worth doing
REFILL_MAX_SLIPPAGE_BPS50vs TWAP, for the refill swap
REFILL_GAS_REWARD250_000 gaskeeper reimbursement, paid in USDG
STAKE_DELAY1 daypassed to addStake; never unlocked
STAKE_TARGETset at deploymentsplitter fills the sponsor’s stake to this before any fee becomes budget
CREATOR_BPSset at deploymentshare of each fee to the creator, paid only once the stake is filled
MAX_CREATOR_BPS2_000the ceiling the constructor enforces on that share

Errors and events

ErrorWhen
PriceNotInitializedcache is zero; cannot happen after the constructor
PaymasterDataTooShortmissing mode byte or truncated permit fields
UnknownModemode byte is not 0 or 1
PermitTooSmallpermit value below the reserve for this op
DepositAlreadySufficientrefill called with deposit ≥ 0.05 ETH
NothingToRefillless than 5 USDG plus the reward available
PriceDeviationTooHighspot vs TWAP over 3% during refill
SwapOutputTooLowrefill swap returned less than TWAP minus 0.5%
NothingToClaimclaimRefund with no balance owed

Events: GasPaidInUsdg(sender, charged, refunded, priceUsed, postOpGasBilled) on every op, plus RefundDeferred, RefundClaimed, PriceCached, Refilled, Staked, Deposited. The app’s footer counters are sums over GasPaidInUsdg.

Known gaps

Contract source is published and verified on Blockscout at deployment.Machine-readable summary at /llms.txt · docs reflect the contracts as of 2026-09-09