Skip to main content
Synto Mobile provides a full-featured wallet interface for managing Solana assets. The wallet screen displays your SOL balance, token holdings, and offers quick actions for common operations like sending, receiving, and requesting airdrops.

Wallet screen overview

The main wallet interface consists of three key sections:

Balance display

Shows SOL balance with real-time updates

Action buttons

Quick access to send, receive, and airdrop

Token list

Displays all SPL tokens with balances
The wallet screen requires an active connection through Mobile Wallet Adapter. Connect your wallet to view balances and perform operations.

Connecting your wallet

Before accessing wallet features, you must establish a connection:
1

Navigate to wallet tab

Tap the wallet icon in the bottom navigation bar.
2

Tap connect button

If no wallet is connected, you’ll see a “Connect” button. Tap it to initiate the connection flow.
3

Choose wallet app

Your device will show available wallet apps (Phantom, Solflare, etc.). Select your preferred wallet.
4

Authorize connection

Approve the connection request in your wallet app. Synto Mobile requests:
  • Read your public address
  • Request transaction approvals
  • Sign messages

Connection component

The connection flow uses the WalletUiButtonConnect component:
export function WalletUiButtonConnect({ label = "Connect" }: { label?: string }) {
  const { connect } = useWalletUi();
  
  return <BaseButton label={label} onPress={() => connect()} />;
}
This triggers the useMobileWallet hook which handles the MWA protocol:
const connect = useCallback(async (): Promise<Account> => {
  try {
    return await transact(async (wallet) => {
      return await authorizeSession(wallet);
    });
  } catch (error) {
    await handleWalletError(error);
    throw error;
  }
}, [authorizeSession, handleWalletError]);

Viewing balances

SOL balance

The primary SOL balance is displayed prominently using the AccountUiBalance component:
<AppView style={{ alignItems: "center", gap: 4 }}>
  <AccountUiBalance address={account.publicKey} />
  <AppText style={{ opacity: 0.7 }}>
    {ellipsify(account.publicKey.toString(), 8)}
  </AppText>
</AppView>
Features:
  • Real-time balance updates via React Query
  • Formatted display with SOL symbol
  • Ellipsified address (e.g., 7xKX...4J2p)
  • Pull-to-refresh support

Token accounts

All SPL tokens are fetched and displayed using AccountUiTokenAccounts:
<AccountUiTokenAccounts address={account.publicKey} />
This component:
  • Queries token accounts using useGetTokenAccounts
  • Displays token symbols and balances
  • Shows token logos when available
  • Groups tokens by type
Balances are fetched using the useGetBalance hook:
const { data: balance, isLoading, error } = useQuery({
  queryKey: ['get-balance', { endpoint, address }],
  queryFn: () => connection.getBalance(address)
});

Pull-to-refresh

Users can refresh balances by pulling down on the wallet screen:
const onRefresh = useCallback(async () => {
  setRefreshing(true);
  await Promise.all([
    invalidateBalance(),
    invalidateTokenAccounts()
  ]);
  setRefreshing(false);
}, [invalidateBalance, invalidateTokenAccounts]);

return (
  <ScrollView
    refreshControl={
      <RefreshControl 
        refreshing={refreshing} 
        onRefresh={onRefresh} 
      />
    }
  >
    {/* Wallet content */}
  </ScrollView>
);
Invalidation functions trigger React Query to refetch data from the blockchain, ensuring balances are always current.

Quick action buttons

Three primary actions are available via AccountUiButtons:
export function AccountUiButtons() {
  const router = useRouter();
  return (
    <View style={{ flexDirection: "row", gap: 8, justifyContent: "center" }}>
      <Button onPressIn={() => router.navigate("/(tabs)/account/airdrop")}>
        Airdrop
      </Button>
      <Button onPressIn={() => router.navigate("/(tabs)/account/send")}>
        Send
      </Button>
      <Button onPressIn={() => router.navigate("/(tabs)/account/receive")}>
        Receive
      </Button>
    </View>
  );
}

Airdrop

Request devnet/testnet SOL for testing

Send

Transfer SOL or tokens to another address

Receive

Display your address and QR code

Sending SOL

The send flow allows transferring SOL to any Solana address.

Transfer workflow

1

Tap send button

Navigate to the send screen from the wallet tab.
2

Enter recipient

Input the destination address or scan a QR code.
3

Specify amount

Enter the SOL amount to send. Balance is displayed for reference.
4

Review transaction

Confirm the recipient address and amount are correct.
5

Approve in wallet

