diff --git a/conf/alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf.example b/conf/alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf.example index 817d067..ffee82b 100644 --- a/conf/alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf.example +++ b/conf/alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf.example @@ -1,8 +1,8 @@ # Wallet and signer used for position-increase transactions ################################################################ -WALLET_ID {{JUPITER_PERPS_WALLET}} -SIGNER_KEY_NAME - +WALLET_ID {{JUPITER_PERPS_WALLET}} +SIGNER_KEY_NAME +RESERVE_USDC_LIMIT 10 # Id Asset Direction CollateralUSDC SizeDeltaUSD MaxSlippageBPS ############################################################################ diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmAction.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmAction.tjava index e38beaa..cb4f1fe 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmAction.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmAction.tjava @@ -6,11 +6,15 @@ import com.r35157.libs.jupiter.perps.JupiterPerpsService; import com.r35157.libs.solana.SolanaBlockChain; import com.r35157.libs.solana.SolanaSignedTransaction; import com.r35157.libs.solana.SolanaUnsignedTransaction; +import com.r35157.libs.valuetypes.basic.MoneyAmount; import java.io.IOException; +import java.math.BigDecimal; import java.nio.file.Path; import java.util.Objects; +import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.SPL_TOKEN_PROGRAM; + public final class JupiterPerpsPositionIncreaseAlarmAction implements ConfiguredAlarmAction { public JupiterPerpsPositionIncreaseAlarmAction( Path configurationDirectory, @@ -51,6 +55,10 @@ public final class JupiterPerpsPositionIncreaseAlarmAction implements Configured JupiterPerpsPositionIncreaseAlarmActionConfiguration.ResolvedPositionIncrease positionIncrease = definition.resolve(variableResolver); + if (!isUSDCBalanceOk(positionIncrease)) { + return; + } + SolanaUnsignedTransaction utrans = jupiter.buildPositionIncreaseTransaction( wallet.getAddress(), @@ -60,6 +68,7 @@ public final class JupiterPerpsPositionIncreaseAlarmAction implements Configured positionIncrease.sizeUsdDelta(), positionIncrease.maxSlippageBps() ); + SolanaSignedTransaction strans = wallet.signTransaction(utrans); ΩSolanaTransactionSignatureΩ signature = jupiter.executePositionIncreaseTransaction(strans); 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 SolanaWallet wallet; private final JupiterPerpsService jupiter; diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfiguration.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfiguration.tjava index db25703..1f62c03 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfiguration.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfiguration.tjava @@ -10,11 +10,13 @@ import java.util.Objects; public record JupiterPerpsPositionIncreaseAlarmActionConfiguration( ΩSolanaWalletIdΩ walletId, String signerKeyName, + ΩUSDCAmountΩ reserveUsdcLimit, Map positionIncreases ) { public JupiterPerpsPositionIncreaseAlarmActionConfiguration { Objects.requireNonNull(walletId, "walletId"); Objects.requireNonNull(signerKeyName, "signerKeyName"); + Objects.requireNonNull(reserveUsdcLimit, "reserveUsdcLimit"); positionIncreases = Map.copyOf(positionIncreases); if (walletId.isBlank()) { @@ -23,6 +25,11 @@ public record JupiterPerpsPositionIncreaseAlarmActionConfiguration( if (signerKeyName.isBlank()) { throw new IllegalArgumentException("Signer key name cannot be blank"); } + if (reserveUsdcLimit.signum() < 0) { + throw new IllegalArgumentException( + "USDC reserve limit cannot be negative: " + reserveUsdcLimit + ); + } } public record PositionIncrease( diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfigurationParser.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfigurationParser.tjava index af602b6..ec7e065 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfigurationParser.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/JupiterPerpsPositionIncreaseAlarmActionConfigurationParser.tjava @@ -1,6 +1,7 @@ package com.r35157.jupiterperpsalarm.impl.ref; import java.io.IOException; +import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.util.LinkedHashMap; @@ -20,6 +21,7 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser { positionIncreases = new LinkedHashMap<>(); ΩSolanaWalletIdΩ walletId = null; String signerKeyName = null; + ΩUSDCAmountΩ reserveUsdcLimit = null; for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) { String line = removeComment(lines.get(lineNumber - 1)).trim(); @@ -43,6 +45,16 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser { throw new IllegalArgumentException("Duplicate SIGNER_KEY_NAME"); } 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 { parsePositionIncrease( columns, @@ -61,13 +73,21 @@ public final class JupiterPerpsPositionIncreaseAlarmActionConfigurationParser { if (walletId == null) { throw new IllegalArgumentException("Missing WALLET_ID in " + path); } + if (signerKeyName == null) { throw new IllegalArgumentException("Missing SIGNER_KEY_NAME in " + path); } + if (reserveUsdcLimit == null) { + throw new IllegalArgumentException( + "Missing RESERVE_USDC_LIMIT in " + path + ); + } + return new JupiterPerpsPositionIncreaseAlarmActionConfiguration( walletId, signerKeyName, + reserveUsdcLimit, positionIncreases ); }