@biconomy/abstractjs
Version:
SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.
481 lines • 23.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSessionQuote = exports.addPaymentPolicyForActions = exports.getCustomStateOverridesForIsModuleInstalled = exports.prepareEnableSessions = exports.prepareInstallSmartSessions = exports.getSessionValidatorInitData = void 0;
const viem_1 = require("viem");
const account_1 = require("../../../account/index.js");
const Utils_1 = require("../../../account/utils/Utils.js");
const constants_1 = require("../../../constants/index.js");
const modules_1 = require("../../../modules/index.js");
const installModule_1 = require("../erc7579/installModule.js");
const isModuleInstalled_1 = require("../erc7579/isModuleInstalled.js");
const supportsModule_1 = require("../erc7579/supportsModule.js");
const getFusionQuote_1 = require("./getFusionQuote.js");
const getQuote_1 = require("./getQuote.js");
const getSessionValidatorInitData = (deploymentVersion, redeemer) => {
const isStxValidator = (0, account_1.versionIsAtLeast)(deploymentVersion.version, constants_1.MEEVersion.V3_0_0);
return isStxValidator
? (0, viem_1.encodePacked)(["address", "uint8", "address"], [deploymentVersion.submodules?.EoaStatelessValidator, 0, redeemer])
: redeemer;
};
exports.getSessionValidatorInitData = getSessionValidatorInitData;
const prepareInstallSmartSessions = async (client, smartSessionValidatorAddress = constants_1.SMART_SESSIONS_ADDRESS) => {
const installInstructions = await Promise.all(client.account.deployments.map(async (deployment) => {
const chainId = deployment.client.chain?.id;
if (!chainId) {
throw new Error("Chain ID is not set");
}
const isModuleInstalled_ = (await deployment.isDeployed())
? await (0, isModuleInstalled_1.isModuleInstalled)(undefined, {
account: deployment,
module: {
address: smartSessionValidatorAddress,
initData: "0x",
type: "validator"
}
})
: false;
if (!isModuleInstalled_) {
const installModuleCalls = await (0, installModule_1.toInstallWithSafeSenderCalls)(deployment, {
address: smartSessionValidatorAddress,
initData: "0x",
type: "validator"
});
const installModuleInstructions = [];
for (const installModuleCall of installModuleCalls) {
const instruction = await client.account.buildComposable({
type: "rawCalldata",
data: {
to: installModuleCall.to,
calldata: installModuleCall.data,
value: installModuleCall.value,
chainId,
metadata: [
{
type: "CUSTOM",
description: "Install smart sessions module",
chainId
}
]
}
});
installModuleInstructions.push(...instruction);
}
return await client.account.buildComposable({
type: "batch",
data: {
instructions: installModuleInstructions
}
});
}
return undefined;
}));
return installInstructions.filter((inx) => inx !== undefined).flat();
};
exports.prepareInstallSmartSessions = prepareInstallSmartSessions;
const prepareEnableSessions = async (client, enableSession, smartSessionValidatorAddress = constants_1.SMART_SESSIONS_ADDRESS, feeToken) => {
const { redeemer, maxPaymentAmount: maxPaymentAmount_, actions: unresolvedSessionActions, batchActions = true } = enableSession;
const sessionActions = (0, account_1.resolveSessionActions)(unresolvedSessionActions);
if (!redeemer) {
throw new Error("Smart session redeemer address is missing");
}
if (!sessionActions || sessionActions.length === 0) {
throw new Error("Smart sessions actions are missing");
}
for (const { actions, chainId } of sessionActions) {
if (actions.length === 0) {
throw new Error(`Smart sessions actions are empty for the chain (${chainId})`);
}
}
let maxPaymentAmount = maxPaymentAmount_ ?? 0n;
if (feeToken && !maxPaymentAmount_) {
const { publicClient } = client.account.deploymentOn(feeToken.chainId, true);
const decimals = await publicClient.readContract({
address: feeToken.address,
abi: viem_1.erc20Abi,
functionName: "decimals"
});
maxPaymentAmount = (0, viem_1.parseUnits)("5", decimals);
}
const permitERC4337Paymaster = true;
const uniqueChainIds = Array.from(new Set(sessionActions.map((sessionAction) => sessionAction.chainId)));
const enableSessionsInstructionsWithSessionDetails = await Promise.all(uniqueChainIds.map(async (chainId) => {
const deployment = client.account.deployments.find((deployment) => deployment.client.chain?.id === chainId);
if (!deployment) {
throw new Error(`Multichain Nexus is not configured on chain ${chainId}`);
}
let sessionActionsForChain = sessionActions.filter((sessionAction) => sessionAction.chainId === chainId);
if (feeToken && feeToken.chainId === chainId) {
sessionActionsForChain = (0, exports.addPaymentPolicyForActions)(sessionActionsForChain, feeToken, maxPaymentAmount);
}
const defaultVersionConfig = (0, modules_1.getMEEVersion)(constants_1.DEFAULT_MEE_VERSION);
const deploymentVersion = deployment.version;
const validatorAddress = deploymentVersion.validatorAddress ||
defaultVersionConfig.validatorAddress;
const sessionValidatorInitData = (0, exports.getSessionValidatorInitData)(deploymentVersion, redeemer);
if (batchActions && sessionActionsForChain.length > 1) {
sessionActionsForChain = client.account.buildSessionAction({
type: "batch",
data: {
actions: sessionActionsForChain
}
});
}
const session = {
sessionValidator: validatorAddress,
sessionValidatorInitData,
salt: (0, modules_1.generateSalt)(),
userOpPolicies: permitERC4337Paymaster
? [(0, account_1.buildActionPolicy)({ type: "sudo" })]
: [],
erc7739Policies: { allowedERC7739Content: [], erc1271Policies: [] },
actions: sessionActionsForChain[0].actions,
permitERC4337Paymaster,
chainId: BigInt(chainId)
};
const sessionDetailsSignature = (0, constants_1.getOwnableValidatorMockSignature)({
threshold: 1
});
const permissionId = (0, constants_1.getPermissionId)({
session: session
});
let enableSessionInstructions = [];
if (!batchActions) {
const condition = (0, modules_1.createCondition)({
targetContract: deployment.address,
functionAbi: constants_1.NexusImplementationAbi,
functionName: "isModuleInstalled",
args: [
(0, supportsModule_1.parseModuleTypeId)("validator"),
(0, viem_1.getAddress)(smartSessionValidatorAddress),
"0x"
],
value: true,
type: modules_1.ConditionType.EQ,
description: "Smart sessions module must be installed"
});
for (const { actions } of sessionActionsForChain) {
const sessionGroup = {
...session,
actions: actions
};
const instructions = await client.account.buildComposable({
type: "default",
data: {
abi: constants_1.SmartSessionAbi,
functionName: "enableSessions",
args: [[sessionGroup]],
to: constants_1.SMART_SESSIONS_ADDRESS,
chainId,
conditions: [condition],
simulationOverrides: {
customOverrides: (0, exports.getCustomStateOverridesForIsModuleInstalled)(smartSessionValidatorAddress, deployment.address, chainId)
},
metadata: [
{
type: "CUSTOM",
description: "Enable smart sessions permissions",
chainId
}
]
}
});
enableSessionInstructions.push(...instructions);
}
}
else {
enableSessionInstructions = await client.account.buildComposable({
type: "default",
data: {
abi: constants_1.SmartSessionAbi,
functionName: "enableSessions",
args: [[session]],
to: constants_1.SMART_SESSIONS_ADDRESS,
chainId,
metadata: [
{
type: "CUSTOM",
description: "Enable smart sessions permissions",
chainId
}
]
}
});
}
return {
instructions: enableSessionInstructions,
sessionDetails: {
mode: constants_1.SmartSessionMode.USE,
permissionId,
signature: sessionDetailsSignature,
enableSessionData: {
enableSession: {
chainDigestIndex: 0,
hashesAndChainIds: [
{
chainId: session.chainId,
sessionDigest: "0x"
}
],
sessionToEnable: session,
permissionEnableSig: "0x"
},
validator: viem_1.zeroAddress,
accountType: "nexus"
}
}
};
}));
return enableSessionsInstructionsWithSessionDetails;
};
exports.prepareEnableSessions = prepareEnableSessions;
const getCustomStateOverridesForIsModuleInstalled = (validatorAddress, accountAddress, chainId) => {
const STORAGE_LOCATION = "0x0bb70095b32b9671358306b0339b4c06e7cbd8cb82505941fba30d1eb5b82f00";
const SENTINEL = "0x0000000000000000000000000000000000000001";
const getValidatorSlot = (validatorAddress) => {
const encoded = (0, viem_1.encodeAbiParameters)((0, viem_1.parseAbiParameters)("address, bytes32"), [validatorAddress, STORAGE_LOCATION]);
return (0, viem_1.keccak256)(encoded);
};
const customOverrides = [];
const customOverrideForValidator = {
contractAddress: accountAddress,
storageSlot: getValidatorSlot(validatorAddress),
chainId,
value: (0, Utils_1.toBytes32)(SENTINEL)
};
customOverrides.push(customOverrideForValidator);
return customOverrides;
};
exports.getCustomStateOverridesForIsModuleInstalled = getCustomStateOverridesForIsModuleInstalled;
const addPaymentPolicyForActions = (sessionActionsForChain, feeToken, maxPaymentAmount) => {
const transferSelector = (0, viem_1.toFunctionSelector)((0, viem_1.getAbiItem)({ abi: viem_1.erc20Abi, name: "transfer" }));
let updatedSessionActionsForChain = sessionActionsForChain.map((sessionAction) => {
const updatedActions = sessionAction.actions.map((action) => {
if (action.actionTargetSelector.toLowerCase() ===
transferSelector.toLowerCase() &&
action.actionTarget.toLowerCase() === feeToken.address.toLowerCase()) {
const updatedActionPolicies = action.actionPolicies.map((actionPolicy) => {
if (actionPolicy.policy.toLowerCase() ===
constants_1.UNIVERSAL_ACTION_POLICY_ADDRESS.toLowerCase()) {
const policyData = (0, viem_1.decodeAbiParameters)(account_1.UniversalPolicyAbi, actionPolicy.initData);
const universalPolicyData = policyData[0];
const conditions = [
(0, account_1.getUniversalActionPolicyConditionType)("equal"),
(0, account_1.getUniversalActionPolicyConditionType)("lessThan"),
(0, account_1.getUniversalActionPolicyConditionType)("lessThanOrEqual")
];
let isPaymentPolicyRuleAdded = false;
const updatedRules = universalPolicyData.paramRules.rules.map((rule) => {
if (!isPaymentPolicyRuleAdded &&
rule.offset === (0, account_1.calldataArgument)(2)) {
isPaymentPolicyRuleAdded = true;
}
if (rule.offset === (0, account_1.calldataArgument)(2) &&
conditions.includes(rule.condition)) {
let updatedRule = {
...rule,
ref: (0, Utils_1.toBytes32)(BigInt(rule.ref) + maxPaymentAmount)
};
if (updatedRule.isLimited !== undefined &&
updatedRule.usage !== undefined) {
updatedRule = {
...updatedRule,
usage: {
...updatedRule.usage,
limit: updatedRule.usage.limit + maxPaymentAmount
}
};
}
return updatedRule;
}
return rule;
});
if (isPaymentPolicyRuleAdded) {
return (0, constants_1.getUniversalActionPolicy)({
valueLimitPerUse: universalPolicyData.valueLimitPerUse,
paramRules: {
length: universalPolicyData.paramRules.length,
rules: updatedRules
}
});
}
if (universalPolicyData.paramRules.length >= 16) {
throw new Error("Failed to add payment policy for the supertransaction. There is policy conflicts within the defined universal action policies");
}
updatedRules[Number(universalPolicyData.paramRules.length)] = {
condition: (0, account_1.getUniversalActionPolicyConditionType)("lessThanOrEqual"),
offset: (0, account_1.calldataArgument)(2),
ref: (0, Utils_1.toBytes32)(maxPaymentAmount),
isLimited: false,
usage: { used: 0n, limit: 0n }
};
return (0, constants_1.getUniversalActionPolicy)({
valueLimitPerUse: universalPolicyData.valueLimitPerUse,
paramRules: {
length: universalPolicyData.paramRules.length + 1n,
rules: updatedRules
}
});
}
return actionPolicy;
});
return { ...action, actionPolicies: updatedActionPolicies };
}
return action;
});
return {
...sessionAction,
actions: updatedActions
};
});
const isPolicyForPaymentTokenExists = updatedSessionActionsForChain.some((sessionAction) => {
return sessionAction.actions.some((action) => {
const isFeeTokenTransferAction = action.actionTargetSelector.toLowerCase() ===
transferSelector.toLowerCase() &&
action.actionTarget.toLowerCase() === feeToken.address.toLowerCase();
if (isFeeTokenTransferAction) {
return action.actionPolicies.some((actionPolicy) => {
if (actionPolicy.policy.toLowerCase() ===
constants_1.UNIVERSAL_ACTION_POLICY_ADDRESS.toLowerCase()) {
return true;
}
return false;
});
}
return false;
});
});
if (!isPolicyForPaymentTokenExists) {
let isPaymentPolicyAdded = false;
const [paymentAction] = (0, account_1.buildSessionAction)({
type: "transfer",
data: {
chainIds: [feeToken.chainId],
contractAddress: feeToken.address,
amountLimitPerAction: maxPaymentAmount,
maxAmountLimit: maxPaymentAmount
}
});
updatedSessionActionsForChain = updatedSessionActionsForChain.map((sessionAction) => {
if (!isPaymentPolicyAdded &&
sessionAction.chainId === feeToken.chainId) {
isPaymentPolicyAdded = true;
return {
...sessionAction,
actions: [...sessionAction.actions, ...paymentAction.actions]
};
}
return sessionAction;
});
}
return updatedSessionActionsForChain;
};
exports.addPaymentPolicyForActions = addPaymentPolicyForActions;
const getSessionQuote = async (client, parameters) => {
const { mode, instructions = [], feeToken, trigger, batch = true } = parameters;
const meeVersions = client.account.deployments.map(({ version, chain }) => ({
chainId: chain.id,
version
}));
if (mode === "PREPARE") {
const { smartSessionValidatorAddress, enableSession } = parameters;
const batchActions = enableSession?.batchActions ?? true;
const sessionValidatorInstallInstructions = await (0, exports.prepareInstallSmartSessions)(client, smartSessionValidatorAddress);
const hasSessionValidatorInstallInstructions = sessionValidatorInstallInstructions.length > 0;
const enableSessionsInstructions = [];
const sessionDetailsArray = [];
if (enableSession) {
const enableSessionsInstructionsWithSessionDetails = await (0, exports.prepareEnableSessions)(client, enableSession, smartSessionValidatorAddress, feeToken);
for (const { instructions, sessionDetails } of enableSessionsInstructionsWithSessionDetails) {
sessionDetailsArray.push(sessionDetails);
enableSessionsInstructions.push(...instructions);
}
}
const hasEnableSessionsInstructions = enableSessionsInstructions.length > 0;
const hasInstructions = instructions.length > 0;
const hasFundingRequest = !!trigger;
if (!hasSessionValidatorInstallInstructions &&
!hasEnableSessionsInstructions &&
!hasInstructions &&
!hasFundingRequest) {
return undefined;
}
const resolvedInstructions = await (0, account_1.resolveInstructions)([
...sessionValidatorInstallInstructions,
...instructions
]);
let partiallyBatchedInstructions = [];
if (batch) {
partiallyBatchedInstructions = await (0, account_1.batchInstructions)({
accountAddress: client.account.signer.address,
meeVersions,
instructions: [...resolvedInstructions]
});
}
else {
partiallyBatchedInstructions = [...resolvedInstructions];
}
const finalInstructions = [
...partiallyBatchedInstructions,
...enableSessionsInstructions
];
const isUnbatchActionsRequired = !batchActions && hasEnableSessionsInstructions;
const { mode: _avoidOne, smartSessionValidatorAddress: _avoidTwo, enableSession: _avoidThree, ...rest } = parameters;
if (hasFundingRequest) {
const fusionQuote = await (0, getFusionQuote_1.default)(client, {
...rest,
trigger: rest.trigger,
feePayer: undefined,
batch: isUnbatchActionsRequired ? false : batch,
instructions: finalInstructions
});
return {
quoteType: fusionQuote.quote.quoteType,
quote: fusionQuote,
...(sessionDetailsArray.length > 0
? { sessionDetails: sessionDetailsArray }
: {})
};
}
const quote = await (0, getQuote_1.default)(client, {
...rest,
batch: isUnbatchActionsRequired ? false : batch,
instructions: finalInstructions
});
return {
quoteType: quote.quoteType,
quote,
...(sessionDetailsArray.length > 0
? { sessionDetails: sessionDetailsArray }
: {})
};
}
const { sessionDetails, mode: _avoidOne, ...rest } = parameters;
const isEnableAndUseSessionDetailExists = sessionDetails.some((sessionDetailsInfo) => sessionDetailsInfo.mode === constants_1.SmartSessionMode.UNSAFE_ENABLE);
if (isEnableAndUseSessionDetailExists) {
throw new Error("ENABLE_AND_USE mode is not supported, session details is invalid.");
}
const quote = await (0, getQuote_1.default)(client, {
...rest,
moduleAddress: constants_1.SMART_SESSIONS_ADDRESS,
shortEncodingSuperTxn: true,
smartSessionMode: "USE",
sessionDetails
});
const startIndex = quote.paymentInfo.sponsored ? 1 : 0;
for (const [index, userOp] of quote.userOps.entries()) {
if (index < startIndex)
continue;
const relevantIndex = sessionDetails.findIndex(({ enableSessionData }) => enableSessionData?.enableSession?.sessionToEnable?.chainId ===
BigInt(userOp.chainId));
if (relevantIndex === -1) {
throw new Error(`No session details found for chainId ${userOp.chainId}`);
}
userOp.sessionDetails = sessionDetails[relevantIndex];
}
return {
quoteType: quote.quoteType,
quote
};
};
exports.getSessionQuote = getSessionQuote;
//# sourceMappingURL=getSessionQuote.js.map