Replace raw endpoints with Client and scaffold remaining market functions (#34)
* Bump solana to 0.3.1 * Use client instead of raw endpoint * Use TOKEN_PROGRAM_ID from spl.constants * Scaffold unimplemented functions * Rename open_order to open_orders * Remove unnecessary code * Remove unused import * Add market client
This commit is contained in:
parent
e2351f5134
commit
9abb0ca3a1
|
@ -96,22 +96,24 @@
|
||||||
},
|
},
|
||||||
"pynacl": {
|
"pynacl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4",
|
"sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420",
|
||||||
"sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4",
|
"sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4",
|
||||||
"sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574",
|
"sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6",
|
||||||
"sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d",
|
"sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6",
|
||||||
"sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25",
|
"sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7",
|
||||||
"sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f",
|
|
||||||
"sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505",
|
"sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505",
|
||||||
"sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122",
|
"sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122",
|
||||||
"sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7",
|
"sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4",
|
||||||
"sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420",
|
"sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f",
|
||||||
"sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f",
|
|
||||||
"sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96",
|
|
||||||
"sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6",
|
|
||||||
"sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514",
|
"sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514",
|
||||||
"sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff",
|
"sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff",
|
||||||
"sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"
|
"sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96",
|
||||||
|
"sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80",
|
||||||
|
"sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25",
|
||||||
|
"sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634",
|
||||||
|
"sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d",
|
||||||
|
"sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f",
|
||||||
|
"sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==1.4.0"
|
"version": "==1.4.0"
|
||||||
|
@ -134,11 +136,11 @@
|
||||||
},
|
},
|
||||||
"solana": {
|
"solana": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0ccb0696272ed91d9eff92a8b4840816d8a9383c3d7665eda591386560c54f70",
|
"sha256:0bb866b3a046ad41f06ad9ad94b7ee9f7e2209c20c038c240f15b17ffd6c97bc",
|
||||||
"sha256:f607d4f66382bf37a76efed5b55e119842b65def23d1c5fe441619d04170e2df"
|
"sha256:f9dd7391e628e1bf7c611cf4445936c84f03ec7551db7f400ef68a5fc2c8cfea"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.2.0"
|
"version": "==0.3.1"
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -227,19 +229,18 @@
|
||||||
},
|
},
|
||||||
"black": {
|
"black": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea",
|
"sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
|
||||||
"sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==20.8b1"
|
"version": "==20.8b1"
|
||||||
},
|
},
|
||||||
"bleach": {
|
"bleach": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f",
|
"sha256:769483204d247465c0b001ead257fb86bba6944bce6fe1b6759c812cceb54e3d",
|
||||||
"sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"
|
"sha256:f9e0205cc57b558c21bdfc11034f9d96b14c4052c25be60885d94f4277c792e0"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
"version": "==3.1.5"
|
"version": "==3.2.0"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -429,11 +430,11 @@
|
||||||
},
|
},
|
||||||
"jupyterlab": {
|
"jupyterlab": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a0a1882456098d2fab4c241a0b16a1df96c36de1c45bddbf5fc40867e3d9340e",
|
"sha256:95d0509557881cfa8a5fcdf225f2fca46faf1bc52fc56a28e0b72fcc594c90ab",
|
||||||
"sha256:a72ffd0d919cba03a5ef8422bc92c3332a957ff97b0490494209c83ad93826da"
|
"sha256:c8377bee30504919c1e79949f9fe35443ab7f5c4be622c95307e8108410c8b8c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.2.7"
|
"version": "==2.2.8"
|
||||||
},
|
},
|
||||||
"jupyterlab-pygments": {
|
"jupyterlab-pygments": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -575,11 +576,11 @@
|
||||||
},
|
},
|
||||||
"nbconvert": {
|
"nbconvert": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:970122eaf3a3ddcfe4e03514b219df4be4af09e70c748faf6ba96f51a25fd09b",
|
"sha256:06c64fd45d4b6424e88eb3bf7e5eb205a0fc8a4c0a69666f0b9a2262c76f59e1",
|
||||||
"sha256:db94117fbac29153834447e31b30cda337d4450e46e0bdb1a36eafbbf4435156"
|
"sha256:d8490f40368a1324521f8e740a0e341dc40bcd6e6926da64fa64b3a8801f16a3"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==6.0.1"
|
"version": "==6.0.3"
|
||||||
},
|
},
|
||||||
"nbformat": {
|
"nbformat": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -722,11 +723,11 @@
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
|
"sha256:2594e8fdb06fef91552f86f4fd3a244d148ab24b66042036e64f29a291515048",
|
||||||
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
|
"sha256:2df50d16b45b977217e02cba6c8422aaddb859f3d0570a88e09b00eafae89c6e"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5'",
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==2.6.1"
|
"version": "==2.7.0"
|
||||||
},
|
},
|
||||||
"pylint": {
|
"pylint": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -746,17 +747,18 @@
|
||||||
},
|
},
|
||||||
"pyrsistent": {
|
"pyrsistent": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"
|
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
|
||||||
],
|
],
|
||||||
"version": "==0.16.0"
|
"markers": "python_version >= '3.5'",
|
||||||
|
"version": "==0.17.3"
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4",
|
"sha256:0e37f61339c4578776e090c3b8f6b16ce4db333889d65d0efb305243ec544b40",
|
||||||
"sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"
|
"sha256:c8f57c2a30983f469bf03e68cdfa74dc474ce56b8f280ddcb080dfd91df01043"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==6.0.1"
|
"version": "==6.0.2"
|
||||||
},
|
},
|
||||||
"pytest-tornasync": {
|
"pytest-tornasync": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
from solana.rpc.api import Client
|
||||||
|
|
||||||
|
|
||||||
|
def market_client(endpoint: str) -> Client:
|
||||||
|
"""RPC client to interact with the Serum Dex."""
|
||||||
|
return Client(endpoint)
|
|
@ -5,12 +5,12 @@ from solana.publickey import PublicKey
|
||||||
from solana.sysvar import SYSVAR_RENT_PUBKEY
|
from solana.sysvar import SYSVAR_RENT_PUBKEY
|
||||||
from solana.transaction import AccountMeta, TransactionInstruction
|
from solana.transaction import AccountMeta, TransactionInstruction
|
||||||
from solana.utils.validate import validate_instruction_keys, validate_instruction_type
|
from solana.utils.validate import validate_instruction_keys, validate_instruction_type
|
||||||
|
from spl.token.constants import TOKEN_PROGRAM_ID # type: ignore # TODO: Fix and remove ignore.
|
||||||
|
|
||||||
from ._layouts.instructions import INSTRUCTIONS_LAYOUT, InstructionType
|
from ._layouts.instructions import INSTRUCTIONS_LAYOUT, InstructionType
|
||||||
from .enums import OrderType, Side
|
from .enums import OrderType, Side
|
||||||
|
|
||||||
DEFAULT_DEX_PROGRAM_ID = PublicKey("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn")
|
DEFAULT_DEX_PROGRAM_ID = PublicKey("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn")
|
||||||
TOKEN_PROGRAM_ID = PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
|
||||||
|
|
||||||
|
|
||||||
class InitializeMarketParams(NamedTuple):
|
class InitializeMarketParams(NamedTuple):
|
||||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
||||||
import math
|
import math
|
||||||
from typing import Any, Iterable, List, NamedTuple
|
from typing import Any, Iterable, List, NamedTuple
|
||||||
|
|
||||||
|
from construct import Struct as cStruct # type: ignore
|
||||||
from solana.account import Account
|
from solana.account import Account
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
from solana.rpc.api import Client
|
from solana.rpc.api import Client
|
||||||
|
@ -19,7 +20,7 @@ from .instructions import DEFAULT_DEX_PROGRAM_ID, CancelOrderParams, MatchOrders
|
||||||
from .instructions import cancel_order as cancel_order_inst
|
from .instructions import cancel_order as cancel_order_inst
|
||||||
from .instructions import match_orders as match_order_inst
|
from .instructions import match_orders as match_order_inst
|
||||||
from .instructions import new_order as new_order_inst
|
from .instructions import new_order as new_order_inst
|
||||||
from .open_order_account import OpenOrderAccount, make_create_account_instruction
|
from .open_orders_account import OpenOrdersAccount, make_create_account_instruction
|
||||||
from .queue_ import decode_event_queue, decode_request_queue
|
from .queue_ import decode_event_queue, decode_request_queue
|
||||||
from .utils import load_bytes_data
|
from .utils import load_bytes_data
|
||||||
|
|
||||||
|
@ -40,11 +41,11 @@ class Market:
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
decoded: Any,
|
decoded: Any, # Construct structure of the market.
|
||||||
base_mint_decimals: int,
|
base_mint_decimals: int,
|
||||||
quote_mint_decimals: int,
|
quote_mint_decimals: int,
|
||||||
options: Any, # pylint: disable=unused-argument
|
options: Any, # pylint: disable=unused-argument
|
||||||
endpoint: str,
|
conn: Client,
|
||||||
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID,
|
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID,
|
||||||
) -> None:
|
) -> None:
|
||||||
# TODO: add options
|
# TODO: add options
|
||||||
|
@ -56,25 +57,28 @@ class Market:
|
||||||
self._skip_preflight = False
|
self._skip_preflight = False
|
||||||
self._confirmations = 10
|
self._confirmations = 10
|
||||||
self._program_id = program_id
|
self._program_id = program_id
|
||||||
self._endpoint = endpoint
|
self._conn = conn
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def LAYOUT() -> cStruct: # pylint: disable=invalid-name
|
||||||
|
"""Construct layout of the market state."""
|
||||||
|
return MARKET_LAYOUT
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def load(
|
def load(conn: Client, market_address: str, options: Any, program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID) -> Market:
|
||||||
endpoint: str, market_address: str, options: Any, program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
|
|
||||||
) -> Market:
|
|
||||||
"""Factory method to create a Market."""
|
"""Factory method to create a Market."""
|
||||||
bytes_data = load_bytes_data(PublicKey(market_address), endpoint)
|
bytes_data = load_bytes_data(PublicKey(market_address), conn)
|
||||||
market_state = MARKET_LAYOUT.parse(bytes_data)
|
market_state = MARKET_LAYOUT.parse(bytes_data)
|
||||||
|
|
||||||
# TODO: add ownAddress check!
|
# TODO: add ownAddress check!
|
||||||
if not market_state.account_flags.initialized or not market_state.account_flags.market:
|
if not market_state.account_flags.initialized or not market_state.account_flags.market:
|
||||||
raise Exception("Invalid market")
|
raise Exception("Invalid market")
|
||||||
|
|
||||||
base_mint_decimals = Market.get_mint_decimals(endpoint, PublicKey(market_state.base_mint))
|
base_mint_decimals = Market.get_mint_decimals(conn, PublicKey(market_state.base_mint))
|
||||||
quote_mint_decimals = Market.get_mint_decimals(endpoint, PublicKey(market_state.quote_mint))
|
quote_mint_decimals = Market.get_mint_decimals(conn, PublicKey(market_state.quote_mint))
|
||||||
|
|
||||||
return Market(market_state, base_mint_decimals, quote_mint_decimals, options, endpoint, program_id=program_id)
|
return Market(market_state, base_mint_decimals, quote_mint_decimals, options, conn, program_id=program_id)
|
||||||
|
|
||||||
def address(self) -> PublicKey:
|
def address(self) -> PublicKey:
|
||||||
"""Return market address."""
|
"""Return market address."""
|
||||||
|
@ -107,7 +111,7 @@ class Market:
|
||||||
return PublicKey(self._decode.request_queue)
|
return PublicKey(self._decode.request_queue)
|
||||||
|
|
||||||
def event_queue(self) -> PublicKey:
|
def event_queue(self) -> PublicKey:
|
||||||
"""Returns quote vault address."""
|
"""Returns event queue address."""
|
||||||
return PublicKey(self._decode.event_queue)
|
return PublicKey(self._decode.event_queue)
|
||||||
|
|
||||||
def __base_spl_token_multiplier(self) -> int:
|
def __base_spl_token_multiplier(self) -> int:
|
||||||
|
@ -142,36 +146,54 @@ class Market:
|
||||||
return int(math.floor(size * 10 ** self._base_spl_token_decimals) / self._decode.base_lot_size)
|
return int(math.floor(size * 10 ** self._base_spl_token_decimals) / self._decode.base_lot_size)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_mint_decimals(endpoint: str, mint_pub_key: PublicKey) -> int:
|
def get_mint_decimals(conn: Client, mint_pub_key: PublicKey) -> int:
|
||||||
"""Get the mint decimals from given public key."""
|
"""Get the mint decimals from given public key."""
|
||||||
bytes_data = load_bytes_data(mint_pub_key, endpoint)
|
bytes_data = load_bytes_data(mint_pub_key, conn)
|
||||||
return MINT_LAYOUT.parse(bytes_data).decimals
|
return MINT_LAYOUT.parse(bytes_data).decimals
|
||||||
|
|
||||||
|
def bids_address(self) -> PublicKey:
|
||||||
|
return PublicKey(self._decode.bids)
|
||||||
|
|
||||||
|
def asks_address(self) -> PublicKey:
|
||||||
|
return PublicKey(self._decode.asks)
|
||||||
|
|
||||||
|
def find_open_orders_accounts_for_owner(self, owner_address: PublicKey) -> List[OpenOrdersAccount]:
|
||||||
|
return OpenOrdersAccount.find_for_market_and_owner(self._conn, self.address(), owner_address, self._program_id)
|
||||||
|
|
||||||
|
def find_quote_token_accounts_for_owner(self, owner_address: PublicKey, include_unwrapped_sol: bool = False):
|
||||||
|
raise NotImplementedError("find_quote_token_accounts_for_owner not implemented.")
|
||||||
|
|
||||||
def load_bids(self) -> OrderBook:
|
def load_bids(self) -> OrderBook:
|
||||||
"""Load the bid order book"""
|
"""Load the bid order book"""
|
||||||
bids_addr = PublicKey(self._decode.bids)
|
bids_addr = PublicKey(self._decode.bids)
|
||||||
bytes_data = load_bytes_data(bids_addr, self._endpoint)
|
bytes_data = load_bytes_data(bids_addr, self._conn)
|
||||||
return OrderBook.decode(self, bytes_data)
|
return OrderBook.decode(self, bytes_data)
|
||||||
|
|
||||||
def load_asks(self) -> OrderBook:
|
def load_asks(self) -> OrderBook:
|
||||||
"""Load the Ask order book."""
|
"""Load the Ask order book."""
|
||||||
asks_addr = PublicKey(self._decode.asks)
|
asks_addr = PublicKey(self._decode.asks)
|
||||||
bytes_data = load_bytes_data(asks_addr, self._endpoint)
|
bytes_data = load_bytes_data(asks_addr, self._conn)
|
||||||
return OrderBook.decode(self, bytes_data)
|
return OrderBook.decode(self, bytes_data)
|
||||||
|
|
||||||
|
def load_orders_for_owner(self) -> List[Order]:
|
||||||
|
raise NotImplementedError("load_orders_for_owner not implemented.")
|
||||||
|
|
||||||
|
def load_base_token_for_owner(self):
|
||||||
|
raise NotImplementedError("load_base_token_for_owner not implemented.")
|
||||||
|
|
||||||
def load_event_queue(self): # returns raw construct type
|
def load_event_queue(self): # returns raw construct type
|
||||||
event_queue_addr = PublicKey(self._decode.event_queue)
|
event_queue_addr = PublicKey(self._decode.event_queue)
|
||||||
bytes_data = load_bytes_data(event_queue_addr, self._endpoint)
|
bytes_data = load_bytes_data(event_queue_addr, self._conn)
|
||||||
return decode_event_queue(bytes_data)
|
return decode_event_queue(bytes_data)
|
||||||
|
|
||||||
def load_request_queue(self): # returns raw construct type
|
def load_request_queue(self): # returns raw construct type
|
||||||
request_queue_addr = PublicKey(self._decode.request_queue)
|
request_queue_addr = PublicKey(self._decode.request_queue)
|
||||||
bytes_data = load_bytes_data(request_queue_addr, self._endpoint)
|
bytes_data = load_bytes_data(request_queue_addr, self._conn)
|
||||||
return decode_request_queue(bytes_data)
|
return decode_request_queue(bytes_data)
|
||||||
|
|
||||||
def load_fills(self, limit=100) -> List[FilledOrder]:
|
def load_fills(self, limit=100) -> List[FilledOrder]:
|
||||||
event_queue_addr = PublicKey(self._decode.event_queue)
|
event_queue_addr = PublicKey(self._decode.event_queue)
|
||||||
bytes_data = load_bytes_data(event_queue_addr, self._endpoint)
|
bytes_data = load_bytes_data(event_queue_addr, self._conn)
|
||||||
events = decode_event_queue(bytes_data, limit)
|
events = decode_event_queue(bytes_data, limit)
|
||||||
return [
|
return [
|
||||||
self.parse_fill_event(event)
|
self.parse_fill_event(event)
|
||||||
|
@ -216,24 +238,27 @@ class Market:
|
||||||
limit_price: int,
|
limit_price: int,
|
||||||
max_quantity: int,
|
max_quantity: int,
|
||||||
client_id: int = 0,
|
client_id: int = 0,
|
||||||
):
|
): # TODO: Add open_orders_address_key param
|
||||||
transaction = Transaction()
|
transaction = Transaction()
|
||||||
signers: List[Account] = [owner]
|
signers: List[Account] = [owner]
|
||||||
open_order_accounts = self.find_open_orders_accounts_for_owner(owner.public_key())
|
open_order_accounts = self.find_open_orders_accounts_for_owner(owner.public_key())
|
||||||
if not open_order_accounts:
|
if not open_order_accounts:
|
||||||
new_open_order_account = Account()
|
new_open_order_account = Account()
|
||||||
|
mbfre_resp = self._conn.get_minimum_balance_for_rent_exemption(OPEN_ORDERS_LAYOUT.sizeof())
|
||||||
|
balanced_needed = mbfre_resp["result"]
|
||||||
transaction.add(
|
transaction.add(
|
||||||
make_create_account_instruction(
|
make_create_account_instruction(
|
||||||
owner.public_key(),
|
owner.public_key(),
|
||||||
new_open_order_account.public_key(),
|
new_open_order_account.public_key(),
|
||||||
Client(self._endpoint).get_minimum_balance_for_rent_exemption(OPEN_ORDERS_LAYOUT.sizeof())[
|
balanced_needed,
|
||||||
"result"
|
|
||||||
],
|
|
||||||
self._program_id,
|
self._program_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
signers.append(new_open_order_account)
|
signers.append(new_open_order_account)
|
||||||
|
|
||||||
|
# TODO: Handle open_orders_address_key
|
||||||
|
# TODO: Handle wrapped sol account
|
||||||
|
|
||||||
transaction.add(
|
transaction.add(
|
||||||
self.make_place_order_instruction(
|
self.make_place_order_instruction(
|
||||||
payer,
|
payer,
|
||||||
|
@ -281,23 +306,16 @@ class Market:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def find_open_orders_accounts_for_owner(self, owner_address: PublicKey) -> List[OpenOrderAccount]:
|
|
||||||
return OpenOrderAccount.find_for_market_and_owner(
|
|
||||||
self._endpoint, self.address(), owner_address, self._program_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def cancel_order_by_client_id(self, owner: str) -> str:
|
def cancel_order_by_client_id(self, owner: str) -> str:
|
||||||
pass
|
raise NotImplementedError("cancel_order_by_client_id not implemented.")
|
||||||
|
|
||||||
def cancel_order(self, owner: Account, order: Order) -> str:
|
def cancel_order(self, owner: Account, order: Order) -> str:
|
||||||
transaction = Transaction()
|
txn = Transaction().add(self.make_cancel_order_instruction(owner.public_key(), order))
|
||||||
transaction.add(self.make_cancel_order_instruction(owner.public_key(), order))
|
return self._send_transaction(txn, owner)
|
||||||
return self._send_transaction(transaction, owner)
|
|
||||||
|
|
||||||
def match_orders(self, fee_payer: Account, limit: int) -> str:
|
def match_orders(self, fee_payer: Account, limit: int) -> str:
|
||||||
transaction = Transaction()
|
txn = Transaction().add(self.make_match_orders_instruction(limit))
|
||||||
transaction.add(self.make_match_orders_instruction(limit))
|
return self._send_transaction(txn, fee_payer)
|
||||||
return self._send_transaction(transaction, fee_payer)
|
|
||||||
|
|
||||||
def make_cancel_order_instruction(self, owner: PublicKey, order: Order) -> TransactionInstruction:
|
def make_cancel_order_instruction(self, owner: PublicKey, order: Order) -> TransactionInstruction:
|
||||||
params = CancelOrderParams(
|
params = CancelOrderParams(
|
||||||
|
@ -326,9 +344,13 @@ class Market:
|
||||||
)
|
)
|
||||||
return match_order_inst(params)
|
return match_order_inst(params)
|
||||||
|
|
||||||
|
def settle_funds(
|
||||||
|
self, owner: Account, open_orders: OpenOrdersAccount, base_wallet: PublicKey, quote_wallet: PublicKey
|
||||||
|
) -> str:
|
||||||
|
raise NotImplementedError("settle_funds not implemented.")
|
||||||
|
|
||||||
def _send_transaction(self, transaction: Transaction, *signers: Account) -> str:
|
def _send_transaction(self, transaction: Transaction, *signers: Account) -> str:
|
||||||
connection = Client(self._endpoint)
|
res = self._conn.send_transaction(transaction, *signers, skip_preflight=self._skip_preflight)
|
||||||
res = connection.send_transaction(transaction, *signers, skip_preflight=self._skip_preflight)
|
|
||||||
if self._confirmations > 0:
|
if self._confirmations > 0:
|
||||||
self.logger.warning("Cannot confirm transaction yet.")
|
self.logger.warning("Cannot confirm transaction yet.")
|
||||||
signature = res.get("result")
|
signature = res.get("result")
|
||||||
|
|
|
@ -21,7 +21,7 @@ class ProgramAccount(NamedTuple):
|
||||||
owner: PublicKey
|
owner: PublicKey
|
||||||
|
|
||||||
|
|
||||||
class OpenOrderAccount:
|
class OpenOrdersAccount:
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -51,12 +51,12 @@ class OpenOrderAccount:
|
||||||
self.client_ids = client_ids
|
self.client_ids = client_ids
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bytes(address: PublicKey, data_bytes: bytes) -> OpenOrderAccount:
|
def from_bytes(address: PublicKey, data_bytes: bytes) -> OpenOrdersAccount:
|
||||||
open_order_decoded = OPEN_ORDERS_LAYOUT.parse(data_bytes)
|
open_order_decoded = OPEN_ORDERS_LAYOUT.parse(data_bytes)
|
||||||
if not open_order_decoded.account_flags.open_orders or not open_order_decoded.account_flags.initialized:
|
if not open_order_decoded.account_flags.open_orders or not open_order_decoded.account_flags.initialized:
|
||||||
raise Exception("Not an open order account or not initialized.")
|
raise Exception("Not an open order account or not initialized.")
|
||||||
|
|
||||||
return OpenOrderAccount(
|
return OpenOrdersAccount(
|
||||||
address=address,
|
address=address,
|
||||||
market=PublicKey(open_order_decoded.market),
|
market=PublicKey(open_order_decoded.market),
|
||||||
owner=PublicKey(open_order_decoded.owner),
|
owner=PublicKey(open_order_decoded.owner),
|
||||||
|
@ -72,8 +72,8 @@ class OpenOrderAccount:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_for_market_and_owner(
|
def find_for_market_and_owner(
|
||||||
endpoint: str, market: PublicKey, owner: PublicKey, program_id: PublicKey
|
conn: Client, market: PublicKey, owner: PublicKey, program_id: PublicKey
|
||||||
) -> List[OpenOrderAccount]:
|
) -> List[OpenOrdersAccount]:
|
||||||
filters = [
|
filters = [
|
||||||
MemcmpOpt(
|
MemcmpOpt(
|
||||||
offset=5 + 8, # 5 bytes of padding, 8 bytes of account flag
|
offset=5 + 8, # 5 bytes of padding, 8 bytes of account flag
|
||||||
|
@ -85,7 +85,7 @@ class OpenOrderAccount:
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
resp = Client(endpoint).get_program_accounts(
|
resp = conn.get_program_accounts(
|
||||||
program_id, encoding="base64", memcmp_opts=filters, data_size=OPEN_ORDERS_LAYOUT.sizeof()
|
program_id, encoding="base64", memcmp_opts=filters, data_size=OPEN_ORDERS_LAYOUT.sizeof()
|
||||||
)
|
)
|
||||||
accounts = []
|
accounts = []
|
||||||
|
@ -100,13 +100,13 @@ class OpenOrderAccount:
|
||||||
lamports=int(account_details["lamports"]),
|
lamports=int(account_details["lamports"]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return [OpenOrderAccount.from_bytes(account.public_key, account.data) for account in accounts]
|
return [OpenOrdersAccount.from_bytes(account.public_key, account.data) for account in accounts]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(endpoint: str, address: str) -> OpenOrderAccount:
|
def load(conn: Client, address: str) -> OpenOrdersAccount:
|
||||||
addr_pub_key = PublicKey(address)
|
addr_pub_key = PublicKey(address)
|
||||||
bytes_data = load_bytes_data(addr_pub_key, endpoint)
|
bytes_data = load_bytes_data(addr_pub_key, conn)
|
||||||
return OpenOrderAccount.from_bytes(addr_pub_key, bytes_data)
|
return OpenOrdersAccount.from_bytes(addr_pub_key, bytes_data)
|
||||||
|
|
||||||
|
|
||||||
def make_create_account_instruction(
|
def make_create_account_instruction(
|
|
@ -4,8 +4,8 @@ from solana.publickey import PublicKey
|
||||||
from solana.rpc.api import Client
|
from solana.rpc.api import Client
|
||||||
|
|
||||||
|
|
||||||
def load_bytes_data(addr: PublicKey, endpoint: str):
|
def load_bytes_data(addr: PublicKey, conn: Client):
|
||||||
res = Client(endpoint).get_account_info(addr)
|
res = conn.get_account_info(addr)
|
||||||
if ("result" not in res) or ("value" not in res["result"]) or ("data" not in res["result"]["value"]):
|
if ("result" not in res) or ("value" not in res["result"]) or ("data" not in res["result"]["value"]):
|
||||||
raise Exception("Cannot load byte data.")
|
raise Exception("Cannot load byte data.")
|
||||||
data = res["result"]["value"]["data"][0]
|
data = res["result"]["value"]["data"][0]
|
||||||
|
|
|
@ -5,13 +5,13 @@ from solana.account import Account
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
from solana.rpc.api import Client
|
from solana.rpc.api import Client
|
||||||
|
|
||||||
__cached_params = {}
|
from src.client import market_client
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration
|
@pytest.mark.integration
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def __bs_params() -> Dict[str, str]:
|
def __bs_params() -> Dict[str, str]:
|
||||||
if not __cached_params:
|
params = {}
|
||||||
with open("tests/crank.log") as crank_log:
|
with open("tests/crank.log") as crank_log:
|
||||||
for line in crank_log.readlines():
|
for line in crank_log.readlines():
|
||||||
if ":" not in line:
|
if ":" not in line:
|
||||||
|
@ -19,12 +19,12 @@ def __bs_params() -> Dict[str, str]:
|
||||||
key, val = line.strip().replace(",", "").split(": ")
|
key, val = line.strip().replace(",", "").split(": ")
|
||||||
assert key, "key must not be None"
|
assert key, "key must not be None"
|
||||||
assert val, "val must not be None"
|
assert val, "val must not be None"
|
||||||
__cached_params[key] = val
|
params[key] = val
|
||||||
return __cached_params
|
return params
|
||||||
|
|
||||||
|
|
||||||
def __bootstrap_account(pubkey: str, secret: str) -> Account:
|
def __bootstrap_account(pubkey: str, secretkey: str) -> Account:
|
||||||
secret = [int(b) for b in secret[1:-1].split(" ")]
|
secret = [int(b) for b in secretkey[1:-1].split(" ")]
|
||||||
account = Account(secret)
|
account = Account(secret)
|
||||||
assert str(account.public_key()) == pubkey, "account must map to provided public key"
|
assert str(account.public_key()) == pubkey, "account must map to provided public key"
|
||||||
return account
|
return account
|
||||||
|
@ -146,5 +146,7 @@ def stubbed_ask_account_pk(__bs_params) -> PublicKey:
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def http_client() -> Client:
|
def http_client() -> Client:
|
||||||
"""Solana http client."""
|
"""Solana http client."""
|
||||||
client = Client()
|
client = market_client("http://localhost:8899")
|
||||||
|
if not client.is_connected():
|
||||||
|
raise Exception("Could not connect to local node. Please run `make int-tests` to run integration tests.")
|
||||||
return client
|
return client
|
||||||
|
|
|
@ -11,9 +11,9 @@ from .utils import confirm_transaction
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration
|
@pytest.mark.integration
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="module")
|
||||||
def bootstrapped_market(stubbed_market_pk: PublicKey, stubbed_dex_program_pk: PublicKey) -> Market:
|
def bootstrapped_market(http_client: Client, stubbed_market_pk: PublicKey, stubbed_dex_program_pk: PublicKey) -> Market:
|
||||||
return Market.load("http://localhost:8899", str(stubbed_market_pk), None, program_id=stubbed_dex_program_pk)
|
return Market.load(http_client, str(stubbed_market_pk), None, stubbed_dex_program_pk)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration
|
@pytest.mark.integration
|
||||||
|
|
|
@ -1,14 +1,33 @@
|
||||||
import base64
|
import base64
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from solana.rpc.api import Client
|
||||||
|
|
||||||
from src.market import MARKET_LAYOUT, Market, Order, OrderBook
|
from src.market import MARKET_LAYOUT, Market, Order, OrderBook
|
||||||
|
|
||||||
from .binary_file_path import ASK_ORDER_BIN_PATH
|
from .binary_file_path import ASK_ORDER_BIN_PATH
|
||||||
|
|
||||||
MARKET_DATA_HEX = "736572756d030000000000000054f23cbc4d93795ce75ecd8173bcf436923112f7b6b024f3afd9b6789124b9680000000000000000c73690f1d4b87aa9337848369c20a682b37e8dcb33f4237a2a8f8b0abd64bd1cd9c75c9a58645ff02ab0741cd6d1790067957d2266165b767259b358ded270fb2dbc6d44f2ab58dce432b5bb31d98517366d6e24e69c0bf5b926ead9ec658935408ffd71c50000000000000000000000e1a461a046199877c4cd3cbafc61c3dfdb088e737b7193a3d28e72b709421fc4544fec927c000000a4fc4c12000000006400000000000000f422ea23ada9e1a9d100ba8443deb041c231e0f79ee6d07d1c1f7042fe4a1ade3b236ea4ba636227dfa22773f41fa02cc91842c2e9330e2ac0a987dc68b520e8c58676b5751c48e22c3bcc6edda8f75f76b1596b9874bd5714366e32d84e3bc0400501895361982c4be67d03af519ac7fd96a8a79f5b15ec7af79f6b70290bf740420f00000000001027000000000000000000000000000070616464696e67" # noqa: E501 # pylint: disable=line-too-long
|
|
||||||
DATA = bytes.fromhex(MARKET_DATA_HEX)
|
|
||||||
|
|
||||||
MARKET_ENCODE = SimpleNamespace(
|
@pytest.fixture(scope="module")
|
||||||
|
def stubbed_data() -> bytes:
|
||||||
|
MARKET_DATA_HEX = ( # pylint: disable=invalid-name
|
||||||
|
"736572756d030000000000000054f23cbc4d93795ce75ecd8173bcf436923112f7b6b024f3afd9b6789124b9680000000000"
|
||||||
|
"000000c73690f1d4b87aa9337848369c20a682b37e8dcb33f4237a2a8f8b0abd64bd1cd9c75c9a58645ff02ab0741cd6d179"
|
||||||
|
"0067957d2266165b767259b358ded270fb2dbc6d44f2ab58dce432b5bb31d98517366d6e24e69c0bf5b926ead9ec65893540"
|
||||||
|
"8ffd71c50000000000000000000000e1a461a046199877c4cd3cbafc61c3dfdb088e737b7193a3d28e72b709421fc4544fec"
|
||||||
|
"927c000000a4fc4c12000000006400000000000000f422ea23ada9e1a9d100ba8443deb041c231e0f79ee6d07d1c1f7042fe"
|
||||||
|
"4a1ade3b236ea4ba636227dfa22773f41fa02cc91842c2e9330e2ac0a987dc68b520e8c58676b5751c48e22c3bcc6edda8f7"
|
||||||
|
"5f76b1596b9874bd5714366e32d84e3bc0400501895361982c4be67d03af519ac7fd96a8a79f5b15ec7af79f6b70290bf740"
|
||||||
|
"420f00000000001027000000000000000000000000000070616464696e67"
|
||||||
|
)
|
||||||
|
return bytes.fromhex(MARKET_DATA_HEX)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def stubbed_market() -> Market:
|
||||||
|
conn = Client("http://stubbed_endpoint:123/")
|
||||||
|
MARKET_ENCODE = SimpleNamespace( # pylint: disable=invalid-name
|
||||||
**{
|
**{
|
||||||
"account_flags": SimpleNamespace(
|
"account_flags": SimpleNamespace(
|
||||||
**{
|
**{
|
||||||
|
@ -24,13 +43,12 @@ MARKET_ENCODE = SimpleNamespace(
|
||||||
"quote_lot_size": 10,
|
"quote_lot_size": 10,
|
||||||
"fee_rate_bps": 0,
|
"fee_rate_bps": 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
return Market(MARKET_ENCODE, 6, 6, None, conn)
|
||||||
BTC_USDC_MARKET = Market(MARKET_ENCODE, 6, 6, None, "http://stubbed_endpoint:123/")
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_market_state():
|
def test_parse_market_state(stubbed_data): # pylint: disable=redefined-outer-name
|
||||||
parsed_market = MARKET_LAYOUT.parse(DATA)
|
parsed_market = MARKET_LAYOUT.parse(stubbed_data)
|
||||||
assert parsed_market.account_flags.initialized
|
assert parsed_market.account_flags.initialized
|
||||||
assert parsed_market.account_flags.market
|
assert parsed_market.account_flags.market
|
||||||
assert not parsed_market.account_flags.open_orders
|
assert not parsed_market.account_flags.open_orders
|
||||||
|
@ -40,31 +58,31 @@ def test_parse_market_state():
|
||||||
assert parsed_market.fee_rate_bps == 0
|
assert parsed_market.fee_rate_bps == 0
|
||||||
|
|
||||||
|
|
||||||
def test_order_book_iterator():
|
def test_order_book_iterator(stubbed_market): # pylint: disable=redefined-outer-name
|
||||||
"""Test order book parsing."""
|
"""Test order book parsing."""
|
||||||
with open(ASK_ORDER_BIN_PATH, "r") as input_file:
|
with open(ASK_ORDER_BIN_PATH, "r") as input_file:
|
||||||
base64_res = input_file.read()
|
base64_res = input_file.read()
|
||||||
data = base64.decodebytes(base64_res.encode("ascii"))
|
data = base64.decodebytes(base64_res.encode("ascii"))
|
||||||
order_book = OrderBook.decode(BTC_USDC_MARKET, data)
|
order_book = OrderBook.decode(stubbed_market, data)
|
||||||
total_orders = sum([1 for _ in order_book.orders()])
|
total_orders = sum([1 for _ in order_book.orders()])
|
||||||
assert total_orders == 15
|
assert total_orders == 15
|
||||||
|
|
||||||
|
|
||||||
def test_order_book_get_l2():
|
def test_order_book_get_l2(stubbed_market): # pylint: disable=redefined-outer-name
|
||||||
with open(ASK_ORDER_BIN_PATH, "r") as input_file:
|
with open(ASK_ORDER_BIN_PATH, "r") as input_file:
|
||||||
base64_res = input_file.read()
|
base64_res = input_file.read()
|
||||||
data = base64.decodebytes(base64_res.encode("ascii"))
|
data = base64.decodebytes(base64_res.encode("ascii"))
|
||||||
order_book = OrderBook.decode(BTC_USDC_MARKET, data)
|
order_book = OrderBook.decode(stubbed_market, data)
|
||||||
for i in range(1, 16):
|
for i in range(1, 16):
|
||||||
assert i == len(order_book.get_l2(i))
|
assert i == len(order_book.get_l2(i))
|
||||||
assert [(11744.6, 4.0632, 117446, 40632)] == order_book.get_l2(1)
|
assert [(11744.6, 4.0632, 117446, 40632)] == order_book.get_l2(1)
|
||||||
|
|
||||||
|
|
||||||
def test_order_book_iterable():
|
def test_order_book_iterable(stubbed_market): # pylint: disable=redefined-outer-name
|
||||||
with open(ASK_ORDER_BIN_PATH, "r") as input_file:
|
with open(ASK_ORDER_BIN_PATH, "r") as input_file:
|
||||||
base64_res = input_file.read()
|
base64_res = input_file.read()
|
||||||
data = base64.decodebytes(base64_res.encode("ascii"))
|
data = base64.decodebytes(base64_res.encode("ascii"))
|
||||||
order_book = OrderBook.decode(BTC_USDC_MARKET, data)
|
order_book = OrderBook.decode(stubbed_market, data)
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for order in order_book:
|
for order in order_book:
|
||||||
cnt += 1
|
cnt += 1
|
||||||
|
|
|
@ -2,7 +2,7 @@ import base64
|
||||||
|
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
|
|
||||||
from src.open_order_account import OPEN_ORDERS_LAYOUT, OpenOrderAccount
|
from src.open_orders_account import OPEN_ORDERS_LAYOUT, OpenOrdersAccount
|
||||||
|
|
||||||
from .binary_file_path import OPEN_ORDER_ACCOUNT_BIN_PATH
|
from .binary_file_path import OPEN_ORDER_ACCOUNT_BIN_PATH
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ def test_decode_open_order_account():
|
||||||
with open(OPEN_ORDER_ACCOUNT_BIN_PATH, "r") as input_file:
|
with open(OPEN_ORDER_ACCOUNT_BIN_PATH, "r") as input_file:
|
||||||
base64_res = input_file.read()
|
base64_res = input_file.read()
|
||||||
data = base64.decodebytes(base64_res.encode("ascii"))
|
data = base64.decodebytes(base64_res.encode("ascii"))
|
||||||
open_order_account = OpenOrderAccount.from_bytes(PublicKey(1), data)
|
open_order_account = OpenOrdersAccount.from_bytes(PublicKey(1), data)
|
||||||
assert open_order_account.market == PublicKey("4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2")
|
assert open_order_account.market == PublicKey("4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2")
|
||||||
assert open_order_account.owner == PublicKey("7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q")
|
assert open_order_account.owner == PublicKey("7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q")
|
||||||
assert len([order for order in open_order_account.orders if order != 0]) == 3
|
assert len([order for order in open_order_account.orders if order != 0]) == 3
|
Loading…
Reference in New Issue