UNPKG

@biconomy/abstractjs

Version:

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

187 lines 7.56 kB
import { erc20Abi, getAbiItem, isAddress, toFunctionSelector } from "viem"; import { buildActionPolicy, calldataArgument } from "./buildActionPolicy.js"; export const resolveSessionActions = (sessionActions) => { return sessionActions.flat(); }; const resolvePoliciesOrApplyUnrestrictedPolicy = (contractAddress, policies) => { const actionPolicies = policies && policies.length > 0 ? policies.map((policy) => { // If it's a PolicyData object (already built), return as is if ("policy" in policy && "initData" in policy) { return policy; } if (policy.type === "spendingLimits") { const updatedPolicy = { ...policy, tokenLimits: policy.tokenLimits.map((tokenLimit) => ({ token: contractAddress, limit: tokenLimit.limit })) }; return buildActionPolicy(updatedPolicy); } // If it's a builder type, build into PolicyData return buildActionPolicy(policy); }) : [buildActionPolicy({ type: "sudo" })]; return actionPolicies; }; const preparePoliciesForERC20Actions = (params, recipientAddressPosition, amountPosition) => { // If there are dev defined policies, it will be processed and returned immediately if (params.policies && params.policies.length > 0) { return resolvePoliciesOrApplyUnrestrictedPolicy(params.contractAddress, params.policies); } let actionsPolicies = []; const { recipientAddress, usageLimit, amountLimitPerAction, maxAmountLimit, validAfter, validUntil } = params; const rules = []; // Restrict recipient if provided, using a universal policy by checking calldata offset 0 if (recipientAddress && isAddress(recipientAddress)) { rules.push({ condition: "equal", calldataOffset: calldataArgument(recipientAddressPosition), comparisonValue: recipientAddress }); } // Restrict by per-action amount using a universal policy by checking calldata offset 32 with cummulative tracking if (maxAmountLimit) { rules.push({ condition: "lessThanOrEqual", calldataOffset: calldataArgument(amountPosition), comparisonValue: amountLimitPerAction || maxAmountLimit, isLimited: true, usage: { limit: maxAmountLimit, used: 0n } }); } else { if (amountLimitPerAction) { // Restrict by per-action amount using a universal policy by checking calldata offset 32 without cummulative tracking if (amountLimitPerAction) { rules.push({ condition: "lessThanOrEqual", calldataOffset: calldataArgument(amountPosition), comparisonValue: amountLimitPerAction }); } } } if (rules.length > 0) { const universalPolicy = buildActionPolicy({ type: "universal", rules }); actionsPolicies.push(universalPolicy); } // Restrict by per-action usage limit if (usageLimit) { actionsPolicies.push(buildActionPolicy({ type: "usageLimit", limit: usageLimit })); } // Restrict by per-action time range limit if (validAfter || validUntil) { // In unix timestamp (Seconds) const currentTime = Math.floor(Date.now() / 1000); const oneDayInSecs = 60 * 60 * 24; actionsPolicies.push(buildActionPolicy({ type: "timeframe", validAfter: validAfter || currentTime, validUntil: validUntil || (validAfter || currentTime) + oneDayInSecs })); } // If no policies are configured, sudo policy will be added by default which is a unrestricted policy if (actionsPolicies.length === 0) { actionsPolicies = [buildActionPolicy({ type: "sudo" })]; } return actionsPolicies; }; export const buildSessionAction = (parameters) => { const { type, data } = parameters; switch (type) { case "transfer": { const functionSignature = toFunctionSelector(getAbiItem({ abi: erc20Abi, name: "transfer" })); const actionPolicies = preparePoliciesForERC20Actions(data, 1, 2); return data.chainIds.map((chainId) => { return { actions: [ { actionTarget: data.contractAddress, actionTargetSelector: functionSignature, actionPolicies } ], chainId }; }); } case "transferFrom": { const functionSignature = toFunctionSelector(getAbiItem({ abi: erc20Abi, name: "transferFrom" })); const actionPolicies = preparePoliciesForERC20Actions(data, 2, 3); return data.chainIds.map((chainId) => { return { actions: [ { actionTarget: data.contractAddress, actionTargetSelector: functionSignature, actionPolicies } ], chainId }; }); } case "approve": { const functionSignature = toFunctionSelector(getAbiItem({ abi: erc20Abi, name: "approve" })); const actionPolicies = preparePoliciesForERC20Actions(data, 1, 2); return data.chainIds.map((chainId) => { return { actions: [ { actionTarget: data.contractAddress, actionTargetSelector: functionSignature, actionPolicies } ], chainId }; }); } case "custom": { const actionPolicies = resolvePoliciesOrApplyUnrestrictedPolicy(data.contractAddress, data.policies); return data.chainIds.map((chainId) => { return { actions: [ { actionTarget: data.contractAddress, actionTargetSelector: data.functionSignature, actionPolicies } ], chainId }; }); } case "batch": { if (data.actions.length < 2) { throw new Error("A Batch must contain at least 2 actions"); } if (data.actions.some(({ chainId }) => Number(chainId) !== Number(data.actions[0].chainId))) { throw new Error("All actions must be on the same chain"); } const batchedActions = data.actions.flatMap(({ actions }) => actions); return [ { actions: batchedActions, chainId: data.actions[0].chainId } ]; } /** * Defensive: Unrecognized type, throw an explicit error. */ default: { throw new Error(`Unknown build action type: ${type}`); } } }; //# sourceMappingURL=buildSessionAction.js.map