unitysdk.mp4
Unity SDK for Solana Mobile Wallet Adapter (MWA) 2.0, providing full API parity with the React Native MWA SDK. Build Android games and apps that connect to Solana wallets like Phantom and Solflare.
Mobile Wallet Adapter (MWA) is a protocol by Solana Mobile that enables Android dApps to communicate with wallet apps installed on the same device. Instead of bundling wallet functionality into your app, MWA leverages the user's existing wallet for transaction signing and authorization.
This SDK wraps the official mobile-wallet-adapter-clientlib-ktx Android library and exposes it to Unity C# through a JNI bridge, following the same architecture as the Godot MWA SDK.
- Full MWA 2.0 API parity with the React Native SDK
- All wallet methods: Authorize, Reauthorize, Deauthorize, Sign Transactions, Sign & Send Transactions, Sign Messages, Get Capabilities
- Sign In With Solana (SIWS) support via
SignInPayload - Extensible authorization cache — built-in
FileMWACache+ customIMWACacheinterface - Disconnect & Reconnect — easily manage wallet sessions with cache persistence
- Event-driven API — C# events for all async operations
- Cluster selection — Devnet, Mainnet, Testnet
- Singleton pattern —
MobileWalletAdapter.Instancefor easy global access - Zero external Unity dependencies — works with any Unity 2021.3+ project
Add via Unity Package Manager using the git URL:
Window > Package Manager > + > Add package from git URL:
https://github.com/nicoeseworthy/Unity-MWA-SDK.git
- File > Build Profiles > Select Android > Switch Platform
- Player Settings > Other Settings:
- Minimum API Level: Android 7.0 (API 24)
- Target API Level: Automatic (highest installed)
- Player Settings > Publishing Settings:
- Enable Custom Main Gradle Template
- Enable Custom Gradle Properties Template
- Enable Custom Gradle Settings Template
- Add MWA dependencies to
Assets/Plugins/Android/mainTemplate.gradle:dependencies { implementation 'com.solanamobile:mobile-wallet-adapter-clientlib-ktx:2.0.3' implementation 'com.solanamobile:rpc-core:0.2.8' implementation 'androidx.activity:activity-compose:1.8.2' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' implementation 'androidx.compose.ui:ui:1.5.4' implementation 'androidx.compose.material:material:1.5.4' } - Add to
Assets/Plugins/Android/gradleTemplate.properties:android.useAndroidX=true android.enableJetifier=true
using Solana.MWA;
using UnityEngine;
public class WalletManager : MonoBehaviour
{
private MobileWalletAdapter mwa;
void Start()
{
mwa = MobileWalletAdapter.Instance;
// Configure your dApp identity (shown to user in wallet)
mwa.Identity = new DappIdentity(
"My Game",
"https://mygame.com",
"favicon.ico"
);
mwa.ActiveCluster = Cluster.Devnet;
// Listen for events
mwa.OnAuthorized += OnWalletConnected;
mwa.OnAuthorizationFailed += OnWalletFailed;
mwa.OnDeauthorized += () => Debug.Log("Disconnected");
}
public void ConnectWallet()
{
mwa.Authorize();
}
void OnWalletConnected(AuthorizationResult result)
{
Debug.Log($"Connected! Account: {result.Accounts[0].Address}");
Debug.Log($"Auth token: {result.AuthToken}");
}
void OnWalletFailed(MWAErrorCode code, string message)
{
Debug.LogError($"Failed ({code}): {message}");
}
}Build to an Android device with a MWA-compatible wallet installed (Phantom, Solflare).
The main entry point. Access via MobileWalletAdapter.Instance (singleton) or attach to a GameObject.
| Property | Type | Description |
|---|---|---|
Identity |
DappIdentity |
dApp identity shown to user in wallet prompt |
ActiveCluster |
Cluster |
Blockchain cluster (Devnet, Mainnet, Testnet) |
AuthCache |
IMWACache |
Authorization cache (default: FileMWACache) |
State |
ConnectionState |
Current state (Disconnected, Connecting, Connected, Signing, Deauthorizing) |
CurrentAuth |
AuthorizationResult |
Current authorization (accounts, auth token) |
Capabilities |
WalletCapabilities |
Last queried wallet capabilities |
IsAuthorized |
bool |
Has valid authorization |
IsConnected |
bool |
Connected with active session |
| Method | React Native Equivalent | Description |
|---|---|---|
Authorize(signInPayload?) |
wallet.authorize() |
Connect to wallet, optionally with SIWS |
Reconnect() |
wallet.reauthorize() |
Restore session from cached auth token |
Deauthorize() |
wallet.deauthorize() |
Revoke authorization, clear cache |
Disconnect() |
— | Alias for Deauthorize() |
| Method | React Native Equivalent | Description |
|---|---|---|
SignTransactions(byte[][] payloads) |
wallet.signTransactions() |
Sign without broadcasting |
SignAndSendTransactions(byte[][] payloads, SendOptions options?) |
wallet.signAndSendTransactions() |
Sign and broadcast to network |
SignMessages(byte[][] messages, byte[][] addresses?) |
wallet.signMessages() |
Sign arbitrary off-chain messages |
| Method | React Native Equivalent | Description |
|---|---|---|
GetCapabilities() |
wallet.getCapabilities() |
Query wallet features and limits |
GetAccount() |
— | Get primary authorized account |
GetAccounts() |
— | Get all authorized accounts |
GetPublicKey() |
— | Get primary account public key |
SetCache(IMWACache) |
— | Replace cache at runtime |
| Event | Signature | Description |
|---|---|---|
OnAuthorized |
Action<AuthorizationResult> |
Wallet authorized successfully |
OnAuthorizationFailed |
Action<MWAErrorCode, string> |
Authorization rejected or failed |
OnDeauthorized |
Action |
Session ended |
OnDeauthorizationFailed |
Action<string> |
Deauthorization failed |
OnCapabilitiesReceived |
Action<WalletCapabilities> |
Capabilities query completed |
OnTransactionsSigned |
Action<byte[][]> |
Transactions signed |
OnTransactionsSignFailed |
Action<MWAErrorCode, string> |
Signing failed |
OnTransactionsSent |
Action<string[]> |
Transactions sent (signatures) |
OnTransactionsSendFailed |
Action<MWAErrorCode, string> |
Send failed |
OnMessagesSigned |
Action<byte[][]> |
Messages signed |
OnMessagesSignFailed |
Action<MWAErrorCode, string> |
Message signing failed |
OnAuthorizationCloned |
Action<string> |
Auth cloned (new token) |
OnCloneFailed |
Action<string> |
Clone failed |
OnStateChanged |
Action<ConnectionState> |
Connection state changed |
new DappIdentity(
name: "My App", // Display name in wallet
uri: "https://app.com", // Your dApp URI
icon: "favicon.ico" // Icon URI
)new SendOptions {
Commitment = "confirmed", // "processed", "confirmed", "finalized"
MinContextSlot = -1, // -1 = unset
SkipPreflight = false,
MaxRetries = -1 // -1 = unset
}new SignInPayload {
Domain = "mygame.com",
Statement = "Sign in to My Game",
// Optional: Uri, Version, ChainId, Nonce, IssuedAt, ExpirationTime, etc.
}| Code | Name | Description |
|---|---|---|
| -1 | AuthorizationFailed |
Wallet rejected authorization |
| -2 | InvalidPayloads |
Invalid transaction payloads |
| -3 | NotSigned |
Signing declined or failed |
| -4 | NotSubmitted |
Transaction submission failed |
| -5 | NotCloned |
Clone authorization failed |
| -6 | TooManyPayloads |
Exceeds wallet's max per request |
| -7 | ClusterNotSupported |
Wallet doesn't support requested cluster |
| -100 | AttestOriginAndroid |
Android origin attestation error |
The SDK includes an extensible cache layer that persists auth tokens across app restarts.
Stores authorization as JSON at Application.persistentDataPath/mwa_auth_cache.json. Works automatically — no configuration needed.
Implement IMWACache for custom storage (PlayerPrefs, SQLite, cloud, etc.):
public class MyCache : IMWACache
{
public AuthorizationResult GetAuthorization() { ... }
public void SetAuthorization(AuthorizationResult auth) { ... }
public void Clear() { ... }
public bool HasAuthorization() { ... }
}
// Use it:
MobileWalletAdapter.Instance.SetCache(new MyCache());| Event | Action |
|---|---|
Authorize() succeeds |
SetAuthorization() persists the token |
Deauthorize() called |
Clear() removes cached data |
| App starts | GetAuthorization() restores session |
Reconnect() called |
Loads cached token, then reauthorizes with wallet |
// After authorization...
byte[] transactionBytes = BuildYourSolanaTransaction();
mwa.OnTransactionsSigned += (signedPayloads) => {
Debug.Log($"Signed {signedPayloads.Length} transactions");
};
mwa.OnTransactionsSignFailed += (code, msg) => {
Debug.LogError($"Sign failed: {msg}");
};
mwa.SignTransactions(new byte[][] { transactionBytes });mwa.OnTransactionsSent += (signatures) => {
Debug.Log($"Transaction signature: {signatures[0]}");
};
mwa.SignAndSendTransactions(
new byte[][] { transactionBytes },
new SendOptions { Commitment = "confirmed" }
);byte[] message = System.Text.Encoding.UTF8.GetBytes("Hello Solana!");
mwa.OnMessagesSigned += (signatures) => {
Debug.Log($"Message signed!");
};
mwa.SignMessages(new byte[][] { message });// On app start, check for cached session
if (mwa.AuthCache.HasAuthorization())
{
mwa.Reconnect(); // Reauthorizes with wallet using cached token
}mwa.Authorize(new SignInPayload {
Domain = "mygame.com",
Statement = "Sign in to My Game"
});| React Native Method | Unity Method | Status |
|---|---|---|
transact(callback) |
N/A (managed internally) | N/A |
wallet.authorize() |
Authorize() |
Done |
wallet.reauthorize() |
Reconnect() |
Done |
wallet.deauthorize() |
Deauthorize() / Disconnect() |
Done |
wallet.getCapabilities() |
GetCapabilities() |
Done |
wallet.signTransactions() |
SignTransactions() |
Done |
wallet.signAndSendTransactions() |
SignAndSendTransactions() |
Done |
wallet.signMessages() |
SignMessages() |
Done |
wallet.cloneAuthorization() |
CloneAuthorization() |
Done |
AuthorizationResultCache |
IMWACache + FileMWACache |
Done |
| Sign In With Solana (SIWS) | SignInPayload on Authorize() |
Done |
Unity C# Layer Android Native Layer (Kotlin)
+---------------------+ +---------------------------+
| MobileWalletAdapter | --JNI--> | MWABridge.kt (static) |
| (MonoBehaviour) | | MWAClient.kt (coroutines) |
+---------------------+ | MWAInitProvider.kt |
| MWATypes.cs | | Uses: |
| IMWACache.cs | | clientlib-ktx:2.0.3 |
| FileMWACache.cs | +---------------------------+
| MWASession.cs |
+---------------------+
- MWAInitProvider — Android
ContentProviderthat createsActivityResultSenderduring app startup (before Activity reaches STARTED state, as required by MWA) - MWABridge — Static JNI entry point called from Unity C# via
AndroidJavaClass - MWAClient — Coroutine-based async operations with polling state (status, resultJson, errorMessage)
- MobileWalletAdapter — Unity
MonoBehaviourthat polls the bridge inUpdate()and fires C# events
The SDK includes a complete example app demonstrating all API methods:
- Cluster selection (Devnet / Mainnet / Testnet)
- Authorize / Deauthorize / Reconnect
- Get Capabilities
- Sign Transactions
- Sign & Send Transactions
- Sign Messages
- Clone Authorization
- Cache management (load from cache, clear cache)
- Real-time output log
Import via Package Manager > Solana Mobile Wallet Adapter > Samples > Basic Example, then run Solana > MWA > Create Example Scene.
If you need to rebuild the Kotlin plugin from source:
cd Android~
# Place Unity's classes.jar in libs/
cp <Unity>/PlaybackEngines/AndroidPlayer/Variations/il2cpp/Release/Classes/classes.jar libs/
# Generate Gradle wrapper (if needed)
gradle wrapper --gradle-version 8.2
# Build
./gradlew assembleRelease
# Copy output
cp build/outputs/aar/SolanaMWAUnityPlugin-release.aar ../Runtime/Plugins/Android/solana-mwa-bridge.aar| Document | Description |
|---|---|
| Getting Started | Overview and 5-minute quickstart |
| Installation | Detailed setup and troubleshooting |
| API Reference | Complete API documentation |
| Cache Layer | Cache customization guide |
| Migration from React Native | Side-by-side API mapping |
- Unity 2021.3 or later
- Android Build Support module
- Android API 24+ (Android 7.0 Nougat)
- MWA-compatible wallet app on device (Phantom, Solflare)
Apache License 2.0. See LICENSE.