> ## Documentation Index
> Fetch the complete documentation index at: https://docs.biconomy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 3. Sign Payload(s)

> Once you've received the payloads from the API, you need to sign them to approve execution.

## Signature Formats by Mode and Version

MEE v2.2.1+ introduces EIP-712 typed data signing for `smart-account` mode, providing better security and user experience through structured, human-readable signing requests.

| Mode            | Account Version       | Signature Format   | Signing Method               |
| --------------- | --------------------- | ------------------ | ---------------------------- |
| `smart-account` | v2.2.1+               | EIP-712 Typed Data | `eth_signTypedData_v4`       |
| `smart-account` | v2.1.0 (upgrade flow) | Personal Message   | `eth_sign` / `personal_sign` |
| `eoa` (Fusion)  | Any                   | Permit / Onchain   | Mode-specific                |
| `eoa-7702`      | v2.1.0                | Personal Message   | `eth_sign` / `personal_sign` |

<Info>
  The API automatically returns the correct `payloadToSign` format based on your account version and mode. Your signing code should detect and handle both formats.
</Info>

## Quick Start: Copy-Paste Utilities

**For TypeScript users:** Copy the utilities below directly into your project.\
**Not using TypeScript?** Implement your own version following the same logic shown in the code.

<Tip>
  **Using a single execution mode?** The utility below handles all signature types, but each mode only uses a subset:

  | Mode            | Signature Types Used                        |
  | --------------- | ------------------------------------------- |
  | `smart-account` | `simple` only (EIP-712 or personal message) |
  | `eoa-7702`      | `simple` only (personal message)            |
  | `eoa` (Fusion)  | `permit` or `onchain` only                  |

  If your application uses only one mode, you can create a lighter implementation that handles just the signature types you'll encounter.
</Tip>

