Mobile
Desenvolvimento Mobile Solana: Tutorial React Native e MWA

Desenvolvimento Mobile Solana: Tutorial React Native e MWA

Tratamento de Erros

Tratamento de Erros

Conexões com carteiras móveis falham de maneiras que aplicativos desktop nunca veem. O app da carteira pode não estar instalado. O usuário pode sair antes de assinar. A sessão pode expirar no meio de uma transação. O Bluetooth em alguns dispositivos Saga pode interromper a conexão.

Um tratamento de erros robusto é o que separa dApps de produção de demos. Esta lição fornece padrões para cada modo de falha no MWA.

Não capture erros e mostre mensagens genéricas. Os erros do MWA dizem exatamente o que aconteceu: use essa informação.

Tipos de Erro do MWA

O SDK do MWA lança tipos de erro específicos. Cada um requer um tratamento diferente:

typescript
import {
  SolanaMobileWalletAdapterError,
  SolanaMobileWalletAdapterProtocolError,
} from "@solana-mobile/mobile-wallet-adapter-protocol";

SolanaMobileWalletAdapterError: Erros de nível de protocolo (sessão, transporte, falhas de associação)

SolanaMobileWalletAdapterProtocolError: Erros de nível da carteira (autorização negada, assinatura rejeitada)

Códigos de Erro do Protocolo

O MWA define códigos de erro específicos. O SDK encapsula erros de protocolo no formato JSON-RPC. Aqui está a referência completa:

