@biconomy/abstractjs
Version:
SDK for Biconomy integration with support for account abstraction, smart accounts, ERC-4337.
187 lines • 7.56 kB
JavaScript
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