UNPKG

@biconomy/abstractjs

Version:

SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.

123 lines 4.63 kB
import { concatHex, encodeAbiParameters, getContract, parseSignature } from "viem"; import { PERMIT_TYPEHASH } from "../../../constants/index.js"; import { TokenWithPermitAbi } from "../../../constants/abi/TokenWithPermitAbi.js"; const PERMIT_PREFIX = "0x177eee02"; /** * Signs a permit quote using EIP-2612 permit signatures. This enables gasless * approvals for ERC20 tokens that implement the permit extension. * * @param client - The Mee client instance * @param parameters - Parameters for signing the permit quote * @param parameters.fusionQuote - The permit quote to sign * @param [parameters.account] - Optional account to use for signing * * @returns Promise resolving to the quote payload with permit signature * * @example * ```typescript * const signedPermitQuote = await signPermitQuote(meeClient, { * fusionQuote: { * quote: quotePayload, * trigger: { * tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", * chainId: 1, * amount: 1000000n // 1 USDC * } * }, * account: smartAccount // Optional * }); * ``` */ export const signPermitQuote = async (client, parameters) => { const { companionAccount: account_ = client.account, fusionQuote: { quote, trigger } } = parameters; // Type guard to ensure we have a TokenTrigger if (trigger.call) { throw new Error("Custom triggers are not supported for permit quotes"); } const signer = account_.signer; if (!trigger.amount) throw new Error("Amount is required to sign a permit quote"); // check if we have an explicit `approvalAmount` set and error if it's smaller than the trigger amount if (trigger.approvalAmount && trigger.amount !== undefined && trigger.approvalAmount < trigger.amount) { throw new Error(`Approval amount must be bigger or equal with the amount from the trigger (triggerAmount: ${trigger.amount} amount: ${trigger.approvalAmount})`); } const amount = trigger.approvalAmount ?? trigger.amount; const { walletClient, address: spender } = account_.deploymentOn(trigger.chainId, true); const owner = signer.address; const token = getContract({ abi: TokenWithPermitAbi, address: trigger.tokenAddress, client: walletClient }); const values = await Promise.allSettled([ token.read.nonces([owner]), token.read.name(), token.read.version(), token.read.DOMAIN_SEPARATOR() ]); const [nonce, name, version, domainSeparator] = values.map((value, i) => { const key = ["nonce", "name", "version", "domainSeparator"][i]; if (value.status === "fulfilled") { return value.value; } if (value.status === "rejected" && key === "version") { return "1"; } throw new Error(`Failed to get value: ${value.reason}`); }); const signature = await walletClient.signTypedData({ domain: { name, version, chainId: trigger.chainId, verifyingContract: trigger.tokenAddress }, types: { Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" } ] }, primaryType: "Permit", message: { owner, spender: spender, value: amount, nonce, deadline: BigInt(quote.hash) }, account: walletClient.account }); const sigComponents = parseSignature(signature); const encodedSignature = encodeAbiParameters([ { name: "token", type: "address" }, { name: "spender", type: "address" }, { name: "domainSeparator", type: "bytes32" }, { name: "permitTypehash", type: "bytes32" }, { name: "amount", type: "uint256" }, { name: "chainId", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "v", type: "uint256" }, { name: "r", type: "bytes32" }, { name: "s", type: "bytes32" } ], [ trigger.tokenAddress, spender, domainSeparator, PERMIT_TYPEHASH, amount, BigInt(trigger.chainId), nonce, sigComponents.v, sigComponents.r, sigComponents.s ]); return { ...quote, signature: concatHex([PERMIT_PREFIX, encodedSignature]) }; }; export default signPermitQuote; //# sourceMappingURL=signPermitQuote.js.map