Mobile Wallet Adapter specification
- Mobile Wallet Adapter specification
- Version
- Non-normative front matter
- Specification
- Support for other chains
Version
This specification uses semantic versioning
Version: 1.0.0
Changelog (oldest to newest)
Version | Description |
---|---|
1.0.0 | Initial release version of the Mobile Wallet Adapter specification (identical to pre-release version 0.9.1) |
Pre-v1.0.0 changelog
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 |
Non-normative front matter
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.
Summary
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.
User stories
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
Requirements
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.
Specification
Terminology
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.
Transport
WebSockets
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
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
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.
Association keypair
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.
Local URI
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>
where:
association_token
is as described aboveport_number
is a random number between 49152 and 65535
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.
Android
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
iOS support is planned for a future version of this specification
Desktop
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.
Remote URI
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>
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 - 1
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.
QR codes
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.
Clipboard
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.
Endpoint-specific URIs
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
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.
Session establishment
APP_PING
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.
HELLO_REQ
Direction
Dapp endpoint to wallet endpoint
Specification
<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
Description
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.
HELLO_RSP
Direction
Wallet endpoint to dapp endpoint
Specification
<Qw>
where:
Qw
: the X9.62-encoded wallet endpoint ephemeral ECDH public keypoint
Description
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 HELLO_RSP
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.
Wallet RPC interface
Operation
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 either an authorize
or a reauthorize
method call. For details on how a session enters and exits an authorized state, see the non-privileged methods.
Encrypted message wrapping
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.
Non-normative commentary
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
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).
authorize
JSON-RPC method specification
Method
authorize
Params
{
“identity”: {
“uri”: “<dapp_uri>”,
“icon”: “<dapp_icon_relative_path>”,
“name”: “<dapp_name>”,
},
"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) 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
cluster
: (optional) if set, the Solana network cluster with which the dapp endpoint intends to interact; supported values includemainnet-beta
,testnet
,devnet
. If not set, defaults tomainnet-beta
.
Result
{
“auth_token”: “<auth_token>”,
“accounts”: [
{“address”: “<address>", “label”: “<label>”},
...
],
“wallet_uri_base”: “<wallet_uri_base>”,
}
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 toreauthorize
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 address for this accountlabel
: (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.
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
.
Errors
-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_CLUSTER_NOT_SUPPORTED
if the wallet endpoint does not support the requested Solana cluster
Description
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.
Dapp 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 cluster
parameter allows the dapp endpoint to select a specific Solana cluster 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 mainnet-beta
for dapp development and testing purposes. Under normal circumstances, this field should be omitted, in which case the wallet endpoint will interact with the mainnet-beta
cluster.
deauthorize
JSON-RPC method specification
Method
deauthorize
Params
{
“auth_token”: “<auth_token>”,
}
where:
auth_token
: an opaque string previously returned by a call toauthorize
,reauthorize
, orclone_authorization
Result
{}
Errors
-32602
(Invalid params) if the params object does not match the format defined above
Description
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
or reauthorize
, the session with be placed into the unauthorized state.
reauthorize
JSON-RPC method specification
Method
reauthorize
Params
{
“identity”: {
“uri”: “<dapp_uri>”,
“icon”: “<dapp_icon_relative_path>”,
“name”: “<dapp_name>”,
},
“auth_token”: “<auth_token>”,
}
where:
identity
: as defined forauthorize
auth_token
: an opaque string previously returned by a call toauthorize
,reauthorize
, orclone_authorization
Result
{
“auth_token”: “<auth_token>”,
“accounts”: [
{“address”: “<address>", “label”: “<label>”},
...
],
“wallet_uri_base”: “<wallet_uri_base>”,
}
where:
auth_token
: as defined forauthorize
accounts
: as defined forauthorize
wallet_uri_base
: as defined forauthorize
Errors
-32602
(Invalid params) if the params object does not match the format defined aboveERROR_AUTHORIZATION_FAILED
if the wallet endpoint declined to authorize the current session withauth_token
for any reason
Description
This method attempts to put the current session in an authorized state, with privileges associated with the specified auth_token
.
On success, the current session will be placed into an authorized state. 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 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.
Non-normative commentary
reauthorize
is intended to be a lightweight operation, as compared to authorize
. 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 reauthorize
.
get_capabilities
JSON-RPC method specification
Method
get_capabilities
Params
{}
Result
{
"supports_clone_authorization": <supports_clone_authorization>,
"supports_sign_and_send_transactions": <supports_sign_and_send_transactions>,
"max_transactions_per_request": <max_transactions_per_request>,
"max_messages_per_request": <max_messages_per_request>,
"supported_transaction_versions": [<supported_transaction_versions>, ...]
}
where:
supports_clone_authorization
:true
if theclone_authorization
method is supported, otherwisefalse
supports_sign_and_send_transactions
:true
if thesign_and_send_transactions
method is supported, otherwisefalse
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).
Errors
-32602
(Invalid params) if the params object does not match the format defined above
Description
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
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.
sign_transactions
JSON-RPC method specification
Method
sign_transactions
Params
{
“payloads”: [“<transaction>”, ...],
}
where:
payloads
: one or more base64-encoded transaction payloads to sign
Result
{
“signed_payloads”: [“<signed_transaction>”, ...],
}
where:
signed_payloads
: the corresponding base64-encoded signed transaction payloads
Errors
-32602
(Invalid params) if the params object does not match the format defined aboveERROR_AUTHORIZATION_FAILED
if the current session is in the unauthorized state, either becauseauthorize
orreauthorize
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:
valid
: an array of booleans with the same length as payloads indicating which are valid
ERROR_NOT_SIGNED
if the wallet endpoint declined to sign these transactions for any reasonERROR_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
.
Description
The wallet endpoint should attempt to simulate the transactions provided by data and present them to the user for approval (if applicable). If approved (or if it does not require approval), the wallet endpoint should sign the transactions with the private keys for the requested authorized account addresses, and return the signed transactions to the dapp endpoint.
sign_and_send_transactions
JSON-RPC method specification
Method
sign_and_send_transactions
Params
{
“payloads”: [“<transaction>”, ...],
"options": {
“min_context_slot”: <min_context_slot>,
}
}
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 checks
Result
{
“signatures”: [“<transaction_signature>”, ...],
}
where:
signatures
: the corresponding base64-encoded transaction signatures
Errors
-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 endpointERROR_AUTHORIZATION_FAILED
if the current session is in the unauthorized state, either becauseauthorize
orreauthorize
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
.
Description
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.
Non-normative commentary
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
).
sign_messages
JSON-RPC method specification
Method
sign_messages
Params
{
"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
orreauthorize
for the current session’s authorization.payloads
: one or more base64url-encoded message payloads to sign
Result
{
“signed_payloads”: [“<signed_message>”, ...],
}
where:
signed_payloads
: the corresponding base64-encoded signed message payloads
Errors
-32602
(Invalid params) if the params object does not match the format defined aboveERROR_AUTHORIZATION_FAILED
if the current session is in the unauthorized state, either becauseauthorize
orreauthorize
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 reasonERROR_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
.
Description
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
.
clone_authorization
JSON-RPC method specification
Method
clone_authorization
Params
{}
Result
{
“auth_token”: “<auth_token>”,
}
where:
auth_token
: as defined forauthorize
Errors
-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
orreauthorize
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
Description
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.
Non-normative commentary
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.
Constants
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_CLUSTER_NOT_SUPPORTED = -7
const ERROR_ATTEST_ORIGIN_ANDROID = -100
Illustrative diagrams
Session establishment
Encrypted message wrapping
Authorize and sign transaction
Reauthorize and sign transaction
Dapp identity verification
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.
Android
Native dapp
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
.
Web dapp
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
, reauthorize
, 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.
authorize, reauthorize, and clone_authorization modifications
Method
authorize, reauthorize, clone_authorization
Params
{
...,
“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
Result
No changes
Errors
...
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.
Non-normative commentary
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.
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
iOS support is planned for a future version of this specification
Remote
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 reauthorize
on the resulting auth token from a remote dapp endpoint.
Reflector protocol
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.
Support for other chains
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.