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
+135 -48
View File
@@ -4,28 +4,39 @@ The Jupiter Perps Price Alarm monitors the Jupiter Perps aggregated oracle accou
Oracle prices are received through Solana WebSocket `accountSubscribe` using `processed` commitment. The application does not poll prices once per second. It also fetches the current oracle account state whenever a WebSocket connection opens, reconnects automatically, and supports multiple RPC endpoints for redundancy.
The alarm is currently initialized and started by `NenjimHubImpl`. Its
`SolanaWallet` and `JupiterPerpsService` are selected by the Hub and injected
before startup. The alarm action configuration files do not select a wallet or
signing key.
> **Important:** This application is not necessarily notification-only. Every active entry in a Jupiter Perps position action file can sign and submit a real transaction. Review both transaction action files before running it.
## Startup flow
The application performs the following steps before it starts monitoring prices:
The alarm performs the following steps before it starts monitoring prices:
1. Parse command-line options and the alarm configuration file.
2. Fetch the wallet's current open Jupiter Perps positions.
3. Populate entry-price and liquidation-price variables.
1. Parse the configured alarm path, WebSocket endpoint environment and
`alarms.conf`.
2. Parse the position-increase and position-decrease action files, including
their structure and reserve settings.
3. Fetch the injected wallet's current open Jupiter Perps positions and populate
entry-price and liquidation-price variables.
4. Resolve and validate every alarm condition.
5. Start the periodic variable refresher and file-trigger watcher.
6. Parse and validate the four action configuration files.
6. Parse the console and Pushover action files and construct all alarm actions.
7. Open one oracle WebSocket connection per configured asset and RPC endpoint.
Startup fails before WebSockets are opened if an alarm condition or action configuration contains an unresolved variable or invalid value.
Startup fails before WebSockets are opened if an alarm condition or an action
value that is resolved during startup contains an unavailable variable or
invalid value. Jupiter transaction-row expressions are resolved and validated
when their alarm triggers.
## Requirements and build
The project uses:
- JDK 25
- Gradle 9.3.1 through the Gradle wrapper
- Gradle 9.6.1 through the Gradle wrapper
- Java preview features
- Detag-generated Java sources from `src/main/tjava`
@@ -43,35 +54,45 @@ To create the application JAR and collect runtime dependencies in `build/libs`:
There is currently no alarm-specific automated self-test task.
## Running the alarm application
## Running through Nenjim
The alarm entry point is:
The alarm no longer has a standalone `main` method. `NenjimHubImpl` constructs
the wallet, Jupiter service and alarm, then starts the alarm as part of normal
Hub startup. Run the configured application with:
```text
com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl
```bash
./gradlew run
```
The Gradle application plugin currently points at the Nenjim Hub main class, so `./gradlew run` does **not** directly start the alarm application. Run `JupiterPerpsAlarmImpl` from the IDE, or run the prepared JAR and dependencies explicitly:
After `./gradlew prepareLibs`, it can also be started explicitly with the Nenjim
Hub main class:
```bash
java --enable-preview \
-cp "build/libs/*" \
com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl \
--config=conf/alarms.conf
com.r35157.nenjim.hubd.impl.ref.Main
```
The repository contains example files for the alarm configuration and every action configuration. The real `conf/*.conf` files are ignored by Git; keep credentials and operational wallet details out of version control.
The current Hub composition binds the alarm to `conf/alarms.conf` and to the
test Jupiter Perps wallet and service. Until Nenjim provides configured
injection, a different configuration or wallet must be selected in the Hub's
composition code, not in an alarm action file.
### Command-line options and environment variables
The repository contains example files for the alarm configuration and every
action configuration. The real `conf/*.conf` files are ignored by Git; keep
credentials and operational details out of version control.
| Purpose | Command-line option | Environment variable | Default |
|---|---|---|---|
| Alarm configuration | `--config=<path>` | `PRICE_ALARMS_CONFIG` | `price-alarms.conf` |
| Solana WebSocket endpoints | `--ws=<url1,url2,...>` | `SOLANA_WS_URLS` | `wss://api.mainnet-beta.solana.com` |
### Environment variables
The command-line option takes precedence over its environment variable.
The Hub does not currently forward alarm-specific command-line options. Solana
WebSocket endpoints can be overridden with:
The variable-refresh file watcher uses the configuration file's parent directory. Until that is changed, use a configuration path with an explicit directory component, such as `conf/alarms.conf` or `./price-alarms.conf`, rather than relying on the bare default filename.
```bash
export SOLANA_WS_URLS='wss://first-provider.example,wss://second-provider.example'
```
If `SOLANA_WS_URLS` is absent, the alarm uses
`wss://api.mainnet-beta.solana.com`.
## Configuration files
@@ -79,11 +100,11 @@ The application uses five configuration files:
| File | Purpose |
|---|---|
| `alarms.conf` | Variables and price-alarm definitions |
| `alarms.conf` | Shared variables and price-alarm definitions |
| `alarmaction_Console.conf` | Alarm IDs written to the console |
| `alarmaction_Pushover.conf` | Pushover credentials and notifications |
| `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` | Jupiter wallet, signer, and position increases |
| `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf` | Jupiter wallet, signer, and position decreases |
| `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` | USDC reserve and position increases |
| `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf` | Position-size reserve and position decreases |
The four action files are resolved in the same directory as the file supplied through `--config`. All five files are required at startup, although an action can contain no alarm IDs. Blank lines and lines beginning with `#` are ignored.
@@ -97,12 +118,16 @@ Example files:
### Format version
Every configuration file must declare its format version as its first actual
configuration entry:
Every configuration file must declare its own format version as its first
actual configuration entry:
```text
FORMAT_VERSION=1
```
| File | Format version |
|---|---:|
| `alarms.conf` | `2` |
| `alarmaction_Console.conf` | `1` |
| `alarmaction_Pushover.conf` | `1` |
| `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` | `2` |
| `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf` | `2` |
Blank lines and full-line comments may appear before it. The application
rejects a missing, duplicate, malformed, misplaced, older, or newer version
@@ -121,12 +146,15 @@ the project-wide policy.
Variable definitions live in `alarms.conf`. A definition consists of a name and a single-token value:
```text
{{JUPITER_PERPS_WALLET}} vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf
{{SOL_WARNING_PRICE}} 150.00
```
Variable names must use uppercase letters, digits, and underscores. User-defined variables can be referenced from alarm conditions and action configuration values as `{{NAME}}`.
`JUPITER_PERPS_WALLET` is required for the initial and periodic Jupiter position fetches. For open SOL, BTC, and ETH positions, the refresher supplies these dynamic variables:
The wallet address used for initial and periodic Jupiter position fetches comes
from the injected `SolanaWallet`; it is not a shared configuration variable.
For open SOL, BTC, and ETH positions belonging to that wallet, the refresher
supplies these dynamic variables:
```text
{{SOL_LONG_ENTRY_PRICE}} {{SOL_LONG_LIQ_PRICE}}
@@ -137,9 +165,17 @@ Variable names must use uppercase letters, digits, and underscores. User-defined
{{ETH_SHORT_ENTRY_PRICE}} {{ETH_SHORT_LIQ_PRICE}}
```
The refresher replaces user-supplied variables with these names when position data is fetched. Only variables for positions returned by Jupiter are populated. Startup validation fails if a condition or action value references a variable that is unavailable after the initial position fetch.
The refresher replaces user-supplied variables with these names when position
data is fetched. Only variables for positions returned by Jupiter are
populated. Startup validation fails if an alarm condition references a variable
that is unavailable after the initial position fetch. Transaction-row
expressions are resolved later, when their alarm triggers.
Alarm conditions, Pushover notes, and Jupiter position-increase rows are resolved again when an alarm triggers, so refreshed values take effect without restarting the application. Pushover credentials and severity plus the Jupiter wallet and signer are resolved during startup.
Alarm conditions, Pushover notes, and Jupiter position-increase and
position-decrease rows are resolved again when an alarm triggers, so refreshed
values take effect without restarting the application. Pushover credentials
and severity are resolved during startup. The wallet and signer belong to the
injected `SolanaWallet` and are not resolved from alarm configuration.
## Alarm definitions
@@ -362,13 +398,13 @@ Credentials, severity, and notes may reference shared variables. Credentials and
### Automatic position increase
`alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` contains the wallet and signer followed by zero or more position increases:
`alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` contains a minimum
USDC reserve followed by zero or more position increases:
```text
FORMAT_VERSION=1
FORMAT_VERSION=2
WALLET_ID {{JUPITER_PERPS_WALLET}}
SIGNER_KEY_NAME <jup-key-name>
RESERVE_USDC_LIMIT 10
# ID ASSET DIRECTION COLLATERAL_USDC SIZE_DELTA_USD MAX_SLIPPAGE_BPS
4 SOL LONG 0.25 2.50 200
@@ -382,11 +418,55 @@ Each active row can submit a real transaction when its alarm triggers. Supported
- `SIZE_DELTA_USD`: zero or greater.
- `MAX_SLIPPAGE_BPS`: from `0` to `10000`.
Wallet, signer, asset, direction, amounts, and slippage may reference shared variables. Wallet and signer are resolved during startup. Every transaction row is validated during startup and resolved again at trigger time, allowing refreshed variables to change the transaction parameters without a restart.
`RESERVE_USDC_LIMIT` is the amount of USDC that must remain after collateral is
deducted. An increase is skipped unless the injected wallet's USDC balance can
cover both the requested collateral and this reserve.
For a configured ID, the action builds a Jupiter position-increase transaction, signs it by running `jup sign -f json --key <SIGNER_KEY_NAME> --tx <serialized-transaction>`, verifies that the returned signer matches `WALLET_ID`, and submits the signed transaction. IDs absent from this file never perform a transaction.
Asset, direction, amounts, and slippage may reference shared variables. Every
transaction row is structurally parsed during startup, then resolved and
validated at trigger time. This allows refreshed variables to change the
transaction parameters without a restart.
The example transaction rows are commented out. Do not activate a row until its alarm condition, wallet, signer, asset, direction, amounts, and slippage have been reviewed.
For a configured ID, the injected `JupiterPerpsService` builds the position
increase, uses its injected wallet to sign it, submits it and awaits the
transaction confirmation. IDs absent from this file never perform a
transaction.
The example transaction rows are commented out. Do not activate a row until
its alarm condition, injected wallet binding, asset, direction, amounts,
reserve, and slippage have been reviewed.
### Automatic position decrease
`alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf` contains a minimum
remaining position-size reserve followed by zero or more position decreases:
```text
FORMAT_VERSION=2
RESERVE_POSITION_SIZE_USD_LIMIT 10
# ID ASSET DIRECTION RECEIVE_TOKEN SIZE_DELTA_USD MAX_SLIPPAGE_BPS
3 SOL SHORT USDC 5.00 200
```
Supported values are:
- `ASSET`: `SOL`, `ETH`, or `BTC`.
- `DIRECTION`: `LONG` or `SHORT`.
- `RECEIVE_TOKEN`: `USDC` or the token matching `ASSET`.
- `SIZE_DELTA_USD`: greater than zero.
- `MAX_SLIPPAGE_BPS`: from `0` to `10000`.
The action finds the injected wallet's single matching open position. It skips
the decrease if the resulting position size would be below
`RESERVE_POSITION_SIZE_USD_LIMIT`. Asset, direction, receive token, amount and
slippage may reference shared variables and are resolved again at trigger time.
For a configured ID, the injected `JupiterPerpsService` builds the position
decrease, uses its injected wallet to sign it, submits it and awaits the
transaction confirmation. IDs absent from this file never perform a
transaction.
## RPC redundancy and reconnect behavior
@@ -407,7 +487,11 @@ Each connection:
## Validation and errors
Structural configuration errors include the file path and line number. After the initial Jupiter position fetch, every condition is resolved and parsed. The action files are then parsed and validated before WebSockets are opened.
Structural configuration errors include the file path and line number. The two
Jupiter action files are parsed before the initial position fetch. After that
fetch, every alarm condition is resolved and parsed, and the console and
Pushover files are loaded before WebSockets are opened. Jupiter transaction-row
expressions are resolved and validated only when their alarm triggers.
Startup validation rejects, among other things:
@@ -418,12 +502,14 @@ Startup validation rejects, among other things:
- missing range targets;
- negative target values or percentages;
- ranges whose lower target exceeds the upper target;
- missing action credentials or wallet settings;
- missing action credentials or reserve settings;
- duplicate action IDs;
- invalid Pushover severities;
- invalid Jupiter assets, directions, amounts, or slippage.
- invalid Pushover severities.
At runtime, a later condition resolution/parsing failure skips that price event for the affected alarm. A later action-variable failure is logged by the action dispatcher, and the remaining actions are still attempted.
At runtime, a later condition resolution/parsing failure skips that price event
for the affected alarm. Invalid Jupiter assets, directions, receive tokens,
amounts, slippage, or unavailable action variables are logged by the action
dispatcher, and the remaining actions are still attempted.
## Current limitations
@@ -434,6 +520,7 @@ At runtime, a later condition resolution/parsing failure skips that price event
- Target expressions support only a decimal value with an optional single percentage adjustment; they are not a general expression language.
- Configuration files are read only during startup; editing an action file requires a restart.
- Action IDs are not cross-validated against `alarms.conf`; an unknown ID is accepted but never triggered.
- Pushover credentials and severity plus the Jupiter wallet and signer are resolved only during startup. Dynamic re-resolution applies to conditions, Pushover notes, and Jupiter transaction rows.
- A bare default configuration path has no parent directory for the refresh watcher; use an explicit path such as `./price-alarms.conf` or `conf/alarms.conf`.
- Pushover credentials and severity are resolved only during startup. Dynamic
re-resolution applies to conditions, Pushover notes, and Jupiter transaction
rows. Wallet and signer selection requires changing the Hub composition.
- This application is a monitoring and automation aid, not a substitute for independent risk controls.
@@ -1,9 +1,5 @@
FORMAT_VERSION=1
FORMAT_VERSION=2
# Wallet and signer used for position-decrease transactions
################################################################
WALLET_ID {{JUPITER_PERPS_WALLET}}
SIGNER_KEY_NAME <SIGNER>
RESERVE_POSITION_SIZE_USD_LIMIT 10
# Id Asset Direction ReceiveToken SizeDeltaUSD MaxSlippageBPS
@@ -1,9 +1,5 @@
FORMAT_VERSION=1
FORMAT_VERSION=2
# Wallet and signer used for position-increase transactions
################################################################
WALLET_ID {{JUPITER_PERPS_WALLET}}
SIGNER_KEY_NAME <SIGNER>
RESERVE_USDC_LIMIT 10
# Id Asset Direction CollateralUSDC SizeDeltaUSD MaxSlippageBPS
+1 -6
View File
@@ -1,9 +1,4 @@
FORMAT_VERSION=1
# General configurations
#####################################################
{{JUPITER_PERPS_WALLET}} vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf
FORMAT_VERSION=2
# Constant Name Value
#####################################
@@ -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 =