EIP-7702 Wallet API Quickstart

Learn how to send user ops using EIP-7702

This short guide will introduce how to prepare and send user operations using a 7702 account in a matter of minutes. We’ll demonstrate how to do it using the SDK client or by using platform-agnostic JSON-RPC APIs.

The logical flow is to prepare the calls you’re looking to send, sign them, and send them!

In API calls, it’s as simple as:

This guide assumes you have an account you can sign with, like an Alchemy Signer or a user’s EOA. You will also need an Alchemy API key, and a gas manager policy ID if you want to sponsor gas.

Don't have an API key?

Start using the Alchemy Wallets API today! Get started for free.

Using The TypeScript SDK

1. Install Prerequisites

You’re going to need the @account-kit/wallet-client and @account-kit/infra. We’ll also be using LocalAccountSigner from @aa-sdk/core as the signer for demonstration purposes.

$npm install @account-kit/wallet-client @account-kit/infra @aa-sdk/core

2. Create A Smart Wallet Client

Create a client for a given signer (e.g. a LocalAccountSigner imported from @aa-sdk/core or an Alchemy Signer).

1import { createSmartWalletClient } from "@account-kit/wallet-client";
2import { alchemy, arbitrumSepolia } from "@account-kit/infra";
3import { LocalAccountSigner } from "@aa-sdk/core";
4
5const signer = LocalAccountSigner.fromPrivateKey(PRIVATE_KEY); // we use a private key signer as an example here
6
7const transport = alchemy({
8 apiKey: ALCHEMY_API_KEY, // use your Alchemy app api key here!
9});
10
11const client = createSmartWalletClient({
12 transport,
13 chain: arbitrumSepolia, // use any chain imported from @account-kit/infra here!
14 mode: "remote",
15 signer,
16});

3. Send A Sponsored User Op

All you need to do is follow a few simple steps to start sending user ops with Wallet APIs!

1// Prepare the calls using the 7702 capability.
2const preparedCalls = await client.prepareCalls({
3 calls: [
4 // Calls here can include `to`, `data`, and `value` params.
5 { to: "0x0000000000000000000000000000000000000000", data: "0x" },
6 ],
7 from: "0xFROM_ADDRESS", // put the account address here
8 capabilities: {
9 eip7702Auth: true,
10 paymasterService: {
11 policyId: "your-gas-manager-policy-id", // put your gas manager policy ID here
12 },
13 },
14});
15
16// Sign the calls.
17const signedCalls = await client.signPreparedCalls(preparedCalls);
18
19// Send the userOp.
20const { preparedCallIds } = await client.sendPreparedCalls(signedCalls);
21
22// Check calls status.
23const status = await client.getCallsStatus(preparedCallIds[0]);

Using The JSON-RPC APIs Directly

1. Prepare Your Calls

You can simply prepare whatever calls you’d like to send, being sure to include the 7702 capability.

$curl --request POST \
> --url https://api.g.alchemy.com/v2/API_KEY \
> --header 'accept: application/json' \
> --header 'content-type: application/json' \
> --data '
>{
> "id": 1,
> "jsonrpc": "2.0",
> "method": "wallet_prepareCalls",
> "params": [
> {
> "calls": [
> // Put the call data here, including `to`, `data`, and optional `value`.
> {
> "to": "0x0000000000000000000000000000000000000000",
> "data": "0x",
> "value": "0x0"
> }
> ],
> "from": "0xACCOUNT_ADDRESS", // put the account address here
> "chainId": "0xCHAIN_ID", // put the chain ID here
> "capabilities": {
> "eip7702Auth": true,
> "paymasterService": {
> "policyId": "your-gas-manager-policy-id" // if sponsoring gas, put your gas manager policy ID here
> }
> }
> }
> ]
>}
>'

If the account isn’t already delegated to Modular Account V2 onchain, this will return an array of calls that must be signed:

