Skip to main content
Sign EIP-7702 authorizations to enable your embedded wallets to gain account abstraction capabilities such as transaction bundling, gas sponsorship, and custom permissions.
EIP-7702 enables externally owned accounts (EOAs) to delegate their execution to smart contract code, allowing EOA wallets to function like smart contract wallets.

Overview

EIP-7702 is a groundbreaking proposal that allows EOA wallets to temporarily delegate their code execution to a smart contract. This enables powerful account abstraction features without requiring users to migrate to new wallet addresses.

Key Benefits

  • Transaction batching: Execute multiple operations in a single transaction
  • Gas sponsorship: Enable gasless transactions for users
  • Custom permissions: Implement spending limits and approval logic
  • Session keys: Allow temporary key access with limited permissions
  • Social recovery: Add recovery mechanisms to EOA wallets
  • Backwards compatibility: EOAs can revert to normal operation anytime

How It Works

  1. User signs an EIP-7702 authorization delegating to a contract
  2. Authorization is included in a transaction
  3. During that transaction, the EOA behaves like the delegated contract
  4. After the transaction, the EOA returns to normal
  5. Authorization can be revoked or updated at any time

React SDK

  • Basic Usage
  • With UI Customization
To sign an EIP-7702 authorization, use the sign7702Authorization method from the useSign7702Authorization hook:
import { useMoonKey } from '@moon-key/react-auth';
import { useSign7702Authorization } from '@moon-key/react-auth/ethereum';

function Sign7702Button() {
  const { user } = useMoonKey();
  const { sign7702Authorization } = useSign7702Authorization();

  const handleSign = async () => {
    if (!user?.wallet) {
      alert('No wallet found');
      return;
    }

    try {
      const authorization = await sign7702Authorization({
        contractAddress: '0x1234567890abcdef1234567890abcdef12345678',
        chainId: 1, // Ethereum mainnet
        nonce: 0 // Optional
      });

      console.log('Authorization signed:', authorization);
      alert('Successfully signed EIP-7702 authorization!');
      
      // Use authorization with your AA provider
    } catch (error) {
      console.error('Failed to sign authorization:', error);
    }
  };

  return (
    <button onClick={handleSign}>
      Enable Account Abstraction
    </button>
  );
}

Parameters

contractAddress
string
required
The address of the smart contract whose code the EOA will delegate to. This is typically an account implementation contract from your AA provider.Example:
contractAddress: '0x1234567890abcdef1234567890abcdef12345678'
chainId
number
required
The chain ID where the authorization will be used.Example:
chainId: 1 // Ethereum mainnet
nonce
number
The nonce for the authorization. If not provided, the current transaction count for the wallet will be used automatically.Example:
nonce: 0

Options Object

The second parameter is an optional configuration object:
uiConfig
object
Configuration for the authorization confirmation modal.
wallet
Wallet
Specific wallet to use for signing. If not provided, uses the user’s default wallet.

Returns

authorization
object
The signed EIP-7702 authorization object.

Complete Examples

Enable Gas Sponsorship

'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSign7702Authorization } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';

export default function GasSponsorshipSetup() {
  const { user, isAuthenticated } = useMoonKey();
  const { sign7702Authorization } = useSign7702Authorization();
  const [isEnabled, setIsEnabled] = useState(false);
  const [isEnabling, setIsEnabling] = useState(false);

  const handleEnable = async () => {
    if (!user?.wallet) {
      alert('Please connect your wallet first');
      return;
    }

    setIsEnabling(true);

    try {
      // Your AA provider's account implementation contract
      const accountImplementation = '0xGasSponsorshipImplementation...';

      // Sign the EIP-7702 authorization
      const authorization = await sign7702Authorization({
        contractAddress: accountImplementation,
        chainId: 8453 // Base
      }, {
        uiConfig: {
          title: 'Enable Gasless Transactions',
          description: 'Sign to enable gas sponsorship for your transactions',
          confirmButtonText: 'Enable Gasless'
        }
      });

      console.log('Authorization created:', authorization);

      // Register with your gas sponsorship service
      const response = await fetch('/api/gas-sponsorship/register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          walletAddress: user.wallet.address,
          authorization,
          chainId: 8453
        })
      });

      if (response.ok) {
        setIsEnabled(true);
        alert('Gasless transactions enabled!');
      } else {
        throw new Error('Failed to register for gas sponsorship');
      }
    } catch (error) {
      console.error('Failed to enable gas sponsorship:', error);
      alert('Failed to enable gasless transactions');
    } finally {
      setIsEnabling(false);
    }
  };

  if (!isAuthenticated) {
    return <p>Please sign in to enable gas sponsorship</p>;
  }

  return (
    <div className="gas-sponsorship">
      <h2>Gasless Transactions</h2>
      <p>Enable gas sponsorship to send transactions without paying gas fees</p>

      {!isEnabled ? (
        <button
          onClick={handleEnable}
          disabled={isEnabling}
          className="enable-button"
        >
          {isEnabling ? 'Enabling...' : 'Enable Gas Sponsorship'}
        </button>
      ) : (
        <div className="enabled-status">
          <p>✅ Gas sponsorship is enabled</p>
          <p>You can now send transactions without gas fees!</p>
        </div>
      )}
    </div>
  );
}

