GitHub Repository

Dependencies

RPC Call Weights

RPC Call Weights Construction

Used to match the request capacity available for a given RPC provider.

var jsonConfig = """
    {
      "getProgramAccounts": 2,
      "getTransaction": 5,
      "sendTransaction": 10
    }
    """;

var ji = JsonIterator.parse(jsonConfig);

var rpcCallWeights = CallWeights.parse(ji);

RPC Caller

Ties together a Load Balancer, RPC Call Weights and an Executor Service to make using remote calls more convenient.

RPC Caller Construction

LoadBalancer<SolanaRpcClient> loadBalancer; // See LoadBalancer in Ravina Core
var rpcCallWeights = CallWeights.createDefault();
var executorService = Executors.newVirtualThreadPerTaskExecutor();

var rpcCaller = new RpcCaller(executorService, loadBalancer, rpcCallWeights);

var getEpochInfoCallContext = CallContext.createContext(
    1,    // Call weight.
    0,    // Minimum capacity required before sending.
    1,    // Maximum times to try to claim capacity.
    true, // Eventually force the call.
    0,    // Maximum times to retry call.
    true  // Measure the call time to be tracked by the load balancer for prioritizing items.
);

var epochInfo = rpcCaller.courteousGet(
    SolanaRpcClient::getEpochInfo,
    getEpochInfoCallContext,
    "rpcClient::getEpochInfo"
);

Epoch Info Service

Periodically polls for slot performance stats and epoch information to create an Epoch data record which provides the following features:

  • Duration remaining for a given time unit.
  • Percent complete.
  • Epochs per year.
  • The current slot index.
  • The current block height accounting for a skip rate.
  • SlotPerformanceStats

Epoch Service Configuration

  • defaultMillisPerSlot: Used if performance samples are not available.
  • minMillisPerSlot: Performance samples will be capped to greater than or equal to this value.
  • maxMillisPerSlot: Performance samples will be capped to less than or equal to this value.
  • slotSampleWindow: The recent time window to draw performance samples.
  • fetchSlotSamplesDelay: Poll new performance samples and epoch information delay.
  • fetchEpochInfoAfterEndDelay: Also fetch new performance samples and epoch information after each epoch rollover.
{
  "defaultMillisPerSlot": 410,
  "minMillisPerSlot": 390,
  "maxMillisPerSlot": 500,
  "slotSampleWindow": "13M",
  "fetchSlotSamplesDelay": "8M",
  "fetchEpochInfoAfterEndDelay": "0.5S"
}

Epoch Info Service Usage

RpcCaller rpcCaller; // See the documentation above.

var jsonConfig = "null";
var epochServiceConfig = EpochServiceConfig.parseConfig(ji);
if (epochServiceConfig == null) {
  epochServiceConfig = EpochServiceConfig.createDefault();
}
var epochInfoService = EpochInfoService.createService(epochServiceConfig, rpcCaller);

var executorService = Executors.newSingleThreadExecutor();
executorService.execute(epochInfoService);

epochInfoService.awaitInitialized();

var epochInfo = epochInfoService.epochInfo();
System.out.println(epochInfo);

Web Socket Manager

Used to manage the connection of a SolanaRpcWebsocket. Call checkConnection to drive re-connections. If errors are observed, the corresponding delay from the provided Backoff will be respected.

Pseudo Web Socket Manager Usage

var backoff = Backoff.exponential(1, 32);
var account = PublicKey.fromBase58Encoded("");

var httpClient = HttpClient.newHttpClient();
var webSocketManager = WebSocketManager.createManager(
    httpClient,
    URI.create("wss://url"),
    backoff,
    webSocket -> webSocket.accountSubscribe(account, System.out::println)
);

for (;;) {
    webSocketManager.checkConnection();
    Thread.sleep(1_000);
}

Web Socket Manager Configuration

  • endpoint: websocket url.
  • backoff: Used for connection attempts in response to failures.

Chain Item Formatter

Used to provide more convenient logging of accounts and transaction hashes.

Formatter Usage

var jsonConfig = """
    {
      "sig": "https://solscan.io/tx/%s",
      "address": "https://solscan.io/account/%s"
    }
    """;
var ji = JsonIterator.parse(jsonConfig);
var formatter = ChainItemFormatter.parseFormatter(ji);
System.out.println(formatter.formatSig("5yem9DTy4mHa94yKVCSuQYixvtt27aF2L2bcms8jsgv2Yvgj2FY4DXYpUmN5TBvRX8xC99R6ea1k5eJJb2hVGmxF"));

Lookup Table Cache

May be used to dynamically fetch lookup tables. Ideally used in conjunction with a websocket to maintain the latest view of a table. The current implementation only removes tables if they are no longer active or no longer exist on-chain.

Lookup Table Cache Usage

var taskExecutor = Executors.newVirtualThreadPerTaskExecutor();
var tableCache = LookupTableCache.createCache(
    taskExecutor,
    128, // initial map capacity
    rpcCaller.rpcClients(),
    AddressLookupTable.FACTORY
);

