Technical Overview

anon0mesh enables offline-first Solana transaction workflows through two complementary approaches: Direct RPC with Arcium Escrow for immediate confidential execution and BLE Mesh Relay for offline transaction queuing. Both methods preserve transaction privacy and enable trustless settlement.


Multi-Signature Transaction Flow with Durable Nonce

Overview

For transactions requiring multiple signatures (escrow, multi-party agreements), anon0mesh supports a coordinated signing flow using durable nonce accounts for offline compatibility.

Sequence Diagram

User A            User B          Application/Backend     Solana Network
(A_keypair)       (B_keypair)                                  │
    │                 │                    │                   │
    │                 │  Build message     │                   │
    │                 │ (incl. Durable     │                   │
    │                 │      Nonce)        │                   │
    │                 ├───────────────────>│                   │
    │                 │                    │                   │
    │                 │  Send unsigned     │                   │
    │                 │  VersionedTx       │                   │
    │<────────────────┴────────────────────┤                   │
    │                 │                    │                   │
    │ tx.sign(A_keypair)                   │                   │
    │ → Partial sig   │                    │                   │
    ├─────────┐       │                    │                   │
    │         │       │                    │                   │
    │<────────┘       │                    │                   │
    │                 │                    │                   │
    │ Send tx.serialize() (signed by A)   │                   │
    ├─────────────────────────────────────>│                   │
    │                 │                    │                   │
    │                 │  Forward partial   │                   │
    │                 │  bytes             │                   │
    │                 │<───────────────────┤                   │
    │                 │                    │                   │
    │                 │ VersionedTx        │                   │
    │                 │   .deserialize()   │                   │
    │                 │ → add signature    │                   │
    │                 │   tx.sign(B_keypair)                   │
    │                 ├─────────┐          │                   │
    │                 │         │          │                   │
    │                 │<────────┘          │                   │
    │                 │                    │                   │
    │                 │ sendRawTransaction(tx.serialize())    │
    │                 │      → with merged signatures          │
    │                 ├───────────────────────────────────────>│
    │                 │                    │               ├─ Validate
    │                 │                    │               ├─ Execute
    │                 │                    │               └─ Confirm
    │                 │                    │                   │
    │                 │  Confirm transaction                   │
    │                 │<───────────────────────────────────────┤
    │                 │                    │                   │
    │ Notify TX validated                 │                   │
    │<────────────────┴────────────────────┤                   │
    │                 │                    │                   │

Implementation

import { 
  Transaction, 
  VersionedTransaction,
  TransactionMessage,
  Connection, 
  Keypair,
  SystemProgram,
  PublicKey,
  NONCE_ACCOUNT_LENGTH
} from '@solana/web3.js';

// Step 1: Application builds unsigned transaction with durable nonce
async function buildMultiSigTransaction(
  connection: Connection,
  nonceAccount: PublicKey,
  nonceAuthority: PublicKey,
  signerA: PublicKey,
  signerB: PublicKey,
  recipient: PublicKey,
  amount: number
): Promise<VersionedTransaction> {
  
  // Get current nonce value
  const nonceAccountInfo = await connection.getAccountInfo(nonceAccount);
  const nonceData = NonceAccount.fromAccountData(nonceAccountInfo.data);
  const currentNonce = nonceData.nonce;
  
  console.log('Building multi-sig transaction with nonce:', currentNonce);
  
  // Build transaction message
  const message = new TransactionMessage({
    payerKey: signerA, // Primary signer pays fees
    recentBlockhash: currentNonce, // Use nonce instead of blockhash
    instructions: [
      // Advance nonce (required first instruction)
      SystemProgram.nonceAdvance({
        noncePubkey: nonceAccount,
        authorizedPubkey: nonceAuthority,
      }),
      // Main transfer instruction
      SystemProgram.transfer({
        fromPubkey: signerA,
        toPubkey: recipient,
        lamports: amount,
      }),
      // Additional instruction requiring signerB
      SystemProgram.transfer({
        fromPubkey: signerB,
        toPubkey: recipient,
        lamports: amount,
      }),
    ],
  }).compileToV0Message();
  
  // Create versioned transaction (unsigned)
  const transaction = new VersionedTransaction(message);
  
  return transaction;
}

// Step 2: User A signs transaction partially
async function userASignsTransaction(
  transaction: VersionedTransaction,
  userAKeypair: Keypair
): Promise<Uint8Array> {
  
  console.log('User A signing transaction...');
  
  // Sign with User A's keypair
  transaction.sign([userAKeypair]);
  
  console.log('User A signature added');
  console.log('Signatures:', transaction.signatures.map(s => 
    Buffer.from(s).toString('base64')
  ));
  
  // Serialize partially signed transaction
  const serialized = transaction.serialize();
  
  return serialized;
}

