Why Starknet needs a standard way to find account capabilities

[Discussion] Account capability discovery for the Wallet API (wallet_getCapabilities)

Summary

The Starknet Wallet API lets a dApp negotiate which RPC spec versions a wallet speaks, but it gives the dApp no way to learn how the connected account actually executes transactions. A growing number of accounts run gasless through a paymaster and execute only via outside execution, so they never self-broadcast a normal INVOKE. A dApp cannot discover this from the wallet, so it estimates fees and builds transactions that assume self-broadcast, and then those paths fail.

This post proposes a small, read only addition to the Wallet API: wallet_getCapabilities, by which a connected wallet reports account-level execution properties (outside execution only, paymaster-backed, sponsored, and session keys) so the dApp routes correctly without per wallet special casing. It follows the shape of EIP-5792 on Ethereum. It adds discovery only. It does not change execution semantics.

This is a follow on to the Wallet API introduced in the New Starknet Wallet-Dapp API discussion [9]. That API gave us version negotiation (wallet_supportedSpecs, wallet_supportedWalletApi) but no capability discovery; this proposal fills that one gap.

Problem

Today a dApp that connects over the Wallet API can call wallet_supportedSpecs and wallet_supportedWalletApi to learn version strings, wallet_getPermissions to learn granted permissions, and wallet_requestAccounts to get the address. None of these tell the dApp:

  • whether the account executes only via SNIP-9 outside execution (it is relayed, never self-broadcast),

  • whether fees are sponsored by a paymaster,

  • whether the wallet supports session keys.

Concretely, for accounts whose validation path is not used for the gasless flow, the account never submits a self-validated INVOKE. A dApp that does not know this will try to estimate fees the normal way and build a direct INVOKE, and that reverts or routes incorrectly. Each wallet works around this with bespoke dApp integrations and documentation that say “always route through our paymaster.” That does not scale, and it is the friction that keeps gasless smart accounts from being drop-in across dApps.

What exists today, and why it does not fill the gap

  1. Version negotiation. wallet_supportedSpecs and wallet_supportedWalletApi report API and spec versions, not account behavior. See the Wallet API spec [1].

  2. On-chain interface detection. Outside execution support is discovered on-chain with SRC-5 supports_interface (SNIP-5), and starknet.js exposes it as getSnip9Version. But this proves “the account supports SNIP-9”, not “the account must be relayed”. SNIP-9 is additive, so an account can support outside execution and still self broadcast. There is no “outside execution only” signal, and an on-chain read is not the natural place for a dApp connection flow to make a routing decision. See SNIP-9 [3].

  3. Paymaster service API. SNIP-29 standardizes a paymaster service (paymaster_isAvailable, paymaster_buildTransaction, paymaster_executeTransaction) with a fee mode that includes a no-fee (sponsored) option. Sponsorship there is a per-request property of the paymaster service, and in practice it is decided per request rather than guaranteed for a given account. There is no way for a dApp to learn from the wallet that an arbitrary connected account would be sponsored. See SNIP-29 [4].

  4. Paymaster compatibility checks. This is the closest existing thing in intent. An applicative paymaster (for example, AVNU’s) can answer whether a given account is eligible for sponsorship, typically by checking the account class hash against an allowlist. But that answer comes from the paymaster service and the on-chain class hash, not from the wallet, and it is single-purpose (one paymaster, one question) rather than an extensible capability map the wallet advertises at connect time. A dApp connected to an arbitrary wallet still has no standard way to ask the wallet what the account can do.

So the pieces for gasless execution exist (SNIP-9 plus SNIP-29), but there is no wallet-side signal that lets a dApp discover an account’s execution model at connect time.

Proposal

Add one read-only method to the Wallet API:


wallet_getCapabilities(address?) -> AccountCapabilities

It returns a small, forward compatible descriptor. Illustrative shape:


{

"execution": {

*// "self": account self validates and self broadcasts a normal INVOKE.*

*// "outside": account executes only via SNIP-9 outside execution (relayed).*

*// "either": supports both.*

"mode": "outside",

"outsideExecutionVersions": \["V2"\]   *// SNIP-9 IOutsideExecution_V2*

  },

"fees": {

*// "sponsored": user pays no gas (paymaster covers it).*

*// "user": user pays gas in a fee token.*

*// "either"*

"payer": "sponsored",

"paymaster": { "snip29": true }      *// routes through a SNIP-29 paymaster*

  },

"sessions": { "supported": true },

"extensions": {}                        *// namespaced, vendor prefixed*

}

