@abstract-foundation/agw-client
Version:
Abstract Global Wallet Client SDK
150 lines • 6.58 kB
JavaScript
import { BaseError, toFunctionSelector, } from "viem";
import { abstract } from "viem/chains";
import { decodeAbiParameters, decodeFunctionData } from "viem/utils";
import { SessionKeyPolicyRegistryAbi } from "./abis/SessionKeyPolicyRegistry.js";
import { SessionKeyValidatorAbi } from "./abis/SessionKeyValidator.js";
import { ADD_MODULE_SELECTOR, BATCH_CALL_SELECTOR, CREATE_SESSION_SELECTOR, SESSION_KEY_POLICY_REGISTRY_ADDRESS, SESSION_KEY_VALIDATOR_ADDRESS, } from "./constants.js";
import { AGWAccountAbi } from "./exports/constants.js";
import { ConstraintCondition, getSessionSpec } from "./sessions.js";
const restrictedSelectors = new Set([
toFunctionSelector("function setApprovalForAll(address, bool)"),
toFunctionSelector("function approve(address, uint256)"),
toFunctionSelector("function transfer(address, uint256)"),
]);
export var SessionKeyPolicyStatus;
(function (SessionKeyPolicyStatus) {
SessionKeyPolicyStatus[SessionKeyPolicyStatus["Unset"] = 0] = "Unset";
SessionKeyPolicyStatus[SessionKeyPolicyStatus["Allowed"] = 1] = "Allowed";
SessionKeyPolicyStatus[SessionKeyPolicyStatus["Denied"] = 2] = "Denied";
})(SessionKeyPolicyStatus || (SessionKeyPolicyStatus = {}));
export async function assertSessionKeyPolicies(client, chainId, account, transaction) {
// Only validate on Abstract mainnet
if (chainId !== abstract.id) {
return;
}
const sessions = [];
if (transaction.to === account.address &&
transaction.data?.substring(0, 10) === BATCH_CALL_SELECTOR) {
const batchCall = decodeFunctionData({
abi: AGWAccountAbi,
data: transaction.data,
});
if (batchCall.functionName === "batchCall") {
for (const call of batchCall.args[0]) {
const subTransaction = {
...transaction,
to: call.target,
data: call.callData,
};
const session = getSessionFromTransaction(account, subTransaction);
if (session) {
sessions.push(session);
}
}
}
}
else {
const session = getSessionFromTransaction(account, transaction);
if (session) {
sessions.push(session);
}
}
if (sessions.length === 0) {
// no session can be parsed from the transaction
return;
}
for (const session of sessions) {
const callPolicies = session.callPolicies;
const transferPolicies = session.transferPolicies;
const checks = [];
for (const callPolicy of callPolicies) {
if (restrictedSelectors.has(callPolicy.selector)) {
const destinationConstraints = callPolicy.constraints.filter((c) => c.index === 0n && c.condition === ConstraintCondition.Equal);
if (destinationConstraints.length === 0) {
throw new BaseError(`Unconstrained token approval/transfer destination in call policy. Selector: ${callPolicy.selector}; Target: ${callPolicy.target}`);
}
for (const constraint of destinationConstraints) {
const [target] = decodeAbiParameters([
{
type: "address",
},
], constraint.refValue);
checks.push({
target,
check: {
address: SESSION_KEY_POLICY_REGISTRY_ADDRESS,
abi: SessionKeyPolicyRegistryAbi,
functionName: "getApprovalTargetStatus",
args: [
callPolicy.target, // token address
target, // allowed spender
],
},
});
}
}
else {
checks.push({
target: callPolicy.target,
check: {
address: SESSION_KEY_POLICY_REGISTRY_ADDRESS,
abi: SessionKeyPolicyRegistryAbi,
functionName: "getCallPolicyStatus",
args: [callPolicy.target, callPolicy.selector],
},
});
}
}
for (const transferPolicy of transferPolicies) {
checks.push({
target: transferPolicy.target,
check: {
address: SESSION_KEY_POLICY_REGISTRY_ADDRESS,
abi: SessionKeyPolicyRegistryAbi,
functionName: "getTransferPolicyStatus",
args: [transferPolicy.target],
},
});
}
const results = await client.multicall({
contracts: checks.map((c) => c.check),
allowFailure: false,
});
for (let i = 0; i < checks.length; i++) {
const result = results[i];
const check = checks[i];
if (Number(result) !== SessionKeyPolicyStatus.Allowed) {
throw new BaseError(`Session key policy violation. Target: ${check?.target}; Status: ${SessionKeyPolicyStatus[Number(result)]}`);
}
}
}
}
function getSessionFromTransaction(account, transaction) {
if (transaction.to === SESSION_KEY_VALIDATOR_ADDRESS &&
transaction.data?.substring(0, 10) === CREATE_SESSION_SELECTOR) {
const sessionSpec = decodeFunctionData({
abi: SessionKeyValidatorAbi,
data: transaction.data,
});
if (sessionSpec.functionName === "createSession") {
return sessionSpec.args[0];
}
}
if (transaction.to === account?.address &&
transaction.data?.substring(0, 10) === ADD_MODULE_SELECTOR) {
const moduleAndData = decodeFunctionData({
abi: AGWAccountAbi,
data: transaction.data,
});
if (moduleAndData.functionName === "addModule" &&
moduleAndData.args[0]
.toLowerCase()
.startsWith(SESSION_KEY_VALIDATOR_ADDRESS.toLowerCase())) {
// Remove '0x' prefix (2 chars) + validator address 20 bytes (40 chars)
const sessionData = moduleAndData.args[0].substring(42);
return decodeAbiParameters([getSessionSpec()], `0x${sessionData}`)[0];
}
}
return undefined;
}
//# sourceMappingURL=sessionValidator.js.map