// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
// Interfaces
import { IActionPolicy } from "@smartsessions/interfaces/IPolicy.sol";
import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
// Libraries
import { VALIDATION_SUCCESS, VALIDATION_FAILED } from "erc7579/interfaces/IERC7579Module.sol";
// Types
import { ConfigId } from "@smartsessions/DataTypes.sol";
// Contracts
import { Ownable } from "solady/auth/Ownable.sol";
struct TargetConfig {
address target;
bool allowed;
}
// forgefmt: disable-start
/// @title Intent Execution Policy
/// @author Rhinestone
/// @notice Action policy that restricts execution targets to a whitelisted set of addresses
/// managed by the contract owner. For ERC20 `approve` calls, validates that the spender
/// is a whitelisted address.
///
/// ┌─────────────────────────────────────────────────────────────────────────┐
/// │ Validation Logic │
/// │ │
/// │ checkAction(target, data) │
/// │ │ │
/// │ ├── target ∈ whitelistedTargets → ALLOW │
/// │ │ │
/// │ ├── selector == approve(address,uint256)? │
/// │ │ ├── YES → spender ∈ whitelistedTargets → ALLOW │
/// │ │ │ otherwise → DENY │
/// │ │ │ │
/// │ │ └── NO → DENY │
/// │ │ │
/// └─────────────────────────────────────────────────────────────────────────┘
// forgefmt: disable-end
contract IntentExecutionPolicy is IActionPolicy, Ownable {
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @dev ERC20 `approve(address,uint256)` function selector
bytes4 internal constant APPROVE_SELECTOR = IERC20.approve.selector;
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
/// @notice Set of whitelisted target addresses
mapping(address target => bool isWhitelisted) public whitelistedTargets;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @dev Emitted when a target address is added or removed from the whitelist
event TargetWhitelisted(address indexed target, bool allowed);
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @param _owner The owner address with permission to manage the whitelist
constructor(address _owner) {
_initializeOwner(_owner);
}
/*//////////////////////////////////////////////////////////////
ADMIN FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Add or remove multiple addresses from the target whitelist
/// @param entries Array of target/allowed pairs
function setWhitelistedTargets(TargetConfig[] calldata entries) external onlyOwner {
for (uint256 i; i < entries.length; i++) {
whitelistedTargets[entries[i].target] = entries[i].allowed;
emit TargetWhitelisted(entries[i].target, entries[i].allowed);
}
}
/*//////////////////////////////////////////////////////////////
INITIALIZATION
//////////////////////////////////////////////////////////////*/
/// @notice No per-session configuration needed; whitelist is managed globally by owner
function initializeWithMultiplexer(address, ConfigId, bytes calldata) external override { }
/*//////////////////////////////////////////////////////////////
ACTION VALIDATION
//////////////////////////////////////////////////////////////*/
/// @notice Validates an action against the whitelist policy
/// @dev If the target is whitelisted, allows immediately. If the call is an ERC20 `approve`,
/// validates the spender is a whitelisted address. Otherwise, denies.
/// @param target The target contract being called
/// @param data The calldata of the action
/// @return VALIDATION_SUCCESS if allowed, VALIDATION_FAILED otherwise
function checkAction(
ConfigId,
address,
address target,
uint256,
bytes calldata data
)
external
view
returns (uint256)
{
// If the target is whitelisted, allow immediately without further checks
if (whitelistedTargets[target]) {
return VALIDATION_SUCCESS;
}
// otherwise If selector is `approve(address,uint256)`, validate the spender
if (data.length >= 4 && bytes4(data[0:4]) == APPROVE_SELECTOR) {
if (data.length >= 36) {
address spender = address(bytes20(data[16:36]));
if (whitelistedTargets[spender]) {
return VALIDATION_SUCCESS;
}
}
return VALIDATION_FAILED;
}
return VALIDATION_FAILED;
}
/*//////////////////////////////////////////////////////////////
ERC165
//////////////////////////////////////////////////////////////*/
/// @notice ERC165 interface support
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return
interfaceId == type(IActionPolicy).interfaceId
|| interfaceId == type(IERC165).interfaceId;
}
}[
{
"type": "constructor",
"inputs": [
{
"name": "_owner",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"name": "AlreadyInitialized",
"type": "error",
"inputs": []
},
{
"name": "NewOwnerIsZeroAddress",
"type": "error",
"inputs": []
},
{
"name": "NoHandoverRequest",
"type": "error",
"inputs": []
},
{
"name": "PolicyNotInitialized",
"type": "error",
"inputs": [
{
"name": "id",
"type": "bytes32",
"internalType": "ConfigId"
},
{
"name": "multiplexer",
"type": "address",
"internalType": "address"
},
{
"name": "account",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "Unauthorized",
"type": "error",
"inputs": []
},
{
"name": "OwnershipHandoverCanceled",
"type": "event",
"inputs": [
{
"name": "pendingOwner",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "OwnershipHandoverRequested",
"type": "event",
"inputs": [
{
"name": "pendingOwner",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "OwnershipTransferred",
"type": "event",
"inputs": [
{
"name": "oldOwner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "newOwner",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "PolicySet",
"type": "event",
"inputs": [
{
"name": "id",
"type": "bytes32",
"indexed": false,
"internalType": "ConfigId"
},
{
"name": "multiplexer",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "account",
"type": "address",
"indexed": false,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "TargetWhitelisted",
"type": "event",
"inputs": [
{
"name": "target",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "allowed",
"type": "bool",
"indexed": false,
"internalType": "bool"
}
],
"anonymous": false
},
{
"name": "cancelOwnershipHandover",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "checkAction",
"type": "function",
"inputs": [
{
"name": "",
"type": "bytes32",
"internalType": "ConfigId"
},
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "target",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "data",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "completeOwnershipHandover",
"type": "function",
"inputs": [
{
"name": "pendingOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "initializeWithMultiplexer",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "bytes32",
"internalType": "ConfigId"
},
{
"name": "",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "owner",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "result",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "ownershipHandoverExpiresAt",
"type": "function",
"inputs": [
{
"name": "pendingOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "renounceOwnership",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "requestOwnershipHandover",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "setWhitelistedTargets",
"type": "function",
"inputs": [
{
"name": "entries",
"type": "tuple[]",
"components": [
{
"name": "target",
"type": "address",
"internalType": "address"
},
{
"name": "allowed",
"type": "bool",
"internalType": "bool"
}
],
"internalType": "struct TargetConfig[]"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "supportsInterface",
"type": "function",
"inputs": [
{
"name": "interfaceId",
"type": "bytes4",
"internalType": "bytes4"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "pure"
},
{
"name": "transferOwnership",
"type": "function",
"inputs": [
{
"name": "newOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "payable"
},
{
"name": "whitelistedTargets",
"type": "function",
"inputs": [
{
"name": "target",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "isWhitelisted",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
}
]0x6080806040526004361015610012575f80fd5b5f3560e01c90816301ffc9a7146104305750806305c00895146103d0578063256929621461038757806354d1f13d14610343578063715018a61461030d5780638da5cb5b146102e1578063989c9e46146102a8578063a18049231461019c578063f04e283e1461014f578063f2fde38b14610112578063fb9ed257146100d65763fee81cf4146100a0575f80fd5b346100d25760203660031901126100d2576100b9610499565b63389a75e1600c525f52602080600c2054604051908152f35b5f80fd5b346100d25760203660031901126100d2576001600160a01b036100f7610499565b165f525f602052602060ff60405f2054166040519015158152f35b60203660031901126100d257610126610499565b61012e6105bd565b8060601b1561014257610140906105d9565b005b637448fbae5f526004601cfd5b60203660031901126100d257610163610499565b61016b6105bd565b63389a75e1600c52805f526020600c20908154421161018f575f61014092556105d9565b636f5e88185f526004601cfd5b346100d25760203660031901126100d2576004356001600160401b0381116100d257366023820112156100d2576004810135906001600160401b0382116100d2576024810190602436918460061b0101116100d2576101f96105bd565b5f5b82811061020457005b8061021d60206102176001948787610578565b0161059c565b828060a01b03610236610231848888610578565b6105a9565b165f525f60205260405f209060ff80198354169115151617905561025e610231828686610578565b7ff6c76eeb7c8ff50ae11742b7c9c659668fd5450291aa2e196914e251b7786abe602061029081610217868a8a610578565b926040519315158452858060a01b031692a2016101fb565b346100d25760603660031901126100d2576102c1610499565b506044356001600160401b0381116100d2576101409036906004016104af565b346100d2575f3660031901126100d257638b78c6d819546040516001600160a01b039091168152602090f35b5f3660031901126100d2576103206105bd565b5f638b78c6d819545f5160206106045f395f51905f528280a35f638b78c6d81955005b5f3660031901126100d25763389a75e1600c52335f525f6020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c925f80a2005b5f3660031901126100d25763389a75e1600c52335f526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d5f80a2005b346100d25760a03660031901126100d2576103e9610483565b506044356001600160a01b03811681036100d2576084356001600160401b0381116100d2576020916104226104289236906004016104af565b916104dc565b604051908152f35b346100d25760203660031901126100d2576004359063ffffffff60e01b82168092036100d2576020916305c0089560e01b8114908115610472575b5015158152f35b6301ffc9a760e01b1490508361046b565b602435906001600160a01b03821682036100d257565b600435906001600160a01b03821682036100d257565b9181601f840112156100d2578235916001600160401b0383116100d257602083818601950101116100d257565b6001600160a01b03165f9081526020819052604090205490919060ff166105725760048110158061054f575b610513575050600190565b6024811015610524575b5050600190565b6024116100d2576010013560601c5f525f60205260ff60405f20541661054b575f8061051d565b5f90565b50806004116100d25781356001600160e01b03191663095ea7b360e01b14610508565b50505f90565b91908110156105885760061b0190565b634e487b7160e01b5f52603260045260245ffd5b3580151581036100d25790565b356001600160a01b03811681036100d25790565b638b78c6d8195433036105cc57565b6382b429005f526004601cfd5b60018060a01b031680638b78c6d819545f5160206106045f395f51905f525f80a3638b78c6d8195556fe8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0a2646970667358221220c4ad519b423de522c0dd1f392b8d7cdfa4acf51ae966816117d5943eda4a490f64736f6c634300081e0033
| Token | Symbol | Balance | Price | Value |
|---|---|---|---|---|
| no token holdings | ||||
| Txn Hash | Age | Event | Topics / Data |
|---|---|---|---|
| 0x36a2d8…a71f21 | 38 days agoFri, 10 Jul 2026 10:03:49 UTC | 0xf6c76e…6abe | [0] 0x000000000000…72c22734 data: 0x000000000000000000…00000001 |
| 0x36a2d8…a71f21 | 38 days agoFri, 10 Jul 2026 10:03:49 UTC | 0xf6c76e…6abe | [0] 0x000000000000…f8842a65 data: 0x000000000000000000…00000001 |
| 0x36a2d8…a71f21 | 38 days agoFri, 10 Jul 2026 10:03:49 UTC | 0xf6c76e…6abe | [0] 0x000000000000…a948c83b data: 0x000000000000000000…00000001 |
| 0x36a2d8…a71f21 | 38 days agoFri, 10 Jul 2026 10:03:49 UTC | 0xf6c76e…6abe | [0] 0x000000000000…bdc7a4b6 data: 0x000000000000000000…00000001 |
| 0xd0bd3f…f5a2cc | 38 days agoFri, 10 Jul 2026 10:03:49 UTC | 0x8be007…57e0 | [0] 0x000000000000…00000000 [1] 0x000000000000…bd00288f |
| Transaction Hash | Method | Block | Age | From | To | Amount | Token | ||
|---|---|---|---|---|---|---|---|---|---|
| no token transfers for this address yet | |||||||||
| Transaction Hash | Method | Block | Age | From | To | Type | Item | ||
|---|---|---|---|---|---|---|---|---|---|
| no NFT transfers for this address yet | |||||||||
| Type | Age | Block | Details |
|---|---|---|---|
| no cross-chain L1↔L2 transactions for this address | |||
| Transaction Hash | Method ? | Block | Age | From | To | Amount | Txn Fee ? | ||
|---|---|---|---|---|---|---|---|---|---|
| 0x36a2d8…a71f21 | setWhitelistedTargets | 5,992,965 | 38 days agoFri, 10 Jul 2026 10:03:49 UTC | 0x61e8…288f | IN | IntentExecutionPolicy | $0.000 ETH | 0.00000886 |
| Block | Age | Parent Transaction Hash | Type | Method | From | To | Value | |
|---|---|---|---|---|---|---|---|---|
| 5,992,961 | 38 days agoFri, 10 Jul 2026 10:03:49 UTC | 0xd0bd3f…f5a2cc | CREATE2 | optimized_routeFill921336808 | 0x914d…43d7 | IN | 0xa09b…4901 | 0 ETH |