📱Seeker
Overview
anon0mesh integrates with Mobile Wallet Adapter (MWA), a protocol specification that enables dApps to connect with mobile wallet apps for Solana transaction and message signing. This allows Seeker and other MWA-compliant wallets to seamlessly sign transactions created through the anon0mesh mesh network, whether online or offline.
Why Mobile Wallet Adapter?
Developers only need to implement MWA once to be compatible with all MWA-compliant wallets, eliminating the need to integrate with each wallet individually. For anon0mesh, this means:
Universal Compatibility - Works with Seeker, Phantom, Solflare, and any MWA-compliant wallet
Offline-First Design - Transactions can be signed offline and queued for relay
Zero Integration Cost - Wallet connection handled by standardized protocol
User Choice - Users select their preferred wallet app
Architecture
┌─────────────────────────────────────────────────────────────┐
│ anon0mesh App │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Transaction Builder │ │
│ │ • Create transaction with durable nonce │ │
│ │ • Add relay fee instruction │ │
│ │ • Prepare for multi-sig if needed │ │
│ └───────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Mobile Wallet Adapter Integration │ │
│ │ • transact() session management │ │
│ │ • authorize() for wallet connection │ │
│ │ • signTransactions() for signing │ │
│ │ • signAndSendTransactions() for submission │ │
│ └───────────────────┬────────────────────────────────┘ │
│ │ │
└──────────────────────┼───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Seeker Seed Vault Wallet (MWA-compliant) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ MWA Endpoint │ │
│ │ • Receive authorization requests │ │
│ │ • Display transaction details │ │
│ │ • Sign with user approval │ │
│ │ • Return signed transaction │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
BLE Mesh Network
(if offline submission)
│
▼
Solana NetworkImplementation
1. Setup and Dependencies
# Install MWA packages
npm install @solana-mobile/mobile-wallet-adapter-protocol-web3js \
@solana-mobile/mobile-wallet-adapter-protocol
# For Expo projects
npx expo install @solana-mobile/mobile-wallet-adapter-protocol-web3js \
@solana-mobile/mobile-wallet-adapter-protocol2. Configure App Identity
The APP_IDENTITY defines your dApp's identity information that will be displayed to users in the wallet:
import { AppIdentity } from '@solana-mobile/mobile-wallet-adapter-protocol';
export const APP_IDENTITY: AppIdentity = {
name: 'anon0mesh',
uri: 'https://anon0mesh.app',
icon: 'favicon.ico', // Resolves to https://anon0mesh.app/favicon.ico
};3. Connect to Seeker Wallet
The transact function starts a session with the wallet app during which your app can send actions like authorize to the wallet:
import {
transact,
Web3MobileWallet
} from '@solana-mobile/mobile-wallet-adapter-protocol-web3js';
import { APP_IDENTITY } from './constants';
// Connect to Seeker or any MWA wallet
async function connectWallet(): Promise<{
address: string;
publicKey: PublicKey;
authToken: string;
}> {
const authResult = await transact(async (wallet: Web3MobileWallet) => {
// Request authorization from wallet
const authorizationResult = await wallet.authorize({
cluster: 'mainnet-beta',
identity: APP_IDENTITY,
});
return authorizationResult;
});
// Extract account information
const account = authResult.accounts[0];
const publicKey = new PublicKey(account.address);
console.log('Connected to wallet:', account.label);
console.log('Public key:', publicKey.toBase58());
return {
address: account.address,
publicKey: publicKey,
authToken: authResult.auth_token,
};
}4. Sign Transaction with Seeker
import { Transaction } from '@solana/web3.js';
async function signTransactionWithSeeker(
transaction: Transaction,
authToken: string
): Promise<Transaction> {
const signedTransactions = await transact(async (wallet: Web3MobileWallet) => {
// Reauthorize with stored token (skips user approval)
await wallet.reauthorize({
auth_token: authToken,
identity: APP_IDENTITY,
});
// Request signature from Seeker
const signedTxs = await wallet.signTransactions({
transactions: [transaction],
});
return signedTxs;
});
return signedTransactions[0];
}5. Complete Offline Transaction Flow with Seeker
import { BLEAdapter } from './infrastructure/ble/BLEAdapter';
import { createPacket, PacketType } from './domain/Packet';
// Full flow: Create, sign with Seeker, broadcast via BLE
async function createAndSignOfflineTransaction(
sender: PublicKey,
recipient: PublicKey,
amount: number,
authToken: string,
bleAdapter: BLEAdapter
): Promise<void> {
console.log('=== Offline Transaction with Seeker Wallet ===\n');
// 1. Build transaction with relay fee
const relayFee = Math.floor(amount * 0.006); // 0.6%
const recipientAmount = amount - relayFee;
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: sender,
toPubkey: recipient,
lamports: recipientAmount,
}),
SystemProgram.transfer({
fromPubkey: sender,
toPubkey: RELAY_PLACEHOLDER_PUBKEY,
lamports: relayFee,
})
);
// Use cached blockhash or placeholder
transaction.recentBlockhash = '11111111111111111111111111111111';
transaction.feePayer = sender;
console.log('Transaction created with relay fee:', relayFee);
// 2. Sign with Seeker via MWA
const signedTransaction = await transact(async (wallet: Web3MobileWallet) => {
await wallet.reauthorize({
auth_token: authToken,
identity: APP_IDENTITY,
});
// Seeker displays transaction details and requests approval
const signedTxs = await wallet.signTransactions({
transactions: [transaction],
});
return signedTxs[0];
});
console.log('✅ Transaction signed by Seeker wallet');
// 3. Serialize for BLE mesh
const serialized = signedTransaction.serialize({
requireAllSignatures: false,
verifySignatures: true,
});
// 4. Create mesh packet
const packet = createPacket({
type: PacketType.TRANSACTION,
payload: serialized,
ttl: 8,
priority: Priority.HIGH,
metadata: {
network: 'mainnet-beta',
totalAmount: amount,
relayFeePercent: 0.006,
requiresRelayFee: true,
expiresAt: Date.now() + 120000, // 2 minutes
createdAt: Date.now(),
},
});
// 5. Broadcast via BLE mesh
await bleAdapter.broadcast(packet);
console.log('📡 Transaction broadcasted via BLE mesh');
console.log('Waiting for online peer to relay...');
}6. Sign and Send Directly (Optional)
MWA wallets can optionally support signAndSendTransactions, which handles both signing and submission:
async function signAndSendWithSeeker(
transaction: Transaction,
authToken: string
): Promise<string> {
const signatures = await transact(async (wallet: Web3MobileWallet) => {
await wallet.reauthorize({
auth_token: authToken,
identity: APP_IDENTITY,
});
// Seeker signs and submits transaction to RPC
const sigs = await wallet.signAndSendTransactions({
transactions: [transaction],
});
return sigs;
});
const signature = signatures[0];
console.log('Transaction submitted by Seeker:', signature);
return signature;
}Advanced Integration Patterns
Multi-Signature with Seeker
// User A signs with Seeker, then forwards to User B
async function multiSigWithSeeker(
transaction: VersionedTransaction,
userAAuthToken: string,
bleAdapter: BLEAdapter
): Promise<void> {
// User A signs with Seeker
const partiallySigned = await transact(async (wallet: Web3MobileWallet) => {
await wallet.reauthorize({
auth_token: userAAuthToken,
identity: APP_IDENTITY,
});
// Sign partially
const signed = await wallet.signTransactions({
transactions: [transaction],
});
return signed[0];
});
console.log('✅ User A signed with Seeker');
// Serialize and broadcast via BLE for User B
const serialized = partiallySigned.serialize();
const packet = createPacket({
type: PacketType.PARTIAL_SIGNATURE,
payload: serialized,
ttl: 8,
metadata: {
requiredSigner: userBPubkey.toBase58(),
signatureCount: 1,
totalRequired: 2,
},
});
await bleAdapter.broadcast(packet);
console.log('Forwarded to User B via BLE mesh');
}Message Signing with Seeker
You can request Seeker to sign arbitrary messages using signMessages:
async function signMessageWithSeeker(
message: string,
authToken: string
): Promise<Uint8Array> {
// Convert message to buffer
const messageBuffer = new Uint8Array(
message.split('').map(c => c.charCodeAt(0))
);
const signedMessages = await transact(async (wallet: Web3MobileWallet) => {
await wallet.reauthorize({
auth_token: authToken,
identity: APP_IDENTITY,
});
const signed = await wallet.signMessages({
addresses: [userAddress],
payloads: [messageBuffer],
});
return signed;
});
return signedMessages[0];
}Disconnect from Seeker
async function disconnectWallet(authToken: string): Promise<void> {
await transact(async (wallet: Web3MobileWallet) => {
// Invalidate the auth token
await wallet.deauthorize({
auth_token: authToken,
});
});
console.log('Disconnected from Seeker wallet');
}React Native Component Example
import React, { useState } from 'react';
import { View, Button, Text, StyleSheet } from 'react-native';
import { transact, Web3MobileWallet } from '@solana-mobile/mobile-wallet-adapter-protocol-web3js';
import { APP_IDENTITY } from './constants';
export default function SeekerConnectButton() {
const [connected, setConnected] = useState(false);
const [address, setAddress] = useState<string>('');
const [authToken, setAuthToken] = useState<string>('');
const handleConnect = async () => {
try {
const authResult = await transact(async (wallet: Web3MobileWallet) => {
const result = await wallet.authorize({
cluster: 'mainnet-beta',
identity: APP_IDENTITY,
});
return result;
});
const account = authResult.accounts[0];
setAddress(account.address);
setAuthToken(authResult.auth_token);
setConnected(true);
console.log('Connected to Seeker:', account.label);
} catch (error) {
console.error('Connection failed:', error);
}
};
const handleDisconnect = async () => {
try {
await transact(async (wallet: Web3MobileWallet) => {
await wallet.deauthorize({
auth_token: authToken,
});
});
setConnected(false);
setAddress('');
setAuthToken('');
console.log('Disconnected from Seeker');
} catch (error) {
console.error('Disconnect failed:', error);
}
};
return (
<View style={styles.container}>
{!connected ? (
<Button title="Connect Seeker Wallet" onPress={handleConnect} />
) : (
<>
<Text style={styles.address}>
Connected: {address.slice(0, 8)}...{address.slice(-8)}
</Text>
<Button title="Disconnect" onPress={handleDisconnect} />
</>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
alignItems: 'center',
},
address: {
marginBottom: 10,
fontSize: 14,
fontFamily: 'monospace',
},
});Anchor Program Integration
React Native apps should use Anchor v0.28.0 because later versions have a polyfill issue:
npm install @coral-xyz/[email protected]To create an AnchorWallet, use MWA transact to implement the required signing functions:
import * as anchor from "@coral-xyz/anchor";
import { transact, Web3MobileWallet } from "@solana-mobile/mobile-wallet-adapter-protocol-web3js";
const anchorWallet = useMemo(() => {
return {
signTransaction: async (transaction: Transaction) => {
return transact(async (wallet: Web3MobileWallet) => {
await wallet.reauthorize({
auth_token: authToken,
identity: APP_IDENTITY,
});
const signedTransactions = await wallet.signTransactions({
transactions: [transaction],
});
return signedTransactions[0];
});
},
signAllTransactions: async (transactions: Transaction[]) => {
return transact(async (wallet: Web3MobileWallet) => {
await wallet.reauthorize({
auth_token: authToken,
identity: APP_IDENTITY,
});
const signedTransactions = await wallet.signTransactions({
transactions: transactions,
});
return signedTransactions;
});
},
publicKey: userPublicKey,
} as anchor.Wallet;
}, [authToken, userPublicKey]);
// Use with Anchor Program
const program = new anchor.Program(IDL, PROGRAM_ID, {
connection: connection,
wallet: anchorWallet,
});Testing with Fake Wallet
For development and testing, use the Solana Mobile Fake Wallet:
# Install Fake Wallet on Android device/emulator
adb install fakewallet.apkWhen prompted inside the wallet, tap Authenticate to enable signing for development sessions.
Best Practices
Security
Auth Token Storage
import AsyncStorage from '@react-native-async-storage/async-storage';
// Store auth token securely
await AsyncStorage.setItem('seeker_auth_token', authResult.auth_token);
// Retrieve for reauthorization
const storedToken = await AsyncStorage.getItem('seeker_auth_token');Session Management
Always use
reauthorize()with stored token to avoid repeated user approvalsImplement token expiration and refresh logic
Clear tokens on logout or deauthorization
User Experience
Transaction Details Display
Show clear transaction breakdown before signing
Display relay fee amount prominently
Explain offline vs. online transaction flow
Error Handling
try {
const result = await transact(async (wallet) => {
// ... wallet operations
});
} catch (error) {
if (error.message.includes('USER_DECLINED')) {
console.log('User declined the request');
} else if (error.message.includes('NO_WALLET_FOUND')) {
console.log('No MWA wallet installed');
// Prompt user to install Seeker
} else {
console.error('Wallet error:', error);
}
}Performance
Connection Pooling
Reuse auth tokens across multiple transactions
Batch transaction signing when possible
Cache recent blockhash for offline scenarios
Comparison: MWA vs. Direct Signing
Security
✅ Keys in secure enclave
⚠️ Keys in app storage
User Control
✅ User approves each tx
❌ Automatic signing
Wallet Support
✅ Works with all MWA wallets
❌ Custom per wallet
Implementation
✅ Standardized API
⚠️ Custom integration
Offline Capability
✅ Sign offline, relay later
✅ Full offline support
Troubleshooting
Issue: "No wallet found"
Ensure Seeker or another MWA wallet is installed
Check that the wallet app is up to date
Verify MWA is enabled in wallet settings
Issue: "Authorization failed"
Clear cached auth tokens and reconnect
Restart the wallet app
Check network cluster matches (mainnet/devnet)
Issue: "Transaction signature verification failed"
Ensure transaction hasn't been modified after signing
Verify all required signatures are present
Check that blockhash is still valid
Resources
Summary
Mobile Wallet Adapter provides anon0mesh with a standardized, secure way to integrate with Seeker and other Solana mobile wallets. The combination of MWA for signing and BLE mesh for offline relay creates a powerful offline-first transaction system where users maintain full custody through their preferred wallet app.
Last updated