A dApp reads the descriptor and, when the account is outside execution only or sponsored, it skips fee estimation and self broadcast and lets the wallet handle the path it already handles on wallet_addInvokeTransaction:


const caps = await wallet.request({ type: "wallet_getCapabilities" }).catch(() => null);

if (caps?.execution?.mode === "outside" || caps?.fees?.payer === "sponsored") {

*// do not estimate or self broadcast; submit the call and let the wallet relay it*

} else {

*// classic estimate plus addInvokeTransaction*

}

This is discovery, not new execution. wallet_addInvokeTransaction already does the right thing inside each wallet. The new method lets the dApp stop fighting it.

Precedent

Ethereum standardized exactly this pattern. EIP-5792 defines wallet_getCapabilities, which returns a per chain map of capability name to capability detail [5]. ERC-7677 builds on it with a paymasterService capability that advertises sponsored execution, so a dApp reads the capability and delegates fee handling instead of estimating or self-funding gas [6]. The proposal here is the Starknet analogue, adapted to SNIP-9 and SNIP-29.

There is no equivalent on Starknet today. The closest existing Starknet work is on-chain. Chipi Pay authored the Session Keys SNIP [7] and the Pluggable Signer Interface draft [8], which argues that an account should be able to declare which signer kind its owner uses so that paymasters, SDKs, and dApps stop hardcoding per wallet integrations. That declaration lands on-chain through SRC-5 and a kind registry. The method proposed here is the Wallet API layer companion to that on-chain work: the same goal of removing per-wallet special casing applied to the dApp connection flow.

Why a spec PR, not a SNIP

The wallet_* interface is not governed by a SNIP. It is an OpenRPC document, wallet-api/wallet_rpc.json, in starkware-libs/starknet-specs, mirrored into starknet-io/types-js, which starknet.js and get-starknet consume [1]. Changes land as PRs with implementer agreement, as wallet_switchStarknetChain did. An additive discovery method fits that process. This thread is the discussion step before such a PR.

Backward compatibility

  • Additive and optional. No existing method changes.

  • A wallet that does not implement the method, or returns an empty object, is treated as execution.mode: "self" and fees.payer: "user", which is the current implicit assumption. No wallet is forced to adopt it.

  • The extensions field lets wallets advertise capabilities ahead of a future spec revision without breaking parsers.

Open questions

  1. Method or field. A new wallet_getCapabilities method (aligned with EIP-5792 naming) versus a capabilities field on wallet_getPermissions. A method is cleaner to cache.

  2. Per account granularity. Should it take an address parameter for multi-account wallets?.

  3. How much paymaster detail to expose, given the wallet routes internally.

  4. Naming of the execution.mode values, to align with SNIP-9 vocabulary.

Who benefits

Any account that runs gasless or relayed, which includes hosted smart account wallets (Ready, Cartridge, Chipi) and any SNIP-29 paymaster integrator. It standardizes a fact these accounts already have and that dApps already need, and it removes per-wallet special casing in the dApp.

Next steps

If there is interest, the path is: agree on the shape in this thread, open a PR to wallet_rpc.json plus the mirror in types-js, and land a reference implementation. Chipi can provide the first reference implementation, returning the outside execution only and sponsored descriptor for its accounts.

References

[1] Starknet Wallet API spec, `wallet-api/wallet_rpc.json`, starkware-libs/starknet-specs. starknet-specs/wallet-api/wallet_rpc.json at master · starkware-libs/starknet-specs · GitHub

[2] Wallet API spec PR, starkware-libs/starknet-specs#203. https://github.com/starkware-libs/starknet-specs/pull/203

[3] SNIP-9: Outside Execution. SNIPs/SNIPS/snip-9.md at main · starknet-io/SNIPs · GitHub

[4] SNIP-29: Applicative Paymaster API Standard. SNIPs/SNIPS/snip-29.md at main · starknet-io/SNIPs · GitHub

[5] EIP-5792: Wallet Call API (defines wallet_getCapabilities). EIP-5792: Wallet Call API

[6] ERC-7677: Paymaster Web Service Capability. ERC-7677: Paymaster Web Service Capability

[7] [SNIP] Session Keys for Smart Accounts (authored by Chipi Pay), Starknet community forum. [SNIP] Session Keys for Smart Accounts

[8] SNIP draft: Pluggable Signer Interface for Smart Accounts (authored by Chipi Pay). # [SNIP] Pluggable Signer Interface for Smart Accounts

[9] New Starknet Wallet-Dapp API (the Wallet API this proposal extends), Starknet community forum, 2024. New Starknet Wallet<>Dapp API