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 079d048..5bc1c7d 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 @@ -1,50 +1,64 @@ 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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.math.BigInteger; import java.util.Base64; +import static com.r35157.libs.jupiter.perps.impl.anchoridl.DecodingPrimitives.*; + class AnchorIdlJupiterPerpsCustodyDecoder { - ΩSPLMintAddressΩ decodeMint( - SolanaAccountInfo custodyAccountInfo + @Nullable JupiterCustodyAccountInfo decode( + @NotNull ΩJupiterCustodyAccountInfoEncodedΩ encodedCustodyAccountInfo ) { - byte[] data = Base64.getDecoder().decode(custodyAccountInfo.dataBase64()); + ΩJupiterPerpsPostionIdlΩ idl = decodeIdl(encodedCustodyAccountInfo); - 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 + JupiterCustodyAccountInfo cai = new JupiterCustodyAccountInfo( + readPublicKey(idl, MINT_OFFSET), + decodeCumulativeInterestRate(idl) ); - return base58.encode(publicKeyBytes); + return cai; } + private ΩJupiterPerpsCustodyAccountIdlΩ decodeIdl(ΩJupiterCustodyAccountInfoEncodedΩ encodedCustodyAccountInfo) { + ΩJupiterPerpsCustodyAccountIdlΩ idl = base64.decode(encodedCustodyAccountInfo.data()); + + if (idl.length < CUMULATIVE_INTEREST_RATE_OFFSET + U128_LENGTH) { + String errMsg = "Jupiter Perps custody account data is too short: " + idl.length; + throw new IllegalArgumentException(errMsg); + } + + return idl; + } + + private BigInteger decodeCumulativeInterestRate(byte[] data) { + byte[] bytes = readU128(data, CUMULATIVE_INTEREST_RATE_OFFSET); + BigInteger cumulativeInterestRate = new BigInteger(1, bytes); + + return cumulativeInterestRate; + } + + // Offsets: + // 8 discriminator + // +32 pool + // +32 mint + // +32 token_account + // +1 decimals + // +1 is_stable + // +45 oracle + // +48 pricing + // +7 permissions + // +8 target_ratio_bps + // +48 assets + 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; + private static final int FUNDING_RATE_STATE_OFFSET = 262; + private static final int CUMULATIVE_INTEREST_RATE_OFFSET = FUNDING_RATE_STATE_OFFSET; - private static final int MINT_OFFSET = - ANCHOR_DISCRIMINATOR_LENGTH - + PUBLIC_KEY_LENGTH; // pool - - 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 16cfe38..fefde85 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 @@ -1,7 +1,5 @@ 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.JupiterPerpsPositionDirection; import com.r35157.libs.solana.SolanaAccountInfo; import org.jetbrains.annotations.NotNull; @@ -12,24 +10,19 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Base64; -import static java.math.BigDecimal.ZERO; +import static com.r35157.libs.jupiter.perps.impl.anchoridl.DecodingPrimitives.*; class AnchorIdlJupiterPerpsPositionDecoder { - @Nullable JupiterPerpsPositionInfo decode(@NotNull SolanaAccountInfo accountInfo) { - byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64()); + @Nullable JupiterPerpsPositionInfo decode( + @NotNull ΩJupiterPerpsPositionInfoEncodedΩ encodedPerpsPositionInfo + ) { + ΩJupiterPerpsPostionIdlΩ idl = decodeIdl(encodedPerpsPositionInfo); - if (data.length < PRICE_OFFSET + U64_LENGTH) { - throw new IllegalArgumentException( - "Jupiter Perps position account data is too short: " + data.length - ); - } - - JupiterPerpsPositionDirection direction = - decodeDirection(data[SIDE_OFFSET]); + JupiterPerpsPositionDirection direction = decodeDirection(idl); long rawEntryPrice = ByteBuffer - .wrap(data, PRICE_OFFSET, U64_LENGTH) + .wrap(idl, PRICE_OFFSET, U64_LENGTH) .order(ByteOrder.LITTLE_ENDIAN) .getLong(); @@ -38,7 +31,7 @@ class AnchorIdlJupiterPerpsPositionDecoder { .movePointLeft(6); long rawCollateralUsd = ByteBuffer - .wrap(data, COLLATERAL_USD_OFFSET, U64_LENGTH) + .wrap(idl, COLLATERAL_USD_OFFSET, U64_LENGTH) .order(ByteOrder.LITTLE_ENDIAN) .getLong(); @@ -47,7 +40,7 @@ class AnchorIdlJupiterPerpsPositionDecoder { .movePointLeft(6); long rawSizeUsd = ByteBuffer - .wrap(data, SIZE_USD_OFFSET, U64_LENGTH) + .wrap(idl, SIZE_USD_OFFSET, U64_LENGTH) .order(ByteOrder.LITTLE_ENDIAN) .getLong(); @@ -55,29 +48,47 @@ class AnchorIdlJupiterPerpsPositionDecoder { .valueOf(rawSizeUsd) .movePointLeft(6); - JupiterPerpsPositionInfo posInfo = new JupiterPerpsPositionInfo(entryPrice, direction, sizeUsd, collateralUsd); + ΩJupiterCustodyAccountAddressΩ custodyAccountAddress = readPublicKey(idl, CUSTODY_OFFSET); + + JupiterPerpsPositionInfo posInfo = new JupiterPerpsPositionInfo( + entryPrice, + direction, + sizeUsd, + collateralUsd, + custodyAccountAddress + ); return posInfo; } - ΩSolanaAddressΩ decodeCustodyAccount(SolanaAccountInfo accountInfo) { - byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64()); + ΩJupiterCustodyAccountAddressΩ decodeCustodyAccountAddress(SolanaAccountInfo accountInfo) { + byte[] data = base64.decode(accountInfo.data()); if (data.length < CUSTODY_OFFSET + PUBLIC_KEY_LENGTH) { - throw new IllegalArgumentException( - "Jupiter Perps position account data is too short: " + data.length - ); + String errorMsg = "Jupiter Perps position account data is too short: " + data.length; + throw new IllegalArgumentException(errorMsg); } - return readPublicKey( - data, - CUSTODY_OFFSET - ); + ΩJupiterCustodyAccountAddressΩ addr = readPublicKey(data, CUSTODY_OFFSET); + + return addr; } - private JupiterPerpsPositionDirection decodeDirection( - byte rawSide - ) { + private ΩJupiterPerpsPostionIdlΩ decodeIdl(ΩJupiterPerpsPositionInfoEncodedΩ encodedPerpsPositionInfo) { + ΩJupiterPerpsPostionIdlΩ idl = base64.decode(encodedPerpsPositionInfo.data()); + + // TODO: Check size correctly, as in AnchorIdlJupiterPerpsCustodyDecoder.decodeIdl() + if (idl.length == 0) { + String errorMsg = "Jupiter Perps position account data is too short: " + idl.length; + throw new IllegalArgumentException(errorMsg); + } + + return idl; + } + + private JupiterPerpsPositionDirection decodeDirection(ΩJupiterPerpsPostionIdlΩ idl) { + byte rawSide = idl[SIDE_OFFSET]; + // Jupiter Perps position side values are encoded as 1 = LONG, 2 = SHORT. JupiterPerpsPositionDirection direction = switch (rawSide) { case 1 -> JupiterPerpsPositionDirection.LONG; @@ -90,23 +101,6 @@ class AnchorIdlJupiterPerpsPositionDecoder { 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 PUBLIC_KEY_LENGTH = 32; private static final int I64_LENGTH = 8; @@ -124,5 +118,5 @@ class AnchorIdlJupiterPerpsPositionDecoder { private static final int SIZE_USD_OFFSET = PRICE_OFFSET + U64_LENGTH; private static final int COLLATERAL_USD_OFFSET = SIZE_USD_OFFSET + U64_LENGTH; - 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/AnchorIdlJupiterPerpsServiceImpl.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/AnchorIdlJupiterPerpsServiceImpl.tjava index 73fc7d7..6753137 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 @@ -42,14 +42,23 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService { ); } - ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo); - - JupiterPerpsPositionInfo info = positionDecoder.decode(accountInfo); + JupiterPerpsPositionInfo perpsPositionInfo = positionDecoder.decode(accountInfo); JupiterPerpsPosition pos; - if(info == null) { + if(perpsPositionInfo == null) { pos = null; } else { + ΩJupiterCustodyAccountAddressΩ custodyAccountAddress = positionDecoder.decodeCustodyAccountAddress(accountInfo); + ΩJupiterCustodyAccountInfoEncodedΩ encodedCustodyAccountInfo = solanaBlockChain.getAccountInfo(custodyAccountAddress); + + if (encodedCustodyAccountInfo == null) { + String errorMsg = "Jupiter Perps custody account does not exist: " + custodyAccountAddress; + throw new IllegalArgumentException(errorMsg); + } + + JupiterCustodyAccountInfo custodyAccountInfo = custodyDecoder.decode(encodedCustodyAccountInfo); + ΩSPLMintAddressΩ mintAddress = custodyAccountInfo.mintAddress(); + ΩUSDCAmountΩ value = ZERO; // TODO - Dummy ΩUSDCAmountΩ pnl = ZERO; // TODO - Dummy BigDecimal pnlPercent = ZERO; // TODO - Dummy @@ -62,16 +71,16 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService { pos = new JupiterPerpsPosition( positionAccount, - tradedTokenMint, - info.direction(), + mintAddress, + perpsPositionInfo.direction(), value, - info.sizeUsd(), + perpsPositionInfo.size(), pnl, pnlPercent, leverage, - info.entryPrice(), + perpsPositionInfo.entryPrice(), marketPrice, - info.collateralUsd(), + perpsPositionInfo.collateral(), totalFees, borrowFeesDue, closeFeePending, @@ -105,12 +114,21 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService { throw new IllegalArgumentException(errorMsg); } - JupiterPerpsPositionInfo info = positionDecoder.decode(accountInfo); - if(info == null || info.sizeUsd().compareTo(ZERO) == 0) { + JupiterPerpsPositionInfo perpsPositionInfo = positionDecoder.decode(accountInfo); + if(perpsPositionInfo == null || perpsPositionInfo.size().compareTo(ZERO) == 0) { continue; } - ΩSPLMintAddressΩ tradedTokenMint = getTradedTokenMint(accountInfo); + ΩJupiterCustodyAccountAddressΩ custodyAccountAddress = perpsPositionInfo.custodyAccountAddress(); + ΩJupiterCustodyAccountInfoEncodedΩ encodedCustodyAccountInfo = solanaBlockChain.getAccountInfo(custodyAccountAddress); + + if (encodedCustodyAccountInfo == null) { + String errorMsg = "Jupiter Perps custody account does not exist: " + custodyAccountAddress; + throw new IllegalArgumentException(errorMsg); + } + + JupiterCustodyAccountInfo custodyAccountInfo = custodyDecoder.decode(encodedCustodyAccountInfo); + ΩSPLMintAddressΩ mintAddress = custodyAccountInfo.mintAddress(); ΩUSDCAmountΩ value = ZERO; // TODO - Dummy ΩUSDCAmountΩ pnl = ZERO; // TODO - Dummy @@ -124,16 +142,16 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService { JupiterPerpsPosition pos = new JupiterPerpsPosition( positionAccount, - tradedTokenMint, - info.direction(), + mintAddress, + perpsPositionInfo.direction(), value, - info.sizeUsd(), + perpsPositionInfo.size(), pnl, pnlPercent, leverage, - info.entryPrice(), + perpsPositionInfo.entryPrice(), marketPrice, - info.collateralUsd(), + perpsPositionInfo.collateral(), totalFees, borrowFeesDue, closeFeePending, @@ -146,22 +164,6 @@ public class AnchorIdlJupiterPerpsServiceImpl implements JupiterPerpsService { 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 = "PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu"; private static final int POSITION_OWNER_OFFSET = 8; diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/DecodingPrimitives.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/DecodingPrimitives.tjava index 2155426..7c35b6d 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/DecodingPrimitives.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/DecodingPrimitives.tjava @@ -1,5 +1,8 @@ package com.r35157.libs.jupiter.perps.impl.anchoridl; +import com.r35157.libs.codec.Base58Codec; +import com.r35157.libs.codec.impl.ref.Base58CodecImpl; + public class DecodingPrimitives { public static byte[] readU128(byte[] data, int offset) { @@ -12,20 +15,23 @@ public class DecodingPrimitives { return bytes; } - public static byte[] readPublicKey(byte[] data, int offset) { - byte[] publicKeyBytes = new byte[PUBLIC_KEY_LENGTH]; + public static ΩSolanaAddressΩ readPublicKey(byte[] data, int offset) { + byte[] bytes = new byte[PUBLIC_KEY_LENGTH]; System.arraycopy( data, offset, - publicKeyBytes, + bytes, 0, PUBLIC_KEY_LENGTH ); - return publicKeyBytes; + ΩSolanaAddressΩ addr = base58.encode(bytes); + return addr; } public static final int U128_LENGTH = 16; public static final int PUBLIC_KEY_LENGTH = 32; + + private static final Base58Codec base58 = new Base58CodecImpl(); } diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/CustodyAccountInfo.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/JupiterCustodyAccountInfo.tjava similarity index 81% rename from src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/CustodyAccountInfo.tjava rename to src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/JupiterCustodyAccountInfo.tjava index b6cb2ec..b44f805 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/CustodyAccountInfo.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/JupiterCustodyAccountInfo.tjava @@ -2,7 +2,7 @@ package com.r35157.libs.jupiter.perps.impl.anchoridl; import java.math.BigInteger; -public record CustodyAccountInfo( +public record JupiterCustodyAccountInfo( ΩSPLMintAddressΩ mintAddress, BigInteger currentCumulativeInterestRate ) { diff --git a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/JupiterPerpsPositionInfo.tjava b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/JupiterPerpsPositionInfo.tjava index 6d03e01..d7a8912 100644 --- a/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/JupiterPerpsPositionInfo.tjava +++ b/src/main/tjava/com/r35157/libs/jupiter/perps/impl/anchoridl/JupiterPerpsPositionInfo.tjava @@ -7,7 +7,8 @@ import java.math.BigDecimal; public record JupiterPerpsPositionInfo( ΩUSDCPriceΩ entryPrice, JupiterPerpsPositionDirection direction, - ΩUSDCAmountΩ sizeUsd, - ΩUSDCAmountΩ collateralUsd + ΩUSDCAmountΩ size, + ΩUSDCAmountΩ collateral, + ΩJupiterCustodyAccountAddressΩ custodyAccountAddress ) { } diff --git a/src/main/tjava/com/r35157/libs/raydium/impl/ref/RaydiumImpl.tjava b/src/main/tjava/com/r35157/libs/raydium/impl/ref/RaydiumImpl.tjava index a983362..f5992a2 100644 --- a/src/main/tjava/com/r35157/libs/raydium/impl/ref/RaydiumImpl.tjava +++ b/src/main/tjava/com/r35157/libs/raydium/impl/ref/RaydiumImpl.tjava @@ -747,7 +747,7 @@ public class RaydiumImpl implements Raydium { private byte[] decodeBase64AccountData(SolanaAccountInfo accountInfo) throws IOException { try { - return Base64.getDecoder().decode(accountInfo.dataBase64()); + return base64.decode(accountInfo.data()); } catch (IllegalArgumentException e) { throw new IOException("Could not decode Solana account data as Base64 for account: " + accountInfo.address(), e); } @@ -954,6 +954,8 @@ public class RaydiumImpl implements Raydium { private static final int RAYDIUM_POOL_STATE_TICK_CURRENT_OFFSET = RAYDIUM_POOL_STATE_SQRT_PRICE_X64_OFFSET + RAYDIUM_U128_LENGTH; + private static final Base64.Decoder base64 = Base64.getDecoder(); + private final SolanaBlockChain solanaBlockChain; private final HttpClient httpClient; private final ObjectMapper objectMapper; diff --git a/src/main/tjava/com/r35157/libs/solana/SolanaAccountInfo.tjava b/src/main/tjava/com/r35157/libs/solana/SolanaAccountInfo.tjava index da0f196..10979b0 100644 --- a/src/main/tjava/com/r35157/libs/solana/SolanaAccountInfo.tjava +++ b/src/main/tjava/com/r35157/libs/solana/SolanaAccountInfo.tjava @@ -1,5 +1,7 @@ package com.r35157.libs.solana; +import org.jetbrains.annotations.NotNull; + /** * Represents basic information about a Solana account. * @@ -14,11 +16,11 @@ package com.r35157.libs.solana; * * @param address the Solana account address * @param owner the Solana program id that owns the account - * @param dataBase64 the account data encoded as Base64, or an empty string if the account has no data + * @param data the account data encoded as Base64, or null if the account has no data */ public record SolanaAccountInfo( - ΩSolanaAddressΩ address, - ΩSolanaProgramIdΩ owner, - String dataBase64 + @NotNull ΩSolanaAddressΩ address, + @NotNull ΩSolanaProgramIdΩ owner, + @NotNull ΩBase64StringΩ data ) { } \ No newline at end of file diff --git a/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/Main.tjava b/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/Main.tjava index 734f032..e7bf5f9 100644 --- a/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/Main.tjava +++ b/src/main/tjava/com/r35157/nenjim/hubd/impl/ref/Main.tjava @@ -25,13 +25,12 @@ public class Main { static void main(String[] args) throws Exception { NenjimHubImpl nenjimHub = new NenjimHubImpl(); - /* SolanaBlockChain sbc = new SolanaBlockChainImpl(); JupiterPerpsService jupiter = new AnchorIdlJupiterPerpsServiceImpl(sbc); ΩSolanaWalletIdΩ walletId = "vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf"; Set positions = jupiter.getOpenPositions(walletId); int a=0; - */ + nenjimHub.awaitShutdown(); /* try {