84: Validate 'jup' and the configured signer when constructing SolanaWalletImpl

This commit is contained in:
2026-09-04 11:39:29 +02:00
parent b6a6d544b5
commit f1dd5ed9b1
7 changed files with 449 additions and 1 deletions
@@ -1,6 +1,7 @@
package com.r35157.cryptowallet.solana.impl.ref;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.cryptowallet.solana.MissingRecipientTokenAccountPolicy;
@@ -28,9 +29,202 @@ public class SolanaWalletImpl implements SolanaWallet {
String signerKeyName,
SolanaBlockChain solanaBlockChain
) {
if (address == null || address.isBlank()) {
throw new IllegalArgumentException(
"Solana wallet address must not be blank"
);
}
if (signerKeyName == null || signerKeyName.isBlank()) {
throw new IllegalArgumentException(
"Solana wallet signer key name must not be blank"
);
}
this.address = address;
this.signerKeyName = signerKeyName;
this.solanaBlockChain = solanaBlockChain;
this.solanaBlockChain = Objects.requireNonNull(
solanaBlockChain,
"Solana blockchain must not be null"
);
validateJupConfiguration();
}
private void validateJupConfiguration() {
JupCommandResult versionResult = runJupCommand(
"jup --version",
"--version"
);
String actualVersion = versionResult.standardOutput().trim();
if (!actualVersion.matches("\\d+\\.\\d+\\.\\d+")) {
throw new IllegalStateException(
"Invalid jup version while validating Solana wallet "
+ "signer '"
+ signerKeyName
+ "': expected version "
+ EXPECTED_JUP_VERSION
+ " but jup --version returned '"
+ actualVersion
+ "'"
);
}
// An exact match is intentional: the CLI arguments and JSON response
// structure used by this class are verified only for this version.
if (!EXPECTED_JUP_VERSION.equals(actualVersion)) {
throw new IllegalStateException(
"Unsupported jup version while validating Solana wallet "
+ "signer '"
+ signerKeyName
+ "': expected "
+ EXPECTED_JUP_VERSION
+ " but found "
+ actualVersion
);
}
JupCommandResult keysResult = runJupCommand(
"jup -f json keys list",
"-f",
"json",
"keys",
"list"
);
JsonNode keys;
try {
keys = OBJECT_MAPPER.readTree(keysResult.standardOutput());
} catch (JsonProcessingException e) {
throw new IllegalStateException(
"Unable to decode the jup key list while validating "
+ "Solana wallet signer '"
+ signerKeyName
+ "'",
e
);
}
if (keys == null || !keys.isArray()) {
throw new IllegalStateException(
"jup returned an invalid key list while validating "
+ "Solana wallet signer '"
+ signerKeyName
+ "': expected a JSON array"
);
}
JsonNode signerKey = null;
for (JsonNode key : keys) {
if (signerKeyName.equals(key.path("name").textValue())) {
signerKey = key;
break;
}
}
if (signerKey == null) {
throw new IllegalStateException(
"Solana wallet signer key '"
+ signerKeyName
+ "' is not available to jup"
);
}
ΩSolanaWalletIdΩ signerAddress = signerKey
.path("address")
.textValue();
if (signerAddress == null || signerAddress.isBlank()) {
throw new IllegalStateException(
"jup signer key '"
+ signerKeyName
+ "' does not contain a valid address; expected "
+ address
);
}
if (!address.equals(signerAddress)) {
throw new IllegalStateException(
"jup signer key '"
+ signerKeyName
+ "' has an unexpected address: expected "
+ address
+ " but found "
+ signerAddress
);
}
}
private JupCommandResult runJupCommand(
String commandDescription,
String... arguments
) {
String[] command = new String[arguments.length + 1];
command[0] = "jup";
System.arraycopy(arguments, 0, command, 1, arguments.length);
Process process;
try {
process = new ProcessBuilder(command).start();
} catch (IOException e) {
throw new IllegalStateException(
"Unable to start required jup CLI command '"
+ commandDescription
+ "' while validating Solana wallet signer '"
+ signerKeyName
+ "'. Ensure jup version "
+ EXPECTED_JUP_VERSION
+ " is installed and available on PATH",
e
);
}
try {
String standardOutput = new String(
process.getInputStream().readAllBytes(),
StandardCharsets.UTF_8
);
String standardError = new String(
process.getErrorStream().readAllBytes(),
StandardCharsets.UTF_8
);
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IllegalStateException(
"Required jup CLI command '"
+ commandDescription
+ "' failed while validating Solana wallet "
+ "signer '"
+ signerKeyName
+ "' with exit code "
+ exitCode
+ ": "
+ standardError.trim()
);
}
return new JupCommandResult(standardOutput);
} catch (IOException e) {
process.destroyForcibly();
throw new IllegalStateException(
"Unable to read the result of required jup CLI command '"
+ commandDescription
+ "' while validating Solana wallet signer '"
+ signerKeyName
+ "'",
e
);
} catch (InterruptedException e) {
process.destroyForcibly();
Thread.currentThread().interrupt();
throw new IllegalStateException(
"Interrupted while running required jup CLI command '"
+ commandDescription
+ "' for Solana wallet signer '"
+ signerKeyName
+ "'",
e
);
}
}
@Override
@@ -475,6 +669,10 @@ public class SolanaWalletImpl implements SolanaWallet {
) {
}
private record JupCommandResult(String standardOutput) {
}
private static final String EXPECTED_JUP_VERSION = "0.10.1";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final ΩSolanaWalletIdΩ address;