For the complete documentation index, see llms.txt. This page is also available as Markdown.

EIP-7702 Integration

EIP-7702 Integration

Status: Experimental. The 7702-capable SDK modules are on npm now. Wallet devs can start integrating and testing. These modules point at a development version of the delegate contract. The on-chain contract registry protocol request is anticipated. The delegate contract address (see "Registry" below) will change once the registry request executes and the production contract is deployed.

EIP-7702 lets an externally owned account (EOA) delegate its execution to a smart contract, for the duration of one signed Type 4 authorization. Railgun uses this to replace Adapt Relay. Adapt Relay is the shared, communal contract that Railgun transactions have used for external DeFi and swap calls (cross-contract calls). In its place, each user's own EOA points to a single whitelisted delegate contract.

This page describes what changes for a wallet that integrates Railgun. It covers why the change exists and what to implement.

Why this exists

Adapt Relay is a shared contract. Every wallet's swap and DeFi transactions execute as the same communal contract instance. This works for a simple swap-and-shield: the output is an ordinary, transferable ERC-20 that Railgun can route back to the user. It does not work for protocols that return a position or receipt token that is non-transferable, or otherwise bound to the depositing address.

Aave is the clearest case. Its obligation tokens (aTokens) are non-transferable. A position opened by a shared relay contract has no way to be attributed, or moved back out, to the individual user who funded it. This puts an entire category of DeFi out of reach of the old shared-contract model: lending and borrowing positions, and non-transferable or account-bound receipt tokens generally.

With EIP-7702, each user delegates their own EOA to the delegate contract instead of routing through a shared one. The DeFi call executes as the user's own account. Whatever position comes back, transferable or not, is already in the address that opened it. It was never held by a shared intermediary. This is what makes protocols like Aave integrable at all, not just safer to integrate.

Reference implementation

Terminal Wallet has a working 7702 integration on the master branch: github.com/Terminal-Wallet/terminal-wallet-cli/. It is a full, working reference for everything below: package upgrade, base-token shield and unshield, private swaps, gas plumbing, broadcaster wiring, and an ephemeral-account admin and recovery UI. Wallet devs who integrate 7702 should expect to make substantially the same changes. This doc walks through what they are.

The underlying on-chain mechanics live in @railgun-community/engine's release branch: github.com/Railgun-Community/engine/compare/main...release (engine 9.7.0-rc.0). Wallet devs who use the official SDK (@railgun-community/wallet, which wraps this engine) will not call most of this directly. It is the source of truth for what the SDK does under the hood. This doc references it below, where it clarifies or corrects something Terminal Wallet's app-level code does not show.

What changes for a wallet integration

Upgrade to 7702-capable SDK versions. The 7702 code paths exist only from these versions forward (still release-candidate, still experimental): @railgun-community/wallet ≥10.10.0-rc.1, @railgun-community/cookbook ≥2.13.0-rc.2, @railgun-community/shared-models ≥8.1.0-rc.1, @railgun-community/waku-broadcaster-client-node ≥9.2.0-rc.2, ethers ≥6.14.3. These are on npm, but under the dev dist-tag, not latest. A plain npm install @railgun-community/wallet will not pull in 7702 support. Pin the exact rc version in package.json (as Terminal Wallet does), or install with the @dev tag (for example npm install @railgun-community/wallet@dev).

Base-token (ETH) shield. Derive the wallet's current EphemeralAccount (via the SDK's getCurrentEphemeralWallet), and pass it into gasEstimateForShieldBaseToken and populateShieldBaseToken. The SDK takes the 7702 path only when it receives an EphemeralAccount signer. The delegate contract wraps ETH to WETH atomically as part of the shield, so there is no separate manual wrap step. Before you derive the account, realign the wallet's local ephemeral index with on-chain history once per session (EphemeralKeyManager.scanHistoryForEphemeralIndex). This protects an imported or restored wallet whose local index counter reset to 0 while indices were already spent on-chain.

Base-token (ETH) unshield also routes through 7702 in the Terminal Wallet reference implementation. getUnshieldBaseTokenGasEstimate flags it explicitly as a 7702 (type-4) relay-adapt transaction, so the same Type4 gas handling below applies. Private ERC-20 transfers and unshields that do not touch the base token are not affected.