// Step 3: Application forwards to User B
async function forwardToUserB(
  serializedTx: Uint8Array,
  backend: ApplicationBackend
): Promise<void> {
  
  console.log('Forwarding partially signed transaction to User B...');
  
  // Send via application backend (could be BLE mesh, HTTP, etc.)
  await backend.sendToUserB({
    transaction: Buffer.from(serializedTx).toString('base64'),
    status: 'awaiting_signature',
  });
}

// Step 4: User B deserializes, adds signature
async function userBSignsTransaction(
  serializedTx: Uint8Array,
  userBKeypair: Keypair
): Promise<VersionedTransaction> {
  
  console.log('User B deserializing transaction...');
  
  // Deserialize transaction (already has User A's signature)
  const transaction = VersionedTransaction.deserialize(serializedTx);
  
  console.log('Existing signatures:', transaction.signatures.length);
  
  // Add User B's signature
  transaction.sign([userBKeypair]);
  
  console.log('User B signature added');
  console.log('Total signatures:', transaction.signatures.length);
  
  return transaction;
}

// Step 5: Submit fully signed transaction
async function submitMultiSigTransaction(
  transaction: VersionedTransaction,
  connection: Connection
): Promise<string> {
  
  console.log('Submitting fully signed transaction...');
  
  // Verify all signatures present
  const requiredSignatures = transaction.message.staticAccountKeys.length;
  const providedSignatures = transaction.signatures.filter(
    sig => sig.some(byte => byte !== 0)
  ).length;
  
  console.log(`Signatures: ${providedSignatures}/${requiredSignatures}`);
  
  if (providedSignatures < requiredSignatures) {
    throw new Error('Missing required signatures');
  }
  
  // Submit to Solana
  const signature = await connection.sendRawTransaction(
    transaction.serialize(),
    {
      skipPreflight: false,
      maxRetries: 3,
    }
  );
  
  console.log('Transaction submitted:', signature);
  
  // Wait for confirmation
  const confirmation = await connection.confirmTransaction(
    signature,
    'confirmed'
  );
  
  if (confirmation.value.err) {
    throw new Error('Transaction failed: ' + JSON.stringify(confirmation.value.err));
  }
  
  console.log('✅ Transaction confirmed on-chain');
  
  return signature;
}

// Step 6: Notify User A of success
async function notifyUserA(
  signature: string,
  backend: ApplicationBackend
): Promise<void> {
  
  console.log('Notifying User A of transaction confirmation...');
  
  await backend.notifyUserA({
    signature: signature,
    status: 'confirmed',
    explorerUrl: `https://solscan.io/tx/${signature}`,
  });
  
  console.log('✅ User A notified');
}

// Complete flow orchestration
async function executeMultiSigFlow(
  connection: Connection,
  userAKeypair: Keypair,
  userBKeypair: Keypair,
  nonceAccount: PublicKey,
  recipient: PublicKey,
  amount: number,
  backend: ApplicationBackend
): Promise<string> {
  
  console.log('=== Multi-Signature Transaction Flow ===\n');
  
  // 1. Build unsigned transaction
  const unsignedTx = await buildMultiSigTransaction(
    connection,
    nonceAccount,
    userAKeypair.publicKey, // Nonce authority
    userAKeypair.publicKey,
    userBKeypair.publicKey,
    recipient,
    amount
  );
  
  // 2. User A signs
  const partiallySigned = await userASignsTransaction(
    unsignedTx,
    userAKeypair
  );
  
  // 3. Forward to User B
  await forwardToUserB(partiallySigned, backend);
  
  // 4. User B signs
  const fullySigned = await userBSignsTransaction(
    partiallySigned,
    userBKeypair
  );
  
  // 5. Submit to Solana
  const signature = await submitMultiSigTransaction(
    fullySigned,
    connection
  );
  
  // 6. Notify User A
  await notifyUserA(signature, backend);
  
  return signature;
}

BLE Mesh Integration

For offline multi-signature coordination via BLE:

// User A broadcasts partially signed transaction via BLE
async function broadcastPartialSignatureBLE(
  partiallySigned: Uint8Array,
  userBPubkey: PublicKey,
  bleAdapter: BLEAdapter
): Promise<void> {
  
  const packet = createPacket({
    type: PacketType.PARTIAL_SIGNATURE,
    payload: partiallySigned,
    ttl: 8,
    priority: Priority.HIGH,
    metadata: {
      requiredSigner: userBPubkey.toBase58(),
      signatureCount: 1,
      totalRequired: 2,
      network: 'mainnet-beta',
    },
  });
  
  await bleAdapter.broadcast(packet);
  console.log('Partially signed transaction broadcasted via BLE');
}

// User B receives, signs, and broadcasts complete transaction
class MultiSigBLECoordinator {
  private bleAdapter: BLEAdapter;
  private userKeypair: Keypair;
  
