ERC Permissions
Draft primitive for simple delegated execution.
auth X to do Y.
Not broad custody. Not app-specific auth spaghetti. Just a narrow, legible primitive: let an operator call one function on one target, with optional EIP-712 signatures for cleaner one-off execution.
Example
“Auth bot to rebalance my LP wrapper.”
PermissionKey({
owner: alice,
operator: bot,
target: lpWrapper,
selector: IUniV3LPWrapper.rebalance.selector
})
// auth bot to rebalance Alice's wrapper positionowner
alice
operator
bot
ability
rebalance()
Principles
Simplicity first. No frills in the primitive.
The point is not to encode every policy on earth. The point is to make the core auth edge dead simple, easy to reason about, and easy for contracts to adopt.
One obvious permission model
Owner, operator, target, selector. That is the shape. If you can explain it in one sentence, wallets and developers can actually use it.
Complexity stays in the registry
Typed data verification, nonces, one-off execution permits, replay protection. Integrating contracts should mostly just use a modifier.
Protocol semantics stay local
The registry answers whether a caller is allowed. The wrapper or protocol still decides what rebalance, compound, or migrate actually means.
Simple DX
Three examples tell the whole story.
Grant a standing right. Protect a target with one modifier. Or use a one-off signed execution permit when you want tighter control.
Permanent auth when ongoing automation makes sense
registry.grant({
operator: bot,
target: address(lpWrapper),
selector: IUniV3LPWrapper.rebalance.selector
});Minimal contract-side integration
modifier onlyAuthorized(address owner) {
if (msg.sender != owner) {
registry.requireAuthorizedCall(owner, msg.sender, address(this), msg.sig);
}
_;
}Permit-style exact execution when you want less ambient authority
registry.executeWithPermit(
ExecutionPermit({
owner: alice,
operator: bot,
target: address(lpWrapper),
selector: IUniV3LPWrapper.compound.selector,
calldataHash: keccak256(data),
nonce: 7,
deadline: deadline
}),
signature,
data
);LP hypothetical
A hypothetical Uniswap LP wrapper is where this becomes obviously useful.
Imagine a wrapper that adds better range management, fee compounding, and automation around v3 or v4 positions. Today you often end up with awkward custody tradeoffs or app-specific auth. This primitive gives a cleaner middle ground.
Better features without handing over blanket control
The wrapper can expose higher-level features like rebalance and collect-and-compound. The operator only gets permission to call those methods — not arbitrary withdrawal powers.
contract UniV3LPWrapper is PermissionedTarget {
function rebalance(
address owner,
uint256 tokenId,
RebalanceParams calldata params
) external onlyAuthorized(owner) {
// unwrap old range
// mint new range
// keep custody model local to the wrapper
}
function collectAndCompound(
address owner,
uint256 tokenId
) external onlyAuthorized(owner) {
// collect fees
// add liquidity back in
}
}Same primitive, more expressive position management
Hook-aware or coordinator-driven products get even more value from narrow delegated calls. The wrapper owns the product semantics. The registry just keeps auth legible.
contract UniV4LPWrapper is PermissionedTarget {
function rebalancePosition(
address owner,
PoolKey calldata pool,
RebalanceParams calldata params
) external onlyAuthorized(owner) {
// wrapper logic stays local
// registry only answers: is this caller allowed?
}
}Feature upgrade
Auto-rebalancing, fee sweeps, compounding, safety exits. These become wrapper-level product features instead of custody-heavy strategy vault assumptions.
Clear trust surface
You are not trusting a bot with everything. You are trusting it with a small set of wrapper methods that are visible and revocable.
Cleaner wallet UX
A wallet prompt can say: allow bot to call rebalance() on LPWrapper. That is much better than asking users to bless an opaque blob of app-specific logic.