Skip to main content

Quickstart: Smart Accounts

Three steps: create an account, make it ready, send calls. @hpp-io/aa-sdk talks to HPP's bundler and paymaster and handles the chain-specific details for you.

npm i @hpp-io/aa-sdk viem

Requirements: Node.js 20+, ESM ("type": "module" or .mjs), viem 2.56+. The SDK runs in Node and in the browser.

Test funds (Sepolia): test ETH from the HPP Sepolia Faucet. For test USDC.e or sandbox sponsorship, see Gas sponsorship → Apply.

Pick the network once and reuse it:

import { hppSepolia, TOKENS } from "@hpp-io/aa-sdk";

const chain = hppSepolia;
const bundlerUrl = "https://aa-sepolia.hpp.io/rpc";
const USDCe = TOKENS[181228].USDCe;

1. Create an account​

The owner is whatever signs for the user. Pick the tab that matches your app.

import { createHppAccount } from "@hpp-io/aa-sdk";
import { privateKeyToAccount } from "viem/accounts";

const user = await createHppAccount({ owner: privateKeyToAccount(KEY), chain, bundlerUrl });
console.log(user.address, user.mode); // mode "7702": the key's own address becomes the smart account

2. Make it ready (once per account)​

await user.ensureReady({ sponsor });

This lands one transaction: the EIP-7702 delegation (same address) or the account deployment (new address). Who pays for it:

  • a funded viem WalletClient passed as sponsor (scripts, servers), or
  • a RemoteSponsor from the browser — two callbacks that hand the signed request to your backend, which sends the transaction and returns its hash, or
  • the owner itself, if you omit sponsor — only for a plain private-key owner holding a little ETH. Embedded-wallet owners cannot send transactions themselves, so they need a sponsor.
import type { RemoteSponsor } from "@hpp-io/aa-sdk";

// browser → your backend sends the setup transaction; the user never needs ETH
const sponsor: RemoteSponsor = {
submitDelegation: ({ account, authorization }) => post("/api/sponsor", { type: "delegate", account, authorization }),
submitDeploy: ({ account, initData, salt }) => post("/api/sponsor", { type: "deploy", account, initData, salt }),
}; // each callback resolves to the transaction hash

On the backend: for delegate, send a type-4 transaction with authorizationList: [authorization] (set gas explicitly, e.g. 3M — estimates on HPP can be low); for deploy, call KernelFactory.createAccount(initData, salt). Check that the authorization targets Kernel v3.3 on the right chain, and rate-limit the endpoint — it spends your ETH. When posting JSON, serialize bigint fields (such as v) as strings.

3. Send calls​

import { encodeFunctionData, parseAbi } from "viem";

const erc20 = parseAbi(["function transfer(address,uint256) returns (bool)"]);

const r = await user.sendCalls([
{ to: USDCe, data: encodeFunctionData({ abi: erc20, functionName: "transfer", args: [to, 1_000_000n] }) },
// add more calls — they run together in ONE UserOperation, or not at all
]);
if (!r.success) throw new Error(`reverted: ${r.txHash}`);

sendCalls returns { success, txHash, userOpHash, receipt, fee }. The first sendCalls on a new account also installs the session module, so it is slightly heavier than later ones.

  • It does not throw when the operation was included but reverted — check success.
  • It throws when the bundler or paymaster rejects the request before inclusion (for example, the account cannot pay its gas). The error carries a reason — see Reference.

Without sponsorship the account pays its own gas in ETH, so it needs a little ETH at its address.

Check it worked: open the transaction on the explorer — https://explorer.hpp.io/tx/${r.txHash} (Mainnet) or https://sepolia-explorer.hpp.io/tx/${r.txHash} (Sepolia). The transaction is the bundler's; your calls appear inside it, from your account's address.

4. Turn on gas sponsorship (optional)​

To let users transact with no ETH at all, add the paymaster option with the policy HPP issued to your app:

const user = await createHppAccount({
owner, chain, bundlerUrl,
paymaster: { policyId: "<your policy>", anchorId: "<this user>" },
});
const r = await user.sendCalls([...]);
console.log(r.fee); // { mode: "sponsored", freeRemaining: 2 } or { mode: "token", charged, symbol: "USDC.e", … }

On HPP Sepolia you can try this today with sandbox access — Gas sponsorship explains policies, anchors, paying gas in USDC.e, and how to request access.

What the SDK handles for you​

HPP is an Arbitrum Orbit chain and Kernel is a modular account. Hand-written ERC-4337 code hits a handful of traps here — a priority fee of 0, preVerificationGas that is mostly L1 data cost, EIP-7702 delegation details, validator install data, and nonce keys for sessions. The SDK takes care of all of them. If you use viem or permissionless directly, read Reference → If you bypass the SDK.

Next​