  async listenForPartialSignatures(): Promise<void> {
    this.bleAdapter.on('packet', async (packet) => {
      if (packet.type !== PacketType.PARTIAL_SIGNATURE) return;
      
      // Check if this signature request is for us
      const metadata = packet.metadata;
      if (metadata.requiredSigner !== this.userKeypair.publicKey.toBase58()) {
        console.log('Signature not required from us, relaying...');
        return;
      }
      
      console.log('Received partial signature request');
      
      // Deserialize and sign
      const partiallySigned = packet.payload;
      const transaction = VersionedTransaction.deserialize(partiallySigned);
      transaction.sign([this.userKeypair]);
      
      console.log('Added our signature, broadcasting complete transaction');
      
      // Broadcast fully signed transaction
      const fullySignedPacket = createPacket({
        type: PacketType.TRANSACTION,
        payload: transaction.serialize(),
        ttl: 8,
        priority: Priority.HIGH,
        metadata: {
          signatureCount: 2,
          complete: true,
          network: 'mainnet-beta',
        },
      });
      
      await this.bleAdapter.broadcast(fullySignedPacket);
    });
  }
}

Security Considerations

Signature Order

  • Order matters! Signatures must be added in the same order as signers in the transaction

  • Use transaction.sign([keypair]) to add signatures correctly

Nonce Validity

  • Durable nonce remains valid until used

  • Once transaction is submitted, nonce advances automatically

  • Must fetch new nonce value for subsequent transactions

Replay Protection

  • Each nonce can only be used once

  • After successful submission, previous signature is invalid

  • Prevents replay attacks

Partial Signature Validation

function validatePartialSignature(
  transaction: VersionedTransaction,
  expectedSigners: PublicKey[]
): boolean {
  const providedSignatures = transaction.signatures.filter(
    sig => sig.some(byte => byte !== 0)
  ).length;
  
  console.log(`Signatures provided: ${providedSignatures}/${expectedSigners.length}`);
  
  // Verify at least one signature present
  if (providedSignatures === 0) {
    throw new Error('No signatures provided');
  }
  
  // Check signature count doesn't exceed expected
  if (providedSignatures > expectedSigners.length) {
    throw new Error('Too many signatures');
  }
  
  return true;
}

Transaction Relay Modes

Mode 1: Direct Transaction via RPC → Arcium Escrow

Standard flow for users with internet connectivity who want confidential escrow execution.

Use Case: User has connectivity and wants to execute a confidential escrow transaction with privacy guarantees.

Flow:

User Wallet          Solana RPC          Arcium Escrow          Solana Chain
    │                    │                      │                      │
    ├─ Create tx         │                      │                      │
    ├─ Sign tx           │                      │                      │
    │                    │                      │                      │
    ├─ Submit ──────────>│                      │                      │
    │                ├─ Validate               │                      │
    │                ├─ Process                │                      │
    │                └─ Forward ──────────────>│                      │
    │                                       ├─ Initialize escrow      │
    │                                       ├─ Lock funds in PDA      │
    │                                       ├─ Create MPC circuit     │
    │                                       ├─ Execute conditions     │
    │                                       └─ Submit settlement ─────>
    │                                           │                  ├─ Verify
    │                                           │                  ├─ Execute
    │                                           │                  └─ Confirm
    │                                           │                      │
    │                <─────────────────────────┴──────────────────────┘
    │                    Confirmation
    │<─ Settlement event─┤

Architecture:

┌─────────────────────────────────────────────────────────────┐
│                    Arcium Escrow System                      │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────────┐         ┌──────────────────┐         │
│  │  Escrow PDA      │         │   Arcium Network │         │
│  │                  │         │                  │         │
│  │  • Locked Funds  │◄────────┤  • Condition     │         │
│  │  • Conditions    │         │    Verification  │         │
│  │  • Parties       │         │  • Secret Shares │         │
│  │  • Timeout       │         │  • MPC Execution │         │
│  └──────────────────┘         └──────────────────┘          │
│           │                             │                    │
│           └─────────────┬───────────────┘                    │
│                         ▼                                    │
│                 Settlement Logic                             │
│                 (Executed in Circuit)                        │
└─────────────────────────────────────────────────────────────┘

Implementation:

import { 
  Transaction, 
  SystemProgram, 
  PublicKey,
  Connection 
} from '@solana/web3.js';
import { ArciumEscrow } from '@arcium/solana-sdk';

// 1. Initialize escrow transaction
async function createEscrowTransaction(
  sender: Keypair,
  recipient: PublicKey,
  amount: number,
  conditions: EscrowConditions
): Promise<Transaction> {
  
  const connection = new Connection('https://api.mainnet-beta.solana.com');
  
  // Create escrow PDA
  const [escrowPDA] = await PublicKey.findProgramAddress(
    [
      Buffer.from('escrow'),
      sender.publicKey.toBuffer(),
      recipient.toBuffer(),
    ],
    ArciumEscrow.PROGRAM_ID
  );
  
  // Build escrow initialization transaction
  const transaction = new Transaction().add(
    ArciumEscrow.initialize({
      escrow: escrowPDA,
      sender: sender.publicKey,
      recipient: recipient,
      amount: amount,
      conditions: {
        timeout: conditions.timeout,
        requiresAttestation: conditions.requiresAttestation,
        circuitId: conditions.circuitId,
      },
    }),
    SystemProgram.transfer({
      fromPubkey: sender.publicKey,
      toPubkey: escrowPDA,
      lamports: amount,
    })
  );
  
  // Get recent blockhash and sign
  transaction.recentBlockhash = (
    await connection.getRecentBlockhash()
  ).blockhash;
  transaction.feePayer = sender.publicKey;
  transaction.sign(sender);
  
  return transaction;
}