<Expandable title="Click to view signing utilities code">
  ```typescript theme={null}
  import {
    Address,
    createWalletClient,
    Hex,
    http,
    OneOf,
    publicActions,
    SignTypedDataParameters,
  } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { base } from "viem/chains";

  /**
   * Types for different signable payloads.
   */

  // Payload for EIP-712 typed data signature (smart-account v2.2.1+, permits)
  type SignableTypedDataPayload = Omit<SignTypedDataParameters, "account">;

  // Payload for personal message signing (legacy v2.1.0, eoa-7702)
  type SignablePersonalMessagePayload = {
    message: {
      raw: Hex;
    };
  };

  // Union type for "simple" quote type payloads (can be either format)
  type SignableSimplePayload = SignableTypedDataPayload | SignablePersonalMessagePayload;

  // Payload for on-chain transaction signing
  type SignableOnChainPayload =
    | {
        data: Hex;
        to: Address;
        chainId: number;
        value: string;
      }
    | {
        data: Hex;
        to: Address;
        chainId: number;
        value?: string | undefined;
      };

  /**
   * Type guard to detect EIP-712 typed data payload.
   * v2.2.1+ smart accounts use typed data, v2.1.0 uses personal message.
   */
  function isTypedDataPayload(
    payload: SignableSimplePayload
  ): payload is SignableTypedDataPayload {
    return 'domain' in payload && 'primaryType' in payload;
  }

  /**
   * Example EOA account and wallet client setup.
   * Replace the private key with a real one for production use.
   */
  const eoaAccount = privateKeyToAccount("0x"); // ⚠️ Replace with your private key

  const walletClient = createWalletClient({
    account: eoaAccount,
    chain: base,
    transport: http(), // ⚠️ Use private paid RPC for production
  }).extend(publicActions);

  /**
   * Signs a "simple" quote payload. Handles both formats:
   * - EIP-712 typed data (v2.2.1+ smart accounts)
   * - Personal message (v2.1.0 legacy accounts, eoa-7702)
   *
   * @param signablePayload - The payload to sign (auto-detected format).
   * @returns The signature as a Hex string.
   *
   * @example
   * // EIP-712 typed data (v2.2.1+ smart account)
   * const signature = await signSimpleQuoteSignablePayload({
   *   domain: { name: "Nexus" },
   *   types: { MeeUserOp: [...], SuperTx: [{ name: "meeUserOps", type: "MeeUserOp[]" }] },
   *   primaryType: "SuperTx",
   *   message: { meeUserOps: [{ userOpHash: "0x...", lowerBoundTimestamp: 0, upperBoundTimestamp: 1765465749 }] }
   * });
   *
   * @example
   * // Personal message (v2.1.0 or eoa-7702)
   * const signature = await signSimpleQuoteSignablePayload({
   *   message: { raw: "0x68656c6c6f" }
   * });
   */
  const signSimpleQuoteSignablePayload = async (
    signablePayload: SignableSimplePayload
  ): Promise<Hex> => {
    if (isTypedDataPayload(signablePayload)) {
      // EIP-712 typed data for v2.2.1+ smart accounts
      return await walletClient.signTypedData({
        ...signablePayload,
        account: eoaAccount,
      });
    } else {
      // Personal message for v2.1.0 accounts or eoa-7702 mode
      return await eoaAccount.signMessage(signablePayload);
    }
  };

  /**
   * Signs a permit quote payload (EIP-712 typed data for ERC20 permits).
   *
   * @param signablePayload - The EIP-712 payload to sign.
   * @returns The signature as a Hex string.
   *
   * @example
   * const signature = await signPermitQuoteSignablePayload({
   *   domain: { ... },
   *   types: { ... },
   *   primaryType: "Permit",
   *   message: { ... }
   * });
   */
  const signPermitQuoteSignablePayload = async (
    signablePayload: SignableTypedDataPayload
  ): Promise<Hex> => {
    const signature = await walletClient.signTypedData({
      ...signablePayload,
      account: eoaAccount,
    });
    return signature;
  };

  /**
   * Signs an on-chain quote payload (transaction).
   *
   * @param signablePayload - The transaction payload to send.
   * @returns The transaction hash as a Hex string.
   *
   * @example
   * const txHash = await signOnChainQuoteSignablePayload({
   *   to: "0x...",
   *   data: "0x...",
   *   value: "0",
   *   chainId: 1
   * });
   */
  const signOnChainQuoteSignablePayload = async (
    signablePayload: SignableOnChainPayload
  ): Promise<Hex> => {
    const hash = await walletClient.sendTransaction({
      to: signablePayload.to,
      data: signablePayload.data,
      value: BigInt(signablePayload.value || "0")
    });
    // Wait for 5 confirmations before returning the hash
    await walletClient.waitForTransactionReceipt({ hash, confirmations: 5 });
    return hash;
  };

  /**
   * Union type for all supported payloads to sign.
   */
  type PayloadToSign = OneOf<
    | {
        signablePayload: SignableSimplePayload;
        metadata?: any;
      }
    | {
        signablePayload: SignableTypedDataPayload;
        metadata: {
          nonce: string;
          name: string;
          version: string;
          domainSeparator: string;
          owner: string;
          spender: string;
          amount: string;
        };
      }
    | SignableOnChainPayload
  >;

  /**
   * Signs a quote payload based on its type.
   * Automatically handles both EIP-712 and personal message formats for "simple" type.
   *
   * @param quoteType - The type of quote from the API response.
   * @param payloadToSign - The payload to sign.
   * @returns The signature or transaction hash as a Hex string.
   *
   * @example
   * // Simple - EIP-712 (v2.2.1+ smart account)
   * const sig = await signQuoteSignablePayload("simple", {
   *   signablePayload: { domain: { name: "Nexus" }, types: {...}, primaryType: "SuperTx", message: { meeUserOps: [...] } }
   * });
   *
   * @example
   * // Simple - Personal message (v2.1.0 or eoa-7702)
   * const sig = await signQuoteSignablePayload("simple", {
   *   signablePayload: { message: { raw: "0x68656c6c6f" } }
   * });
   *
   * @example
   * // Permit (EIP-712 for ERC20)
   * const sig = await signQuoteSignablePayload("permit", {
   *   signablePayload: { domain: {...}, types: {...}, primaryType: "Permit", message: {...} },
   *   metadata: { ... }
   * });
   *
   * @example
   * // On-chain transaction
   * const txHash = await signQuoteSignablePayload("onchain", {
   *   to: "0x...",
   *   data: "0x...",
   *   value: "0",
   *   chainId: 1
   * });
   */
  const signQuoteSignablePayload = async (
    quoteType: string,
    payloadToSign: PayloadToSign
  ): Promise<Hex> => {
    let signature: Hex = "0x";

    switch (quoteType) {
      case "simple":
        // Auto-detects EIP-712 vs personal message format
        signature = await signSimpleQuoteSignablePayload(payloadToSign.signablePayload || payloadToSign);
        break;
      case "permit":
        signature = await signPermitQuoteSignablePayload(payloadToSign.signablePayload);
        break;
      case "onchain":
        signature = await signOnChainQuoteSignablePayload(payloadToSign);
        break;
      default:
        throw new Error("Unsupported quote type, can't sign the payload");
    }

    return signature;
  };
  ```
