@biconomy/abstractjs
Version:
SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.
151 lines • 6.24 kB
JavaScript
import { concatHex, encodeAbiParameters, erc20Abi, zeroAddress } from "viem";
import { encodeFunctionData } from "viem";
import { ForwarderAbi } from "../../../constants/abi/ForwarderAbi.js";
export const FUSION_NATIVE_TRANSFER_PREFIX = "0x150b7a02";
export const ON_CHAIN_PREFIX = "0x177eee01";
/**
* Generates a trigger call from a trigger
* @private
*/
const generateTriggerCallFromTrigger = async ({ trigger, spender, recipient, version }) => {
let triggerCall;
// build custom call
if (trigger.call) {
triggerCall = trigger.call;
}
else if (trigger.tokenAddress === zeroAddress) {
// If the token address is zero address, we need to send eth via the ETH forwarder
const forwardCalldata = encodeFunctionData({
abi: ForwarderAbi,
functionName: "forward",
args: [recipient]
});
const ethForwardCall = {
to: version.ethForwarderAddress,
data: forwardCalldata,
value: trigger.amount
};
triggerCall = ethForwardCall;
}
else {
// erc20 trigger
// 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;
if (!amount)
throw new Error("Invalid trigger amount");
const approveCall = {
to: trigger.tokenAddress,
data: encodeFunctionData({
abi: erc20Abi,
functionName: "approve",
args: [spender, amount]
})
};
triggerCall = approveCall;
}
return triggerCall;
};
/**
* Prepares the executable payload required for sending an on-chain quote transaction.
* This function generates the appropriate call data for the trigger (either native or ERC20),
* appends the quote hash to the call data, and returns the payload ready for execution.
* The returned object contains the executable payload and optional metadata (currently empty, but can be extended).
*
* @param quoteParams - The on-chain quote parameters, including the quote and trigger
* @param spender - The address that will be used as the spender (for token approvals)
* @param recipient - The address that will receive the funds (for native transfers)
* @returns An object containing the executable payload and metadata
*
* @example
* ```typescript
* const { executablePayload, metadata } = await prepareExecutableOnChainQuotePayload(
* fusionQuote,
* spenderAddress,
* recipientAddress
* );
* // executablePayload: { to, data, value } (ready for sendTransaction)
* // metadata: {}
* ```
*/
export const prepareExecutableOnChainQuotePayload = async (quoteParams, spender, recipient, version) => {
const { quote, trigger } = quoteParams;
const triggerCall = await generateTriggerCallFromTrigger({
trigger,
spender,
recipient,
version
});
// This will always be a non-composable transaction, so composability is not a concern here.
const dataOrPrefix = triggerCall?.data ?? FUSION_NATIVE_TRANSFER_PREFIX;
const call = { ...triggerCall, data: concatHex([dataOrPrefix, quote.hash]) };
return {
executablePayload: call,
metadata: {}
};
};
/**
* Formats the signed on-chain quote payload by attaching the transaction hash and chainId,
* and encoding them as required by the MEE service for on-chain quotes.
* Metadata is currently unused but reserved for future extensibility.
*
* @param quoteParams - The original on-chain quote parameters
* @param _metadata - Optional metadata (currently unused)
* @param hash - The transaction hash to attach to the quote
* @returns The signed on-chain quote payload with the signature field
*
* @example
* ```typescript
* const signedOnChainQuote = formatSignedOnChainQuotePayload(
* fusionQuote,
* {},
* txHash
* );
* // signedOnChainQuote: { ...quote, signature: '0x177eee01<encodedHashAndChainId>' }
* ```
*/
export const formatSignedOnChainQuotePayload = (quoteParams, _metadata, // This is unused for now. But can be extended in future
hash) => {
const { quote, trigger } = quoteParams;
const signature = concatHex([
ON_CHAIN_PREFIX,
encodeAbiParameters([{ type: "bytes32" }, { type: "uint256" }], [hash, BigInt(trigger.chainId)])
]);
return {
...quote,
signature
};
};
/**
* Signs a fusion quote with a tx send client side.
*
* @param client - The Mee client to use
* @param params - The parameters for the fusion quote
* @returns The signed quote
* @example
* const signedQuote = await signOnChainQuote(meeClient, {
* quote: quotePayload,
* account: smartAccount
* })
*/
export const signOnChainQuote = async (client, params) => {
const { confirmations = 2, companionAccount: account_ = client.account, fusionQuote: { trigger } } = params;
const { walletClient, address: spender, version } = account_.deploymentOn(trigger.chainId, true);
// By default the trigger amount will be deposited to sca account.
// if a custom recipient is defined ? It will deposit to the recipient address
const recipient = trigger.recipientAddress || spender;
const { executablePayload, metadata } = await prepareExecutableOnChainQuotePayload(params.fusionQuote, spender, // In terms of token approval. Spender will be used for approving for SCA
recipient, // In terms of native token deposit, this recipient will be used for target deposit address
version);
// @ts-ignore
const hash = await walletClient.sendTransaction(executablePayload);
// @ts-ignore
await walletClient.waitForTransactionReceipt({ hash, confirmations });
return formatSignedOnChainQuotePayload(params.fusionQuote, metadata, hash);
};
export default signOnChainQuote;
//# sourceMappingURL=signOnChainQuote.js.map