From 32f4fe39b79842d4468d51699516592e7d4cface Mon Sep 17 00:00:00 2001 From: skynetcapital <67430414+skynetcapital@users.noreply.github.com> Date: Thu, 6 May 2021 09:09:37 -0700 Subject: [PATCH] Add reading of decimals from Token Mint account data, and implement it in MarketBuilder --- .../java/org/p2p/solanaj/serum/Market.java | 24 +++++++++ .../org/p2p/solanaj/serum/MarketBuilder.java | 52 ++++++++++++++----- .../org/p2p/solanaj/serum/SerumUtils.java | 28 +++++++++- .../org/p2p/solanaj/core/MainnetTest.java | 2 +- 4 files changed, 91 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/p2p/solanaj/serum/Market.java b/src/main/java/org/p2p/solanaj/serum/Market.java index 86f09657..23dd39b7 100644 --- a/src/main/java/org/p2p/solanaj/serum/Market.java +++ b/src/main/java/org/p2p/solanaj/serum/Market.java @@ -34,6 +34,26 @@ public class Market { private OrderBook bidOrderBook; private OrderBook askOrderBook; + // Data from token mints + private byte baseDecimals; + private byte quoteDecimals; + + public byte getBaseDecimals() { + return baseDecimals; + } + + public void setBaseDecimals(byte baseDecimals) { + this.baseDecimals = baseDecimals; + } + + public byte getQuoteDecimals() { + return quoteDecimals; + } + + public void setQuoteDecimals(byte quoteDecimals) { + this.quoteDecimals = quoteDecimals; + } + public OrderBook getBidOrderBook() { return bidOrderBook; } @@ -299,6 +319,10 @@ public String toString() { ", quoteLotSize=" + quoteLotSize + ", feeRateBps=" + feeRateBps + ", referrerRebatesAccrued=" + referrerRebatesAccrued + + ", bidOrderBook=" + bidOrderBook + + ", askOrderBook=" + askOrderBook + + ", baseDecimals=" + baseDecimals + + ", quoteDecimals=" + quoteDecimals + '}'; } } diff --git a/src/main/java/org/p2p/solanaj/serum/MarketBuilder.java b/src/main/java/org/p2p/solanaj/serum/MarketBuilder.java index cd03fccc..add3cfcc 100644 --- a/src/main/java/org/p2p/solanaj/serum/MarketBuilder.java +++ b/src/main/java/org/p2p/solanaj/serum/MarketBuilder.java @@ -8,6 +8,7 @@ import java.util.Base64; import java.util.List; +import java.util.logging.Logger; /** * Builds a {@link Market} object, which can have polled data including bid/ask {@link OrderBook}s @@ -17,6 +18,10 @@ public class MarketBuilder { private final RpcClient client = new RpcClient(Cluster.MAINNET); private PublicKey publicKey; private boolean retrieveOrderbooks = false; + private static final Logger LOGGER = Logger.getLogger(MarketBuilder.class.getName()); + + // TODO move all publickey consts to it's own static class + private static final PublicKey WRAPPED_SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112"); public MarketBuilder setRetrieveOrderBooks(boolean retrieveOrderbooks) { this.retrieveOrderbooks = retrieveOrderbooks; @@ -31,7 +36,7 @@ public Market build() { Market market = new Market(); // Get account Info - byte[] base64AccountInfo = getAccountData(); + byte[] base64AccountInfo = retrieveAccountData(); // Read market if (base64AccountInfo == null) { @@ -42,33 +47,56 @@ public Market build() { // Get Order books if (retrieveOrderbooks) { - byte[] base64BidOrderbook = getOrderbookData(market.getBids()); - byte[] base64AskOrderbook = getOrderbookData(market.getAsks()); + // TODO - multi-thread these + // Data from the order books + byte[] base64BidOrderbook = retrieveAccountData(market.getBids()); + byte[] base64AskOrderbook = retrieveAccountData(market.getAsks()); OrderBook bidOrderBook = OrderBook.readOrderBook(base64BidOrderbook); OrderBook askOrderBook = OrderBook.readOrderBook(base64AskOrderbook); market.setBidOrderBook(bidOrderBook); market.setAskOrderBook(askOrderBook); + + // Data from the token mints + // TODO - multi-thread these + byte baseDecimals = getMintDecimals(market.getBaseMint()); + byte quoteDecimals = getMintDecimals(market.getQuoteMint()); + + LOGGER.info(String.format("Base decimals = %d", baseDecimals)); + LOGGER.info(String.format("Quote decimals = %d", quoteDecimals)); + + market.setBaseDecimals(baseDecimals); + market.setQuoteDecimals(quoteDecimals); } return market; } - private byte[] getAccountData() { - AccountInfo accountInfo = null; - try { - accountInfo = client.getApi().getAccountInfo(publicKey); - } catch (RpcException e) { - e.printStackTrace(); + /** + * Retrieves decimals for a given Token Mint's {@link PublicKey} from Solana account data. + * @param tokenMint + * @return + */ + private byte getMintDecimals(PublicKey tokenMint) { + if (tokenMint.equals(WRAPPED_SOL_MINT)) { + return 9; } - final List accountData = accountInfo.getValue().getData(); + // RPC call to get mint's account data into decoded bytes (already base64 decoded) + byte[] accountData = retrieveAccountData(tokenMint); - return Base64.getDecoder().decode(accountData.get(0)); + // Deserialize accountData into the MINT_LAYOUT enum + byte decimals = SerumUtils.readDecimalsFromTokenMintData(accountData); + + return decimals; + } + + private byte[] retrieveAccountData() { + return retrieveAccountData(publicKey); } - private byte[] getOrderbookData(PublicKey publicKey) { + private byte[] retrieveAccountData(PublicKey publicKey) { AccountInfo orderBook = null; try { diff --git a/src/main/java/org/p2p/solanaj/serum/SerumUtils.java b/src/main/java/org/p2p/solanaj/serum/SerumUtils.java index b3954bad..693d7070 100644 --- a/src/main/java/org/p2p/solanaj/serum/SerumUtils.java +++ b/src/main/java/org/p2p/solanaj/serum/SerumUtils.java @@ -4,6 +4,7 @@ import org.p2p.solanaj.core.PublicKey; import java.nio.ByteBuffer; +import java.util.logging.Logger; /** * version 2 market offsets. @@ -41,6 +42,9 @@ */ public class SerumUtils { + private static final Logger LOGGER = Logger.getLogger(SerumUtils.class.getName()); + + // Market public static final int OWN_ADDRESS_OFFSET = 13; private static final int VAULT_SIGNER_NONCE_OFFSET = 28; private static final int BASE_MINT_OFFSET = 53; @@ -61,8 +65,8 @@ public class SerumUtils { private static final int FEE_RATE_BPS_OFFSET = 365; private static final int REFERRER_REBATES_ACCRUED_OFFSET = 373; - - + // Token mint + private static final int TOKEN_MINT_DECIMALS_OFFSET = 44; public static PublicKey readOwnAddressPubkey(byte[] bytes) { return PublicKey.readPubkey(bytes, OWN_ADDRESS_OFFSET); @@ -183,4 +187,24 @@ public static void writeClientId(ByteBuffer result, long clientId) { public static void writeLimit(ByteBuffer result) { result.putShort(49, (short) 65535); } + + /** + * Reads the decimals value from decoded account data of a given token mint + * + * Note: MINT_LAYOUT = struct([blob(44), u8('decimals'), blob(37)]); + * + * 0-43 = other data + * index 44 = the single byte of decimals we want + * 45-... = other data + * + * @param accountData decoded account data from the token mint + * @return int containing the number of decimals in the token mint + */ + public static byte readDecimalsFromTokenMintData(byte[] accountData) { + // Read a SINGLE byte at offset 44 + byte result = accountData[TOKEN_MINT_DECIMALS_OFFSET]; + LOGGER.info(String.format("Market decimals byte = %d", result)); + + return result; + } } \ No newline at end of file diff --git a/src/test/java/org/p2p/solanaj/core/MainnetTest.java b/src/test/java/org/p2p/solanaj/core/MainnetTest.java index d5335e7e..bd921c3a 100644 --- a/src/test/java/org/p2p/solanaj/core/MainnetTest.java +++ b/src/test/java/org/p2p/solanaj/core/MainnetTest.java @@ -160,7 +160,7 @@ public void testPriceDeserialization() { */ @Test public void marketBuilderSolUsdcTest() { - final PublicKey solUsdcPublicKey = new PublicKey("7xMDbYTCqQEcK2aM9LbetGtNFJpzKdfXzLL5juaLh4GJ");; + final PublicKey solUsdcPublicKey = new PublicKey("9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT");; final Market solUsdcMarket = new MarketBuilder() .setPublicKey(solUsdcPublicKey)