diff --git a/mango/src/main/java/com/mmorrell/mango/model/MangoOrder.java b/mango/src/main/java/com/mmorrell/mango/model/MangoOrder.java new file mode 100644 index 0000000..c83ab01 --- /dev/null +++ b/mango/src/main/java/com/mmorrell/mango/model/MangoOrder.java @@ -0,0 +1,28 @@ +package com.mmorrell.mango.model; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import org.p2p.solanaj.core.PublicKey; + +/** + * Class that represents a Serum order. + */ +@Data +@Builder +public class MangoOrder { + + private long price; + private long quantity; + private long clientOrderId; + private float floatPrice; + private float floatQuantity; + private PublicKey owner; + + // used in newOrderv3. no constructor, only setters/getters + private long maxQuoteQuantity; + private long clientId; + private boolean buy; + +} diff --git a/mango/src/main/java/com/mmorrell/mango/model/MangoOrderBook.java b/mango/src/main/java/com/mmorrell/mango/model/MangoOrderBook.java new file mode 100644 index 0000000..1beaae2 --- /dev/null +++ b/mango/src/main/java/com/mmorrell/mango/model/MangoOrderBook.java @@ -0,0 +1,119 @@ +package com.mmorrell.mango.model; + +import java.util.ArrayList; +import java.util.Comparator; + +/** + * This class represents a Serum MangoOrderbook, that get deserialized from bytes. + * + * Note: + * + * buffer_layout_1.blob(5), + * layout_1.accountFlagsLayout('accountFlags'), + * slab_1.SLAB_LAYOUT.replicate('slab'), + * buffer_layout_1.blob(7), + * + */ +public class MangoOrderBook { + + private MangoSlab mangoSlab; + private byte baseDecimals; + private byte quoteDecimals; + private long baseLotSize; + private long quoteLotSize; + + public static MangoOrderBook readMangoOrderBook(byte[] data) { + final MangoOrderBook mangoMangoOrderBook = new MangoOrderBook(); + + final MangoSlab mangoSlab = MangoSlab.readOrderBookSlab(data); + mangoMangoOrderBook.setSlab(mangoSlab); + + return mangoMangoOrderBook; + + } + + public ArrayList getMangoOrders() { + if (mangoSlab == null) { + return null; + } + + final ArrayList mangoOrders = new ArrayList<>(); + + mangoSlab.getMangoSlabNodes().forEach(slabNode -> { + if (slabNode instanceof MangoSlabLeafNode) { + MangoSlabLeafNode slabLeafNode = (MangoSlabLeafNode) slabNode; + mangoOrders.add(MangoOrder.builder() + .price(slabLeafNode.getPrice()) + .quantity(slabLeafNode.getQuantity()) + .clientOrderId(slabLeafNode.getClientOrderId()) + .floatPrice(MangoUtils.priceLotsToNumber(slabLeafNode.getPrice(), baseDecimals, quoteDecimals, baseLotSize, quoteLotSize)) + .floatQuantity((float) ((slabLeafNode.getQuantity() * baseLotSize) / MangoUtils.getBaseSplTokenMultiplier(baseDecimals))) + .owner(slabLeafNode.getOwner()) + .build() + ); + } + }); + + return mangoOrders; + + + } + + /** + * Retrieves the top {@link MangoOrder} for bids (sorted by price descending). + * @return + */ + public MangoOrder getBestBid() { + final ArrayList MangoOrders = getMangoOrders(); + MangoOrders.sort(Comparator.comparingLong(MangoOrder::getPrice).reversed()); + return MangoOrders.get(0); + } + + public MangoOrder getBestAsk() { + final ArrayList MangoOrders = getMangoOrders(); + MangoOrders.sort(Comparator.comparingLong(MangoOrder::getPrice)); + return MangoOrders.get(0); + } + + public MangoSlab getSlab() { + return mangoSlab; + } + + public void setSlab(MangoSlab mangoSlab) { + this.mangoSlab = mangoSlab; + } + + public void setBaseDecimals(byte baseDecimals) { + this.baseDecimals = baseDecimals; + } + + public byte getBaseDecimals() { + return baseDecimals; + } + + public void setQuoteDecimals(byte quoteDecimals) { + this.quoteDecimals = quoteDecimals; + } + + public byte getQuoteDecimals() { + return quoteDecimals; + } + + + public void setBaseLotSize(long baseLotSize) { + this.baseLotSize = baseLotSize; + } + + public long getBaseLotSize() { + return baseLotSize; + } + + public void setQuoteLotSize(long quoteLotSize) { + this.quoteLotSize = quoteLotSize; + } + + public long getQuoteLotSize() { + return quoteLotSize; + } + +} diff --git a/mango/src/main/java/com/mmorrell/mango/model/MangoPerpMarket.java b/mango/src/main/java/com/mmorrell/mango/model/MangoPerpMarket.java new file mode 100644 index 0000000..7858121 --- /dev/null +++ b/mango/src/main/java/com/mmorrell/mango/model/MangoPerpMarket.java @@ -0,0 +1,40 @@ +package com.mmorrell.mango.model; + +import lombok.Builder; +import lombok.Data; +import org.bitcoinj.core.Utils; +import org.p2p.solanaj.core.PublicKey; + +@Data +@Builder +// 4nfmQP3KmUqEJ6qJLsS3offKgE96YUB4Rp7UQvm2Fbi9 mngo-perp +public class MangoPerpMarket { + + // metadata 0-7 ignored + private static final int MANGO_GROUP_OFFSET = 8; + private static final int BIDS_OFFSET = MANGO_GROUP_OFFSET + 32; + private static final int ASKS_OFFSET = BIDS_OFFSET + 32; + private static final int EVENT_QUEUE_OFFSET = ASKS_OFFSET + 32; + private static final int QUOTE_LOT_SIZE_OFFSET = EVENT_QUEUE_OFFSET + 32; + private static final int BASE_LOT_SIZE_OFFSET = QUOTE_LOT_SIZE_OFFSET + 8; + + private PublicKey mangoGroup; + private PublicKey bids; + private PublicKey asks; + private PublicKey eventQueue; + private long quoteLotSize; + private long baseLotSize; + + public static MangoPerpMarket readMangoPerpMarket(byte[] data) { + final MangoPerpMarket mangoPerpMarket = MangoPerpMarket.builder() + .mangoGroup(PublicKey.readPubkey(data, MANGO_GROUP_OFFSET)) + .bids(PublicKey.readPubkey(data, BIDS_OFFSET)) + .asks(PublicKey.readPubkey(data, ASKS_OFFSET)) + .eventQueue(PublicKey.readPubkey(data, EVENT_QUEUE_OFFSET)) + .quoteLotSize(Utils.readInt64(data, QUOTE_LOT_SIZE_OFFSET)) + .baseLotSize(Utils.readInt64(data, BASE_LOT_SIZE_OFFSET)) + .build(); + + return mangoPerpMarket; + } +} diff --git a/mango/src/main/java/com/mmorrell/mango/model/MangoSlab.java b/mango/src/main/java/com/mmorrell/mango/model/MangoSlab.java new file mode 100644 index 0000000..d3f9cb6 --- /dev/null +++ b/mango/src/main/java/com/mmorrell/mango/model/MangoSlab.java @@ -0,0 +1,328 @@ +package com.mmorrell.mango.model; + +import org.bitcoinj.core.Utils; +import org.p2p.solanaj.core.PublicKey; +import org.p2p.solanaj.utils.ByteUtils; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; + + +/** + * export const ORDERBOOK_LAYOUT = struct([ + * blob(5), 0-4 + * accountFlagsLayout('accountFlags'), 5-12 + * SLAB_LAYOUT.replicate('slab'), 13 - ... + * blob(7), + * ]); + *

+ * first 5 bytes = "serum", start at position 5 + *

+ * note: zero(4) = blob(4) = 4 bytes + *

+ * export const SLAB_LAYOUT = struct([ + * SLAB_HEADER_LAYOUT, + * seq( + * SLAB_NODE_LAYOUT, + * offset( + * SLAB_HEADER_LAYOUT.layoutFor('bumpIndex'), + * SLAB_HEADER_LAYOUT.offsetOf('bumpIndex') - SLAB_HEADER_LAYOUT.span, + * ), + * 'nodes', + * ), + * ]); + *

+ *

+ * slab header layout: + * const SLAB_HEADER_LAYOUT = struct( + * [ + * // Number of modified slab nodes + * u32('bumpIndex'), 13-16 + * zeros(4), // Consider slabs with more than 2^32 nodes to be invalid 17-20 + *

+ * // Linked list of unused nodes + * u32('freeListLen'), 21-24 + * zeros(4), //25-28 + * u32('freeListHead'), 29-32 + *

+ * u32('root'), 33-36 + *

+ * u32('leafCount'), 37-40 + * zeros(4), 41-44. + * ], + * 'header', + * ); + *

+ * 45 - 48 = ??? = tag and then 68 bytes of data, for N number of times, where N = offset( + * * SLAB_HEADER_LAYOUT.layoutFor('bumpIndex'), + * * SLAB_HEADER_LAYOUT.offsetOf('bumpIndex') - SLAB_HEADER_LAYOUT.span, + * * ); + *

+ * 45-48 = tag 1, + * 49-116 = blob 1 + * 117-120 = tag 2 + * 121-188 = blob 2 + * 189-192 = tag 3 + * ... + *

+ * const SLAB_NODE_LAYOUT = union(u32('tag'), blob(68), 'node'); + * SLAB_NODE_LAYOUT.addVariant(0, struct([]), 'uninitialized'); + * SLAB_NODE_LAYOUT.addVariant( + * 1, + * struct([ + * // Only the first prefixLen high-order bits of key are meaningful + * u32('prefixLen'), + * u128('key'), + * seq(u32(), 2, 'children'), + * ]), + * 'innerNode', + * ); + *

+ * .... + */ +// Takes in bytes following an AccountFlag object. +public class MangoSlab { + + private static final int INT32_SIZE = 4; + + // Offsets. TODO put these in their own file + // STARTS at 13, since accountflags from the orderbook struct ends there. TODO - refactor this into something sensible + + private static final int BUMP_INDEX_OFFSET = 13; + private static final int FREE_LIST_LEN_OFFSET = 21; + private static final int FREE_LIST_HEAD_OFFSET = 29; + private static final int ROOT_OFFSET = 33; + private static final int LEAF_COUNT_OFFSET = 37; + private static final int SLAB_NODE_OFFSET = 45; + + private int bumpIndex; + private int freeListLen; + private int freeListHead; // memory address? + private int root; + private int leafCount; + private ArrayList MangoSlabNodes; + + public static MangoSlab readOrderBookSlab(byte[] data) { + final MangoSlab mangoSlab = new MangoSlab(); + + int bumpIndex = mangoSlab.readBumpIndex(data); + mangoSlab.setBumpIndex(bumpIndex); + + int freeListLen = mangoSlab.readFreeListLen(data); + mangoSlab.setFreeListLen(freeListLen); + + int freeListHead = mangoSlab.readFreeListHead(data); + mangoSlab.setFreeListHead(freeListHead); + + int root = mangoSlab.readRoot(data); + mangoSlab.setRoot(root); + + int leafCount = mangoSlab.readLeafcount(data); + mangoSlab.setLeafCount(leafCount); + + ArrayList MangoSlabNodes = new ArrayList<>(); + byte[] MangoSlabNodeBytes = ByteUtils.readBytes(data, SLAB_NODE_OFFSET, data.length - 45); + + // TODO - pass in the start of the MangoSlabNodes binary instead of start of entire binary + MangoSlabNodes = mangoSlab.readMangoSlabNodes(MangoSlabNodeBytes, bumpIndex); + mangoSlab.setMangoSlabNodes(MangoSlabNodes); + + return mangoSlab; + } + + /** + * [tag 4 bytes][blob 68 bytes] + * repeated for N times + * todo- add parameter N to this call + * + * @param data + * @return + */ + private ArrayList readMangoSlabNodes(byte[] data, int bumpIndex) { + ArrayList MangoSlabNodes = new ArrayList<>(); + + for (int i = 0; i < bumpIndex; i++) { + MangoSlabNodes.add(readMangoSlabNode(ByteUtils.readBytes(data, (72 * i), 72))); + } + + return MangoSlabNodes; + } + + public MangoSlabNode readMangoSlabNode(byte[] data) { + int tag = readInt32(ByteUtils.readBytes(data, 0, INT32_SIZE)); + byte[] blob1 = ByteUtils.readBytes(data, 4, 68); + MangoSlabNode MangoSlabNode; + + if (tag == 0) { +// System.out.println("tag 0 detected: uninitialized"); + MangoSlabNode = null; + } else if (tag == 1) { +// System.out.println("tag 1 detected: innernode"); + int prefixLen = readInt32(ByteUtils.readBytes(blob1, 0, INT32_SIZE)); +// System.out.println("prefixLen = " + prefixLen); + + // Only the first prefixLen high-order bits of key are meaningful\ + int numBytesToRead = (int) Math.ceil(prefixLen / 4.00); +// System.out.println("size of key (in bytes) = " + numBytesToRead); + + byte[] key = ByteUtils.readBytes(blob1, 4, numBytesToRead); +// System.out.println("key = " + new String(key)); + + int child1 = readInt32(ByteUtils.readBytes(blob1, 20, 4)); +// System.out.println("child1 = " + child1); + + int child2 = readInt32(ByteUtils.readBytes(blob1, 24, 4)); +// System.out.println("child2 = " + child2); + + MangoSlabNode = new MangoSlabInnerNode(prefixLen, key, child1, child2); + } else if (tag == 2) { +// System.out.println("tag 2 detected: leafnode"); + byte ownerSlot = ByteUtils.readBytes(blob1, 0, 1)[0]; +// System.out.println("ownerSlot = " + ownerSlot); + byte feeTier = ByteUtils.readBytes(blob1, 1, 1)[0]; +// System.out.println("feeTier = " + feeTier); + // 2 empty bytes + + // "(price, seqNum)" + // key starts at byte 4, u128. u128 = 128 bits = 16 * 8 + byte[] key = ByteUtils.readBytes(blob1, 4, 16); +// System.out.println("key = " + new String(key)); + long seqNum = Utils.readInt64(key, 0); + long price = Utils.readInt64(key, 8); + +// System.out.println("price = " + price); + + + // Open orders account + PublicKey owner = PublicKey.readPubkey(blob1, 20); +// System.out.println("owner = " + owner.toBase58()); + + // In units of lot size + long quantity = Utils.readInt64(blob1, 52); +// System.out.println("quantity = " + quantity); + + long clientOrderId = Utils.readInt64(blob1, 60); +// System.out.println("clientOrderId = " + clientOrderId); + + MangoSlabNode = new MangoSlabLeafNode(ownerSlot, feeTier, key, owner, quantity, clientOrderId, price); + } else if (tag == 3) { +// System.out.println("tag 3 detected: freenode"); + int next = readInt32(ByteUtils.readBytes(blob1, 0, 4)); +// System.out.println("next = " + next); + + MangoSlabNode = new MangoSlabInnerNode(); + } else if (tag == 4) { +// System.out.println("tag 4 detected: lastfreenode"); + MangoSlabNode = null; + } else { + throw new RuntimeException("unknown tag detected during slab deserialization = " + tag); + } + +// System.out.println(); + + return MangoSlabNode; + } + + private String getTagType(int tag) { + if (tag == 1) { + return "innerNode"; + } else { + return "unknown"; + } + } + + public int getLeafCount() { + return leafCount; + } + + public void setLeafCount(int leafCount) { + this.leafCount = leafCount; + } + + private int readLeafcount(byte[] data) { + final byte[] bumpIndexBytes = ByteUtils.readBytes(data, LEAF_COUNT_OFFSET, INT32_SIZE); + + return readInt32(bumpIndexBytes); + } + + public int getRoot() { + return root; + } + + public void setRoot(int root) { + this.root = root; + } + + private int readRoot(byte[] data) { + final byte[] bumpIndexBytes = ByteUtils.readBytes(data, ROOT_OFFSET, INT32_SIZE); + + return readInt32(bumpIndexBytes); + } + + public int getFreeListHead() { + return freeListHead; + } + + public void setFreeListHead(int freeListHead) { + this.freeListHead = freeListHead; + } + + private int readFreeListHead(byte[] data) { + final byte[] bumpIndexBytes = ByteUtils.readBytes(data, FREE_LIST_HEAD_OFFSET, INT32_SIZE); + + return readInt32(bumpIndexBytes); + } + + private int readFreeListLen(byte[] data) { + final byte[] bumpIndexBytes = ByteUtils.readBytes(data, FREE_LIST_LEN_OFFSET, INT32_SIZE); + + return readInt32(bumpIndexBytes); + } + + public int getFreeListLen() { + return freeListLen; + } + + public void setFreeListLen(int freeListLen) { + this.freeListLen = freeListLen; + } + + public int getBumpIndex() { + return bumpIndex; + } + + public void setBumpIndex(int bumpIndex) { + this.bumpIndex = bumpIndex; + } + + private int readBumpIndex(byte[] data) { + final byte[] bumpIndexBytes = ByteUtils.readBytes(data, BUMP_INDEX_OFFSET, INT32_SIZE); + + return readInt32(bumpIndexBytes); + } + + public int readInt32(byte[] data) { + // convert 4 bytes into an int. + + // create a byte buffer and wrap the array + ByteBuffer bb = ByteBuffer.wrap(data); + + // if the file uses little endian as apposed to network + // (big endian, Java's native) format, + // then set the byte order of the ByteBuffer + bb.order(ByteOrder.LITTLE_ENDIAN); + + // read your integers using ByteBuffer's getInt(). + // four bytes converted into an integer! + return bb.getInt(0); + } + + public ArrayList getMangoSlabNodes() { + return MangoSlabNodes; + } + + public void setMangoSlabNodes(ArrayList MangoSlabNodes) { + this.MangoSlabNodes = MangoSlabNodes; + } +} diff --git a/mango/src/main/java/com/mmorrell/mango/model/MangoSlabInnerNode.java b/mango/src/main/java/com/mmorrell/mango/model/MangoSlabInnerNode.java new file mode 100644 index 0000000..6f0ccbd --- /dev/null +++ b/mango/src/main/java/com/mmorrell/mango/model/MangoSlabInnerNode.java @@ -0,0 +1,63 @@ +package com.mmorrell.mango.model; + +import java.util.Arrays; + +public class MangoSlabInnerNode extends MangoSlabNode { + + int prefixLen; + byte[] key; + int child1; + int child2; + + public MangoSlabInnerNode() { + } + + public MangoSlabInnerNode(int prefixLen, byte[] key, int child1, int child2) { + this.prefixLen = prefixLen; + this.key = key; + this.child1 = child1; + this.child2 = child2; + } + + public int getPrefixLen() { + return prefixLen; + } + + public void setPrefixLen(int prefixLen) { + this.prefixLen = prefixLen; + } + + public byte[] getKey() { + return key; + } + + public void setKey(byte[] key) { + this.key = key; + } + + public int getChild1() { + return child1; + } + + public void setChild1(int child1) { + this.child1 = child1; + } + + public int getChild2() { + return child2; + } + + public void setChild2(int child2) { + this.child2 = child2; + } + + @Override + public String toString() { + return "SlabInnerNode{" + + "prefixLen=" + prefixLen + + ", key=" + Arrays.toString(key) + + ", child1=" + child1 + + ", child2=" + child2 + + '}'; + } +} diff --git a/mango/src/main/java/com/mmorrell/mango/model/MangoSlabLeafNode.java b/mango/src/main/java/com/mmorrell/mango/model/MangoSlabLeafNode.java new file mode 100644 index 0000000..4213b56 --- /dev/null +++ b/mango/src/main/java/com/mmorrell/mango/model/MangoSlabLeafNode.java @@ -0,0 +1,95 @@ +package com.mmorrell.mango.model; + +import org.p2p.solanaj.core.PublicKey; + +import java.util.Arrays; + +public class MangoSlabLeafNode extends MangoSlabNode { + + private byte ownerSlot; + private byte feeTier; + private byte[] key; + private PublicKey owner; + private long quantity; + private long clientOrderId; + private long price; + + public MangoSlabLeafNode(byte ownerSlot, byte feeTier, byte[] key, PublicKey owner, long quantity, long clientOrderId, long price) { + this.ownerSlot = ownerSlot; + this.feeTier = feeTier; + this.key = key; + this.owner = owner; + this.quantity = quantity; + this.clientOrderId = clientOrderId; + this.price = price; + } + + public byte getOwnerSlot() { + return ownerSlot; + } + + public void setOwnerSlot(byte ownerSlot) { + this.ownerSlot = ownerSlot; + } + + public byte getFeeTier() { + return feeTier; + } + + public void setFeeTier(byte feeTier) { + this.feeTier = feeTier; + } + + public byte[] getKey() { + return key; + } + + public void setKey(byte[] key) { + this.key = key; + } + + public PublicKey getOwner() { + return owner; + } + + public void setOwner(PublicKey owner) { + this.owner = owner; + } + + public long getQuantity() { + return quantity; + } + + public void setQuantity(long quantity) { + this.quantity = quantity; + } + + public long getClientOrderId() { + return clientOrderId; + } + + public void setClientOrderId(long clientOrderId) { + this.clientOrderId = clientOrderId; + } + + public long getPrice() { + return price; + } + + public void setPrice(long price) { + this.price = price; + } + + @Override + public String toString() { + return "SlabLeafNode{" + + "ownerSlot=" + ownerSlot + + ", feeTier=" + feeTier + + ", key=" + Arrays.toString(key) + + ", owner=" + owner + + ", quantity=" + quantity + + ", clientOrderId=" + clientOrderId + + ", price=" + price + + '}'; + } +} diff --git a/mango/src/main/java/com/mmorrell/mango/model/MangoSlabNode.java b/mango/src/main/java/com/mmorrell/mango/model/MangoSlabNode.java new file mode 100644 index 0000000..a894462 --- /dev/null +++ b/mango/src/main/java/com/mmorrell/mango/model/MangoSlabNode.java @@ -0,0 +1,34 @@ +package com.mmorrell.mango.model; + +public abstract class MangoSlabNode { + + public MangoSlabNode() { + } + + // first 4 bytes + private int tag; + // bytes 5-72 + private byte[] blob; + + // TODO add getters for variants of the blob or make this an interface + + /** + * returns the variant of this slabnode. 5 possible values [uninitialized (0), innerNode(1), leafNode(2), freeNode(3), lastFreeNode(4)); + * @return variant of the slabNode + */ + public int getTag() { + return tag; + } + + public void setTag(int tag) { + this.tag = tag; + } + + public byte[] getBlob() { + return blob; + } + + public void setBlob(byte[] blob) { + this.blob = blob; + } +} diff --git a/mango/src/main/java/com/mmorrell/mango/model/MangoUtils.java b/mango/src/main/java/com/mmorrell/mango/model/MangoUtils.java index 9754cdc..f8e943c 100644 --- a/mango/src/main/java/com/mmorrell/mango/model/MangoUtils.java +++ b/mango/src/main/java/com/mmorrell/mango/model/MangoUtils.java @@ -5,4 +5,35 @@ public class MangoUtils { public static final int ACCOUNT_FLAGS_SIZE_BYTES = 8; public static final int U64_SIZE_BYTES = 8; + public static double getBaseSplTokenMultiplier(byte baseDecimals) { + return Math.pow(10, baseDecimals); + } + + public static double getQuoteSplTokenMultiplier(byte quoteDecimals) { + return Math.pow(10, quoteDecimals); + } + + public static float priceLotsToNumber(long price, byte baseDecimals, byte quoteDecimals, long baseLotSize, long quoteLotSize) { + double top = (price * quoteLotSize * getBaseSplTokenMultiplier(baseDecimals)); + double bottom = (baseLotSize * getQuoteSplTokenMultiplier(quoteDecimals)); + + return (float) (top / bottom); + } + + public static long priceNumberToLots(float price, byte quoteDecimals, long baseLotSize, byte baseDecimals, long quoteLotSize) { + double top = (price * Math.pow(10, quoteDecimals) * baseLotSize); + double bottom = Math.pow(10, baseDecimals) * quoteLotSize; + return (long) Math.ceil(top / bottom); + } + + public static float baseSizeLotsToNumber(long size, long baseLotSize, long baseMultiplier) { + double top = size * baseLotSize; + return (float) (top / baseMultiplier); + } + + public static long baseSizeNumberToLots(float size, byte baseDecimals, long baseLotSize) { + double top = Math.round(size * Math.pow(10, baseDecimals)); + return (long) Math.ceil(top / baseLotSize); + } + } diff --git a/mango/src/test/java/MangoTest.java b/mango/src/test/java/MangoTest.java index 31da754..f81768b 100644 --- a/mango/src/test/java/MangoTest.java +++ b/mango/src/test/java/MangoTest.java @@ -306,4 +306,13 @@ public void pubkeyReadTest() { PublicKey pubkey2 = PublicKey.readPubkey(rawData2, 0); LOGGER.info("Pubkey2 = " + pubkey2.toBase58()); } + + @Test + public void mangoV3PerpMarketTest() throws RpcException { + PublicKey mngoUsdcPerp = new PublicKey("4nfmQP3KmUqEJ6qJLsS3offKgE96YUB4Rp7UQvm2Fbi9"); + byte[] data = Base64.getDecoder().decode(client.getApi().getAccountInfo(mngoUsdcPerp).getValue().getData().get(0)); + + MangoPerpMarket mngoUsdcPerpMarket = MangoPerpMarket.readMangoPerpMarket(data); + LOGGER.info("mngoUsdcPerpMarket = " + mngoUsdcPerpMarket.toString()); + } }