Skip to main content
EIP-7702 lets an existing EOA act as a smart account. It works by delegating the EOA to a smart account implementation, giving it full smart account capabilities while keeping the same address and onchain history. Use this when your users already have an EOA with assets or history they want to preserve, and you want to unlock modules, session keys, and gas sponsorship without migrating to a new address.
EIP-7702 requires your app to control the delegation target. It is not supported for external wallets (MetaMask, Coinbase, etc.) since those wallets do not allow delegating to an arbitrary contract. For external wallet users, use a plain EOA or create a separate smart account.
See What is EIP-7702? for a conceptual overview.

When to use EIP-7702

  • Your users have an embedded wallet (Privy, Dynamic, etc.) with existing assets
  • You want smart account features — session keys, gas sponsorship, modules — without migrating funds
  • You need the account address to stay the same as the EOA

Limitations

  • No key rotation: the EOA is always the root owner. If it’s lost or compromised, the account is unrecoverable.
  • No multisig: the EOA acts as a root key that overrides any other owner.
  • Embedded wallets only: not supported for external wallets.

Send an intent

Sending a transaction with an EIP-7702 account requires two additional signing steps compared to a standard smart account.
1

Create the account

Pass the EOA as both the owner and the eoa field. The presence of eoa tells the SDK to use the EIP-7702 flow.
import { RhinestoneSDK } from '@rhinestone/sdk'
import { privateKeyToAccount } from 'viem/accounts'
import { arbitrum, base } from 'viem/chains'
import { encodeFunctionData, erc20Abi, parseUnits } from 'viem'

const eoaAccount = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)

const rhinestone = new RhinestoneSDK({
  apiKey: process.env.RHINESTONE_API_KEY as string,
})

const account = await rhinestone.createAccount({
  owners: {
    type: 'ecdsa',
    accounts: [eoaAccount],
  },
  eoa: eoaAccount,
})
2

Sign the initialisation data

This signature authorises the smart account setup on the EOA. It only needs to be signed once — cache it and reuse it on subsequent transactions.
const eip7702InitSignature = await account.signEip7702InitData()
The init signature is valid cross-chain, so you can cache it and reuse it. Always provide it when preparing a transaction — the account may need to be initialised on any of the source or target chains.
3

Prepare the transaction

Pass eip7702InitSignature when preparing. The SDK includes it in the transaction so the account is initialised on any chain it hasn’t been set up on yet.
const recipientAddress = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'

const preparedTx = await account.prepareTransaction({
  sourceChains: [arbitrum],
  targetChain: base,
  calls: [
    {
      to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: 'transfer',
        args: [recipientAddress, parseUnits('10', 6)],
      }),
    },
  ],
  tokenRequests: [
    {
      address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
      amount: parseUnits('10', 6),
    },
  ],
  eip7702InitSignature,
})
4

Sign the transaction and authorisation

Sign both the transaction payload and the EIP-7702 authorisation.
const signedTx = await account.signTransaction(preparedTx)
const authorizations = await account.signAuthorizations(preparedTx)
5

Submit and wait

const result = await account.submitTransaction(signedTx, authorizations)
const status = await account.waitForExecution(result)

Next steps

EOA (plain)

Use a plain EOA with intents, no delegation required.

Create a smart account

Start fresh with a full smart account: modules, session keys, gas sponsorship.