solana_kit_mobile_wallet_adapter 0.5.3
solana_kit_mobile_wallet_adapter: ^0.5.3 copied to clipboard
Flutter plugin for the Solana Mobile Wallet Adapter protocol. Enables dApps to communicate with wallet apps for transaction signing on Android. iOS compiles but is a no-op.
solana_kit_mobile_wallet_adapter #
Flutter plugin for the Solana Mobile Wallet Adapter (MWA) protocol.
The plugin covers both sides of the protocol:
- dApp-side client flows: launch a wallet, establish a session, request signatures
- wallet-side server flows: receive authorize/sign requests from dApps
Platform support #
| Platform | dApp (client) | Wallet (server) |
|---|---|---|
| Android | Supported | Supported |
| iOS | No-op | No-op |
| Web | N/A | N/A |
Use isMwaSupported() / assertMwaSupported() before invoking MWA APIs.
Android-only Mobile Wallet Adapter
Real wallet handoff is available only on Android today.
On iOS,
solana_kit_mobile_wallet_adapterremains a safe stub/no-op because the current Solana MWA ecosystem does not expose an equivalent iOS integration target.Gate wallet-handoff flows with
isMwaSupported()/assertMwaSupported()and present a clear fallback such as browser-wallet instructions, a manual deep link path, or an explicit unsupported-platform message on iOS.
What this package includes #
dApp-side APIs #
transact()for simple one-call session lifecycleLocalAssociationScenariofor explicit same-device controlstartRemoteScenario()for reflector-based cross-device sessionsKitMobileWallettyped wrapper over the protocol wallet interface
wallet-side APIs #
WalletScenariolifecycle and request routingWalletScenarioCallbacksfor authorize/sign/deauthorize handling- Typed request objects (
AuthorizeDappRequest,SignTransactionsRequest, etc.) MwaDigitalAssetLinksHostApifor Android package verification
Installation #
flutter pub add solana_kit_mobile_wallet_adapter
Inside this monorepo, workspace dependency resolution is automatic.
dApp usage #
Simple lifecycle (transact) #
import 'package:solana_kit_mobile_wallet_adapter/solana_kit_mobile_wallet_adapter.dart';
import 'package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart';
Future<void> main() async {
const base64EncodedTransaction = 'base64-encoded-transaction';
final auth = await transact((wallet) async {
final authorizeResult = await wallet.authorize(
identity: const AppIdentity(name: 'My dApp'),
chain: 'solana:mainnet',
);
await wallet.signTransactions(
payloads: [base64EncodedTransaction],
);
return authorizeResult;
});
print(auth);
}
Manual local association #
import 'package:solana_kit_mobile_wallet_adapter/solana_kit_mobile_wallet_adapter.dart';
import 'package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart';
Future<void> main() async {
final scenario = LocalAssociationScenario();
try {
final rawWallet = await scenario.start();
final wallet = wrapWithKitApi(rawWallet);
final auth = await wallet.authorize(
identity: const AppIdentity(name: 'My dApp'),
chain: 'solana:devnet',
);
final signed = await wallet.signTransactions(
payloads: ['base64tx1', 'base64tx2'],
);
print(auth);
print(signed);
} finally {
await scenario.close();
}
}
Remote association (cross-device) #
startRemoteScenario resolves once a real reflector ID has been negotiated and a valid association URI is available.
import 'package:solana_kit_mobile_wallet_adapter/solana_kit_mobile_wallet_adapter.dart';
import 'package:solana_kit_mobile_wallet_adapter_protocol/solana_kit_mobile_wallet_adapter_protocol.dart';
Future<void> main() async {
final remote = await startRemoteScenario(
const RemoteWalletAssociationConfig(
reflectorHost: 'reflector.example.com',
),
);
// Display this as QR for wallet scan.
final uriForQr = remote.associationUri;
try {
final wallet = await remote.wallet;
await wallet.getCapabilities();
} finally {
remote.close();
}
print(uriForQr);
}
wallet usage #
Start a wallet scenario #
import 'package:solana_kit_mobile_wallet_adapter/solana_kit_mobile_wallet_adapter.dart';
class MyWalletCallbacks implements WalletScenarioCallbacks {
static const base64PublicKey =
'11111111111111111111111111111111';
List<String> signPayloads(List<String> payloads) => payloads;
@override
void onAuthorizeRequest(AuthorizeDappRequest request) {
request.completeWithAuthorize(
accounts: [
AuthorizedAccount(
address: base64PublicKey,
label: 'Main Account',
),
],
authToken: 'issued-token',
);
}
@override
void onSignTransactionsRequest(SignTransactionsRequest request) {
final signed = signPayloads(request.payloads);
request.completeWithSignedPayloads(signed);
}
@override
void onReauthorizeRequest(ReauthorizeDappRequest request) {
request.completeWithReauthorize(
accounts: [AuthorizedAccount(address: base64PublicKey)],
authToken: 'renewed-token',
);
}
@override
void onScenarioReady() {}
@override
void onScenarioServingClients() {}
@override
void onScenarioServingComplete() {}
@override
void onScenarioComplete() {}
@override
void onScenarioError(Object? error) {}
@override
void onScenarioTeardownComplete() {}
@override
void onSignMessagesRequest(SignMessagesRequest request) {}
@override
void onSignAndSendTransactionsRequest(SignAndSendTransactionsRequest request) {}
@override
void onDeauthorizedEvent(DeauthorizedEvent event) {}
}
Future<void> main() async {
final scenario = WalletScenario(
walletName: 'My Wallet',
config: const MobileWalletAdapterConfig(
maxTransactionsPerSigningRequest: 10,
optionalFeatures: ['solana:signTransactions'],
),
callbacks: MyWalletCallbacks(),
);
await scenario.start();
}
Digital Asset Links verification (wallet-side) #
import 'package:solana_kit_mobile_wallet_adapter/solana_kit_mobile_wallet_adapter.dart';
Future<void> main() async {
final dal = MwaDigitalAssetLinksHostApi();
final callingPackage = await dal.getCallingPackage();
final isVerified = await dal.verifyCallingPackage(
clientIdentityUri: 'https://example.com',
);
print(callingPackage);
print(isVerified);
}
This is useful when wallet policy requires Android app-origin verification before honoring sensitive requests.
Native parity and behavior notes #
- Android wallet-side implementation is backed by Solana Mobile
walletlibrequest/scenario APIs. - Request lifecycle is explicit:
- native request -> Dart callback ->
completeWith*-> native resolve/cancel
- native request -> Dart callback ->
- Each
WalletScenariocan be started once. Close the current scenario before creating another scenario on the same native method channel. Closing during native scenario creation also cancels startup and closes the native scenario. - Wallet callbacks only receive native events with the active scenario's session ID. Events with missing or different session IDs and queued events received after closure are ignored before authorization or signing callbacks run.
- Local/remote transport handling enforces inbound encrypted sequence ordering.
- Remote association supports reflector protocol negotiation (
binaryandbase64).
Maintenance and CI #
- Android native compile safety is enforced in CI by building a temporary Android Flutter app that depends on this plugin.
- Local equivalent command:
dart run scripts/check_mobile_wallet_adapter_android_compile.dart
Architecture #
- Dart: protocol/session handling via
solana_kit_mobile_wallet_adapter_protocol - Android Kotlin: intent launch + walletlib/DAL host bridges
- iOS Swift: safe no-op plugin for mixed-platform app compatibility
Manual testing app #
A runnable Android-first Flutter example app is available in example/.
It demonstrates explicit boundaries for platform support gating, wallet session state, message signing, and sign-and-send transaction handoff. On iOS, the example keeps the app shell alive and shows fallback UX instead of pretending wallet handoff is supported.
cd packages/solana_kit_mobile_wallet_adapter/example
flutter pub get
flutter run
For emulator/device wallet setup (including Solana's mock MWA wallet), follow:
Example #
Use example/main.dart as a runnable starting point for solana_kit_mobile_wallet_adapter.
- Import path:
package:solana_kit_mobile_wallet_adapter/solana_kit_mobile_wallet_adapter.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.
Mobile browsers #
On web the Mobile Wallet Adapter associates through the localhost WebSocket transport, which requires a secure context (HTTPS or localhost). The pairing sheet can appear before the wallet app is foreground — the session establishes as soon as the wallet app resumes. transact accepts a connectionTimeout (default: 30s on native, 3 minutes on web) and a launchIntent override.
To revoke a pairing, use the wallet app's linked-dApps settings or the example app's Deauthorize action.