Nota: A especificação bruta do protocolo MWA define constantes como ERROR_AUTHORIZATION_FAILED=***, mas o SDK apresenta esses erros como erros JSON-RPC na faixa de -32xxx`. A tabela abaixo mostra os códigos como aparecem nos objetos de erro JavaScript/TypeScript.

CódigoConstanteSignificadoAção do Usuário
-32700ERROR_JSON_RPC_PARSEJSON inválido na requisiçãoBug no seu código
-32600ERROR_JSON_RPC_INVALID_REQUESTRequisição RPC malformadaBug no seu código
-32601ERROR_JSON_RPC_METHOD_NOT_FOUNDMétodo desconhecido chamadoVerifique o nome do método
-32602ERROR_JSON_RPC_INVALID_PARAMSParâmetros inválidosVerifique os tipos dos parâmetros
-32603ERROR_JSON_RPC_INTERNALErro interno da carteiraTente novamente
-32000ERROR_AUTHORIZATION_FAILEDAutorização negada pelo usuárioUsuário cancelou. Respeite
-32001ERROR_INVALID_PAYLOADSFormato de transação inválidoVerifique a construção da transação
-32002ERROR_NOT_SIGNEDUsuário recusou assinarUsuário cancelou. Respeite
-32003ERROR_NOT_SUBMITTEDA carteira não conseguiu enviarVerifique a rede, tente novamente
-32004ERROR_NOT_CLONEDFalha no clone (descontinuado)Raro, tente novamente
-32005ERROR_TOO_MANY_PAYLOADSMuitas transaçõesDivida em lotes menores
-32010ERROR_ATTEST_ORIGIN_ANDROIDFalha na atestação de origemProblema de configuração do Android

Tratador de Erros Estruturado

Construa um tratador de erros centralizado:

typescript
// src/utils/mwaErrorHandler.ts
import {
  SolanaMobileWalletAdapterError,
  SolanaMobileWalletAdapterProtocolError,
} from "@solana-mobile/mobile-wallet-adapter-protocol";

export interface MWAErrorResult {
  userMessage: string;
  shouldRetry: boolean;
  isUserCancellation: boolean;
  originalError: Error;
}

const ERROR_MESSAGES: Record<string, string> = {
  ERROR_AUTHORIZATION_FAILED: "Conexão com a carteira cancelada",
  ERROR_NOT_SIGNED: "Assinatura da transação recusada",
  ERROR_NOT_SUBMITTED: "Não foi possível enviar a transação. Verifique sua conexão.",
  ERROR_TOO_MANY_PAYLOADS: "Muitas transações. Tente enviar menos.",
  ERROR_INVALID_PAYLOADS: "Formato de transação inválido",
  ERROR_ATTEST_ORIGIN_ANDROID: "Falha na verificação do app. Por favor, reinstale.",
};

export function handleMWAError(error: unknown): MWAErrorResult {
  // Não é um erro do MWA
  if (!(error instanceof Error)) {
    return {
      userMessage: "Ocorreu um erro inesperado",
      shouldRetry: true,
      isUserCancellation: false,
      originalError: new Error(String(error)),
    };
  }

  // Erro de nível de protocolo (resposta da carteira)
  if (error instanceof SolanaMobileWalletAdapterProtocolError) {
    const message = ERROR_MESSAGES[error.code] ?? error.message;
    const isUserCancellation =
      error.code === "ERROR_AUTHORIZATION_FAILED" || error.code === "ERROR_NOT_SIGNED";

    return {
      userMessage: message,
      shouldRetry: !isUserCancellation,
      isUserCancellation,
      originalError: error,
    };
  }

  // Erro de nível do SDK (associação, sessão)
  if (error instanceof SolanaMobileWalletAdapterError) {
    // Comum: nenhuma carteira instalada
    if (error.message.includes("Found no installed wallet")) {
      return {
        userMessage: "Nenhuma carteira Solana encontrada. Por favor, instale um app de carteira.",
        shouldRetry: false,
        isUserCancellation: false,
        originalError: error,
      };
    }

    // Timeout de sessão
    if (error.message.includes("timeout") || error.message.includes("Timeout")) {
      return {
        userMessage: "A conexão com a carteira expirou. Por favor, tente novamente.",
        shouldRetry: true,
        isUserCancellation: false,
        originalError: error,
      };
    }

    // Erro genérico do SDK
    return {
      userMessage: "Não foi possível conectar à carteira. Por favor, tente novamente.",
      shouldRetry: true,
      isUserCancellation: false,
      originalError: error,
    };
  }

  // Erro padrão (problemas de rede, etc.)
  if (error.message.includes("Network") || error.message.includes("fetch")) {
    return {
      userMessage: "Erro de rede. Verifique sua conexão.",
      shouldRetry: true,
      isUserCancellation: false,
      originalError: error,
    };
  }

  return {
    userMessage: error.message || "Ocorreu um erro",
    shouldRetry: true,
    isUserCancellation: false,
    originalError: error,
  };
}
Expand
[80 more lines]

Usando o Tratador de Erros

Envolva suas chamadas transact:

typescript
import { Alert } from "react-native";
import { transact } from "@solana-mobile/mobile-wallet-adapter-protocol-web3js";
import { handleMWAError } from "../utils/mwaErrorHandler";

async function connectWallet() {
  try {
    await transact(async (wallet) => {
      const result = await wallet.authorize({
        identity: APP_IDENTITY,
        chain: "solana:devnet",
      });
      // Lidar com sucesso
    });
  } catch (error) {
    const { userMessage, isUserCancellation, shouldRetry } = handleMWAError(error);

    if (isUserCancellation) {
      // Usuário cancelou intencionalmente — não mostrar erro
      console.log("Usuário cancelou");
      return;
    }

    if (shouldRetry) {
      Alert.alert("Erro", userMessage, [
        { text: "Cancelar", style: "cancel" },
        { text: "Tentar Novamente", onPress: () => connectWallet() },
      ]);
    } else {
      Alert.alert("Erro", userMessage);
    }
  }
}
Expand
[17 more lines]

Padrões de Retry

Implemente backoff exponencial para falhas transitórias:

typescript
// src/utils/retry.ts
export interface RetryOptions {
  maxAttempts: number;
  baseDelayMs: number;
  maxDelayMs: number;
}

const DEFAULT_OPTIONS: RetryOptions = {
  maxAttempts: 3,
  baseDelayMs: 1000,
  maxDelayMs: 10000,
};

export async function withRetry<T>(
  operation: () => Promise<T>,
  shouldRetry: (error: unknown) => boolean,
  options: Partial<RetryOptions> = {},
): Promise<T> {
  const { maxAttempts, baseDelayMs, maxDelayMs } = {
    ...DEFAULT_OPTIONS,
    ...options,
  };

  let lastError: unknown;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;

      if (!shouldRetry(error) || attempt === maxAttempts) {
        throw error;
      }

      // Backoff exponencial com jitter
      const delay = Math.min(
        baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 1000,
        maxDelayMs,
      );

      console.log(`Tentativa ${attempt} falhou, tentando novamente em ${delay}ms...`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }

  throw lastError;
}
Expand
[33 more lines]

Use com o MWA:

typescript
import { handleMWAError } from "../utils/mwaErrorHandler";
import { withRetry } from "../utils/retry";

async function sendTransactionWithRetry(transaction: VersionedTransaction) {
  return await withRetry(
    async () => {
      return await transact(async (wallet) => {
        await authorizeSession(wallet);
        const [signature] = await wallet.signAndSendTransactions({
          transactions: [transaction],
        });
        return signature;
      });
    },
    (error) => {
      const result = handleMWAError(error);
      return result.shouldRetry && !result.isUserCancellation;
    },
    { maxAttempts: 3 },
  );
}
Expand
[6 more lines]

Erros do Ciclo de Vida da Sessão

A sessão do MWA pode encerrar inesperadamente:

Usuário sai do app: A sessão permanece ativa brevemente, mas a carteira pode fechá-la.

Carteira em background por muito tempo: O Android pode encerrar o processo da carteira.

Token de autenticação expirado: Os tokens não duram para sempre.

Trate erros de sessão re-autorizando:

typescript
async function robustTransact<T>(callback: (wallet: Web3MobileWallet) => Promise<T>): Promise<T> {
  return await transact(async (wallet) => {
    try {
      return await callback(wallet);
    } catch (error) {
      // Se for um erro de autenticação, o token pode estar desatualizado
      if (
        error instanceof SolanaMobileWalletAdapterProtocolError &&
        error.code === "ERROR_AUTHORIZATION_FAILED"
      ) {
        // Limpar token em cache
        await AsyncStorage.removeItem("mwa_auth_token");

        // Re-autorizar sem o token em cache
        await wallet.authorize({
          identity: APP_IDENTITY,
          chain: "solana:devnet",
        });

        // Tentar a operação novamente
        return await callback(wallet);
      }

      throw error;
    }
  });
}
Expand
[12 more lines]

Nenhuma Carteira Instalada

Trate o caso em que nenhuma carteira está instalada:

typescript
import { Linking, Platform } from 'react-native';

const WALLET_STORE_URLS = {
  phantom: {
    ios: 'https://apps.apple.com/app/phantom-solana-wallet/id1598432977',
    android: 'https://play.google.com/store/apps/details?id=app.phantom',
  },
  solflare: {
    ios: 'https://apps.apple.com/app/solflare/id1580902717',
    android: 'https://play.google.com/store/apps/details?id=com.solflare.mobile',
  },
};

function NoWalletPrompt() {
  const openStore = async (wallet: 'phantom' | 'solflare') => {
    const url = WALLET_STORE_URLS[wallet][Platform.OS === 'ios' ? 'ios' : 'android'];
    await Linking.openURL(url);
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Nenhuma Carteira Encontrada</Text>
      <Text style={styles.subtitle}>
        Instale uma carteira Solana para continuar
      </Text>
      <Button
        title="Obter Phantom"
        onPress={() => openStore('phantom')}
      />
      <Button
        title="Obter Solflare"
        onPress={() => openStore('solflare')}
      />
    </View>
  );
}
Expand
[21 more lines]

Integre com seu fluxo de conexão:

typescript
function ConnectButton() {
  const [showNoWallet, setShowNoWallet] = useState(false);

  const handleConnect = async () => {
    try {
      await transact(async (wallet) => {
        await wallet.authorize({ /* ... */ });
      });
    } catch (error) {
      const result = handleMWAError(error);

      if (result.userMessage.includes('Nenhuma carteira Solana')) {
        setShowNoWallet(true);
        return;
      }

      if (!result.isUserCancellation) {
        Alert.alert('Erro', result.userMessage);
      }
    }
  };

  if (showNoWallet) {
    return <NoWalletPrompt />;
  }

  return <Button title="Conectar Carteira" onPress={handleConnect} />;
}
Expand
[13 more lines]

Erros de Simulação de Transação

Antes de enviar para uma carteira, simule transações para capturar erros cedo:

typescript
import { Connection, VersionedTransaction, SendTransactionError } from "@solana/web3.js";

interface SimulationResult {
  success: boolean;
  error?: string;
  logs?: string[];
}

async function simulateTransaction(
  connection: Connection,
  transaction: VersionedTransaction,
): Promise<SimulationResult> {
  try {
    const result = await connection.simulateTransaction(transaction, {
      commitment: "confirmed",
    });

    if (result.value.err) {
      return {
        success: false,
        error: formatSimulationError(result.value.err),
        logs: result.value.logs ?? undefined,
      };
    }

    return { success: true, logs: result.value.logs ?? undefined };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : "A simulação falhou",
    };
  }
}

function formatSimulationError(err: any): string {
  if (typeof err === "string") return err;

  if (err.InstructionError) {
    const [index, reason] = err.InstructionError;
    if (typeof reason === "object" && reason.Custom !== undefined) {
      return `Instrução ${index} falhou com erro personalizado ${reason.Custom}`;
    }
    return `Instrução ${index} falhou: ${JSON.stringify(reason)}`;
  }

  return JSON.stringify(err);
}
Expand
[32 more lines]

Use antes da assinatura na carteira:

typescript
async function sendWithSimulation(
  transaction: VersionedTransaction,
  connection: Connection,
): Promise<string> {
  // Simular primeiro
  const simulation = await simulateTransaction(connection, transaction);

  if (!simulation.success) {
    throw new Error(`A transação falharia: ${simulation.error}`);
  }

  // Abrir a carteira apenas se a simulação passou
  return await transact(async (wallet) => {
    await authorizeSession(wallet);
    const [signature] = await wallet.signAndSendTransactions({
      transactions: [transaction],
    });
    return signature;
  });
}
Expand
[5 more lines]

Componente de Feedback do Usuário

Crie um componente para o status da transação:

typescript
// components/TransactionStatus.tsx
import React from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';

type Status = 'idle' | 'simulating' | 'signing' | 'confirming' | 'success' | 'error';

interface Props {
  status: Status;
  signature?: string;
  error?: string;
}

const STATUS_MESSAGES: Record<Status, string> = {
  idle: '',
  simulating: 'Simulando transação...',
  signing: 'Aprove na sua carteira...',
  confirming: 'Confirmando na blockchain...',
  success: 'Transação confirmada!',
  error: 'A transação falhou',
};

export function TransactionStatus({ status, signature, error }: Props) {
  if (status === 'idle') return null;

  return (
    <View style={styles.container}>
      {(status === 'simulating' || status === 'signing' || status === 'confirming') && (
        <ActivityIndicator size="small" />
      )}
      <Text style={[styles.text, status === 'error' && styles.errorText]}>
        {STATUS_MESSAGES[status]}
      </Text>
      {status === 'error' && error && (
        <Text style={styles.errorDetail}>{error}</Text>
      )}
      {status === 'success' && signature && (
        <Text style={styles.signature}>
          {signature.slice(0, 20)}...
        </Text>
      )}
    </View>
  );
}
Expand
[28 more lines]

Logging para Debug

Em desenvolvimento, registre todas as interações do MWA:

typescript
// src/utils/mwaLogger.ts
const DEBUG = __DEV__;

export function logMWARequest(method: string, params: unknown): void {
  if (!DEBUG) return;
  console.log(`[MWA] → ${method}`, JSON.stringify(params, null, 2));
}

export function logMWAResponse(method: string, result: unknown): void {
  if (!DEBUG) return;
  console.log(`[MWA] ← ${method}`, JSON.stringify(result, null, 2));
}

export function logMWAError(method: string, error: unknown): void {
  console.error(`[MWA] ✕ ${method}`, error);
}
Expand
[1 more lines]

Envolva suas chamadas transact:

typescript
async function trackedTransact<T>(callback: (wallet: Web3MobileWallet) => Promise<T>): Promise<T> {
  const startTime = Date.now();

  try {
    const result = await transact(callback);
    console.log(`[MWA] Sessão concluída em ${Date.now() - startTime}ms`);
    return result;
  } catch (error) {
    console.error(`[MWA] Sessão falhou após ${Date.now() - startTime}ms`, error);
    throw error;
  }
}

Um bom tratamento de erros faz seu dApp parecer profissional. Na próxima lição, abordaremos como testar sua implementação em dispositivos reais.

Blueshift © 2026Commit: 3c44267