Protocolo Reflector e Depuração
Esta última lição aborda dois tópicos críticos: o protocolo reflector para conexões remotas, e técnicas práticas de depuração para sessões MWA.
Arquitetura do Reflector
O reflector permite que dApps em um dispositivo (laptop, desktop) se conectem a carteiras em outro (celular). É um servidor de retransmissão WebSocket que encaminha mensagens criptografadas entre endpoints.
+---------------+ +----------------+ +---------------+
| Web dApp |------->| Reflector |<-------| Mobile |
| (laptop) | WSS | Server | WSS | Wallet |
+---------------+ +----------------+ +---------------+Princípios de Design
O reflector é intencionalmente simples:
Não confiável: Todo o conteúdo das mensagens é criptografado. O reflector vê apenas texto cifrado.
Sem estado: Sem contas de usuário, sem armazenamento persistente de mensagens.
Limitado: Impõe timeouts e limites de tamanho.
Substituível: Qualquer reflector compatível com os padrões funciona.
Mensagens do Protocolo Reflector
O reflector usa um formato de mensagem simples. Diferente de alguns protocolos, as mensagens nem todas têm prefixos de tipo; o formato depende do contexto:
Mensagens do Reflector para dApp
Formatos de Mensagem
REFLECTOR_ID (reflector -> dApp):
+------------------------------------------------+
| varint length | ID bytes (opaque) |
+------------------------------------------------+O ID é uma sequência de bytes opaca, codificada em base64url quando colocada no URI de associação.
APP_PING (reflector -> ambos os endpoints):
+--------------------------------------+
| (empty message - 0 bytes) |
+--------------------------------------+Enviado a ambos os endpoints quando a contraparte se conecta. A dApp deve aguardar isto antes de enviar HELLO_REQ.
Encaminhamento de Mensagens
Após o APP_PING, o reflector torna-se transparente. As mensagens são encaminhadas sem qualquer wrapper:
dApp sends: [encrypted message bytes]
Reflector forwards: [encrypted message bytes] (unchanged)
Wallet receives: [encrypted message bytes]Fluxo de Conexão do Reflector
Fluxo completo para uma conexão remota:
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) | |Timeouts do Reflector
Tratando Timeouts
// O SDK trata dos timeouts internamente, mas você pode configurar
await transact(async (wallet) => {
// Suas operações aqui
}, {
// Alguns SDKs permitem configuração de timeout
sessionTimeout: 30000 // milissegundos
});Se ocorrer um timeout, você receberá um erro:
try {
await transact(...);
} catch (error) {
if (error.message.includes('timeout')) {
// O usuário demorou muito ou problemas de rede
}
}Formato do Código QR
Para conexões remotas, a dApp exibe um código QR contendo o URI de associação:
solana-wallet:/v1/associate/remote
?association=BASE64URL_ENCODED_PUBLIC_KEY
&id=BASE64URL_ENCODED_REFLECTOR_ID
&reflector=reflect.myapp.comRenderizando o Código QR
import QRCode from 'react-qr-code';
function WalletConnectQR({ uri }: { uri: string }) {
return (
<div style={{ background: 'white', padding: 16 }}>
<QRCode value={uri} size={256} />
</div>
);
}Considerações de Segurança
O código QR contém a chave pública de associação; mantenha-o visível apenas para o usuário pretendido
O ID do reflector não é secreto, mas ajuda a parear a conexão
Qualquer pessoa que escanear o QR pode tentar se conectar (condição de corrida possível)
Executando Seu Próprio Reflector
O reflector oficial roda em wss://reflect.solanamobile.com. Você pode rodar o seu:
Por Que Rodar o Seu Próprio?
Confiabilidade: Sem dependência de serviço de terceiros
Latência: Implante perto dos seus usuários
Privacidade: Seu tráfego não passa por servidores externos
Customização: Ajuste timeouts, logging, limites
Implementação de Referência
A equipe da Solana Mobile fornece um reflector de referência. Componentes principais:
// Lógica simplificada do reflector
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);
}
}
}Depurando Sessões MWA
Quando algo dá errado, a depuração sistemática ajuda a isolar o problema.
Camadas de Depuração
+-----------------------------------------+
| 4. Camada de Aplicação | <- Código da sua dApp
| - Construção de transações |
| - Tratamento de respostas |
+-----------------------------------------+
| 3. Camada RPC | <- Métodos JSON-RPC
| - Parâmetros de métodos |
| - Códigos de erro |
+-----------------------------------------+
| 2. Camada de Sessão | <- Criptografia, chaves
| - Sucesso do handshake |
| - Decrypt/encrypt |
+-----------------------------------------+
| 1. Camada de Transporte | <- WebSocket, reflector
| - Estabelecimento de conexão |
| - Entrega de mensagens |
+-----------------------------------------+Depure de baixo para cima: verifique o transporte, depois a sessão, depois RPC, depois sua lógica de aplicação.
Depuração da Camada de Transporte
Sintomas
"Connection failed"
"Connection refused"
"WebSocket error"
Verificações
O app da carteira está rodando?
shellscript# Verifique se a carteira está escutando no Android adb shell netstat -tlnp | grep <port>Consegue acessar o reflector?
shellscriptcurl -v https://reflect.solanamobile.comA porta está em uso?
shellscript# No dispositivo adb shell netstat -tlnp | grep 49200
Registrando Eventos WebSocket
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');
};Depuração da Camada de Sessão
Sintomas
"Session establishment failed"
"Decryption error"
"Invalid signature"
Verificações
Registre a estrutura do HELLO_REQ
typescriptconsole.log('HELLO_REQ:', { type: message[0], QdLength: 65, SaLength: 64, totalLength: message.length });Verifique os formatos de chave
typescript// Chave pública P-256 deve ter 65 bytes, começando com 0x04 console.log('Qd first byte:', Qd[0]); // Deve ser 4 console.log('Qd length:', Qd.length); // Deve ser 65Verifique a verificação de assinatura manualmente
typescriptconst isValid = await crypto.subtle.verify( { name: 'ECDSA', hash: 'SHA-256' }, associationPublicKey, signature, Qd ); console.log('Signature valid:', isValid);Compare as chaves derivadas
typescript// Registre a chave de sessão (APENAS EM DESENVOLVIMENTO!) console.log('Session key:', Array.from(sessionKey).map(b => b.toString(16)).join(''));
Depuração da Camada RPC
Sintomas
"Method not found"
"Invalid params"
Códigos de erro específicos (-1 a -5)
Verificações
Registre mensagens JSON-RPC brutas
typescriptconst request = { jsonrpc: '2.0', id: '1', method: 'authorize', params: { identity } }; console.log('Request:', JSON.stringify(request, null, 2));Valide os payloads das transações
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); } });Verifique as funcionalidades antes dos métodos
typescriptconst caps = await wallet.getCapabilities(); console.log('Wallet capabilities:', caps); if (transactions.length > caps.max_transactions_per_request) { console.warn('Too many transactions!'); }
Depuração da Camada de Aplicação
Problemas Comuns
Simulação de transação falhou
// Pré-simule antes de enviar à carteira
const simulation = await connection.simulateTransaction(transaction);
if (simulation.value.err) {
console.error('Simulation failed:', simulation.value.err);
console.log('Logs:', simulation.value.logs);
}Conta autorizada incorreta
// Verifique se você está usando a conta certa
const result = await wallet.authorize(...);
console.log('Authorized accounts:', result.accounts.map(a => ({
address: a.display_address,
label: a.label
})));Blockhash desatualizado
// Obtenha um blockhash fresco logo antes de assinar
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;Ferramentas de Depuração
Android Debug Bridge (ADB)
# Visualize logs relacionados ao MWA
adb logcat | grep -i "mwa\|wallet\|solana"
# Verifique conexões de rede
adb shell netstat -tlnp
# Force a parada da carteira para testar reconexão
adb shell am force-stop com.phantom.walletReact Native Debugger
// Ative a inspeção de rede no React Native Debugger
// Ou use o Flipper para logs de redeCharles Proxy / mitmproxy
Para conexões via reflector, faça proxy do tráfego HTTPS:
mitmproxy --mode upstream:https://reflect.solanamobile.comNota: Você verá payloads MWA criptografados, mas pode verificar problemas em nível de transporte.
Wrapper de Logging Personalizado
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-o
wallet.authorize = wrapWithLogging(wallet.authorize, 'authorize');Padrões de Recuperação de Erros
Retry com 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');
}
// Uso
const result = await withRetry(() =>
transact(wallet => wallet.authorize(identity))
);Recuperação de Sessão
let authToken: string | null = null;
async function ensureConnected() {
return transact(async (wallet) => {
try {
// Tente reautorização primeiro
const result = await wallet.authorize({
identity,
auth_token: authToken
});
authToken = result.auth_token;
return result;
} catch (error) {
// Token inválido, limpe e tente novamente
authToken = null;
const result = await wallet.authorize({ identity });
authToken = result.auth_token;
return result;
}
});
}Monitoramento em Produção
Acompanhe a saúde do MWA em produção:
Métricas para Coletar
Taxa de sucesso de conexão: % de chamadas
transact()que tiveram sucessoTempo de estabelecimento de sessão: Tempo do Intent até sessão criptografada
Taxa de autorização: % de solicitações de autorização aprovadas
Distribuição de erros: Quais códigos de erro ocorrem com mais frequência
Exemplo de 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;
}
}Resumo do Curso
Agora você explorou o protocolo MWA de baixo para cima:
Arquitetura: Três camadas (transporte, sessão, RPC) e dois papéis (dApp, carteira)
Transporte: WebSocket via localhost ou via reflector
Associação: Formato URI, negociação de versão, propósitos do keypair
Estabelecimento de Sessão: Handshake ECDH, derivação de chaves HKDF
Mensagens Criptografadas: AES-128-GCM com números de sequência
Métodos JSON-RPC: authorize, sign_and_send_transactions, e mais
Verificação de Identidade: Digital Asset Links e atestado
Reflector e Depuração: Conexões remotas e solução de problemas
Com esse conhecimento, você pode:
Depurar problemas de MWA em qualquer camada
Implementar suporte MWA no lado da carteira
Auditar implementações MWA para segurança
Estender o protocolo para casos de uso personalizados
A especificação oficial do MWA é sua referência definitiva. Agora você pode lê-la com total compreensão.
Feliz desenvolvimento no Solana Mobile.