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;
}