This proposal defines complementary JSON-RPC methods to EIP-5792's wallet_sendCalls
to enable an Application to sign over a call bundle (instead of the Wallet). Methods defined in this proposal are purposed for the Application to sign over calls
and submit them to the Wallet with an accompanying signature. This is in contrast to wallet_sendCalls
, where a Wallet itself signs over the call bundle.
Applications are increasingly seeking to use session keys and scoped permissions to make transactions on a user's behalf, without the user having to approve and sign each transaction in their wallet's interface. One example is an application that automates a user's decentralised exchange trading. As Smart Contract Accounts (SCAs) contain arbitrary execution interfaces that lead to varying calldata formats, it is not possible for an Application to know how to sign over an action for any arbitrary Account implementation, without maintaining a mapping of Account implementations to their signing logic. Apps need a simple way to request the payload that their session keys should sign, given a call or set of calls they wish to make, and a way to execute the calls once signed.
In this specification, we define two new JSON-RPC methods: wallet_prepareCalls
and wallet_sendPreparedCalls
.
wallet_prepareCalls
Instructs a Wallet to prepare a call bundle according to the Account's implementation. It returns a digest
of the call bundle to sign over, as well as the parameters required to fulfil a wallet_sendPreparedCalls
request (ie. capabilities
, chainId
, context
, and version
).
After calling
wallet_prepareCalls
, consumers are expected to sign over thedigest
and submit thesignature
and prepared calls to the Wallet viawallet_sendPreparedCalls
.
The request is identical to that of
wallet_sendCalls
.
type Request = {
method: 'wallet_prepareCalls',
params: [{
// Calls to be executed.
calls: {
to: `0x${string}`;
data?: `0x${string}`;
value?: `0x${string}`;
capabilities?: Record<string, any>;
}[];
// Capabilities.
capabilities?: Record<string, any>
// Chain ID of the chain the calls are being submitted to.
chainId: `0x${string}`;
// Sender address.
from?: `0x${string}`;
// Key (hint) that will be used to sign the call bundle.
key?: {
// Whether the digest will be prehashed by the key (default behavior of WebCrypto APIs).
prehash?: boolean,
// Public key.
publicKey: `0x${string}`,
// Key type.
type: 'secp256k1' | 'p256' | 'webauthn-p256'
};
// JSON-RPC method version.
version: string;
}]
}
The response is intended to be forwarded to
wallet_sendPreparedCalls
(minus thedigest
).
type Response = {
// Capabilities to be forwarded to `wallet_sendPreparedCalls`.
capabilities: Record<string, any>
// Chain ID of the chain the calls are being submitted to.
chainId: `0x${string}`
// Data specific to the Wallet to be forwarded to `wallet_sendPreparedCalls`
// (e.g. ERC-4337 UserOperation or alternative).
context: unknown
// Key (hint) that will be used to sign the call bundle.
key?: {
// Whether the digest will be prehashed by the key (default behavior of WebCrypto APIs).
prehash: boolean,
// Public key.
publicKey: `0x${string}`,
// Key type.
type: 'secp256k1' | 'p256' | 'webauthn-p256' | 'webcrypto-p256'
},
// Digest of the call bundle to sign over.
digest: `0x${string}`
// JSON-RPC method version.
version: string
}
const response = await provider.request({
method: 'wallet_prepareCalls',
params: [{
calls: [{
to: '0xcafebabecafebabecafebabecafebabecafebabe',
data: '0xdeadbeef',
}],
capabilities: {
paymasterService: {
url: 'https://...',
},
},
chainId: '0x1',
key: {
prehash: false,
publicKey: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
type: 'p256',
},
version: '1',
}]
})
/**
* {
* capabilities: {
* paymasterService: {
* url: 'https://...'
* }
* },
* chainId: '0x1',
* context: { ... },
* digest: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
* key: {
* prehash: false,
* publicKey: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
* type: 'p256',
* },
* version: '1',
* }
*/
wallet_sendPreparedCalls
Instructs a Wallet to execute a prepared call bundle (response of wallet_prepareCalls
) with an accompanying signature
.
The request is identical to the response of
wallet_prepareCalls
, except that it includes asignature
.
type Request = {
method: 'wallet_sendPreparedCalls',
params: [{
// Capabilities.
capabilities: Record<string, any>
// Chain ID of the chain the calls are being submitted to.
chainId: `0x${string}`
// Data specific to the Wallet from the `wallet_prepareCalls` response.
// (e.g. ERC-4337 UserOperation or alternative).
context: unknown
// Key that was used to sign the call bundle.
key: {
// Whether the digest will be prehashed by the key (default behavior of WebCrypto APIs).
prehash?: boolean,
// Public key.
publicKey: `0x${string}`,
// Key type.
type: 'secp256k1' | 'p256' | 'webauthn-p256' | 'webcrypto-p256'
},
// Signature of the call bundle.
signature: `0x${string}`
// JSON-RPC method version.
version: string
}]
}
The response is identical to that of
wallet_sendCalls
, except that it is a list.
type Response = {
id: string,
capabilities: Record<string, any>
}[]
const { digest, ...request } = await provider.request({
method: 'wallet_prepareCalls',
params: [{
calls: [{
to: '0xcafebabecafebabecafebabecafebabecafebabe',
data: '0xdeadbeef',
}],
capabilities: {
paymasterService: {
url: 'https://...',
},
},
chainId: '0x1',
key: {
prehash: false,
publicKey: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
type: 'p256',
},
version: '1',
}]
})
const signature = p256.sign(digest, privateKey)
const response = await provider.request({
method: 'wallet_sendPreparedCalls',
params: [{
...request,
signature
}]
})
/**
* [
* {
* id: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
* }
* ]
*/
These endpoints extend the interface established by EIP-5792, given the emergent needs of application developers.
Adding new endpoints for this specific use case is simpler than adding additional options to the wallet_sendCalls
endpoint.
Surfacing the prepared calls should be relatively simple for wallets, who already need to do preparation internally in order to support wallet_sendCalls
, and who then submit signed calls on chain.
TBD
Needs discussion.
Copyright and related rights waived via CC0.