DeFi and swap (cross-contract call) flows. Switch from gasEstimateForUnprovenCrossContractCalls and generateCrossContractCallsProof to the 7702 variants: gasEstimateForUnprovenCrossContractCalls7702 and generateCrossContractCallsProof7702. If you use a cookbook swap recipe (for example the 0x recipe), watch for this gotcha: the stock recipe defaults its quote's taker and recipient to the network's relayAdaptContract address. Under 7702, the delegate code executes as the user's own ephemeral EOA, not as a separate contract instance. A quote built against the old relay-adapt address routes the bought tokens to a non-executing address, and the downstream shield then operates on the wrong account. Override the quote's taker and recipient to the ephemeral EOA's own address instead. Also use the recipe's own minGasLimit (its floor, for example around 2.7M for swap-and-shield) rather than a hardcoded value. A swap's variable external call under-estimates gas and needs a real floor, or it reverts out of gas. Deterministic operations (shield, unshield, recovery, with no variable external call) can safely use 0 instead.

Gas typing, common to all 7702 flows. A 7702 (type-4) transaction must keep its EIP-1559 fee fields (maxFeePerGas and maxPriorityFeePerGas) through the whole pipeline. Tag these flows explicitly, with an is7702Transaction flag through your gas-details plumbing, so a broadcaster-routing branch does not downgrade them to Type1 or legacy pricing. That downgrade would zero out the priority fee on the populated transaction. Separately, pin overallBatchMinGasPrice to 0 for 7702 batches. That field is a legacy gas-price commitment check on the Railgun smart contract. A non-zero value reverts as "Gas price too low" once pricing is governed by the type-4 maxFeePerGas instead.

Broadcaster wiring. 7702 flows need a 7702-capable broadcaster. Filter broadcaster discovery to broadcasters that advertise 7702 support, using the use7702Only flag on the find-broadcaster calls. When you submit, pass the signed authorization tuple and explicit type-4 fee overrides (gasLimit, maxFeePerGas, maxPriorityFeePerGas) through to the broadcaster call. Without them, the broadcaster silently falls back to a legacy submission and drops the authorization, so the bundle can never broadcast. Pass your own already-computed gas limit rather than relying on the broadcaster's own estimateGas on the type-4 transaction. That estimate under-counts 7702 execution and can submit below the delegate contract's minimum-gas check, which fails with "Not enough gas supplied".

7702-delegated (ephemeral) EOA management. A delegated EOA is a real on-chain account, not a disposable signer. It can end up holding a balance the wallet needs to know about. If a DeFi or swap call fails partway through (a slippage revert, or an out-of-gas failure before the re-shield step), the leftover value lands in that EOA rather than in a shared contract. It sits there until the wallet notices and acts. This applies beyond DeFi and swap flows too. Any delegated EOA, including one touched only by a shield or base-token unshield flow, can end up holding native gas token or leftover balances the user did not intend to leave there.

These delegated EOAs are ephemeral. A wallet derives them from a sequential local index per wallet and chain, rather than reusing the user's single long-lived address, for privacy. Use the current index for a given operation, so the gas estimate, proof, and submission all agree on the same address. Only advance, or "ratchet," to the next index after that operation has actually broadcast. Never ratchet speculatively. Keep a durable local record of the index, not just an in-memory value. Realign it against on-chain history on session start and on demand: check which addresses show up as unshield recipients in the wallet's local transaction history. A restored or imported wallet's local counter will not know what has already been used on-chain.

Ship tooling for this. Do not leave it to the user to notice a stray balance. Build an admin view that shows the current ephemeral index and address, and lets the user scan for balances at any given index. Scan for both the native token and ERC-20s: combine a curated token list with a recent-block log scan for the recipient, since an arbitrary swap output can be missed by the curated list alone. Let the user recover stranded funds by reshielding them directly back into their own RAILGUN balance. Recovery targets a specific, possibly prior, ephemeral index. It must not disturb the wallet's live, current index, and must never ratchet the live index afterward.

