EIP-7702 Wallet API Quickstart (API)

Learn how to send user ops using EIP-7702 with any RPC client

We’ll demonstrate how to use the wallet_prepareCalls, wallet_sendPreparedCalls, and wallet_getCallsStatus endpoints.

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 // If using Alchemy's signer service, this is the full raw payload to sign.
14 // If using a library like Viem, you can instead construct the authorization
15 // using the address, nonce, and chainId.
16 "rawPayload": "0xRAW_PAYLOAD_TO_SIGN"
17 }
18 },
19 {
20 "type": "user-operation-v070",
21 "data": {...},
22 "chainId": "0xCHAIN_ID",
23 "signatureRequest": {
24 "type": "personal_sign",
25 "data": {
26 "raw": "0xHASH_TO_SIGN"
27 },
28 // If using Alchemy's signer service, this is the full raw payload to sign.
29 // If using a library like Viem, you can instead use `personal_sign` to sign the hash.
30 "rawPayload": "0xRAW_PAYLOAD_TO_SIGN"
31 }
32 }
33 ]
34}

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 // If using Alchemy's signer service, this is the full raw payload to sign.
11 // If using a library like Viem, you can instead use `personal_sign` to sign the hash.
12 "rawPayload": "0xRAW_PAYLOAD_TO_SIGN"
13 }
14}

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:

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

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.