@biconomy/abstractjs
Version:
SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.
193 lines • 8.39 kB
JavaScript
import { concatHex } from "viem";
import { SIG_TYPE_SIMPLE_P256 } from "../../../account/utils/Constants.js";
import { versionIsAtLeast } from "../../../account/utils/getVersion.js";
import { isP256Signer } from "../../../account/utils/toP256Signer.js";
import { MEEVersion } from "../../../constants/index.js";
import { DEFAULT_MEE_SPONSORSHIP_PAYMASTER_ACCOUNT } from "../../createMeeClient.js";
const DEFAULT_PREFIX = "0x177eee00";
/**
* Prepares the payload required for signing a quote using personal message signatures.
* This function extracts the hash from the quote and formats it as a signable message for personal signature (eth_sign).
* Used for MEE versions < 2.2.0.
* The returned object contains the signable payload and optional metadata (currently empty, but can be extended).
*
* @param quote - The quote payload to be signed
* @returns An object containing the signable payload and metadata
*
* @example
* ```typescript
* const { signablePayload, metadata } = preparePersonalSignableQuotePayload(quotePayload);
* // signablePayload: { message: { raw: quotePayload.hash } }
* // metadata: {}
* ```
*/
export const preparePersonalSignableQuotePayload = (quote) => {
return {
signablePayload: {
message: { raw: quote.hash }
},
metadata: {}
};
};
/**
* Prepares the signable payload for EIP-712 typed data signatures.
* Used for MEE versions >= 2.2.1.
* It is an EIP-712 data structure with the following fields:
* - SuperTx(MeeUserOp[] meeUserOps)
* - MeeUserOp(bytes32 userOpHash,uint256 lowerBoundTimestamp,uint256 upperBoundTimestamp)
* - userOpHash and timestamps are present in the quote.userOps array for every userOp
*
* Note: Custom SuperTx data structs with mixed types are not supported yet.
* Only SuperTx(MeeUserOp[] meeUserOps)
*
* @param quote - The quote payload to be signed
* @param eip712Domain - The EIP-712 domain to be used for the signature
* @returns An object containing the signable payload and metadata
*
* @example
* ```typescript
* const { signablePayload, metadata } = prepareTypedDataSignableQuotePayload(quote, eip712Domain);
* // signablePayload: { domain, types, primaryType, message }
* // metadata: {}
* ```
*/
export const prepareTypedDataSignableQuotePayload = (quote, eip712Domain) => {
const isTrustedSponsorship = quote.userOps[0].userOp.sender.toLowerCase() ===
DEFAULT_MEE_SPONSORSHIP_PAYMASTER_ACCOUNT.toLowerCase();
const signablePayload = {
domain: {
name: eip712Domain.domain.name // name
// version: eip712Domain.domain.version // version, not used for the domain separator here
// chainId and verifyingContract are not used for the domain separator here
// since they are included in the userOpHash for every userOp
// chainId:,
// verifyingContract:
},
types: {
MeeUserOp: [
{ name: "userOpHash", type: "bytes32" },
{ name: "lowerBoundTimestamp", type: "uint256" },
{ name: "upperBoundTimestamp", type: "uint256" }
],
SuperTx: [{ name: "meeUserOps", type: "MeeUserOp[]" }]
},
primaryType: "SuperTx",
message: {
// If it is a trusted sponsorship, the payment userop can be skipped because it is not going to be executed at all.
meeUserOps: (isTrustedSponsorship
? quote.userOps.slice(1)
: quote.userOps).map((userOp) => ({
userOpHash: userOp.userOpHash,
lowerBoundTimestamp: userOp.lowerBoundTimestamp,
upperBoundTimestamp: userOp.upperBoundTimestamp
}))
}
};
return {
signablePayload,
metadata: {}
};
};
/**
* Formats the signed quote payload by attaching the signature to the original quote.
* The signature is prefixed and concatenated as required by the MEE service.
* Metadata is currently unused but reserved for future extensibility.
*
* @param quote - The original quote payload
* @param _metadata - Optional metadata (currently unused)
* @param signature - The signature to be attached to the quote
* @param meeVersion - The MEE version of the quote
* @returns The signed quote payload with the signature field
*
* @example
* ```typescript
* const signedQuote = formatSignedQuotePayload(quotePayload, {}, signature);
* // signedQuote: { ...quotePayload, signature: '0x177eee00<signature>' }
* ```
*/
export const formatSignedQuotePayload = (quote, _metadata, // This is unused for now. But can be extended in future
signature, meeVersions, isP256) => {
const prefix = isP256 ? SIG_TYPE_SIMPLE_P256 : DEFAULT_PREFIX;
return {
...quote,
signature: concatHex([prefix, signature]),
meeVersions: meeVersions,
isEIP712TrustedSponsorshipSupported: true
};
};
/**
* Signs a quote using the provided account's signer or the client's default account.
* Signs depending on the MEE version in the quote.
* For MEE >= 2.2.1, uses EIP-712 typed data signatures.
* For MEE < 2.2.1, uses personal message signatures.
* This is for the `smart-account` mode only.
*
* The signatures are required for executing the quote through the MEE service.
*
* @param client - The Mee client instance
* @param params - Parameters for signing the quote
* @param params.quote - The quote to sign (may contain operations across multiple chains)
* @param [params.account] - Optional account to use for signing
*
* @returns Promise resolving to the quote payload with added signature
*
* @example
* ```typescript
* const signedQuote = await signQuote(meeClient, {
* quote: quotePayload,
* account: smartAccount // Optional
* });
* // signedQuote.signature: Hex
* ```
*/
export const signQuote = async (client, params) => {
const { account: account_ = client.account, quote } = params;
const signer = account_.signer;
const isP256 = isP256Signer(signer);
const startIndex = quote.paymentInfo.sponsored ? 1 : 0;
const chainId = quote.userOps[startIndex].chainId;
// detect all the mee versions for the chains involved in the quote request
// pass userOps starting with startIndex
const meeVersions = getMeeVersionsForQuote(account_, quote.userOps.slice(startIndex));
// for MEE >= 2.2.1, use EIP-712 typed data signatures
if (versionIsAtLeast(meeVersions[0].version.version, MEEVersion.V2_2_1)) {
// get the eip712 domain
// since we only use the name from the 712 domain (see prepareTypedDataSignableQuotePayload)
// we can use the same eip712 domain for all the chains involved in the quote request
const deployment = account_.deploymentOn(Number(chainId), true);
const eip712Domain = await deployment.getEip712Domain();
if (!eip712Domain) {
throw new Error(`EIP-712 domain data not found for the multichain account on chain ${chainId}`);
}
const result = prepareTypedDataSignableQuotePayload(quote, eip712Domain);
const { signablePayload, metadata } = result;
const typedDataSignature = await signer.signTypedData(signablePayload);
return formatSignedQuotePayload(quote, metadata, typedDataSignature, meeVersions, isP256);
}
// for MEE < 2.2.1, use personal message signatures
const result = preparePersonalSignableQuotePayload(quote);
const { signablePayload, metadata } = result;
const personalSignature = await signer.signMessage(signablePayload);
return formatSignedQuotePayload(quote, metadata, personalSignature, meeVersions, isP256);
};
/**
*
* @param account - The multichain orchestrator smart account
* @param userOps - The user operations to get the MEE versions for. they do not include payment userOp if stx is sponsored
* @returns The MEE versions for the user operations
*/
export const getMeeVersionsForQuote = (account, userOps) => {
const usedChains = new Set();
for (const op of userOps) {
usedChains.add(Number(op.chainId));
}
return Array.from(usedChains, (chainId) => {
const deployment = account.deploymentOn(chainId, true);
return {
version: deployment.version,
chainId
};
});
};
export default signQuote;
//# sourceMappingURL=signQuote.js.map