99: Add read-only Phoenix Perps positions to EvelynPerpsService
This commit is contained in:
+52
-18
@@ -10,6 +10,8 @@ import com.r35157.evelyn.service.perps.PerpsSnapshot.Direction;
|
||||
import com.r35157.libs.codec.impl.ref.Base58CodecImpl;
|
||||
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
|
||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
||||
import com.r35157.libs.phoenix.perps.PhoenixPerpsPosition;
|
||||
import com.r35157.libs.phoenix.perps.PhoenixPerpsService;
|
||||
import com.r35157.nenjim.process.NenjimProcess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
@@ -26,12 +28,14 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Reads Jupiter positions in the background; consumers only read the published snapshot. */
|
||||
/** Reads Jupiter and Phoenix positions in the background; consumers only read the published snapshot. */
|
||||
public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimProcess, AutoCloseable {
|
||||
public EvelynPerpsServiceImpl(@NotNull List<PerpsWalletConfiguration> wallets,
|
||||
@NotNull JupiterPerpsService jupiter) {
|
||||
@NotNull JupiterPerpsService jupiter,
|
||||
@NotNull PhoenixPerpsService phoenix) {
|
||||
this.wallets = List.copyOf(wallets);
|
||||
this.jupiter = Objects.requireNonNull(jupiter, "jupiter");
|
||||
this.phoenix = Objects.requireNonNull(phoenix, "phoenix");
|
||||
var addresses = new HashSet<String>();
|
||||
var base58 = new Base58CodecImpl();
|
||||
for (PerpsWalletConfiguration wallet : this.wallets) {
|
||||
@@ -60,32 +64,60 @@ public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimP
|
||||
|
||||
private void refresh() {
|
||||
for (PerpsWalletConfiguration wallet : wallets) {
|
||||
if (closed || Thread.currentThread().isInterrupted()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
List<Position> positions = jupiter.getOpenPositions(wallet.address()).stream()
|
||||
refreshVenue(wallet, "jupiter", "Jupiter", () -> jupiter.getOpenPositions(wallet.address()).stream()
|
||||
.sorted(Comparator.comparing(JupiterPerpsPosition::positionAccount))
|
||||
.map(EvelynPerpsServiceImpl::position)
|
||||
.toList();
|
||||
if (closed || Thread.currentThread().isInterrupted()) {
|
||||
return;
|
||||
}
|
||||
lastSuccessful.put(wallet.address(), new Wallet(wallet.address(), wallet.address(),
|
||||
List.of(new Venue("jupiter", "Jupiter", positions))));
|
||||
snapshot = new PerpsSnapshot(wallets.stream()
|
||||
.map(configured -> lastSuccessful.get(configured.address()))
|
||||
.filter(Objects::nonNull)
|
||||
.toList());
|
||||
refreshVenue(wallet, "phoenix", "Phoenix", () -> phoenix.getOpenPositions(wallet.address()).stream()
|
||||
.sorted(Comparator.comparingInt(PhoenixPerpsPosition::traderPdaIndex)
|
||||
.thenComparingInt(PhoenixPerpsPosition::subaccountIndex)
|
||||
.thenComparing(PhoenixPerpsPosition::symbol))
|
||||
.map(EvelynPerpsServiceImpl::position)
|
||||
.toList());
|
||||
} catch (InterruptedException exception) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (IOException | RuntimeException exception) {
|
||||
log.warn("Unable to refresh Jupiter Perps positions for wallet {}", wallet.address(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshVenue(PerpsWalletConfiguration wallet, String venueId, String venueName,
|
||||
PositionReader reader) throws InterruptedException {
|
||||
if (closed || Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException("Perps refresh stopped");
|
||||
}
|
||||
try {
|
||||
List<Position> positions = reader.read();
|
||||
if (closed || Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException("Perps refresh stopped");
|
||||
}
|
||||
lastSuccessful.put(new WalletVenue(wallet.address(), venueId), new Venue(venueId, venueName, positions));
|
||||
snapshot = new PerpsSnapshot(wallets.stream().map(configured -> {
|
||||
List<Venue> venues = VENUE_ORDER.stream()
|
||||
.map(id -> lastSuccessful.get(new WalletVenue(configured.address(), id)))
|
||||
.filter(Objects::nonNull).toList();
|
||||
return new Wallet(configured.address(), configured.address(), venues);
|
||||
}).filter(current -> !current.venues().isEmpty()).toList());
|
||||
} catch (IOException | RuntimeException exception) {
|
||||
log.warn("Unable to refresh {} Perps positions for wallet {}", venueName, wallet.address(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static Position position(PhoenixPerpsPosition position) {
|
||||
String id = position.traderPdaIndex() + ":" + position.subaccountIndex() + ":" + position.symbol();
|
||||
String instrument = position.symbol().endsWith("-PERP") ? position.symbol() : position.symbol() + "-PERP";
|
||||
Direction direction = position.quantity().signum() > 0 ? Direction.LONG : Direction.SHORT;
|
||||
return new Position(id, instrument, direction, position.sizeUsd());
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface PositionReader {
|
||||
List<Position> read() throws IOException, InterruptedException;
|
||||
}
|
||||
|
||||
private record WalletVenue(String wallet, String venue) { }
|
||||
|
||||
private static Position position(JupiterPerpsPosition position) {
|
||||
Direction direction = switch (position.direction()) {
|
||||
case LONG -> Direction.LONG;
|
||||
@@ -126,8 +158,10 @@ public final class EvelynPerpsServiceImpl implements EvelynPerpsService, NenjimP
|
||||
private static final long REFRESH_SECONDS = 60;
|
||||
private final List<PerpsWalletConfiguration> wallets;
|
||||
private final JupiterPerpsService jupiter;
|
||||
private final PhoenixPerpsService phoenix;
|
||||
private static final List<String> VENUE_ORDER = List.of("jupiter", "phoenix");
|
||||
// Accessed only by the single refresh worker; consumers see immutable snapshots.
|
||||
private final Map<String, Wallet> lastSuccessful = new HashMap<>();
|
||||
private final Map<WalletVenue, Venue> lastSuccessful = new HashMap<>();
|
||||
private ScheduledExecutorService worker;
|
||||
private volatile boolean closed;
|
||||
private volatile PerpsSnapshot snapshot;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.r35157.libs.phoenix.perps;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A nonzero Phoenix position. Quantity is signed base units, not raw lots; sizeUsd is
|
||||
* absolute current notional (abs(quantity) * mark price), not collateral or entry value.
|
||||
* Identity within a wallet is traderPdaIndex + subaccountIndex + symbol, independent of
|
||||
* changing sequence numbers. Prices and trader state are separate REST snapshots.
|
||||
*/
|
||||
public record PhoenixPerpsPosition(int traderPdaIndex, int subaccountIndex, String symbol,
|
||||
ΩAmountΩ quantity, ΩAmountΩ sizeUsd) {
|
||||
public PhoenixPerpsPosition {
|
||||
Objects.requireNonNull(symbol, "symbol");
|
||||
Objects.requireNonNull(quantity, "quantity");
|
||||
Objects.requireNonNull(sizeUsd, "sizeUsd");
|
||||
if (traderPdaIndex < 0 || subaccountIndex < 0 || symbol.isBlank()
|
||||
|| !symbol.equals(symbol.strip()) || quantity.signum() == 0 || sizeUsd.signum() <= 0) {
|
||||
throw new IllegalArgumentException("Invalid Phoenix position");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.r35157.libs.phoenix.perps;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/** Read-only Phoenix positions; no signing, activation or trading operations. */
|
||||
public interface PhoenixPerpsService extends NenjimComponent {
|
||||
/**
|
||||
* Reads all nonzero cross-margin and isolated positions for authority's trader PDA index 0.
|
||||
* A successful empty list means no open positions. Failed reads/conversions throw rather
|
||||
* than returning an empty or partial portfolio. The returned list is immutable and ordered
|
||||
* by trader PDA index, subaccount index and market symbol.
|
||||
*/
|
||||
@NotNull List<PhoenixPerpsPosition> getOpenPositions(@NotNull ΩSolanaWalletIdΩ authority)
|
||||
throws IOException, InterruptedException;
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package com.r35157.libs.phoenix.perps.impl.ref;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.r35157.libs.codec.impl.ref.Base58CodecImpl;
|
||||
import com.r35157.libs.phoenix.perps.PhoenixPerpsPosition;
|
||||
import com.r35157.libs.phoenix.perps.PhoenixPerpsService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.DateTimeException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Public REST reader with shared request pacing and no portfolio cache. See docs/PhoenixPerps.md for API contracts and units. */
|
||||
public final class PhoenixPerpsServiceImpl implements PhoenixPerpsService {
|
||||
public PhoenixPerpsServiceImpl(@NotNull HttpClient httpClient, @NotNull ObjectMapper objectMapper) {
|
||||
this(httpClient, objectMapper, URI.create("https://perp-api.phoenix.trade/"), Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
public PhoenixPerpsServiceImpl(@NotNull HttpClient httpClient, @NotNull ObjectMapper objectMapper,
|
||||
@NotNull ΩRestEndpointBaseΩ endpoint, @NotNull Duration timeout) {
|
||||
this(httpClient, objectMapper, endpoint, timeout, Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
public PhoenixPerpsServiceImpl(@NotNull HttpClient httpClient, @NotNull ObjectMapper objectMapper,
|
||||
@NotNull ΩRestEndpointBaseΩ endpoint, @NotNull Duration timeout,
|
||||
@NotNull Duration minimumRequestInterval) {
|
||||
this.httpClient = Objects.requireNonNull(httpClient, "httpClient");
|
||||
this.objectMapper = Objects.requireNonNull(objectMapper, "objectMapper").copy()
|
||||
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS,
|
||||
DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY,
|
||||
DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
|
||||
this.endpoint = Objects.requireNonNull(endpoint, "endpoint");
|
||||
this.timeout = Objects.requireNonNull(timeout, "timeout");
|
||||
this.minimumRequestNanos = Objects.requireNonNull(minimumRequestInterval, "minimumRequestInterval").toNanos();
|
||||
if (minimumRequestNanos < 0) {
|
||||
throw new IllegalArgumentException("Phoenix request interval must not be negative");
|
||||
}
|
||||
if (timeout.isNegative() || timeout.isZero()) {
|
||||
throw new IllegalArgumentException("Phoenix request timeout must be positive");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<PhoenixPerpsPosition> getOpenPositions(@NotNull ΩSolanaWalletIdΩ authority)
|
||||
throws IOException, InterruptedException {
|
||||
if (new Base58CodecImpl().decode(authority).length != 32) {
|
||||
throw new IllegalArgumentException("Invalid Solana authority: " + authority);
|
||||
}
|
||||
JsonNode state = get("v1/trader/state/" + authority + "?traderPdaIndex=0");
|
||||
if (!authority.equals(text(state, "authority")) || integer(state, "traderPdaIndex") != 0) {
|
||||
throw invalid("Trader state does not match requested authority/PDA index");
|
||||
}
|
||||
JsonNode snapshot = required(state, "snapshot");
|
||||
if (integer(snapshot, "version") != 1) {
|
||||
throw invalid("Unsupported trader snapshot version");
|
||||
}
|
||||
var rawPositions = new ArrayList<RawPosition>();
|
||||
var subaccounts = new HashSet<Integer>();
|
||||
for (JsonNode subaccount : array(required(snapshot, "subaccounts"), "subaccounts")) {
|
||||
int index = integer(subaccount, "subaccountIndex");
|
||||
if (index < 0 || !subaccounts.add(index)) {
|
||||
throw invalid("Invalid or duplicate subaccount index: " + index);
|
||||
}
|
||||
// The API explicitly permits omitted positions for an empty subaccount.
|
||||
if (!subaccount.has("positions")) {
|
||||
continue;
|
||||
}
|
||||
var symbols = new HashSet<String>();
|
||||
for (JsonNode position : array(required(subaccount, "positions"), "positions")) {
|
||||
String symbol = text(position, "symbol");
|
||||
if (!symbols.add(symbol)) {
|
||||
throw invalid("Duplicate position in subaccount " + index + ": " + symbol);
|
||||
}
|
||||
BigInteger lots = lots(position);
|
||||
if (lots.signum() != 0) {
|
||||
rawPositions.add(new RawPosition(index, symbol, lots));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rawPositions.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
rawPositions.sort(Comparator.comparingInt(RawPosition::subaccountIndex)
|
||||
.thenComparing(RawPosition::symbol));
|
||||
Map<String, Integer> decimals = marketDecimals();
|
||||
var prices = new HashMap<String, BigDecimal>();
|
||||
var positions = new ArrayList<PhoenixPerpsPosition>();
|
||||
for (RawPosition position : rawPositions) {
|
||||
Integer baseLotsDecimals = decimals.get(position.symbol());
|
||||
if (baseLotsDecimals == null) {
|
||||
throw invalid("Missing market metadata for " + position.symbol());
|
||||
}
|
||||
BigDecimal price = prices.get(position.symbol());
|
||||
if (price == null) {
|
||||
price = markPrice(position.symbol());
|
||||
prices.put(position.symbol(), price);
|
||||
}
|
||||
try {
|
||||
// Official SDK: baseLotsToBaseUnits(lots, d) = lots / 10**d (d can be negative).
|
||||
BigDecimal quantity = new BigDecimal(position.lots())
|
||||
.scaleByPowerOfTen(Math.negateExact(baseLotsDecimals));
|
||||
positions.add(new PhoenixPerpsPosition(0, position.subaccountIndex(), position.symbol(),
|
||||
quantity, quantity.abs().multiply(price)));
|
||||
} catch (ArithmeticException | IllegalArgumentException exception) {
|
||||
throw new IOException("Cannot convert Phoenix position " + position.symbol(), exception);
|
||||
}
|
||||
}
|
||||
return List.copyOf(positions);
|
||||
}
|
||||
|
||||
private Map<String, Integer> marketDecimals() throws IOException, InterruptedException {
|
||||
var decimals = new HashMap<String, Integer>();
|
||||
for (JsonNode market : array(get("v1/view/exchange/markets"), "markets")) {
|
||||
String symbol = text(market, "symbol");
|
||||
if (decimals.putIfAbsent(symbol, integer(market, "baseLotsDecimals")) != null) {
|
||||
throw invalid("Duplicate market metadata for " + symbol);
|
||||
}
|
||||
}
|
||||
return decimals;
|
||||
}
|
||||
|
||||
private ΩPriceΩ markPrice(String symbol) throws IOException, InterruptedException {
|
||||
String encodedSymbol = URLEncoder.encode(symbol, StandardCharsets.UTF_8).replace("+", "%20");
|
||||
JsonNode response = get("v1/market/" + encodedSymbol + "/mark-price");
|
||||
if (!symbol.equals(text(response, "symbol"))) {
|
||||
throw invalid("Mark price symbol does not match " + symbol);
|
||||
}
|
||||
JsonNode price = required(required(response, "markPrice"), "price");
|
||||
// Jackson parses JSON decimals directly as BigDecimal; never go through double.
|
||||
if (!price.isNumber() || price.decimalValue().signum() <= 0) {
|
||||
throw invalid("Missing or invalid mark price for " + symbol);
|
||||
}
|
||||
return price.decimalValue();
|
||||
}
|
||||
|
||||
private JsonNode get(String path) throws IOException, InterruptedException {
|
||||
awaitRequestSlot();
|
||||
HttpRequest request = HttpRequest.newBuilder(endpoint.resolve(path))
|
||||
.timeout(timeout).header("Accept", "application/json").GET().build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
if (response.statusCode() == 429) {
|
||||
deferRequests(response.headers().firstValue("Retry-After").orElse("60"));
|
||||
}
|
||||
if (response.statusCode() != 200) {
|
||||
throw new IOException("Phoenix GET " + path + " returned HTTP " + response.statusCode());
|
||||
}
|
||||
JsonNode result = objectMapper.readTree(response.body());
|
||||
if (result == null || result.isNull()) {
|
||||
throw invalid("Empty JSON response for " + path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Shared by PROD and TEST; wait interruptibly, without holding either service's snapshot lock.
|
||||
private synchronized void awaitRequestSlot() throws IOException, InterruptedException {
|
||||
checkCooldown();
|
||||
long remaining = nextRequestNanos - System.nanoTime();
|
||||
if (remaining > 0) {
|
||||
TimeUnit.NANOSECONDS.sleep(remaining);
|
||||
}
|
||||
checkCooldown();
|
||||
nextRequestNanos = System.nanoTime() + minimumRequestNanos;
|
||||
}
|
||||
|
||||
private void checkCooldown() throws IOException {
|
||||
if (Instant.now().isBefore(retryNotBefore)) {
|
||||
throw new IOException("Phoenix rate-limit cooldown until " + retryNotBefore);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void deferRequests(String retryAfter) {
|
||||
Instant now = Instant.now();
|
||||
Instant retryAt;
|
||||
try {
|
||||
retryAt = retryAfter.matches("[0-9]+")
|
||||
? now.plusSeconds(Long.parseLong(retryAfter))
|
||||
: ZonedDateTime.parse(retryAfter, DateTimeFormatter.RFC_1123_DATE_TIME).toInstant();
|
||||
if (!retryAt.isAfter(now)) {
|
||||
retryAt = now.plusSeconds(60);
|
||||
}
|
||||
} catch (DateTimeException | ArithmeticException | NumberFormatException exception) {
|
||||
retryAt = now.plusSeconds(60);
|
||||
}
|
||||
if (retryAt.isAfter(retryNotBefore)) {
|
||||
retryNotBefore = retryAt;
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonNode required(JsonNode object, String field) throws IOException {
|
||||
if (!object.isObject() || !object.hasNonNull(field)) {
|
||||
throw invalid("Missing or null field: " + field);
|
||||
}
|
||||
return object.get(field);
|
||||
}
|
||||
|
||||
private static JsonNode array(JsonNode value, String field) throws IOException {
|
||||
if (!value.isArray()) {
|
||||
throw invalid("Expected array: " + field);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String text(JsonNode object, String field) throws IOException {
|
||||
JsonNode value = required(object, field);
|
||||
if (!value.isTextual() || value.textValue().isBlank()
|
||||
|| !value.textValue().equals(value.textValue().strip())) {
|
||||
throw invalid("Expected nonblank text: " + field);
|
||||
}
|
||||
return value.textValue();
|
||||
}
|
||||
|
||||
private static int integer(JsonNode object, String field) throws IOException {
|
||||
JsonNode value = required(object, field);
|
||||
if (!value.isIntegralNumber() || !value.canConvertToInt()) {
|
||||
throw invalid("Expected integer: " + field);
|
||||
}
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
private static BigInteger lots(JsonNode position) throws IOException {
|
||||
String value = text(position, "basePositionLots");
|
||||
if (!value.matches("-?[0-9]+")) {
|
||||
throw invalid("Expected signed integer basePositionLots");
|
||||
}
|
||||
return new BigInteger(value);
|
||||
}
|
||||
|
||||
private static IOException invalid(String message) {
|
||||
return new IOException("Invalid Phoenix response: " + message);
|
||||
}
|
||||
|
||||
private record RawPosition(int subaccountIndex, String symbol, BigInteger lots) { }
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ΩRestEndpointBaseΩ endpoint;
|
||||
private final Duration timeout;
|
||||
private final long minimumRequestNanos;
|
||||
private long nextRequestNanos = System.nanoTime();
|
||||
private Instant retryNotBefore = Instant.MIN;
|
||||
}
|
||||
@@ -28,6 +28,8 @@ 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.phoenix.perps.PhoenixPerpsService;
|
||||
import com.r35157.libs.phoenix.perps.impl.ref.PhoenixPerpsServiceImpl;
|
||||
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
|
||||
import com.r35157.libs.jupiter.swap.JupiterSwapService;
|
||||
import com.r35157.libs.jupiter.swap.impl.ref.JupiterSwapServiceImpl;
|
||||
@@ -247,6 +249,9 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
JUPITER_PERPS_EVELYN_TEST_ID,
|
||||
jupiterServicePerpsTest);
|
||||
|
||||
phoenixPerpsService = new PhoenixPerpsServiceImpl(httpClient, objectMapper);
|
||||
registryServiceManager.registerComponent(PHOENIX_PERPS_ID, phoenixPerpsService);
|
||||
|
||||
jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
|
||||
registryServiceManager.registerComponent(
|
||||
JUPITER_SWAP_EVELYN_BURNER_PROD_ID,
|
||||
@@ -362,13 +367,13 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
}
|
||||
|
||||
private NenjimProcess constructEvelynPerpsProdProcess() {
|
||||
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl(EVELYN_PERPS_PROD_WALLETS, jupiterServicePerpsProd);
|
||||
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl(EVELYN_PERPS_PROD_WALLETS, jupiterServicePerpsProd, phoenixPerpsService);
|
||||
evelynPerpsServiceProd = process;
|
||||
return process;
|
||||
}
|
||||
|
||||
private NenjimProcess constructEvelynPerpsTestProcess() {
|
||||
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl(EVELYN_PERPS_TEST_WALLETS, jupiterServicePerpsTest);
|
||||
EvelynPerpsServiceImpl process = new EvelynPerpsServiceImpl(EVELYN_PERPS_TEST_WALLETS, jupiterServicePerpsTest, phoenixPerpsService);
|
||||
evelynPerpsServiceTest = process;
|
||||
return process;
|
||||
}
|
||||
@@ -547,6 +552,8 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
new NenjimComponentId("jupiter.perps.evelyn-prod");
|
||||
private static final NenjimComponentId JUPITER_PERPS_EVELYN_TEST_ID =
|
||||
new NenjimComponentId("jupiter.perps.evelyn-test");
|
||||
private static final NenjimComponentId PHOENIX_PERPS_ID =
|
||||
new NenjimComponentId("phoenix.perps.default");
|
||||
private static final NenjimComponentId JUPITER_PERPS_ALARM_ID =
|
||||
new NenjimComponentId("jupiter-perps-alarm.default");
|
||||
private static final NenjimComponentId EVELYN_STATUS_PROD_ID =
|
||||
@@ -604,6 +611,7 @@ public final class NenjimHubImpl implements NenjimHub {
|
||||
private SolanaWallet solanaWalletEvelynBurnerProd;
|
||||
private JupiterPerpsService jupiterServicePerpsProd;
|
||||
private JupiterPerpsService jupiterServicePerpsTest;
|
||||
private PhoenixPerpsService phoenixPerpsService;
|
||||
|
||||
// Price-source components
|
||||
private PriceSource hardcodedPriceSource;
|
||||
|
||||
Reference in New Issue
Block a user