layout | title |
---|---|
default |
Mobile Wallet Adapter 2.0 specification |
{%- comment -%} Please don't introduce unnecessary line breaks in this specification - it's diff-unfriendly. {%- endcomment -%}
- TOC {: toc}
This specification uses semantic versioning
Version: 2.0.0
Version | Description |
---|---|
1.0.0 | Initial release version of the Mobile Wallet Adapter specification (identical to pre-release version 0.9.1) |
2.0.0 | Mobile Wallet Adapter 2.0 specification release |
2.1.0 | Add optional wallet icon parameter to authorize response |
This is retained for historical reference only. None of these versions were official releases, and there are no guarantees of backward compatibility.
Version | Description |
---|---|
0.1.0 | Initial draft |
0.2.0 | Updates based on wallet adapter feedback |
0.2.1 | Fix a few missed pluralizations |
0.3.0 | Sessions now track authorization statefully, rather than by providing auth_token to each privileged method |
0.3.1 | Enforce HTTPS for endpoint-specific URIs |
0.3.2 | Replace timeout placeholders with minimum timeouts |
0.3.3 | sign_messages should take multiple addresses for signing (for parity with sign_transactions behavior) |
0.9.0 | Advancing spec version to 0.9.0 (near-final version) |
0.9.1 | Integrate some review feedback |
The contents of this section attempt to explain this specification with context and goals, but do not form a formal part of the specification itself.
The goal of this specification is to define a protocol to expose Solana iOS and Android wallet app functionality (authorization and transaction signing) to dapps. It is optimized for use cases where the wallet app is running on a mobile device (Android or iOS), but does not preclude usage by wallets on a desktop OS (Linux, macOS, Windows, etc). This protocol is freely available to all Solana developers, and the intention is for this to become the standard for connecting all dapps to mobile wallet apps.
These user stories outline the goals and user experiences that this protocol aims to enable.
- As a user, I want my phone to be a universal wallet for any transaction, regardless of where I am interacting with a dapp (i.e. on a single device, or on a different nearby device)
- As a user, I want my dapp transactions to be private and secure
- As a user, I want local dapp authorizations and transactions to be as simple as those I experience using the web-based wallet-adapter
- As a user, I want my authorization of a dapp by my wallet to be a one-time operation
- As a native dapp developer, I want my dapp to work with all native wallet apps
- As a web dapp developer, I want my mobile browser friendly dapp to work with all native wallet apps
- As a dapp or wallet app developer, I want permissively licensed open source reference implementations of the protocol to be available for my use
These requirements are derived from the user stories, and exist to guide specification design decisions.
-
All communication between dapps and wallet apps should take place over a secure channel.
Rationale: this prevents transaction tampering and frontrunning.
-
At least one transport mechanism should be a standard web technology (for e.g. HTTP/S, WebSockets, etc).
Rationale: this supports using web-based dapps with native wallet apps.
-
The protocol must support one or more mechanisms to exchange a shared token (for e.g., URI links, QR codes, NFC, BT, etc), which shall be used to establish the secure channel.
Rationale: this protects against MITM attacks during secure channel establishment.
-
At least one shared token exchange mechanism must be available to remote browser-based dapps (e.g. displaying a QR code).
Rationale: this supports using remote dapps with native wallet apps.
-
The shared token must be able to establish a secure channel an indefinite number of times.
Rationale: this supports persistent dapp <-> wallet app connections after a one-time authorization.
-
For communication between a dapp and native wallet app running on a single device, the protocol must not require the use of a remote communication intermediary (though this does not preclude the optional use of one, at the discretion of the dapp and/or wallet app).
Rationale: a remote intermediate (such as a reflector server) increases both the communication latency and the attack surface (by intentionally introducing a 3rd party to communications).
-
The protocol must support a mechanism (such as a custom URI scheme) that allows launching a native app from a mobile web browser on both Android and iOS.
Rationale: the user should not be required to manually launch a wallet app for local wallet app interaction.
-
The dapp must identify itself to the wallet app during authorization.
Rationale: this allows the wallet app to give the user context about what they are authorizing.
-
The reference implementation(s) of the protocol should be Apache 2.0 licensed, and made available on a public repository.
association token - a base64url encoding of an ECDSA public keypoint
dapp endpoint - an app implementing user-facing functionality (e.g. DeFi services, blockchain games, etc). This endpoint acts as both the initiator and the client in this protocol.
reflector - an intermediary which brokers connections between two endpoints, when they are unable to communicate directly themselves. It should be viewed as a potential adversary.
wallet endpoint - an app implementing wallet-like functionality (i.e. providing transaction signing services). This endpoint acts as the server in this protocol.
Namespaced identifiers are used in the format ${namespace}:${reference}
to refer to objects like blockchains and features in a canonical and human readable form.
Namespaced chain identifiers are used to refer to blockchains with which a dapp intends to interact with. This scheme is compatible with CAIP-2 chain IDs, but are not required to be used.
The currently supported Solana network chains are solana:mainnet
, solana:testnet
, and solana:devnet
.
Previous versions of this specification used a cluster
parameter to specify the Solana network cluster with which the dapp endpoint intends to interact. The supported values for the cluster
parameter were mainnet-beta
, testnet
, and devnet
. These values have been replaced by the above chain references, but will continue to be supported in the interest of backwards compatibility with previous versions of the specification.
Feature identifiers are used to identify features that are supported by a wallet or account. The namespaces solana
and experimental
are reserved for use by the core protocol specification. The solana
namespace includes the currently defined standard mandatory and optional features features. Features under the experimental
namespace are optional.
These features are mandatory and must be implemented by wallet endpoints. Dapp endpoints can assume that these features are always available without an explicit call to get_capabilities
.
These features are optional, wallet endpoints may choose to implement these features. Dapp endpoints can check if a wallet supports these features by calling get_capabilities
.
solana:signInWithSolana
, an optional extension to theauthorize
method.solana:cloneAuthorization
These features are deprecated, but can be supported by wallet endpoints to maintain backwards compatibility with dapp endpoints using previous version of the Mobile Wallet Adapter specification. Support for these features is optional and should be indicated in the list of supported features returned by get_capabilities
.
WebSockets is a mandatory transport protocol for mobile-wallet-adapter implementations. Dapp endpoints must be able to act as a WebSocket client, and wallet endpoints must be able to act as either a WebSocket server or client (depending on whether the connection is local, or if a reflector is used, respectively).
A WebSocket client API is available in all major browsers, enabling web-based dapps (both mobile and desktop) to use this transport.
When connecting to a Local URI, the dapp endpoint must request, and the wallet endpoint must respond with, the com.solana.mobilewalletadapter.v1
WebSocket subprotocol. When connecting to a Remote URI, the both the dapp and wallet endpoints must request, and the reflector server must respond with, both the com.solana.mobilewalletadapter.v1
and com.solana.mobilewalletadapter.v1.reflector
WebSocket subprotocols.
When the wallet endpoint is acting as a WebSocket server, it must send periodic PING
frames to the dapp endpoint.
Bluetooth LE is an optional transport protocol for mobile-wallet-adapter implementations. It enforces a proximity requirement on the endpoints, which some users may find desirable from a security standpoint. It also eliminates the need for a reflector when connecting dapp and wallet endpoints running on different systems, removing an attack surface from the protocol.
A Web Bluetooth client API is available in many browsers, enabling web-based dapps (both mobile and desktop) to use this transport.
The details of this transport are not defined in this version of the mobile-wallet-adapter protocol. It is expected to be defined in a future protocol version.
Association is the process of establishing a shared association identifier between a dapp endpoint and a wallet endpoint (with no requirement that these be running on the same system). An association is ephemeral - it persists only until the transport is disconnected. A new association is performed every time a dapp endpoint seeks to connect to a wallet endpoint.
The dapp endpoint should generate an ephemeral EC keypair on the P-256 curve, and encode the public keypoint Qa using the X9.62 public key format (0x04 || x || y)
. This public keypoint is then base64url-encoded, and the resulting string is called the association token. The private keypoint for this keypair will be used during session establishment.
When establishing a session, dapp and wallet endpoints must agree on a version of the protocol to be used for the session. Association URIs include a protocol version in the first path segment (solana-wallet:/v1/...
) to indicate the version of the protocol and corresponding behaviors used for the session. Any future revisions to this specification that introduce protocol breaking changes must increment the version field in the association URI.
A version query parameter is used in the association uris below to allow dapp endpoints to specify the major version(s) of the protocol that the client supports. If this version parameter is present in the association URI, the wallet endpoint should additionally send a session properties payload during session establishment to indicate the negotiated version to the client. If no version query parameter is present, the connection is assumed to be a legacy connection (v1).
When running on Android or iOS, the dapp endpoint should first attempt to associate with a local wallet endpoint by opening a URI (either from within the browser for a web dapp, or directly from a native dapp) with the solana-wallet:
scheme. The URI should be formatted as:
solana-wallet:/v1/associate/local?association=<association_token>&port=<port_number>&v=<version>
where:
association_token
is as described aboveport_number
is a random number between 49152 and 65535version
is the major version of the protocol that the client supports. This value can be repeated to specify multiple major versions supported by the client. The wallet endpoint should select the highest version from this set that it supports.
Once the URI is opened, the dapp endpoint should attempt to connect to the local WebSocket address, ws://localhost:<port_number>/solana-wallet
, and proceed to Session establishment.
If the WebSocket transport is unavailable locally after no less than 30 seconds, the dapp endpoint should display user guidance (e.g. download a wallet) and optionally present the opportunity to connect to a remote wallet endpoint using one or more of the other association mechanisms.
If a wallet endpoint is installed which has registered an Activity for this URI scheme and format, it will be launched. Upon launch via this URI, the wallet endpoint should start a WebSocket server on port port_number
and begin listening for connections to /solana-wallet
for no less than 10 seconds. This websocket server should only accept connections from the localhost.
Whether launched from a web browser or a native dapp endpoint, the Intent’s action will be android.intent.action.VIEW
and the category will be android.intent.category.BROWSABLE
. When launched by a web browser, no caller identity will be available, and as such, the referrer details available within the Intent cannot be used to verify the origin of the association. When launched by a native dapp endpoint, this Intent should be sent with startActivityForResult
, allowing the wallet endpoint to query the caller identity. The result returned to the calling dapp endpoint is not specified.
iOS support is planned for a future version of this specification
Since desktop OSes do not generally allow launching an app with a URI, a dapp endpoint should not attempt to use this association scheme. One or more of the other association mechanisms should be utilized instead.
When running on a desktop OS, or when connecting to a local wallet endpoint fails, the dapp endpoint may present a URI suitable for connection via a reflector WebSocket server, which will reflect traffic between two parties. The URI should be formatted as:
solana-wallet:/v1/associate/remote?association=<association_token>&reflector=<host_authority>&id=<reflector_unique_id>&v=<version>
where:
association_token
is as described abovehost_authority
is the address of a publicly routable WebSocket server implementing the reflector protocolreflector_unique_id
is a number generated securely at random by the dapp endpoint, 0 ≤ n ≤ 2^53 - 1version
is the major version of the protocol that the client supports, as described above
This URI should be provided to the wallet endpoint through an out-of-band mechanism, detailed in the subsections below. Each of the dapp and wallet endpoints should attempt to connect to the WebSocket address wss://<host_authority>/reflect?id=<reflector_unique_id>
. On connection, each endpoint should wait for the Reflector protocol to signal that the counterparty endpoint has connected.
The dapp endpoint must wait no less than 30 seconds for reflection to commence. The wallet endpoint must wait no less than 10 seconds for reflection to commence. If it does not commence, the endpoints will disconnect and present appropriate error messages to the user.
Dapp endpoints must support displaying the remote URI to the user encoded as a QR code. After displaying a QR code, the dapp endpoint should connect to the specified reflector. Wallet endpoints on devices with a camera should support scanning QR codes within the app, receiving notifications from the system that a QR code encoding a remote URI has been received, or both. Upon receipt of a remote URI from a scanned QR code, the wallet endpoint should attempt to connect to the specified reflector.
Dapp endpoints may optionally also support copying the remote URI to the system clipboard. After copying the remote URI to the clipboard, the dapp endpoint should connect to the specified reflector. Wallet endpoints on desktop OSes should provide a method to accept a remote URI from the system clipboard. Upon receipt of a remote URI from the system clipboard, the wallet endpoint should attempt to connect to the specified reflector.
During Session Establishment, the wallet endpoint may return a URI prefix to use for future association attempts. This is expected to be used with App Links or Universal Links, to ensure that the desired wallet app is launched by the dapp. A dapp should reject URI prefixes with schemes other than https:
for security reasons. If a dapp has been informed of a URI prefix for a wallet, it should use it with the same path elements and parameters provided as for the solana-wallet:
URI scheme. For e.g., if an Android wallet endpoint handles App Links for solanaexamplewallet.io, it could provide a prefix of:
https://solanaexamplewallet.io/mobilewalletadapter
The dapp endpoint would then assemble the following URI to begin association with that wallet locally:
https://solanaexamplewallet.io/mobilewalletadapter/v1/associate/local?association=<association_token>&port=<port_number>
All other aspects of associating are identical to those specified in the relevant preceding section.
Bluetooth LE session establishment is not defined in this version of the mobile-wallet-adapter protocol. It is expected to be defined in a future protocol version.
While the expectation is that the WebSocket server be responsible for periodically issuing PING
s to the client endpoint, the introduction of a reflector adds a requirement that the endpoints need to be notified when the counterparty is available.
An APP_PING
is an empty message. It is sent by the reflector to each endpoint when both endpoints have connected to the reflector. On first connecting to a reflector, the endpoints should wait to receive this message before initiating any communications. After any other message has been received, the APP_PING
message becomes a no-op, and should be ignored.
Dapp endpoint to wallet endpoint
<Qd><Sa>
where:
Qd
: the X9.62-encoded dapp endpoint ephemeral ECDH public keypointSa
: a P1363-encoded ECDSA-SHA256 signature of Qd using the association keypair
The HELLO_REQ
message is the first message sent after a connection is established between the endpoints, and begins a Diffie-Hellman-Merkle key exchange. The dapp endpoint generates an ephemeral P-256 EC keypair and X9.62-encodes the public keypoint Qd
. This encoded public keypoint is then ECDSA-SHA256-signed with the private keypoint of the association keypair, and the P1363-encoded signature appended to the encoded Qd
public keypoint to form the HELLO_REQ message. The private keypoint of the P-256 EC keypair is retained for use on receipt of the HELLO_RSP
message.
On receipt, the wallet endpoint should verify the signature of Qd
using the association token. If signature verification is successful, the wallet endpoint should prepare and send a HELLO_RSP
message to the dapp endpoint.
If qd signature verification fails, if no HELLO_REQ
message is received by the wallet endpoint within no less than 10 seconds, or if a second HELLO_REQ
message is received by the wallet endpoint at any time during the connection, all ephemeral key materials should be discarded, and the connection should be closed.
Wallet endpoint to dapp endpoint
<Qw><session_props>
where:
Qw
: the X9.62-encoded wallet endpoint ephemeral ECDH public keypointsession_props
: an encrypted message containing the session properties JSON payload, as described below.
{
"v":"<version>"
}
where:
version
: is the major version of the protocol in use for the session. This is expected to be the highest protocol version supported by both dapp and wallet endpoints, as specified during association
In response to a valid HELLO_REQ
message to the wallet endpoint (which should be the first message received after a connection is established between the endpoints), it should generate a P-256 EC keypair and X9.62-encode the public keypoint Qw
. This encoded public keypoint Qw
forms the first part of the HELLO_RSP
message.
If a v=
query parameter is present in the association URI presented by the dapp endpoint, the wallet endpoint MUST append an encrypted session properties message to the HELLO_RSP
message. If the connecting dapp endpoint runs software for a legacy version of this standard (i.e. no v=
parameter present during association), the wallet MUST NOT send the session_props
message to the client, and must assume that the connection is a legacy connection. If the wallet endpoint does not support legacy connections, it should close the connection immediately upon receipt of the HELLO_REQ
message.
Upon sending of the HELLO_RSP
message by the wallet endpoint, and receipt of the HELLO_RSP
message by the dapp endpoint, each endpoint is now in possession of all necessary key materials to generate a shared secret for the chosen encryption algorithm, using the ECDH (as specified by NIST SP 800-56A) and HKDF (as specified by RFC5869) algorithms with the following KDF parameters:
ikm
: the 32-byte ECDH-derived secretsalt
: the 65 byte X9.62-encoded public keypoint Qa of the association keypairL
: 16 bytes (such that the output of the HKDF may be used directly as an AES-128 key)
Once each endpoint has calculated the ephemeral shared secret, they should proceed to providing or consuming the Wallet RPC interface.
If either public keypoint Qd
or Qw
is not valid, if no HELLO_RSP
message is received by the dapp endpoint within no less than 10 seconds, or if a second HELLO_RSP
message is received by the dapp endpoint at any time during the connection, all ephemeral key materials should be discarded, and the connection should be closed.
After session establishment completes, the wallet endpoint is ready to accept JSON-RPC 2.0 non-privileged method calls from the dapp endpoint. To invoke privileged methods, a dapp endpoint must first put the session into an authorized state via an authorize
method call. For details on how a session enters and exits an authorized state, see the non-privileged methods.
After the session establishment process completes, every message received by an endpoint is expected to be encrypted with AES-128-GCM (as specified by NIST SP 800-38D). The sending endpoint should prepare the encrypted message by concatenating:
- the message sequence number, a 4-byte big-endian unsigned integer
- a random 12-byte IV (which should be newly generated for each encrypted message)
- the AES-128-GCM message ciphertext, encrypted using the private key created during session establishment and the random IV, and including the 4-byte message sequence number as additional authorized data (AAD)
- the 16-byte authentication tag generated during AES-128-GCM encryption
After decrypting the ciphertext with the shared secret generated during session establishment and verifying the authentication tag, the receiving endpoint should further interpret it as a JSON-RPC 2.0 message.
The message sequence number is monotonically increasing, and starts at 1 when session establishment completes. Each endpoint maintains its own independent sequence number, and increments it by 1 each time an encrypted message is created and sent. On receipt of an encrypted message, each endpoint should verify that the sequence number is 1 greater than that of the previous message received (other than for the first message received). On receipt of a message with a sequence number set to anything other than the expected next value, the encrypted message should be discarded and the connection closed.
Why does the protocol specify this, rather than rely on, e.g., TLS?
- In remote usage, a reflector server is used to mediate a connection between a dapp and wallet endpoint. This reflector is viewed as an adversary, and so should not have access to the plaintext of endpoint communications.
- This protocol supports multiple transports (i.e.
ws:
for local connections,wss:
for reflector connections, Bluetooth LE for wireless connections). Each of these has different confidentiality and authenticity guarantees. By encrypting messages at the application layer, the protocol can provide a uniform minimum security guarantee for a heterogeneous set of transports.
Non-privileged methods do not require the current session to be in an authorized state to invoke them (though they may still accept an auth_token
to provide their functionality).
{: .no_toc }
authorize
{: .no_toc }
{
“identity”: {
“uri”: “<dapp_uri>”,
“icon”: “<dapp_icon_relative_path>”,
“name”: “<dapp_name>”,
},
"chain": "<chain>",
"features": ["<feature_id>", ...],
"addresses": ["<address>", ...],
“auth_token”: “<auth_token>”,
"sign_in_payload": <sign_in_payload>,
"cluster": "<cluster>"
}
where:
identity
: a JSON object, containing:uri
: (optional) a URI representing the web address associated with the dapp endpoint making this authorization request. If present, it must be an absolute, hierarchical URI.icon
: (optional) either a data URI containing a base64-encoded SVG, WebP, PNG, or GIF image or a relative path (fromuri
) to an image asset file of an icon identifying the dapp endpoint making this authorization requestname
: (optional) the display name for this dapp endpoint
chain
: (optional) if set, the chain identifier for the chain with which the dapp endpoint intends to interact; supported values includesolana:mainnet
,solana:testnet
,solana:devnet
,mainnet-beta
,testnet
,devnet
. If not set, defaults tosolana:mainnet
.addresses
: (optional) if set, a list of base64 encoded account addresses that the dapp endpoint wishes to be included in the authorized scope. Defaults tonull
.features
: (optional) if set, a list of feature identifiers that the dapp endpoint intends to use in the session. Defaults tonull
.auth_token
: (optional) an opaque string previously returned by a call toauthorize
, orclone_authorization
. When present, the wallet endpoint should attempt to reauthorize the dapp endpoint silently without prompting the user.sign_in_payload
: (optional) a value object containing the Sign-In input fields as described by the Sign In With Solana specification. If present, the wallet endpoint should present the SIWS message to the user and return the resulting signature and signed message, as described below.cluster
: (optional) an alias forchain
. This parameter is maintained for backwards compatibility with previous versions of the spec, and will be ignored if thechain
parameter is present.
{: .no_toc }
{
“auth_token”: “<auth_token>”,
“accounts”: [
{
“address”: “<address>",
"display_address": "<display_address>",
"display_address_format": "<display_address_format>",
“label”: “<label>”,
"icon": "<icon>",
"chains": ["<chain_id>", ...],
"features": ["<feature_id>", ...]
},
...
],
“wallet_uri_base”: “<wallet_uri_base>”,
“wallet_icon”: “<wallet_icon>”,
"sign_in_result": {
“address”: “<address>",
"signed_message": "<signed_message>"
"signature": "<signature>"
"signature_type": "<signature_type>"
}
}
where:
auth_token
: an opaque string representing a unique identifying token issued by the wallet endpoint to the dapp endpoint. The format and contents are an implementation detail of the wallet endpoint. The dapp endpoint can use this on future connections to reauthorize access to privileged methods.accounts
: one or more value objects that represent the accounts to which this auth token corresponds. These objects hold the following properties:address
: a base64-encoded public key for this account.display_address
: (optional) the address for this account. The format of this string will depend on the chain, and is specified by thedisplay_address_format
fielddisplay_address_format
: (optional) the format of thedisplay_address
.chains
: a list of chain identifiers supported by this account. These should be a subset of the chains supported by the wallet.features
: (optional) a list of feature identifiers that represent the features that are supported by this account. These features must be a subset of the features returned byget_capabilities
. If this parameter is not present the account has access to all available features (both mandatory and optional) supported by the wallet.label
: (optional) a human-readable string that describes the account. Wallet endpoints that allow their users to label their accounts may choose to return those labels here to enhance the user experience at the dapp endpoint.icon
: (optional) a data URI containing a base64-encoded SVG, WebP, PNG, or GIF image of an icon for the account. This may be displayed by the app.
wallet_uri_base
: (optional) if this wallet endpoint has an endpoint-specific URI that the dapp endpoint should use for subsequent connections, this member will be included in the result object. The dapp endpoint should use this URI for all subsequent connections where it expects to use thisauth_token
.wallet_icon
: (optional) a data URI containing a base64-encoded SVG, WebP, PNG, or GIF image of an icon for the wallet. This may be displayed by the app.sign_in_result
: (optional) if the authorize request included a Sign In With Solana sign in payload, the result must be returned here as a value object containing the following:address
: the address of the account that was signed in. The address of the account may be different from the provided input address, but must be the address of one of the accounts returned in theaccounts
field.signed_message
: the base64-encoded signed message payloadsignature
: the base64-encoded signaturesignature_type
: (optional) the type of the message signature produced. If not provided in this response, the signature must be"ed25519"
.
{: .no_toc }
-32602
(Invalid params) if the params object does not match the format defined aboveERROR_AUTHORIZATION_FAILED
if the wallet endpoint did not authorize access to the requested privileged methodsERROR_CHAIN_NOT_SUPPORTED
if the wallet endpoint does not support the requested chainINVALID_SIWS_PAYLOAD
if thesign_in_payload
parameter was included but invalid
This method allows the dapp endpoint to request authorization from the wallet endpoint for access to privileged methods. On success, it returns an auth_token
providing access to privileged methods, along with addresses and optional labels for all authorized accounts. It may also return a URI suitable for future use as an endpoint-specific URI. After a successful call to authorize
, the current session will be placed into an authorized state, with privileges associated with the returned auth_token
. On failure, the current session with be placed into the unauthorized state.
The returned auth_token
is an opaque string with meaning only to the wallet endpoint which created it. It is recommended that the wallet endpoint include a mechanism to authenticate the contents of auth tokens it issues (for e.g., with an HMAC, or by encryption with a secret symmetric key). This auth_token
may be used to reauthorize future sessions between these dapp and wallet endpoints by including it in future calls to authorize
.
If an auth_token
is provided by the dapp endpoint, the wallet endpoint should put the current session in an authorized state, with privileges associated with the specified auth_token
. Additionally, updated values for auth_token
, accounts
, and/or wallet_uri_base
will be returned. These may differ from those originally provided in the authorize response for this auth token; if so, they override any previous values for these parameters. The prior values should be discarded and not reused. This allows a wallet endpoint to update the auth token used by the dapp endpoint, or to modify the set of authorized account addresses or their labels without requiring the dapp endpoint to restart the authorization process.
If an auth_token
is provided by the dapp endpoint, and the result is ERROR_AUTHORIZATION_FAILED
, this auth token cannot be reused, and should be discarded. The dapp endpoint should request a new token with the authorize
method. The session with be placed into the unauthorized state.
Wallet endpoints should make every effort possible to verify the authenticity of the presented identity. While the uri
parameter is optional, it is strongly recommended - without it, the wallet endpoint may not be able to verify the authenticity of the dapp.
The chain
parameter allows the dapp endpoint to select a specific chain with which to interact. This is relevant for both sign_transactions
, where a wallet may refuse to sign transactions without a currently valid blockhash, and for sign_and_send_transactions
, where the wallet endpoint must know which cluster to submit the transactions to. This parameter would normally be used to select a cluster other than solana:mainnet
for dapp development and testing purposes. Under normal circumstances, this field should be omitted, in which case the wallet endpoint will interact with the solana:mainnet
cluster.
If both an auth_token
and chain
are provided by the dapp endpoint, the wallet must verify that the specified chain is the same that was associated with the specified auth_token
. If the chain
was not previously authorized for use with this auth_token
, the wallet must request authorization for the new chain from the user and issue a new auth_token
associated with the new chain.
There are 3 parameters in the result that are derived from the unique identity of the account: address
, display_address
, and display_address_format
. address
contains the public key of the account (for whatever cryptographic system is used as the authority for the account). For example, in the Solana blockchain, this is the Ed25519 public key. address
should always be encoded with base64, as this parameter is intended for programmatic consumption rather than human consumption. In contrast, the display_address
and display_address_format
are representations of the account appropriate for human consumption. The contents of these two fields are chain-specific; for the Solana blockchain, display_address_format
will be base58
, and display_address
will be the public key for the account, encoded with base58. Other chains may use different encodings and/or different methods for obtaining a human-consumable address.
The features
and addresses
parameters in the request indicate to the wallet endpoint what account(s) should be used in the session. addresses
can be used by the dapp to request accounts that it already knows about (likely from a previous session, a cached user account, etc.). The features
parameter can be used to request accounts that have access to a specified feature, or set of features. These parameters are not meant to limit what the wallet endpoint is providing access to, they are used to provide clues to the wallet as to what accounts are most appropriate to use for this request.
Wallet endpoints may optionally support the sign_in_payload
parameter as an implementation of Sign In with Solana. Wallets should present a dedicated UI that clearly displays the relevant parameters of the sign in request to the user before asking them to sign. The wallet endpoint must return the sign_in_result
in the response, or a relevant error if the sign in payload was invalid. If a wallet endpoint does not support this method, dapp endpoints can manually construct the sign in message and use the sign_messages
request to obtain a sign in signature from the wallet endpoint after authorization.
Previous versions of this specification (pre 2.0) defined a separate reauthorize
method that required an auth_token
parameter and attempted to put the current session in an authorized state, with privileges associated with the specified auth_token
. The addition of the optional auth-token
parameter on the authorize
method allows dapps to reauthorize a previously issued auth token. This change was made to simplify the interface and reduce confusion on when to use each method, and to align with the functionality of the wallet-standard connect
feature which takes an optional silent
parameter to request accounts that have already been authorized without prompting the user.
Authorization of a previously issued auth token is intended to be a lightweight operation, as compared to issuing a new auth token. It normally should not present any UI to the user, but instead perform only the subset of dapp endpoint identity checks that can be performed quickly and without user intervention. The intent is to quickly verify that an auth token remains valid for use by this dapp endpoint. If verification fails for any reason, the wallet endpoint should report ERROR_AUTHORIZATION_FAILED
to the dapp endpoint, which would then discard the stored auth token and begin authorization from scratch with a new call to authorize
.
Wallet endpoints should balance the responsibility for performing these identity checks against their latency impact. For example, a wallet endpoint may choose to accept an auth token issued in the last several minutes without performing any additional checks, but require dapp endpoint identity checks to be performed for an auth token that was issued before then. This facilitates multiple sessions to be established in a short timespan, related to the same user interaction with a dapp endpoint. The auth token validity policy is a competency of the wallet endpoint, and dictating any specific auth token lifespan or timeout is outside the scope of this protocol specification. However, it is strongly recommended that wallet endpoints do not issue auth tokens with unlimited lifespans or for which dapp endpoint identity checks are never performed on authorize
.
{: .no_toc }
deauthorize
{: .no_toc }
{
“auth_token”: “<auth_token>”,
}
where:
auth_token
: an opaque string previously returned by a call toauthorize
, orclone_authorization
{: .no_toc }
{}
{: .no_toc }
-32602
(Invalid params) if the params object does not match the format defined above
This method will make the provided auth_token
invalid for use (if it ever was valid). To avoid disclosure, this method will not indicate whether the auth_token
was previously valid to the caller.
If, during the current session, the specified auth token was returned by the most recent call to authorize
, the session with be placed into the unauthorized state.
{: .no_toc }
get_capabilities
{: .no_toc }
{}
{: .no_toc }
{
"max_transactions_per_request": <max_transactions_per_request>,
"max_messages_per_request": <max_messages_per_request>,
"supported_transaction_versions": [<supported_transaction_versions>, ...]
"features": ["<feature_id>"]
}
where:
max_transactions_per_request
: (optional) if present, the max number of transaction payloads which can be signed by a singlesign_transactions
orsign_and_send_transactions
request. If absent, the implementation doesn't publish a specific limit for this parameter.max_messages_per_request
: (optional) if present, the max number of transaction payloads which can be signed by a singlesign_messages
request. If absent, the implementation doesn't publish a specific limit for this parameter.supported_transaction_versions
: the Solana network transaction formats supported by this wallet endpoint. Allowed values are those defined forTransactionVersion
(for e.g.,"legacy"
,0
, etc).features
: a list of feature identifiers for the optional features supported by this wallet endpoint. Mandatory features should not be included in this list because all valid wallet endpoints must implement these features. Dapp endpoints can assume that mandatory features are supported by the wallet.
{: .no_toc }
-32602
(Invalid params) if the params object does not match the format defined above
This method can be used to enumerate the capabilities and limits of a wallet endpoint's implementation of this specification. It returns whether optional specification features are supported, as well as any implementation-specific limits.
Privileged methods require the current session to be in an authorized state to invoke them. For details on how a session enters and exits an authorized state, see the non-privileged methods.
{: .no_toc }
sign_and_send_transactions
{: .no_toc }
{
“payloads”: [“<transaction>”, ...],
"options": {
“min_context_slot”: <min_context_slot>,
"commitment": "<commitment>",
"skip_preflight": <skip_preflight>,
"max_retries": <max_retries>,
"wait_for_commitment_to_send_next_transaction": <wait_for_commitment_to_send_next_transaction>
}
}
where:
payloads
: one or more base64-encoded transaction payload to signoptions
: (optional) a JSON object, containing:min_context_slot
: (optional) if set, the minimum slot number at which to perform preflight transaction checkscommitment
: (optional) desired commitment level. Expected values are"finalized"
,"confirmed"
and"processed"
. If provided, confirm the transaction after sendingskip_preflight
: (optional) boolean to enable/disable transaction verification at the RPCmax_retries
: (optional) maximum number of times for the RPC node to retry sending the transaction to the leaderwait_for_commitment_to_send_next_transaction
: (optional) boolean to wait till specified commitment before sending the next transaction. Ifcommitment
not specified, wait till"confirmed"
commitment
{: .no_toc }
{
“signatures”: [“<transaction_signature>”, ...],
}
where:
signatures
: the corresponding base64-encoded transaction signatures
{: .no_toc }
-
-32602
(Invalid params) if the params object does not match the format defined above -
-32601
(Method not found) ifsign_and_send_transactions
is not supported by this wallet endpoint -
ERROR_AUTHORIZATION_FAILED
if the current session is in the unauthorized state, either becauseauthorize
has not been invoked for the current session, or because the current session's authorization has been revoked by the wallet endpoint -
ERROR_INVALID_PAYLOADS
“data”: { “valid”: [<transaction_valid>, ...] }
if any transaction does not represent a valid transaction for signing, where:
transaction_valid
: an array of booleans with the same length aspayloads
indicating which are valid
-
ERROR_NOT_SIGNED
if the wallet endpoint declined to sign this transaction for any reason -
ERROR_NOT_SUBMITTED
“data”: { “signatures”: [“<transaction_signature>”, ...], }
if the wallet endpoint was unable to submit one or more of the signed transactions to the network, where:
signatures
: the corresponding base64-encoded transaction signatures for transactions which were successfully sent to the network, ornull
for transactions which were unable to be submitted to the network for any reason
-
ERROR_TOO_MANY_PAYLOADS
if the wallet endpoint is unable to sign all transactions due to exceeding implementation limits. These limits may be available viaget_capabilities
.
Implementation of this method by a wallet endpoint is optional.
The wallet endpoint should attempt to simulate the transactions provided by payloads
and present them to the user for approval (if applicable). If approved (or if it does not require approval), the wallet endpoint should verify the transactions, sign them with the private keys for the authorized addresses, submit them to the network, and return the transaction signatures to the dapp endpoint.
options
allows customization of how the wallet endpoint processes the transactions it sends to the Solana network. If specified, min_context_slot
specifies the minimum slot number that the transactions should be evaluated at. This allows the wallet endpoint to wait for its network RPC node to reach the same point in time as the node used by the dapp endpoint, ensuring that, e.g., the recent blockhash encoded in the transactions will be available.
This method is optional, to support signing-only wallet endpoints which do not have any form of network connectivity.
it does not allow the dapp endpoint to specify the network RPC server to submit the transaction to; that is at the discretion of the wallet endpoint. If this is a detail that matters to the dapp endpoint, it should instead use the sign_transactions
method and submit the transaction to a network RPC server of its choosing.
It is recommended that dapp endpoints verify that each transaction reached an appropriate level of commitment (typically either confirmed
or finalized
).
{: .no_toc }
sign_messages
{: .no_toc }
{
"addresses": ["<address>", ...],
“payloads”: [“<message>”, ...],
}
where:
addresses
: one or more base64-encoded addresses of the accounts which should be used to signmessage
. These should be a subset of the addresses returned byauthorize
for the current session's authorization.payloads
: one or more base64url-encoded message payloads to sign
{: .no_toc }
{
“signed_payloads”: [“<signed_message>”, ...],
}
where:
signed_payloads
: the corresponding base64-encoded signed message payloads
{: .no_toc }
-
-32602
(Invalid params) if the params object does not match the format defined above -
ERROR_AUTHORIZATION_FAILED
if the current session is in the unauthorized state, either becauseauthorize
has not been invoked for the current session, or because the current session's authorization has been revoked by the wallet endpoint -
ERROR_INVALID_PAYLOADS
“data”: { “valid”: [<message_valid>, ...], }
if any message does not represent a valid message for signing, where:
message_valid
: an array of booleans with the same length aspayloads
indicating which are valid
-
ERROR_NOT_SIGNED
if the wallet endpoint declined to sign these messages for any reason -
ERROR_TOO_MANY_PAYLOADS
if the wallet endpoint is unable to sign all messages due to exceeding implementation limits. These limits may be available viaget_capabilities
.
The wallet endpoint should present the provided messages for approval. If approved, the wallet endpoint should sign the messages with the private key for the authorized account address, and return the signed messages to the dapp endpoint. The signatures should be appended to the message, in the same order as addresses
.
This method should not be used for transaction signing. The wallet endpoint should additionally check that the payloads to be signed are not a transaction message. If any payloads in the request can be successfully parsed as a Solana transaction message, the wallet should immediately reject the request and return ERROR_INVALID_PAYLOADS
and appropriately indicate the invalid payloads as described above.
{: .no_toc }
clone_authorization
{: .no_toc }
{}
{: .no_toc }
{
“auth_token”: “<auth_token>”,
}
where:
auth_token
: as defined forauthorize
{: .no_toc }
-32602
(Invalid params) if the params object does not match the format defined above-32601
(Method not found) ifclone_authorization
is not supported by this wallet endpointERROR_AUTHORIZATION_FAILED
if the current session is in the unauthorized state, either becauseauthorize
has not been invoked for the current session, or because the current session's authorization has been revoked by the wallet endpointERROR_NOT_CLONED
if the wallet endpoint declined to clone the current authorization for any reason
Implementation of this method by a wallet endpoint is optional.
This method attempts to clone the session's currently active authorization in a form suitable for sharing with another instance of the dapp endpoint, possibly running on a different system. Whether or not the wallet endpoint supports cloning an auth_token
is an implementation detail. If this method succeeds, it will return an auth_token
appropriate for sharing with another instance of the same dapp endpoint.
The clone_authorization
method enables sharing of an authorization between related instances of a dapp endpoint (for example, running on a mobile device and a desktop OS). This is a sensitive operation; dapp endpoints must endeavor to transfer the token securely between dapp endpoint instances. The ability of wallet endpoints to validate the identity of the holder of the cloned token is an implementation detail, and may be weaker than that of the original token. As such, not all wallet endpoints are expected to support this feature.
The protocol defines the following constants:
const ERROR_AUTHORIZATION_FAILED = -1
const ERROR_INVALID_PAYLOADS = -2
const ERROR_NOT_SIGNED = -3
const ERROR_NOT_SUBMITTED = -4
const ERROR_NOT_CLONED = -5
const ERROR_TOO_MANY_PAYLOADS = -6
const ERROR_CHAIN_NOT_SUPPORTED = -7
const ERROR_ATTEST_ORIGIN_ANDROID = -100
{% plantuml %} {% include_relative _diagrams/association.plantuml %} {% endplantuml %}
{% plantuml %} {% include_relative _diagrams/encrypted_message_wrapping.plantuml %} {% endplantuml %}
{% plantuml %} {% include_relative _diagrams/authorize_and_sign.plantuml %} {% endplantuml %}
{% plantuml %} {% include_relative _diagrams/reauthorize_and_sign.plantuml %} {% endplantuml %}
These methods have been deprecated in the current version of the specification, but should still be supported by wallet endpoints for backwards compatibility. These methods may be removed in a future version of the specification.
Dapp endpoint identity verification is domain-based - through various platform-specific mechanisms, a dapp endpoint attests to the wallet that it is associated with a specific web domain. Wallet endpoints are responsible for deciding whether to extend trust to each dapp based on the attested web domain.
Identity verification on Android relies on Digital Asset Links to associate apps with a web domain. Both native wallet apps and native dapps on Android should:
- Be identified by package name and signing key fingerprint in a
/.well-known/assetlinks.json
file hosted on the web domain of the app developer - Use App Links for all Activity
<intent-filter>
elements in the app manifest that accept the web domain, (or any subdomain)
Failure to establish these Digital Asset Links will result in the inability of wallet endpoints to verify the identity of dapp endpoints, and puts the users’ funds at risk from malicious dapps.
Native dapp endpoints are required to associate with the wallet endpoint using either a Local URI or an Endpoint-specific URI, started via an Intent with startActivityForResult
. This allows the wallet endpoint to retrieve the calling package identity, with getCallingPackage
.
To verify the authenticity of the identity provided to authorize
, the wallet endpoint should check the Digital Asset Link for the identity
element URI and ensure that the calling package is signed with a certificate listed in an android_app
statement within the Digital Asset Link file.
If the identity
element does not contain a URI, or if the dapp endpoint’s calling package cannot be verified against an android_app
target in the Digital Asset Link file, it is recommended that wallet endpoints decline to issue an authorization token to the dapp endpoint and return ERROR_AUTHORIZATION_FAILED
.
Chromium (and related browsers, such as Chrome) associate with the wallet endpoint using either a Local URI or an Endpoint-specific URI, started via an Intent with startActivity
, which does not provide the calling identity. In addition, EXTRA_REFERRER
may not always be provided in the Intent, and even if it is, it is not a secure source of identity on Android. To provide dapp endpoint identity verification, a different method is required.
The browser security sandbox can attest to the origin of a web dapp endpoint, but only within the confines of the sandbox. To extend trust from the browser to the wallet endpoint, it must be brought into the browser security model. Trusted Web Activities enable this by allowing a verifiable postMessage
channel to be established between the native wallet app and the web browser (once again, using Digital Asset Links). Once a channel is established, the wallet app can request that a trusted script running within the browser attest to the identity of the dapp.
The wallet endpoint’s software developer must host an HTML document containing an attestation script on a web domain whose Digital Asset Link file contains an android_app
target verifying the wallet endpoint app. This attestation script is responsible for:
- Generating an attestation keypair for the wallet endpoint, and issuing the public key to it
- Attesting to the origin of dapp endpoints, by signing an attestation payload with the requested private key
The contents of this script are an implementation detail of wallet endpoints.
On an authorize
, or clone_authorization
attempt from a local web dapp endpoint, the wallet endpoint should check if the provided identity has been authorized during this session. If not, it should return ERROR_ATTEST_ORIGIN_ANDROID
with an identity challenge. The dapp endpoint should load the identity attestation script and use postMessage to convey the dapp endpoint’s origin and the wallet endpoint challenge. The attestation script should construct a response to the challenge containing the origin, sign it with the corresponding wallet attestation private key, and return it to the dapp endpoint. The dapp endpoint should reattempt the failed authorization request, including the attestation response.
{: .no_toc }
authorize, clone_authorization
{: .no_toc }
{
...,
“attest_origin”: “<attest_origin_token>”,
}
where:
attest_origin_token
: a token, generated byattest_origin_uri
, from which the wallet endpoint can verify the authenticity of theidentity
parameter to this method
{: .no_toc }
No changes
{: .no_toc }
...
ERROR_ATTEST_ORIGIN_ANDROID
“data”: {
“context”: “<context>”,
“challenge”: “<challenge>”,
“attest_origin_uri”: “<uri>”,
}
if the identity
provided in params requires verification, where:
context
: the attestation context (for e.g., a key ID)challenge
: a base64-encoded challenge nonce uniquely identifying this attestation requesturi
: the URI of the wallet endpoint HTML document containing the attestation script
On receipt of an ERROR_ATTEST_ORIGIN_ANDROID
response, the dapp endpoint should decode the challenge nonce, construct the string “attest-origin” || decoded_challenge || session_secret
, calculate the SHA256 hash of this string, and base64-encode the result. This value, h
, binds challenge
to this dapp endpoint (by including the session secret). It should then construct the JSON message:
{
“m: “origin-attest”,
“h”: <h>,
“context”: <context>,
}
The dapp endpoint should load origin_attest_uri
into an invisible iframe, and postMessage(origin_attest_msg)
to it. The dapp endpoint should then await a response message from the iframe (via a message event listener). On receipt, the dapp endpoint should destroy the iframe, and reattempt the authorization request to the wallet endpoint, including the returned message contents as the attest_origin
parameter.
Many of the details of this identity verification process are left as an implementation detail of the wallet endpoint. However, as an illustrative example, here is one scheme which could be used to implement identity verification.
{% plantuml %} {% include_relative _diagrams/android_origin_attestation.plantuml %} {% endplantuml %}
This approach uses a Custom Tab (possibly in Trusted Web Activity mode) with the same browser backend as the association request to securely create the attestation keypair, which is stored in local storage of the wallet endpoints web domain (from which the origin attestation script is fetched). The public keypoint is returned and stored by the wallet.
When the wallet determines that an origin attestation is necessary, it returns this same origin attestation script URI to the dapp endpoint, along with the challenge parameters. The dapp endpoint constructs the appropriate origin attestation message, opens the attestation script in an iframe, and sends it the message using postMessage. This provides the dapp endpoint origin to the attestation script, signed with the private keypoint. This message is returned to the dapp endpoint, which then reattempts the authorization request with the message as the attest_origin parameter. The wallet endpoint is able to use the corresponding public keypoint to validate the authenticity of this message, and then subsequently validate the dapp endpoint identity using the token payload.
iOS support is planned for a future version of this specification
No dapp endpoint verification method is defined by this version of the mobile-wallet-adapter protocol. Wallet endpoints are free to perform whatever implementation-specific verification techniques they desire (including rejecting all remote dapp endpoint authorization requests as unverified).
If a wallet endpoint supports the clone_authorization
method, at a minimum it should also support reauthorizing (via authorize
) on the resulting auth token from a remote dapp endpoint.
Reflection is an extremely simple protocol; it will reflect all communications received from each endpoint to the other endpoint, up to a maximum size of 4KB per frame.
The reflector should listen for WebSocket secure connections to:
wss://<host_authority>/reflect?id=<reflector_unique_id>
The reflector will maintain two data sets:
- Half open reflections - this set contains endpoint connections which are waiting for their corresponding counterparty to connect, along with the connection established time
- Fully open reflections - this set contains endpoint connections for which the corresponding counterparty has connected, along with the reflection established time
On a new connection, the reflector will take the following action:
- If there is an entry in the fully open reflections data set for the specified
reflector_unique_id
, the connection will be closed immediately - If there is an entry in the half open reflections data set for the specified
reflector_unique_id
, that entry will be removed and a new entry added to the fully open reflections data set for the connection pair. Reflection will be started for this connection pair. - Otherwise, an entry will be added to the half open reflections data set for this connection. All incoming data on this connection will be silently discarded.
When reflection begins, the reflector will send an APP_PING
message to each connection, and then begin transmitting all messages received from each connection to the other connection in the pair.
On a disconnection:
- If the connection is part of the fully open reflections data set, the entry will be removed and the other connection closed as well
- Otherwise, the entry for the connection will be removed from the half-open reflections data set
Entries in the half open data set should be removed, and the connection closed, if still present in this set no less than 30 seconds after being added. Entries in the fully open data set should be removed, and both connections closed, if still present in this set no less than 90 seconds after being added. Entries in either data set may also be removed, and the connection(s) closed, before these time periods have elapsed at the discretion of the reflector (for e.g., during periods of limited resource availability).
To ensure that all active connections are maintained, the reflector shall ensure that periodic PING
frames are sent to each connection.
While the initial version of this specification is designed to support the Solana network, future support for other chain types is envisioned. This could be accomplished with:
- modified association URIs (using schemes of the form
newchain-wallet://
instead ofsolana-wallet://
) - modified parameters to the RPC methods
authorize
would have a different set of chain identifiersget_capabilities
would be used to express domain-specific capabilities relevant to the chainsign_and_send_transactions
would use chain-specific shapes for theoptions
object
The specifics of the application of Mobile Wallet Adapter to each additional chain would be captured either with an update to this specification, or an extension accompanying this specification, as appropriate.