13: Create initial Jupiter Perps position API

This commit is contained in:
2026-06-25 09:55:16 +02:00
parent 539068978f
commit 96d1289730
4 changed files with 152 additions and 0 deletions
@@ -0,0 +1,22 @@
package com.r35157.libs.jupiter.perps;
import java.math.BigDecimal;
/**
* Represents a Jupiter Perps position.
*
* <p>A Jupiter Perps position is represented on-chain by a Solana account owned by
* the Jupiter Perps program. This record contains the public API view of such a
* 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 entryPrice the entry price of the position, denominated in USDC
*/
public record JupiterPerpsPosition(
ΩJupiterPerpsPositionAccountΩ positionAccount,
ΩUSDCPriceΩ entryPrice
) {
}
@@ -0,0 +1,30 @@
package com.r35157.libs.jupiter.perps;
import java.io.IOException;
/**
* Service for reading Jupiter Perps positions.
*
* <p>This service is read-only. It does not open, close, modify, or sign transactions
* for Jupiter Perps positions.</p>
*
* <p>The first supported operation is reading a known Jupiter Perps position account
* and returning its decoded position data.</p>
*/
public interface JupiterPerpsPositionService {
/**
* Reads a Jupiter Perps position from a known position account.
*
* <p>The supplied account must be the Solana account that stores the Jupiter Perps
* position state. It is not the wallet address, token account, custody account, pool
* account, or position request account.</p>
*
* @param positionAccount the Solana account address of the Jupiter Perps position
* @return the decoded Jupiter Perps position
* @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(ΩJupiterPerpsPositionAccountΩ positionAccount)
throws IOException, InterruptedException;
}
@@ -0,0 +1,57 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
import com.r35157.libs.solana.SolanaAccountInfo;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Base64;
class AnchorIdlJupiterPerpsPositionDecoder {
JupiterPerpsPosition decode(
ΩJupiterPerpsPositionAccountΩ positionAccount,
SolanaAccountInfo accountInfo
) {
byte[] data = Base64.getDecoder().decode(accountInfo.dataBase64());
if (data.length < PRICE_OFFSET + U64_LENGTH) {
throw new IllegalArgumentException(
"Jupiter Perps position account data is too short: " + data.length
);
}
long rawEntryPrice = ByteBuffer
.wrap(data, PRICE_OFFSET, U64_LENGTH)
.order(ByteOrder.LITTLE_ENDIAN)
.getLong();
ΩUSDCPriceΩ entryPrice = BigDecimal
.valueOf(rawEntryPrice)
.movePointLeft(6);
JupiterPerpsPosition pos = new JupiterPerpsPosition(
positionAccount,
entryPrice
);
return pos;
}
private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
private static final int PUBLIC_KEY_LENGTH = 32;
private static final int I64_LENGTH = 8;
private static final int U64_LENGTH = 8;
private static final int SIDE_ENUM_LENGTH = 1;
private static final int PRICE_OFFSET =
ANCHOR_DISCRIMINATOR_LENGTH
+ PUBLIC_KEY_LENGTH // owner
+ PUBLIC_KEY_LENGTH // pool
+ PUBLIC_KEY_LENGTH // custody
+ PUBLIC_KEY_LENGTH // collateralCustody
+ I64_LENGTH // openTime
+ I64_LENGTH // updateTime
+ SIDE_ENUM_LENGTH; // side
}
@@ -0,0 +1,43 @@
package com.r35157.libs.jupiter.perps.impl.anchoridl;
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
import com.r35157.libs.jupiter.perps.JupiterPerpsPositionService;
import com.r35157.libs.solana.SolanaAccountInfo;
import com.r35157.libs.solana.SolanaBlockChain;
import java.io.IOException;
public class AnchorIdlJupiterPerpsPositionServiceImpl implements JupiterPerpsPositionService {
public AnchorIdlJupiterPerpsPositionServiceImpl(
SolanaBlockChain solanaBlockChain
) {
this.solanaBlockChain = solanaBlockChain;
this.positionDecoder = new AnchorIdlJupiterPerpsPositionDecoder();
}
@Override
public JupiterPerpsPosition getPosition(ΩJupiterPerpsPositionAccountΩ positionAccount)
throws IOException, InterruptedException {
SolanaAccountInfo accountInfo = solanaBlockChain.getAccountInfo(positionAccount);
if (accountInfo == null) {
throw new IllegalArgumentException("Jupiter Perps position account does not exist: " + positionAccount);
}
if (!JUPITER_PERPS_PROGRAM_ID.equals(accountInfo.owner())) {
throw new IllegalArgumentException(
"Account is not owned by Jupiter Perps program: " + positionAccount
);
}
JupiterPerpsPosition pos = positionDecoder.decode(positionAccount, accountInfo);
return pos;
}
private static final ΩJupiterPerpsProgramIdΩ JUPITER_PERPS_PROGRAM_ID =
"PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu";
private final SolanaBlockChain solanaBlockChain;
private final AnchorIdlJupiterPerpsPositionDecoder positionDecoder;
}