Konten ini sedang diterjemahkan dan akan tersedia di sini ketika siap.
Reflector Protocol & Debugging
This final lesson covers two critical topics: the reflector protocol for remote connections, and practical debugging techniques for MWA sessions.
Reflector Architecture
The reflector enables dApps on one device (laptop, desktop) to connect to wallets on another (phone). It's a WebSocket relay server that forwards encrypted messages between endpoints.
+---------------+ +----------------+ +---------------+
| Web dApp |------->| Reflector |<-------| Mobile |
| (laptop) | WSS | Server | WSS | Wallet |
+---------------+ +----------------+ +---------------+Design Principles
The reflector is intentionally simple:
Untrusted: All message content is encrypted. The reflector sees only ciphertext.
Stateless: No user accounts, no persistent storage of messages.
Limited: Enforces timeouts and size limits.
Replaceable: Any standards-compliant reflector works.
Reflector Protocol Messages
The reflector uses a simple message format. Unlike some protocols, messages don't all have type prefixes; the format depends on context:
Reflector-to-dApp Messages
| Message | Format | Description |
REFLECTOR_ID | <length><id_bytes> | Your assigned session ID (length is varint-encoded) |
APP_PING | Empty (0 bytes) | Both endpoints connected, proceed with handshake |
Message Formats
REFLECTOR_ID (reflector -> dApp):
+------------------------------------------------+
| varint length | ID bytes (opaque) |
+------------------------------------------------+The ID is an opaque byte sequence, base64url-encoded when placed in the association URI.
APP_PING (reflector -> both endpoints):
+--------------------------------------+
| (empty message - 0 bytes) |
+--------------------------------------+Sent to both endpoints when the counterparty connects. The dApp should wait for this before sending HELLO_REQ.
Message Forwarding
After APP_PING, the reflector becomes transparent. Messages are forwarded without any wrapper:
dApp sends: [encrypted message bytes]
Reflector forwards: [encrypted message bytes] (unchanged)
Wallet receives: [encrypted message bytes]Reflector Connection Flow
Complete flow for a remote connection:
Time | dApp | Reflector | Wallet
------+-------------------------+-------------------------+------------------
t0 | Connect to | |
| wss://reflector/reflect | |
|------------------------>| |
| | |
t1 |<------------------------| |
| REFLECTOR_ID: <len><id> | |
| | |
t2 | Generate QR code with: | |
| - association token | |
| - id=<base64url(id)> | |
| - reflector hostname | |
| | |
t3 | | | User scans QR
| | |
t4 | |<------------------------| Connect to
| | wss://reflector/reflect |
| | ?id=<base64url(id)> |
| | |
t5 |<------------------------|------------------------>|
| APP_PING (empty) | APP_PING (empty) |
| | |
t6 | Send HELLO_REQ | |
|------------------------>| |
| | Forward (transparent) |
| |------------------------>|
| | | Parse HELLO_REQ
t7 | |<------------------------| Send HELLO_RSP
| | Forward (transparent) |
|<------------------------| |
| | |
t8 | (Session established) | |
| (Encrypted from here) | |Reflector Timeouts
| State | Timeout | Action |
| dApp connected, waiting for wallet | 30 seconds | Close dApp connection |
| Session active, no messages | 90 seconds | Close both connections |
| Single message size | 4096 bytes | Close connection |
Handling Timeouts
// The SDK handles timeouts internally, but you can configure
await transact(async (wallet) => {
// Your operations here
}, {
// Some SDKs allow timeout configuration
sessionTimeout: 30000 // milliseconds
});If a timeout occurs, you'll receive an error:
try {
await transact(...);
} catch (error) {
if (error.message.includes('timeout')) {
// User took too long or network issues
}
}QR Code Format
For remote connections, the dApp displays a QR code containing the association URI:
solana-wallet:/v1/associate/remote
?association=BASE64URL_ENCODED_PUBLIC_KEY
&id=BASE64URL_ENCODED_REFLECTOR_ID
&reflector=reflect.myapp.comRendering the QR Code
import QRCode from 'react-qr-code';
function WalletConnectQR({ uri }: { uri: string }) {
return (
<div style={{ background: 'white', padding: 16 }}>
<QRCode value={uri} size={256} />
</div>
);
}Security Considerations
The QR code contains the association public key; keep it visible only to the intended user
The reflector ID is not secret but helps pair the connection
Anyone who scans the QR can attempt to connect (race condition possible)
Running Your Own Reflector
The official reflector runs at wss://reflect.solanamobile.com. You can run your own:
Why Run Your Own?
Reliability: Not dependent on third-party service
Latency: Deploy close to your users
Privacy: Your traffic doesn't go through external servers
Customization: Adjust timeouts, logging, limits
Reference Implementation
The Solana Mobile team provides a reference reflector. Key components:
// Simplified reflector logic
class ReflectorSession {
id: string;
dAppSocket: WebSocket | null;
walletSocket: WebSocket | null;
createdAt: number;
forward(from: WebSocket, message: Buffer) {
const other = from === this.dAppSocket
? this.walletSocket
: this.dAppSocket;
if (other) {
const forwarded = Buffer.concat([
Buffer.from([0x02]), // FORWARDED_MESSAGE
message
]);
other.send(forwarded);
}
}
}Debugging MWA Sessions
When something goes wrong, systematic debugging helps isolate the issue.
Debug Layers
+-----------------------------------------+
| 4. Application Layer | <- Your dApp code
| - Transaction construction |
| - Response handling |
+-----------------------------------------+
| 3. RPC Layer | <- JSON-RPC methods
| - Method parameters |
| - Error codes |
+-----------------------------------------+
| 2. Session Layer | <- Encryption, keys
| - Handshake success |
| - Decrypt/encrypt |
+-----------------------------------------+
| 1. Transport Layer | <- WebSocket, reflector
| - Connection establishment |
| - Message delivery |
+-----------------------------------------+Debug from the bottom up: verify transport, then session, then RPC, then your application logic.
Transport Layer Debugging
Symptoms
"Connection failed"
"Connection refused"
"WebSocket error"
Checks
Is the wallet app running?
shellscript# Check if wallet is listening on Android adb shell netstat -tlnp | grep <port>Can you reach the reflector?
shellscriptcurl -v https://reflect.solanamobile.comIs the port in use?
shellscript# On the device adb shell netstat -tlnp | grep 49200
Logging WebSocket Events
const ws = new WebSocket(uri);
ws.onopen = () => console.log('[WS] Connected');
ws.onclose = (e) => console.log('[WS] Closed', e.code, e.reason);
ws.onerror = (e) => console.log('[WS] Error', e);
ws.onmessage = (e) => {
console.log('[WS] Message', e.data.byteLength, 'bytes');
};Session Layer Debugging
Symptoms
"Session establishment failed"
"Decryption error"
"Invalid signature"
Checks
Log the HELLO_REQ structure
typescriptconsole.log('HELLO_REQ:', { type: message[0], QdLength: 65, SaLength: 64, totalLength: message.length });Verify key formats
typescript// P-256 public key should be 65 bytes, starting with 0x04 console.log('Qd first byte:', Qd[0]); // Should be 4 console.log('Qd length:', Qd.length); // Should be 65Check signature verification manually
typescriptconst isValid = await crypto.subtle.verify( { name: 'ECDSA', hash: 'SHA-256' }, associationPublicKey, signature, Qd ); console.log('Signature valid:', isValid);Compare derived keys
typescript// Log session key (DEVELOPMENT ONLY!) console.log('Session key:', Array.from(sessionKey).map(b => b.toString(16)).join(''));
RPC Layer Debugging
Symptoms
"Method not found"
"Invalid params"
Specific error codes (-1 through -5)
Checks
Log raw JSON-RPC messages
typescriptconst request = { jsonrpc: '2.0', id: '1', method: 'authorize', params: { identity } }; console.log('Request:', JSON.stringify(request, null, 2));Validate transaction payloads
typescriptimport { Transaction } from '@solana/web3.js'; payloads.forEach((payload, i) => { try { const tx = Transaction.from(Buffer.from(payload, 'base64')); console.log(`Transaction ${i}:`, { signatures: tx.signatures.length, instructions: tx.instructions.length, recentBlockhash: tx.recentBlockhash }); } catch (e) { console.error(`Transaction ${i} invalid:`, e); } });Check capabilities before methods
typescriptconst caps = await wallet.getCapabilities(); console.log('Wallet capabilities:', caps); if (transactions.length > caps.max_transactions_per_request) { console.warn('Too many transactions!'); }
Application Layer Debugging
Common Issues
Transaction simulation failed
// Pre-simulate before sending to wallet
const simulation = await connection.simulateTransaction(transaction);
if (simulation.value.err) {
console.error('Simulation failed:', simulation.value.err);
console.log('Logs:', simulation.value.logs);
}Wrong account authorized
// Check you're using the right account
const result = await wallet.authorize(...);
console.log('Authorized accounts:', result.accounts.map(a => ({
address: a.display_address,
label: a.label
})));Stale blockhash
// Get fresh blockhash right before signing
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;Debug Tools
Android Debug Bridge (ADB)
# View MWA-related logs
adb logcat | grep -i "mwa\|wallet\|solana"
# Check network connections
adb shell netstat -tlnp
# Force-kill wallet to test reconnection
adb shell am force-stop com.phantom.walletReact Native Debugger
// Enable network inspection in React Native Debugger
// Or use Flipper for network logsCharles Proxy / mitmproxy
For reflector connections, proxy HTTPS traffic:
mitmproxy --mode upstream:https://reflect.solanamobile.comNote: You'll see encrypted MWA payloads, but can verify transport-level issues.
Custom Logging Wrapper
function wrapWithLogging<T extends (...args: any[]) => any>(
fn: T,
name: string
): T {
return (async (...args) => {
console.log(`[MWA] ${name} called with:`, args);
try {
const result = await fn(...args);
console.log(`[MWA] ${name} returned:`, result);
return result;
} catch (error) {
console.error(`[MWA] ${name} threw:`, error);
throw error;
}
}) as T;
}
// Use it
wallet.authorize = wrapWithLogging(wallet.authorize, 'authorize');Error Recovery Patterns
Retry with Backoff
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = baseDelay * Math.pow(2, attempt);
console.log(`Retry ${attempt + 1} after ${delay}ms`);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('Unreachable');
}
// Usage
const result = await withRetry(() =>
transact(wallet => wallet.authorize(identity))
);Session Recovery
let authToken: string | null = null;
async function ensureConnected() {
return transact(async (wallet) => {
try {
// Try reauthorization first
const result = await wallet.authorize({
identity,
auth_token: authToken
});
authToken = result.auth_token;
return result;
} catch (error) {
// Token invalid, clear and retry
authToken = null;
const result = await wallet.authorize({ identity });
authToken = result.auth_token;
return result;
}
});
}Production Monitoring
Track MWA health in production:
Metrics to Collect
Connection success rate: % of
transact()calls that succeedSession establishment time: Time from Intent to encrypted session
Authorization rate: % of authorization requests approved
Error distribution: Which error codes occur most often
Example Analytics
async function trackedTransact<T>(
callback: (wallet: WalletAPI) => Promise<T>
): Promise<T> {
const startTime = Date.now();
try {
const result = await transact(callback);
analytics.track('mwa_session_success', {
duration: Date.now() - startTime
});
return result;
} catch (error) {
analytics.track('mwa_session_error', {
error: error.message,
code: error.code,
duration: Date.now() - startTime
});
throw error;
}
}Course Summary
You've now explored the MWA protocol from the bottom up:
Architecture: Three layers (transport, session, RPC) and two roles (dApp, wallet)
Transport: WebSocket over localhost or via reflector
Association: URI format, version negotiation, keypair purposes
Session Establishment: ECDH handshake, HKDF key derivation
Encrypted Messages: AES-128-GCM with sequence numbers
JSON-RPC Methods: authorize, sign_and_send_transactions, and more
Identity Verification: Digital Asset Links and attestation
Reflector & Debugging: Remote connections and troubleshooting
With this knowledge, you can:
Debug MWA issues at any layer
Implement wallet-side MWA support
Audit MWA implementations for security
Extend the protocol for custom use cases
The official MWA specification is your definitive reference. Now you can read it with full understanding.
Happy building on Solana Mobile.