UI Customization

Customize the authorization signing modal:
await sign7702Authorization({
  contractAddress: '0x...',
  chainId: 1
}, {
  uiConfig: {
    title: 'Enable Smart Features',
    description: 'This allows your wallet to use advanced account abstraction features',
    confirmButtonText: 'Enable',
    cancelButtonText: 'Cancel'
  }
});

Hide Modal

await sign7702Authorization({
  contractAddress: '0x...',
  chainId: 1
}, {
  uiConfig: {
    showWalletUI: false
  }
});
Hiding the modal removes the user’s ability to review the authorization before signing. Only use this for trusted operations or after obtaining explicit user consent.

Best Practices

Always verify the contract address before delegating:
// ✅ Good - Validate contract
const isValidImplementation = await verifyContract(contractAddress);
if (!isValidImplementation) {
  throw new Error('Invalid or untrusted contract');
}

await sign7702Authorization({
  contractAddress,
  chainId: 1
});
Ensure the chain ID matches your target network:
const CHAIN_IDS = {
  mainnet: 1,
  base: 8453,
  optimism: 10,
  arbitrum: 42161
};

// Use correct chain ID
await sign7702Authorization({
  contractAddress: accountImplementation,
  chainId: CHAIN_IDS.base
});
Let MoonKey handle nonces automatically unless you have specific requirements:
// ✅ Good - Let MoonKey handle nonce
await sign7702Authorization({
  contractAddress,
  chainId: 1
  // nonce is automatically determined
});

// Only specify nonce if you need precise control
await sign7702Authorization({
  contractAddress,
  chainId: 1,
  nonce: specificNonce
});
Clearly explain what authorization means:
const handleEnable = async () => {
  const confirmed = confirm(
    'This will allow your wallet to use smart contract features. ' +
    'You maintain full control and can revoke this at any time. Continue?'
  );

  if (!confirmed) return;

  await sign7702Authorization({
    contractAddress,
    chainId: 1
  });
};
Allow users to revoke authorization:
async function revokeAuthorization(walletAddress: string) {
  // Call your AA provider's revocation endpoint
  await fetch('/api/aa-provider/revoke', {
    method: 'POST',
    body: JSON.stringify({ walletAddress })
  });

  alert('Authorization revoked. Your wallet is back to normal.');
}
Provide clear error messages:
try {
  await sign7702Authorization({
    contractAddress,
    chainId: 1
  });
} catch (error: any) {
  if (error.code === 'USER_REJECTED') {
    console.log('User cancelled authorization');
    return;
  } else if (error.message?.includes('nonce')) {
    alert('Nonce error. Please try again.');
  } else if (error.message?.includes('chain')) {
    alert('Wrong network. Please switch to the correct network.');
  } else {
    alert('Failed to authorize: ' + error.message);
  }
}

Troubleshooting

Common issues:
  • Wrong contract address: Verify the account implementation contract
  • Chain ID mismatch: Ensure authorization chain matches transaction chain
  • Nonce issues: Let MoonKey handle nonce automatically
  • Contract not compatible: Ensure contract supports EIP-7702
// Debug authorization
const authorization = await sign7702Authorization({
  contractAddress,
  chainId
});

console.log('Authorization details:', {
  contract: authorization.contractAddress,
  chain: authorization.chainId,
  nonce: authorization.nonce
});
Handle cancellations gracefully:
try {
  const authorization = await sign7702Authorization({
    contractAddress,
    chainId: 1
  });
} catch (error: any) {
  if (error.code === 'USER_REJECTED') {
    // User deliberately cancelled - don't show error
    console.log('Authorization cancelled by user');
    return;
  }
  // Handle other errors
  alert('Authorization failed: ' + error.message);
}
Ensure user is on the correct network:
import { useWalletChain } from '@moon-key/react-auth/ethereum';

const { chainId, switchChain } = useWalletChain();
const targetChainId = 8453; // Base

if (chainId !== targetChainId) {
  try {
    await switchChain(targetChainId);
  } catch (error) {
    alert('Please switch to Base network');
    return;
  }
}

await sign7702Authorization({
  contractAddress,
  chainId: targetChainId
});
Validate contract addresses:
import { isAddress } from 'viem';

if (!isAddress(contractAddress)) {
  alert('Invalid contract address');
  return;
}

// Optionally verify contract is deployed
const code = await publicClient.getBytecode({
  address: contractAddress as `0x${string}`
});

if (!code || code === '0x') {
  alert('Contract not found at this address');
  return;
}

await sign7702Authorization({
  contractAddress,
  chainId: 1
});

Next Steps