Skip to content

UserProxyV4 has two owner-only market-swap entry points. Both call the same settlement path after the input is in the proxy. executeAsAgent uses that settlement path too, but its authorization and spender rules are described on agent delegation.

execute(
address router,
address spender,
address tokenIn,
uint256 amountIn,
address tokenOut,
uint256 minOut,
bytes data
) external payable returns (uint256 amountOut)

execute is onlyOwner and non-reentrant. For an ERC-20 input, it pulls exactly amountIn from the owner into the proxy, approves the selected pull target, and calls router with data. For a native input, the owner supplies the input as msg.value.

The proxy does not maintain a router allowlist. The owner supplies router, spender, and the opaque route data for this direct path. The spender model determines which address receives the temporary input approval:

spender Pull target and approval behavior
address(0) The pull target is router; the proxy approves router for amountIn.
Non-zero address The pull target is the supplied spender; the proxy approves it for amountIn.
Canonical Permit2 The proxy approves Permit2 for the ERC-20, then gives Permit2 a router-specific allowance.

The approval is for the current input amount only. After settlement, the proxy revokes the pull approval. A direct call can choose a non-zero spender; the additional restriction for agent calls is described below.

executePermit2In(
IPermit2.PermitTransferFrom permit,
bytes permitSig,
address router,
address spender,
address tokenOut,
uint256 minOut,
bytes data
) external returns (uint256 amountOut)

This path is onlyOwner and non-reentrant. It passes a plain Permit2 PermitTransferFrom to the canonical Permit2 contract. It does not use an Intents Protocol Permit2 witness.

The input token is permit.permitted.token, and the input amount is permit.permitted.amount. Permit2 transfers that amount from the owner to the proxy. The proxy then uses the same router and spender model as execute, measures the route output, and settles to the owner.

When the selected pull target is Permit2, the proxy rejects an amount above type(uint160).max with Permit2AmountOverflow. It approves Permit2 for the ERC-20 and calls PERMIT2.approve(token, router, amount, uint48(block.timestamp + 1)). The Permit2 allowance expiry is one second; the router-level Permit2 allowance is cleared during settlement.

Native ETH is not available on executePermit2In: neither the permitted input token nor tokenOut may be the native sentinel.

After input acquisition and approval, _settleExecuted performs these checks and transfers:

  1. Revert with MinOutZero when minOut == 0, or with AmountInZero when amountIn == 0.
  2. Call router with data and the call value selected by the entry point.
  3. Measure the proxy’s tokenOut balance increase. A failed router call reverts with RouterCallFailed(returnData).
  4. Revert with SlippageExceeded(received, minOut) when the measured amount is below minOut.
  5. Transfer the output balance to owner.
  6. For ERC-20 output, measure the owner’s balance increase. If it is below minOut, revert with InsufficientOwnerDelivery(delivered, required).
  7. Revoke the pull approval and send leftover tokenIn to the owner.

The output recipient is always owner. The call has no recipient argument, and the router cannot redirect the settled output through calldata.

The native sentinel is:

0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE

For execute with that value as tokenIn, msg.value must equal amountIn, or the call reverts with EthValueMismatch(sent, expected). The pull target must be router; a different target reverts with NativeNotSupportedInOrder.

Sending ETH with an ERC-20 tokenIn reverts with UnexpectedEthValue(sent). Native input is not available on executePermit2In or in an agent order. Native output is handled by the shared owner-settlement path; strict native delivery reverts if the owner transfer fails.

tokenIn == tokenOut reverts with SameToken because the input and output balance measurements would otherwise describe the same asset.

The Intents Protocol signs a Permit2 witness under its own protocol flow. The User Proxy’s executePermit2In accepts a plain PermitTransferFrom, and its agent path signs an AgentOrder under the proxy’s own EIP-712 domain. See Intents signing only for comparison, not as a proxy integration recipe.