@biconomy/abstractjs
Version:
SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.
123 lines • 4.63 kB
JavaScript
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