Smart accounts for HPP. Let your users sign in with an email wallet, transact without holding ETH, and give an AI agent a spending limit — in a few lines of TypeScript.
The SDK talks to HPP's own ERC-4337 bundler and paymaster and takes care of HPP's chain-specific details,
so you work with plain sendCalls([...]).
📖 Full documentation: docs.hpp.io/smart-accounts
- Create a smart account for any user — their existing address (EIP-7702) or a new one.
- Send several calls as one transaction — approve + pay in a single signature.
- Let users skip ETH — HPP can cover the gas, or charge it in USDC.e.
- Pay x402 APIs from a smart account.
- Give an agent limited, revocable permissions — a USDC.e budget for x402 payments, or a session for specific contracts.
npm i @hpp-io/aa-sdk viemRequires Node.js 20+, ESM ("type": "module" or .mjs), and viem 2.56+. Works in Node and the browser.
| Chain ID | Bundler + paymaster URL | SDK chain | |
|---|---|---|---|
| HPP Mainnet | 190415 |
https://aa.hpp.io/rpc |
hppMainnet |
| HPP Sepolia (testnet) | 181228 |
https://aa-sepolia.hpp.io/rpc |
hppSepolia |
Start on Sepolia. Test ETH: HPP Sepolia Faucet.
Send USDC.e from a smart account on HPP Sepolia. The account here pays its own gas, so first send about 0.0001 test ETH to the key's address.
import { createHppAccount, hppSepolia, TOKENS } from "@hpp-io/aa-sdk";
import { privateKeyToAccount } from "viem/accounts";
import { encodeFunctionData, parseAbi } from "viem";
// 1. Create the account. With a private key, the key's own address becomes the smart account.
const user = await createHppAccount({
owner: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`),
chain: hppSepolia,
bundlerUrl: "https://aa-sepolia.hpp.io/rpc",
});
// 2. Make it ready — a one-time setup transaction.
await user.ensureReady();
// 3. Send calls. Everything in the array runs in one transaction.
const USDCe = TOKENS[181228].USDCe;
const erc20 = parseAbi(["function transfer(address to, uint256 amount) returns (bool)"]);
const r = await user.sendCalls([
{ to: USDCe, data: encodeFunctionData({ abi: erc20, functionName: "transfer", args: [user.address, 0n] }) },
]);
console.log(r.success, `https://sepolia-explorer.hpp.io/tx/${r.txHash}`);sendCalls does not throw if the transaction was included but reverted — check r.success.
Next: Quickstart in the docs — embedded wallets (Privy), browser wallets, and paying setup for your users.
Add the policy HPP issued to your app. The SDK picks the right paymaster and tells you what was charged.
const user = await createHppAccount({
owner, chain, bundlerUrl,
paymaster: { policyId: "<your policy>", anchorId: "<this user's anchor>" },
});
const r = await user.sendCalls(calls);
r.fee; // { mode: "sponsored", … } or { mode: "token", charged, symbol: "USDC.e", … }To get a policy, open a Gas sponsorship request. Keep anchor IDs private. → Gas sponsorship
Wrap the wallet's signing functions; the user keeps their address.
import { toEmbeddedOwner } from "@hpp-io/aa-sdk";
const owner = toEmbeddedOwner({
address,
signMessage: (hash) => provider.request({ method: "personal_sign", params: [hash, address] }),
signTypedData: (td) => provider.request({ method: "eth_signTypedData_v4", params: [address, JSON.stringify(td, (_, v) => (typeof v === "bigint" ? v.toString() : v))] }),
signAuthorization, // the wallet's EIP-7702 authorization signer
});Embedded wallets cannot send the setup transaction themselves, so pass a sponsor to ensureReady.
→ Quickstart: embedded wallet
Use the smart account as the x402 signer. Payments are signatures — no transaction, no gas.
import { x402Client, x402HTTPClient } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm/exact/client";
const client = new x402HTTPClient(new x402Client().register("eip155:181228", new ExactEvmScheme(user.account)));Sign through the account, not the raw owner key: tokens check signatures with the account's code. Full
example: examples/x402-exact.mjs.
const grant = await user.grantPaymentDelegation({
agent: agentAddress, token: USDCe, limit: 5_000_000n, // 5 USDC.e in total
validUntil: BigInt(Math.floor(Date.now() / 1000) + 30 * 86400), // 30 days
});
// give the agent grant.permissionContext; revoke any time:
await user.revokePaymentDelegation(grant.delegation);The agent pays x402 services from the user's account within the limit, enforced on-chain, and never holds
funds. Example: examples/x402-erc7710.mjs. For agents that call contracts
instead, use grantSession. → Agent permissions
import { paymasterReason } from "@hpp-io/aa-sdk";
try { await user.sendCalls(calls); }
catch (e) { console.log(paymasterReason(e)); } // e.g. "fee_balance_insufficient", or nullEvery error and how to fix it: Errors & troubleshooting.
If you talk to HPP's bundler without this SDK, you need to handle these yourself:
- Gas price — HPP's priority fee is 0; ask
rundler_getUserOperationGasPriceinstead of a generic estimator. - preVerificationGas — mostly L1 data cost on HPP; the SDK adds a 15% buffer.
- EIP-7702 setup — sent as its own type-4 transaction, never as a
0x7702factory marker in a UserOperation. - 7702 signatures — the owner signs the EIP-191-prefixed UserOperation hash.
- Session module install — Kernel needs the
executeselector in the install data; done in the first operation. - Session nonce key — type byte
0x01(a common helper emits0x00, which routes to the owner key). - Account signatures (ERC-1271) — wrapped in the account's Kernel domain so tokens and x402 accept them.
- USDC.e gas — the token paymaster needs a real
paymasterPostOpGasLimit; the SDK keeps the one the paymaster returns. - Parallel nonce lanes — a lane's first use costs more gas; the SDK estimates each operation separately.
Details: Reference → If you bypass the SDK.
- Questions, bugs, sponsorship requests: open an issue. Never post private keys, anchor IDs, or policy IDs.
- HPP Smart Wallet users: HPP Telegram.
npm install
npm run build
npm test # unit tests, no networkexamples/ holds the end-to-end scripts we run against HPP Sepolia; the comment at the top of each script
shows how to run it.
MIT