📱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 Network

Implementation

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-protocol

2. 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.apk

When 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 approvals

  • Implement 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

Aspect
Mobile Wallet Adapter
Direct Key Management

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