From f5a2183a4068a5b1555595cab7273898dbeff584 Mon Sep 17 00:00:00 2001 From: Minimons Date: Thu, 13 Aug 2026 20:17:41 +0200 Subject: [PATCH] X: Clean-up and documentation updates --- README_alarm.md | 183 ++++++--- ...psPositionDecreaseAlarmAction.conf.example | 6 +- ...psPositionIncreaseAlarmAction.conf.example | 6 +- conf/alarms.conf.example | 7 +- .../evelyn/core/impl/ref/EvelynImpl.tjava | 20 +- .../impl/hardcoded/HardcodedPriceSource.tjava | 5 +- .../impl/ref/EvelynMissionControlImpl.tjava | 6 +- .../jupiterperpsalarm/JupiterPerpsAlarm.tjava | 17 +- .../impl/ref/AlarmConfigurationParser.tjava | 2 +- .../impl/ref/JupiterPerpsAlarmImpl.tjava | 124 +++--- ...iterPerpsEntryPriceVariableRefresher.tjava | 16 +- ...tionDecreaseAlarmActionConfiguration.tjava | 14 - ...creaseAlarmActionConfigurationParser.tjava | 39 +- ...tionIncreaseAlarmActionConfiguration.tjava | 10 - ...creaseAlarmActionConfigurationParser.tjava | 28 +- .../r35157/libs/objcache/ObjectCache.tjava | 48 ++- .../objcache/impl/ref/ObjectCacheImpl.tjava | 18 +- .../libs/raydium/impl/ref/RaydiumImpl.tjava | 17 +- .../impl/ref/SolanaBlockChainImpl.tjava | 17 +- .../impl/ref/NenjimComposerImpl.tjava | 1 - .../com/r35157/nenjim/hubd/NenjimHub.tjava | 21 +- .../r35157/nenjim/hubd/impl/ref/Main.tjava | 77 +--- .../nenjim/hubd/impl/ref/NenjimHubImpl.tjava | 364 +++++++++--------- .../impl/ref/SolanaBlockChainImplTest.java | 10 +- 24 files changed, 515 insertions(+), 541 deletions(-) diff --git a/README_alarm.md b/README_alarm.md index d4aef73..74e3751 100644 --- a/README_alarm.md +++ b/README_alarm.md @@ -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=` | `PRICE_ALARMS_CONFIG` | `price-alarms.conf` | -| Solana WebSocket endpoints | `--ws=` | `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 +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 --tx `, 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. diff --git a/conf/alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf.example b/conf/alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf.example index a0a5fd2..7b33f66 100644 --- a/conf/alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf.example +++ b/conf/alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf.example @@ -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 RESERVE_POSITION_SIZE_USD_LIMIT 10 # Id Asset Direction ReceiveToken SizeDeltaUSD MaxSlippageBPS diff --git a/conf/alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf.example b/conf/alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf.example index 1f10f92..1bc2994 100644 --- a/conf/alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf.example +++ b/conf/alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf.example @@ -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 RESERVE_USDC_LIMIT 10 # Id Asset Direction CollateralUSDC SizeDeltaUSD MaxSlippageBPS diff --git a/conf/alarms.conf.example b/conf/alarms.conf.example index 7023e76..f16ccfc 100644 --- a/conf/alarms.conf.example +++ b/conf/alarms.conf.example @@ -1,9 +1,4 @@ -FORMAT_VERSION=1 - -# General configurations -##################################################### -{{JUPITER_PERPS_WALLET}} vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf - +FORMAT_VERSION=2 # Constant Name Value ##################################### diff --git a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava index c6d0b38..dda13e5 100644 --- a/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava +++ b/src/main/tjava/com/fanitas/evelyn/core/impl/ref/EvelynImpl.tjava @@ -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 statusIndexHistory = new ArrayList<>(); + private final List statusIndexHistory; private ScheduledExecutorService statusIndexScheduler; private Instant lastPersistedTimestamp; diff --git a/src/main/tjava/com/r35157/assetaz/services/ticker/plugins/pricesource/impl/hardcoded/HardcodedPriceSource.tjava b/src/main/tjava/com/r35157/assetaz/services/ticker/plugins/pricesource/impl/hardcoded/HardcodedPriceSource.tjava index 589760f..2047c71 100644 --- a/src/main/tjava/com/r35157/assetaz/services/ticker/plugins/pricesource/impl/hardcoded/HardcodedPriceSource.tjava +++ b/src/main/tjava/com/r35157/assetaz/services/ticker/plugins/pricesource/impl/hardcoded/HardcodedPriceSource.tjava @@ -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"); } diff --git a/src/main/tjava/com/r35157/evelyn/emc/impl/ref/EvelynMissionControlImpl.tjava b/src/main/tjava/com/r35157/evelyn/emc/impl/ref/EvelynMissionControlImpl.tjava index f10a97b..5b6fa8c 100644 --- a/src/main/tjava/com/r35157/evelyn/emc/impl/ref/EvelynMissionControlImpl.tjava +++ b/src/main/tjava/com/r35157/evelyn/emc/impl/ref/EvelynMissionControlImpl.tjava @@ -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); } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/JupiterPerpsAlarm.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/JupiterPerpsAlarm.tjava index 3d6142e..2ec7f05 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/JupiterPerpsAlarm.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/JupiterPerpsAlarm.tjava @@ -1,4 +1,19 @@ package com.r35157.jupiterperpsalarm; -public class JupiterPerpsAlarm { +/** + * Monitors configured Jupiter Perps prices and dispatches matching alarm + * actions. + * + *

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.

+ */ +public interface JupiterPerpsAlarm { + /** + * Starts alarm monitoring asynchronously. + * + *

Each instance must be started at most once.

+ */ + void start(); } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava index 52fcc4f..ddbfde7 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava @@ -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() { } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsAlarmImpl.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsAlarmImpl.tjava index e54f837..d593107 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsAlarmImpl.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsAlarmImpl.tjava @@ -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 definitions, AlarmVariableResolver variableResolver ) { @@ -244,7 +238,7 @@ public final class JupiterPerpsAlarmImpl { } } - private static Map> groupByAsset( + private Map> groupByAsset( List definitions ) { Map> 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= Default: price-alarms.conf - --ws= 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; } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsEntryPriceVariableRefresher.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsEntryPriceVariableRefresher.tjava index 86e72b2..41a319d 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsEntryPriceVariableRefresher.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsEntryPriceVariableRefresher.tjava @@ -16,10 +16,12 @@ public final class JupiterPerpsEntryPriceVariableRefresher public JupiterPerpsEntryPriceVariableRefresher( Map 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 positions = jupiterPerpsService.getOpenPositions(wallet); + Set 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 variables; private final JupiterPerpsService jupiterPerpsService; + private final ΩSolanaWalletIdΩ walletAddress; } \ No newline at end of file diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionDecreaseAlarmActionConfiguration.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionDecreaseAlarmActionConfiguration.tjava index 6bbc4b9..885f650 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionDecreaseAlarmActionConfiguration.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionDecreaseAlarmActionConfiguration.tjava @@ -8,14 +8,10 @@ import java.util.Map; import java.util.Objects; public record JupiterPerpsPositionDecreaseAlarmActionConfiguration( - ΩSolanaWalletIdΩ walletId, - String signerKeyName, ΩUSDCAmountΩ reservePositionSizeUsdLimit, Map 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( diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionDecreaseAlarmActionConfigurationParser.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionDecreaseAlarmActionConfigurationParser.tjava index 7b23e4b..d45d4b3 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionDecreaseAlarmActionConfigurationParser.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionDecreaseAlarmActionConfigurationParser.tjava @@ -26,8 +26,6 @@ public final class JupiterPerpsPositionDecreaseAlarmActionConfigurationParser { ); Map 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() { } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfiguration.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfiguration.tjava index 1f62c03..c796343 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfiguration.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfiguration.tjava @@ -8,23 +8,13 @@ import java.util.Map; import java.util.Objects; public record JupiterPerpsPositionIncreaseAlarmActionConfiguration( - ΩSolanaWalletIdΩ walletId, - String signerKeyName, ΩUSDCAmountΩ reserveUsdcLimit, Map 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 diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfigurationParser.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfigurationParser.tjava index 5551bef..9d09724 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfigurationParser.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfigurationParser.tjava @@ -26,8 +26,6 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser { ); Map 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() { } diff --git a/src/main/tjava/com/r35157/libs/objcache/ObjectCache.tjava b/src/main/tjava/com/r35157/libs/objcache/ObjectCache.tjava index 7715f72..b72d7f8 100644 --- a/src/main/tjava/com/r35157/libs/objcache/ObjectCache.tjava +++ b/src/main/tjava/com/r35157/libs/objcache/ObjectCache.tjava @@ -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. + * + *

A time to live of zero makes the entry immediately expired.

+ * + * @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. + * + *

An expired entry is treated as absent and is removed lazily by + * this operation.

+ * + * @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(); - -} \ No newline at end of file +} diff --git a/src/main/tjava/com/r35157/libs/objcache/impl/ref/ObjectCacheImpl.tjava b/src/main/tjava/com/r35157/libs/objcache/impl/ref/ObjectCacheImpl.tjava index 080d76f..a77173e 100644 --- a/src/main/tjava/com/r35157/libs/objcache/impl/ref/ObjectCacheImpl.tjava +++ b/src/main/tjava/com/r35157/libs/objcache/impl/ref/ObjectCacheImpl.tjava @@ -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 objects; } \ No newline at end of file diff --git a/src/main/tjava/com/r35157/libs/raydium/impl/ref/RaydiumImpl.tjava b/src/main/tjava/com/r35157/libs/raydium/impl/ref/RaydiumImpl.tjava index 7048187..103486e 100644 --- a/src/main/tjava/com/r35157/libs/raydium/impl/ref/RaydiumImpl.tjava +++ b/src/main/tjava/com/r35157/libs/raydium/impl/ref/RaydiumImpl.tjava @@ -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 diff --git a/src/main/tjava/com/r35157/libs/solana/impl/ref/SolanaBlockChainImpl.tjava b/src/main/tjava/com/r35157/libs/solana/impl/ref/SolanaBlockChainImpl.tjava index b67327b..2047c74 100644 --- a/src/main/tjava/com/r35157/libs/solana/impl/ref/SolanaBlockChainImpl.tjava +++ b/src/main/tjava/com/r35157/libs/solana/impl/ref/SolanaBlockChainImpl.tjava @@ -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 diff --git a/src/main/tjava/com/r35157/nenjim/composer/impl/ref/NenjimComposerImpl.tjava b/src/main/tjava/com/r35157/nenjim/composer/impl/ref/NenjimComposerImpl.tjava index f126979..9540dce 100644 --- a/src/main/tjava/com/r35157/nenjim/composer/impl/ref/NenjimComposerImpl.tjava +++ b/src/main/tjava/com/r35157/nenjim/composer/impl/ref/NenjimComposerImpl.tjava @@ -12,7 +12,6 @@ public final class NenjimComposerImpl implements NenjimComposer { } private void showWindow() { - var window = new Stage(); window.setTitle("Nenjim Composer"); window.show(); diff --git a/src/main/tjava/com/r35157/nenjim/hubd/NenjimHub.tjava b/src/main/tjava/com/r35157/nenjim/hubd/NenjimHub.tjava index bab6555..679eb87 100644 --- a/src/main/tjava/com/r35157/nenjim/hubd/NenjimHub.tjava +++ b/src/main/tjava/com/r35157/nenjim/hubd/NenjimHub.tjava @@ -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. + *

Dynamic binding through this method is not yet implemented by the + * reference Hub.

+ * + * @param fqInterfaceName fully qualified interface name; the interface must + * extend {@link NenjimProcess} */ void startProcess(String fqInterfaceName); @@ -28,8 +38,7 @@ public interface NenjimHub { HashMap getRunningProcesses(); /** - * A no-operation (noop). This method is suppoted to do nothing. + * Performs no operation. */ void noop(); } - diff --git a/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/Main.tjava b/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/Main.tjava index be45ddd..80e5a82 100644 --- a/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/Main.tjava +++ b/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/Main.tjava @@ -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 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); } diff --git a/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/NenjimHubImpl.tjava b/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/NenjimHubImpl.tjava index 10f273d..a01fe88 100644 --- a/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/NenjimHubImpl.tjava +++ b/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/NenjimHubImpl.tjava @@ -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 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 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 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; } diff --git a/src/test/java/com/r35157/libs/solana/impl/ref/SolanaBlockChainImplTest.java b/src/test/java/com/r35157/libs/solana/impl/ref/SolanaBlockChainImplTest.java index f727104..941d635 100644 --- a/src/test/java/com/r35157/libs/solana/impl/ref/SolanaBlockChainImplTest.java +++ b/src/test/java/com/r35157/libs/solana/impl/ref/SolanaBlockChainImplTest.java @@ -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 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 =