1{
2 "type": "array",
3 "data": [
4 {
5 "type": "authorization",
6 "data": {
7 "address": "0xDELEGATION_ADDRESS",
8 "nonce": "0xNONCE"
9 },
10 "chainId": "0xCHAIN_ID",
11 "signatureRequest": {
12 "type": "eip7702Auth"
13 }
14 },
15 {
16 "type": "user-operation-v070",
17 "data": {...},
18 "chainId": "0xCHAIN_ID",
19 "signatureRequest": {
20 "type": "personal_sign",
21 "data": {
22 "raw": "0xHASH_TO_SIGN"
23 }
24 }
25 }
26 ]
27}

For subsequent calls, only one call will be returned (unless the owner removed or changed the delegation):

1{
2 "type": "user-operation-v070",
3 "data": {...},
4 "chainId": "0xCHAIN_ID",
5 "signatureRequest": {
6 "type": "personal_sign",
7 "data": {
8 "raw": "0xHASH_TO_SIGN"
9 }
10 }
11}

2. Sign The Call(s)

Sign the prepared calls. How exactly you do this will differ depending on your language. Here is an example using Viem in TypeScript:

1// Assuming you have created a wallet client (see https://viem.sh/docs/clients/wallet).
2
3// Assuming `preparedCalls` is the result from `wallet_prepareCalls`.
4const userOpCall =
5 preparedCalls.type === "array"
6 ? preparedCalls.data.find((it) => it.type === "user-operation-v070")
7 : preparedCalls;
8
9const authorizationCall =
10 preparedCalls.type === "array"
11 ? preparedCalls.data.find((it) => it.type === "authorization")
12 : undefined;
13
14// Sign the user operation hash.
15const userOpSignature = await walletClient.signMessage({
16 message: userOpCall.signatureRequest.data,
17});
18
19// Sign the EIP-7702 authorization, if it was included in the previous step's result.
20const signedAuthorization = !authorizationCall
21 ? undefined
22 : await walletClient.signAuthorization({
23 contractAddress: authorizationCall.data.address,
24 nonce: hexToNumber(authorizationCall.data.nonce),
25 chainId: hexToNumber(authorizationCall.chainId),
26 });

3. Send The Prepared Calls

Now that you have the prepared calls & signatures, you’re ready to send the calls!

$curl --request POST \
> --url https://api.g.alchemy.com/v2/API_KEY \
> --header 'accept: application/json' \
> --header 'content-type: application/json' \
> --data '
>{
> "id": 1,
> "jsonrpc": "2.0",
> "method": "wallet_sendPreparedCalls",
> "params": [
> {
> "type": "array",
> "data": [
> {
> "type": "user-operation-v070",
> "data": {...}, // include the user op call data returned from `wallet_prepareCalls`
> "chainId": "0xCHAIN_ID", // put the chain id here
> "signature": {
> "type": "secp256k1",
> "data": "0xUSEROP_SIGNATURE"
> }
> },
> // The signed authorization only needs to be included if an authorization was returned from `wallet_prepareCalls`.
> {
> "type": "authorization",
> "data": {...}, // include the authorization call data returned from `wallet_prepareCalls`
> "chainId": "0xCHAIN_ID", // put the chain id here
> "signature": {
> "type": "secp256k1",
> "data": "0xAUTHORIZATION_SIGNATURE" // this can be serialized in hex format, or a raw signature (r,s,v or r,s,yParity)
> }
> }
> ]
> }
> ]
>}
>'

This will return the array of prepared call IDs.

4. Check The Calls Status

Now you can simply call wallet_getCallsStatus to check the status of the calls.

$curl --request POST \
> --url https://api.g.alchemy.com/v2/API_KEY \
> --header 'accept: application/json' \
> --header 'content-type: application/json' \
> --data '
>{
> "id": 1,
> "jsonrpc": "2.0",
> "method": "wallet_getCallsStatus",
> "params": [
> "0xPREPARED_CALL_ID"
> ]
>}
>'

See here for all of the possible results.