Skip to main content

Core Concepts

EIP-7702 Delegation

EIP-7702 allows users to delegate transaction execution to a smart contract. This is essential for enabling cross-chain transactions without requiring users to sign multiple transactions. Read more about EIP-7702 here.

Intent

An Intent is the parent action which contains ChainBatch(es) wherein each ChainBatch is for a specific chain and has many Call(s).

Chain Batch

Each chain involved in a cross-chain transaction requires its own batch with:
  • Chain ID
  • Call data (what to execute)
  • Recent block number

Chain Batch

A Chain Batch is the per-chain unit containing calls, chainId, and recentBlock. Each Chain Batch is hashed to create its hash which is included in the intent signature.

Setup and Dependencies

Required Dependencies

{
  "dependencies": {
    "viem": "^2.0.0",
    "wagmi": "^2.0.0"
  }
}

viem

Used for interacting with EVM chains. Documentation

wagmi

Used for wallet management and signing. Documentation

Core Type Definitions

import { Address, Hash, Hex } from "viem";

export type Call = {
  to: Address;
  value: bigint;
  data: Hex;
};

export interface ChainBatch {
  chainId: bigint | number;
  calls: Call[];
  recentBlock: bigint | number;
}

export type ChainAuthorization = {
  hash: Hash;
  chainId: bigint;
  calls: Call[];
  recentBlock: bigint;
};

// Intent is the parent action containing ChainBatch(es)
export type Intent = ChainBatch[];

export interface Authorization {
  address: string;
  chainId: number;
  nonce: number;
  r: string;
  s: string;
  yParity: number;
}

export interface TXSubmitData {
  authorization: Authorization[];
  intentAuthorization: {
    signature: string;
    chainBatches: Array<{
      hash: string;
      chainId: bigint | number;
      calls: Call[];
      recentBlock: bigint | number;
    }>;
  };
}
Types Guide

Intent Creation Implementation

Core Intent Functions

Here are the actual implementations you need:
import {
  Address,
  encodeAbiParameters,
  Hash,
  keccak256,
  PublicClient,
} from "viem";

const CallAbi = {
  type: "tuple",
  components: [
    { name: "to", type: "address" },
    { name: "value", type: "uint" },
    { name: "data", type: "bytes" },
  ],
} as const;

const ChainAuthorizationSignatureComponentsAbi = [
  { name: "chainId", type: "uint256" },
  { name: "calls", ...CallAbi, type: "tuple[]" },
  { name: "recentBlock", type: "uint256" },
] as const;

export function hashIntents(intent: Intent): ChainAuthorization[] {
  return intent.map(({ chainId, calls, recentBlock }) => {
    chainId = BigInt(chainId);
    recentBlock = BigInt(recentBlock);

    const hash = keccak256(
      encodeAbiParameters(ChainAuthorizationSignatureComponentsAbi, [
        chainId,
        calls,
        recentBlock,
      ]),
    );

    return {
      hash,
      chainId,
      calls,
      recentBlock,
    };
  });
}

export function getAuthorizationHash(
  chainAuthorizations: ChainAuthorization[],
): Hash {
  const chainAuthorizationHashes = chainAuthorizations.map(({ hash }) => hash);
  return keccak256(
    encodeAbiParameters([{ type: "bytes32[]" }], [chainAuthorizationHashes]),
  );
}

export function encodeAuthorization(authorization: Authorization): Hash {
  return encodeAbiParameters([AuthorizationAbi], [authorization]);
}

Utility Functions

export async function getRecentBlock(
  publicClient: PublicClient,
): Promise<bigint> {
  try {
    return await publicClient.getBlockNumber();
  } catch (error) {
    console.error("Error getting recent block:", error);
    return BigInt(0);
  }
}

export async function getAccountNonce(
  address: Address,
  publicClient: PublicClient,
): Promise<number> {
  try {
    return await publicClient.getTransactionCount({ address });
  } catch (error) {
    console.error("Error getting account nonce:", error);
    return 0;
  }
}

Creating Intent with Chain Batches

async function createIntent(
  sourceChainId: number,
  destinationChainId: number,
  sourceCalls: Call[],
  destinationCalls: Call[],
) {
  // Get chain-specific clients (implement based on your setup)
  const sourceClient = getPublicClient({ chainId: sourceChainId });
  const destinationClient = getPublicClient({ chainId: destinationChainId });

  // Get recent blocks from each chain
  const sourceRecentBlock = await getRecentBlock(sourceClient);
  const destinationRecentBlock = await getRecentBlock(destinationClient);

  // Create intent with chain batches
  const intent: Intent = [
    {
      chainId: sourceChainId,
      recentBlock: sourceRecentBlock,
      calls: sourceCalls,
    },
    {
      chainId: destinationChainId,
      recentBlock: destinationRecentBlock + BigInt(10), // Add buffer
      calls: destinationCalls,
    },
  ];

  // Hash the intent to create chain authorizations
  const chainAuthorizations = hashIntents(intent);

  return chainAuthorizations;
}