</Expandable>

## How It Works: Three Signature Types

* The API returns an array of payloads to sign.
* In most cases this array has just one item.
* Each item in the payload is one of three signature types (simple, permit, onchain) depending on your execution mode and token support

<Tabs>
  <Tab title="Simple">
    ### Simple (Smart Account & EIP-7702)

    **What**: Signs a message for smart account or EIP-7702 execution\
    **When**: `smart-account` mode or `eoa-7702` mode\
    **Result**: Off-chain signature

    <Info>
      **By default, v2.2.1+ uses EIP-712 typed data** for `smart-account` mode. Personal message signing only occurs in two cases: `eoa-7702` mode, or when upgrading legacy v2.1.0 smart accounts.
    </Info>

    #### EIP-712 Typed Data (Default for Smart Accounts)

    For v2.2.1+ smart accounts (the default), the payload contains EIP-712 structured data:

    ```javascript theme={null}
    // Example payload from API (v2.2.1+ smart account)
    {
      domain: {
        name: "Nexus"
      },
      types: {
        MeeUserOp: [
          { name: "userOpHash", type: "bytes32" },
          { name: "lowerBoundTimestamp", type: "uint256" },
          { name: "upperBoundTimestamp", type: "uint256" }
        ],
        SuperTx: [
          { name: "meeUserOps", type: "MeeUserOp[]" }
        ]
      },
      primaryType: "SuperTx",
      message: {
        meeUserOps: [
          {
            userOpHash: "0xa6c23856046666c02f7a85f0b2e7dbc78beac25db5c1955ddc9eb78e0237fc9f",
            lowerBoundTimestamp: 0,
            upperBoundTimestamp: 1765465749
          }
        ]
      }
    }
    ```

    **How to sign EIP-712:**

    ```typescript theme={null}
    const signature = await walletClient.signTypedData({
      ...payload,
      account
    });
    ```

    #### Personal Message (EIP-7702 & Legacy Upgrade Only)

    For `eoa-7702` mode and v2.1.0 accounts during upgrade, the payload contains a raw message:

    ```javascript theme={null}
    // Example payload from API (v2.1.0 or eoa-7702)
    {
      message: {
        raw: "0xed96a92aa94b8b1927fc7c52ca3b3fcd0d706147dfbeda34a62dc232f6993029"
      }
    }
    ```

    **How to sign personal message:**

    ```typescript theme={null}
    const signature = await walletClient.signMessage({
      ...payload,
      account
    });
    ```

    #### Detecting the Format

    Use a type guard to automatically detect and sign the correct format:

    ```typescript theme={null}
    // Type guard to detect EIP-712 typed data
    function isTypedDataSignature(payload: unknown): payload is TypedDataPayload {
      return typeof payload === 'object' && payload !== null
        && 'domain' in payload && 'primaryType' in payload;
    }

    // Sign based on detected format
    const signature = isTypedDataSignature(payload)
      ? await walletClient.signTypedData({ ...payload, account })  // EIP-712 for v2.2.1+
      : await walletClient.signMessage({ ...payload, account });   // Personal message for v2.1.0/7702
    ```
  </Tab>

  <Tab title="Permit">
    ### Permit (EIP-712 Typed Data)

    **What**: Signs structured EIP-712 data for ERC20 Permit\
    **When**: EOAs with tokens that support ERC20Permit\
    **Result**: Off-chain signature\
    **Special**: API embeds supertx hash in `deadline` field

    ```javascript theme={null}
    // Example payload from API
    {
      signablePayload: {
        domain: {
          name: "USD Coin",
          version: "2",
          chainId: 1,
          verifyingContract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
        },
        types: {
          Permit: [/* ... */]
        },
        primaryType: "Permit",
        message: {
          owner: "0x...",
          spender: "0x...",
          value: "1000000000",
          nonce: 0,
          deadline: "0xabcdef..." // ← Supertx hash here!
        }
      },
      metadata: {
        nonce: "0",
        name: "USD Coin",
        version: "2",
        domainSeparator: "0x06c37168a7db5138defc7866392bb87a741f9b3d104deb5094588ce041cae335",
        owner: "0x742d35C9a91B1D5b5D24Dc30e8F0dF8E84b5d1c4",
        spender: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45",
        amount: "1000000000000000000000"
      }
    }
    ```

    **How to sign:**

    ```typescript theme={null}
    const signature = await walletClient.signTypedData({
      ...payload.signablePayload,
      account
    });
    ```
  </Tab>

  <Tab title="Onchain">
    ### Onchain (Transaction)

    **What**: Sends an actual blockchain transaction\
    **When**: EOAs with tokens that don't support ERC20Permit\
    **Result**: Transaction hash\
    **Special**: API appends supertx hash to calldata

    ```javascript theme={null}
    // Example payload from API
    {
      to: "0x1111111254EEB25477B68fb85Ed929f73A960582",
      data: "0xa9059cbb...abcdef1234567890", // ← Supertx hash appended!
      value: "0",
      chainId: 1
    }
    ```

    **How to handle:**

    ```typescript theme={null}
    // Send approval transaction on-chain
    const txHash = await walletClient.sendTransaction({
      to: payload.to,
      data: payload.data,
      value: payload.value || 0n
    });

    // Wait for confirmation
    await publicClient.waitForTransactionReceipt({ hash: txHash });

    // Use txHash as signature
    const signedPayload = [{ ...payload, signature: txHash }];
    ```
  </Tab>
