solana_kit_mobile_wallet_adapter_protocol
Pure Dart implementation of the Solana Mobile Wallet Adapter v2.0 protocol. It handles P-256 cryptography, session handshakes, encrypted message framing, JSON-RPC request/response encoding, association URI building/parsing, and protocol version negotiation.
The package has zero Flutter dependency and runs in server-side Dart, CLI tools, or any Dart environment.
Public-key import validates that uncompressed keys encode finite points on P-256. ECDH also checks the key domains and curve equation before multiplying by the private scalar. Malformed points, noncanonical coordinates, and mismatched curves throw ArgumentError.
createSiwsMessage rejects carriage returns and line feeds in scalar fields and resource entries with FormatException, preventing fields from injecting additional signed lines. Valid single-line messages remain unchanged.
What it covers
- P-256 ECDSA/ECDH cryptography via
pointycastle(pure Dart, cross-platform) - AES-128-GCM encryption with sequence number AAD for replay attack prevention
- HKDF-SHA256 key derivation with association public key as salt
- HELLO_REQ / HELLO_RSP handshake for session establishment
- JSON-RPC 2.0 message encryption/decryption
- Association URI building and parsing (local + remote)
- Wallet proxy with v1/legacy backwards compatibility
- Sign In With Solana (SIWS) message builder following EIP-4361
- JWS ES256 compact serialization for attestation
- Central error codes integrated with
solana_kit_errors
Installation
Install the package directly:
dependencies:
"solana_kit_mobile_wallet_adapter_protocol": ^0.5.2
If your app uses several Solana Kit packages together, you can also depend on the umbrella package instead:
dart pub add solana_kit
Inside this monorepo, Dart workspace resolution uses the local package automatically.
Documentation
- Package page: https://pub.dev/packages/solana_kit_mobile_wallet_adapter_protocol
- API reference: https://pub.dev/documentation/solana_kit_mobile_wallet_adapter_protocol/latest/
- Workspace docs: https://openbudgetfun.github.io/solana_kit/
- Package catalog entry: https://openbudgetfun.github.io/solana_kit/reference/package-catalog#solana_kit_mobile_wallet_adapter_protocol
- Source code: https://github.com/openbudgetfun/solana_kit/tree/main/packages/solana_kit_mobile_wallet_adapter_protocol
For architecture notes, getting-started guides, and cross-package examples, start with the workspace docs site and then drill down into the package README and API reference.
Usage
Association keypair generation
import 'package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart';
void main() {
// Generate keypairs for a new session.
final associationKeypair = generateAssociationKeypair();
final ecdhKeypair = generateEcdhKeypair();
// Get the association token (base64url-encoded public key).
final token = getAssociationToken(associationKeypair.publicKey);
print(token);
print(ecdhKeypair.publicKey);
}
Building association URIs
Wallet-specific baseUri paths are preserved with or without a trailing slash. Parsing identifies the final association endpoint, so wallet path prefixes containing local or remote do not change the association type.
import 'dart:typed_data';
import 'package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart';
void main() {
final associationKeypair = generateAssociationKeypair();
// Local association (same-device).
final uri = buildLocalAssociationUri(
associationKeypair.publicKey,
55123,
);
// -> solana-wallet:/v1/associate/local?association=...&port=55123
// Remote association (via reflector).
final reflectorId = Uint8List.fromList([1, 2, 3]);
final remoteUri = buildRemoteAssociationUri(
associationKeypair.publicKey,
'reflect.example.com',
reflectorId,
);
print(uri);
print(remoteUri);
}
HELLO handshake
import 'dart:typed_data';
import 'package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart';
void main() {
final associationKeypair = generateAssociationKeypair();
final ecdhKeypair = generateEcdhKeypair();
// Create HELLO_REQ (129 bytes: 65B ECDH pubkey + 64B ECDSA signature).
final helloReq = createHelloReq(ecdhKeypair, associationKeypair);
// Parse HELLO_RSP from the wallet.
final helloRspBytes = Uint8List(129);
final result = parseHelloRsp(helloRspBytes, associationKeypair, ecdhKeypair);
final sharedSecret = result.sharedSecret; // 16-byte AES key
print(helloReq.length);
print(sharedSecret);
}
Encrypted messaging
import 'dart:typed_data';
import 'package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart';
void main() {
final sharedSecret = Uint8List(16);
// Encrypt a JSON-RPC request.
final encrypted = encryptJsonRpcRequest(
1, // sequence number
'authorize',
{'chain': 'solana:mainnet'},
sharedSecret,
);
// Decrypt a JSON-RPC response.
final encryptedBytes = Uint8List(0);
final response = decryptJsonRpcResponse(encryptedBytes, sharedSecret);
print(encrypted);
print(response);
}
Wallet proxy
import 'package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart';
Future<void> main() async {
// Create a wallet proxy that handles v1/legacy compatibility.
final sendRequest = (String method, Map<String, Object?> params) async {
return <String, Object?>{};
};
final sessionProps = const SessionProperties(
protocolVersion: ProtocolVersion.v1,
);
final wallet = createMobileWalletProxy(sendRequest, sessionProps);
final authResult = await wallet.authorize({
'identity': {'name': 'My dApp'},
'chain': 'solana:mainnet',
});
print(authResult);
}
Protocol details
Wire format
Each encrypted message follows this binary format:
[4B sequence number (big-endian)] [12B random IV] [ciphertext + 16B GCM tag]
The 4-byte sequence number is also used as AAD (Additional Authenticated Data) for AES-GCM, preventing replay attacks.
Handshake flow
- dApp generates association keypair (P-256 ECDSA) and ECDH keypair (P-256)
- dApp sends HELLO_REQ:
[65B ECDH pubkey][64B ECDSA signature] - Wallet sends HELLO_RSP:
[65B wallet ECDH pubkey][optional encrypted session props] - Both sides derive shared secret via ECDH + HKDF-SHA256 -> 16-byte AES key
- All subsequent messages use AES-128-GCM with incrementing sequence numbers
Protocol versions
- v1: Uses chain identifiers (
solana:mainnet,solana:devnet,solana:testnet), feature arrays, and separatereauthorizemethod - legacy: Uses cluster names (
mainnet-beta,devnet,testnet), boolean feature flags (supports_sign_and_send_transactions), andauthorizewithauth_tokenfor reauthorization
Example
Use example/main.dart as a runnable starting point for solana_kit_mobile_wallet_adapter_protocol.
- Import path:
package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart - This section is centrally maintained with
mdtto keep package guidance aligned. - After updating shared docs templates, run
docs:updatefrom the repo root.
Maintenance
- Validate docs in CI and locally with
docs:check. - Keep examples focused on one workflow and reference package README sections for deeper API details.