Delegation Signing

EIP-7702 Delegation Process

Each chain requires a separate EIP-7702 delegation signature.
async function signDelegation(
  walletClient: WalletClient,
  address: Address,
  chainId: number,
  delegateContractAddress: Address,
): Promise<Authorization> {
  const publicClient = getPublicClient({ chainId });
  const nonce = await getAccountNonce(address, publicClient);

  const domain = {
    name: "Authorization",
    version: "1",
    chainId: BigInt(chainId),
    verifyingContract: delegateContractAddress,
  } as const;

  const types = {
    Authorization: [
      { name: "contractAddress", type: "address" },
      { name: "chainId", type: "uint256" },
      { name: "nonce", type: "uint256" },
    ],
  } as const;

  const message = {
    contractAddress: delegateContractAddress,
    chainId: BigInt(chainId),
    nonce: BigInt(nonce),
  } as const;

  const signature = await walletClient.signTypedData({
    account: address,
    domain,
    types,
    primaryType: "Authorization",
    message,
  });

  const authorization = {
    address: address.toString(),
    chainId: Number(chainId),
    nonce: Number(nonce),
    r: signature.slice(0, 66) as `0x${string}`,
    s: `0x${signature.slice(66, 130)}` as `0x${string}`,
    yParity: parseInt(signature.slice(130, 132), 16) as 0 | 1,
  };

  return authorization;
}

Multi-Chain Delegation Example

async function signMultiChainDelegations(
  walletClient: WalletClient,
  address: Address,
  sourceChainId: number,
  destinationChainId: number,
) {
  const delegateContractAddress =
    "0x8A6EA383e3b570e1897ca6E4E52B5B669CcbF4AE" as Address;

  // Sign delegation for source chain
  const sourceAuth = await signDelegation(
    walletClient,
    address,
    sourceChainId,
    delegateContractAddress,
  );

  // Sign delegation for destination chain
  const destinationAuth = await signDelegation(
    walletClient,
    address,
    destinationChainId,
    delegateContractAddress,
  );

  return [sourceAuth, destinationAuth];
}

Transaction Submission

Complete Transaction Submission

async function submitCrossChainTransaction(
  sourceChainId: number,
  destinationChainId: number,
  sourceCalls: Call[],
  destinationCalls: Call[],
  authorizations: Authorization[],
) {
  // 1. Create intent with chain batches
  const chainAuthorizations = await createIntent(
    sourceChainId,
    destinationChainId,
    sourceCalls,
    destinationCalls,
  );

  // 2. Sign intent authorization
  const digest = getAuthorizationHash(chainAuthorizations);
  const intentSignature = await walletClient.signMessage({
    account: address,
    message: { raw: digest },
  });

  // 3. Submit to API
  const request: TXSubmitData = {
    authorization: authorizations,
    intentAuthorization: {
      signature: intentSignature,
      chainAuthorizations,
    },
  };
  const response = await fetch(`${url}/transaction/submit`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(request, (key, value) =>
      typeof value === "bigint" ? value.toString() : value,
    ),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`API error: ${response.status} - ${errorText}`);
  }

  return response;
}

Best Practices

1. Chain Switching

// Always switch to the correct chain before signing delegations
async function signDelegationForChain(chainId: number, targetNetwork: Network) {
  // Switch to target chain (hook from reown appkit)
  await switchNetwork(targetNetwork);

  // Wait for network switch to complete
  await new Promise((resolve) => setTimeout(resolve, 2000));

  // Sign delegation
  return await signDelegation(chainId);
}

2. Progress Tracking

// Track transaction progress
const [progress, setProgress] = useState<string>("");

const executeTransaction = async () => {
  setProgress("Signing source chain delegation...");
  const sourceAuth = await signDelegation(sourceChainId);

  setProgress("Signing destination chain delegation...");
  const destinationAuth = await signDelegation(destinationChainId);

  setProgress("Submitting transaction...");
  const result = await submitTransaction([sourceAuth, destinationAuth]);

  setProgress("Transaction submitted successfully");
  return result;
};