/** * Reconciliation and Settlement Module for TOSS * * Implements Section 9-10 of the TOSS Technical Paper: * - Synchronisation and reconciliation with onchain state * - Deterministic failure handling * - Conflict resolution */ import { Connection, PublicKey, Transaction, SystemProgram, } from '@solana/web3.js'; import type { SolanaIntent, IntentStatus } from './intent'; import { isIntentExpired } from './intent'; import { secureStoreIntent, getAllSecureIntents, } from './storage/secureStorage'; import { TossError, NetworkError } from './errors'; // Helper for logging during reconciliation const msg = (message: string) => { if (typeof console !== 'undefined') { console.log(`[TOSS Reconciliation] ${message}`); } }; /** * Result of intent settlement attempt */ export interface SettlementResult { intentId: string; status: 'success' | 'failed' | 'rejected'; signature?: string; error?: string; timestamp: number; } /** * State of a device's reconciliation with onchain */ export interface ReconciliationState { lastSyncTime: number; lastSyncSlot: number; processedIntents: string[]; // Intent IDs that were successfully settled failedIntents: string[]; // Intent IDs that failed or were rejected conflictingIntents: string[]; // Intent IDs with detected conflicts } /** * Validates an intent can be settled with current onchain state */ export async function validateIntentOnchain( intent: SolanaIntent, connection: Connection ): Promise<{ valid: boolean; error?: string }> { try { // Check if intent has expired if (isIntentExpired(intent)) { return { valid: false, error: 'Intent has expired', }; } // Fetch sender account info const senderPublicKey = new PublicKey(intent.from); const senderAccountInfo = await connection.getAccountInfo(senderPublicKey); if (!senderAccountInfo) { return { valid: false, error: 'Sender account does not exist', }; } // GAP #3 FIX: Check if sender is a program account (cannot be source of transfer) if (senderAccountInfo.executable) { return { valid: false, error: 'Sender is a program account and cannot send funds', }; } // Validate sender has sufficient balance if (senderAccountInfo.lamports < intent.amount) { return { valid: false, error: `Insufficient balance: have ${senderAccountInfo.lamports}, need ${intent.amount}`, }; } // GAP #3 FIX: Check if sender is frozen (token account freezing) if (senderAccountInfo.data && senderAccountInfo.data.length > 0) { // If account has data, it might be a token account - check frozen status // Token account structure: owner (32) + mint (32) + owner (32) + amount (8) + decimals (1) + isInitialized (1) + isFrozen (1) if (senderAccountInfo.data.length >= 106) { const isFrozen = senderAccountInfo.data[105] !== 0; if (isFrozen) { return { valid: false, error: 'Sender account is frozen and cannot send funds', }; } } } // Validate recipient exists (if not a system account) const recipientPublicKey = new PublicKey(intent.to); const recipientAccountInfo = await connection.getAccountInfo(recipientPublicKey); if (!recipientAccountInfo && intent.amount > 0) { // Recipient account doesn't exist - this is okay, will be created by transfer // But we should verify it's a valid public key format (already done above) } // GAP #3 FIX: Validate nonce account if using durable nonce if (intent.nonceAccountAddress && intent.nonceAuth) { const nonceAddress = new PublicKey(intent.nonceAccountAddress); const nonceAccountInfo = await connection.getAccountInfo(nonceAddress); if (!nonceAccountInfo) { return { valid: false, error: 'Nonce account does not exist', }; } // Check nonce account is owned by SystemProgram const SYSTEM_PROGRAM_ID = new PublicKey( '11111111111111111111111111111111' ); if (!nonceAccountInfo.owner.equals(SYSTEM_PROGRAM_ID)) { return { valid: false, error: 'Nonce account is not owned by SystemProgram', }; } } // Fetch recent transactions to check for double-spend const signatures = await connection.getSignaturesForAddress( senderPublicKey, { limit: 100, } ); // Check if this nonce has been used recently for (const sig of signatures) { const tx = await connection.getParsedTransaction(sig.signature); if (tx?.transaction.message) { // Check if nonce matches and transaction was successful const instructions = tx.transaction.message.instructions; for (const instruction of instructions) { // Look for SystemProgram transfers with same nonce if ( 'parsed' in instruction && instruction.parsed?.type === 'transfer' ) { const parsedIx = instruction.parsed; if ( parsedIx.info?.source === intent.from && parsedIx.info?.destination === intent.to ) { // Check if this is a duplicate if (tx.slot && tx.blockTime) { const timeDiff = Math.floor(Date.now() / 1000) - tx.blockTime; // If transaction was confirmed within the nonce window, it's a potential conflict if (timeDiff < 5 * 60) { return { valid: false, error: `Potential double-spend detected: similar transaction already confirmed`, }; } } } } } } } return { valid: true }; } catch (error) { return { valid: false, error: `Onchain validation failed: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * Builds a Solana transaction from an intent */ export async function buildTransactionFromIntent( intent: SolanaIntent, connection: Connection, feePayer?: PublicKey ): Promise { try { const senderPublicKey = new PublicKey(intent.from); const recipientPublicKey = new PublicKey(intent.to); const feePayerPubkey = feePayer || senderPublicKey; // Get latest blockhash const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed'); // Create transfer instruction const transferInstruction = SystemProgram.transfer({ fromPubkey: senderPublicKey, toPubkey: recipientPublicKey, lamports: intent.amount, }); // Create transaction const transaction = new Transaction(); transaction.add(transferInstruction); // If using nonce account, add nonce advance instruction if (intent.nonceAccount && intent.nonceAuth) { const nonceAccountPubkey = new PublicKey(intent.nonceAccount); const nonceAuthPubkey = new PublicKey(intent.nonceAuth); const nonceAdvanceInstruction = SystemProgram.nonceAdvance({ noncePubkey: nonceAccountPubkey, authorizedPubkey: nonceAuthPubkey, }); transaction.add(nonceAdvanceInstruction); } transaction.feePayer = feePayerPubkey; transaction.recentBlockhash = blockhash; transaction.lastValidBlockHeight = lastValidBlockHeight; return transaction; } catch (error) { throw new TossError( `Failed to build transaction from intent: ${error instanceof Error ? error.message : String(error)}`, 'TRANSACTION_BUILD_FAILED' ); } } /** * Submits a transaction to the network with confirmation */ export async function submitTransactionToChain( transaction: Transaction, connection: Connection, maxRetries: number = 3 ): Promise { let lastError: Error | null = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { // Serialize and send transaction const rawTransaction = transaction.serialize(); const signature = await connection.sendRawTransaction(rawTransaction, { skipPreflight: false, preflightCommitment: 'confirmed', }); // Wait for confirmation const confirmation = await connection.confirmTransaction( signature, 'confirmed' ); if (confirmation.value.err) { throw new Error( `Transaction failed: ${JSON.stringify(confirmation.value.err)}` ); } return signature; } catch (error) { lastError = error as Error; // Don't retry if it's a signature error (transaction already processed) if (lastError.message?.includes('Signature verification failed')) { throw lastError; } // Exponential backoff if (attempt < maxRetries) { const delay = 1000 * Math.pow(2, attempt - 1); await new Promise((resolve) => setTimeout(resolve, delay)); } } } throw new NetworkError( `Failed to submit transaction after ${maxRetries} attempts: ${lastError?.message}`, { context: 'submitTransactionToChain', cause: lastError, } ); } /** * GAP #7 FIX: Submit transaction to Arcium MXE program for confidential execution * Per TOSS Paper Section 7: "Arcium operates strictly before onchain execution" */ export async function submitTransactionToArciumMXE( intent: SolanaIntent, connection: Connection, mxeProgramId: PublicKey, provider: any, // AnchorProvider maxRetries: number = 3 ): Promise { if (!intent.encrypted) { throw new Error( 'Intent must be encrypted with Arcium data to submit to MXE' ); } try { // GAP #7 FIX: Actual Arcium MXE Integration // Per TOSS Paper Section 7: "Arcium operates strictly before onchain execution" // Import Arcium helper for confidential computation const { encryptForArciumInternal } = await import('./internal/arciumHelper'); // Extract sensitive intent parameters for encryption const plaintextValues = [ BigInt(intent.amount), BigInt(intent.nonce), BigInt(intent.expiry), ]; // Encrypt parameters with Arcium const encrypted = await encryptForArciumInternal( mxeProgramId, plaintextValues, provider ); msg?.(' Intent parameters encrypted with Arcium MXE'); // PRODUCTION: Build MXE submission instruction // Per TOSS Paper Section 7: "Arcium operates strictly before onchain execution" // The MXE program will: // 1. Receive encrypted intent data // 2. Decrypt inside trusted execution environment // 3. Validate constraints privately // 4. Execute the transfer instruction confidentially // 5. Return encrypted result only owner can decrypt // Serialize encrypted data for MXE program instruction const encryptedDataBuffer = Buffer.concat([ // Ephemeral public key (32 bytes) Buffer.from(encrypted.publicKey), // Nonce (16 bytes) Buffer.from(encrypted.nonce), // Ciphertext - serialize each field Buffer.from( JSON.stringify({ amount: encrypted.ciphertext[0], nonce: encrypted.ciphertext[1], expiry: encrypted.ciphertext[2], }) ), ]); msg?.( ' Encrypted data prepared for MXE program (size: ' + encryptedDataBuffer.length + ' bytes)' ); // PRODUCTION: Create MXE instruction with encrypted metadata // This instruction invokes the MXE program to execute the transfer privately const mxeInstruction: any = { programId: mxeProgramId, keys: [ { pubkey: intent.from, isSigner: true, isWritable: true }, // Payer { pubkey: intent.to, isSigner: false, isWritable: true }, // Recipient { pubkey: provider.wallet.publicKey, isSigner: true, isWritable: false, }, // Intent signer ], data: encryptedDataBuffer, }; msg?.( ' Submitting encrypted intent to MXE program for confidential execution' ); // PRODUCTION: Build transaction with MXE instruction // The MXE program receives encrypted intent, decrypts privately, and executes const mxeTransaction = new (await import('@solana/web3.js')).Transaction(); // Add the encrypted MXE instruction mxeTransaction.add({ programId: mxeInstruction.programId, keys: mxeInstruction.keys, data: mxeInstruction.data, }); // Set transaction metadata const latestBlockhash = await connection.getLatestBlockhash('confirmed'); mxeTransaction.recentBlockhash = latestBlockhash.blockhash; mxeTransaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight; mxeTransaction.feePayer = provider.wallet.publicKey; // PRODUCTION: Submit encrypted transaction to network // Network validators verify signature but cannot see unencrypted intent details const mxeSignature = await submitTransactionToChain( mxeTransaction, connection, maxRetries ); msg?.(' MXE transaction submitted - encrypted execution in progress'); msg?.(' Signature: ' + mxeSignature); msg?.(' Intent details remain confidential until settlement'); return mxeSignature; } catch (error) { throw new TossError( `Failed to submit transaction to Arcium MXE: ${error instanceof Error ? error.message : String(error)}`, 'ARCIUM_SUBMISSION_FAILED' ); } } /** * Attempts to settle a single intent and returns the result */ export async function settleIntent( intent: SolanaIntent, connection: Connection, feePayer?: PublicKey ): Promise { const timestamp = Math.floor(Date.now() / 1000); try { // Validate intent against onchain state const validation = await validateIntentOnchain(intent, connection); if (!validation.valid) { return { intentId: intent.id, status: 'rejected', error: validation.error, timestamp, }; } // Build transaction from intent const transaction = await buildTransactionFromIntent( intent, connection, feePayer ); // Submit transaction to chain const signature = await submitTransactionToChain(transaction, connection); return { intentId: intent.id, status: 'success', signature, timestamp, }; } catch (error) { return { intentId: intent.id, status: 'failed', error: error instanceof Error ? error.message : String(error), timestamp, }; } } /** * Core reconciliation: process all pending intents for settlement */ export async function reconcilePendingIntents( connection: Connection, feePayer?: PublicKey ): Promise { try { // Fetch all pending intents from storage const allIntents = await getAllSecureIntents(); const pendingIntents = allIntents.filter((i) => i.status === 'pending'); if (pendingIntents.length === 0) { return []; } // Sort by creation time to maintain ordering pendingIntents.sort((a, b) => a.createdAt - b.createdAt); // Settle each intent and collect results const settlementResults: SettlementResult[] = []; for (const intent of pendingIntents) { const result = await settleIntent(intent, connection, feePayer); settlementResults.push(result); // Update intent status based on settlement result let newStatus: IntentStatus; let error: string | undefined; switch (result.status) { case 'success': newStatus = 'settled'; break; case 'rejected': newStatus = 'failed'; error = result.error; break; case 'failed': newStatus = 'failed'; error = result.error; break; } // Update the intent in storage const updatedIntent: SolanaIntent = { ...intent, status: newStatus, updatedAt: Math.floor(Date.now() / 1000), error, signatures: result.signature ? [result.signature] : undefined, }; await secureStoreIntent(updatedIntent); } return settlementResults; } catch (error) { throw new NetworkError( `Reconciliation failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error } ); } } /** * Detects conflicts between local intents and onchain state */ export async function detectConflicts( connection: Connection ): Promise<{ intentId: string; conflict: string }[]> { const conflicts: { intentId: string; conflict: string }[] = []; try { const allIntents = await getAllSecureIntents(); for (const intent of allIntents) { // Skip already settled or failed intents if (intent.status !== 'pending') continue; // Check for conflicts const validation = await validateIntentOnchain(intent, connection); if (!validation.valid) { conflicts.push({ intentId: intent.id, conflict: validation.error || 'Unknown conflict', }); } } return conflicts; } catch (error) { throw new NetworkError('Conflict detection failed', { cause: error }); } } /** * Gets reconciliation state summary */ export async function getReconciliationState( connection: Connection ): Promise { const allIntents = await getAllSecureIntents(); const slot = await connection.getSlot('confirmed'); return { lastSyncTime: Math.floor(Date.now() / 1000), lastSyncSlot: slot, processedIntents: allIntents .filter((i) => i.status === 'settled') .map((i) => i.id), failedIntents: allIntents .filter((i) => i.status === 'failed') .map((i) => i.id), conflictingIntents: allIntents .filter((i) => isIntentExpired(i) && i.status === 'pending') .map((i) => i.id), }; }