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

# Signing the intent

> Sign each element of the intent using EIP-712.

<Info>
  Always fetch a fresh quote immediately before signing. Submitting signatures built from a stale quote will result in an expiry error.
</Info>

Warp uses [EIP-712](https://eips.ethereum.org/EIPS/eip-712) for typed, human-readable signing. The signature scheme depends on the settlement layer used for each bundle element.

## Signature types

**Permit2 signatures** are used for standard crosschain intents. Each source chain element requires a unique signature. The index of the signature must correspond to the index of the element it signs.

**Intent Executor signatures** are used when the bundle uses the Intent Executor settlement layer (i.e. the user has an ERC-7579 smart account with the Intent Executor installed). These use a different EIP-712 domain — not the standard Permit2 domain. The `getTypedData` function below handles this automatically based on the `settlementLayer` field in the element.

## Recipient signatures

If the recipient is different from the sender, the recipient is a smart account, and there are destination executions to run, the recipient must also sign. Their signature is attached as the `destinationSignature`.

## Preparing the typed data

```ts theme={null}
interface TokenPermissions {
  token: Address;
  amount: bigint;
}

interface Ops {
  to: Address;
  value: string;
  data: Hex;
}

interface Op {
  vt: Hex;
  ops: Ops[];
}

interface IntentOpElementMandate {
  recipient: Address;
  tokenOut: [[string, string]];
  destinationChainId: string;
  fillDeadline: string;
  destinationOps: Op;
  preClaimOps: Op;
  qualifier: {
    settlementContext: {
      settlementLayer: string;
      usingJIT: boolean;
      using7579: boolean;
    };
    encodedVal: Hex;
  };
  minGas: string;
}

interface IntentOpElement {
  arbiter: Address;
  chainId: string;
  idsAndAmounts: [[string, string]];
  spendTokens: [[string, string]];
  beforeFill: boolean;
  smartAccountStatus: {
    accountType: string;
    isDeployed: boolean;
    isERC7579: boolean;
    erc7579AccountType: string;
    erc7579AccountVersion: string;
  };
  mandate: IntentOpElementMandate;
}

function toToken(id: bigint): Address {
  return `0x${(id & ((1n << 160n) - 1n)).toString(16).padStart(40, "0")}`;
}

function getTypedData(
  element: IntentOpElement,
  nonce: bigint,
  expires: bigint,
) {
  const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";

  const tokens = element.idsAndAmounts.map(([id, amount]) => [
    BigInt(id),
    BigInt(amount),
  ]);
  const tokenPermissions = tokens.reduce<TokenPermissions[]>(
    (permissions, [id, amountIn]) => {
      const token = toToken(BigInt(id));
      const amount = BigInt(amountIn);
      permissions.push({ token, amount });
      return permissions;
    },
    [],
  );

  const spender = element.arbiter;
  const mandate = element.mandate;

  return {
    domain: {
      name: "Permit2",
      chainId: Number(element.chainId),
      verifyingContract: PERMIT2_ADDRESS,
    },
    types: {
      TokenPermissions: [
        { name: "token", type: "address" },
        { name: "amount", type: "uint256" },
      ],
      Token: [
        { name: "token", type: "address" },
        { name: "amount", type: "uint256" },
      ],
      Target: [
        { name: "recipient", type: "address" },
        { name: "tokenOut", type: "Token[]" },
        { name: "targetChain", type: "uint256" },
        { name: "fillExpiry", type: "uint256" },
      ],
      Ops: [
        { name: "to", type: "address" },
        { name: "value", type: "uint256" },
        { name: "data", type: "bytes" },
      ],
      Op: [
        { name: "vt", type: "bytes32" },
        { name: "ops", type: "Ops[]" },
      ],
      Mandate: [
        { name: "target", type: "Target" },
        { name: "minGas", type: "uint128" },
        { name: "originOps", type: "Op" },
        { name: "destOps", type: "Op" },
        { name: "q", type: "bytes32" },
      ],
      PermitBatchWitnessTransferFrom: [
        { name: "permitted", type: "TokenPermissions[]" },
        { name: "spender", type: "address" },
        { name: "nonce", type: "uint256" },
        { name: "deadline", type: "uint256" },
        { name: "mandate", type: "Mandate" },
      ],
    },
    primaryType: "PermitBatchWitnessTransferFrom",
    message: {
      permitted: tokenPermissions,
      spender,
      nonce,
      deadline: expires,
      mandate: {
        target: {
          recipient: mandate.recipient,
          tokenOut: mandate.tokenOut.map((token) => ({
            token: toToken(BigInt(token[0])),
            amount: BigInt(token[1]),
          })),
          targetChain: BigInt(mandate.destinationChainId),
          fillExpiry: BigInt(mandate.fillDeadline),
        },
        minGas: BigInt(mandate.minGas),
        originOps: mandate.preClaimOps,
        destOps: mandate.destinationOps,
        q: keccak256(mandate.qualifier.encodedVal),
      },
    },
  } as const;
}
```

## Signing each element

One signature per element, in order:

```ts theme={null}
const signatures: Hex[] = [];

for (const element of intentOp.elements) {
  const typedData = getTypedData(
    element,
    BigInt(intentOp.nonce),
    BigInt(intentOp.expires),
  );

  const signature = await signTypedData(wagmiConfig, { ...typedData });
  signatures.push(signature);
}

const originSignatures = signatures;
const destinationSignature = signatures.at(-1); // last signature doubles as destination
```

## Next steps

<Card title="Submitting the intent" icon="send" href="./submitting-the-intent">
  Submit the signed intent to the Orchestrator.
</Card>