Your wallet app will prompt for transaction approval. Review fees and approve.
6

Wait for confirmation

Transaction is submitted to the blockchain. A signature is returned immediately, but confirmation takes a few seconds.

Transfer implementation

The useTransferSol hook handles the complete transfer lifecycle:
export function useTransferSol({ address }: { address: PublicKey }) {
  const connection = useConnection();
  const { signAndSendTransaction } = useWalletUi();
  const invalidateBalance = useGetBalanceInvalidate({ address });
  
  return useMutation({
    mutationKey: ['transfer-sol', { endpoint: connection.rpcEndpoint, address }],
    mutationFn: async (input: { destination: PublicKey; amount: number }) => {
      let signature: TransactionSignature = "";
      try {
        const { transaction, latestBlockhash, minContextSlot } = 
          await createTransaction({
            publicKey: address,
            destination: input.destination,
            amount: input.amount,
            connection,
          });
        
        // Sign and send transaction
        signature = await signAndSendTransaction(transaction, minContextSlot);
        
        // Wait for confirmation
        await connection.confirmTransaction(
          { signature, ...latestBlockhash },
          'confirmed'
        );
        
        return signature;
      } catch (error: unknown) {
        console.log('Transaction failed', error, signature);
        throw error;
      }
    },
    onSuccess: async (signature) => {
      console.log('Transaction successful:', signature);
      await invalidateBalance();
    },
    onError: (error) => {
      console.error('Transaction failed:', error);
    },
  });
}

Transaction creation

The createTransaction function builds a proper Solana transaction:
import { 
  SystemProgram, 
  Transaction, 
  LAMPORTS_PER_SOL 
} from '@solana/web3.js';

export async function createTransaction({
  publicKey,
  destination,
  amount,
  connection
}) {
  // Get latest blockhash
  const { blockhash, lastValidBlockHeight } = 
    await connection.getLatestBlockhash();
  
  // Get minimum slot
  const minContextSlot = await connection.getSlot();
  
  // Create transfer instruction
  const transaction = new Transaction({
    feePayer: publicKey,
    blockhash,
    lastValidBlockHeight,
  }).add(
    SystemProgram.transfer({
      fromPubkey: publicKey,
      toPubkey: destination,
      lamports: amount * LAMPORTS_PER_SOL,
    })
  );
  
  return { 
    transaction, 
    latestBlockhash: { blockhash, lastValidBlockHeight },
    minContextSlot 
  };
}
Transactions cannot be reversed once confirmed on the blockchain. Always double-check recipient addresses before approving.

Requesting airdrops

On devnet and testnet, users can request SOL airdrops:

Airdrop flow

1

Navigate to airdrop

Tap the “Airdrop” button from the wallet screen.
2

Request SOL

Tap “Request Airdrop” to receive 1 SOL (devnet/testnet only).
3

Wait for confirmation

The airdrop transaction is submitted and confirmed automatically.
4

Balance updates

Your SOL balance refreshes to show the new amount.

Implementation

The useRequestAirdrop hook manages airdrop requests:
export function useRequestAirdrop({ address }: { address: PublicKey }) {
  const connection = useConnection();
  const invalidateBalance = useGetBalanceInvalidate({ address });
  
  return useMutation({
    mutationKey: ['request-airdrop', { endpoint: connection.rpcEndpoint, address }],
    mutationFn: async (amount: number = 1) => {
      const signature = await connection.requestAirdrop(
        address,
        amount * LAMPORTS_PER_SOL
      );
      
      await connection.confirmTransaction(signature, 'confirmed');
      return signature;
    },
    onSuccess: async () => {
      await invalidateBalance();
    },
  });
}
Airdrops are only available on devnet and testnet networks. Mainnet does not support airdrops.

Receiving SOL

To receive SOL or tokens, share your wallet address:

Receive screen features

  • Address display: Full public key with copy button
  • QR code: Scannable code containing your address
  • Share button: Share address via native share sheet
  • Address format: Both base58 and ellipsified versions

Getting your address

The connected account’s public key is accessible via useWalletUi:
const { account } = useWalletUi();

if (account) {
  const address = account.publicKey.toString();
  const shortAddress = ellipsify(address, 8);
  
  // Display: 7xKX...4J2p
  // Full: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU4J2p
}

Token management

Viewing token balances

All SPL tokens are automatically detected and displayed:
const { data: tokenAccounts, isLoading } = useGetTokenAccounts({ 
  address: account.publicKey 
});

