Skip to content

Commit

Permalink
Make GetInstruction more generic for jsonParsed data - we can't possi…
Browse files Browse the repository at this point in the history
…bly know the program specific parser used so just use Map<String, Object> and get the client to parse it if they want
  • Loading branch information
ml-james committed Nov 4, 2024
1 parent 56d7dd2 commit 1726543
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 87 deletions.
1 change: 1 addition & 0 deletions client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ check.dependsOn unitTest
check.dependsOn integrationTest
check.dependsOn checkstyleMain
check.dependsOn checkstyleTest
check.dependsOn checkstyleIntegrationTest

javadoc {
title = 'solana4j-json-rpc'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -19,7 +20,7 @@ class GetTransactionContractTest extends SolanaClientIntegrationTestBase
@Test
void shouldGetTransactionDefaultOptionalParams() throws SolanaJsonRpcClientException
{
final String transactionSignature = api.requestAirdrop(payerAccount, Sol.lamports(BigDecimal.ONE)).getResponse();
final String transactionSignature = api.requestAirdrop(dummyAccount, Sol.lamports(BigDecimal.ONE)).getResponse();

final var response = waitForTransactionSuccess(transactionSignature);

Expand Down Expand Up @@ -50,7 +51,7 @@ void shouldGetTransactionDefaultOptionalParams() throws SolanaJsonRpcClientExcep
@Test
void shouldGetTransactionBase58EncodingOptionalParam() throws SolanaJsonRpcClientException
{
final String transactionSignature = api.requestAirdrop(payerAccount, Sol.lamports(BigDecimal.ONE)).getResponse();
final String transactionSignature = api.requestAirdrop(dummyAccount, Sol.lamports(BigDecimal.ONE)).getResponse();
final SolanaClientOptionalParams optionalParams = new SolanaJsonRpcClientOptionalParams();
optionalParams.addParam("encoding", "base58");

Expand All @@ -64,7 +65,7 @@ void shouldGetTransactionBase58EncodingOptionalParam() throws SolanaJsonRpcClien
@Test
void shouldGetTransactionJsonEncodingOptionalParam() throws SolanaJsonRpcClientException
{
final String transactionSignature = api.requestAirdrop(payerAccount, Sol.lamports(BigDecimal.ONE)).getResponse();
final String transactionSignature = api.requestAirdrop(dummyAccount, Sol.lamports(BigDecimal.ONE)).getResponse();
final SolanaClientOptionalParams optionalParams = new SolanaJsonRpcClientOptionalParams();
optionalParams.addParam("encoding", "json");

Expand Down Expand Up @@ -100,7 +101,7 @@ void shouldGetTransactionJsonEncodingOptionalParam() throws SolanaJsonRpcClientE
@Test
void shouldGetTransactionJsonParsedEncodingOptionalParam() throws SolanaJsonRpcClientException
{
final String transactionSignature = api.requestAirdrop(payerAccount, Sol.lamports(BigDecimal.ONE)).getResponse();
final String transactionSignature = api.requestAirdrop(dummyAccount, Sol.lamports(BigDecimal.ONE)).getResponse();
final SolanaClientOptionalParams optionalParams = new SolanaJsonRpcClientOptionalParams();
optionalParams.addParam("encoding", "jsonParsed");

Expand Down Expand Up @@ -135,28 +136,28 @@ void shouldGetTransactionJsonParsedEncodingOptionalParam() throws SolanaJsonRpcC

final var instruction = message.getInstructions().get(0);

// json
// would have been present if encoding json not jsonParsed
assertThat(instruction.getData()).isNull();
assertThat(instruction.getAccounts()).isNull();
assertThat(instruction.getProgramIdIndex()).isNull();
assertThat(instruction.getStackHeight()).isEqualTo(null);

// jsonParsed
// i think the best we can do here is really just return a Map<String, Object> and let the user do their own parsing
// since the parsing is very much program specific
assertThat(instruction.getProgram()).isEqualTo("system");
assertThat(instruction.getProgramId()).isEqualTo("11111111111111111111111111111111");

final var parsedInstruction = instruction.getInstructionParsed();
assertThat(parsedInstruction.getType()).isEqualTo("transfer");

final var parsedInstructionInfo = parsedInstruction.getInfo();
assertThat(parsedInstructionInfo.getDestination()).isNotEmpty();
assertThat(parsedInstructionInfo.getLamports()).isEqualTo(1000000000L);
assertThat(parsedInstructionInfo.getSource()).isNotEmpty();

assertThat(message.getRecentBlockhash()).isNotEmpty();

final var signatures = parsedTransactionData.getSignatures();
assertThat(signatures).hasSize(1);
assertThat(parsedInstruction.get("type")).isEqualTo("transfer");
assertThat(parsedInstruction.get("info"))
.usingRecursiveComparison()
.ignoringFields("source")
.isEqualTo(Map.of(
"destination", "4Nd1mnszWRVFzzsxMgcTzdFoC8Wx5mPQD9KZx3qtDr1M",
"lamports", 1000000000,
"source", "ignoredAsItChanges"
)
);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class RequestAirdropContractTest extends SolanaClientIntegrationTestBase
@Test
void shouldRequestAirdrop() throws SolanaJsonRpcClientException
{
assertThat(api.requestAirdrop(payerAccount, Sol.lamports(BigDecimal.ONE)).getResponse()).isNotBlank();
assertThat(api.requestAirdrop(dummyAccount, Sol.lamports(BigDecimal.ONE)).getResponse()).isNotBlank();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

public class SolanaClientIntegrationTestBase extends IntegrationTestBase
{
protected final String dummyAccount = "4Nd1mnszWRVFzzsxMgcTzdFoC8Wx5mPQD9KZx3qtDr1M";
// found in shared/src/test-support/resources/testcontainers/accounts
protected final String payerAccount = "EjmK3LTW8oBSJp14zvQ55PMvPJCuQwRrnjd17P4vrQYo";
protected final String tokenAccount = "BxoHF6TfQYXkpThMC7hVeex37U3duprVZuFAa7Akortf";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,63 @@
import java.util.List;
import java.util.Map;

/**
/**
* Represents account information data in a Solana transaction.
* This interface provides access to both encoded and parsed account information,
* allowing clients to retrieve details in a flexible format.
*/
public interface AccountInfoData
{
/**
* Returns the encoded account information.
* This data is represented as a list of base64-encoded strings, providing a compact form
* of the account information for efficient storage and transmission.
*
* @return a list of base64-encoded strings representing the account information
*/
List<String> getAccountInfoEncoded();

/**
* Returns the parsed account information.
* This data provides a structured representation of account details, including the associated program,
* allocated space, and any additional parsed data fields.
*
* @return an {@link AccountInfoParsedData} object representing the parsed account information
*/
AccountInfoParsedData getAccountInfoParsed();

/**
* Represents parsed account information data within an account.
* This nested interface provides access to the program associated with the account,
* the allocated space for the account, and a dynamically structured parsed data map.
*/
interface AccountInfoParsedData
{
/**
* Returns the name or identifier of the program associated with the account.
* This program defines the logic for the account's operations within the Solana blockchain.
*
* @return a string representing the program's name or identifier
*/
String getProgram();

/**
* Returns the allocated space for the account in bytes.
* The space represents the amount of storage allocated to this account within the Solana blockchain.
*
* @return the size of the account in bytes
*/
int getSpace();

/**
* Returns a map representation of parsed data fields for the account.
* This parsed data is represented as a {@link Map} with string keys and object values,
* allowing flexible access to dynamically structured data fields within the account.
*
* @return a {@link Map} where keys are field names and values are the corresponding parsed data,
* potentially containing nested structures as {@code Map<String, Object>}
*/
Map<String, Object> getParsedData();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.lmax.solana4j.client.api;

import java.util.List;
import java.util.Map;

/**
* Represents an instruction in a Solana transaction.
Expand Down Expand Up @@ -52,7 +53,20 @@ public interface Instruction
String getProgramId();


InstructionParsed getInstructionParsed();
/**
* Returns a map representation of the parsed instruction data.
* The parsed instruction data is represented as a {@link Map} with string keys and object values,
* allowing dynamic access to the fields of the instruction without a predefined structure.
* <p>
* This method is useful for cases where the instruction data may have a variable or unknown format.
* Nested objects within the instruction data will also be represented as maps,
* providing a flexible way to query the instruction's content.
* </p>
*
* @return a {@link Map} where keys are field names and values are the corresponding data,
* potentially nested as {@code Map<String, Object>} for complex structures
*/
Map<String, Object> getInstructionParsed();

/**
* Returns the stack height at which this instruction operates, if applicable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.lmax.solana4j.client.api.Instruction;
import com.lmax.solana4j.client.api.InstructionParsed;

import java.util.List;
import java.util.Map;

final class InstructionDTO implements Instruction
{
private final List<Integer> accounts;
private final InstructionParsedDTO instructionParsed;
private final Map<String, Object> instructionParsed;
private final String data;
private final String program;
private final String programId;
Expand All @@ -20,7 +20,7 @@ final class InstructionDTO implements Instruction
@JsonCreator
InstructionDTO(
final @JsonProperty("accounts") List<Integer> accounts,
final @JsonProperty("parsed") InstructionParsedDTO instructionParsed,
final @JsonProperty("parsed") Map<String, Object> instructionParsed,
final @JsonProperty("data") String data,
final @JsonProperty("program") String program,
final @JsonProperty("programId") String programId,
Expand Down Expand Up @@ -67,7 +67,7 @@ public String getProgramId()
}

@Override
public InstructionParsed getInstructionParsed()
public Map<String, Object> getInstructionParsed()
{
return instructionParsed;
}
Expand All @@ -83,75 +83,12 @@ public String toString()
{
return "InstructionDTO{" +
"accounts=" + accounts +
", parsedInstruction=" + instructionParsed +
", instructionParsed=" + instructionParsed +
", data='" + data + '\'' +
", program='" + program + '\'' +
", programId='" + programId + '\'' +
", programIdIndex=" + programIdIndex +
", stackHeight=" + stackHeight +
'}';
}

static final class InstructionParsedDTO implements InstructionParsed
{
private final InstructionInfoParsed info;
private final String type;

@JsonCreator
InstructionParsedDTO(
final @JsonProperty("info") InstructionInfoParsedDTO info,
final @JsonProperty("type") String type)
{
this.info = info;
this.type = type;
}

@Override
public InstructionInfoParsed getInfo()
{
return info;
}

@Override
public String getType()
{
return type;
}

static class InstructionInfoParsedDTO implements InstructionInfoParsed
{
private final String destination;
private final long lamports;
private final String source;

@JsonCreator
InstructionInfoParsedDTO(
final @JsonProperty("destination") String destination,
final @JsonProperty("lamports") long lamports,
final @JsonProperty("source") String source)
{
this.destination = destination;
this.lamports = lamports;
this.source = source;
}

@Override
public String getDestination()
{
return destination;
}

@Override
public long getLamports()
{
return lamports;
}

@Override
public String getSource()
{
return source;
}
}
}
}

0 comments on commit 1726543

Please sign in to comment.