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:
- Someone holding USDG and no ETH who wants to send USDG or top up a little ETH without buying it somewhere else first.
- A dapp or wallet on Robinhood Chain that wants to offer gas in USDG without running a paymaster server, signing quotes, or trusting a third party’s price feed.
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:
- Contracts written: the USDG paymaster, the sponsor and the fee splitter. 106 unit, adversarial and invariant tests (the invariants ran ten million random calls without a violation) and 28 fork tests against the real EntryPoints, real USDG, the real pool, Uniswap v4 and the Pons factory.
- Not deployed. Gas to deploy is about 0.0025 ETH. The two USDG paymasters additionally need working capital, 1.2 ETH at the deploy script’s defaults; the free-gas side is funded by creator fees instead.
- Not audited. See known gaps before routing real volume.
How billing works
Every user operation is settled in three steps by the EntryPoint, plus one that anyone can trigger later.
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.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.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
- Source: the Uniswap V3 ETH/USDG 0.01% pool at
0x52e65B17fB6E5BA00Ed806f37Afcd2DaA50271Ca. Nothing else. No Chainlink, no signer, no admin-set value. - TWAP window: 30 minutes, computed from
observe([1800, 0])with floor rounding on the tick, exactly like Uniswap’s oracle library. - Deviation guard: if spot differs from TWAP by more than 3%,
refill()reverts withPriceDeviationTooHighand postOp bills the user at the cheaper of spot and TWAP. - Cache: validation uses the last TWAP stored by any postOp,
refill(), orpoke(). It is valid for 6 hours, enforced by the EntryPoint throughvalidUntil. After a quiet period, anyone callspoke()once and ops flow again.
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
| Function | Who | What it does |
|---|---|---|
refill() | anyone | USDG 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() | anyone | Refreshes the cached TWAP. Needed only after 6 hours without any op. |
addStake() | anyone, payable | Stakes 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 sender | Pulls a refund that could not be pushed during postOp. |
receive() | anyone | Any 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:
owneror any privileged addresswithdraw,withdrawTo, or any path that returns ETH to a caller beyond the refill gas rewardunlockStakeorwithdrawStake- fee, markup, or spread parameter
- oracle setter or price override
- proxy, upgrade path, or pause
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.
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.
The daily budget
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.
| Rule | Value | Why |
|---|---|---|
| Epoch | 1 day | How often the budget is resized. |
| Runway | 90 epochs | A day may use a ninetieth of the reserve. |
| Holder allowance | up to 5× | Holding the token raises an address’s share of the day, never the day’s size. |
| Per-address share | 1/50th | One address cannot take a day’s budget. |
| Reservation | maxCost | Booked at validation, released in postOp, so a bundle cannot overspend. |
| Open tier | ¼ share | What any address gets with nothing placed. OPEN_SHARE_DIVISOR = 4. |
| Open slice | 30% | 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 share | 10 USDG placed | Or one HOLDING_UNIT of the token. Never spent; withdrawable on any day it has not paid for gas. |
HOLDING_UNIT | 250,000 tokens | held per extra share of the daily allowance; 0.025% of a Pons launch’s supply |
MAX_BONUS_SHARES | 4 | ceiling 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.
| Call | When | What breaks without it |
|---|---|---|
poke() | cache older than 5 hours | After six hours the EntryPoint rejects every operation. This is the one that takes the paymaster offline. |
refill() | deposit under 0.05 ETH | The deposit runs dry while the contract sits on plenty of USDG. |
roll() | at the day boundary | Free gas stops until the next budget is sized. |
claimAndDistribute() | whenever fees have accrued | Creator 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.
quoteMaxChargeCheckout 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.
allowsponsorshipForRecurring 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.
depositTokenA 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.
onOpenTierAn 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.
allowpatronAllowanceTipping 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.
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.
- 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. - Permit. The sender signs an EIP-2612 permit on USDG with the paymaster as spender and that value. No approval transaction, no ETH.
- 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
| Bytes | Field | Notes |
|---|---|---|
| [0:20] | paymaster address | |
| [20:36] | paymasterVerificationGasLimit uint128 | 120_000 recommended |
| [36:52] | paymasterPostOpGasLimit uint128 | 100_000 recommended |
| [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 |
Pay for your users
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
| Phase | Measured on fork | Set |
|---|---|---|
| validate, mode 0 (allowance) | ≈ 35k | 60_000 |
| validate, mode 1 (permit through the USDG proxy) | ≈ 97k | 120_000 |
| postOp | ≈ 91k | 100_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.
| Contract | Address |
|---|---|
| thru paymaster · EntryPoint v0.7 (predicted) | 0x96A27b2909B661c4dAe9Aa785f4f519f928c42ee |
| thru paymaster · EntryPoint v0.8 (predicted) | 0x63700615c3Dc4935fb2145FdA9C194B3AcF9bE08 |
| EntryPoint v0.7 | 0x0000000071727De22E5E9d8BAf0edAc6f37da032 |
| EntryPoint v0.8 | 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108 |
| USDG | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 |
| WETH | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 |
| Uniswap V3 ETH/USDG 0.01% | 0x52e65B17fB6E5BA00Ed806f37Afcd2DaA50271Ca |
| Sponsor paymaster (not deployed) | pending launch |
| Fee splitter (not deployed) | pending launch |
| Pons fee escrow | 0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e |
| Pons factory | 0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e |
| Uniswap v4 PoolManager | 0x8366a39CC670B4001A1121B8F6A443A643e40951 |
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
| Name | Value | Meaning |
|---|---|---|
TWAP_WINDOW | 30 min | window for postOp and refill pricing |
BUFFER_BPS | 500 | 5% over-reserve at validation, refunded |
MAX_DEVIATION_BPS | 300 | spot vs TWAP guard |
MAX_CACHE_AGE | 6 h | how long a cached price validates ops |
POST_OP_GAS | 100_000 | headroom quoted at validation |
POST_OP_TAIL_GAS | 15_000 | unmeasurable tail added to the bill |
REFILL_THRESHOLD | 0.05 ETH | refill allowed below this deposit |
REFILL_TARGET | 0.5 ETH | refill tops up to this |
MIN_REFILL_USDG | 5 USDG | smallest swap worth doing |
REFILL_MAX_SLIPPAGE_BPS | 50 | vs TWAP, for the refill swap |
REFILL_GAS_REWARD | 250_000 gas | keeper reimbursement, paid in USDG |
STAKE_DELAY | 1 day | passed to addStake; never unlocked |
STAKE_TARGET | set at deployment | splitter fills the sponsor’s stake to this before any fee becomes budget |
CREATOR_BPS | set at deployment | share of each fee to the creator, paid only once the stake is filled |
MAX_CREATOR_BPS | 2_000 | the ceiling the constructor enforces on that share |
Errors and events
| Error | When |
|---|---|
PriceNotInitialized | cache is zero; cannot happen after the constructor |
PaymasterDataTooShort | missing mode byte or truncated permit fields |
UnknownMode | mode byte is not 0 or 1 |
PermitTooSmall | permit value below the reserve for this op |
DepositAlreadySufficient | refill called with deposit ≥ 0.05 ETH |
NothingToRefill | less than 5 USDG plus the reward available |
PriceDeviationTooHigh | spot vs TWAP over 3% during refill |
SwapOutputTooLow | refill swap returned less than TWAP minus 0.5% |
NothingToClaim | claimRefund 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
- Not deployed. Deploying everything — both paymasters, the token, the sponsor and the splitter — costs about 0.0025 ETH of gas at this chain’s price, plus a 0.0005 ETH launch fee. The stake and the free-gas budget come from creator fees, not from whoever deploys.
- The sponsor needs a stake before it sponsors anything. Its validation touches no other contract, but it does read its own storage — the day’s budget, the open tier’s slice, a patron’s offer — and ERC-7562 allows an entity to read its own storage only when it is staked. The splitter buys the stake from the first creator fees, and the stake can never be withdrawn.
- Free gas is not yet offered through the ERC-7677 service. A wallet that asks the service for a paymaster is pointed at the USDG paymaster only; the sponsor is reached today through the site’s own operations. Adding the sponsor to the service is the next piece of work.
- No audit. The contract holds an ETH deposit nobody can withdraw or rescue.
- Under-recovers by about 1%. The EntryPoint’s 10% penalty on unused call gas is invisible to postOp and deliberately never billed. The reserve absorbs it along with the pool’s 1 bp swap fee. Zero fee means the reserve slowly drains unless someone tops it up.
- The storage rules are now enforced in the simulation, the opcode rules are not. The local bundler traces paymaster validation and rejects an operation that touches storage ERC-7562 disallows. Deciding that rule exactly needs the preimages keccak was called with, which only an opcode-level tracer exposes and anvil does not offer, so the check scans a bounded set of mapping slots instead. It resolves USDG’s layout correctly; a token using ERC-7201 namespaced storage would be flagged wrongly.
- Permit mode untested against a live bundler. USDG’s permit reads
block.timestampduring validation (ERC-7562 OP-011). Circle’s USDC paymaster does the same and is accepted by major bundlers, so it is expected to pass. - Distribution depends on the ecosystem. Users need a smart account or an EIP-7702 delegation, and a bundler that serves chain 4663. The contract cannot fix either.