@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