diff --git a/core/src/main/java/software/sava/core/tx/TransactionSkeleton.java b/core/src/main/java/software/sava/core/tx/TransactionSkeleton.java index e5a0f76..7bdf3b8 100644 --- a/core/src/main/java/software/sava/core/tx/TransactionSkeleton.java +++ b/core/src/main/java/software/sava/core/tx/TransactionSkeleton.java @@ -3,6 +3,7 @@ import software.sava.core.accounts.PublicKey; import software.sava.core.accounts.lookup.AddressLookupTable; import software.sava.core.accounts.meta.AccountMeta; +import software.sava.core.programs.Discriminator; import java.util.Arrays; import java.util.Map; @@ -166,6 +167,8 @@ default AccountMeta[] parseAccounts(final Stream lookupTable AccountMeta[] parseAccounts(final AddressLookupTable lookupTable); + PublicKey[] parseProgramAccounts(); + int serializedInstructionsLength(); Instruction[] parseInstructions(final AccountMeta[] accounts); @@ -174,11 +177,23 @@ default Instruction[] parseLegacyInstructions() { return parseInstructions(parseAccounts()); } - PublicKey[] parseProgramAccounts(); - + /** + * Program accounts will be included for each instruction. + * Instruction accounts will not. + */ Instruction[] parseInstructionsWithoutAccounts(); - Instruction[] parseInstructionsWithAccounts(); - + /** + * If this is a versioned transaction accounts which are indexed into a lookup table will be null. + * Signing accounts and program accounts will always be included. + */ Instruction[] parseInstructionsWithoutTableAccounts(); + + Instruction[] filterInstructions(final AccountMeta[] accounts, final Discriminator discriminator); + + default Instruction[] filterInstructionsWithoutTableAccounts(final Discriminator discriminator) { + return filterInstructions(parseAccounts(), discriminator); + } + + Instruction[] filterInstructionsWithoutAccounts(final Discriminator discriminator); } diff --git a/core/src/main/java/software/sava/core/tx/TransactionSkeletonRecord.java b/core/src/main/java/software/sava/core/tx/TransactionSkeletonRecord.java index 982c6eb..2be30e3 100644 --- a/core/src/main/java/software/sava/core/tx/TransactionSkeletonRecord.java +++ b/core/src/main/java/software/sava/core/tx/TransactionSkeletonRecord.java @@ -4,6 +4,7 @@ import software.sava.core.accounts.lookup.AddressLookupTable; import software.sava.core.accounts.meta.AccountMeta; import software.sava.core.encoding.Base58; +import software.sava.core.programs.Discriminator; import java.util.Arrays; import java.util.List; @@ -207,7 +208,7 @@ public Instruction[] parseInstructions(final AccountMeta[] accounts) { o += getByteLen(data, o); for (int a = 0; a < numIxAccounts; ++a) { accountIndex = data[o++] & 0xFF; - ixAccounts[a] = accounts[accountIndex]; + ixAccounts[a] = accountIndex < accounts.length ? accounts[accountIndex] : null; } final int len = decode(data, o); @@ -218,13 +219,21 @@ public Instruction[] parseInstructions(final AccountMeta[] accounts) { return instructions; } + private int accountOffset(final int accountIndex) { + return accountsOffset + (accountIndex * PUBLIC_KEY_LENGTH); + } + + private PublicKey getAccount(final int accountIndex) { + return PublicKey.readPubKey(data, accountOffset(accountIndex)); + } + @Override public PublicKey[] parseProgramAccounts() { final var programs = new PublicKey[numInstructions]; for (int i = 0, o = instructionsOffset, programAccountIndex, numIxAccounts, len; i < numInstructions; ++i) { programAccountIndex = decode(data, o); o += getByteLen(data, o); - programs[i] = PublicKey.readPubKey(data, accountsOffset + (programAccountIndex * PUBLIC_KEY_LENGTH)); + programs[i] = getAccount(programAccountIndex); numIxAccounts = decode(data, o); o += getByteLen(data, o); @@ -245,7 +254,7 @@ public Instruction[] parseInstructionsWithoutAccounts() { for (int i = 0, o = instructionsOffset, numIxAccounts, len; i < numInstructions; ++i) { final int programAccountIndex = decode(data, o); o += getByteLen(data, o); - final var programAccount = PublicKey.readPubKey(data, accountsOffset + (programAccountIndex * PUBLIC_KEY_LENGTH)); + final var programAccount = getAccount(programAccountIndex); numIxAccounts = decode(data, o); o += getByteLen(data, o); @@ -260,9 +269,59 @@ public Instruction[] parseInstructionsWithoutAccounts() { } @Override - public Instruction[] parseInstructionsWithAccounts() { - final var accounts = parseAccounts(); - return parseInstructions(accounts); + public Instruction[] filterInstructions(final AccountMeta[] accounts, final Discriminator discriminator) { + final var instructions = new Instruction[numInstructions]; + int d = 0; + for (int i = 0, o = instructionsOffset, numIxAccounts, len; i < numInstructions; ++i) { + final int programAccountIndex = decode(data, o); + o += getByteLen(data, o); + + numIxAccounts = decode(data, o); + o += getByteLen(data, o); + int accountsOffset = o; + o += numIxAccounts; + + len = decode(data, o); + o += getByteLen(data, o); + + if (discriminator.equals(data, o)) { + final var ixAccounts = new AccountMeta[numIxAccounts]; + for (int a = 0; a < numIxAccounts; ++a) { + final int accountIndex = data[accountsOffset++] & 0xFF; + ixAccounts[a] = accountIndex < accounts.length ? accounts[accountIndex] : null; + } + instructions[d++] = createInstruction(getAccount(programAccountIndex), Arrays.asList(ixAccounts), data, o, len); + } + o += len; + } + return d == numInstructions + ? instructions + : Arrays.copyOfRange(instructions, 0, d); + } + + @Override + public Instruction[] filterInstructionsWithoutAccounts(final Discriminator discriminator) { + final var instructions = new Instruction[numInstructions]; + int d = 0; + for (int i = 0, o = instructionsOffset, numIxAccounts, len; i < numInstructions; ++i) { + final int programAccountIndex = decode(data, o); + o += getByteLen(data, o); + + numIxAccounts = decode(data, o); + o += getByteLen(data, o); + o += numIxAccounts; + + len = decode(data, o); + o += getByteLen(data, o); + + if (discriminator.equals(data, o)) { + instructions[d++] = createInstruction(getAccount(programAccountIndex), NO_ACCOUNTS, data, o, len); + } + o += len; + } + return d == numInstructions + ? instructions + : Arrays.copyOfRange(instructions, 0, d); } private AccountMeta[] parseIncludedAccounts() { diff --git a/core/src/test/java/software/sava/core/tx/TransactionSerializationTests.java b/core/src/test/java/software/sava/core/tx/TransactionSerializationTests.java index d672508..59fee3a 100644 --- a/core/src/test/java/software/sava/core/tx/TransactionSerializationTests.java +++ b/core/src/test/java/software/sava/core/tx/TransactionSerializationTests.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static software.sava.core.accounts.PublicKey.fromBase58Encoded; import static software.sava.core.accounts.meta.AccountMeta.*; +import static software.sava.core.programs.Discriminator.toDiscriminator; final class TransactionSerializationTests { @@ -22,11 +23,12 @@ void testVariableLengthIxLength() { final byte[] data = Base64.getDecoder().decode(""" ATgc2Iye/GlwnpSeIytu+tYkb2A+5VJhc1yui59+7/PMQSuywEqpb3k8wHCKnupEuC5fDTUjvGhASTEH5c90UACAAQAFEU4rs4al2vatnKR6MtsLLzl+Q24T1Y5kkYBmPhrq9O/VzcyvadLTPMXTLHJ2IKteqvqoQAgRH4dVHOW+cw1EkNOJB31VpbsTMHY+t2f1XsB3tBoNB1994dc/uso8Y9VUcRCcPGXQaDMBtOvEnG0Lyr4Lf68erOMMjG6weDn4HuIS6tSjkUAFDNLqypEZqieck8DZMKBobFJb3fYlMJjWpjHvHv25qj1olz/ZenFlAVmw6stGZYC5aF5nQ9ZqQr8vxXTXpuq5/UeOzPqvqL7sJuBwFgO//vEZG9uw6edrxAd2vInnwNHlA4uvk7TwFNJd9xWnndlfBJ5f9fX36m+JwJ9fAlkt3jAFytFpv8wnPC/6I0tpd+F+Bw3UOdTTA8X8HR7XL/DvxwiqYIadWSBAIms1hbo9KoaOYES91ZtNIF/jeSWG+N64PtIqGyqU3OdPOEd0TTjj79CJx+HICgFkwRrNq0B12gG6uYd+a79dybCsJPRSedzSl8R6nwJYXSLJGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAJWBt/6PKcF0R86zH0ytUdAc7K5LbdbpdsVcWwvkMUGK86EED9glv4WMHdJchvP2ZZvDPQq6PniaYqUsrsmZ94ETjVRi2k6UWSxzbmoMl/cGeYhlpwGEqRE3YD3BBue3swYOAAUCgE8SAA4ABQEAAAIADgAJAyKiAAAAAAAADwYdDAABFg0IllUsHJUO0hoPHQACAwwREhMUARUWFx0eGBkEHxogGxwFBgcICQoLigEyEHMzqHo5LQIAAAACAAAAAAECAAAAAgEDAAAABBIAAAAEBQQGBwIICQoLBAAMDA0ODxABCQAAABEMEhMUFQgCAAQSAAAABBYEFxgCCAoJGQQADAwNGhscCAAAABAnTB2IE8QJ6ANkAAoAAQC0ZeZpAAAAAFDDAAAAAAAAAAAAAAAAAAAAAAAAAAAQAB9Qb3dlcmVkIGJ5IGJsb1hyb3V0ZSBUcmFkZXIgQXBpAonsVzlUh3H7+XOmaklk0KWZm+wt34ECwzGrxcB6Sb7VCQRLSE8FSUpRTgQMAgsHmoTIVF9hkZWFmpxCqc8zcavo9Yu6S8ysQBz59/7iw1ADVllVAA=="""); final var skeleton = TransactionSkeleton.deserializeSkeleton(data); - final var instructions = skeleton.parseInstructionsWithoutTableAccounts(); + var instructions = skeleton.parseInstructionsWithoutTableAccounts(); assertEquals(6, instructions.length); var ix = instructions[4]; assertEquals("B4cUJzpPVNKdnjnN7x7MhhXzJT5Z9SpteFp3JK3Y7bow", ix.programId().publicKey().toBase58()); assertEquals(138, ix.len()); + ix = instructions[5]; assertEquals("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx", ix.programId().publicKey().toBase58()); assertEquals(31, ix.len()); @@ -37,6 +39,40 @@ void testVariableLengthIxLength() { assertEquals("AHPsnG22z2BwdeURWWPPQzLYRb6jT9PPDXzQ3hMF7hNc", tableAccount.toBase58()); tableAccount = lookupTables[1]; assertEquals("BQBDhjQ32VYzNzXW8vViqpuLiz2TAYcW1cVZDAL4Hf67", tableAccount.toBase58()); + + final var accounts = skeleton.parseAccounts(); + for (int i = 0; i < 3; ++i) { + ix = instructions[i]; + final var discriminator = toDiscriminator(ix.discriminator(1)); + var instructionArray = skeleton.filterInstructions(accounts, discriminator); + assertEquals(1, instructionArray.length); + assertEquals(ix, instructionArray[0]); + } + + for (int i = 3; i < instructions.length; ++i) { + ix = instructions[i]; + final var discriminator = toDiscriminator(ix.discriminator(8)); + var instructionArray = skeleton.filterInstructions(accounts, discriminator); + assertEquals(1, instructionArray.length); + assertEquals(ix, instructionArray[0]); + } + + instructions = skeleton.parseInstructionsWithoutAccounts(); + for (int i = 0; i < 3; ++i) { + ix = instructions[i]; + final var discriminator = toDiscriminator(ix.discriminator(1)); + var instructionArray = skeleton.filterInstructionsWithoutAccounts(discriminator); + assertEquals(1, instructionArray.length); + assertEquals(ix, instructionArray[0]); + } + + for (int i = 3; i < instructions.length; ++i) { + ix = instructions[i]; + final var discriminator = toDiscriminator(ix.discriminator(8)); + var instructionArray = skeleton.filterInstructionsWithoutAccounts(discriminator); + assertEquals(1, instructionArray.length); + assertEquals(ix, instructionArray[0]); + } } private void testMultipleLookupTables(final TransactionSkeleton skeleton, diff --git a/rpc/src/main/java/software/sava/rpc/json/http/ws/Channel.java b/rpc/src/main/java/software/sava/rpc/json/http/ws/Channel.java index b5ccf32..9b42ccb 100644 --- a/rpc/src/main/java/software/sava/rpc/json/http/ws/Channel.java +++ b/rpc/src/main/java/software/sava/rpc/json/http/ws/Channel.java @@ -1,6 +1,6 @@ package software.sava.rpc.json.http.ws; -public enum Channel { +enum Channel { account, logs, @@ -17,11 +17,11 @@ public enum Channel { this.unSubscribe = name() + "Unsubscribe"; } - public String subscribe() { + String subscribe() { return subscribe; } - public String unSubscribe() { + String unSubscribe() { return unSubscribe; } } diff --git a/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaJsonRpcWebsocket.java b/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaJsonRpcWebsocket.java index 32a3906..1dceaf9 100644 --- a/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaJsonRpcWebsocket.java +++ b/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaJsonRpcWebsocket.java @@ -5,13 +5,13 @@ import software.sava.core.accounts.token.TokenAccount; import software.sava.core.rpc.Filter; import software.sava.rpc.json.http.request.Commitment; -import software.sava.rpc.json.http.request.RpcEncoding; import software.sava.rpc.json.http.request.TransactionDetails; import software.sava.rpc.json.http.response.*; import systems.comodal.jsoniter.CharBufferFunction; import systems.comodal.jsoniter.JsonIterator; import java.net.URI; +import java.net.http.HttpClient; import java.net.http.WebSocket; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -24,11 +24,12 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; import static java.lang.System.Logger.Level.*; import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.stream.Collectors.joining; import static software.sava.rpc.json.http.response.AccountInfo.BYTES_IDENTITY; import static systems.comodal.jsoniter.JsonIterator.fieldEquals; @@ -389,7 +390,7 @@ public boolean programSubscribe(final Commitment commitment, if (sub == null || !sub.containsKey(commitment)) { final var filtersJson = filters.isEmpty() ? "" : filters.stream() .map(Filter::toJson) - .collect(Collectors.joining(",", ",\"filters\":[", "]")); + .collect(joining(",", ",\"filters\":[", "]")); final var params = String.format(""" "%s",{"commitment":"%s","encoding":"base64"%s}""", @@ -410,42 +411,42 @@ public boolean programUnsubscribe(final Commitment commitment, final PublicKey p return queueUnsubscribe(program.toBase58(), Channel.program, commitment, this.programSubs); } - @Override - public boolean transactionSubscribe(final Commitment commitment, - final boolean vote, - final boolean failed, - final String signature, - final Collection accountInclude, - final Collection accountExclude, - final Collection accountRequired, - final RpcEncoding encoding, - final TransactionDetails transactionDetails, - final boolean showRewards, - final Consumer consumer) { - final var signatureField = signature == null || signature.isBlank() - ? "" - : String.format(""" - ",signature":"%s\"""", signature); - + /// Only supported by Helius on a Business or Professional plan + private boolean transactionSubscribe(final Commitment commitment, + final boolean vote, + final boolean failed, + final Collection accountInclude, + final Collection accountExclude, + final Collection accountRequired, + final TransactionDetails transactionDetails, + final boolean showRewards, + final Consumer consumer) { final var includes = accountInclude == null || accountInclude.isEmpty() ? "" - : accountInclude.stream().map(PublicKey::toBase58).collect(Collectors.joining("\",\"", ",\"accountInclude\":[\"", "\"]")); + : accountInclude.stream() + .map(PublicKey::toBase58) + .collect(joining("\",\"", ",\"accountInclude\":[\"", "\"]")); + final var excludes = accountExclude == null || accountExclude.isEmpty() ? "" - : accountExclude.stream().map(PublicKey::toBase58).collect(Collectors.joining("\",\"", ",\"accountExclude\":[\"", "\"]")); + : accountExclude.stream() + .map(PublicKey::toBase58). + collect(joining("\",\"", ",\"accountExclude\":[\"", "\"]")); + final var required = accountRequired == null || accountRequired.isEmpty() ? "" - : accountRequired.stream().map(PublicKey::toBase58).collect(Collectors.joining("\",\"", ",\"accountRequired\":[\"", "\"]")); + : accountRequired.stream() + .map(PublicKey::toBase58) + .collect(joining("\",\"", ",\"accountRequired\":[\"", "\"]")); + final var params = String.format(""" - {"vote":%b,"failed":%b%s%s%s%s},{"commitment":"%s","encoding":"%s","transactionDetails":"%s","showRewards":%b,"maxSupportedTransactionVersion":0}""", + {"vote":%b,"failed":%b%s%s%s},{"commitment":"%s","encoding":"base64","transactionDetails":"%s","showRewards":%b,"maxSupportedTransactionVersion":0}""", vote, failed, - signatureField, includes, excludes, required, commitment.getValue(), - encoding, transactionDetails, showRewards ); @@ -561,7 +562,7 @@ private void onWholeMessage(final char[] msg, final int tail, final JsonIterator ji, final WebSocket webSocket) { - // System.out.format("<- %s%n", new String(msg, offset, tail - offset)); + System.out.format("<- %s%n", new String(msg, offset, tail - offset)); try { if (ji.skipUntil("method") == null) { if (ji.reset(offset).skipUntil("error") != null) { @@ -606,6 +607,8 @@ private void onWholeMessage(final char[] msg, ji.skipUntil("result"); if (channel == Channel.transaction) { ji.skipUntil("transaction"); + + } else { final int resultMark = ji.mark(); ji.skipUntil("context"); @@ -827,4 +830,26 @@ public void close() { this.programSubs.clear(); this.slotSub.set(null); } + + + public static void main(final String[] args) throws InterruptedException { + final var meteoraProgramId = PublicKey.fromBase58Encoded("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"); + try (final var executorService = Executors.newVirtualThreadPerTaskExecutor()) { + try (final var httpClient = HttpClient.newBuilder().executor(executorService).build()) { + final var websocketBuilder = SolanaRpcWebsocket.build(); + websocketBuilder.uri(URI.create("wss://atlas-mainnet.helius-rpc.com?api-key=")); + websocketBuilder.webSocketBuilder(httpClient); + websocketBuilder.commitment(Commitment.CONFIRMED); + final var webSocket = websocketBuilder.create(); +// webSocket.transactionSubscribe( +// Commitment.CONFIRMED, +// List.of(), List.of(), +// List.of(meteoraProgramId), +// result -> System.out.println(result) +// ); + webSocket.connect(); + HOURS.sleep(1); + } + } + } } diff --git a/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaRpcWebsocket.java b/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaRpcWebsocket.java index 0f4eb3d..dfa9ddd 100644 --- a/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaRpcWebsocket.java +++ b/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaRpcWebsocket.java @@ -3,10 +3,8 @@ import software.sava.core.accounts.PublicKey; import software.sava.core.accounts.SolanaAccounts; import software.sava.core.rpc.Filter; -import software.sava.rpc.json.http.request.RpcEncoding; import software.sava.rpc.json.http.SolanaNetwork; import software.sava.rpc.json.http.request.Commitment; -import software.sava.rpc.json.http.request.TransactionDetails; import software.sava.rpc.json.http.response.AccountInfo; import software.sava.rpc.json.http.response.ProcessedSlot; import software.sava.rpc.json.http.response.TxLogs; @@ -15,7 +13,6 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.WebSocket; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -102,40 +99,6 @@ boolean programSubscribe(final Commitment commitment, boolean programUnsubscribe(final Commitment commitment, final PublicKey program); - boolean transactionSubscribe(final Commitment commitment, - final boolean vote, - final boolean failed, - final String signature, - final Collection accountInclude, - final Collection accountExclude, - final Collection accountRequired, - final RpcEncoding encoding, - final TransactionDetails transactionDetails, - final boolean showRewards, - final Consumer consumer); - - default boolean transactionSubscribe(final Commitment commitment, - final String signature, - final Collection accountInclude, - final Collection accountExclude, - final Collection accountRequired, - final RpcEncoding rpcEncoding, - final Consumer consumer) { - return transactionSubscribe( - commitment, - false, - false, - signature, - accountInclude, - accountExclude, - accountRequired, - rpcEncoding, - TransactionDetails.full, - false, - consumer - ); - } - boolean slotSubscribe(final Consumer consumer); boolean slotUnsubscribe(); @@ -147,31 +110,31 @@ interface Builder { SolanaRpcWebsocket create(); - default void uri(final String endpoint) { - uri(URI.create(endpoint)); + default Builder uri(final String endpoint) { + return uri(URI.create(endpoint)); } - default void uri(final SolanaNetwork network) { - uri(network.getWebSocketEndpoint()); + default Builder uri(final SolanaNetwork network) { + return uri(network.getWebSocketEndpoint()); } - void uri(final URI uri); + Builder uri(final URI uri); - default void webSocketBuilder(final HttpClient httpClient) { - webSocketBuilder(httpClient.newWebSocketBuilder()); + default Builder webSocketBuilder(final HttpClient httpClient) { + return webSocketBuilder(httpClient.newWebSocketBuilder()); } - void webSocketBuilder(final WebSocket.Builder webSocketBuilder); + Builder webSocketBuilder(final WebSocket.Builder webSocketBuilder); - void reConnect(final long reConnect); + Builder reConnect(final long reConnect); - void writeOrPingDelay(final long writeOrPingDelay); + Builder writeOrPingDelay(final long writeOrPingDelay); - void subscriptionAndPingCheckDelay(final long subscriptionAndPingCheckDelay); + Builder subscriptionAndPingCheckDelay(final long subscriptionAndPingCheckDelay); - void commitment(final Commitment commitment); + Builder commitment(final Commitment commitment); - void solanaAccounts(SolanaAccounts solanaAccounts); + Builder solanaAccounts(final SolanaAccounts solanaAccounts); URI wsUri(); diff --git a/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaRpcWebsocketBuilder.java b/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaRpcWebsocketBuilder.java index 7330ba9..1211c5e 100644 --- a/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaRpcWebsocketBuilder.java +++ b/rpc/src/main/java/software/sava/rpc/json/http/ws/SolanaRpcWebsocketBuilder.java @@ -65,37 +65,44 @@ public Commitment commitment() { } @Override - public void uri(final URI uri) { + public SolanaRpcWebsocket.Builder uri(final URI uri) { this.wsUri = uri; + return this; } @Override - public void webSocketBuilder(final WebSocket.Builder webSocketBuilder) { + public SolanaRpcWebsocket.Builder webSocketBuilder(final WebSocket.Builder webSocketBuilder) { this.webSocketBuilder = webSocketBuilder; + return this; } @Override - public void reConnect(final long reConnect) { + public SolanaRpcWebsocket.Builder reConnect(final long reConnect) { this.reConnect = reConnect; + return this; } @Override - public void writeOrPingDelay(final long writeOrPingDelay) { + public SolanaRpcWebsocket.Builder writeOrPingDelay(final long writeOrPingDelay) { this.writeOrPingDelay = writeOrPingDelay; + return this; } @Override - public void subscriptionAndPingCheckDelay(final long subscriptionAndPingCheckDelay) { + public SolanaRpcWebsocket.Builder subscriptionAndPingCheckDelay(final long subscriptionAndPingCheckDelay) { this.subscriptionAndPingCheckDelay = subscriptionAndPingCheckDelay; + return this; } @Override - public void commitment(final Commitment commitment) { + public SolanaRpcWebsocket.Builder commitment(final Commitment commitment) { this.commitment = commitment; + return this; } @Override - public void solanaAccounts(final SolanaAccounts solanaAccounts) { + public SolanaRpcWebsocket.Builder solanaAccounts(final SolanaAccounts solanaAccounts) { this.solanaAccounts = solanaAccounts; + return this; } }