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:
Navigate to wallet tab
Tap the wallet icon in the bottom navigation bar.
Tap connect button
If no wallet is connected, you’ll see a “Connect” button. Tap it to initiate the connection flow.
Choose wallet app
Your device will show available wallet apps (Phantom, Solflare, etc.). Select your preferred wallet.
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
Fetching balances
Token accounts
Balances are fetched using the useGetBalance hook: const { data : balance , isLoading , error } = useQuery ({
queryKey: [ 'get-balance' , { endpoint , address }],
queryFn : () => connection . getBalance ( address )
});
Token accounts are fetched with useGetTokenAccounts: const { data : tokenAccounts } = useQuery ({
queryKey: [ 'get-token-accounts' , { endpoint , address }],
queryFn : async () => {
const accounts = await connection . getParsedTokenAccountsByOwner (
address ,
{ programId: TOKEN_PROGRAM_ID }
);
return accounts . value ;
}
});
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.
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
Tap send button
Navigate to the send screen from the wallet tab.
Enter recipient
Input the destination address or scan a QR code.
Specify amount
Enter the SOL amount to send. Balance is displayed for reference.
Review transaction
Confirm the recipient address and amount are correct.
Approve in wallet
Your wallet app will prompt for transaction approval. Review fees and approve.
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
Navigate to airdrop
Tap the “Airdrop” button from the wallet screen.
Request SOL
Tap “Request Airdrop” to receive 1 SOL (devnet/testnet only).
Wait for confirmation
The airdrop transaction is submitted and confirmed automatically.
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 ;
},
});
Wallet authorization
Synto Mobile uses the Mobile Wallet Adapter protocol for secure authorization:
Authorization lifecycle
Initial authorization
First connection requests full authorization with the selected cluster (devnet/mainnet).
Token persistence
The auth token is saved to AsyncStorage for subsequent sessions.
Reauthorization
When performing operations, the app reauthorizes using the cached token if valid.
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
Verify addresses
Always double-check recipient addresses before sending. Solana addresses are case-sensitive and must be valid base58 strings.
Check balances
Ensure sufficient SOL balance for transaction fees (rent + network fees).
Use devnet for testing
Test transactions on devnet before using mainnet to avoid mistakes with real funds.
Keep auth tokens secure
Auth tokens are stored in AsyncStorage. Don’t share device access or clear app data unnecessarily.
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