</Tabs>

## The Magic: Supertx Hash Embedding

For EOA mode, the API cleverly embeds the supertransaction hash into your signatures. This creates a cryptographic link between funding and execution, enabling single-signature cross-chain operations.

**For Permit**: The supertx hash replaces the `deadline` field

```javascript theme={null}
// Before: deadline = 1700000000
// After:  deadline = "0xabcdef1234567890..." (supertx hash)
```

**For Onchain**: The supertx hash is appended to calldata

```javascript theme={null}
// Before: 0xa9059cbb000000000000000000000000...
// After:  0xa9059cbb000000000000000000000000...abcdef1234567890
```

## Usage by Execution Mode

<Tabs>
  <Tab title="EOA">
    **Signature Requirements:**

    * One signature per funding token
    * Type depends on token support (permit or onchain)

    ```typescript theme={null}
    // 1. Get quote
    const quoteResponse = await fetch('/v1/quote', {
      method: 'POST',
      headers: { 'X-API-Key': 'YOUR_API_KEY' },
      body: JSON.stringify({
        mode: "eoa",
        ownerAddress: "0x...",
        fundingTokens: [{
          tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
          chainId: 1,
          amount: "1000000000"
        }],
        instructions: [...]
      })
    });

    const { payloadToSign, quote, fee, quoteType, ownerAddress } = await quoteResponse.json();

    // 2. Sign each payload (one per funding token)
    for (let i = 0; i < payloadToSign.length; i++) {
      const payload = payloadToSign[i];

      // Use the utility function - it handles all types
      const signature = await signQuoteSignablePayload(quoteType, payload);

      // Add signature back to payload
      payloadToSign[i].signature = signature;
    }

    // 3. Execute with signed payloads
    await fetch('/v1/execute', {
      method: 'POST',
      body: JSON.stringify({
        ownerAddress,
        fee,
        quoteType,
        quote,
        payloadToSign
      })
    });
    ```
  </Tab>

  <Tab title="EOA-7702">
    **Signature Requirements:**

    * Authorization signature (if not already delegated)
    * One simple signature for execution

    ```typescript theme={null}
    // 1. Get quote (may need authorization first)
    let quoteResponse = await fetch('/v1/quote', {
      method: 'POST',
      headers: { 'X-API-Key': 'YOUR_API_KEY' },
      body: JSON.stringify({
        mode: "eoa-7702",
        ownerAddress: "0x...",
        instructions: [...]
      })
    });

    // 2. Handle authorization if needed
    if (quoteResponse.status === 412) {
      const error = await quoteResponse.json();
      const auth = error.authorizations[0];

      // Sign authorization (wallet-specific method)
      const authSig = await wallet.signAuthorization({
        contractAddress: auth.address,
        chainId: auth.chainId,
        nonce: auth.nonce
      });

      // Retry with authorization
      quoteResponse = await fetch('/v1/quote', {
        method: 'POST',
        body: JSON.stringify({
          mode: "eoa-7702",
          ownerAddress: "0x...",
          authorizations: [authSig],
          instructions: [...]
        })
      });
    }

    const { payloadToSign, quote, fee, quoteType, ownerAddress } = await quoteResponse.json();

    // 3. Sign execution (always simple type)
    const signature = await signQuoteSignablePayload(quoteType, payloadToSign[0]);
    payloadToSign[0].signature = signature;

    // 4. Execute
    await fetch('/v1/execute', {
      method: 'POST',
      body: JSON.stringify({
        ownerAddress,
        fee,
        quoteType,
        quote,
        payloadToSign
      })
    });
    ```
  </Tab>

  <Tab title="Smart Account">
    **Signature Requirements:**

    * Always exactly one signature
    * **v2.2.1+ accounts**: EIP-712 typed data (structured, human-readable)
    * **v2.1.0 accounts** (upgrade flow): Personal message

    <Info>
      v2.2.1+ uses EIP-712 typed data which provides better UX - wallets display structured transaction details instead of opaque hex strings, helping users understand what they're signing.
    </Info>

    ```typescript theme={null}
    // Type guard to detect signature format
    function isTypedDataSignature(payload: unknown): payload is TypedDataPayload {
      return typeof payload === 'object' && payload !== null
        && 'domain' in payload && 'primaryType' in payload;
    }

    // 1. Get quote
    const quoteResponse = await fetch('/v1/quote', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        mode: "smart-account",
        ownerAddress: "0x...",
        composeFlows: [...]
      })
    });

    const { payloadToSign, quote, fee, quoteType, ownerAddress } = await quoteResponse.json();

    // 2. Sign - auto-detect format based on account version
    const payload = payloadToSign[0];
    const signature = isTypedDataSignature(payload.signablePayload || payload)
      ? await walletClient.signTypedData({ ...(payload.signablePayload || payload), account })
      : await walletClient.signMessage({ ...(payload.signablePayload || payload), account });

    payloadToSign[0].signature = signature;

    // 3. Execute
    await fetch('/v1/execute', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ownerAddress,
        fee,
        quoteType,
        quote,
        payloadToSign
      })
    });
    ```
  </Tab>
