# Sign EIP-7702 authorization

[EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) lets an externally owned account (EOA) delegate its execution to smart-contract code, so an embedded wallet gains account-abstraction features — transaction batching, gas sponsorship, and session keys — while keeping its original address.

:::warning
This applies to **Delegated Accounts** only. EOA and Smart Account types do not require an EIP-7702 authorization.
:::

The authorization is **signed once per chain** — the first transaction that includes it deploys the delegation. Later transactions on the same chain don't need it again; switching to a new chain requires a new authorization.

:::info\[Supported chains vs. other chains]
On a [supported EVM chain](/docs/configuration/chains), Openfort delegates automatically to the **Calibur** implementation (the default unless you configure another) — nothing else to do. On a chain Openfort does not natively support, you can still delegate to any 7702 implementation deployed on that chain by signing the authorization yourself with viem — see [Delegate to a different implementation](#delegate-to-a-different-implementation).
:::

## Recommended: native delegation to Calibur

:::note
Requires `@openfort/openfort-js` ≥ 1.5.0. Earlier versions return an `AA24 signature error` on the first delegated send.
:::

Configure the embedded wallet as a Delegated Account, then send a transaction. On the first send, the SDK signs and attaches the one-time EIP-7702 authorization to Openfort's [Calibur](/docs/configuration/addresses) implementation for you — no viem, no manual signing.

:::code-group

```ts [delegate-native.ts]
import { AccountTypeEnum, RecoveryMethod } from '@openfort/openfort-js'
import openfort from './openfortConfig'

const CHAIN_ID = 84532 // Base Sepolia

export async function delegateNative() {
  // 1. Configure the embedded wallet as a Delegated Account on this chain.
  await openfort.embeddedWallet.configure({
    chainId: CHAIN_ID,
    accountType: AccountTypeEnum.DELEGATED_ACCOUNT,
    recoveryParams: { recoveryMethod: RecoveryMethod.PASSWORD, password: 'user-password' },
  })

  // 2. Send through a sponsored provider. The first send signs and attaches the
  //    EIP-7702 authorization to Openfort's Calibur implementation automatically.
  const provider = await openfort.embeddedWallet.getEthereumProvider({
    feeSponsorship: 'pol_...', // your gas sponsorship
  })
  const [from] = (await provider.request({ method: 'eth_accounts' })) as string[]

  return await provider.request({
    method: 'wallet_sendCalls',
    params: [
      {
        version: '1.0',
        chainId: `0x${CHAIN_ID.toString(16)}`,
        from,
        calls: [{ to: '0x0000000000000000000000000000000000000000', value: '0x0', data: '0x' }],
      },
    ],
  })
}
```

```ts [openfortConfig.ts]
import { Openfort } from '@openfort/openfort-js'

const openfort = new Openfort({
  baseConfiguration: {
    publishableKey: 'YOUR_OPENFORT_PUBLISHABLE_KEY',
  },
  shieldConfiguration: {
    shieldPublishableKey: 'YOUR_SHIELD_PUBLISHABLE_KEY',
  },
})

export default openfort
```

:::

Using Calibur keeps your transactions visible in the [dashboard](https://dashboard.openfort.io) and lets Openfort act as bundler and paymaster.

## Delegate to a different implementation

Use this path when you want an implementation other than Calibur — the [`Simple` 7702 account](/docs/configuration/addresses) or your own contract — **or when you're on a chain Openfort doesn't natively delegate on** but that has a 7702 implementation available. Sign the authorization yourself with the embedded signer and send the UserOperation with [viem](https://viem.sh/). Set `IMPLEMENTATION_ADDRESS` to the target implementation.

:::warning
Only [Openfort-supported implementations](/docs/configuration/addresses) appear in the dashboard and can be tracked. Transactions from a smart account Openfort does not natively support will not show up there.
:::

The embedded signer produces raw ECDSA signatures over a digest, so sign both the authorization and the UserOperation hash with `hashMessage: false` (no EIP-191 prefix).

```ts [delegate-custom.ts]
import {
  createPublicClient,
  createWalletClient,
  http,
  parseSignature,
  zeroAddress,
  type Address,
  type Hex,
} from 'viem'
import { toAccount } from 'viem/accounts'
import { hashAuthorization, hashTypedData } from 'viem/utils'
import {
  createBundlerClient,
  createPaymasterClient,
  toSimple7702SmartAccount,
} from 'viem/account-abstraction'
import { baseSepolia } from 'viem/chains'
import openfort from './openfortConfig'

const CHAIN = baseSepolia
const OPENFORT_RPC_URL = `https://api.openfort.io/rpc/${CHAIN.id}`
const PUBLISHABLE_KEY = process.env.OPENFORT_PUBLISHABLE_KEY!
const FEE_SPONSORSHIP_ID = process.env.OPENFORT_FEE_SPONSORSHIP_ID! // pol_...

// The implementation to delegate to. `Simple` 7702 account (EntryPoint v0.8) shown here.
const IMPLEMENTATION_ADDRESS = '0xe6Cae83BdE06E4c305530e199D7217f42808555B'

export async function delegateCustom() {
  const provider = await openfort.embeddedWallet.getEthereumProvider()
  const [address] = (await provider.request({ method: 'eth_accounts' })) as Address[]

  const signDigest = (hash: Hex) =>
    openfort.embeddedWallet.signMessage(hash, {
      arrayifyMessage: true,
      hashMessage: false,
    }) as Promise<Hex>

  const owner = Object.assign(
    toAccount({
      address,
      signMessage: ({ message }) =>
        openfort.embeddedWallet.signMessage(
          typeof message === 'string' ? message : (message.raw as Hex),
        ) as Promise<Hex>,
      signTypedData: (typedData) => signDigest(hashTypedData(typedData)),
      signTransaction: () => {
        throw new Error('Transactions are sent as UserOperations through the bundler.')
      },
    }),
    {
      sign: ({ hash }: { hash: Hex }) => signDigest(hash),
      signAuthorization: (authorization) => signDigest(hashAuthorization(authorization)),
    },
  )

  const publicClient = createPublicClient({ chain: CHAIN, transport: http() })
  const openfortTransport = http(OPENFORT_RPC_URL, {
    fetchOptions: { headers: { Authorization: `Bearer ${PUBLISHABLE_KEY}` } },
  })

  const account = await toSimple7702SmartAccount({ client: publicClient, owner })
  if (await account.isDeployed()) return

  const bundlerClient = createBundlerClient({
    account,
    client: publicClient,
    paymaster: createPaymasterClient({ transport: openfortTransport }),
    paymasterContext: { feeSponsorshipId: FEE_SPONSORSHIP_ID },
    transport: openfortTransport,
  })

  // 1. Sign the one-time authorization delegating the EOA to the implementation.
  const walletClient = createWalletClient({ chain: CHAIN, transport: http(), account: owner })
  const preAuthorization = await walletClient.prepareAuthorization({
    contractAddress: IMPLEMENTATION_ADDRESS,
  })
  const authorization = {
    ...preAuthorization,
    ...parseSignature(await signDigest(hashAuthorization(preAuthorization))),
  }

  // 2. Send the first sponsored UserOperation with the authorization attached.
  const userOpHash = await bundlerClient.sendUserOperation({
    calls: [{ to: zeroAddress, value: 0n, data: '0x' }],
    authorization,
  })
  const receipt = await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash })
  return receipt.receipt.transactionHash
}
```

To delegate an external wallet (Privy, Turnkey, and others) from your server, see the server-side flow in the [React guide](/docs/products/embedded-wallet/react/wallet/actions/eip-7702-authorization#delegate-an-external-wallet-server-side).

## Related

* [Account types](/docs/products/embedded-wallet/account-types)
* [Entity addresses](/docs/configuration/addresses) — supported implementations per chain
* [Use smart wallets](/docs/products/embedded-wallet/javascript/smart-wallet/send)
* [Sign EIP-7702 authorization (React)](/docs/products/embedded-wallet/react/wallet/actions/eip-7702-authorization)