AddressLookupTable table = tableCache.getOrFetchTable(PublicKey.fromBase58Encoded(""));

Transaction Processor

Convenience methods for creating, simulating, signing and sending transactions.

Transaction Processor Construction

SigningService signingService; // See the Sava KMS project.
var servicePublicKey = signingService.publicKey().join();

WebSocketManager webSocketManager; // See the documentation above.

LoadBalancer<SolanaRpcClient> rpcClients; // See LoadBalancer in Ravina Core
LoadBalancer<SolanaRpcClient> sendClients; // Used only for sending transactions.
LoadBalancer<FeeProvider> feeProviders; // Provides priority fees given a transaction.
var callWeights = CallWeights.createDefault();

var formatter = ChainItemFormatter.createDefault();
var solanaAccounts = SolanaAccounts.MAIN_NET;

var taskExecutor = Executors.newVirtualThreadPerTaskExecutor();

var tableCache = LookupTableCache.createCache(taskExecutor, 128, rpcCaller.rpcClients());

var transactionProcessor = TransactionProcessor.createProcessor(
    taskExecutor,
    signingService,
    tableCache,
    servicePublicKey,
    solanaAccounts,
    formatter,
    rpcClients,
    sendClients,
    feeProviders,
    callWeights,
    webSocketManager
);

Transaction Monitor Service

Provides functionality for tracking transaction confirmation via asynchronous and polling signature status RPC calls. Up to 256 transaction signatures are checked for each polling call. It is recommended to use this with the Instruction or Batch Instruction Services, rather than directly.

Transaction Monitor Service Construction

// See the documentation above for construction of these components.
WebSocketManager webSocketManager; 
RpcCaller rpcCaller;
EpochInfoService epochInfoService;

// TransactionProcessor extends this interface, otherwise a simpler implementation may be provided.
TxPublisher txPublisher;

var formatter = ChainItemFormatter.createDefault();

var txMonitorJsonConfig = """
    {
      "webSocketConfirmationTimeout": "5S",
      "minSleepBetweenSigStatusPolling": "3S",
      "retrySendDelay": "5S",
      "minBlocksRemainingToResend": 8
    }""";
var ji = JsonIterator.parse(txMonitorJsonConfig);

var txMonitorConfig = TxMonitorConfig.parseConfig(ji);

var txMonitorService = TxMonitorService.createService(
    formatter,
    rpcCaller,
    epochInfoService,
    webSocketManager,
    txMonitorConfig,
    txPublisher
);

Instruction Service

Handles transaction simulation, creation, and confirmation/expiration monitoring.

The transaction is simulated against a Confirmed state.

The blockhash and its height is taken from the simulation result to enable expiration tracking.

After sending a transaction, a confirmed state signature status Web Socket subscription is made, which expires based on the Transaction Monitor configuration.

If a response message is received over the Web Socket and a finalized state is desired an additional subscription is made, which expires two times the estimated duration of 32 blocks.

If the Web Socket subscriptions time out the service falls back to polling signature status calls. The monitor will make at least one call without transaction history and optionally continue to do so until the transactions’ block has expired. After which one final call is made with transaction history enabled to ensure the transaction did not land but was expired from the recent history cache.

While waiting for expiration the transaction may optionally be re-sent based on the Transaction Monitor configuration.

Instruction Service Usage

// See the documentation above for construction of these components.
EpochInfoService epochInfoService;
RpcCaller rpcCaller;
TransactionProcessor transactionProcessor;
TxMonitorService txMonitorService;

var nativeProgramClient = NativeProgramClient.createClient();

var instructionService = InstructionService.createService(
    rpcCaller,
    transactionProcessor,
    nativeProgramClient,
    epochInfoService,
    txMonitorService
);

// Populated from the Jupiter API.
List<Instruction> jupiterSwapInstructions;
List<PublicKey> lookupTableKeys;

var maxSOLPriorityFee = new BigDecimal("0.001");
var maxLamportPriorityFee = LamportDecimal.fromBigDecimal(maxSOLPriorityFee);

var transactionResult = instructionService.processInstructions(
    1.1, // Compute budget multiplier applied against simulation consumption.
    jupiterSwapInstructions,
    maxLamportPriorityFee, // Caps the priority fee from the Transaction Processor's Fee Provider.
    FINALIZED, // Await finalization if successful.
    CONFIRMED, // Only await confirmed if fails, e.g., slippage exceeded.
    true, // Verify expired, if the network is not aware of the transaction wait 151 blocks to ensure it cannot be applied.
    true, // Continue to re-send the transaction based on the Transaction Monitor configuration.
    0, // Number of times to re-construct the transaction with a new block hash after expiration.
    transactionProcessor.transactionFactory(lookupTableKeys), // Uses an internal address lookup table cache.
    "jupiter swap" // log context.
);

Batch Instruction Service

Extends the Instruction Service functionality by dynamically batching multiple instructions which may exceed the size of a single transaction into multiple transactions.