Compare commits
5 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
| ba9a167a57 | |||
| 0c2f00f791 | |||
| 8f0f4061ee | |||
| 3f47f9a895 | |||
| a52dbec41a |
@@ -1,56 +1,8 @@
|
|||||||
# General configurations
|
# Id Asset Direction Target Trigger Severity Note
|
||||||
#####################################################
|
######################################################################################################################
|
||||||
{{JUPITER_PERPS_WALLET}} 8abcYourWalletAddressHere
|
1 SOL ABOVE 97.03-1.25% ONETIME CRITICAL "CRITICAL: Risiko for Perps Solana short LIKVIDERING!"
|
||||||
|
2 SOL BELOW 48.72+1.25% ONETIME CRITICAL "CRITICAL: Risiko for Perps Solana long LIKVIDERING!"
|
||||||
|
3 BTC ABOVE 85032.87-1% ONETIME CRITICAL "CRITICAL: Risiko for Perps Bitcoin short LIKVIDERING!"
|
||||||
# Constant Name Value
|
4 BTC BELOW 42779.40+1% ONETIME CRITICAL "CRITICAL: Risiko for Perps Bitcoin long LIKVIDERING!"
|
||||||
####################################
|
5 ETH ABOVE 2296.13-1% ONETIME CRITICAL "CRITICAL: Risiko for Perps Ethereum short LIKVIDERING!"
|
||||||
|
6 ETH BELOW 1155.19+1% ONETIME CRITICAL "CRITICAL: Risiko for Perps Ethereum long LIKVIDERING!"
|
||||||
{{SOL_LONG_ENTRY_PRICE}} 73.67
|
|
||||||
{{SOL_LONG_LIQ_PRICE}} 70.28
|
|
||||||
{{SOL_SHORT_ENTRY_PRICE}} 70.47
|
|
||||||
{{SOL_SHORT_LIQ_PRICE}} 75.01
|
|
||||||
|
|
||||||
{{ETH_LONG_ENTRY_PRICE}} 1589.63
|
|
||||||
{{ETH_LONG_LIQ_PRICE}} 1461.11
|
|
||||||
{{ETH_SHORT_ENTRY_PRICE}} 1545.29
|
|
||||||
{{ETH_SHORT_LIQ_PRICE}} 1698.42
|
|
||||||
|
|
||||||
{{BTC_LONG_ENTRY_PRICE}} 61236.03
|
|
||||||
{{BTC_LONG_LIQ_PRICE}} 40984.70
|
|
||||||
{{BTC_SHORT_ENTRY_PRICE}} 59451.94
|
|
||||||
{{BTC_SHORT_LIQ_PRICE}} 65084.53
|
|
||||||
|
|
||||||
|
|
||||||
# Id Asset Direction Target Trigger Severity Note
|
|
||||||
###############################################################################################################################
|
|
||||||
|
|
||||||
# SOL SHORT
|
|
||||||
1 SOL ABOVE {{SOL_SHORT_LIQ_PRICE}}-1.25% ONETIME CRITICAL "🚨 LIKVIDERINGS-risiko SOL Short!"
|
|
||||||
2 SOL ABOVE {{SOL_SHORT_ENTRY_PRICE}}+3% PERSISTENT:900 INFO "🌱 Short SOL now"
|
|
||||||
3 SOL BELOW {{SOL_SHORT_ENTRY_PRICE}}-3% PERSISTENT:900 INFO "💵 Close SOL Short"
|
|
||||||
|
|
||||||
# SOL LONG
|
|
||||||
4 SOL BELOW {{SOL_LONG_LIQ_PRICE}}+1.25% ONETIME CRITICAL "🚨 LIKVIDERINGS-risiko SOL Long!"
|
|
||||||
5 SOL BELOW {{SOL_LONG_ENTRY_PRICE}}-3% PERSISTENT:900 INFO "🌱 Long SOL now"
|
|
||||||
6 SOL ABOVE {{SOL_LONG_ENTRY_PRICE}}+3% PERSISTENT:900 INFO "💵 Close SOL Long"
|
|
||||||
|
|
||||||
# ETH SHORT
|
|
||||||
7 ETH ABOVE {{ETH_SHORT_LIQ_PRICE}}-1% ONETIME CRITICAL "🚨 LIKVIDERINGS-risiko ETH Short!"
|
|
||||||
8 ETH ABOVE {{ETH_SHORT_ENTRY_PRICE}}+3% PERSISTENT:900 INFO "🌱 Short ETH now"
|
|
||||||
9 ETH BELOW {{ETH_SHORT_ENTRY_PRICE}}-3% PERSISTENT:900 INFO "💵 Close ETH Short"
|
|
||||||
|
|
||||||
# ETH LONG
|
|
||||||
10 ETH BELOW {{ETH_LONG_LIQ_PRICE}}+1% ONETIME CRITICAL "🚨 LIKVIDERINGS-risiko ETH Long!"
|
|
||||||
11 ETH BELOW {{ETH_LONG_ENTRY_PRICE}}-3% PERSISTENT:900 INFO "🌱 Long ETH now"
|
|
||||||
12 ETH ABOVE {{ETH_LONG_ENTRY_PRICE}}+3% PERSISTENT:900 INFO "💵 Close ETH Long"
|
|
||||||
|
|
||||||
# BTC SHORT
|
|
||||||
13 BTC ABOVE {{BTC_SHORT_LIQ_PRICE}}-1% ONETIME CRITICAL "🚨 LIKVIDERINGS-risiko BTC Short!"
|
|
||||||
14 BTC ABOVE {{BTC_SHORT_ENTRY_PRICE}}+3% PERSISTENT:900 INFO "🌱 Short BTC now"
|
|
||||||
15 BTC BELOW {{BTC_SHORT_ENTRY_PRICE}}-3% PERSISTENT:900 INFO "💵 Close BTC Short"
|
|
||||||
|
|
||||||
# BTC LONG
|
|
||||||
16 BTC BELOW {{BTC_LONG_LIQ_PRICE}}+1% ONETIME CRITICAL "🚨 LIKVIDERINGS-risiko BTC Long!"
|
|
||||||
17 BTC BELOW {{BTC_LONG_ENTRY_PRICE}}-3% PERSISTENT:900 INFO "🌱 Long BTC now"
|
|
||||||
18 BTC ABOVE {{BTC_LONG_ENTRY_PRICE}}+3% PERSISTENT:900 INFO "💵 Close BTC Long"
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ "$#" -ne 1 ]; then
|
|
||||||
echo "Usage: $0 <github-snapshot-branch>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BRANCH="$1"
|
|
||||||
|
|
||||||
SOURCE="$HOME/projects/com_r35157_nenjim-hubd-impl_ref"
|
|
||||||
TARGET="$HOME/projects/com_r35157_nenjim-hubd-impl_ref_github_snapshot"
|
|
||||||
|
|
||||||
cd "$TARGET"
|
|
||||||
|
|
||||||
git fetch origin
|
|
||||||
|
|
||||||
if git show-ref --verify --quiet "refs/heads/$BRANCH"; then
|
|
||||||
git switch "$BRANCH"
|
|
||||||
elif git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
|
|
||||||
git switch --track "origin/$BRANCH"
|
|
||||||
else
|
|
||||||
git switch --create "$BRANCH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rsync -a --delete \
|
|
||||||
--exclude '.git' \
|
|
||||||
--exclude 'conf/*.conf' \
|
|
||||||
--exclude 'conf/*.xml' \
|
|
||||||
"$SOURCE/" \
|
|
||||||
"$TARGET/"
|
|
||||||
|
|
||||||
git add -A
|
|
||||||
|
|
||||||
if git diff --cached --quiet; then
|
|
||||||
echo "No snapshot changes to publish on branch '$BRANCH'."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
git commit -m "Mirror snapshot"
|
|
||||||
git push -u origin "$BRANCH"
|
|
||||||
@@ -2,5 +2,5 @@ package com.r35157.jupiterperpsalarm.impl.ref;
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface AlarmAction {
|
public interface AlarmAction {
|
||||||
void trigger(OraclePrice price, ResolvedPriceAlarm alarm);
|
void trigger(OraclePrice price, PriceAlarmDefinition alarm);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.r35157.jupiterperpsalarm.impl.ref;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public record AlarmConfiguration(
|
|
||||||
List<PriceAlarmDefinition> definitions,
|
|
||||||
Map<String, String> variables
|
|
||||||
) {
|
|
||||||
public AlarmConfiguration {
|
|
||||||
Objects.requireNonNull(definitions, "definitions");
|
|
||||||
Objects.requireNonNull(variables, "variables");
|
|
||||||
|
|
||||||
definitions = List.copyOf(definitions);
|
|
||||||
variables = new ConcurrentHashMap<>(variables);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+60
-19
@@ -3,13 +3,14 @@ package com.r35157.jupiterperpsalarm.impl.ref;
|
|||||||
import com.r35157.jupiterperpsalarm.AlarmSeverity;
|
import com.r35157.jupiterperpsalarm.AlarmSeverity;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public final class AlarmConfigurationParser {
|
public final class AlarmConfigurationParser {
|
||||||
|
|
||||||
public static AlarmConfiguration parse(Path path) throws IOException {
|
public static List<PriceAlarmDefinition> parse(Path path) throws IOException {
|
||||||
List<String> lines = Files.readAllLines(path);
|
List<String> lines = Files.readAllLines(path);
|
||||||
List<PriceAlarmDefinition> alarms = new ArrayList<>();
|
List<PriceAlarmDefinition> alarms = new ArrayList<>();
|
||||||
Map<String, String> constants = new LinkedHashMap<>();
|
Map<String, String> constants = new LinkedHashMap<>();
|
||||||
@@ -26,7 +27,8 @@ public final class AlarmConfigurationParser {
|
|||||||
if(isConstantDefinition(trimmed)) {
|
if(isConstantDefinition(trimmed)) {
|
||||||
parseConstantDefinition(constants, line);
|
parseConstantDefinition(constants, line);
|
||||||
} else {
|
} else {
|
||||||
alarms.add(parseLine(line));
|
String resolvedLine = replaceConstants(line, constants);
|
||||||
|
alarms.add(parseLine(resolvedLine));
|
||||||
}
|
}
|
||||||
} catch (RuntimeException exception) {
|
} catch (RuntimeException exception) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
@@ -40,7 +42,7 @@ public final class AlarmConfigurationParser {
|
|||||||
throw new IllegalArgumentException("No alarms found in " + path);
|
throw new IllegalArgumentException("No alarms found in " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AlarmConfiguration(alarms, constants);
|
return List.copyOf(alarms);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PriceAlarmDefinition parseLine(String line) {
|
static PriceAlarmDefinition parseLine(String line) {
|
||||||
@@ -56,7 +58,7 @@ public final class AlarmConfigurationParser {
|
|||||||
cursor.nextToken("direction").toUpperCase(Locale.ROOT)
|
cursor.nextToken("direction").toUpperCase(Locale.ROOT)
|
||||||
);
|
);
|
||||||
|
|
||||||
String targetExpression = cursor.nextToken("target");
|
BigDecimal target = parseTarget(cursor.nextToken("target"));
|
||||||
|
|
||||||
TriggerConfiguration triggerConfiguration = parseTrigger(
|
TriggerConfiguration triggerConfiguration = parseTrigger(
|
||||||
cursor.nextToken("trigger")
|
cursor.nextToken("trigger")
|
||||||
@@ -79,7 +81,7 @@ public final class AlarmConfigurationParser {
|
|||||||
id,
|
id,
|
||||||
asset,
|
asset,
|
||||||
direction,
|
direction,
|
||||||
targetExpression,
|
target,
|
||||||
triggerConfiguration.trigger(),
|
triggerConfiguration.trigger(),
|
||||||
triggerConfiguration.gracePeriod(),
|
triggerConfiguration.gracePeriod(),
|
||||||
severity,
|
severity,
|
||||||
@@ -87,6 +89,28 @@ public final class AlarmConfigurationParser {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BigDecimal parseTargetPercentage(
|
||||||
|
String targetStr,
|
||||||
|
int operatorIndex,
|
||||||
|
boolean add
|
||||||
|
) {
|
||||||
|
BigDecimal base = new BigDecimal(targetStr.substring(0, operatorIndex));
|
||||||
|
String percentStr = targetStr.substring(operatorIndex + 1, targetStr.length() - 1);
|
||||||
|
BigDecimal percent = new BigDecimal(percentStr);
|
||||||
|
|
||||||
|
BigDecimal delta = base
|
||||||
|
.multiply(percent)
|
||||||
|
.divide(BigDecimal.valueOf(100));
|
||||||
|
|
||||||
|
BigDecimal target = add ? base.add(delta) : base.subtract(delta);
|
||||||
|
|
||||||
|
validateTarget(base, targetStr);
|
||||||
|
validateTarget(percent, targetStr);
|
||||||
|
validateTarget(target, targetStr);
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
private static TriggerConfiguration parseTrigger(String triggerText) {
|
private static TriggerConfiguration parseTrigger(String triggerText) {
|
||||||
String normalized = triggerText.toUpperCase(Locale.ROOT);
|
String normalized = triggerText.toUpperCase(Locale.ROOT);
|
||||||
|
|
||||||
@@ -131,6 +155,35 @@ public final class AlarmConfigurationParser {
|
|||||||
throw new IllegalArgumentException("Unknown trigger: " + triggerText);
|
throw new IllegalArgumentException("Unknown trigger: " + triggerText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void validateTarget(BigDecimal target, String originalTargetStr) {
|
||||||
|
if (target.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Target must be zero or positive: " + originalTargetStr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal parseTarget(String targetStr) {
|
||||||
|
String trimmedTargetStr = targetStr.trim();
|
||||||
|
|
||||||
|
if (trimmedTargetStr.endsWith("%")) {
|
||||||
|
int plusIndex = trimmedTargetStr.indexOf('+');
|
||||||
|
int minusIndex = trimmedTargetStr.indexOf('-', 1);
|
||||||
|
|
||||||
|
if (plusIndex >= 0) {
|
||||||
|
return parseTargetPercentage(trimmedTargetStr, plusIndex, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minusIndex >= 0) {
|
||||||
|
return parseTargetPercentage(trimmedTargetStr, minusIndex, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal target = new BigDecimal(trimmedTargetStr);
|
||||||
|
validateTarget(target, targetStr);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
private static final class Cursor {
|
private static final class Cursor {
|
||||||
private Cursor(String line) {
|
private Cursor(String line) {
|
||||||
this.line = line;
|
this.line = line;
|
||||||
@@ -237,21 +290,9 @@ public final class AlarmConfigurationParser {
|
|||||||
|
|
||||||
validateConstantName(name);
|
validateConstantName(name);
|
||||||
|
|
||||||
constants.put(parseVariableName(name), value);
|
if (constants.putIfAbsent(name, value) != null) {
|
||||||
}
|
throw new IllegalArgumentException("Duplicate constant: " + name);
|
||||||
|
|
||||||
private static String parseVariableName(String variableName) {
|
|
||||||
if (!variableName.startsWith("{{") || !variableName.endsWith("}}")) {
|
|
||||||
throw new IllegalArgumentException("Invalid variable name: " + variableName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String parsedVariableName = variableName.substring(2, variableName.length() - 2);
|
|
||||||
|
|
||||||
if (parsedVariableName.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("Variable name cannot be blank: " + variableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedVariableName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String replaceConstants(
|
private static String replaceConstants(
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
package com.r35157.jupiterperpsalarm.impl.ref;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
public final class AlarmTargetParser {
|
|
||||||
|
|
||||||
private AlarmTargetParser() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BigDecimal parse(String targetStr) {
|
|
||||||
String trimmedTargetStr = targetStr.trim();
|
|
||||||
|
|
||||||
if (trimmedTargetStr.endsWith("%")) {
|
|
||||||
int plusIndex = trimmedTargetStr.indexOf('+');
|
|
||||||
int minusIndex = trimmedTargetStr.indexOf('-', 1);
|
|
||||||
|
|
||||||
if (plusIndex >= 0) {
|
|
||||||
return parseTargetPercentage(trimmedTargetStr, plusIndex, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minusIndex >= 0) {
|
|
||||||
return parseTargetPercentage(trimmedTargetStr, minusIndex, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal target = new BigDecimal(trimmedTargetStr);
|
|
||||||
validateTarget(target, targetStr);
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigDecimal parseTargetPercentage(
|
|
||||||
String targetStr,
|
|
||||||
int operatorIndex,
|
|
||||||
boolean add
|
|
||||||
) {
|
|
||||||
BigDecimal base = new BigDecimal(targetStr.substring(0, operatorIndex));
|
|
||||||
String percentStr = targetStr.substring(operatorIndex + 1, targetStr.length() - 1);
|
|
||||||
BigDecimal percent = new BigDecimal(percentStr);
|
|
||||||
|
|
||||||
BigDecimal delta = base
|
|
||||||
.multiply(percent)
|
|
||||||
.divide(BigDecimal.valueOf(100));
|
|
||||||
|
|
||||||
BigDecimal target = add ? base.add(delta) : base.subtract(delta);
|
|
||||||
|
|
||||||
validateTarget(base, targetStr);
|
|
||||||
validateTarget(percent, targetStr);
|
|
||||||
validateTarget(target, targetStr);
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void validateTarget(BigDecimal target, String originalTargetStr) {
|
|
||||||
if (target.compareTo(BigDecimal.ZERO) < 0) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Target must be zero or positive: " + originalTargetStr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.r35157.jupiterperpsalarm.impl.ref;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class AlarmVariableResolver {
|
|
||||||
|
|
||||||
public AlarmVariableResolver(Map<String, String> variables) {
|
|
||||||
this.variables = Objects.requireNonNull(variables, "variables");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String resolve(String text) {
|
|
||||||
Objects.requireNonNull(text, "text");
|
|
||||||
|
|
||||||
String resolvedText = text;
|
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : variables.entrySet()) {
|
|
||||||
String placeholder = "{{" + entry.getKey() + "}}";
|
|
||||||
resolvedText = resolvedText.replace(placeholder, entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolvedText.contains("{{") || resolvedText.contains("}}")) {
|
|
||||||
throw new IllegalArgumentException("Unresolved variable in text: " + text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolvedText;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, String> variables;
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ public final class AssetPriceAlarmMonitor {
|
|||||||
public AssetPriceAlarmMonitor(
|
public AssetPriceAlarmMonitor(
|
||||||
JupiterPerpsAsset asset,
|
JupiterPerpsAsset asset,
|
||||||
List<PriceAlarmDefinition> definitions,
|
List<PriceAlarmDefinition> definitions,
|
||||||
AlarmVariableResolver variableResolver,
|
|
||||||
AlarmAction action
|
AlarmAction action
|
||||||
) {
|
) {
|
||||||
this.asset = asset;
|
this.asset = asset;
|
||||||
@@ -23,7 +22,7 @@ public final class AssetPriceAlarmMonitor {
|
|||||||
"Alarm asset " + definition.asset() + " does not match monitor " + asset
|
"Alarm asset " + definition.asset() + " does not match monitor " + asset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new PriceAlarm(definition, variableResolver, action);
|
return new PriceAlarm(definition, action);
|
||||||
})
|
})
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public final class CompositeAlarmAction implements AlarmAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void trigger(OraclePrice price, ResolvedPriceAlarm alarm) {
|
public void trigger(OraclePrice price, PriceAlarmDefinition alarm) {
|
||||||
for (AlarmAction action : actions) {
|
for (AlarmAction action : actions) {
|
||||||
try {
|
try {
|
||||||
action.trigger(price, alarm);
|
action.trigger(price, alarm);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.r35157.jupiterperpsalarm.impl.ref;
|
|||||||
|
|
||||||
public final class ConsoleAlarmAction implements AlarmAction {
|
public final class ConsoleAlarmAction implements AlarmAction {
|
||||||
@Override
|
@Override
|
||||||
public void trigger(OraclePrice price, ResolvedPriceAlarm alarm) {
|
public void trigger(OraclePrice price, PriceAlarmDefinition alarm) {
|
||||||
System.err.println();
|
System.err.println();
|
||||||
System.err.println("============================================================");
|
System.err.println("============================================================");
|
||||||
System.err.printf(
|
System.err.printf(
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
package com.r35157.jupiterperpsalarm.impl.ref;
|
package com.r35157.jupiterperpsalarm.impl.ref;
|
||||||
|
|
||||||
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 java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -18,7 +13,6 @@ public final class JupiterPerpsAlarmImpl {
|
|||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
config = Config.parse(args, System.getenv());
|
config = Config.parse(args, System.getenv());
|
||||||
} catch (IllegalArgumentException exception) {
|
} catch (IllegalArgumentException exception) {
|
||||||
@@ -28,34 +22,13 @@ public final class JupiterPerpsAlarmImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<PriceAlarmDefinition> definitions;
|
List<PriceAlarmDefinition> definitions;
|
||||||
AlarmConfiguration alarmConfiguration;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
alarmConfiguration = AlarmConfigurationParser.parse(config.alarmConfiguration());
|
definitions = AlarmConfigurationParser.parse(config.alarmConfiguration());
|
||||||
definitions = alarmConfiguration.definitions();
|
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
String errMsg = "Could not load alarm configuration: " + exception.getMessage() + "!";
|
String errMsg = "Could not load alarm configuration: " + exception.getMessage() + "!";
|
||||||
throw new IllegalStateException(errMsg, exception);
|
throw new IllegalStateException(errMsg, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
AlarmVariableResolver variableResolver = new AlarmVariableResolver(
|
|
||||||
alarmConfiguration.variables()
|
|
||||||
);
|
|
||||||
|
|
||||||
SolanaBlockChain solanaBlockChain = new SolanaBlockChainImpl();
|
|
||||||
JupiterPerpsService jupiterPerpsService = new AnchorIdlJupiterPerpsServiceImpl(solanaBlockChain);
|
|
||||||
JupiterPerpsEntryPriceVariableRefresher entryPriceVariableRefresher =
|
|
||||||
new JupiterPerpsEntryPriceVariableRefresher(alarmConfiguration.variables(), jupiterPerpsService);
|
|
||||||
entryPriceVariableRefresher.refresh();
|
|
||||||
|
|
||||||
JupiterPerpsEntryPriceVariableRefreshWatcher entryPriceVariableRefreshWatcher =
|
|
||||||
new JupiterPerpsEntryPriceVariableRefreshWatcher(
|
|
||||||
config.alarmConfiguration().getParent(),
|
|
||||||
entryPriceVariableRefresher
|
|
||||||
);
|
|
||||||
|
|
||||||
entryPriceVariableRefreshWatcher.start();
|
|
||||||
|
|
||||||
List<AlarmAction> actions = new ArrayList<>();
|
List<AlarmAction> actions = new ArrayList<>();
|
||||||
actions.add(new ConsoleAlarmAction());
|
actions.add(new ConsoleAlarmAction());
|
||||||
|
|
||||||
@@ -79,10 +52,9 @@ public final class JupiterPerpsAlarmImpl {
|
|||||||
Map<JupiterPerpsAsset, AssetPriceAlarmMonitor> monitors = new EnumMap<>(
|
Map<JupiterPerpsAsset, AssetPriceAlarmMonitor> monitors = new EnumMap<>(
|
||||||
JupiterPerpsAsset.class
|
JupiterPerpsAsset.class
|
||||||
);
|
);
|
||||||
|
|
||||||
definitionsByAsset.forEach((asset, assetDefinitions) -> monitors.put(
|
definitionsByAsset.forEach((asset, assetDefinitions) -> monitors.put(
|
||||||
asset,
|
asset,
|
||||||
new AssetPriceAlarmMonitor(asset, assetDefinitions, variableResolver, action)
|
new AssetPriceAlarmMonitor(asset, assetDefinitions, action)
|
||||||
));
|
));
|
||||||
|
|
||||||
List<OracleWebSocketClient> clients = new ArrayList<>();
|
List<OracleWebSocketClient> clients = new ArrayList<>();
|
||||||
@@ -113,7 +85,7 @@ public final class JupiterPerpsAlarmImpl {
|
|||||||
assetDefinitions.forEach(definition -> System.out.printf(
|
assetDefinitions.forEach(definition -> System.out.printf(
|
||||||
" %s %s USD, %s, severity=%s%n",
|
" %s %s USD, %s, severity=%s%n",
|
||||||
definition.direction(),
|
definition.direction(),
|
||||||
definition.targetExpression(),
|
definition.target().toPlainString(),
|
||||||
definition.trigger(),
|
definition.trigger(),
|
||||||
definition.severity()
|
definition.severity()
|
||||||
));
|
));
|
||||||
|
|||||||
-92
@@ -1,92 +0,0 @@
|
|||||||
package com.r35157.jupiterperpsalarm.impl.ref;
|
|
||||||
|
|
||||||
import java.nio.file.*;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.StandardWatchEventKinds;
|
|
||||||
import java.nio.file.WatchEvent;
|
|
||||||
import java.nio.file.WatchKey;
|
|
||||||
import java.nio.file.WatchService;
|
|
||||||
|
|
||||||
public final class JupiterPerpsEntryPriceVariableRefreshWatcher {
|
|
||||||
|
|
||||||
public JupiterPerpsEntryPriceVariableRefreshWatcher(
|
|
||||||
Path confDirectory,
|
|
||||||
JupiterPerpsEntryPriceVariableRefresher refresher
|
|
||||||
) {
|
|
||||||
this.confDirectory = Objects.requireNonNull(confDirectory, "confDirectory");
|
|
||||||
this.refresher = Objects.requireNonNull(refresher, "refresher");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
Thread thread = new Thread(
|
|
||||||
this::run,
|
|
||||||
"jupiter-perps-entry-price-variable-refresh-watcher"
|
|
||||||
);
|
|
||||||
|
|
||||||
thread.setDaemon(true);
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void run() {
|
|
||||||
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
|
|
||||||
|
|
||||||
confDirectory.register(
|
|
||||||
watchService,
|
|
||||||
StandardWatchEventKinds.ENTRY_CREATE
|
|
||||||
);
|
|
||||||
|
|
||||||
System.out.println(
|
|
||||||
"Jupiter Perps entry price variable refresh watcher started for directory: "
|
|
||||||
+ confDirectory
|
|
||||||
);
|
|
||||||
while (true) {
|
|
||||||
WatchKey key = watchService.take();
|
|
||||||
|
|
||||||
for (WatchEvent<?> event : key.pollEvents()) {
|
|
||||||
Path path = (Path) event.context();
|
|
||||||
|
|
||||||
if (!REFRESH_TRIGGER_FILE_NAME.equals(path.getFileName().toString())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Path triggerFile = confDirectory.resolve(path);
|
|
||||||
|
|
||||||
System.out.println("Refresh trigger file detected: " + triggerFile);
|
|
||||||
|
|
||||||
try {
|
|
||||||
refresher.refresh();
|
|
||||||
} finally {
|
|
||||||
Files.deleteIfExists(triggerFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!key.reset()) {
|
|
||||||
System.err.println(
|
|
||||||
"Jupiter Perps entry price variable refresh watcher stopped: watch key is no longer valid"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException exception) {
|
|
||||||
System.err.println(
|
|
||||||
"Could not start Jupiter Perps entry price variable refresh watcher: "
|
|
||||||
+ exception.getMessage()
|
|
||||||
);
|
|
||||||
} catch (InterruptedException exception) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
|
|
||||||
System.err.println(
|
|
||||||
"Jupiter Perps entry price variable refresh watcher interrupted"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String REFRESH_TRIGGER_FILE_NAME =
|
|
||||||
"jupiter-perps-alarm-var.refresh";
|
|
||||||
|
|
||||||
private final Path confDirectory;
|
|
||||||
private final JupiterPerpsEntryPriceVariableRefresher refresher;
|
|
||||||
}
|
|
||||||
-86
@@ -1,86 +0,0 @@
|
|||||||
package com.r35157.jupiterperpsalarm.impl.ref;
|
|
||||||
|
|
||||||
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
|
|
||||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public final class JupiterPerpsEntryPriceVariableRefresher {
|
|
||||||
|
|
||||||
public JupiterPerpsEntryPriceVariableRefresher(
|
|
||||||
Map<String, String> variables,
|
|
||||||
JupiterPerpsService jupiterPerpsService
|
|
||||||
) {
|
|
||||||
this.variables = Objects.requireNonNull(variables, "variables");
|
|
||||||
this.jupiterPerpsService = Objects.requireNonNull(jupiterPerpsService, "jupiterPerpsService");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refresh() {
|
|
||||||
ΩSolanaWalletIdΩ wallet = variables.get("JUPITER_PERPS_WALLET");
|
|
||||||
|
|
||||||
if (wallet == null || wallet.isBlank()) {
|
|
||||||
System.err.println("Cannot refresh Jupiter Perps entry price variables: JUPITER_PERPS_WALLET is not configured");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Set<JupiterPerpsPosition> positions = jupiterPerpsService.getOpenPositions(wallet);
|
|
||||||
|
|
||||||
System.out.println(
|
|
||||||
"Fetched " + positions.size()
|
|
||||||
+ " open Jupiter Perps positions for wallet: " + wallet
|
|
||||||
);
|
|
||||||
|
|
||||||
removeEntryPriceVariables();
|
|
||||||
|
|
||||||
for (JupiterPerpsPosition position : positions) {
|
|
||||||
String variableName = createEntryPriceVariableName(position);
|
|
||||||
|
|
||||||
System.out.println(
|
|
||||||
"Jupiter Perps position maps to variable "
|
|
||||||
+ variableName
|
|
||||||
+ " = "
|
|
||||||
+ position.entryPrice()
|
|
||||||
);
|
|
||||||
variables.put(variableName, position.entryPrice().toPlainString());
|
|
||||||
}
|
|
||||||
} catch (IOException | InterruptedException exception) {
|
|
||||||
if (exception instanceof InterruptedException) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println(
|
|
||||||
"Could not refresh Jupiter Perps entry price variables: "
|
|
||||||
+ exception.getMessage()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeEntryPriceVariables() {
|
|
||||||
variables.remove("SOL_LONG_ENTRY_PRICE");
|
|
||||||
variables.remove("SOL_SHORT_ENTRY_PRICE");
|
|
||||||
variables.remove("BTC_LONG_ENTRY_PRICE");
|
|
||||||
variables.remove("BTC_SHORT_ENTRY_PRICE");
|
|
||||||
variables.remove("ETH_LONG_ENTRY_PRICE");
|
|
||||||
variables.remove("ETH_SHORT_ENTRY_PRICE");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String createEntryPriceVariableName(JupiterPerpsPosition position) {
|
|
||||||
String asset = switch (position.tradedTokenMint()) {
|
|
||||||
case "So11111111111111111111111111111111111111112" -> "SOL";
|
|
||||||
case "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh" -> "BTC";
|
|
||||||
case "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" -> "ETH";
|
|
||||||
default -> throw new IllegalArgumentException(
|
|
||||||
"Unsupported Jupiter Perps traded token mint: " + position.tradedTokenMint()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return asset + "_" + position.direction() + "_ENTRY_PRICE";
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, String> variables;
|
|
||||||
private final JupiterPerpsService jupiterPerpsService;
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,11 @@
|
|||||||
package com.r35157.jupiterperpsalarm.impl.ref;
|
package com.r35157.jupiterperpsalarm.impl.ref;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public final class PriceAlarm {
|
public final class PriceAlarm {
|
||||||
|
|
||||||
public PriceAlarm(
|
public PriceAlarm(PriceAlarmDefinition definition, AlarmAction action) {
|
||||||
PriceAlarmDefinition definition,
|
|
||||||
AlarmVariableResolver variableResolver,
|
|
||||||
AlarmAction action
|
|
||||||
) {
|
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
this.variableResolver = variableResolver;
|
|
||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,23 +16,9 @@ public final class PriceAlarm {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal target;
|
|
||||||
try {
|
|
||||||
target = AlarmTargetParser.parse(
|
|
||||||
variableResolver.resolve(definition.targetExpression())
|
|
||||||
);
|
|
||||||
} catch (RuntimeException exception) {
|
|
||||||
System.err.printf(
|
|
||||||
"Could not resolve target for alarm %d: %s%n",
|
|
||||||
definition.id(),
|
|
||||||
exception.getMessage()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean reached = definition.direction().reached(
|
boolean reached = definition.direction().reached(
|
||||||
price.priceUsd(),
|
price.priceUsd(),
|
||||||
target
|
definition.target()
|
||||||
);
|
);
|
||||||
|
|
||||||
boolean enteredTriggeredSide = previousReached == null
|
boolean enteredTriggeredSide = previousReached == null
|
||||||
@@ -57,13 +37,13 @@ public final class PriceAlarm {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger(price, target);
|
trigger(price);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.trigger() == AlarmTrigger.PERSISTENT) {
|
if (definition.trigger() == AlarmTrigger.PERSISTENT) {
|
||||||
if (lastTriggeredAt == null || persistentGracePeriodHasPassed()) {
|
if (lastTriggeredAt == null || persistentGracePeriodHasPassed()) {
|
||||||
trigger(price, target);
|
trigger(price);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -95,39 +75,13 @@ public final class PriceAlarm {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trigger(OraclePrice price, BigDecimal target) {
|
private void trigger(OraclePrice price) {
|
||||||
triggerCount++;
|
triggerCount++;
|
||||||
lastTriggeredAt = Instant.now();
|
lastTriggeredAt = Instant.now();
|
||||||
|
action.trigger(price, definition);
|
||||||
String note;
|
|
||||||
|
|
||||||
try {
|
|
||||||
note = variableResolver.resolve(definition.note());
|
|
||||||
} catch (RuntimeException exception) {
|
|
||||||
System.err.printf(
|
|
||||||
"Could not resolve note for alarm %d: %s%n",
|
|
||||||
definition.id(),
|
|
||||||
exception.getMessage()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResolvedPriceAlarm resolvedAlarm = new ResolvedPriceAlarm(
|
|
||||||
definition.id(),
|
|
||||||
definition.asset(),
|
|
||||||
definition.direction(),
|
|
||||||
target,
|
|
||||||
definition.trigger(),
|
|
||||||
definition.triggerGracePeriod(),
|
|
||||||
definition.severity(),
|
|
||||||
note
|
|
||||||
);
|
|
||||||
|
|
||||||
action.trigger(price, resolvedAlarm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final PriceAlarmDefinition definition;
|
private final PriceAlarmDefinition definition;
|
||||||
private final AlarmVariableResolver variableResolver;
|
|
||||||
private final AlarmAction action;
|
private final AlarmAction action;
|
||||||
|
|
||||||
private Instant lastTriggeredAt;
|
private Instant lastTriggeredAt;
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package com.r35157.jupiterperpsalarm.impl.ref;
|
|||||||
|
|
||||||
import com.r35157.jupiterperpsalarm.AlarmSeverity;
|
import com.r35157.jupiterperpsalarm.AlarmSeverity;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public record PriceAlarmDefinition(
|
public record PriceAlarmDefinition(
|
||||||
int id,
|
int id,
|
||||||
JupiterPerpsAsset asset,
|
JupiterPerpsAsset asset,
|
||||||
PriceDirection direction,
|
PriceDirection direction,
|
||||||
String targetExpression,
|
BigDecimal target,
|
||||||
AlarmTrigger trigger,
|
AlarmTrigger trigger,
|
||||||
ΩsecondsΩ triggerGracePeriod,
|
ΩsecondsΩ triggerGracePeriod,
|
||||||
AlarmSeverity severity,
|
AlarmSeverity severity,
|
||||||
@@ -17,13 +18,12 @@ public record PriceAlarmDefinition(
|
|||||||
public PriceAlarmDefinition {
|
public PriceAlarmDefinition {
|
||||||
Objects.requireNonNull(asset, "asset");
|
Objects.requireNonNull(asset, "asset");
|
||||||
Objects.requireNonNull(direction, "direction");
|
Objects.requireNonNull(direction, "direction");
|
||||||
Objects.requireNonNull(targetExpression, "targetExpression");
|
Objects.requireNonNull(target, "target");
|
||||||
Objects.requireNonNull(trigger, "trigger");
|
Objects.requireNonNull(trigger, "trigger");
|
||||||
Objects.requireNonNull(severity, "severity");
|
|
||||||
Objects.requireNonNull(note, "note");
|
Objects.requireNonNull(note, "note");
|
||||||
|
|
||||||
if (targetExpression.isBlank()) {
|
if (target.signum() < 0) {
|
||||||
throw new IllegalArgumentException("Target expression cannot be blank");
|
throw new IllegalArgumentException("Target price cannot be negative (was: " + target.signum() + ")!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triggerGracePeriod < 0) {
|
if (triggerGracePeriod < 0) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public final class PushoverAlarmAction implements AlarmAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void trigger(OraclePrice price, ResolvedPriceAlarm alarm) {
|
public void trigger(OraclePrice price, PriceAlarmDefinition alarm) {
|
||||||
String title = "Jupiter Perps " + price.asset() + " alarm";
|
String title = "Jupiter Perps " + price.asset() + " alarm";
|
||||||
String message = createMessage(price, alarm);
|
String message = createMessage(price, alarm);
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ public final class PushoverAlarmAction implements AlarmAction {
|
|||||||
form("user", userKey) + "&" +
|
form("user", userKey) + "&" +
|
||||||
form("title", title) + "&" +
|
form("title", title) + "&" +
|
||||||
form("message", message) + "&" +
|
form("message", message) + "&" +
|
||||||
createPushoverSeverityParameters(alarm.severity());
|
createPushoverSeverityParameters(alarm.severity());;
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder(PUSHOVER_URI)
|
HttpRequest request = HttpRequest.newBuilder(PUSHOVER_URI)
|
||||||
.timeout(Duration.ofSeconds(15))
|
.timeout(Duration.ofSeconds(15))
|
||||||
@@ -69,7 +69,7 @@ public final class PushoverAlarmAction implements AlarmAction {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String createMessage(OraclePrice price, ResolvedPriceAlarm alarm) {
|
private static String createMessage(OraclePrice price, PriceAlarmDefinition alarm) {
|
||||||
return String.format(
|
return String.format(
|
||||||
"%d - %s: %s%n%n%s is %s USD.%nTarget: %s %s USD.%nOracle time: %s.%nSlot: %d.",
|
"%d - %s: %s%n%n%s is %s USD.%nTarget: %s %s USD.%nOracle time: %s.%nSlot: %d.",
|
||||||
alarm.id(),
|
alarm.id(),
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.r35157.jupiterperpsalarm.impl.ref;
|
|
||||||
|
|
||||||
import com.r35157.jupiterperpsalarm.AlarmSeverity;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
public record ResolvedPriceAlarm(
|
|
||||||
int id,
|
|
||||||
JupiterPerpsAsset asset,
|
|
||||||
PriceDirection direction,
|
|
||||||
BigDecimal target,
|
|
||||||
AlarmTrigger trigger,
|
|
||||||
ΩsecondsΩ triggerGracePeriod,
|
|
||||||
AlarmSeverity severity,
|
|
||||||
String note
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.r35157.libs.codec;
|
|
||||||
|
|
||||||
public interface Base58Codec {
|
|
||||||
String encode(byte[] input);
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package com.r35157.libs.codec.impl.ref;
|
|
||||||
|
|
||||||
import com.r35157.libs.codec.Base58Codec;
|
|
||||||
|
|
||||||
public class Base58CodecImpl implements Base58Codec {
|
|
||||||
|
|
||||||
public String encode(byte[] input) {
|
|
||||||
if (input.length == 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] copy = input.clone();
|
|
||||||
|
|
||||||
int zeros = 0;
|
|
||||||
while (zeros < copy.length && copy[zeros] == 0) {
|
|
||||||
zeros++;
|
|
||||||
}
|
|
||||||
|
|
||||||
char[] encoded = new char[copy.length * 2];
|
|
||||||
int outputStart = encoded.length;
|
|
||||||
|
|
||||||
int inputStart = zeros;
|
|
||||||
while (inputStart < copy.length) {
|
|
||||||
int remainder = divmod58(
|
|
||||||
copy,
|
|
||||||
inputStart
|
|
||||||
);
|
|
||||||
|
|
||||||
if (copy[inputStart] == 0) {
|
|
||||||
inputStart++;
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded[--outputStart] = ALPHABET[remainder];
|
|
||||||
}
|
|
||||||
|
|
||||||
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
|
|
||||||
outputStart++;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (zeros-- > 0) {
|
|
||||||
encoded[--outputStart] = ENCODED_ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String(
|
|
||||||
encoded,
|
|
||||||
outputStart,
|
|
||||||
encoded.length - outputStart
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int divmod58(byte[] number, int startAt) {
|
|
||||||
int remainder = 0;
|
|
||||||
|
|
||||||
for (int i = startAt; i < number.length; i++) {
|
|
||||||
int digit = number[i] & 0xff;
|
|
||||||
int temp = remainder * 256 + digit;
|
|
||||||
|
|
||||||
number[i] = (byte) (temp / 58);
|
|
||||||
remainder = temp % 58;
|
|
||||||
}
|
|
||||||
|
|
||||||
return remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final char[] ALPHABET =
|
|
||||||
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
|
||||||
|
|
||||||
private static final char ENCODED_ZERO =
|
|
||||||
ALPHABET[0];
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.r35157.libs.jupiter.perps;
|
package com.r35157.libs.jupiter.perps;
|
||||||
|
|
||||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,37 +9,14 @@ import java.math.BigDecimal;
|
|||||||
* the Jupiter Perps program. This record contains the public API view of such a
|
* the Jupiter Perps program. This record contains the public API view of such a
|
||||||
* position.</p>
|
* position.</p>
|
||||||
*
|
*
|
||||||
|
* <p>The initial version of this API only exposes the position account and the entry
|
||||||
|
* price. More position fields may be added later as the Jupiter Perps API matures.</p>
|
||||||
|
*
|
||||||
* @param positionAccount the Solana account address of the Jupiter Perps position
|
* @param positionAccount the Solana account address of the Jupiter Perps position
|
||||||
* @param tradedTokenMint the mint address of the token being traded
|
|
||||||
* @param direction whether the position is long or short
|
|
||||||
* @param value the amount the position is worth if closed now
|
|
||||||
* @param size the leveraged amount used to open the contracts
|
|
||||||
* @param pnl the amount in usd in profit or loss on this position
|
|
||||||
* @param pnlPercent the profit and loss represented as a percentage
|
|
||||||
* @param leverage TODO
|
|
||||||
* @param entryPrice the entry price of the position, denominated in USDC
|
* @param entryPrice the entry price of the position, denominated in USDC
|
||||||
* @param marketPrice the current spot price of the token
|
|
||||||
* @param collateral the amount of USD representing the collateral for this position
|
|
||||||
* @param totalFees the total amount of fees (TODO: is that including pending/due fees)
|
|
||||||
* @param borrowFeesDue the amount of USD that is currently outstanding
|
|
||||||
* @param closeFeePending the fee in USD for closing the account (TODO: multiple accounts - when adding collateral?)
|
|
||||||
* @param accountRent refundable amount locked for Solana account renting
|
|
||||||
*/
|
*/
|
||||||
public record JupiterPerpsPosition(
|
public record JupiterPerpsPosition(
|
||||||
ΩJupiterPerpsPositionAccountΩ positionAccount,
|
ΩJupiterPerpsPositionAccountΩ positionAccount,
|
||||||
ΩSPLMintAddressΩ tradedTokenMint,
|
ΩUSDCPriceΩ entryPrice
|
||||||
JupiterPerpsPositionDirection direction,
|
|
||||||
ΩUSDCAmountΩ value,
|
|
||||||
ΩUSDCAmountΩ size,
|
|
||||||
ΩUSDCAmountΩ pnl,
|
|
||||||
BigDecimal pnlPercent,
|
|
||||||
BigDecimal leverage,
|
|
||||||
ΩUSDCPriceΩ entryPrice,
|
|
||||||
ΩUSDCPriceΩ marketPrice,
|
|
||||||
ΩUSDCAmountΩ collateral,
|
|
||||||
ΩUSDCAmountΩ totalFees,
|
|
||||||
ΩUSDCAmountΩ borrowFeesDue,
|
|
||||||
ΩUSDCAmountΩ closeFeePending,
|
|
||||||
ΩSolanaAmountΩ accountRent
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.r35157.libs.jupiter.perps;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direction of a Jupiter Perps position.
|
|
||||||
*/
|
|
||||||
public enum JupiterPerpsPositionDirection {
|
|
||||||
LONG,
|
|
||||||
SHORT
|
|
||||||
}
|
|
||||||
+4
-4
@@ -4,15 +4,15 @@ import java.io.IOException;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for reading Jupiter Perps data.
|
* Service for reading Jupiter Perps positions.
|
||||||
*
|
*
|
||||||
* <p>This service is read-only. It does not open, close, modify, or sign transactions
|
* <p>This service is read-only. It does not open, close, modify, or sign transactions
|
||||||
* for Jupiter Perpetual Contracts.</p>
|
* for Jupiter Perps positions.</p>
|
||||||
*
|
*
|
||||||
* <p>NOTICE: The first supported operation is reading a known Jupiter Perps position account
|
* <p>The first supported operation is reading a known Jupiter Perps position account
|
||||||
* and returning its decoded position data.</p>
|
* and returning its decoded position data.</p>
|
||||||
*/
|
*/
|
||||||
public interface JupiterPerpsService {
|
public interface JupiterPerpsPositionService {
|
||||||
/**
|
/**
|
||||||
* Reads a Jupiter Perps position from a known position account.
|
* Reads a Jupiter Perps position from a known position account.
|
||||||
*
|
*
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
package com.r35157.libs.jupiter.perps.impl.anchoridl;
|
|
||||||
|
|
||||||
import com.r35157.libs.codec.Base58Codec;
|
|
||||||
import com.r35157.libs.codec.impl.ref.Base58CodecImpl;
|
|
||||||
import com.r35157.libs.solana.SolanaAccountInfo;
|
|
||||||
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
class AnchorIdlJupiterPerpsCustodyDecoder {
|
|
||||||
|
|
||||||
ΩSPLMintAddressΩ decodeMint(
|
|
||||||
SolanaAccountInfo custodyAccountInfo
|
|
||||||
) {
|
|
||||||
byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64());
|
|
||||||
|
|
||||||
if (data.length < MINT_OFFSET + PUBLIC_KEY_LENGTH) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Jupiter Perps custody account data is too short: " + data.length
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return readPublicKey(data, MINT_OFFSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ΩSPLMintAddressΩ readPublicKey(
|
|
||||||
byte[] data,
|
|
||||||
int offset
|
|
||||||
) {
|
|
||||||
byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH];
|
|
||||||
|
|
||||||
System.arraycopy(
|
|
||||||
data,
|
|
||||||
offset,
|
|
||||||
publicKeyBytes,
|
|
||||||
0,
|
|
||||||
PUBLIC_KEY_LENGTH
|
|
||||||
);
|
|
||||||
|
|
||||||
return base58.encode(publicKeyBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
|
|
||||||
private static final int PUBLIC_KEY_LENGTH = 32;
|
|
||||||
|
|
||||||
private static final int MINT_OFFSET =
|
|
||||||
ANCHOR_DISCRIMINATOR_LENGTH
|
|
||||||
+ PUBLIC_KEY_LENGTH; // pool
|
|
||||||
|
|
||||||
private static final Base58Codec base58 = new Base58CodecImpl();
|
|
||||||
}
|
|
||||||
+12
-114
@@ -1,26 +1,18 @@
|
|||||||
package com.r35157.libs.jupiter.perps.impl.anchoridl;
|
package com.r35157.libs.jupiter.perps.impl.anchoridl;
|
||||||
|
|
||||||
import com.r35157.libs.codec.Base58Codec;
|
|
||||||
import com.r35157.libs.codec.impl.ref.Base58CodecImpl;
|
|
||||||
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
|
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
|
||||||
import com.r35157.libs.jupiter.perps.JupiterPerpsPositionDirection;
|
|
||||||
import com.r35157.libs.solana.SolanaAccountInfo;
|
import com.r35157.libs.solana.SolanaAccountInfo;
|
||||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
|
||||||
import com.r35157.libs.valuetypes.basic.WellKnownCurrencyTypes;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
import static java.math.BigDecimal.ZERO;
|
|
||||||
|
|
||||||
class AnchorIdlJupiterPerpsPositionDecoder {
|
class AnchorIdlJupiterPerpsPositionDecoder {
|
||||||
|
|
||||||
JupiterPerpsPosition decode(
|
JupiterPerpsPosition decode(
|
||||||
ΩJupiterPerpsPositionAccountΩ positionAccount,
|
ΩJupiterPerpsPositionAccountΩ positionAccount,
|
||||||
SolanaAccountInfo accountInfo,
|
SolanaAccountInfo accountInfo
|
||||||
ΩSPLMintAddressΩ tradedTokenMint
|
|
||||||
) {
|
) {
|
||||||
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
|
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
|
||||||
|
|
||||||
@@ -30,9 +22,6 @@ class AnchorIdlJupiterPerpsPositionDecoder {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
JupiterPerpsPositionDirection direction =
|
|
||||||
decodeDirection(data[SIDE_OFFSET]);
|
|
||||||
|
|
||||||
long rawEntryPrice = ByteBuffer
|
long rawEntryPrice = ByteBuffer
|
||||||
.wrap(data, PRICE_OFFSET, U64_LENGTH)
|
.wrap(data, PRICE_OFFSET, U64_LENGTH)
|
||||||
.order(ByteOrder.LITTLE_ENDIAN)
|
.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
@@ -42,118 +31,27 @@ class AnchorIdlJupiterPerpsPositionDecoder {
|
|||||||
.valueOf(rawEntryPrice)
|
.valueOf(rawEntryPrice)
|
||||||
.movePointLeft(6);
|
.movePointLeft(6);
|
||||||
|
|
||||||
long rawCollateralUsd = ByteBuffer
|
|
||||||
.wrap(data, COLLATERAL_USD_OFFSET, U64_LENGTH)
|
|
||||||
.order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
.getLong();
|
|
||||||
|
|
||||||
ΩUSDCAmountΩ collateralUsd = BigDecimal
|
|
||||||
.valueOf(rawCollateralUsd)
|
|
||||||
.movePointLeft(6);
|
|
||||||
|
|
||||||
long rawSizeUsd = ByteBuffer
|
|
||||||
.wrap(data, SIZE_USD_OFFSET, U64_LENGTH)
|
|
||||||
.order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
.getLong();
|
|
||||||
|
|
||||||
ΩUSDCAmountΩ sizeUsd = BigDecimal
|
|
||||||
.valueOf(rawSizeUsd)
|
|
||||||
.movePointLeft(6);
|
|
||||||
|
|
||||||
ΩUSDCAmountΩ value = ZERO; // TODO - Dummy
|
|
||||||
ΩUSDCAmountΩ pnl = ZERO; // TODO - Dummy
|
|
||||||
BigDecimal pnlPercent = ZERO; // TODO - Dummy
|
|
||||||
BigDecimal leverage = ZERO; // TODO - Dummy
|
|
||||||
ΩUSDCPriceΩ marketPrice = ZERO; // TODO - Dummy
|
|
||||||
ΩUSDCAmountΩ totalFees = ZERO; // TODO - Dummy
|
|
||||||
ΩUSDCAmountΩ borrowFeesDue = ZERO; // TODO - Dummy
|
|
||||||
ΩUSDCAmountΩ closeFeePending = ZERO; // TODO - Dummy
|
|
||||||
ΩSolanaAmountΩ accountRent = new MoneyAmount(ZERO, WellKnownCurrencyTypes.SOLANA.getCurrencyType()); // TODO - Dummy
|
|
||||||
|
|
||||||
JupiterPerpsPosition pos = new JupiterPerpsPosition(
|
JupiterPerpsPosition pos = new JupiterPerpsPosition(
|
||||||
positionAccount,
|
positionAccount,
|
||||||
tradedTokenMint,
|
entryPrice
|
||||||
direction,
|
|
||||||
value,
|
|
||||||
sizeUsd,
|
|
||||||
pnl,
|
|
||||||
pnlPercent,
|
|
||||||
leverage,
|
|
||||||
entryPrice,
|
|
||||||
marketPrice,
|
|
||||||
collateralUsd,
|
|
||||||
totalFees,
|
|
||||||
borrowFeesDue,
|
|
||||||
closeFeePending,
|
|
||||||
accountRent
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
ΩSolanaAddressΩ decodeCustodyAccount(SolanaAccountInfo accountInfo) {
|
|
||||||
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
|
|
||||||
|
|
||||||
if (data.length < CUSTODY_OFFSET + PUBLIC_KEY_LENGTH) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Jupiter Perps position account data is too short: " + data.length
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return readPublicKey(
|
|
||||||
data,
|
|
||||||
CUSTODY_OFFSET
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private JupiterPerpsPositionDirection decodeDirection(
|
|
||||||
byte rawSide
|
|
||||||
) {
|
|
||||||
// Jupiter Perps position side values are encoded as 1 = LONG, 2 = SHORT.
|
|
||||||
JupiterPerpsPositionDirection direction = switch (rawSide) {
|
|
||||||
case 1 -> JupiterPerpsPositionDirection.LONG;
|
|
||||||
case 2 -> JupiterPerpsPositionDirection.SHORT;
|
|
||||||
default -> throw new IllegalArgumentException(
|
|
||||||
"Unknown Jupiter Perps position side: " + rawSide
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ΩSolanaAddressΩ readPublicKey(
|
|
||||||
byte[] data,
|
|
||||||
int offset
|
|
||||||
) {
|
|
||||||
byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH];
|
|
||||||
|
|
||||||
System.arraycopy(
|
|
||||||
data,
|
|
||||||
offset,
|
|
||||||
publicKeyBytes,
|
|
||||||
0,
|
|
||||||
PUBLIC_KEY_LENGTH
|
|
||||||
);
|
|
||||||
|
|
||||||
return base58.encode(publicKeyBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
|
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
|
||||||
private static final int PUBLIC_KEY_LENGTH = 32;
|
private static final int PUBLIC_KEY_LENGTH = 32;
|
||||||
private static final int I64_LENGTH = 8;
|
private static final int I64_LENGTH = 8;
|
||||||
private static final int SIDE_ENUM_LENGTH = 1;
|
|
||||||
private static final int U64_LENGTH = 8;
|
private static final int U64_LENGTH = 8;
|
||||||
|
private static final int SIDE_ENUM_LENGTH = 1;
|
||||||
|
|
||||||
private static final int OWNER_OFFSET = ANCHOR_DISCRIMINATOR_LENGTH;
|
private static final int PRICE_OFFSET =
|
||||||
private static final int POOL_OFFSET = OWNER_OFFSET + PUBLIC_KEY_LENGTH;
|
ANCHOR_DISCRIMINATOR_LENGTH
|
||||||
private static final int CUSTODY_OFFSET = POOL_OFFSET + PUBLIC_KEY_LENGTH;
|
+ PUBLIC_KEY_LENGTH // owner
|
||||||
private static final int COLLATERAL_CUSTODY_OFFSET = CUSTODY_OFFSET + PUBLIC_KEY_LENGTH;
|
+ PUBLIC_KEY_LENGTH // pool
|
||||||
private static final int OPEN_TIME_OFFSET = COLLATERAL_CUSTODY_OFFSET + PUBLIC_KEY_LENGTH;
|
+ PUBLIC_KEY_LENGTH // custody
|
||||||
private static final int UPDATE_TIME_OFFSET = OPEN_TIME_OFFSET + I64_LENGTH;
|
+ PUBLIC_KEY_LENGTH // collateralCustody
|
||||||
private static final int SIDE_OFFSET = UPDATE_TIME_OFFSET + I64_LENGTH;
|
+ I64_LENGTH // openTime
|
||||||
private static final int PRICE_OFFSET = SIDE_OFFSET + SIDE_ENUM_LENGTH;
|
+ I64_LENGTH // updateTime
|
||||||
private static final int SIZE_USD_OFFSET = PRICE_OFFSET + U64_LENGTH;
|
+ SIDE_ENUM_LENGTH; // side
|
||||||
private static final int COLLATERAL_USD_OFFSET = SIZE_USD_OFFSET + U64_LENGTH;
|
|
||||||
|
|
||||||
private static final Base58Codec base58 = new Base58CodecImpl();
|
|
||||||
}
|
}
|
||||||
+8
-27
@@ -1,7 +1,7 @@
|
|||||||
package com.r35157.libs.jupiter.perps.impl.anchoridl;
|
package com.r35157.libs.jupiter.perps.impl.anchoridl;
|
||||||
|
|
||||||
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
|
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
|
||||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
import com.r35157.libs.jupiter.perps.JupiterPerpsPositionService;
|
||||||
import com.r35157.libs.solana.SolanaAccountInfo;
|
import com.r35157.libs.solana.SolanaAccountInfo;
|
||||||
import com.r35157.libs.solana.SolanaBlockChain;
|
import com.r35157.libs.solana.SolanaBlockChain;
|
||||||
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
|
import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter;
|
||||||
@@ -10,14 +10,13 @@ import java.io.IOException;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
|
public class AnchorIdlJupiterPerpsPositionServiceImpl implements JupiterPerpsPositionService {
|
||||||
|
|
||||||
public AnchorIdlJupiterPerpsServiceImpl(
|
public AnchorIdlJupiterPerpsPositionServiceImpl(
|
||||||
SolanaBlockChain solanaBlockChain
|
SolanaBlockChain solanaBlockChain
|
||||||
) {
|
) {
|
||||||
this.solanaBlockChain = solanaBlockChain;
|
this.solanaBlockChain = solanaBlockChain;
|
||||||
this.positionDecoder = new AnchorIdlJupiterPerpsPositionDecoder();
|
this.positionDecoder = new AnchorIdlJupiterPerpsPositionDecoder();
|
||||||
this.custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -35,9 +34,7 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo);
|
JupiterPerpsPosition pos = positionDecoder.decode(positionAccount, accountInfo);
|
||||||
JupiterPerpsPosition pos = positionDecoder.decode(positionAccount, accountInfo, tradedTokenMint);
|
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,36 +61,20 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService {
|
|||||||
throw new IllegalArgumentException(errorMsg);
|
throw new IllegalArgumentException(errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo);
|
JupiterPerpsPosition position = positionDecoder.decode(
|
||||||
|
address,
|
||||||
JupiterPerpsPosition position = positionDecoder.decode(address, accountInfo, tradedTokenMint);
|
accountInfo
|
||||||
|
);
|
||||||
positions.add(position);
|
positions.add(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Set.copyOf(positions);
|
return Set.copyOf(positions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ΩSPLMintAddressΩ getTradedTokenMint(SolanaAccountInfo positionAccountInfo)
|
|
||||||
throws IOException, InterruptedException
|
|
||||||
{
|
|
||||||
ΩSolanaAddressΩ custodyAccount = positionDecoder.decodeCustodyAccount(positionAccountInfo);
|
|
||||||
SolanaAccountInfo custodyAccountInfo = solanaBlockChain.getAccountInfo(custodyAccount);
|
|
||||||
|
|
||||||
if (custodyAccountInfo == null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Jupiter Perps custody account does not exist: " + custodyAccount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ΩSPLMintAddressΩ mintAddress = custodyDecoder.decodeMint(custodyAccountInfo);
|
|
||||||
return mintAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final ΩJupiterPerpsProgramIdΩ JUPITER_PERPS_PROGRAM_ID =
|
private static final ΩJupiterPerpsProgramIdΩ JUPITER_PERPS_PROGRAM_ID =
|
||||||
"PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu";
|
"PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu";
|
||||||
private static final int POSITION_OWNER_OFFSET = 8;
|
private static final int POSITION_OWNER_OFFSET = 8;
|
||||||
|
|
||||||
private final SolanaBlockChain solanaBlockChain;
|
private final SolanaBlockChain solanaBlockChain;
|
||||||
private final AnchorIdlJupiterPerpsPositionDecoder positionDecoder;
|
private final AnchorIdlJupiterPerpsPositionDecoder positionDecoder;
|
||||||
private final AnchorIdlJupiterPerpsCustodyDecoder custodyDecoder;
|
|
||||||
}
|
}
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
package 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 java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides read-oriented access to the Solana blockchain.
|
|
||||||
*
|
|
||||||
* <p>This interface exposes the Solana operations needed by higher-level
|
|
||||||
* integrations. It can fetch native SOL balances, SPL token holdings, NFT-like
|
|
||||||
* token holding candidates, account information and program derived addresses.</p>
|
|
||||||
*
|
|
||||||
* <p>The interface is intentionally generic and does not contain Raydium-specific
|
|
||||||
* logic. Higher-level integrations are expected to interpret Solana accounts,
|
|
||||||
* token holdings and derived addresses according to their own domain rules.</p>
|
|
||||||
*/
|
|
||||||
public interface SolanaBlockChain {
|
|
||||||
/**
|
|
||||||
* Fetches the native SOL balance for a Solana address.
|
|
||||||
*
|
|
||||||
* @param address the Solana address to inspect
|
|
||||||
* @return the native SOL balance for the address
|
|
||||||
* @throws IOException if the balance could not be fetched or parsed
|
|
||||||
* @throws InterruptedException if the calling thread is interrupted while fetching the balance
|
|
||||||
*/
|
|
||||||
ΩSolanaAmountΩ getBalanceInSolana(ΩSolanaAddressΩ address) throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the native SOL balance for a Solana address in lamports.
|
|
||||||
*
|
|
||||||
* <p>Lamports are the smallest unit of native SOL.</p>
|
|
||||||
*
|
|
||||||
* @param address the Solana address to inspect
|
|
||||||
* @return the native SOL balance for the address in lamports
|
|
||||||
* @throws IOException if the balance could not be fetched or parsed
|
|
||||||
* @throws InterruptedException if the calling thread is interrupted while fetching the balance
|
|
||||||
*/
|
|
||||||
ΩlamportsΩ getBalanceInLamport(ΩSolanaAddressΩ address) throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches SPL token holdings owned by a Solana address for a specific token program.
|
|
||||||
*
|
|
||||||
* <p>The supplied token program decides which token accounts are inspected. For example,
|
|
||||||
* callers may query the original SPL Token Program or the Token-2022 Program depending
|
|
||||||
* on which token accounts they need to discover.</p>
|
|
||||||
*
|
|
||||||
* @param ownerAddress the Solana owner address whose token holdings should be inspected
|
|
||||||
* @param splProgramId the SPL token program to query
|
|
||||||
* @return a map of SPL mint addresses to token holding information
|
|
||||||
* @throws IOException if the token holdings could not be fetched or parsed
|
|
||||||
* @throws InterruptedException if the calling thread is interrupted while fetching token holdings
|
|
||||||
*/
|
|
||||||
Map<ΩSPLMintAddressΩ, SPLTokenHolding> getSPLTokenHoldings(
|
|
||||||
ΩSolanaAddressΩ ownerAddress,
|
|
||||||
SolanaSPLTokenProgram splProgramId
|
|
||||||
) throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches NFT-like token mint address candidates owned by a Solana address for a specific token program.
|
|
||||||
*
|
|
||||||
* <p>This method identifies token holdings that look like NFTs within the supplied token
|
|
||||||
* program. A returned address is only a candidate. Higher-level integrations are responsible
|
|
||||||
* for deciding whether the returned address has domain-specific meaning.</p>
|
|
||||||
*
|
|
||||||
* // TODO This method currently identifies candidates from the owner's token holdings only.
|
|
||||||
* // A token with zero decimals and an owner balance of one is not guaranteed to be a real NFT,
|
|
||||||
* // because the mint's total supply may still be greater than one. A future implementation
|
|
||||||
* // should verify the mint supply, for example by using Solana getTokenSupply, before treating
|
|
||||||
* // the result as a confirmed NFT.
|
|
||||||
*
|
|
||||||
* @param ownerAddress the Solana owner address whose NFT-like holdings should be inspected
|
|
||||||
* @param splProgram the SPL token program to query
|
|
||||||
* @return the NFT-like Solana mint address candidates owned by the address
|
|
||||||
* @throws IOException if the NFT candidate addresses could not be fetched or parsed
|
|
||||||
* @throws InterruptedException if the calling thread is interrupted while fetching NFT candidate addresses
|
|
||||||
*/
|
|
||||||
Set<ΩSolanaNFTAddressΩ> getSolanaNFTCandidateAddresses(
|
|
||||||
ΩSolanaAddressΩ ownerAddress,
|
|
||||||
SolanaSPLTokenProgram splProgram
|
|
||||||
) throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a Solana program derived address for a program id and a set of seeds.
|
|
||||||
*
|
|
||||||
* <p>The seeds describe the logical inputs used to derive the address. The implementation
|
|
||||||
* is responsible for converting each seed into the byte representation required by Solana.</p>
|
|
||||||
*
|
|
||||||
* @param programId the Solana program id used to derive the address
|
|
||||||
* @param seeds the seeds used when deriving the program address
|
|
||||||
* @return the derived Solana address together with its bump value
|
|
||||||
*/
|
|
||||||
SolanaProgramDerivedAddress findProgramAddress(
|
|
||||||
ΩSolanaProgramIdΩ programId,
|
|
||||||
List<SolanaProgramAddressSeed> seeds
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches account information for a Solana account address.
|
|
||||||
*
|
|
||||||
* <p>If the account does not exist, this method returns {@code null}. If the account exists,
|
|
||||||
* the returned value contains the account address, the owning Solana program id and the
|
|
||||||
* account data encoded as Base64.</p>
|
|
||||||
*
|
|
||||||
* @param accountAddress the Solana account address to inspect
|
|
||||||
* @return account information, or {@code null} if the account does not exist
|
|
||||||
* @throws IOException if the account information could not be fetched or parsed
|
|
||||||
* @throws InterruptedException if the calling thread is interrupted while fetching account information
|
|
||||||
*/
|
|
||||||
SolanaAccountInfo getAccountInfo(ΩSolanaAddressΩ accountAddress)
|
|
||||||
throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a raw 32-byte Solana address into its textual Solana address representation.
|
|
||||||
*
|
|
||||||
* <p>This method is intended for callers that have obtained raw Solana address bytes from
|
|
||||||
* account data and need the normal string representation used elsewhere in the API.</p>
|
|
||||||
*
|
|
||||||
* @param addressBytes the raw 32-byte Solana address
|
|
||||||
* @return the encoded Solana address
|
|
||||||
* @throws IllegalArgumentException if the supplied byte array is not a valid Solana address length
|
|
||||||
*/
|
|
||||||
ΩSolanaAddressΩ encodeSolanaAddress(byte[] addressBytes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the total supply of an SPL token mint for a specific token program.
|
|
||||||
*
|
|
||||||
* <p>The supplied token program identifies which SPL token program owns the mint,
|
|
||||||
* for example the original SPL Token Program or the Token-2022 Program.</p>
|
|
||||||
*
|
|
||||||
* @param mintAddress the SPL mint address whose supply should be fetched
|
|
||||||
* @param splProgram the SPL token program to query
|
|
||||||
* @return the SPL token supply for the mint
|
|
||||||
* @throws IOException if the token supply could not be fetched or parsed
|
|
||||||
* @throws InterruptedException if the calling thread is interrupted while fetching token supply
|
|
||||||
*/
|
|
||||||
SPLTokenSupply getSPLTokenSupply(
|
|
||||||
ΩSPLMintAddressΩ mintAddress,
|
|
||||||
SolanaSPLTokenProgram splProgram
|
|
||||||
) throws IOException, InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches accounts owned by a Solana program using server-side account data filters.
|
|
||||||
*
|
|
||||||
* <p>This method uses Solana's {@code getProgramAccounts} RPC call. The supplied filters
|
|
||||||
* are sent to the RPC node, so matching is performed server-side instead of fetching all
|
|
||||||
* accounts owned by the program and filtering them locally.</p>
|
|
||||||
*
|
|
||||||
* <p>The initial supported filter type is {@link SolanaProgramAccountMemcmpFilter}, which
|
|
||||||
* matches bytes at a specific offset in the account data.</p>
|
|
||||||
*
|
|
||||||
* @param programId the Solana program id that owns the accounts to search
|
|
||||||
* @param filters the memcmp filters to apply when searching program accounts
|
|
||||||
* @return the matching program accounts
|
|
||||||
* @throws IOException if the program accounts could not be fetched or parsed
|
|
||||||
* @throws InterruptedException if the calling thread is interrupted while fetching program accounts
|
|
||||||
*/
|
|
||||||
Set<SolanaAccountInfo> getProgramAccounts(
|
|
||||||
ΩSolanaProgramIdΩ programId,
|
|
||||||
Set<SolanaProgramAccountMemcmpFilter> filters
|
|
||||||
) throws IOException, InterruptedException;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.r35157.libs.solana;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter used when fetching accounts owned by a Solana program.
|
|
||||||
*
|
|
||||||
* <p>The initial supported filter type is {@code memcmp}, which asks the
|
|
||||||
* Solana RPC node to only return accounts where the account data at a specific
|
|
||||||
* byte offset matches a base58 encoded value.</p>
|
|
||||||
*
|
|
||||||
* @param offset the byte offset in the account data where comparison starts
|
|
||||||
* @param bytes the base58 encoded bytes to match
|
|
||||||
*/
|
|
||||||
public record SolanaProgramAccountMemcmpFilter(
|
|
||||||
int offset,
|
|
||||||
String bytes
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.r35157.libs.solana.valuetypes;
|
|
||||||
|
|
||||||
import com.r35157.libs.valuetypes.basic.CurrencyType;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines well-known currency types used by the Solana integration.
|
|
||||||
*
|
|
||||||
* <p>Each enum value wraps a {@link CurrencyType} with a stable identifier and a
|
|
||||||
* human-readable currency name. These predefined values are intended for common
|
|
||||||
* currencies that the Solana-related modules need to reference consistently.</p>
|
|
||||||
*/
|
|
||||||
public enum WellKnownCurrencyTypes {
|
|
||||||
/**
|
|
||||||
* Native Solana currency.
|
|
||||||
*/
|
|
||||||
SOLANA(new CurrencyType(
|
|
||||||
UUID.fromString("019e0116-fce5-792f-a647-fa6da4dffec5"),
|
|
||||||
"Solana",
|
|
||||||
"SOL")
|
|
||||||
),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Syrup USDC token currency.
|
|
||||||
*/
|
|
||||||
SYRUPUSDC(new CurrencyType(
|
|
||||||
UUID.fromString("019e1d51-0600-7956-8231-f3b7058a91c2"),
|
|
||||||
"SyrupUSDC",
|
|
||||||
"SyrupUSDC")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a well-known currency type entry.
|
|
||||||
*
|
|
||||||
* @param currencyType the currency type represented by this enum value
|
|
||||||
*/
|
|
||||||
WellKnownCurrencyTypes(CurrencyType currencyType) {
|
|
||||||
this.currencyType = currencyType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the currency type represented by this enum value.
|
|
||||||
*
|
|
||||||
* @return the represented currency type
|
|
||||||
*/
|
|
||||||
public CurrencyType getCurrencyType() {
|
|
||||||
return currencyType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final CurrencyType currencyType;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.r35157.libs.valuetypes.basic;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
public record MoneyPrice(
|
|
||||||
ΩPriceΩ price,
|
|
||||||
CurrencyType currencyType
|
|
||||||
) { }
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.r35157.nenjim.hubd.impl.ref;
|
package com.r35157.nenjim.hubd.impl.ref;
|
||||||
|
|
||||||
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
|
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
|
||||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
import com.r35157.libs.jupiter.perps.JupiterPerpsPositionService;
|
||||||
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
|
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsPositionServiceImpl;
|
||||||
import com.r35157.libs.solana.SolanaBlockChain;
|
import com.r35157.libs.solana.SolanaBlockChain;
|
||||||
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
|
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
|
||||||
import com.r35157.nenjim.hubd.ctx.Context;
|
import com.r35157.nenjim.hubd.ctx.Context;
|
||||||
@@ -24,10 +24,9 @@ public class Main {
|
|||||||
// TODO: Consider if we really need a Main class or we just need to move the main method to NenjimHubImpl?
|
// 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(String[] args) throws Exception {
|
||||||
NenjimHubImpl nenjimHub = new NenjimHubImpl();
|
NenjimHubImpl nenjimHub = new NenjimHubImpl();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
SolanaBlockChain sbc = new SolanaBlockChainImpl();
|
SolanaBlockChain sbc = new SolanaBlockChainImpl();
|
||||||
JupiterPerpsService jupiter = new AnchorIdlJupiterPerpsServiceImpl(sbc);
|
JupiterPerpsPositionService jupiter = new AnchorIdlJupiterPerpsPositionServiceImpl(sbc);
|
||||||
ΩSolanaWalletIdΩ walletId = "vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf";
|
ΩSolanaWalletIdΩ walletId = "vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf";
|
||||||
Set<JupiterPerpsPosition> positions = jupiter.getOpenPositions(walletId);
|
Set<JupiterPerpsPosition> positions = jupiter.getOpenPositions(walletId);
|
||||||
int a=0;
|
int a=0;
|
||||||
|
|||||||
Reference in New Issue
Block a user