User Proxy Security
The User Proxy has one owner per clone and no factory administrator. Its security boundary is the owner, the owner-configured agent policy, the selected router, the selected pull target, the token contracts, canonical Permit2 where used, and EVM transaction atomicity.
This page describes UserProxyV4. It is not the threat model for IntentSettlerV1; that protocol
has different contracts, order signatures, recipients, chains, and balance rules. Compare with
the Intents trust model only when distinguishing the two systems.
Trust model
Section titled “Trust model”The factory and implementation have no owner, administrator, pause switch, allowlist, or upgrade
path. Each clone is initialized once for its user. Every operational entry point — swaps, agent
policy, sweeps — is onlyOwner, and swap entry points use the shared non-reentrant guard.
Two entry points are not owner-gated, and both are deliberate. executeAsAgent is callable by
anyone holding a valid agent signature; that is the point of delegation. init(address) is
external with no modifier. Three things make that safe, and it is worth being precise about which
one does the work:
- It is single-use — it reverts
AlreadyInitializedonceowneris non-zero. - The canonical address can only be produced by the factory. It is CREATE2 with the factory as
deployer and
keccak256(abi.encode("AgentSwap.UserProxy.v4", user))as the salt, and the factory callsinit(user)in the same transaction that deploys the clone. A user’s proxy is therefore never left deployed-but-uninitialised for someone else to claim; there is no window to race. - The shared implementation sets its own
ownerto0xdeadin its constructor, so it can never be initialised or driven directly.
Anyone can of course deploy their own clone of the implementation and initialise it to themselves,
but that clone lives at a different address and is nobody’s canonical proxy. Which is the practical
rule: resolve a user’s proxy through proxyOf or predictProxy, and never trust a proxy address
someone hands you.
The owner must still trust:
| Trust anchor | Boundary |
|---|---|
| Owner wallet | The owner can execute direct swaps, grant or revoke agents, change caps, and sweep the proxy. |
| Agent keys | An active agent can sign orders within the policy and its remaining input-token caps. |
| Router and token behavior | The route and ERC-20 contracts are external code selected by the owner or authorized agent. |
| Canonical Permit2 | Permit2 verifies and executes the plain transfer used by executePermit2In, and supplies the Permit2 allowance path. |
| EVM and chain | Atomic transaction execution and the deployed chain’s support for the implementation’s opcodes are assumed. |
The proxy has no external price reference. minOut is the value floor chosen by the direct caller
or signed by the agent. A transaction that receives less than the floor, or delivers less than the
floor to the owner, reverts atomically.
Structural controls
Section titled “Structural controls”amountIn == 0andminOut == 0are rejected by the shared settlement path.- For an ERC-20 input, the router receives the supplied route data only after the input amount is pulled and the temporary approval is set; native input is passed as call value.
- The proxy measures the output balance delta and requires it to meet
minOut. - Output is always settled to
owner; there is no recipient parameter. - Temporary input approvals are revoked after the route, and leftover input is returned to the owner.
- A shared reentrancy guard covers
execute,executePermit2In, andexecuteAsAgent. - Owner-only sweeps provide recovery for native ETH and ERC-20 balances held by the proxy.
These controls enforce the signed or directly supplied floor. They do not determine whether the floor represents fair market value.
The relayer boundary
Section titled “The relayer boundary”Anyone may relay a valid executeAsAgent call. The agent signature covers:
agent, router, tokenIn, amountIn, tokenOut, minOut, nonce, deadlineIt does not cover spender or routerData. A relayer can therefore choose the pull target and
route calldata at execution time. The relayer can choose a route down to the agent’s minOut; this
is real discretion in the design, not an authority-free relay.
The agent path limits that discretion in three ways:
spender == address(0)resolves to the signed router.- A non-zero pull target must be the signed router or canonical Permit2, or the call reverts with
SpenderNotAllowed. - The input amount is charged against the agent’s current per-token, per-epoch cap.
The relayer cannot change the signed router, tokens, input amount, minimum output, nonce, or deadline. It can censor or delay its own transaction. A successful route still settles its output to the owner, not to the relayer.
Agent and owner compromise
Section titled “Agent and owner compromise”A compromised agent key can authorize orders until the policy expires or is revoked, subject to
the action mask, allowed input tokens, non-zero caps, nonces, minOut, and the remaining epoch
budget. Revocation increments the generation and orphans the old generation’s permissions and
caps.
A compromised owner key has the owner’s authority: it can execute direct swaps, grant policies, change caps, revoke agents, and sweep balances. The proxy does not reduce the authority of the owner key.
Accepted residuals
Section titled “Accepted residuals”- A weak
minOutpermits economically poor routing down to that floor. - A relayer may choose poor or expensive route data, or may not submit an otherwise valid order.
- Non-standard token behavior may be unsupported or behave unexpectedly.
- A compromised agent may spend up to its remaining on-chain cap before a revocation transaction is effective.
- The owner must choose appropriate tokens, route data, and minimum outputs.
Clients should derive proxies from the canonical factory, pin the factory and implementation addresses, and simulate unfamiliar router calldata before submitting it. For deployment and ABI values, see the proxy reference. For deterministic address derivation, see the factory.