Solana Agave 2.0 upgrade reference

A quick reference on the Solana node client transition to Agave and the deprecated methods

In brief

Agave is the Rust-based Solana node client that is also a fork of the original Solana Labs client.

The primary purpose of Agave replacing the original Solana Labs client is make the network multi-client. The multi-client part mostly means the introduction of another node client — the C-based Firedancer.

Nodes are expected to upgrade to Agave v2.0 by October 21, 2024.

For you as a developer, this means there are a few methods that will be deprecated with other methods replacing them. The methods that are getting the deprecation have already been outdated for quite a bit of time and all of the replacement methods (except for isBlockhashValid) aren't new. So the chances are you won't be significantly affected.

See:

Methods: deprecated and replaced

Deprecated callReplacement call
confirmTransactiongetSignatureStatuses
getSignatureStatusgetSignatureStatuses
getSignatureConfirmationgetSignatureStatuses
getConfirmedSignaturesForAddressgetSignaturesForAddress
getConfirmedBlockgetBlock
getConfirmedBlocksgetBlocks
getConfirmedBlocksWithLimitgetBlocksWithLimit
getConfirmedTransactiongetTransaction
getConfirmedSignaturesForAddress2getSignaturesForAddress
getRecentBlockhashgetLatestBlockhash
getFeesgetFeeForMessage
getFeeCalculatorForBlockhashisBlockhashValid or getFeeForMessage
getFeeRateGovernorgetFeeForMessage
getSnapshotSlotgetHighestSnapshotSlot
getStakeActivationgetAccountInfo | Solana (alternative approach)

Replacing confirmTransaction with getSignaturesStatuses

import { Connection, Transaction, SendOptions } from '@solana/web3.js';

async function sendAndConfirmTransaction(
  connection: Connection,
  transaction: Transaction,
  timeout: number = 30000, // 30 seconds default timeout
  options?: SendOptions
): Promise<string> {
  // Send the transaction
  const signature = await connection.sendTransaction(transaction, options?.signers || [], {
    ...options,
    skipPreflight: options?.skipPreflight || false
  });

  // Create a promise that will reject after the timeout
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(`Transaction confirmation timeout after ${timeout}ms`));
    }, timeout);
  });

  // Create the confirmation promise
  const confirmationPromise = (async () => {
    let done = false;
    while (!done) {
      // Get the status of the transaction
      const response = await connection.getSignatureStatuses([signature]);
      const status = response.value[0];

      if (status) {
        if (status.err) {
          throw new Error(`Transaction failed: ${status.err.toString()}`);
        }

        // Check if we have enough confirmations
        if (status.confirmationStatus === 'finalized') {
          done = true;
          return signature;
        }
      }

      // Wait a bit before checking again
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  })();

  // Race between timeout and confirmation
  try {
    return await Promise.race([confirmationPromise, timeoutPromise]);
  } catch (error) {
    // If it's a timeout error, we should still return the signature
    if (error.message.includes('timeout')) {
      return signature;
    }
    throw error;
  }
}

// Example usage
async function example() {
  const connection = new Connection('CHAINSTACK_NODE');
  const transaction = new Transaction();
  // ... add your transaction instructions here ...

  try {
    const signature = await sendAndConfirmTransaction(connection, transaction, 60000, {
      skipPreflight: false,
      // ... other options
    });
    console.log('Transaction confirmed:', signature);
  } catch (error) {
    console.error('Transaction failed:', error);
  }
}
from solana.rpc.api import Client
from solana.transaction import Transaction
from solana.rpc.commitment import Commitment
from typing import Optional, List, Union
import time
import asyncio
from concurrent.futures import TimeoutError

class TransactionConfirmationError(Exception):
    """Custom exception for transaction confirmation errors"""
    pass

async def send_and_confirm_transaction(
    client: Client,
    transaction: Transaction,
    signers: List,
    timeout: int = 30,  # timeout in seconds
    commitment: Union[str, Commitment] = "finalized"
) -> str:
    """
    Send and confirm a transaction using getSignatureStatuses
    
    Args:
        client: Solana client instance
        transaction: Transaction to send
        signers: List of signers for the transaction
        timeout: Maximum time to wait for confirmation in seconds
        commitment: Commitment level to use
    
    Returns:
        Transaction signature
    
    Raises:
        TransactionConfirmationError: If transaction fails or times out
    """
    
    # Send the transaction
    try:
        response = client.send_transaction(
            transaction,
            *signers,
            opts={
                "skip_preflight": False,
                "preflight_commitment": commitment
            }
        )
        signature = response["result"]
    except Exception as e:
        raise TransactionConfirmationError(f"Failed to send transaction: {str(e)}")

    # Start time for timeout tracking
    start_time = time.time()

    async def confirm_transaction() -> str:
        while True:
            try:
                # Check if we've exceeded timeout
                if time.time() - start_time > timeout:
                    raise TimeoutError(f"Transaction confirmation timeout after {timeout} seconds")

                # Get transaction status
                response = client.get_signature_statuses([signature])
                
                if response["result"]["value"][0] is None:
                    # Transaction not yet processed, wait and retry
                    await asyncio.sleep(1)
                    continue

                status = response["result"]["value"][0]

                # Check if transaction failed
                if status.get("err"):
                    raise TransactionConfirmationError(
                        f"Transaction failed: {status['err']}"
                    )

                # Check confirmation status
                conf_status = status.get("confirmationStatus")
                if conf_status == "finalized":
                    return signature

                # Wait before checking again
                await asyncio.sleep(1)

            except TimeoutError:
                # Return signature even on timeout
                return signature
            except Exception as e:
                if not isinstance(e, TimeoutError):
                    raise TransactionConfirmationError(f"Confirmation failed: {str(e)}")

    try:
        return await confirm_transaction()
    except Exception as e:
        raise TransactionConfirmationError(str(e))

# Example usage
async def example():
    # Initialize client
    client = Client("CHAINSTACK_NODE")
    
    # Create and populate your transaction
    transaction = Transaction()
    # ... add your transaction instructions here ...
    
    try:
        signature = await send_and_confirm_transaction(
            client=client,
            transaction=transaction,
            signers=[],  # Add your signers here
            timeout=60,  # 60 seconds timeout
            commitment="finalized"
        )
        print(f"Transaction confirmed: {signature}")
    except TransactionConfirmationError as e:
        print(f"Transaction failed: {str(e)}")

# Run the example
if __name__ == "__main__":
    asyncio.run(example())