EIP-7702 accounts must be delegated onchain before creating a session. If the account has already sent calls, it will already be delegated. If it hasn't sent any calls before, delegate it by sending an empty call as the owner. See Send Transactions for the complete flow.
# Prepare and send a delegation transaction
PREPARE_RESPONSE=$(curl -s --request POST \
--url https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"id": 1,
"jsonrpc": "2.0",
"method": "wallet_prepareCalls",
"params": [
{
"calls": [{"to": "0x0000000000000000000000000000000000000000", "data": "0x", "value": "0x0"}],
"from": "'$SIGNER_ADDRESS'",
"chainId": "'$CHAIN_ID'",
"capabilities": {
"paymasterService": {"policyId": "'$POLICY_ID'"}
}
}
]
}')
# Sign and send (see Send Transactions for complete signing flow)To create a session key:
- Get the public address of a key you want to use as a session key. This can be any key pair that has the ability to sign (aka a signer that is either a local signer like an EOA or signer generated with a signer provider).
- Create a session for that key, by passing it as the
publicKeyin a call towallet_createSession. (Note that this must be the public key address, not the full public key.)
Use your signer address directly as the account field to enable EIP-7702 by default.
Note that the expiry is in seconds and represents a UNIX timestamp (e.g. 1776657600 for April 20th, 2077).
curl --request POST \
--url https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"jsonrpc": "2.0",
"id": 1,
"method": "wallet_createSession",
"params": [
{
"account": "'$SIGNER_ADDRESS'",
"chainId": "'$CHAIN_ID'",
"expirySec": '$EXPIRY_TIMESTAMP',
"key": {
"publicKey": "'$SESSION_KEY_ADDRESS'",
"type": "secp256k1"
},
"permissions": [
{
"type": "root"
}
]
}
]
}'This will return two key elements:
- The session ID
- The signature request that must be signed by the account owner to authorize the session key
Keep note of the session ID, you'll need it later!
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"sessionId": "0xSESSION_ID",
"signatureRequest": {
"type": "eth_signTypedData_v4",
"data": {...},
"rawPayload": "0xRAW_PAYLOAD_TO_SIGN"
}
}
}With the session ID received in step 2 and the signature from step 3, we're now ready to prepare some calls!
curl --request POST \
--url https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"id": 1,
"jsonrpc": "2.0",
"method": "wallet_prepareCalls",
"params": [
{
"capabilities": {
"paymasterService": {
"policyId": "'$POLICY_ID'"
},
"permissions": {
"sessionId": "'$SESSION_ID'",
"signature": "'$SESSION_SIGNATURE'"
}
},
"calls": [
{
"to": "0x0000000000000000000000000000000000000000"
}
],
"from": "'$SIGNER_ADDRESS'",
"chainId": "'$CHAIN_ID'"
}
]
}
'This will return the userop request (the data field) and a signature request, for example:
{
"type": "user-operation-v070",
"data": {...useropRequest},
"chainId": "0xCHAIN_ID",
"signatureRequest": {
"type": "personal_sign",
"data": {
"raw": "0x_HASH_TO_SIGN"
},
"rawPayload": "0xRAW_PAYLOAD_TO_SIGN"
}
}With the returned signature request, sign the userop hash using the session key (not the owner). This signature will be valid as long as it is within the permissions the session key has.
Note that the type field in the signatureRequest indicates the signature type needed. In this case, we need to personal_sign the hash.
# Sign with the SESSION key (not owner!)
USEROP_SIGNATURE=$(cast wallet sign --private-key "$SESSION_PRIVATE_KEY" "$RAW_HASH")With the signature from step 5 and the useropRequest from step 4, you're good to go to send the call!
curl --request POST \
--url https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"id": 1,
"jsonrpc": "2.0",
"method": "wallet_sendPreparedCalls",
"params": [
{
"type": "user-operation-v070",
"data": {...useropRequest},
"chainId": "'$CHAIN_ID'",
"capabilities": {
"permissions": {
"sessionId": "'$SESSION_ID'",
"signature": "'$SESSION_SIGNATURE'"
}
},
"signature": {
"type": "secp256k1",
"data": "'$USEROP_SIGNATURE'"
}
}
]
}
'This will return the call ID!