X: Clean-up and documentation updates

This commit is contained in:
2026-08-14 13:50:10 +02:00
parent 3d5de0e09b
commit f5a2183a40
24 changed files with 515 additions and 541 deletions
@@ -47,27 +47,17 @@ import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.T
public class EvelynImpl implements Evelyn {
/*public EvelynImpl(
State state,
DesiredPositionCalculator desiredPositionCalculator,
Raydium raydium,
SolanaBlockChain solanaChain
) {
this.state = state;
this.desiredPositionCalculator = desiredPositionCalculator;
this.raydium = raydium;
this.solanaChain = solanaChain;
}*/
public EvelynImpl(
@NotNull String instanceName,
@NotNull TickerService tickerService,
@NotNull TradingPair eveUsdtTradingPair
@NotNull TradingPair eveUsdtTradingPair,
@NotNull Clock clock
) {
this(
instanceName,
tickerService,
eveUsdtTradingPair,
Clock.systemUTC(),
clock,
SAMPLE_DELAY_MINUTES,
TimeUnit.MINUTES,
DATA_ROOT
@@ -131,6 +121,8 @@ public class EvelynImpl implements Evelyn {
this.clock = validatedClock;
this.sampleDelay = sampleDelay;
this.sampleDelayUnit = validatedDelayUnit;
statusIndexHistory = new ArrayList<>();
}
@Override
@@ -766,7 +758,7 @@ public class EvelynImpl implements Evelyn {
private final Clock clock;
private final long sampleDelay;
private final TimeUnit sampleDelayUnit;
private final List<EvelynStatusIndexPoint> statusIndexHistory = new ArrayList<>();
private final List<EvelynStatusIndexPoint> statusIndexHistory;
private ScheduledExecutorService statusIndexScheduler;
private Instant lastPersistedTimestamp;
@@ -44,7 +44,10 @@ public final class HardcodedPriceSource implements PriceSource {
currencyIdentityService,
"currencyIdentityService"
);
this.clock = Objects.requireNonNull(clock, "clock");
this.clock = Objects.requireNonNull(
clock,
"clock"
);
if (observationDelay <= 0) {
throw new IllegalArgumentException("observationDelay must be positive");
}
@@ -28,9 +28,9 @@ import java.util.function.Function;
public final class EvelynMissionControlImpl implements EvelynMissionControl {
public EvelynMissionControlImpl(Evelyn evelynProd, Evelyn evelynTest) {
this.evelynProd = Objects.requireNonNull(evelynProd);
this.evelynTest = Objects.requireNonNull(evelynTest);
public EvelynMissionControlImpl(Evelyn evelynServiceProd, Evelyn evelynServiceTest) {
this.evelynProd = Objects.requireNonNull(evelynServiceProd, "evelynServiceProd");
this.evelynTest = Objects.requireNonNull(evelynServiceTest, "evelynServiceTest");
refreshTimeline.setCycleCount(Timeline.INDEFINITE);
}
@@ -1,4 +1,19 @@
package com.r35157.jupiterperpsalarm;
public class JupiterPerpsAlarm {
/**
* Monitors configured Jupiter Perps prices and dispatches matching alarm
* actions.
*
* <p>An instance is expected to be fully configured with its wallet and
* Jupiter service before it is started. Those dependencies determine which
* wallet is observed and used for transaction actions; alarm action files do
* not select them.</p>
*/
public interface JupiterPerpsAlarm {
/**
* Starts alarm monitoring asynchronously.
*
* <p>Each instance must be started at most once.</p>
*/
void start();
}
@@ -320,7 +320,7 @@ public final class AlarmConfigurationParser {
private static final Pattern PERSISTENT_GRACE_PERIOD_PATTERN =
Pattern.compile("(\\d+)(ms|s|m|h|d|w|M|y)");
private static final int SUPPORTED_FORMAT_VERSION = 1;
private static final int SUPPORTED_FORMAT_VERSION = 2;
private AlarmConfigurationParser() {
}
@@ -1,15 +1,9 @@
package com.r35157.jupiterperpsalarm.impl.ref;
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.cryptowallet.solana.impl.ref.SolanaWalletImpl;
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
import com.r35157.libs.objcache.ObjectCache;
import com.r35157.libs.objcache.impl.ref.ObjectCacheImpl;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.impl.cached.CachedSolanaBlockChain;
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
import org.jetbrains.annotations.NotNull;
import java.net.URI;
import java.nio.file.Path;
@@ -18,19 +12,52 @@ import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
public final class JupiterPerpsAlarmImpl {
public final class JupiterPerpsAlarmImpl implements JupiterPerpsAlarm {
public static void start(
String[] args,
CurrencyIdentityService currencyIdentityService
) throws Exception {
public JupiterPerpsAlarmImpl(
@NotNull SolanaWallet solanaWallet,
@NotNull JupiterPerpsService jupiterServicePerps,
@NotNull String configFilename
) {
this.solanaWallet = Objects.requireNonNull(
solanaWallet,
"solanaWallet"
);
this.jupiterServicePerps = Objects.requireNonNull(
jupiterServicePerps,
"jupiterServicePerps"
);
this.configFilename = Objects.requireNonNull(
configFilename,
"configFilename"
);
this.thread = new Thread(() -> {
try {
run();
} catch (Throwable throwable) {
throwable.printStackTrace(System.err);
}
}, "Nenjim Plugin - JupiterPerpsAlarm");
this.thread.setDaemon(false);
}
@Override
public void start() {
thread.start();
}
private void run() throws Exception {
Config config;
System.out.println("Starting Jupiter Perps Alarms...");
try {
System.out.print("Parsing commandline parameters... ");
config = Config.parse(args, System.getenv());
config = Config.parse(new String[] { configFilename }, System.getenv());
System.out.println("Done.");
} catch (IllegalArgumentException exception) {
printUsage();
@@ -60,14 +87,6 @@ public final class JupiterPerpsAlarmImpl {
.getParent();
System.out.print("Initializing dependencies... ");
ObjectCache objectCache = new ObjectCacheImpl();
SolanaBlockChain realSolanaBlockChain = new SolanaBlockChainImpl(
currencyIdentityService
);
SolanaBlockChain solanaBlockChain = new CachedSolanaBlockChain(
realSolanaBlockChain,
objectCache
);
JupiterPerpsPositionIncreaseAlarmActionConfiguration
positionIncreaseConfiguration =
@@ -80,19 +99,6 @@ public final class JupiterPerpsAlarmImpl {
variableResolver
);
SolanaWallet positionIncreaseWallet = new SolanaWalletImpl(
positionIncreaseConfiguration.walletId(),
positionIncreaseConfiguration.signerKeyName(),
solanaBlockChain
);
JupiterPerpsService positionIncreaseJupiter =
new AnchorIdlJupiterPerpsServiceImpl(
solanaBlockChain,
positionIncreaseWallet,
currencyIdentityService
);
JupiterPerpsPositionDecreaseAlarmActionConfiguration
positionDecreaseConfiguration =
JupiterPerpsPositionDecreaseAlarmActionConfigurationParser
@@ -104,23 +110,11 @@ public final class JupiterPerpsAlarmImpl {
variableResolver
);
SolanaWallet positionDecreaseWallet = new SolanaWalletImpl(
positionDecreaseConfiguration.walletId(),
positionDecreaseConfiguration.signerKeyName(),
solanaBlockChain
);
JupiterPerpsService positionDecreaseJupiter =
new AnchorIdlJupiterPerpsServiceImpl(
solanaBlockChain,
positionDecreaseWallet,
currencyIdentityService
);
JupiterPerpsEntryPriceVariableRefresher entryPriceVariableRefresher =
new JupiterPerpsEntryPriceVariableRefresher(
alarmConfiguration.variables(),
positionIncreaseJupiter
jupiterServicePerps,
solanaWallet.getAddress()
);
System.out.println("Done.");
@@ -146,14 +140,14 @@ public final class JupiterPerpsAlarmImpl {
actions.add(new ConsoleAlarmAction(alarmConfigurationDirectory));
actions.add(new JupiterPerpsPositionIncreaseAlarmAction(
positionIncreaseConfiguration,
positionIncreaseWallet,
positionIncreaseJupiter,
solanaWallet,
jupiterServicePerps,
variableResolver
));
actions.add(new JupiterPerpsPositionDecreaseAlarmAction(
positionDecreaseConfiguration,
positionDecreaseWallet,
positionDecreaseJupiter,
solanaWallet,
jupiterServicePerps,
variableResolver
));
actions.add(new PushoverAlarmAction(
@@ -215,7 +209,7 @@ public final class JupiterPerpsAlarmImpl {
new CountDownLatch(1).await();
}
private static void validateAlarmConditions(
private void validateAlarmConditions(
List<PriceAlarmDefinition> definitions,
AlarmVariableResolver variableResolver
) {
@@ -244,7 +238,7 @@ public final class JupiterPerpsAlarmImpl {
}
}
private static Map<JupiterPerpsAsset, List<PriceAlarmDefinition>> groupByAsset(
private Map<JupiterPerpsAsset, List<PriceAlarmDefinition>> groupByAsset(
List<PriceAlarmDefinition> definitions
) {
Map<JupiterPerpsAsset, List<PriceAlarmDefinition>> result = new EnumMap<>(
@@ -258,19 +252,16 @@ public final class JupiterPerpsAlarmImpl {
return result;
}
private static void printUsage() {
private void printUsage() {
System.err.println("""
Usage:
gradle run
gradle run --args='--config=/path/to/price-alarms.conf'
JupiterPerpsAlarmImpl is constructed and started by NenjimHubImpl.
Options:
--config=<path> Default: price-alarms.conf
--ws=<url1,url2,...> Default: wss://api.mainnet-beta.solana.com
Current Hub configuration:
--config=conf/alarms.conf
Environment:
PRICE_ALARMS_CONFIG Alternative default configuration path
SOLANA_WS_URLS Comma-separated RPC WebSocket endpoints
Default: wss://api.mainnet-beta.solana.com
""");
}
@@ -321,6 +312,9 @@ public final class JupiterPerpsAlarmImpl {
}
}
private JupiterPerpsAlarmImpl() {
}
private final SolanaWallet solanaWallet;
private final JupiterPerpsService jupiterServicePerps;
private final String configFilename;
private final Thread thread;
}
@@ -16,10 +16,12 @@ public final class JupiterPerpsEntryPriceVariableRefresher
public JupiterPerpsEntryPriceVariableRefresher(
Map<String, String> variables,
JupiterPerpsService jupiterPerpsService
JupiterPerpsService jupiterPerpsService,
ΩSolanaWalletIdΩ walletAddress
) {
this.variables = Objects.requireNonNull(variables, "variables");
this.jupiterPerpsService = Objects.requireNonNull(jupiterPerpsService, "jupiterPerpsService");
this.walletAddress = Objects.requireNonNull(walletAddress, "walletAddress");
scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(
@@ -56,20 +58,13 @@ public final class JupiterPerpsEntryPriceVariableRefresher
public synchronized void refresh() {
System.out.println("Executing refresh now...");
ΩSolanaWalletIdΩ wallet = variables.get("JUPITER_PERPS_WALLET");
if (wallet == null || wallet.isBlank()) {
System.err.println("Cannot refresh Jupiter Perps price variables: JUPITER_PERPS_WALLET is not configured");
return;
}
try {
System.out.println(" Fetching 'Open Positions':");
Set<JupiterPerpsPosition> positions = jupiterPerpsService.getOpenPositions(wallet);
Set<JupiterPerpsPosition> positions = jupiterPerpsService.getOpenPositions(walletAddress);
System.out.println(
"Fetched " + positions.size()
+ " open Jupiter Perps positions for wallet: " + wallet
+ " open Jupiter Perps positions for wallet: " + walletAddress
);
@@ -208,4 +203,5 @@ public final class JupiterPerpsEntryPriceVariableRefresher
private final ScheduledExecutorService scheduler;
private final Map<String, String> variables;
private final JupiterPerpsService jupiterPerpsService;
private final ΩSolanaWalletIdΩ walletAddress;
}
@@ -8,14 +8,10 @@ import java.util.Map;
import java.util.Objects;
public record JupiterPerpsPositionDecreaseAlarmActionConfiguration(
ΩSolanaWalletIdΩ walletId,
String signerKeyName,
ΩUSDCAmountΩ reservePositionSizeUsdLimit,
Map<Integer, PositionDecrease> positionDecreases
) {
public JupiterPerpsPositionDecreaseAlarmActionConfiguration {
Objects.requireNonNull(walletId, "walletId");
Objects.requireNonNull(signerKeyName, "signerKeyName");
Objects.requireNonNull(reservePositionSizeUsdLimit, "reservePositionSizeUsdLimit");
if (reservePositionSizeUsdLimit.signum() < 0) {
@@ -26,16 +22,6 @@ public record JupiterPerpsPositionDecreaseAlarmActionConfiguration(
}
positionDecreases = Map.copyOf(positionDecreases);
if (walletId.isBlank()) {
throw new IllegalArgumentException("Wallet id cannot be blank");
}
if (signerKeyName.isBlank()) {
throw new IllegalArgumentException(
"Signer key name cannot be blank"
);
}
}
public record PositionDecrease(
@@ -26,8 +26,6 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
);
Map<Integer, JupiterPerpsPositionDecreaseAlarmActionConfiguration.PositionDecrease>
positionDecreases = new LinkedHashMap<>();
ΩSolanaWalletIdΩ walletId = null;
String signerKeyName = null;
ΩUSDCAmountΩ reservePositionSizeUsdLimit = null;
for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) {
@@ -44,27 +42,7 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
String[] columns = line.split("\\s+");
String firstColumn = columns[0].toUpperCase(Locale.ROOT);
if (firstColumn.equals("WALLET_ID")) {
requireColumnCount(columns, 2, "WALLET_ID value");
if (walletId != null) {
throw new IllegalArgumentException(
"Duplicate WALLET_ID"
);
}
walletId = variableResolver.resolve(columns[1]);
} else if (firstColumn.equals("SIGNER_KEY_NAME")) {
requireColumnCount(
columns,
2,
"SIGNER_KEY_NAME value"
);
if (signerKeyName != null) {
throw new IllegalArgumentException(
"Duplicate SIGNER_KEY_NAME"
);
}
signerKeyName = variableResolver.resolve(columns[1]);
} else if (firstColumn.equals("RESERVE_POSITION_SIZE_USD_LIMIT")) {
if (firstColumn.equals("RESERVE_POSITION_SIZE_USD_LIMIT")) {
requireColumnCount(
columns,
2,
@@ -94,17 +72,6 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
}
}
if (walletId == null) {
throw new IllegalArgumentException(
"Missing WALLET_ID in " + path
);
}
if (signerKeyName == null) {
throw new IllegalArgumentException(
"Missing SIGNER_KEY_NAME in " + path
);
}
if (reservePositionSizeUsdLimit == null) {
throw new IllegalArgumentException(
"Missing RESERVE_POSITION_SIZE_USD_LIMIT in " + path
@@ -112,8 +79,6 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
}
return new JupiterPerpsPositionDecreaseAlarmActionConfiguration(
walletId,
signerKeyName,
reservePositionSizeUsdLimit,
positionDecreases
);
@@ -173,7 +138,7 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser {
: line.substring(0, commentStart);
}
private static final int SUPPORTED_FORMAT_VERSION = 1;
private static final int SUPPORTED_FORMAT_VERSION = 2;
private JupiterPerpsPositionDecreaseAlarmActionConfigurationParser() {
}
@@ -8,23 +8,13 @@ import java.util.Map;
import java.util.Objects;
public record JupiterPerpsPositionIncreaseAlarmActionConfiguration(
ΩSolanaWalletIdΩ walletId,
String signerKeyName,
ΩUSDCAmountΩ reserveUsdcLimit,
Map<Integer, PositionIncrease> positionIncreases
) {
public JupiterPerpsPositionIncreaseAlarmActionConfiguration {
Objects.requireNonNull(walletId, "walletId");
Objects.requireNonNull(signerKeyName, "signerKeyName");
Objects.requireNonNull(reserveUsdcLimit, "reserveUsdcLimit");
positionIncreases = Map.copyOf(positionIncreases);
if (walletId.isBlank()) {
throw new IllegalArgumentException("Wallet id cannot be blank");
}
if (signerKeyName.isBlank()) {
throw new IllegalArgumentException("Signer key name cannot be blank");
}
if (reserveUsdcLimit.signum() < 0) {
throw new IllegalArgumentException(
"USDC reserve limit cannot be negative: " + reserveUsdcLimit
@@ -26,8 +26,6 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser {
);
Map<Integer, JupiterPerpsPositionIncreaseAlarmActionConfiguration.PositionIncrease>
positionIncreases = new LinkedHashMap<>();
ΩSolanaWalletIdΩ walletId = null;
String signerKeyName = null;
ΩUSDCAmountΩ reserveUsdcLimit = null;
for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) {
@@ -44,19 +42,7 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser {
String[] columns = line.split("\\s+");
String firstColumn = columns[0].toUpperCase(Locale.ROOT);
if (firstColumn.equals("WALLET_ID")) {
requireColumnCount(columns, 2, "WALLET_ID value");
if (walletId != null) {
throw new IllegalArgumentException("Duplicate WALLET_ID");
}
walletId = variableResolver.resolve(columns[1]);
} else if (firstColumn.equals("SIGNER_KEY_NAME")) {
requireColumnCount(columns, 2, "SIGNER_KEY_NAME value");
if (signerKeyName != null) {
throw new IllegalArgumentException("Duplicate SIGNER_KEY_NAME");
}
signerKeyName = variableResolver.resolve(columns[1]);
} else if (firstColumn.equals("RESERVE_USDC_LIMIT")) {
if (firstColumn.equals("RESERVE_USDC_LIMIT")) {
requireColumnCount(columns, 2, "RESERVE_USDC_LIMIT value");
if (reserveUsdcLimit != null) {
throw new IllegalArgumentException(
@@ -81,14 +67,6 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser {
}
}
if (walletId == null) {
throw new IllegalArgumentException("Missing WALLET_ID in " + path);
}
if (signerKeyName == null) {
throw new IllegalArgumentException("Missing SIGNER_KEY_NAME in " + path);
}
if (reserveUsdcLimit == null) {
throw new IllegalArgumentException(
"Missing RESERVE_USDC_LIMIT in " + path
@@ -96,8 +74,6 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser {
}
return new JupiterPerpsPositionIncreaseAlarmActionConfiguration(
walletId,
signerKeyName,
reserveUsdcLimit,
positionIncreases
);
@@ -150,7 +126,7 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser {
return commentStart < 0 ? line : line.substring(0, commentStart);
}
private static final int SUPPORTED_FORMAT_VERSION = 1;
private static final int SUPPORTED_FORMAT_VERSION = 2;
private JupiterPerpsPositionIncreaseAlarmActionConfigurationParser() {
}
@@ -1,24 +1,56 @@
package com.r35157.libs.objcache;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Stores objects under structured keys for a caller-supplied time to live.
*/
public interface ObjectCache {
/**
* Stores or replaces an object.
*
* <p>A time to live of zero makes the entry immediately expired.</p>
*
* @param key cache key
* @param object object to cache
* @param ttlMillis time to live in milliseconds
* @throws NullPointerException if {@code key} or {@code object} is null
* @throws IllegalArgumentException if {@code ttlMillis} is negative
*/
void put(
ObjectCacheKey key,
Object object,
@NotNull ObjectCacheKey key,
@NotNull Object object,
long ttlMillis
);
@Nullable
Object get(
ObjectCacheKey key
/**
* Returns the cached object when it exists and has not expired.
*
* <p>An expired entry is treated as absent and is removed lazily by
* this operation.</p>
*
* @param key cache key
* @return cached object, or {@code null} when absent or expired
* @throws NullPointerException if {@code key} is null
*/
@Nullable Object get(
@NotNull ObjectCacheKey key
);
/**
* Removes an entry when present.
*
* @param key cache key
* @throws NullPointerException if {@code key} is null
*/
void remove(
ObjectCacheKey key
@NotNull ObjectCacheKey key
);
/**
* Removes all entries.
*/
void clear();
}
}
@@ -2,6 +2,7 @@ package com.r35157.libs.objcache.impl.ref;
import com.r35157.libs.objcache.ObjectCache;
import com.r35157.libs.objcache.ObjectCacheKey;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
@@ -10,10 +11,14 @@ import java.util.concurrent.ConcurrentMap;
public final class ObjectCacheImpl implements ObjectCache {
public ObjectCacheImpl() {
objects = new ConcurrentHashMap<>();
}
@Override
public void put(
ObjectCacheKey key,
Object object,
@NotNull ObjectCacheKey key,
@NotNull Object object,
long ttlMillis
) {
Objects.requireNonNull(key);
@@ -37,7 +42,7 @@ public final class ObjectCacheImpl implements ObjectCache {
@Override
public @Nullable Object get(
ObjectCacheKey key
@NotNull ObjectCacheKey key
) {
Objects.requireNonNull(key);
@@ -57,7 +62,7 @@ public final class ObjectCacheImpl implements ObjectCache {
@Override
public void remove(
ObjectCacheKey key
@NotNull ObjectCacheKey key
) {
Objects.requireNonNull(key);
objects.remove(key);
@@ -68,9 +73,6 @@ public final class ObjectCacheImpl implements ObjectCache {
objects.clear();
}
private final ConcurrentMap<
ObjectCacheKey,
ObjectCacheEntry
> objects = new ConcurrentHashMap<>();
private final ConcurrentMap<ObjectCacheKey, ObjectCacheEntry> objects;
}
@@ -41,16 +41,27 @@ import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.T
public class RaydiumImpl implements Raydium {
public RaydiumImpl(
HttpClient httpClient,
ObjectMapper objectMapper,
SolanaBlockChain solanaBlockChain,
CurrencyIdentityService currencyIdentityService
) {
this.solanaBlockChain = Objects.requireNonNull(solanaBlockChain, "solanaBlockChain");
this.httpClient = Objects.requireNonNull(
httpClient,
"httpClient"
);
this.objectMapper = Objects.requireNonNull(
objectMapper,
"objectMapper"
);
this.solanaBlockChain = Objects.requireNonNull(
solanaBlockChain,
"solanaBlockChain"
);
this.currencyIdentityService = Objects.requireNonNull(
currencyIdentityService,
"currencyIdentityService"
);
this.httpClient = HttpClient.newHttpClient();
this.objectMapper = new ObjectMapper();
}
@Override
@@ -10,6 +10,7 @@ import com.r35157.libs.solana.*;
import com.r35157.libs.solana.valuetypes.SolanaProgramDerivedAddress;
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
import com.r35157.libs.valuetypes.basic.MoneyAmount;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -37,13 +38,23 @@ import static com.r35157.libs.solana.SolanaConstants.RPC_URL;
public class SolanaBlockChainImpl implements SolanaBlockChain {
public SolanaBlockChainImpl(CurrencyIdentityService currencyIdentityService) {
public SolanaBlockChainImpl(
@NotNull HttpClient httpClient,
@NotNull ObjectMapper objectMapper,
@NotNull CurrencyIdentityService currencyIdentityService
) {
this.httpClient = Objects.requireNonNull(
httpClient,
"httpClient"
);
this.objectMapper = Objects.requireNonNull(
objectMapper,
"objectMapper"
);
this.currencyIdentityService = Objects.requireNonNull(
currencyIdentityService,
"currencyIdentityService"
);
this.httpClient = HttpClient.newHttpClient();
this.objectMapper = new ObjectMapper();
}
@Override
@@ -12,7 +12,6 @@ public final class NenjimComposerImpl implements NenjimComposer {
}
private void showWindow() {
var window = new Stage();
window.setTitle("Nenjim Composer");
window.show();
@@ -6,17 +6,27 @@ import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
/**
* Coordinates the lifecycle of Nenjim services and applications.
*/
public interface NenjimHub {
/**
* Start the NenjimHub - This is the first thing to do
* Starts all initialized services and applications in dependency order,
* then blocks while the Hub remains online.
*
* @throws Exception if startup or shutdown waiting fails
*/
void start() throws Exception;
/**
* Starts a process based on the provided fully qualified interface name.
* The actual implementation that is run is binded according to the Context.
* Requests startup of the implementation bound to a fully qualified process
* interface name in the active context.
*
* @param fqInterfaceName the fully qualified interface name of the process to start. The interface must extend the {@code NenjimProcess} interface.
* <p>Dynamic binding through this method is not yet implemented by the
* reference Hub.</p>
*
* @param fqInterfaceName fully qualified interface name; the interface must
* extend {@link NenjimProcess}
*/
void startProcess(String fqInterfaceName);
@@ -28,8 +38,7 @@ public interface NenjimHub {
HashMap<Integer, NenjimProcess> getRunningProcesses();
/**
* A no-operation (noop). This method is suppoted to do nothing.
* Performs no operation.
*/
void noop();
}
@@ -1,87 +1,12 @@
package com.r35157.nenjim.hubd.impl.ref;
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
import com.r35157.nenjim.hubd.ctx.Context;
import com.r35157.nenjim.hubd.NenjimHub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import com.r35157.nenjim.hubd.ctx.ContextManager;
import com.r35157.nenjim.hubd.journal.JournalManager;
import com.r35157.nenjim.hubd.impl.ref.JournalManagerImpl;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
public class Main {
// TODO: Consider if we really need a Main class or we just need to move the main method to NenjimHubImpl?
static void main(String[] args) throws Exception {
static void main() throws Exception {
NenjimHub nenjimHub = new NenjimHubImpl();
nenjimHub.start();
/*SolanaBlockChain sbc = new SolanaBlockChainImpl();
JupiterPerpsService jupiter = new AnchorIdlJupiterPerpsServiceImpl(sbc);
ΩSolanaWalletIdΩ walletId = "vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf";
Set<JupiterPerpsPosition> positions = jupiter.getOpenPositions(walletId);
*/
/*while(true) {
String addr = "31eu4pNE2vb8ErASGoJDHZXvjezhuE2PxjdnuQcCbUv7";
JupiterPerpsPosition pos = jupiter.getPosition(addr);
ΩUSDCAmountΩ a = pos.borrowFeesDue();
}*/
//int i = 0;
/* try {
log.info("Auto-starting 2 Nenjim application(s)...");
log.info(" Starting AssetAZCore...");
//com.r35157.;
log.info(" Starting jupiterperpsalarm...");
String[] params = new String[] {
"--config=/home/minimons/projects/com_r35157_nenjim-hubd-impl_ref/conf/alarms.conf"
};
com.r35157.jupiterperpsalarm.Main.main(params);
*/
//hub = new NenjimHubImpl();
/*
String classesCacheDirRaw = "~/.config/nenjim/cache/classes";
String classesCacheDir = (classesCacheDirRaw.startsWith("~/"))
? System.getProperty("user.home") + classesCacheDirRaw.substring(1)
: classesCacheDirRaw;
Path pathToClassesCache = Path.of(classesCacheDir);
if(Files.exists(pathToClassesCache) == false) {
System.err.println("Cannot find '" + classesCacheDirRaw + "'");
System.exit(-1);
}*/
//System.out.println("Initializing initial NenjimClassLoader with default context...");
//JournalManager journalManager = new JournalManagerImpl();
//ContextManager contextManager = new ContextManagerImpl();
//Context defaultContext = contextManager.getDefault();
//NenjimClassLoader nenjimClassLoader = new NenjimClassLoader(journalManager, defaultContext);
//Class<?> clazz = nenjimClassLoader.findClass("com.r35157.nenjim.hubd.impl.ref.NenjimHubImpl");
/*NenjimHub nenjimHub = null;
Context defaultContext = new Context();
Object instance = clazz.getDeclaredConstructor().newInstance();
NenjimKicker kicker = (NenjimKicker)instance;
*/
//int a = 0;
}
private static final Logger log = LoggerFactory.getLogger(Main.class);
}
@@ -2,6 +2,7 @@ package com.r35157.nenjim.hubd.impl.ref;
import com.fanitas.evelyn.core.Evelyn;
import com.fanitas.evelyn.core.impl.ref.EvelynImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource;
import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded.HardcodedPriceSource;
@@ -9,9 +10,17 @@ import com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool.R
import com.r35157.assetaz.services.ticker.impl.ref.TickerServiceImpl;
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
import com.r35157.assetaz.services.cis.impl.hc.HardcodedCurrencyIdentityService;
import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.cryptowallet.solana.impl.ref.SolanaWalletImpl;
import com.r35157.evelyn.emc.EvelynMissionControl;
import com.r35157.evelyn.emc.impl.ref.EvelynMissionControlImpl;
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
import com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
import com.r35157.libs.objcache.ObjectCache;
import com.r35157.libs.objcache.impl.ref.ObjectCacheImpl;
import com.r35157.libs.solana.impl.cached.CachedSolanaBlockChain;
import com.r35157.nenjim.composer.NenjimComposer;
import com.r35157.nenjim.composer.impl.ref.NenjimComposerImpl;
import com.r35157.nenjim.hubd.NenjimHub;
@@ -32,6 +41,8 @@ import crypto.r35157.nenjim.NenjimProcess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.http.HttpClient;
import java.time.Clock;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
@@ -39,9 +50,11 @@ import static com.r35157.assetaz.services.cis.CurrencyTypeIds.EVE_ID;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;
public class NenjimHubImpl implements NenjimHub {
public NenjimHubImpl() throws Exception {
public NenjimHubImpl() {
log.info("Initializing NenjimHub...");
initializeAll();
nextProcessId = 1;
//processesScope = new StructuredTaskScope.ShutdownOnFailure();
processes = new HashMap<>();
@@ -49,85 +62,35 @@ public class NenjimHubImpl implements NenjimHub {
@Override
public void start() throws Exception {
log.info("Starting autorun processes:");
startAutoRunProcesses();
log.info("Starting:");
log.info(" Services...");
tickerService.start();
jupiterPerpsAlarm.start();
evelynServiceProd.start();
evelynServiceTest.start();
log.info(" Applications...");
missionControl.start();
nenjimComposer.start();
processManager.start();
testTool.start();
sodaTaskManager.start();
suwimoClient.start();
System.out.println("Done - Now online!");
waitForAndShutdown();
}
private void waitForAndShutdown() throws InterruptedException {
System.out.println("Done - Now online!");
/*
try {
processesScope.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
processesScope.close();
*/
awaitShutdown();
System.out.println("Nenjim is now shutdown (stopped all processes)!");
@Override
public HashMap<Integer, NenjimProcess> getRunningProcesses() {
return processes;
}
private void startAutoRunProcesses() {
CurrencyIdentityService cis = new HardcodedCurrencyIdentityService();
SolanaBlockChain solanaBlockChain = new SolanaBlockChainImpl(cis);
Raydium raydium = new RaydiumImpl(solanaBlockChain, cis);
//PriceSource hardcodedPriceSource = new HardcodedPriceSource(cis);
PriceSource raydiumPoolPriceSource = createEVEUSDTPriceSource(cis, raydium);
TickerService tickerService = startAssetAZTickerService(raydiumPoolPriceSource);
//startJupiterPerpsAlarm(cis);
TradingPair eveUsdt = createEVEUSDTTradingPair(cis);
Evelyn evelynProd = new EvelynImpl("Production", tickerService, eveUsdt);
Evelyn evelynTest = new EvelynImpl("Test", tickerService, eveUsdt);
evelynProd.start();
evelynTest.start();
startEvelynMissionControl(evelynProd, evelynTest);
//startNenjimComposer();
//startNenjimProcessManager();
//startNenjimTestTool();
//startSodaTaskManager();
//startSuwimoClient();
// TODO: Old but more correct way to auto start plugins - but it is currently broken.
/*
String[] processesToAutoStart = {
//"com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl"
//"crypto.r35157.nenjim.NenjimHubSocketAdminAdapter",
//"crypto.r35157.nenjim.NenjimHubRestAdminAdapter",
//"crypto.r35157.nenjim.NenjimHubRPCAdminAdapter",
//"crypto.r35157.nenjim.SuwimoHub",
//"crypto.r35157.nenjim.SodaTaskManager",
//"crypto.r35157.assetaz.hub.AssetAZHub"
};
for (String processInterfaceName : processesToAutoStart) {
startProcess(processInterfaceName);
}
*/
}
private PriceSource createEVEUSDTPriceSource(CurrencyIdentityService cis, Raydium raydium) {
TradingPair eveUsdt = createEVEUSDTTradingPair(cis);
PriceSource priceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUsdt);
return priceSource;
}
private TradingPair createEVEUSDTTradingPair(CurrencyIdentityService cis) {
return new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
}
private TickerService startAssetAZTickerService(PriceSource... priceSources) {
TickerService tickerService = new TickerServiceImpl(priceSources);
tickerService.start();
return tickerService;
@Override
public void noop() {
System.out.println("NenjimHub command: 'noop'");
}
@Override
@@ -157,126 +120,115 @@ public class NenjimHubImpl implements NenjimHub {
});*/
}
@Override
public HashMap<Integer, NenjimProcess> getRunningProcesses() {
return processes;
private void initializeAll() {
log.info(" Initializing building blocks...");
initializeBuildingBlocks();
log.info(" Initializing plugins...");
initializePlugins();
log.info(" Initializing services...");
initializeServices();
log.info(" Initializing applications...");
initializeApplications();
}
@Override
public void noop() {
System.out.println("NenjimHub command: 'noop'");
private void initializeBuildingBlocks() {
objectCache = new ObjectCacheImpl();
clockUTC = Clock.systemUTC();
cis = new HardcodedCurrencyIdentityService();
httpClient = HttpClient.newHttpClient();
objectMapper = new ObjectMapper();
solanaBlockChain = new SolanaBlockChainImpl(httpClient, objectMapper, cis);
cachedSolanaBlockChain = new CachedSolanaBlockChain(solanaBlockChain, objectCache);
raydium = new RaydiumImpl(httpClient, objectMapper, cachedSolanaBlockChain, cis);
eveUSDT = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
solanaWalletEvelynPerpsTest = new SolanaWalletImpl(
"6NyYEoXmJyBXvU4pvv9aoxZNdyqqtkmPBZFEDkRDHcqp",
"evelyn_perps_test",
solanaBlockChain
);
solanaWalletEvelynPerpsProd = new SolanaWalletImpl(
"H5Yrdm7B4FHhTxhPw9SVwFzQyTEL6bAPptZuTyRxcepq",
"evelyn_perps_prod",
solanaBlockChain
);
}
private void awaitShutdown() throws InterruptedException {
private void initializePlugins() {
hardcodedPriceSource = new HardcodedPriceSource(cis);
raydiumPoolPriceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUSDT);
}
private void initializeServices() {
tickerService = new TickerServiceImpl(
//hardcodedPriceSource, // Activating this could be dangerous as other services rely on these prices
raydiumPoolPriceSource
);
jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
cachedSolanaBlockChain,
solanaWalletEvelynPerpsProd,
cis
);
jupiterServicePerpsTest = new AnchorIdlJupiterPerpsServiceImpl(
cachedSolanaBlockChain,
solanaWalletEvelynPerpsTest,
cis
);
jupiterPerpsAlarm = new JupiterPerpsAlarmImpl(
solanaWalletEvelynPerpsTest,
jupiterServicePerpsTest,
"--config=conf/alarms.conf"
);
evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);
evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);
// TODO: Old but more correct way to auto start plugins - but it is currently broken.
/*
String[] processesToAutoStart = {
//"com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl"
//"crypto.r35157.nenjim.NenjimHubSocketAdminAdapter",
//"crypto.r35157.nenjim.NenjimHubRestAdminAdapter",
//"crypto.r35157.nenjim.NenjimHubRPCAdminAdapter",
//"crypto.r35157.nenjim.SuwimoHub",
//"crypto.r35157.nenjim.SodaTaskManager",
//"crypto.r35157.assetaz.hub.AssetAZHub"
};
for (String processInterfaceName : processesToAutoStart) {
startProcess(processInterfaceName);
}
*/
}
private void initializeApplications() {
nenjimComposer = new NenjimComposerImpl();
processManager = new NenjimProcessManagerImpl();
testTool = new NenjimTestToolImpl();
sodaTaskManager = new SodaTaskManagerImpl();
missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
suwimoClient = new SuwimoClientImpl();
}
private void waitForAndShutdown() throws InterruptedException {
/*
try {
processesScope.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
processesScope.close();
*/
shutdownLatch.await();
}
private void startJupiterPerpsAlarm(
CurrencyIdentityService currencyIdentityService
) {
Thread thread = new Thread(() -> {
try {
JupiterPerpsAlarmImpl.start(
new String[] {"--config=conf/alarms.conf"},
currencyIdentityService
);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}, "Nenjim Plugin - JupiterPerpsAlarm");
thread.setDaemon(false);
thread.start();
}
private void startEvelynMissionControl(Evelyn evelynProd, Evelyn evelynTest) {
Thread thread = new Thread(() -> {
try {
EvelynMissionControl emc = new EvelynMissionControlImpl(evelynProd, evelynTest);
emc.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}, "Nenjim Plugin - Evelyn Mission Control");
thread.setDaemon(false);
thread.start();
}
private void startNenjimComposer() {
Thread thread = new Thread(() -> {
try {
NenjimComposer nc = new NenjimComposerImpl();
nc.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}, "Nenjim Plugin - Nenjim Composer");
thread.setDaemon(false);
thread.start();
}
private void startNenjimProcessManager() {
Thread thread = new Thread(() -> {
try {
NenjimProcessManager npm = new NenjimProcessManagerImpl();
npm.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}, "Nenjim Plugin - Nenjim Process Manager");
thread.setDaemon(false);
thread.start();
}
private void startNenjimTestTool() {
Thread thread = new Thread(() -> {
try {
NenjimTestTool ntt = new NenjimTestToolImpl();
ntt.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}, "Nenjim Plugin - Nenjim Test Tool");
thread.setDaemon(false);
thread.start();
}
private void startSodaTaskManager() {
Thread thread = new Thread(() -> {
try {
SodaTaskManager stm = new SodaTaskManagerImpl();
stm.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}, "Nenjim Plugin - Soda Task Manager");
thread.setDaemon(false);
thread.start();
}
private void startSuwimoClient() {
Thread thread = new Thread(() -> {
try {
SuwimoClient client = new SuwimoClientImpl();
client.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}, "Nenjim Plugin - Suwimo Client");
thread.setDaemon(false);
thread.start();
System.out.println("Nenjim is now shutdown (stopped all processes)!");
}
private static final Logger log = LoggerFactory.getLogger(NenjimHubImpl.class);
@@ -287,4 +239,38 @@ public class NenjimHubImpl implements NenjimHub {
private HashMap<Integer, NenjimProcess> processes;
//private StructuredTaskScope.ShutdownOnFailure processesScope;
private int nextProcessId;
// Dependency components
private ObjectCache objectCache;
private Clock clockUTC;
private CurrencyIdentityService cis;
private SolanaBlockChain solanaBlockChain;
private SolanaBlockChain cachedSolanaBlockChain;
private Raydium raydium;
private TradingPair eveUSDT;
private HttpClient httpClient;
private ObjectMapper objectMapper;
private SolanaWallet evelynPerpsProd;
private SolanaWallet solanaWalletEvelynPerpsProd;
private SolanaWallet solanaWalletEvelynPerpsTest;
private JupiterPerpsService jupiterServicePerpsProd;
private JupiterPerpsService jupiterServicePerpsTest;
// Plugins
private PriceSource hardcodedPriceSource;
private PriceSource raydiumPoolPriceSource;
// Services
private TickerService tickerService;
private JupiterPerpsAlarm jupiterPerpsAlarm;
private Evelyn evelynServiceProd;
private Evelyn evelynServiceTest;
// Applications
private EvelynMissionControl missionControl;
private NenjimComposer nenjimComposer;
private NenjimProcessManager processManager;
private NenjimTestTool testTool;
private SodaTaskManager sodaTaskManager;
private SuwimoClient suwimoClient;
}
@@ -1,5 +1,6 @@
package com.r35157.libs.solana.impl.ref;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
import com.r35157.assetaz.services.cis.ExternalCurrencyReference;
import com.r35157.assetaz.valuetypes.CurrencyType;
@@ -10,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.net.http.HttpClient;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
@@ -154,7 +156,11 @@ class SolanaBlockChainImplTest {
long fee,
AtomicReference<String> feeMessage
) {
return new SolanaBlockChainImpl(CURRENCY_IDENTITIES) {
return new SolanaBlockChainImpl(
HTTP_CLIENT,
OBJECT_MAPPER,
CURRENCY_IDENTITIES
) {
@Override
public long getBalanceInLamport(String address) {
return balance;
@@ -215,6 +221,8 @@ class SolanaBlockChainImplTest {
return Set.of();
}
};
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String SENDER =
"So11111111111111111111111111111111111111112";
private static final String RECIPIENT =