Created
January 23, 2026 16:24
-
-
Save whatl3y/23d359d68a668f4c26cda42e14fa6317 to your computer and use it in GitHub Desktop.
Some wallets are mysteriously and unexpectedly adding an EIP7702 delegation, this is an easy way to remove it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // To use this script: | |
| // bash-cli$ RPC_URL=YOUR_RPC_URL PRIVATE_KEY=YOUR_KEY node dist/tasks/removeEip7702Delegation | |
| import assert from 'assert' | |
| import { | |
| createWalletClient, | |
| createPublicClient, | |
| http, | |
| parseGwei, | |
| type Hex, | |
| } from 'viem' | |
| import { privateKeyToAccount } from 'viem/accounts' | |
| import { recoverAuthorizationAddress } from 'viem/utils' | |
| async function removeEip7702Delegation() { | |
| assert(process.env.RPC_URL, 'RPC_URL not provided') | |
| assert(process.env.PRIVATE_KEY, 'PRIVATE_KEY not provided') | |
| const privateKey = ( | |
| process.env.PRIVATE_KEY.startsWith('0x') | |
| ? process.env.PRIVATE_KEY | |
| : `0x${process.env.PRIVATE_KEY}` | |
| ) as Hex | |
| const account = privateKeyToAccount(privateKey) | |
| console.log(`Account address: ${account.address}`) | |
| const publicClient = createPublicClient({ | |
| transport: http(process.env.RPC_URL), | |
| }) | |
| const walletClient = createWalletClient({ | |
| account, | |
| transport: http(process.env.RPC_URL), | |
| }) | |
| // Get the chain ID from the RPC | |
| const chainId = await publicClient.getChainId() | |
| console.log(`Chain ID: ${chainId}`) | |
| // Check current code at the account address to see if there's a delegation | |
| const code = await publicClient.getCode({ address: account.address }) | |
| if (!code || code === '0x') { | |
| console.log('No delegation found on this account. It is already a normal EOA.') | |
| return | |
| } | |
| // EIP-7702 delegated accounts have code starting with 0xef0100 followed by the delegate address | |
| if (code.startsWith('0xef0100')) { | |
| const delegateAddress = `0x${code.slice(8)}` // Remove 0xef0100 prefix | |
| console.log(`Current delegation found to: ${delegateAddress}`) | |
| } else { | |
| console.log(`Account has code but it doesn't appear to be an EIP-7702 delegation: ${code.slice(0, 20)}...`) | |
| console.log('This might be a contract account, not a delegated EOA.') | |
| return | |
| } | |
| // Get the current nonce for the account | |
| const currentNonce = await publicClient.getTransactionCount({ address: account.address }) | |
| console.log(`Current account nonce: ${currentNonce}`) | |
| // Sign an authorization to clear the delegation by setting delegate to address(0) | |
| // Per EIP-7702, setting the target to address(0) removes the delegation | |
| // IMPORTANT: The transaction increments the sender's nonce BEFORE validating authorizations. | |
| // Since we're sending from the same account, we need to use currentNonce + 1 | |
| const authorizationNonce = currentNonce + 1 | |
| console.log(`Authorization nonce (currentNonce + 1): ${authorizationNonce}`) | |
| const authorization = await account.signAuthorization({ | |
| contractAddress: '0x0000000000000000000000000000000000000000', | |
| chainId, | |
| nonce: authorizationNonce, | |
| }) | |
| console.log('Signed authorization to remove delegation') | |
| console.log(`Authorization details:`, JSON.stringify(authorization, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)) | |
| // Verify the authorization signature locally before sending | |
| const recoveredAddress = await recoverAuthorizationAddress({ authorization }) | |
| console.log(`Recovered signer address: ${recoveredAddress}`) | |
| console.log(`Expected signer address: ${account.address}`) | |
| if (recoveredAddress.toLowerCase() !== account.address.toLowerCase()) { | |
| console.error('ERROR: Authorization signature does not recover to the expected address!') | |
| console.error('This indicates a signing issue.') | |
| return | |
| } | |
| console.log('Authorization signature verified locally ✓') | |
| // Get current gas prices | |
| const gasPrice = await publicClient.getGasPrice() | |
| const maxPriorityFeePerGas = parseGwei('1') | |
| const maxFeePerGas = gasPrice + maxPriorityFeePerGas | |
| console.log(`Gas price: ${gasPrice}`) | |
| console.log(`Max fee per gas: ${maxFeePerGas}`) | |
| // Send a transaction with the authorization list to remove the delegation | |
| // We send a simple self-transfer with the authorization | |
| // Explicit gas limit to avoid estimation failures on some chains (only pay for gas used) | |
| const hash = await walletClient.sendTransaction({ | |
| chain: null, | |
| to: account.address, | |
| value: 0n, | |
| gas: 100000n, | |
| authorizationList: [authorization], | |
| maxFeePerGas, | |
| maxPriorityFeePerGas, | |
| }) | |
| console.log(`Transaction sent: ${hash}`) | |
| // Wait for the transaction to be mined | |
| console.log('Waiting for transaction confirmation...') | |
| const receipt = await publicClient.waitForTransactionReceipt({ hash }) | |
| console.log(`Transaction confirmed in block ${receipt.blockNumber}`) | |
| console.log(`Status: ${receipt.status === 'success' ? 'Success' : 'Failed'}`) | |
| console.log(`Transaction type: ${receipt.type}`) | |
| console.log(`View transaction: Check your block explorer for hash ${hash}`) | |
| // Verify the delegation was removed | |
| const newCode = await publicClient.getCode({ address: account.address }) | |
| if (!newCode || newCode === '0x') { | |
| console.log('Delegation successfully removed! Account is now a normal EOA.') | |
| } else { | |
| console.log(`Warning: Account still has code: ${newCode.slice(0, 20)}...`) | |
| } | |
| } | |
| ;(async function execute() { | |
| try { | |
| await removeEip7702Delegation() | |
| process.exit(0) | |
| } catch (err) { | |
| console.error('Error removing delegation:', err) | |
| process.exit(1) | |
| } | |
| })() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment