From 09bd70b348fe772304f53ac086c4cafd300cae139d0020741cb32f485228aee8 Mon Sep 17 00:00:00 2001 From: Minimons Date: Thu, 25 Jun 2026 19:43:45 +0200 Subject: [PATCH] 14: Add getProgramAccounts(...) API method --- .../impl/ref/SolanaBlockChainImpl.tjava | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/main/tjava/com/r35157/libs/solana/impl/ref/SolanaBlockChainImpl.tjava b/src/main/tjava/com/r35157/libs/solana/impl/ref/SolanaBlockChainImpl.tjava index fe308a5..938d4dd 100644 --- a/src/main/tjava/com/r35157/libs/solana/impl/ref/SolanaBlockChainImpl.tjava +++ b/src/main/tjava/com/r35157/libs/solana/impl/ref/SolanaBlockChainImpl.tjava @@ -331,6 +331,113 @@ public class SolanaBlockChainImpl implements SolanaBlockChain { ); } + @Override + public Set getProgramAccounts( + ΩSolanaProgramIdΩ programId, + Set filters + ) throws IOException, InterruptedException { + String jsonBody = createGetProgramAccountsBody(programId, filters); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(RPC_URL)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .build(); + + HttpResponse response = sendThrottled(request); + + if (response.statusCode() != 200) { + throw new IOException("RPC call failed: HTTP " + response.statusCode() + "\n" + response.body()); + } + + JsonNode root = objectMapper.readTree(response.body()); + + if (root.has("error")) { + throw new IOException("Solana RPC error: " + root.get("error").toPrettyString()); + } + + JsonNode result = root.path("result"); + + if (!result.isArray()) { + throw new IOException("getProgramAccounts response did not contain result array!"); + } + + Set accountInfos = new HashSet<>(); + + for (JsonNode accountNode : result) { + JsonNode pubkeyNode = accountNode.path("pubkey"); + JsonNode account = accountNode.path("account"); + + if (!pubkeyNode.isTextual()) { + throw new IOException("getProgramAccounts response contained account without textual pubkey!"); + } + + JsonNode ownerNode = account.path("owner"); + if (!ownerNode.isTextual()) { + throw new IOException("getProgramAccounts response contained account without textual owner!"); + } + + JsonNode dataNode = account.path("data"); + if (!dataNode.isArray() || dataNode.isEmpty() || !dataNode.get(0).isTextual()) { + throw new IOException("getProgramAccounts response contained account without base64 data!"); + } + + accountInfos.add(new SolanaAccountInfo( + pubkeyNode.asText(), + ownerNode.asText(), + dataNode.get(0).asText() + )); + } + + return Set.copyOf(accountInfos); + } + + private String createGetProgramAccountsBody( + ΩSolanaProgramIdΩ programId, + Set filters + ) throws IOException { + StringBuilder filtersJson = new StringBuilder(); + + boolean first = true; + for (SolanaProgramAccountMemcmpFilter filter : filters) { + if (!first) { + filtersJson.append(","); + } + + filtersJson.append(""" + { + "memcmp": { + "offset": %d, + "bytes": "%s" + } + } + """.formatted( + filter.offset(), + filter.bytes() + )); + + first = false; + } + + return """ + { + "jsonrpc": "2.0", + "id": 1, + "method": "getProgramAccounts", + "params": [ + "%s", + { + "commitment": "finalized", + "encoding": "base64", + "filters": [ + %s + ] + } + ] + } + """.formatted(programId, filtersJson); + } + private synchronized HttpResponse sendThrottled(HttpRequest request) throws IOException, InterruptedException { waitBeforeRemoteCall();