UNPKG

@biconomy/abstractjs

Version:

SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.

481 lines 23.3 kB
"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