[SNIP] Starknet Provider JavaScript API

Simple Summary

A JavaScript Starknet Provider API for consistency across clients and applications.

Inspired by EIP-1193.

Abstract

A common convention in the Starknet web application (“dapp”) ecosystem is for key management software (“wallets”) to expose their API via a JavaScript object in the web page. This object is called “the Provider”.

This SIMP formalizes a Starknet Provider API to promote wallet interoperability. The API is designed to be minimal, event-driven, and agnostic of transport and RPC protocols. Its functionality is easily extended by defining new RPC methods and message event types.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”,
and “OPTIONAL” in this document are to be interpreted as described in RFC-2119.

Definitions

This section is non-normative.

  • Provider
    A JavaScript object made available to a consumer, that provides access to Ethereum by means of a Client.
  • Client
    An endpoint that receives Remote Procedure Call (RPC) requests from the Provider, and returns their results.
  • Wallet
    An end-user application that manages private keys, performs signing operations, and acts as a middleware between the Provider and the Client.
  • Remote Procedure Call (RPC)
    A Remote Procedure Call (RPC), is any request submitted to a Provider for some procedure that is to be processed by a Provider, its Wallet, or its Client.

Connectivity

The Provider is said to be “connected” when it can service RPC requests to at least one chain.

The Provider is said to be “disconnected” when it cannot service RPC requests to any chain at all.

To service an RPC request, the Provider must successfully submit the request to the remote location, and receive a response. In other words, if the Provider is unable to communicate with its Client, for example due to network issues, the Provider is disconnected.

API

Interface written in typescript is available in Appendix I

Properties

id

Type: string

A unique identifier for the wallet provider.
Used by dapps to easily identify a wallet.

  • The id MUST be unique among all wallet providers.
  • The id SHOULD NOT change between different versions of the wallet provider.

name

Type: string

The name of the wallet.
Can be used by dapps to display the list of available wallets during connection process.

version

Type: string

TRhe version of the wallet implementation.
Dapps could use it to adapt to changes between two versions of a wallet provider.

  • The version SHOULD use semver

icon

Type: string

An image as a base64 encoded string representing the icon/logo of the wallet provider.
Can be used by dapps to display the list of available wallets during connection process.

  • The icon MAY be implemented
  • The icon format MUST be an image encoded in base64

Methods

request

Based on EIP-2696

Type: <T extends RpcMessage>(call: Omit<T, "result">) => Promise<T["result"]>

Usage example with specific RPC method:

interface RpcMessage {
  type: string;
  params?: any;
  result: never;
}

interface extends RpcMessageSwitchChain {
  type: "wallet_switchStarknetChain"
  params: SwitchStarknetChainParameter
  result: boolean
}

const switchChainSuccess = await Provider.request<RpcMessageSwitchChain>(args: RequestArguments);

addListener

Based on NodeJS EventEmitter

Type: addListener(eventName: string, listener: (...params: unknown[]) => void): void

This methods enable dapps to register to a specific event.

removeListener

Based on NodeJS EventEmitter

Type: removeListener(eventName: string, listener: (...params: unknown[]) => void): void

This methods enable dapps to register to a specific event.

Events

connect

Based on EIP-1193

interface ProviderConnectInfo {
  readonly chainId: string;
}

Provider.on('connect', listener: (connectInfo: ProviderConnectInfo) => void): Provider;

disconnect

Based on EIP-1193

interface ProviderRpcError extends Error {
  message: string;
  code: number;
  data?: unknown;
}

Provider.on('disconnect', listener: (error: ProviderRpcError) => void): Provider;

chainChanged

Based on EIP-1193

The Provider emits chainChanged when connecting to a new chain.

Provider.on('chainChanged', listener: (chainId: string) => void): Provider;

The event emits a hexadecimal string chainId.

accountsChanged

Based on EIP-1193

The Provider emits accountsChanged if the accounts returned from the Provider (eth_accounts) change.

Provider.on('accountsChanged', listener: (accounts: AccountInterface[]) => void): Provider;

message??

Based on EIP-1193

interface ProviderMessage {
  readonly type: string;
  readonly data: unknown;
}

Provider.on('message', listener: (message: ProviderMessage) => void): Provider;

Appendix

Appendix I

ProviderInterface comes from starknetjs

interface RequestArguments {
  readonly type: string;
  readonly params?: readonly unknown[] | object;
}

interface WalletProviderInterface {
  id: string;
  name: string;
  version: string;
  icon?: string;
  provider: ProviderInterface;

  request(args: RequestArguments): Promise<unknown>;
  addListener(event: string, listener: (...args: unknown[]) => void): this;
  removeListener(event: string, listener: (...args: unknown[]) => void): this;
}
13 Likes

Some comments regarding this proposal:

  • On this API, I voluntarily didn’t define how to connect an account as it will come with another Proposal. So there are no enable method and no account property.
  • For events subscription, current Ethereum implementations use methods on and removeListener. I think the name are not consistent, it should either be on/off or addListener/removeListener which I chose to follow NodeJS EventEmitter standard.
10 Likes

Should the icon be an object {dark: string, light: string} to support switching themes?

10 Likes

I kept compatibility with existing libraries like get-starknet and ArgentX.

Maybe we can keep the icon property and add a new optional iconDark property?

10 Likes

we can have both on & addListener, to keep compatibility with L1 specs.

10 Likes

I think it will be best to keep it simple, e.g. the wallet should expose its logo via icon, and if it doesn’t work for a dapp, that dapp could always provide a custom icon based on the id field.
(pls keep in mind that usually logos are designed to work well in both light & dark modes)

7 Likes

I don’t think starknet.js is the right place to declare Wallet<->dApp interfaces.
It’s better to manage it at the same place where dApps & Wallet are being connected → get-starknet

8 Likes

It’s what I suggested on another SIMP regarding the AccountInterface : [SIMP] Connect accounts to dapps - #2 by ltoussaint

First, I think we should remove any reference to starknet.js interfaces from SIMPs, so maybe we should declare them explicitly on the SIMPs which need them.

Then, get-starknet is clearly a better place than starknet.js to define those typescript interface; that being said, maybe it could be a good idea to create a specific repository to define (using typescript) all interfaces defined on SIMPs.
A project or library may need these interfaces without using get-starknet, so it’s maybe better to create a repository dedicated to SIMPs interface?

6 Likes

sure, and it makes sense to host it on https://github.com/starknet-community-libs (@bbrandtom)


Edit by @galronx (14 Nov, 2022): starknet-community-libs is now Starknet · GitHub

7 Likes

Would it be possible/beneficial to write the specification in OpenRpc? The GitHub - open-rpc/generator: Multi-Component & Multi-Language Generators for OpenRPC can then be used to create the typescript classes.

7 Likes