</Tabs>

## Summary

1. **Copy the utilities** - They handle all signature types automatically
2. **Get a quote** - The API returns the appropriate payload type
3. **Sign with utilities** - `signQuoteSignablePayload()` routes to the right method
4. **Execute** - Send signed payloads back to complete the supertransaction

The utilities abstract away the complexity while the API's clever hash embedding enables powerful single-signature cross-chain operations.

## Why EIP-712 Typed Data?

MEE v2.2.1+ uses EIP-712 typed data signing for `smart-account` mode for several benefits:

<AccordionGroup>
  <Accordion title="Human-Readable Signing">
    Wallets display structured data instead of opaque hex strings. Users can see transaction details like amounts, recipients, and deadlines before signing.
  </Accordion>

  <Accordion title="Phishing Resistance">
    Users can verify transaction details in their wallet UI before signing, making it harder for malicious sites to trick users into signing harmful transactions.
  </Accordion>

  <Accordion title="Domain Separation">
    Signatures are cryptographically bound to specific contracts and chains, preventing signature replay attacks across different applications or networks.
  </Accordion>

  <Accordion title="Type Safety">
    Structured types prevent signature malleability attacks and ensure data integrity throughout the signing process.
  </Accordion>
</AccordionGroup>

## Migration Notes

When migrating from v2.1.0 to v2.2.1+ or supporting both versions:

1. **Update signing logic** - Use the type guard to handle both payload formats
2. **Test with both formats** - Ensure backward compatibility with legacy accounts
3. **No API changes needed** - The API automatically returns the correct format based on account version
4. **Upgrade transactions use personal message** - When upgrading a v2.1.0 account, the upgrade quote uses personal message signing (not EIP-712)
