41: Skip Jupiter Perps position increase when USDC balance is insufficient

This commit is contained in:
2026-08-05 10:12:39 +02:00
parent 5d940d37cc
commit 3ba92bae4b
4 changed files with 78 additions and 3 deletions
@@ -1,8 +1,8 @@
# Wallet and signer used for position-increase transactions # Wallet and signer used for position-increase transactions
################################################################ ################################################################
WALLET_ID {{JUPITER_PERPS_WALLET}} WALLET_ID {{JUPITER_PERPS_WALLET}}
SIGNER_KEY_NAME <SIGNER> SIGNER_KEY_NAME <SIGNER>
RESERVE_USDC_LIMIT 10
# Id Asset Direction CollateralUSDC SizeDeltaUSD MaxSlippageBPS # Id Asset Direction CollateralUSDC SizeDeltaUSD MaxSlippageBPS
############################################################################ ############################################################################
@@ -6,11 +6,15 @@ import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.solana.SolanaBlockChain; import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.SolanaSignedTransaction; import com.r35157.libs.solana.SolanaSignedTransaction;
import com.r35157.libs.solana.SolanaUnsignedTransaction; import com.r35157.libs.solana.SolanaUnsignedTransaction;
import com.r35157.libs.valuetypes.basic.MoneyAmount;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Objects; import java.util.Objects;
import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.SPL_TOKEN_PROGRAM;
public final class JupiterPerpsPositionIncreaseAlarmAction implements ConfiguredAlarmAction { public final class JupiterPerpsPositionIncreaseAlarmAction implements ConfiguredAlarmAction {
public JupiterPerpsPositionIncreaseAlarmAction( public JupiterPerpsPositionIncreaseAlarmAction(
Path configurationDirectory, Path configurationDirectory,
@@ -51,6 +55,10 @@ public final class JupiterPerpsPositionIncreaseAlarmAction implements Configured
JupiterPerpsPositionIncreaseAlarmActionConfiguration.ResolvedPositionIncrease JupiterPerpsPositionIncreaseAlarmActionConfiguration.ResolvedPositionIncrease
positionIncrease = definition.resolve(variableResolver); positionIncrease = definition.resolve(variableResolver);
if (!isUSDCBalanceOk(positionIncrease)) {
return;
}
SolanaUnsignedTransaction utrans = SolanaUnsignedTransaction utrans =
jupiter.buildPositionIncreaseTransaction( jupiter.buildPositionIncreaseTransaction(
wallet.getAddress(), wallet.getAddress(),
@@ -60,6 +68,7 @@ public final class JupiterPerpsPositionIncreaseAlarmAction implements Configured
positionIncrease.sizeUsdDelta(), positionIncrease.sizeUsdDelta(),
positionIncrease.maxSlippageBps() positionIncrease.maxSlippageBps()
); );
SolanaSignedTransaction strans = wallet.signTransaction(utrans); SolanaSignedTransaction strans = wallet.signTransaction(utrans);
ΩSolanaTransactionSignatureΩ signature = jupiter.executePositionIncreaseTransaction(strans); ΩSolanaTransactionSignatureΩ signature = jupiter.executePositionIncreaseTransaction(strans);
System.out.println("Position Increase Signature: " + signature); System.out.println("Position Increase Signature: " + signature);
@@ -68,6 +77,45 @@ public final class JupiterPerpsPositionIncreaseAlarmAction implements Configured
} }
} }
private boolean isUSDCBalanceOk(JupiterPerpsPositionIncreaseAlarmActionConfiguration.ResolvedPositionIncrease positionIncrease)
throws IOException, InterruptedException {
ΩAmountΩ usdcBalance = wallet.getSPLTokenBalance(
JupiterPerpsReceiveToken.USDC.receiveTokenMint(),
SPL_TOKEN_PROGRAM
);
if (usdcBalance == null) {
System.err.println("Position Increase of "
+ positionIncrease.collateralUsd()
+ " USDC skipped: Wallet has no USDC token account!"
);
return false;
}
ΩUSDCAmountΩ collateralUsd = positionIncrease.collateralUsd();
ΩUSDCAmountΩ reserveUsdcLimit = configuration.reserveUsdcLimit();
ΩUSDCAmountΩ requiredUsdcBalance = collateralUsd.add(reserveUsdcLimit);
if (usdcBalance.compareTo(requiredUsdcBalance) < 0) {
System.err.println(
"Position Increase of "
+ positionIncrease.collateralUsd()
+ " USDC skipped: Wallet has "
+ usdcBalance
+ " USDC, but "
+ requiredUsdcBalance
+ " USDC is required ("
+ collateralUsd
+ " collateral + "
+ reserveUsdcLimit
+ " reserve)."
);
return false;
}
return true;
}
private final JupiterPerpsPositionIncreaseAlarmActionConfiguration configuration; private final JupiterPerpsPositionIncreaseAlarmActionConfiguration configuration;
private final SolanaWallet wallet; private final SolanaWallet wallet;
private final JupiterPerpsService jupiter; private final JupiterPerpsService jupiter;
@@ -10,11 +10,13 @@ import java.util.Objects;
public record JupiterPerpsPositionIncreaseAlarmActionConfiguration( public record JupiterPerpsPositionIncreaseAlarmActionConfiguration(
ΩSolanaWalletIdΩ walletId, ΩSolanaWalletIdΩ walletId,
String signerKeyName, String signerKeyName,
ΩUSDCAmountΩ reserveUsdcLimit,
Map<Integer, PositionIncrease> positionIncreases Map<Integer, PositionIncrease> positionIncreases
) { ) {
public JupiterPerpsPositionIncreaseAlarmActionConfiguration { public JupiterPerpsPositionIncreaseAlarmActionConfiguration {
Objects.requireNonNull(walletId, "walletId"); Objects.requireNonNull(walletId, "walletId");
Objects.requireNonNull(signerKeyName, "signerKeyName"); Objects.requireNonNull(signerKeyName, "signerKeyName");
Objects.requireNonNull(reserveUsdcLimit, "reserveUsdcLimit");
positionIncreases = Map.copyOf(positionIncreases); positionIncreases = Map.copyOf(positionIncreases);
if (walletId.isBlank()) { if (walletId.isBlank()) {
@@ -23,6 +25,11 @@ public record JupiterPerpsPositionIncreaseAlarmActionConfiguration(
if (signerKeyName.isBlank()) { if (signerKeyName.isBlank()) {
throw new IllegalArgumentException("Signer key name cannot be blank"); throw new IllegalArgumentException("Signer key name cannot be blank");
} }
if (reserveUsdcLimit.signum() < 0) {
throw new IllegalArgumentException(
"USDC reserve limit cannot be negative: " + reserveUsdcLimit
);
}
} }
public record PositionIncrease( public record PositionIncrease(
@@ -1,6 +1,7 @@
package com.r35157.jupiterperpsalarm.impl.ref; package com.r35157.jupiterperpsalarm.impl.ref;
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.LinkedHashMap; import java.util.LinkedHashMap;
@@ -20,6 +21,7 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser {
positionIncreases = new LinkedHashMap<>(); positionIncreases = new LinkedHashMap<>();
ΩSolanaWalletIdΩ walletId = null; ΩSolanaWalletIdΩ walletId = null;
String signerKeyName = null; String signerKeyName = null;
ΩUSDCAmountΩ reserveUsdcLimit = null;
for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) { for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) {
String line = removeComment(lines.get(lineNumber - 1)).trim(); String line = removeComment(lines.get(lineNumber - 1)).trim();
@@ -43,6 +45,16 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser {
throw new IllegalArgumentException("Duplicate SIGNER_KEY_NAME"); throw new IllegalArgumentException("Duplicate SIGNER_KEY_NAME");
} }
signerKeyName = variableResolver.resolve(columns[1]); signerKeyName = variableResolver.resolve(columns[1]);
} else if (firstColumn.equals("RESERVE_USDC_LIMIT")) {
requireColumnCount(columns, 2, "RESERVE_USDC_LIMIT value");
if (reserveUsdcLimit != null) {
throw new IllegalArgumentException(
"Duplicate RESERVE_USDC_LIMIT"
);
}
reserveUsdcLimit = new ΩUSDCAmountΩ(
variableResolver.resolve(columns[1])
);
} else { } else {
parsePositionIncrease( parsePositionIncrease(
columns, columns,
@@ -61,13 +73,21 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser {
if (walletId == null) { if (walletId == null) {
throw new IllegalArgumentException("Missing WALLET_ID in " + path); throw new IllegalArgumentException("Missing WALLET_ID in " + path);
} }
if (signerKeyName == null) { if (signerKeyName == null) {
throw new IllegalArgumentException("Missing SIGNER_KEY_NAME in " + path); throw new IllegalArgumentException("Missing SIGNER_KEY_NAME in " + path);
} }
if (reserveUsdcLimit == null) {
throw new IllegalArgumentException(
"Missing RESERVE_USDC_LIMIT in " + path
);
}
return new JupiterPerpsPositionIncreaseAlarmActionConfiguration( return new JupiterPerpsPositionIncreaseAlarmActionConfiguration(
walletId, walletId,
signerKeyName, signerKeyName,
reserveUsdcLimit,
positionIncreases positionIncreases
); );
} }