Skip to main content

Agent permissions

A smart account can hand someone else — an AI agent, a bot, your backend — a limited permission. The limits are enforced by the chain, and the user can revoke them at any time. There are two kinds:

Payment delegation (ERC-7710)Session
ForPaying x402 servicesSending your own transactions to specific contracts
The agent's key holdsNothing — payments come from the user's accountNothing — operations run on the user's account
LimitsTotal USDC.e (or per period), expiry, number of payments; each payment exact amount + recipientAllowed contracts and functions, token spend, number of operations, time window, native value
Who submitsThe HPP facilitator settles each paymentThe agent sends UserOperations through the bundler
GasNone for the agent — the facilitator pays settlement gasUnder the user's sponsorship policy, or paid in USDC.e
In HPP Smart WalletPay x402 servicesCall contracts (session)

A session cannot sign an x402 payment: x402's signature is checked through the account's ERC-1271 validation and never passes the session's rules. For x402, use a payment delegation.

Payment delegations (ERC-7710)​

The account signs a delegation to the agent's address with caps attached (MetaMask Delegation Framework enforcers, deployed on HPP). For each x402 payment the agent signs a narrower re-delegation — this amount, this seller, short expiry — and the HPP facilitator redeems it through the user's account.

User side — one operation the first time (installs the delegation executor), then signatures only:

import { parseUnits } from "viem";

const grant = await user.grantPaymentDelegation({
agent: agentAddress,
token: USDCe,
limit: parseUnits("5", 6), // or period: { amount, seconds } for a rolling cap
validUntil: BigInt(Math.floor(Date.now() / 1000) + 30 * 86400),
maxCalls: 100n,
});
// give the agent grant.permissionContext; keep grant.delegation to revoke later
await user.revokePaymentDelegation(grant.delegation); // one operation; effective once confirmed

Agent side — for a 402 accept whose extra.assetTransferMethod is "erc7710". The seller copies the HPP facilitator's signer addresses into extra.facilitatorAddresses (they come from the facilitator's /supported response):

import { buildPaymentRedelegation, signPaymentRedelegation } from "@hpp-io/aa-sdk";
import type { Address } from "viem";

const leaf = buildPaymentRedelegation(chain.id, agentKey.address, {
parentPermissionContext: grant.permissionContext,
facilitatorAddresses: requirements.extra.facilitatorAddresses as Address[],
token, amount, payTo, // from the 402 accept: asset, amount, payTo
});
const payload = await signPaymentRedelegation({ chainId: chain.id, agent: agentKey, leaf, parentPermissionContext: grant.permissionContext });
// → x402 payload { delegationManager, permissionContext, delegator } for the exact scheme

Put that payload in a standard x402 PaymentPayload for the accept you chose and retry the request with the payment header, as in the x402 buyer quickstart. Each re-delegation is valid for 10 minutes by default.

The easiest agent side is the MCP bridge: set HPP_PAYMENT_DELEGATION to the permission context and it handles every erc7710 payment — see Pay from an agent.

Payment delegations work with sellers settled by the HPP facilitator — its /supported response carries an erc7710 block (DelegationManager and enforcer addresses) under the exact scheme. @metamask/x402 with @metamask/smart-accounts-kit produces the same bytes if you register HPP's contracts with erc7710Environment(chain.id).

Security model of payment delegations​

Who can spendOnly the agent's key. Every payment is a re-delegation the agent signs; the facilitator cannot pay with the user's permission on its own
What enforces the limitsCaveat contracts checked by the DelegationManager on every redemption — total (or per-period) USDC.e, expiry, number of payments. Each payment is further narrowed to one exact amount, one recipient, the HPP facilitator as the only redeemer, and a short expiry
Restrictions only narrowA re-delegation can add restrictions to the user's permission but never remove or widen them
What the agent cannot doMove tokens other than the one granted, exceed the limit, act after expiry, or change the account
If HPP_PAYMENT_DELEGATION leaksNot enough on its own — spending also needs the agent's private key
If the agent's key leaksWhoever has it, together with the delegation, can spend up to the remaining limit before it expires. Revoke at once
RevokingOne operation disables the permission on-chain; payments fail once it is confirmed (usually seconds). Under HPP Smart Wallet's policy its fee is normally covered, with a daily cap on sponsored revokes

Keep limits small and expiries short, and give each agent its own key so one revoke never affects another.

Sessions​

A session is a key with rules, installed on the user's account. The agent signs with its own key; the account checks the rules on-chain before anything runs. Over the limit, wrong contract, expired, or revoked → the operation is rejected at validation and no gas is spent.

warning

A session without actions limits — or with a sudo policy — lets the key do anything the account can. Always set the contract, function, amount, and time window.

User side:

const { permissionId } = await user.grantSession({
signer: agentAddress,
actions: [
{ target: USDCe, signature: "transfer(address,uint256)" },
{ target: MARKET, selector: "0x12345678" }, // a function selector or a human-readable signature
],
spend: [{ token: USDCe, limit: 5_000_000n }], // cumulative cap per token
usageLimit: 20n, // max operations
validUntil: Math.floor(Date.now() / 1000) + 30 * 86400,
valueLimit: 0n, // max native value per operation
});
// revoke: await user.revokeSession(permissionId)
FieldWhat it limits
actions[]Which contract + function the agent may call. Anything else is rejected
spendTotal of a token the agent may move through those actions, over the session's life
usageLimitNumber of operations
validUntil / validAfterTime window (Unix seconds)
valueLimitNative ETH per operation

Anything you leave out is unlimited — for an agent, set the contract, the function, the amount, and the time window.

Agent side:

import { createHppSessionClient } from "@hpp-io/aa-sdk";

const agentClient = await createHppSessionClient({
account: userAddress, // the user's smart account
permissionId,
signer: agentKey, // viem LocalAccount that supports sign({ hash })
chain, bundlerUrl,
paymaster, // optional — same option as createHppAccount
});
const r = await agentClient.sendCalls([{ to: USDCe, data: transferCalldata }]);

If the agent's operations should be charged in USDC.e under the user's policy, also allow the fee approval in the session with feeApproveAction — it can approve only the paymaster, capped per operation and in total, and does not count against spend:

import { feeApproveAction } from "@hpp-io/aa-sdk";

// the USDC.e paymaster for your network — see Reference → Contracts, or read it from a token-mode quote:
const ERC20_PAYMASTER = "0xa56ee8524BCf81397bA29f069Bfc695a3E703B97"; // HPP Mainnet

actions: [
{ target: USDCe, signature: "transfer(address,uint256)" },
feeApproveAction({ token: USDCe, paymaster: ERC20_PAYMASTER, perCall: 300_000n, total: 5_000_000n }),
],

user.sessionUsage(permissionId, { feeToken: USDCe }) reads how much a session has used from its on-chain policies.

Throughput: one account, one operation at a time​

Operations from the same account are sequential: the next one is accepted only after the previous one is mined. To do several things quickly, batch them: sendCalls([c1, …, c10]) runs them in one operation — faster and cheaper than ten separate ones. (The bundler accepts up to 4 operations per account in flight using different nonce keys, but sendCalls does not expose that; it needs the low-level bundler client.)

A service acting for many users cannot get around this by batching — use one account per user.