diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsPosition.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsPosition.tjava index c898e86..995fbd3 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsPosition.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsPosition.tjava @@ -10,20 +10,36 @@ import java.math.BigDecimal; * position.
* * @param positionAccount the Solana account address of the Jupiter Perps position - * @param entryPrice the entry price of the position, denominated in USDC - * @param direction whether the position is long or short * @param tradedTokenMint the mint address of the token being traded - * @param sizeUsd the size of this position in USD - * @param collateralUsd the amount of USD representing the collateral for this position - * @oaram borrowFeeUsd TODO + * @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 + * @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( ΩJupiterPerpsPositionAccountΩ positionAccount, - ΩUSDCPriceΩ entryPrice, - JupiterPerpsPositionDirection direction, ΩSPLMintAddressΩ tradedTokenMint, - ΩUSDCAmountΩ sizeUsd, - ΩUSDCAmountΩ collateralUsd, - ΩUSDCAmountΩ borrowFeeUsd + 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 ) { } \ No newline at end of file diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsService.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsService.tjava index c513c2d..45ac671 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsService.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/JupiterPerpsService.tjava @@ -1,5 +1,8 @@ package com.r35157.libs.jupiter.perps; +import com.r35157.libs.solana.SolanaAccountInfo; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.util.Set; @@ -25,9 +28,12 @@ public interface JupiterPerpsService { * @throws IOException if the position account could not be fetched or decoded * @throws InterruptedException if the calling thread is interrupted while fetching * the position account - */ + JupiterPerpsPosition getPosition(@NotNull SolanaAccountInfo accountInfo) + throws IOException, InterruptedException + { JupiterPerpsPosition getPosition(ΩJupiterPerpsPositionAccountΩ positionAccount) throws IOException, InterruptedException; + */ /** * Finds open Jupiter Perps positions owned by a wallet. diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsCustodyDecoder.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsCustodyDecoder.tjava index 78b86c4..facd83e 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsCustodyDecoder.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsCustodyDecoder.tjava @@ -7,76 +7,46 @@ import com.r35157.libs.solana.SolanaAccountInfo; import java.math.BigInteger; import java.util.Base64; +import static com.r35157.libs.jupiter.perps.impl.anchoridl.DecodingPrimitives.*; + public class AnchorIdlJupiterPerpsCustodyDecoder { - public ΩSPLMintAddressΩ decodeMint( - SolanaAccountInfo custodyAccountInfo - ) { - byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64()); + public CustodyAccountInfo decode(SolanaAccountInfo custodyAccountInfo) { + byte[] data = decodeIdl(custodyAccountInfo); - if (data.length < MINT_OFFSET + PUBLIC_KEY_LENGTH) { - throw new IllegalArgumentException( - "Jupiter Perps custody account data is too short: " + data.length - ); - } + CustodyAccountInfo cai = new CustodyAccountInfo( + decodeMintAddress(data), + decodeCumulativeInterestRate(data) + ); - return readPublicKey(data, MINT_OFFSET); + return cai; } - public BigInteger decodeCurrentCumulativeInterestRate( - SolanaAccountInfo custodyAccountInfo - ) { - byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64()); + private byte[] decodeIdl(SolanaAccountInfo custodyAccountInfo) { + byte[] data = base64.decode(custodyAccountInfo.dataBase64()); if (data.length < CUMULATIVE_INTEREST_RATE_OFFSET + U128_LENGTH) { - throw new IllegalArgumentException( - "Jupiter Perps custody account data is too short: " + data.length - ); + String errMsg = "Jupiter Perps custody account data is too short: " + data.length; + throw new IllegalArgumentException(errMsg); } - return readU128( - data, - CUMULATIVE_INTEREST_RATE_OFFSET - ); + return data; } - private ΩSPLMintAddressΩ readPublicKey( - byte[] data, - int offset - ) { - byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH]; + private ΩSPLMintAddressΩ decodeMintAddress(byte[] data) { + byte[] mintAddressBytes = readPublicKey(data, MINT_OFFSET); + ΩSPLMintAddressΩ mintAddress = base58.encode(mintAddressBytes); - System.arraycopy( - data, - offset, - publicKeyBytes, - 0, - PUBLIC_KEY_LENGTH - ); - - return base58.encode(publicKeyBytes); + return mintAddress; } - private static BigInteger readU128( - byte[] data, - int offset - ) { - byte[] bytes = new byte[U128_LENGTH]; + private BigInteger decodeCumulativeInterestRate(byte[] data) { + byte[] bytes = readU128(data, CUMULATIVE_INTEREST_RATE_OFFSET); + BigInteger cumulativeInterestRate = new BigInteger(1, bytes); - for (int i = 0; i < U128_LENGTH; i++) { - bytes[i] = data[offset + U128_LENGTH - 1 - i]; - } - - return new BigInteger( - 1, - bytes - ); + return cumulativeInterestRate; } - private static final int U128_LENGTH = 16; - private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8; - private static final int PUBLIC_KEY_LENGTH = 32; - // Offsets: // 8 discriminator // +32 pool @@ -90,9 +60,11 @@ public class AnchorIdlJupiterPerpsCustodyDecoder { // +8 target_ratio_bps // +48 assets + private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8; private static final int MINT_OFFSET = ANCHOR_DISCRIMINATOR_LENGTH + PUBLIC_KEY_LENGTH; private static final int FUNDING_RATE_STATE_OFFSET = 262; private static final int CUMULATIVE_INTEREST_RATE_OFFSET = FUNDING_RATE_STATE_OFFSET; private static final Base58Codec base58 = new Base58CodecImpl(); + private static final Base64.Decoder base64 = Base64.getDecoder(); } \ No newline at end of file diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsPositionDecoder.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsPositionDecoder.tjava index bdca852..03ae283 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsPositionDecoder.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsPositionDecoder.tjava @@ -15,11 +15,7 @@ import java.util.Base64; public class AnchorIdlJupiterPerpsPositionDecoder { - JupiterPerpsPosition decode( - ΩJupiterPerpsPositionAccountΩ positionAccount, - SolanaAccountInfo accountInfo, - ΩSPLMintAddressΩ tradedTokenMint - ) { + public JupiterPerpsPositionInfo decode(SolanaAccountInfo accountInfo) { byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64()); if (data.length < PRICE_OFFSET + U64_LENGTH) { @@ -58,29 +54,17 @@ public class AnchorIdlJupiterPerpsPositionDecoder { .valueOf(rawSizeUsd) .movePointLeft(6); - AnchorIdlJupiterPerpsCustodyDecoder custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder(); - BigInteger currentCumulativeInterestRate = custodyDecoder.decodeCurrentCumulativeInterestRate(collateralCustodyAccountInfo); - BigInteger cumulativeInterestSnapshot = decodeCumulativeInterestSnapshot(positionAccountInfo); - BigInteger difference = currentCumulativeInterestRate.subtract(cumulativeInterestSnapshot); - - ΩUSDCAmountΩ borrowFeeUsd = - new BigDecimal(difference) - .multiply(sizeUsd) - .divide(BigDecimal.valueOf(1_000_000_000L), 6, RoundingMode.CEILING); - - JupiterPerpsPosition pos = new JupiterPerpsPosition( - positionAccount, + JupiterPerpsPositionInfo posInfo = new JupiterPerpsPositionInfo( entryPrice, direction, - tradedTokenMint, sizeUsd, - collateralUsd, - borrowFeeUsd + collateralUsd ); - return pos; + return posInfo; } + public BigInteger decodeCumulativeInterestSnapshot(SolanaAccountInfo accountInfo) { byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64()); diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava index f3349f3..a6eb56f 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava @@ -5,8 +5,12 @@ import com.r35157.libs.jupiter.perps.JupiterPerpsService; import com.r35157.libs.solana.SolanaAccountInfo; import com.r35157.libs.solana.SolanaBlockChain; import com.r35157.libs.solana.SolanaProgramAccountMemcmpFilter; +import org.jetbrains.annotations.NotNull; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; import java.util.HashSet; import java.util.Set; @@ -20,26 +24,47 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService { this.custodyDecoder = new AnchorIdlJupiterPerpsCustodyDecoder(); } + /* @Override - public JupiterPerpsPosition getPosition(ΩJupiterPerpsPositionAccountΩ positionAccount) - throws IOException, InterruptedException { - SolanaAccountInfo accountInfo = solanaBlockChain.getAccountInfo(positionAccount); + public JupiterPerpsPosition getPosition(@NotNull SolanaAccountInfo accountInfo) + throws IOException, InterruptedException + { + guard(accountInfo); - if (accountInfo == null) { - throw new IllegalArgumentException("Jupiter Perps position account does not exist: " + positionAccount); - } + JupiterPerpsPositionInfo posInfo = positionDecoder.decode(accountInfo); + ΩUSDCAmountΩ sizeUsd = posInfo.sizeUsd(); - if (!JUPITER_PERPS_PROGRAM_ID.equals(accountInfo.owner())) { - throw new IllegalArgumentException( - "Account is not owned by Jupiter Perps program: " + positionAccount - ); - } + CustodyAccountInfo cai = custodyDecoder.decode(custodyAccountInfo); + BigInteger cumulativeInterestSnapshot = positionDecoder.decodeCumulativeInterestSnapshot(positionAccountInfo); + ΩUSDCAmountΩ borrowFeePendingUsd = calculateBorrowFeeUsd( + cumulativeInterestSnapshot, + sizeUsd + ); - ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo); - JupiterPerpsPosition pos = positionDecoder.decode(positionAccount, accountInfo, tradedTokenMint); + JupiterPerpsPosition pos = new JupiterPerpsPosition( + accountInfo, + posInfo.entryPrice(), + posInfo.direction(), + sizeUsd, + posInfo.collateralUsd(), + cai.mintAddress(), + borrowFeePendingUsd + ); return pos; } + */ + + private void guard(SolanaAccountInfo accountInfo) { + ΩSolanaProgramIdΩ owner = accountInfo.owner(); + if (!JUPITER_PERPS_PROGRAM_ID.equals(owner)) { + String errorMsg = "Account '" + accountInfo.address() + "' is not owned by Jupiter Perps program (" + + JUPITER_PERPS_PROGRAM_ID + "), instead it is owned by '" + + owner + + "'"; + throw new IllegalArgumentException(errorMsg); + } + } @Override public Set