--- layout: default title: Mobile Wallet Adapter 2.0 specification --- {%- comment -%} Please don't introduce unnecessary line breaks in this specification - it's diff-unfriendly. {%- endcomment -%} # Mobile Wallet Adapter specification 1. TOC {: toc} # Version This specification uses [semantic versioning](https://en.wikipedia.org/wiki/Software_versioning#Semantic_versioning) **Version: 2.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) | | 2.0.0 | Mobile Wallet Adapter 2.0 specification release | | 2.1.0 | Add optional wallet icon parameter to authorize response | ### 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](#privileged-methods) | | 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. 1. 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) 1. As a user, I want my dapp transactions to be private and secure 1. As a user, I want local dapp authorizations and transactions to be as simple as those I experience using the web-based wallet-adapter 1. As a user, I want my authorization of a dapp by my wallet to be a one-time operation 1. As a native dapp developer, I want my dapp to work with all native wallet apps 1. As a web dapp developer, I want my mobile browser friendly dapp to work with all native wallet apps 1. 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. 1. All communication between dapps and wallet apps should take place over a secure channel. _Rationale: this prevents transaction tampering and frontrunning._ 1. 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._ 1. 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._ 1. 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._ 1. 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._ 1. 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)._ 1. 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._ 1. 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._ 1. 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. ### Nostr Specific Terminology _nostr keypair_ - an ephemeral secp256k1 keypair used for signing Nostr events and routing messages between endpoints via a Nostr relay _nostr relay_ - a [Nostr protocol (NIP-01)](https://github.com/nostr-protocol/nips/blob/master/01.md) server that relays events between connected clients. Like the reflector, it should be viewed as a potential adversary. _session identifier_ - a hex-encoded SHA-256 hash derived from the association token, used to route Nostr events between endpoints of an MWA session ## Identifiers Namespaced identifiers are used in the format `${namespace}:${reference}` to refer to objects like blockchains and features in a canonical and human readable form. ### Chain Identifiers Namespaced chain identifiers are used to refer to blockchains with which a dapp intends to interact with. This scheme is compatible with [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) 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 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. #### Mandatory Features 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`](#get_capabilities). - [`solana:signMessages`](#sign_messages) - [`solana:signAndSendTransaction`](#sign_and_send_transactions) #### Optional Features 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`](#get_capabilities). - `solana:signInWithSolana`, an optional extension to the [`authorize`](#authorize) method. - [`solana:cloneAuthorization`](#clone_authorization) ### Deprecated Features 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`](#get_capabilities). - [`solana:signTransactions`](#sign_transactions) ## 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. Two WebSocket subprotocols are used to establish transport behaviors between endpoints. 1. The `com.solana.mobilewalletadapter.v1` subprotocol indicates that the server supports binary WebSocket data frames. When this protocol is in use, endpoints must send and receive payloads as raw unencoded binary using a binary data frame. 1. The `com.solana.mobilewalletadapter.v1.base64` subprotocol indicates that the server supports UTF-8 text WebSocket data frames. When this protocol is in use, endpoints must send and receive payloads as Base64 encoded text using a UTF-8 text data frame. When connecting to a [Local URI](#local-uri), the dapp endpoint must request both the `com.solana.mobilewalletadapter.v1` and `com.solana.mobilewalletadapter.v1.base64` WebSocket subprotocols. The wallet endpoint must respond with one of the `com.solana.mobilewalletadapter.v1` or `com.solana.mobilewalletadapter.v1.base64` WebSocket subprotocols to indicate which of the binary or base64 encoded data frames must be used by the dapp endpoint. When connecting to a [Remote URI](#remote-uri), both the dapp and wallet endpoints must request the `com.solana.mobilewalletadapter.v1` and `com.solana.mobilewalletadapter.v1.base64` WebSocket subprotocols. The reflector server must respond to both endpoints with one of the `com.solana.mobilewalletadapter.v1` or `com.solana.mobilewalletadapter.v1.base64` WebSocket subprotocols to indicate which of the binary or base64 encoded data frames must be used by both endpoints. When the wallet endpoint is acting as a WebSocket server, it must send periodic [`PING`](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2) 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. ### Nostr [Nostr (NIP-01)](https://github.com/nostr-protocol/nips/blob/master/01.md) is an optional transport protocol for mobile-wallet-adapter implementations. It provides an alternative to the [reflector protocol](#reflector-protocol) for remote connections between dapp and wallet endpoints, and serves as a fallback transport where local connections may be unavailable. Nostr relays are independently operated and interchangeable. Any NIP-01 compliant relay that supports ephemeral events and tag filtering can serve as an MWA transport relay. This ensures that the protocol does not depend on any single relay operator. When Nostr is used as the transport, MWA protocol messages are carried as [Nostr events](#nostr-event-structure). The Nostr relay routes events between the dapp and wallet endpoints based on a shared [session identifier](#session-identifier-derivation). All existing MWA protocol layers above the transport — [session establishment](#session-establishment), [encrypted message wrapping](#encrypted-message-wrapping), and the [Wallet RPC interface](#wallet-rpc-interface) — are unchanged. #### Nostr event structure MWA messages are transported using a custom Nostr ephemeral event kind. Ephemeral events (kind 20000-29999) are not expected to be stored by relays; they are forwarded to connected clients with matching subscriptions and then discarded. ```json { "id": "<32-byte lowercase hex SHA-256 of the serialized event>", "pubkey": "<32-byte lowercase hex sender nostr public key>", "created_at": , "kind": 20012, "tags": [ ["d", ""], ["p", "<32-byte lowercase hex recipient nostr public key>"] ], "content": "", "sig": "<64-byte lowercase hex Schnorr signature over secp256k1>" } ``` The kind number `20012` is provisional and subject to change pending publication of a formal NIP. ##### Tags The `d` tag is required on every MWA transport event. It contains the [session identifier](#session-identifier-derivation) and is used by both endpoints to subscribe to events belonging to their session. The `p` tag is required on events _where the sender knows the recipient's Nostr public key_. It enables relay-side optimizations and access controls. For the first message in a session (before the recipient's Nostr public key is known), the `p` tag may be omitted. ##### Content The `content` field contains a [base64-encoded (RFC 4648)](https://datatracker.ietf.org/doc/html/rfc4648#section-4) MWA protocol message. This is the same binary payload that would otherwise be sent as a WebSocket frame: - During [session establishment](#session-establishment): [`HELLO_REQ`](#hello_req) or [`HELLO_RSP`](#hello_rsp) binary payloads - After session establishment: [encrypted message wrapping](#encrypted-message-wrapping) payloads (sequence number + IV + ciphertext + authentication tag) An empty `content` field has no defined meaning in the MWA protocol and should be ignored by the recipient, with the exception of the [`CONNECT`](#connect) event. ##### Event verification Endpoints must verify the Nostr event `id` and `sig` fields (per [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md)) before processing any received event. Events with an invalid signature or mismatched ID should be silently discarded. #### Session identifier derivation The session identifier allows both endpoints to subscribe to events belonging to their session on the Nostr relay without prior knowledge of each other's Nostr public keys. Both endpoints independently derive the same session identifier from the association token exchanged during [association](#association). The session identifier is derived as follows: ``` session_identifier = lowercase_hex(SHA-256(base64url_decode(association_token))) ``` where: - `association_token` is the base64url-encoded X9.62 public keypoint as defined in [association keypair](#association-keypair) - `base64url_decode` decodes the association token to its raw 65-byte public keypoint - `SHA-256` produces a 32-byte hash - `lowercase_hex` encodes the hash as a 64-character lowercase hexadecimal string The session identifier is not secret — it is derived from the association token which is itself a public key. The one-way hash prevents direct recovery of the association token from the session identifier. ## 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. ### Protocol Version Negotiation 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](#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). ### 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=&port=&v= ``` where: - `association_token` is as described above - `port_number` is a random number between 49152 and 65535 - `version` 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:/solana-wallet`, and proceed to [Session establishment](#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`](https://developer.android.com/reference/android/content/Intent#ACTION_VIEW) and the category will be [`android.intent.category.BROWSABLE`](https://developer.android.com/reference/android/content/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`](https://developer.android.com/reference/android/app/Activity#startActivityForResult(android.content.Intent,%20int)), 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](#reflector-protocol), which will reflect traffic between two parties. The URI should be formatted as: ``` solana-wallet:/v1/associate/remote?association=&reflector=&id=&v= ``` where: - `association_token` is as described above - `host_authority` is the address of a publicly routable WebSocket server implementing the reflector protocol - `reflector_unique_id` is the base64url encoded [reflector ID](#reflector_id) byte sequence generated securely at random by the reflector server - `version` is the major version of the protocol that the client supports, as described above The dapp endpoint should attempt to connect to the WebSocket address `wss:///reflect`. On connection, the dapp endpoint must wait for the server to respond with a [`REFLECTOR_ID`](#reflector_id) message before proceeding to association. Once the dapp has recieved the unique reflector ID, the association URI should be provided to the wallet endpoint through an out-of-band mechanism, detailed in the subsections below. The wallet endpoint should attempt to connect to the WebSocket address `wss:///reflect?id=`. On connection, each endpoint should wait for the [Reflector protocol](#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. ### Nostr URI When using a [Nostr relay](#nostr) as the transport, the dapp endpoint may present a URI suitable for connection via Nostr. The URI should be formatted as: ``` solana-wallet:/v1/associate/{local|remote}/nostr?association=&relay=&pubkey=&v= ``` where: - `association_token` is as described above - `relay` is the domain of a Nostr relay (e.g. `relay.example.com`) - `nostr_pubkey` is the 64-character lowercase hex-encoded public key of the dapp endpoint's ephemeral [Nostr keypair](#nostr-specific-terminology) - `version` is the major version of the protocol that the client supports, as described above The dapp must provide the correct `local` or `remote` path to inform the wallet of the context of the connection. This allows the walet endpoint to cater its UX accordingly for both on device or remote MWA connections. Prior to presenting the association URI, the dapp endpoint should generate an ephemeral secp256k1 [Nostr keypair](#nostr-specific-terminology), derive the [session identifier](#session-identifier-derivation) from the association token, connect to the specified Nostr relay, and subscribe to session events: ```json ["REQ", "", {"kinds": [20012], "#d": [""]}] ``` The association URI should then be provided to the wallet endpoint through an out-of-band mechanism. The same mechanisms described for both [local](#local-uri) and [remote](#remote-uri) URIs apply. Upon receipt of a Nostr URI, the wallet endpoint should generate its own ephemeral secp256k1 [Nostr keypair](#nostr-specific-terminology), derive the [session identifier](#session-identifier-derivation) from the association token, connect to the specified Nostr relay, and subscribe to session events using the same filter as the dapp endpoint. The wallet endpoint should then publish a [`CONNECT`](#connect) event to signal readiness to the dapp endpoint. The dapp endpoint must wait no less than 30 seconds for the wallet endpoint's [`CONNECT`](#connect) event. The wallet endpoint must wait no less than 10 seconds for [`HELLO_REQ`](#hello_req) after sending the `CONNECT` event. If these timeouts elapse, the endpoints should disconnect from the relay and present appropriate error messages to the user. Once both endpoints have identified their counterpart's Nostr public key (the dapp endpoint learns the wallet's from `CONNECT`; the wallet endpoint learns the dapp's from the [Nostr URI](#nostr-uri)), all subsequent events received for the session MUST originate from the expected counterpart public key. Events with an unexpected `pubkey` must be silently discarded. When the Nostr transport is in use, all subsequent protocol messages ([`HELLO_REQ`](#hello_req), [`HELLO_RSP`](#hello_rsp), and [encrypted messages](#encrypted-message-wrapping)) are sent as [Nostr events](#nostr-event-structure) with the binary payload base64-encoded in the `content` field. Message ordering is determined by the MWA [sequence numbers](#encrypted-message-wrapping), not by the Nostr event `created_at` timestamp. If an endpoint's WebSocket connection to the Nostr relay is closed for any reason, the session is terminated. Endpoints must not attempt to reconnect to the relay to resume a terminated session. A new [association](#association) should be performed to establish a new session. ### Endpoint-specific URIs During [Session Establishment](#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](https://developer.android.com/training/app-links) or [Universal Links](https://developer.apple.com/ios/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=&port= ``` 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 ### REFLECTOR_ID #### Direction WebSocket reflector server to dapp endpoint #### Specification ``` ``` where: - `id_length` is the varint encoded byte length of the `reflector_unique_id` - `reflector_unique_id` is an opaque byte sequence generated by the WebSocket reflector server, as defined in the [reflector protocol](#reflector-protocol) #### Description When a WebSocket reflector is in use for remote connections, the reflector server must send the dapp endpoint a unique reflector ID as defined in the [reflector protocol](#reflector-protocol) immediately after a successful connection is established. On receipt of the `REFLECTOR_ID`, the dapp endpoint should proceed to session establishment with the wallet, passing this reflector ID (URL endcoded) in the [Remote URI](#remote-uri) sent to the wallet. ### APP_PING While the expectation is that the WebSocket server be responsible for periodically issuing [`PING`s](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2) 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. ### CONNECT #### Direction Wallet endpoint to dapp endpoint #### Specification A [Nostr event](#nostr-event-structure) of kind `20012` with a `msg` tag with value `CONNECT` and an empty `content` field: ```json { "kind": 20012, "pubkey": "", "tags": [ ["d", ""], ["p", ""], ["msg", "CONNECT"] ], "content": "" } ``` where: - `wallet_nostr_pubkey` is the wallet endpoint's ephemeral Nostr public key - `session_identifier` is the [session identifier](#session-identifier-derivation) derived from the association token - `dapp_nostr_pubkey` is the dapp endpoint's Nostr public key, obtained from the [Nostr URI](#nostr-uri) #### Description When a [Nostr relay](#nostr) is in use for connections, the `CONNECT` event signals that the wallet endpoint has connected to the relay and is ready to begin session establishment. It serves an analogous role to [`APP_PING`](#app_ping) — notifying the dapp endpoint that the counterparty is available. The `CONNECT` event's `pubkey` field reveals the wallet endpoint's Nostr public key to the dapp endpoint, enabling subsequent events to include a `p` tag for directed routing. On receipt of the `CONNECT` event, the dapp endpoint should verify that the event's `d` tag matches the expected session identifier, record the wallet endpoint's Nostr public key from the event's `pubkey` field, and proceed to send [`HELLO_REQ`](#hello_req). If additional `CONNECT` events are received from different Nostr public keys for the same session, they should be ignored — only the first valid `CONNECT` event is processed. ### SESSION_END #### Direction Dapp endpoint to wallet endpoint #### Specification A [Nostr event](#nostr-event-structure) of kind `20012` with a `msg` tag with value `SESSION_END` and an empty `content` field: ```json { "kind": 20012, "pubkey": "", "tags": [ ["d", ""], ["p", ""], ["msg", "SESSION_END"], ], "content": "" } ``` where: - `dapp_nostr_pubkey` is the dapp endpoint's Nostr public key, obtained from the [Nostr URI](#nostr-uri) - `wallet_nostr_pubkey` is the wallet endpoint's ephemeral Nostr public key - `session_identifier` is the [session identifier](#session-identifier-derivation) derived from the association token #### Description When a [Nostr relay](#nostr) is in use, the dapp endpoint may send a `SESSION_END` message to notify the wallet endpoint that the dapp is terminating the session. Nostr relays do not provide a way for one endpoint to be notified when another subscriber disconnects or unsubscribes. This message serves an analogous role to the explicit closure mechanics used in local and reflector websockets where endpoint closures can be propogated to the opposite party. When the wallet endpoint receives a `SESSION_END` message it should immediately close its Nostr relay connection and end the MWA session cleany. ### HELLO_REQ #### Direction Dapp endpoint to wallet endpoint #### Specification ``` ``` where: - `Qd`: the X9.62-encoded dapp endpoint ephemeral ECDH public keypoint - `Sa`: a P1363-encoded ECDSA-SHA256 signature of Qd using the [association keypair](#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](#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 ``` ``` where: - `Qw`: the X9.62-encoded wallet endpoint ephemeral ECDH public keypoint - `session_props`: an [encrypted message](#encrypted-message-wrapping) containing the session properties JSON payload, as described below. ##### Session Properties ``` { "v":"" } ``` 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](#protocol-version-negotiation) #### 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 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](#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](#encrypted-message-wrapping), using the ECDH (as specified by [NIST SP 800-56A](https://csrc.nist.gov/publications/detail/sp/800-56a/rev-3/final)) and HKDF (as specified by [RFC5869](https://datatracker.ietf.org/doc/html/rfc5869)) algorithms with the following KDF parameters: - `ikm`: the 32-byte ECDH-derived secret - `salt`: the 65 byte X9.62-encoded public keypoint Qa of the [association keypair](#association-keypair) - `L`: 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](#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](#session-establishment) completes, the wallet endpoint is ready to accept [JSON-RPC 2.0](https://www.jsonrpc.org/specification) 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`](#authorize) method call. For details on how a session enters and exits an authorized state, see the [non-privileged methods](#non-privileged-methods). ### Encrypted message wrapping After the [session establishment](#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](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf)). 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](#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](#session-establishment) and verifying the authentication tag, the receiving endpoint should further interpret it as a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) 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 {: .no_toc } ``` authorize ``` ###### Params {: .no_toc } ``` { “identity”: { “uri”: “”, “icon”: “”, “name”: “”, }, "chain": "", "features": ["", ...], "addresses": ["
", ...], “auth_token”: “”, "sign_in_payload": , "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 (from `uri`) to an image asset file of an icon identifying the dapp endpoint making this authorization request - `name`: (optional) the display name for this dapp endpoint - `chain`: (optional) if set, the [chain identifier](#chain-identifiers) for the chain with which the dapp endpoint intends to interact; supported values include `solana:mainnet`, `solana:testnet`, `solana:devnet`, `mainnet-beta`, `testnet`, `devnet`. If not set, defaults to `solana: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 to `null`. - `features`: (optional) if set, a list of [feature identifiers](#feature-identifiers) that the dapp endpoint intends to use in the session. Defaults to `null`. - `auth_token`: (optional) an opaque string previously returned by a call to [`authorize`](#authorize), or [`clone_authorization`](#clone_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](https://github.com/phantom/sign-in-with-solana?tab=readme-ov-file#sign-in-input-fields). 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 for `chain`. This parameter is maintained for backwards compatibility with previous versions of the spec, and will be ignored if the `chain` parameter is present. ###### Result {: .no_toc } ``` { “auth_token”: “”, “accounts”: [ { “address”: “
", "display_address": "", "display_address_format": "", “label”: “