Architecture notes

  • The delegate contract is a single, general-purpose, whitelisted contract. No wallet should deploy its own version. All wallets that integrate this flow should point at the same canonical delegate contract, rather than build a bespoke implementation. A wallet-specific contract creates a distinguishable on-chain fingerprint: which contract a user's transactions route through becomes metadata that narrows down which wallet app they use. That works against Railgun's anonymity set, not for it.

  • First-time use requires the user to sign the Type 4 delegation authorization before their EOA can use the delegate contract. Handle this as an explicit first-use step in your flow. Do not assume delegation is already in place. Check on-chain whether the account is already delegated before you prompt the user to sign again.

  • EIP-7702 (native account delegation) and ERC-4337 (UserOperations, bundlers, paymasters) are not the same thing, and they are not compatible with each other. If your wallet has existing 4337 smart-account infrastructure, do not assume it interoperates with this flow. This integration is built on native 7702 authorizations, not UserOperations.

  • Two separate signatures are involved, not one. First, the EIP-7702 Authorization tuple itself: the native account-delegation signature that points the ephemeral EOA's code at the delegate contract address. Second, a separate EIP-712 execution signature over the specific batch of calls being executed ({payloadHash}, domain {name: 'RelayAdapt7702', version: '1', chainId, verifyingContract: <the ephemeral EOA's own address>}). The second signature is what lets a broadcaster, or anyone, submit the transaction on the user's behalf without holding the ephemeral's private key. The delegate contract verifies the execution signature on-chain, rather than trusting msg.sender. If you integrate below the level the official SDK wraps, you must produce both signatures and keep them in sync (see the nonce note below). If you call the SDK's own shield, swap, and unshield functions, this is handled for you.

  • Ephemeral EOA derivation uses a fully hardened BIP-32 path: m/44'/60'/0'/7702'/<railgunIndex>'/<chainId>'/<index>'. It derives from the wallet's own mnemonic, and mnemonic password if any. Chain ID is baked into the path, so ephemeral addresses are chain-specific by construction. This matches the "per wallet and chain index" model described above.

  • The engine supports pluggable ephemeral signers (setEphemeralSignerProvider) for wallets that do not want mnemonic-derived ephemeral keys, for example a hardware wallet or MPC signer. A custom provider owns its own key derivation and must manage its own index (setEphemeralKeyIndex). The engine's automatic index-ratcheting and on-chain-history resync (incrementEphemeralKeyIndex, scanHistoryForEphemeralIndex) work only for the default HD-derived provider. This matters if your wallet does not do mnemonic-based key management.

  • The delegate contract itself has at least two execution ABI shapes: a current nonce-aware execute(), and a LegacyPreExecuteNonce variant without it. The engine tries both when it parses or validates a 7702 transaction it did not construct itself. Wallet devs do not need to branch on this directly; the SDK's execution-type default handles it. It is worth knowing if you debug a transaction that fails to parse.

Registry

Subject to change. This section describes the delegate-address configuration mechanism as implemented in @railgun-community/engine 9.7.0-rc.0 (release branch, still pre-release), as of 2026-07-30. How the higher-level @railgun-community/wallet SDK surfaces this to app-level wallet devs is not yet confirmed. Either layer could change before a stable release. Re-check this section against the current SDK before you rely on it.

It is anticipated that a future protocol request will provide an on-chain registry of contract addresses specified by RAIL security stakers as the canonical contracts that make up the protocol, as well as the supporting non-protocol contracts, such as the 7702 delegate contract.

However, until such a protocol request is submitted, the SDK assumes that a wallet developer will provide the delegate contract address.

Currently, it is not something the SDK resolves live from the registry every time. At the engine level, network setup takes an explicit delegate contract address (relayAdapt7702ContractAddress) as a parameter. There is no code path where the engine calls the registry and substitutes whatever it returns in place of your configured value. If you also supply the registry's address (railgunRegistryContractAddress), the engine does a one-time check on load. It calls the registry's getContractAddress('RelayAdapt7702') and logs a warning if that disagrees with the address you configured. It does not block, and it does not switch to the registry's value. The engine keeps using whatever address you configured either way.

In practice, this means your app, or its remote-configuration mechanism, is responsible for keeping the delegate address current.

If your wallet needs to override or pin a specific delegate address, for example ahead of an SDK release, check whether @railgun-community/wallet's network-loading function exposes an equivalent parameter.

Experimental addresses: These are the delegate contract addresses currently shipped in @railgun-community/shared-models 8.1.0-rc.1. They are not the product of a protocol request and are not canonical. Treat them as experimental, and expect them to change once the delegate contract goes through a protocol request.

Network

Delegate contract (RelayAdapt7702)

Ethereum

0x05aE73C5925d843864AE6f261f3175dE2ebCd963

BNB Chain

0x48cf4b897f64D81212c1423D78a05E828d0cE19d

Polygon