// 2. Submit to RPC and monitor escrow
async function submitEscrowTransaction(
  transaction: Transaction,
  connection: Connection
): Promise<EscrowResult> {
  
  // Submit transaction
  const signature = await connection.sendRawTransaction(
    transaction.serialize(),
    { skipPreflight: false }
  );
  
  console.log('Escrow initialized:', signature);
  
  // Wait for confirmation
  const confirmation = await connection.confirmTransaction(
    signature,
    'confirmed'
  );
  
  if (confirmation.value.err) {
    throw new Error('Escrow initialization failed');
  }
  
  // Monitor escrow state
  const escrowState = await monitorEscrowExecution(
    transaction,
    connection
  );
  
  return escrowState;
}

// 3. Arcium MPC circuit validates and settles
async function monitorEscrowExecution(
  transaction: Transaction,
  connection: Connection
): Promise<EscrowResult> {
  
  const escrowPDA = transaction.instructions[0].keys[0].pubkey;
  
  // Listen for Arcium circuit events
  const subscriptionId = connection.onAccountChange(
    escrowPDA,
    async (accountInfo) => {
      const escrowData = ArciumEscrow.decode(accountInfo.data);
      
      console.log('Escrow state:', escrowData.state);
      
      if (escrowData.state === 'SETTLED') {
        console.log('Settlement confirmed:', escrowData.settlementTx);
        return {
          settled: true,
          signature: escrowData.settlementTx,
          timestamp: escrowData.settledAt,
        };
      }
      
      if (escrowData.state === 'CANCELLED') {
        console.log('Escrow cancelled, funds returned');
        return {
          settled: false,
          reason: 'CANCELLED',
        };
      }
    },
    'confirmed'
  );
  
  // Timeout after 1 hour
  await new Promise(resolve => setTimeout(resolve, 3600000));
  connection.removeAccountChangeListener(subscriptionId);
}

// 4. Escrow conditions interface
interface EscrowConditions {
  timeout: number;              // Unix timestamp
  requiresAttestation: boolean; // Needs external proof
  circuitId: string;            // Arcium circuit to execute
  metadata?: any;               // Custom conditions
}

// Example usage
const conditions: EscrowConditions = {
  timeout: Date.now() + 86400000, // 24 hours
  requiresAttestation: true,
  circuitId: 'escrow-settlement-v1',
  metadata: {
    deliveryConfirmation: true,
    oracleVerification: true,
  },
};

const escrowTx = await createEscrowTransaction(
  senderKeypair,
  recipientPubkey,
  1000000000, // 1 SOL
  conditions
);

const result = await submitEscrowTransaction(escrowTx, connection);

Arcium Circuit Logic:

// Pseudocode for Arcium escrow settlement circuit
circuit EscrowSettlement {
    // Private inputs (encrypted)
    private sender: Pubkey
    private recipient: Pubkey
    private amount: u64
    private conditions: EscrowConditions
    
    // Public inputs
    public attestation: Signature
    public timestamp: i64
    
    // Computation
    fn execute() -> SettlementDecision {
        // 1. Verify attestation signature
        if !verify_signature(attestation, conditions.oracle_pubkey) {
            return SettlementDecision::Cancel;
        }
        
        // 2. Check timeout
        if timestamp > conditions.timeout {
            return SettlementDecision::Refund;
        }
        
        // 3. Validate conditions
        if conditions.requires_attestation && attestation.valid {
            return SettlementDecision::Settle {
                to: recipient,
                amount: amount,
            };
        }
        
        SettlementDecision::Cancel
    }
    
    // Output (public)
    public settlement: SettlementDecision
}

Privacy Guarantees:

  • Sender/Recipient Privacy: Encrypted in circuit, not visible on-chain

  • Amount Privacy: Hidden in escrow PDA until settlement

  • Condition Privacy: Circuit evaluates conditions

  • Oracle Privacy: Attestation verified without revealing source


Mode 2: BLE Mesh Relay (Offline-First)

For users without connectivity who want to queue transactions for relay.

Use Case: User is offline, creates and signs transaction, broadcasts via BLE mesh, online peer relays to Solana.

Flow:

Offline User          BLE Mesh          Online Peer        Solana RPC
    │                    │                    │                 │
    ├─ Create tx         │                    │                 │
