Skip to content

Instantly share code, notes, and snippets.

@whatl3y
Created January 23, 2026 16:24
Show Gist options
  • Select an option

  • Save whatl3y/23d359d68a668f4c26cda42e14fa6317 to your computer and use it in GitHub Desktop.

Select an option

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
// 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