0x48cf4b897f64D81212c1423D78a05E828d0cE19d

Arbitrum

0x48cf4b897f64D81212c1423D78a05E828d0cE19d

Ethereum Sepolia (testnet)

0x2df3D82C06339387A4532C685daaF39A218Cf56E

Rescuing funds from a delegated EOA while preserving privacy and saving gas

The core Railgun-orchestrated unshield-swap-reshield flow does not require the delegated EOA to hold its own gas. A broadcaster can submit that transaction and pay gas itself. The signed Type 4 authorization only has to come from the user's EOA, not from the gas payer. The broadcaster sponsors the whole thing the same way it does today.

Recovering a stray balance does not require funding the EOA either. This corrects an earlier assumption in this doc. In Terminal Wallet's recovery flow, the funds move by building a 7702 relay-adapt batch signed by the target ephemeral EOA. The EOA authorizes; it does not need to hold gas. The wallet submits the batch either through a broadcaster, with the fee paid in a token, or by self-broadcasting from the user's own already-funded public wallet as the transaction sender. Either way, the stranded balance gets reshielded straight back into the user's RAILGUN balance, with no gas top-up needed at the ephemeral address.

Privacy note for implementers

If you build sponsor or paymaster-style gas UX on top of this, do not require the user to reveal their shielded note in plaintext to a third party to get a gas quote or sponsorship. That defeats the privacy guarantee Railgun exists to provide. If you need a gas-sponsorship model, design it so the sponsor learns only what it needs to validate and price the transaction, not the user's private balance contents.

Integration checklist

  • For now, since there is no governance-approved registry yet: configure one of the experimental delegate contract addresses from the table above in your app or config. Treat it as temporary.

  • Keep your configured delegate address current through your own config and release process. The SDK does not resolve or swap it in for you from the registry.

  • Upgrade to 7702-capable SDK versions: @railgun-community/wallet ≥10.10.0-rc.1, @railgun-community/cookbook ≥2.13.0-rc.2, @railgun-community/shared-models ≥8.1.0-rc.1, @railgun-community/waku-broadcaster-client-node ≥9.2.0-rc.2, ethers ≥6.14.3. Pin the exact rc version, or use the @dev dist-tag. latest still resolves to the pre-7702 stable release. These are still rc and experimental; expect them to move.

  • Base-token shield: derive an EphemeralAccount (via getCurrentEphemeralWallet) and pass it to gasEstimateForShieldBaseToken and populateShieldBaseToken.

  • Base-token unshield: mark it as a 7702 (type-4) relay-adapt transaction in your gas-details plumbing. Do not assume it is unaffected.

  • Private swaps and cross-contract calls: switch to gasEstimateForUnprovenCrossContractCalls7702 and generateCrossContractCallsProof7702.

  • If you use a swap recipe: override the quote's taker and recipient to the ephemeral EOA's own address, not the network's relay-adapt contract address.

  • Use the swap recipe's own minGasLimit for swaps, not a hardcoded floor. Deterministic operations (shield, unshield, recovery) can use 0.

  • Keep EVMGasType.Type4 through the whole pipeline for 7702 flows. Do not let a broadcaster-routing branch downgrade it to Type1.

  • Pin overallBatchMinGasPrice to 0 for 7702 batches.

  • Filter broadcaster discovery to 7702-capable broadcasters for 7702 flows.

  • Pass the signed authorization tuple and explicit type-4 fee and gas-limit overrides through to the broadcaster submission call.

  • Sync the local ephemeral index from on-chain history once per session. Keep a durable local record of the index, not just an in-memory, per-session value.

  • Ratchet the ephemeral index forward only after a relay-adapt transaction has actually broadcast successfully. Never ratchet speculatively, and never after a recovery build, which targets a specific, possibly prior, index.

  • Ship ephemeral-account admin and recovery tooling: show the current index and address, scan for stranded balances at a given index, and let the user reshield stranded funds back into RAILGUN.

  • Recovery does not require funding the ephemeral EOA with gas: a broadcaster or the user's own public wallet pays. Reserve the unshield-to-address gas top-up pattern for direct third-party DApp interaction only.

  • Test on mainnet. Testnets do not have the real DeFi liquidity and interactions needed to validate swap flows end to end.

  • If you have existing 4337 smart-account infrastructure, treat this as a separate, non-interoperable code path.

Last updated

Was this helpful?