@biconomy/abstractjs
Version:
SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.
172 lines • 6.96 kB
JavaScript
import { encodeFunctionData } from "viem";
import { ComposabilityVersion, ForwarderAbi } from "../../../constants/index.js";
import { TokenWithPermitAbi } from "../../../constants/abi/TokenWithPermitAbi.js";
import { isComposableCallRequired, isRuntimeComposableValue } from "../../../modules/utils/composabilityCalls.js";
import { getFunctionContextFromAbi } from "../../../modules/utils/runtimeAbiEncoding.js";
import { isNativeToken } from "../../utils/index.js";
import buildComposable, { buildComposableCall } from "./buildComposable.js";
/**
* Builds an instruction for transferring tokens. This function creates the necessary
* instruction for a standard ERC20 transfer.
*
* @param baseParams - Base configuration for the instruction
* @param baseParams.account - The account that will execute the transfer
* @param baseParams.currentInstructions - Optional array of existing instructions to append to
* @param parameters - Parameters for the transfer
* @param parameters.chainId - Chain ID where the transfer will be executed
* @param parameters.tokenAddress - Address of the token to transfer
* @param parameters.amount - Amount to transfer
* @param [parameters.gasLimit] - Optional gas limit for the transfer
* @param [parameters.recipient] - Optional recipient address
*
* @returns Promise resolving to array of instructions
*
* @example
* ```typescript
* const instructions = await buildWithdrawal(
* { accountAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' },
* {
* chainId: 1,
* tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
* amount: 1000000n, // 1 USDC
* gasLimit: 65000n,
* recipient: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
* }
* );
* ```
*/
export const buildWithdrawal = async (baseParams, parameters, composabilityParams) => {
const { currentInstructions = [], accountAddress, meeVersions } = baseParams;
const { chainId, tokenAddress, amount, gasLimit, recipient = accountAddress, // EOA or owner account address
metadata: metadataOverride, lowerBoundTimestamp, upperBoundTimestamp, executionSimulationRetryDelay, simulationOverrides } = parameters;
const [meeVersionInfo] = meeVersions.filter((meeVersion) => meeVersion.chainId === chainId);
if (!meeVersionInfo) {
throw new Error("MEE version is required to build a native token transfer");
}
const meeVersion = meeVersionInfo.version;
const { forceComposableEncoding = false } = composabilityParams ?? {
forceComposableEncoding: false
};
const metadata = metadataOverride || [
{
type: "WITHDRAW",
tokenAddress: isRuntimeComposableValue(tokenAddress)
? "RUNTIME_VALUE"
: tokenAddress,
fromAddress: accountAddress,
toAddress: recipient,
amount: isRuntimeComposableValue(amount)
? "RUNTIME_VALUE"
: amount,
chainId
}
];
let withdrawalCall;
if (isNativeToken(tokenAddress)) {
// native token withdrawal
if (isRuntimeComposableValue(amount) || forceComposableEncoding) {
// composable call
if (!composabilityParams?.composabilityVersion) {
throw new Error("Composability version is required to build a call with the runtime injected param");
}
const { composabilityVersion } = composabilityParams;
if (composabilityVersion === ComposabilityVersion.V1_0_0) {
throw new Error("Runtime values for Native tokens are not supported for Composability v1.0.0");
}
// Uses buildComposable to build a native token transfer via eth forwarder
return buildComposable(baseParams, {
to: meeVersion.ethForwarderAddress,
abi: ForwarderAbi,
functionName: "forward",
value: amount,
gasLimit,
args: [recipient],
chainId,
metadata: metadataOverride || metadata,
lowerBoundTimestamp,
upperBoundTimestamp,
executionSimulationRetryDelay,
simulationOverrides
}, composabilityParams);
}
// not composable call
withdrawalCall = [
{
to: recipient,
value: amount,
...(gasLimit ? { gasLimit } : {})
}
];
}
else {
// ERC20 withdrawal
const abi = TokenWithPermitAbi;
const functionSig = "transfer";
const args = [
recipient,
amount
];
const functionContext = getFunctionContextFromAbi(functionSig, abi);
// Check for the runtime arguments and detect the need for composable call
const isComposableCall = forceComposableEncoding
? true
: isComposableCallRequired(functionContext, args);
// If the composable call is detected ? The call needs to composed with runtime encoding
if (isComposableCall) {
// composable call
if (!composabilityParams) {
throw new Error("Composability params are required to build a call with the runtime injected param");
}
const composableCallParams = {
to: tokenAddress,
functionName: functionSig,
args: args,
abi,
chainId,
...(gasLimit ? { gasLimit } : {})
};
withdrawalCall = await buildComposableCall(composableCallParams, composabilityParams);
return [
...currentInstructions,
{
calls: withdrawalCall,
chainId,
isComposable: true,
metadata,
lowerBoundTimestamp,
upperBoundTimestamp,
executionSimulationRetryDelay,
simulationOverrides
}
];
}
// not composable call
withdrawalCall = [
{
to: tokenAddress,
data: encodeFunctionData({
abi,
functionName: functionSig,
args: args
}),
...(gasLimit ? { gasLimit } : {})
}
];
}
// composable calls return early
// so if we reach this point, it means that the call is not composable
return [
...currentInstructions,
{
calls: withdrawalCall,
chainId,
metadata,
lowerBoundTimestamp,
upperBoundTimestamp,
executionSimulationRetryDelay,
simulationOverrides
}
];
};
export default buildWithdrawal;
//# sourceMappingURL=buildWithdrawal.js.map