├─ Add 2% (0.6% fee goes to relayer│           │                 │
    ├─ Sign with wallet  │                    │                 │
    ├─ Serialize packet  │                    │                 │
    │ (includes relay    │                    │                 │
    │  fee instruction)  │                    │                 │
    │                    │                    │                 │
    ├─ Broadcast BLE ───>│                    │                 │
    │                ├─ Relay (TTL)          │                 │
    │                └───────────────────────>│                 │
    │                                      ├─ Deserialize      │
    │                                      ├─ Validate sig     │
    │                                      ├─ Insert pubkey    │
    │                                      ├─ Check blockhash  │
    │                                      └─ Submit ──────────>
    │                                         │             ├─ Process
    │                                         │             ├─ Pay 2% fee
    │                                         │             └─ Confirm
    │                                         │                 │
    │                <─ Confirmation ─────────┴─────────────────┘
    │                    │ (with relay fee)   │
    │<─ Status update ───┤                    │

Packet Structure:

// BLE packet format for Solana transactions with relay fee
interface SolanaTransactionPacket {
  header: {
    magic: 0xAB0D;           // Protocol identifier
    version: 0x01;           // Protocol version
    type: PacketType.TRANSACTION;
    ttl: number;             // Remaining hops
    flags: PacketFlags;      // Priority, encryption, etc.
    length: number;          // Payload size
  };
  
  packetId: Uint8Array;      // 32 bytes - SHA256 hash
  
  payload: {
    transaction: Uint8Array; // Serialized Solana transaction with placeholder
    totalAmount: number;     // Original transfer amount
    relayFeePercent: number; // 2% (200 basis points)
    metadata: {
      network: 'mainnet' | 'devnet' | 'testnet';
      priority: TransactionPriority;
      expiresAt: number;     // Blockhash expiry
      createdAt: number;
      requiresRelayFee: boolean; // Must pay relay fee
    };
  };
  
  signature: Uint8Array;     // 64 bytes - ed25519 signature
}

Implementation:

import { Transaction, Connection, Keypair, SystemProgram, PublicKey } from '@solana/web3.js';
import { BLEAdapter } from './infrastructure/ble/BLEAdapter';
import { createPacket, PacketType } from './domain/Packet';

// Relay fee configuration
const RELAY_FEE_PERCENT = 0.006; // 0.6%
const RELAY_PLACEHOLDER_PUBKEY = new PublicKey('11111111111111111111111111111111');

// 1. Create transaction offline with relay fee
async function createOfflineTransactionWithRelayFee(
  sender: Keypair,
  recipient: PublicKey,
  amount: number,
  cachedBlockhash?: string
): Promise<{ transaction: Transaction; totalAmount: number }> {
  
  // Calculate relay fee (2% of amount)
  const relayFee = Math.floor(amount * RELAY_FEE_PERCENT);
  const recipientAmount = amount - relayFee;
  
  console.log(`Original amount: ${amount} lamports`);
  console.log(`Relay fee (0.6%): ${relayFee} lamports`);
  console.log(`Recipient gets: ${recipientAmount} lamports`);
  
  const transaction = new Transaction().add(
    // Main transfer to recipient
    SystemProgram.transfer({
      fromPubkey: sender.publicKey,
      toPubkey: recipient,
      lamports: recipientAmount,
    }),
    // Relay fee transfer (placeholder pubkey, will be replaced)
    SystemProgram.transfer({
      fromPubkey: sender.publicKey,
      toPubkey: RELAY_PLACEHOLDER_PUBKEY, // Replaced by first relayer
      lamports: relayFee,
    })
  );
  
  // Use cached blockhash if available, or placeholder
  transaction.recentBlockhash = cachedBlockhash || 
    '11111111111111111111111111111111';
  
  transaction.feePayer = sender.publicKey;
  transaction.sign(sender);
  
  return { transaction, totalAmount: amount };
}

// 2. Serialize and broadcast via BLE
async function broadcastViaBLE(
  transaction: Transaction,
  totalAmount: number,
  bleAdapter: BLEAdapter
): Promise<void> {
  
  // Serialize transaction
  const serialized = transaction.serialize({
    requireAllSignatures: false,
    verifySignatures: true,
  });
  
  // Create mesh packet with relay fee metadata
  const packet = createPacket({
    type: PacketType.TRANSACTION,
    payload: serialized,
    ttl: 8, // Maximum 8 hops
    priority: Priority.HIGH,
    metadata: {
      network: 'mainnet-beta',
      totalAmount: totalAmount,
      relayFeePercent: RELAY_FEE_PERCENT,
      requiresRelayFee: true,
      expiresAt: Date.now() + 120000, // 2 minutes
      createdAt: Date.now(),
    },
  });
  
  // Broadcast to mesh
  await bleAdapter.broadcast(packet);
  
  console.log('Transaction broadcasted via BLE mesh with relay fee');
  console.log(`First relayer will receive ${Math.floor(totalAmount * RELAY_FEE_PERCENT)} lamports`);
}

// 3. Online peer receives, inserts pubkey, and relays
class OnlinePeerRelayWithFee {
  private connection: Connection;
  private bleAdapter: BLEAdapter;
  private relayerKeypair: Keypair;
  private seenTransactions: Set<string>;
  
  constructor(rpcUrl: string, bleAdapter: BLEAdapter, relayerKeypair: Keypair) {
    this.connection = new Connection(rpcUrl);
    this.bleAdapter = bleAdapter;
    this.relayerKeypair = relayerKeypair;
    this.seenTransactions = new Set();
  }
  
  async startRelayService(): Promise<void> {
    console.log(`Relay service started. Relayer pubkey: ${this.relayerKeypair.publicKey.toBase58()}`);
    
    // Listen for transaction packets
    this.bleAdapter.on('packet', async (packet) => {
      if (packet.type !== PacketType.TRANSACTION) return;
      
      try {
        await this.relayTransactionWithFee(packet);
      } catch (error) {
        console.error('Relay failed:', error);
      }
    });
  }
  
  private async relayTransactionWithFee(packet: Packet): Promise<void> {
    // 1. Check for duplicates (first relayer wins)
    const txHash = hashPacket(packet);
    if (this.seenTransactions.has(txHash)) {
      console.log('Transaction already relayed by another peer');
      return;
    }
    this.seenTransactions.add(txHash);
    
    // 2. Deserialize transaction
    const transaction = Transaction.from(packet.payload);
    
    // 3. Validate signature
    if (!transaction.verifySignatures()) {
      throw new Error('Invalid transaction signature');
    }
    
    // 4. Check blockhash validity
    const metadata = packet.metadata;
    if (Date.now() > metadata.expiresAt) {
      console.log('Transaction expired, not relaying');
      return;
    }
    
    // 5. Check if relay fee is required
    if (!metadata.requiresRelayFee) {
      throw new Error('Transaction does not include relay fee');
    }
    
    // 6. Insert relayer pubkey into fee instruction
    const relayFeeAmount = Math.floor(metadata.totalAmount * metadata.relayFeePercent);
    
    // Find the relay fee instruction (second transfer instruction)
    const feeInstruction = transaction.instructions[1];
    if (feeInstruction.programId.equals(SystemProgram.programId)) {
      // Replace placeholder pubkey with actual relayer pubkey
      const keys = feeInstruction.keys;
      const recipientKey = keys.find(k => 
        k.pubkey.equals(RELAY_PLACEHOLDER_PUBKEY)
      );
      
      if (recipientKey) {
        recipientKey.pubkey = this.relayerKeypair.publicKey;
        console.log(`✅ Inserted relayer pubkey: ${this.relayerKeypair.publicKey.toBase58()}`);
        console.log(`💰 Relay fee: ${relayFeeAmount} lamports (${metadata.relayFeePercent * 100}%)`);
      }
    }
    
    // 7. Update blockhash if needed
    if (transaction.recentBlockhash === '11111111111111111111111111111111') {
      const { blockhash } = await this.connection.getRecentBlockhash();
      transaction.recentBlockhash = blockhash;
    }
    
    // 8. Submit to Solana RPC
    const signature = await this.connection.sendRawTransaction(
      transaction.serialize(),
      {
        skipPreflight: false,
        maxRetries: 3,
      }
    );
    
    console.log('Transaction submitted:', signature);
    console.log(`Relay fee will be paid to: ${this.relayerKeypair.publicKey.toBase58()}`);
    
    // 9. Wait for confirmation
    const confirmation = await this.connection.confirmTransaction(
      signature,
      'confirmed'
    );
    
    // 10. Broadcast confirmation back to mesh
    if (!confirmation.value.err) {
      await this.broadcastConfirmation(txHash, signature, relayFeeAmount);
    }
  }
  
  private async broadcastConfirmation(
    txHash: string,
    signature: string,
    relayFee: number
  ): Promise<void> {
    const confirmationPacket = createPacket({
      type: PacketType.TRANSACTION_CONFIRMATION,
      payload: encodeConfirmation({ 
        txHash, 
        signature,
        relayerPubkey: this.relayerKeypair.publicKey.toBase58(),
        relayFee: relayFee,
      }),
      ttl: 8,
      priority: Priority.NORMAL,
    });
    
    await this.bleAdapter.broadcast(confirmationPacket);
    console.log('Confirmation broadcasted to mesh');
  }
}

// 4. Offline user receives confirmation with relay fee info
class OfflineUserMonitor {
  private bleAdapter: BLEAdapter;
  private pendingTransactions: Map<string, TransactionMetadata>;
  
  async monitorConfirmations(): Promise<void> {
    this.bleAdapter.on('packet', (packet) => {
      if (packet.type !== PacketType.TRANSACTION_CONFIRMATION) return;
      
      const { txHash, signature, relayerPubkey, relayFee } = decodeConfirmation(packet.payload);
      
      const metadata = this.pendingTransactions.get(txHash);
      if (metadata) {
        console.log('✅ Transaction confirmed:', signature);
        console.log(`💰 Relay fee paid: ${relayFee} lamports to ${relayerPubkey}`);
        
        metadata.status = 'confirmed';
        metadata.signature = signature;
        metadata.relayerPubkey = relayerPubkey;
        metadata.relayFee = relayFee;
        
        // Notify user
        this.notifyUser(metadata);
      }
    });
  }
  
  private notifyUser(metadata: TransactionMetadata): void {
    // Update UI with relay fee information
    console.log('Transaction Details:');
    console.log(`  Signature: ${metadata.signature}`);
    console.log(`  Relayed by: ${metadata.relayerPubkey}`);
    console.log(`  Relay fee: ${metadata.relayFee} lamports (0.6%)`);
    console.log(`  Status: ${metadata.status}`);
  }
}

// Example usage
const bleAdapter = new BLEAdapter();

// Offline user creates transaction with relay fee
const { transaction, totalAmount } = await createOfflineTransactionWithRelayFee(
  userKeypair,
  recipientPubkey,
  1000000, // 1,000,000 lamports (0.001 SOL)
  cachedBlockhash
);
// Recipient will receive: 994,000 lamports (99.4%)
// Relayer will receive: 6,000 lamports (0.6%)

await broadcastViaBLE(transaction, totalAmount, bleAdapter);

// Online peer (relayer) with their keypair
const relayerKeypair = Keypair.generate(); // Or load existing keypair
const relay = new OnlinePeerRelayWithFee(
  'https://api.mainnet-beta.solana.com',
  bleAdapter,
  relayerKeypair
);
await relay.startRelayService();

// Monitor confirmations with relay fee info
const monitor = new OfflineUserMonitor(bleAdapter);
await monitor.monitorConfirmations();

Relay Fee Economics

Fee Structure

interface RelayFeeStructure {
  percentage: number;        // 0.6% (60 basis points)
  minimumFee: number;        // Minimum fee in lamports
  maximumFee: number;        // Maximum fee cap (optional)
  priorityMultiplier: number; // 1.5x for high priority
}

const RELAY_FEE_CONFIG: RelayFeeStructure = {
  percentage: 0.006,        // 0.6%
  minimumFee: 5000,         // 0.000005 SOL minimum
  maximumFee: 1000000,      // 0.001 SOL maximum (optional)
  priorityMultiplier: 1.0,  // No multiplier for standard
};

function calculateRelayFee(
  amount: number,
  priority: Priority = Priority.NORMAL
): number {
  let fee = Math.floor(amount * RELAY_FEE_CONFIG.percentage);
  
  // Apply priority multiplier
  if (priority === Priority.HIGH) {
    fee = Math.floor(fee * 1.5);
  } else if (priority === Priority.URGENT) {
    fee = Math.floor(fee * 2.0);
  }
  
  // Enforce minimum and maximum
  fee = Math.max(fee, RELAY_FEE_CONFIG.minimumFee);
  if (RELAY_FEE_CONFIG.maximumFee) {
    fee = Math.min(fee, RELAY_FEE_CONFIG.maximumFee);
  }
  
  return fee;
}

Example Fee Calculations

Transaction Amount
Relay Fee (0.6%)
Recipient Gets
Relayer Earns

0.001 SOL (1M lamports)

6,000 lamports

994,000 lamports

6,000 lamports

0.01 SOL (10M lamports)

60,000 lamports

9,940,000 lamports

60,000 lamports

0.1 SOL (100M lamports)

600,000 lamports

99,400,000 lamports

600,000 lamports

1 SOL (1B lamports)

6,000,000 lamports

994,000,000 lamports

6,000,000 lamports

Race Condition Handling

Multiple peers may receive the transaction simultaneously. First to submit wins:

class RelayCompetition {
  private submissionAttempts: Map<string, SubmissionAttempt[]>;
  
  async handleRaceCondition(txHash: string, relayerPubkey: PublicKey): Promise<boolean> {
    const attempts = this.submissionAttempts.get(txHash) || [];
    
    // Record attempt with timestamp
    attempts.push({
      relayerPubkey: relayerPubkey.toBase58(),
      timestamp: Date.now(),
    });
    
    this.submissionAttempts.set(txHash, attempts);
    
    // Try to submit
    try {
      await this.submitTransaction(txHash);
      console.log(`✅ Won relay race! Fee earned by ${relayerPubkey.toBase58()}`);
      return true;
    } catch (error) {
      if (error.message.includes('already processed')) {
        console.log(`❌ Lost relay race. Another peer was faster.`);
        return false;
      }
      throw error;
    }
  }
}

Anti-Spam Protection

class RelaySpamProtection {
  private relayHistory: Map<string, RelayHistory>;
  
  canRelayTransaction(
    peerId: string,
    transactionSize: number
  ): boolean {
    const history = this.relayHistory.get(peerId) || {
      successfulRelays: 0,
      failedRelays: 0,
      totalFeesEarned: 0,
      lastRelayTime: 0,
    };
    
    // Rate limit: max 100 relays per hour
    const hourAgo = Date.now() - 3600000;
    if (history.lastRelayTime > hourAgo) {
      const recentRelays = this.countRecentRelays(peerId, hourAgo);
      if (recentRelays >= 100) {
        return false;
      }
    }
    
    // Reputation check: success rate > 80%
    const totalRelays = history.successfulRelays + history.failedRelays;
    if (totalRelays > 10) {
      const successRate = history.successfulRelays / totalRelays;
      if (successRate < 0.8) {
        return false;
      }
    }
    
    return true;
  }
}

BLE-Specific Optimizations

Transaction Compression

import { compress, decompress } from 'lz4';

function compressTransaction(tx: Transaction): Uint8Array {
  const serialized = tx.serialize();
  const compressed = compress(serialized);
  
  console.log(`Original: ${serialized.length} bytes`);
  console.log(`Compressed: ${compressed.length} bytes`);
  console.log(`Savings: ${((1 - compressed.length / serialized.length) * 100).toFixed(1)}%`);
  
  return compressed;
}

function decompressTransaction(data: Uint8Array): Transaction {
  const decompressed = decompress(data);
  return Transaction.from(decompressed);
}

Fragmentation for Large Transactions

// BLE MTU typically 512 bytes, need to fragment larger transactions
class TransactionFragmenter {
  private readonly FRAGMENT_SIZE = 400; // Leave room for headers
  
  fragment(tx: Transaction): Uint8Array[] {
    const serialized = tx.serialize();
    const fragments: Uint8Array[] = [];
    
    for (let i = 0; i < serialized.length; i += this.FRAGMENT_SIZE) {
      const fragment = serialized.slice(i, i + this.FRAGMENT_SIZE);
      fragments.push(fragment);
    }
    
    return fragments;
  }
  
  reassemble(fragments: Uint8Array[]): Transaction {
    const totalLength = fragments.reduce((sum, f) => sum + f.length, 0);
    const reassembled = new Uint8Array(totalLength);
    
    let offset = 0;
    for (const fragment of fragments) {
      reassembled.set(fragment, offset);
      offset += fragment.length;
    }
    
    return Transaction.from(reassembled);
  }
}

Hybrid Approach: BLE + Arcium

Combine offline BLE relay with confidential Arcium execution.

Flow:

Offline User     BLE Mesh     Online Peer     Arcium Escrow     Solana
    │               │              │                 │              │
    ├─ Create       │              │                 │              │
    ├─ Encrypt      │              │                 │              │
    ├─ BLE ────────>│              │                 │              │
    │           ├─ Relay           │                 │              │
    │           └─────────────────>│                 │              │
    │                          ├─ Submit ───────────>│              │
    │                              │             ├─ Execute         │
    │                              │             └─────────────────>│
    │                              │                 │          ├─ Confirm
    │           <─ Confirmation ───┴─────────────────┴──────────────┘

Implementation:

// Create encrypted intent for Arcium
const intent = {
  type: 'escrow_transfer',
  params: {
    amount: 1000000,
    recipient: recipientPubkey.toBase58(),
    conditions: {
      timeout: Date.now() + 86400000,
      requiresAttestation: true,
    },
  },
};

// Encrypt for Arcium
const encrypted = await arciumClient.encrypt(
  JSON.stringify(intent),
  arciumNetworkPublicKey
);

// Broadcast via BLE
const packet = createPacket({
  type: PacketType.ARCIUM_INTENT,
  payload: encrypted,
  ttl: 12,
  priority: Priority.HIGH,
});

await bleAdapter.broadcast(packet);

Best Practices

Transaction Validation

function validateTransactionPacket(packet: Packet): boolean {
  // 1. Check packet structure
  if (!packet.header || !packet.payload) {
    return false;
  }
  
  // 2. Verify TTL
  if (packet.header.ttl <= 0) {
    return false;
  }
  
  // 3. Check expiry
  if (packet.metadata.expiresAt < Date.now()) {
    return false;
  }
  
  // 4. Validate transaction signature
  const transaction = Transaction.from(packet.payload);
  if (!transaction.verifySignatures()) {
    return false;
  }
  
  return true;
}

Rate Limiting

class RelayRateLimiter {
  private counters = new Map<string, { count: number; reset: number }>();
  private readonly LIMIT = 100; // 100 tx per hour
  private readonly WINDOW = 3600000; // 1 hour
  
  canRelay(peerId: string): boolean {
    const now = Date.now();
    const counter = this.counters.get(peerId);
    
    if (!counter || now > counter.reset) {
      this.counters.set(peerId, { count: 1, reset: now + this.WINDOW });
      return true;
    }
    
    if (counter.count >= this.LIMIT) {
      return false;
    }
    
    counter.count++;
    return true;
  }
}

Summary

Mode
Connectivity
Privacy
Latency
Use Case

Direct RPC → Arcium

Online

High

Low

Confidential escrow with immediate execution

BLE Mesh Relay

Offline

High (w/ Noise Protocol)

Medium

Offline transaction queuing and relay

Choose the appropriate mode based on your connectivity, privacy requirements, and latency tolerance.

Last updated