return (
  <View>
    {tokenAccounts?.map((account) => (
      <TokenAccountItem 
        key={account.pubkey.toString()}
        account={account}
      />
    ))}
  </View>
);

Token account structure

Each token account contains:
interface TokenAccount {
  pubkey: PublicKey;          // Account address
  account: {
    data: {
      parsed: {
        info: {
          mint: string;         // Token mint address
          owner: string;        // Your wallet address
          tokenAmount: {
            amount: string;     // Raw amount
            decimals: number;   // Token decimals
            uiAmount: number;   // Formatted amount
          };
        };
      };
    };
  };
}

Getting individual token balance

Query a specific token’s balance using useGetTokenAccountBalance:
const { data: balance } = useQuery({
  queryKey: ['get-token-account-balance', { endpoint, address: tokenAccountAddress }],
  queryFn: async () => {
    const response = await connection.getTokenAccountBalance(tokenAccountAddress);
    return response.value;
  },
});
Use uiAmount for user-facing display:
const displayBalance = balance.uiAmount;
// Example: 100.50 USDC

Wallet authorization

Synto Mobile uses the Mobile Wallet Adapter protocol for secure authorization:

Authorization lifecycle

1

Initial authorization

First connection requests full authorization with the selected cluster (devnet/mainnet).
2

Token persistence

The auth token is saved to AsyncStorage for subsequent sessions.
3

Reauthorization

When performing operations, the app reauthorizes using the cached token if valid.
4

Deauthorization

Users can disconnect, which clears the auth token and closes the session.

Authorization hook

The useAuthorization hook manages the authorization state:
export function useAuthorization() {
  const { selectedCluster } = useCluster();
  const fetchQuery = useFetchAuthorization();
  const persistMutation = usePersistAuthorization();
  
  const authorizeSession = useCallback(
    async (wallet: AuthorizeAPI) => {
      const authorizationResult = await wallet.authorize({
        identity: { name: 'Synto Mobile', uri: 'https://synto.io' },
        chain: selectedCluster.id,
        auth_token: fetchQuery.data?.authToken,
      });
      return (await handleAuthorizationResult(authorizationResult)).selectedAccount;
    },
    [fetchQuery.data?.authToken, selectedCluster.id]
  );
  
  const deauthorizeSessions = useCallback(async () => {
    await invalidateAuthorizations();
    await persistMutation.mutateAsync(null);
  }, [invalidateAuthorizations, persistMutation]);
  
  return {
    accounts: fetchQuery.data?.accounts ?? null,
    authorizeSession,
    deauthorizeSessions,
    selectedAccount: fetchQuery.data?.selectedAccount ?? null,
  };
}

Error handling

Authorization errors trigger automatic disconnection:
const handleWalletError = useCallback(async (error: any) => {
  console.error('Wallet error:', error);
  
  const isAuthError = 
    error?.message?.includes('authorization') ||
    error?.message?.includes('not authorized') ||
    error?.message?.includes('wallet not connected') ||
    error?.code === 'UNAUTHORIZED';
  
  if (isAuthError) {
    console.log('Auto-disconnect due to authorization error');
    await deauthorizeSessions();
  }
  
  throw error;
}, [deauthorizeSessions]);
If authorization expires or fails, the wallet automatically disconnects. Reconnect to continue using wallet features.

Network selection

Synto Mobile supports multiple Solana clusters:
  • Mainnet Beta: Production network with real SOL
  • Devnet: Testing network with free SOL via airdrops
  • Testnet: Alternative testing network
  • Custom RPC: Connect to custom endpoints
The selected cluster affects:
  • Which blockchain data is read
  • Where transactions are submitted
  • Whether airdrops are available
Always verify you’re on the correct network before performing transactions. Mainnet transactions use real SOL and cannot be reversed.

Best practices

1

Verify addresses

Always double-check recipient addresses before sending. Solana addresses are case-sensitive and must be valid base58 strings.
2

Check balances

Ensure sufficient SOL balance for transaction fees (rent + network fees).
3

Use devnet for testing

Test transactions on devnet before using mainnet to avoid mistakes with real funds.
4

Keep auth tokens secure

Auth tokens are stored in AsyncStorage. Don’t share device access or clear app data unnecessarily.
5

Monitor confirmations

Wait for transaction confirmation before considering transfers complete. Use ‘confirmed’ or ‘finalized’ commitment levels.

Next steps

AI chat interface

Use natural language to execute wallet operations

Mobile Wallet Adapter

Learn how transactions are signed securely

Build docs developers (and LLMs) love