diff --git a/pyserum/_layouts/instructions.py b/pyserum/_layouts/instructions.py index c615f09..a0b92a0 100644 --- a/pyserum/_layouts/instructions.py +++ b/pyserum/_layouts/instructions.py @@ -1,9 +1,9 @@ """Layouts for dex instructions data.""" from enum import IntEnum -from construct import Switch from construct import Bytes, Const, Int8ul, Int16ul, Int32ul, Int64ul, Pass from construct import Struct as cStruct +from construct import Switch from .slab import KEY @@ -19,6 +19,8 @@ class InstructionType(IntEnum): NEW_ORDER_V3 = 10 CANCEL_ORDER_V2 = 11 CANCEL_ORDER_BY_CLIENT_ID_V2 = 12 + CLOSE_OPEN_ORDERS = 14 + INIT_OPEN_ORDERS = 15 _VERSION = 0 @@ -70,6 +72,9 @@ _CANCEL_ORDER_V2 = cStruct( _CANCEL_ORDER_BY_CLIENTID_V2 = cStruct("client_id" / Int64ul) +_CLOSE_OPEN_ORDERS = cStruct() +_INIT_OPEN_ORDERS = cStruct() + INSTRUCTIONS_LAYOUT = cStruct( "version" / Const(_VERSION, Int8ul), "instruction_type" / Int32ul, @@ -87,6 +92,8 @@ INSTRUCTIONS_LAYOUT = cStruct( InstructionType.NEW_ORDER_V3: _NEW_ORDER_V3, InstructionType.CANCEL_ORDER_V2: _CANCEL_ORDER_V2, InstructionType.CANCEL_ORDER_BY_CLIENT_ID_V2: _CANCEL_ORDER_BY_CLIENTID_V2, + InstructionType.CLOSE_OPEN_ORDERS: _CLOSE_OPEN_ORDERS, + InstructionType.INIT_OPEN_ORDERS: _INIT_OPEN_ORDERS, }, ), ) diff --git a/pyserum/_layouts/open_orders.py b/pyserum/_layouts/open_orders.py index dd9c84d..f49a499 100644 --- a/pyserum/_layouts/open_orders.py +++ b/pyserum/_layouts/open_orders.py @@ -1,4 +1,5 @@ -from construct import Bytes, Int64ul, Padding, Struct as cStruct +from construct import Bytes, Int64ul, Padding +from construct import Struct as cStruct from .account_flags import ACCOUNT_FLAGS_LAYOUT diff --git a/pyserum/_layouts/queue.py b/pyserum/_layouts/queue.py index 4ff4684..8b1e7de 100644 --- a/pyserum/_layouts/queue.py +++ b/pyserum/_layouts/queue.py @@ -1,4 +1,4 @@ -from construct import BitStruct, BitsInteger, BitsSwapped, Bytes, Const, Flag, Int8ul, Int32ul, Int64ul, Padding +from construct import BitsInteger, BitsSwapped, BitStruct, Bytes, Const, Flag, Int8ul, Int32ul, Int64ul, Padding from construct import Struct as cStruct from .account_flags import ACCOUNT_FLAGS_LAYOUT diff --git a/pyserum/_layouts/slab.py b/pyserum/_layouts/slab.py index 9541f4c..f3359ee 100644 --- a/pyserum/_layouts/slab.py +++ b/pyserum/_layouts/slab.py @@ -3,8 +3,9 @@ from __future__ import annotations from enum import IntEnum -from construct import Switch, Bytes, Int8ul, Int32ul, Int64ul, Padding +from construct import Bytes, Int8ul, Int32ul, Int64ul, Padding from construct import Struct as cStruct +from construct import Switch from .account_flags import ACCOUNT_FLAGS_LAYOUT diff --git a/pyserum/async_connection.py b/pyserum/async_connection.py index 7311e31..119bb7c 100644 --- a/pyserum/async_connection.py +++ b/pyserum/async_connection.py @@ -1,9 +1,10 @@ from typing import List + import httpx from solana.rpc.async_api import AsyncClient as async_conn # pylint: disable=unused-import # noqa:F401 -from .market.types import MarketInfo, TokenInfo from .connection import LIVE_MARKETS_URL, TOKEN_MINTS_URL, parse_live_markets, parse_token_mints +from .market.types import MarketInfo, TokenInfo async def get_live_markets(httpx_client: httpx.AsyncClient) -> List[MarketInfo]: diff --git a/pyserum/async_open_orders_account.py b/pyserum/async_open_orders_account.py index 0b6917c..838bb32 100644 --- a/pyserum/async_open_orders_account.py +++ b/pyserum/async_open_orders_account.py @@ -1,10 +1,11 @@ from __future__ import annotations from typing import List -from solana.rpc.async_api import AsyncClient + from solana.publickey import PublicKey -from solana.rpc.types import Commitment +from solana.rpc.async_api import AsyncClient from solana.rpc.commitment import Recent +from solana.rpc.types import Commitment from .async_utils import load_bytes_data from .open_orders_account import _OpenOrdersAccountCore diff --git a/pyserum/connection.py b/pyserum/connection.py index 5e99c3d..77f0adc 100644 --- a/pyserum/connection.py +++ b/pyserum/connection.py @@ -1,9 +1,9 @@ -from typing import List, Dict, Any +from typing import Any, Dict, List import requests - -from solana.rpc.api import Client as conn # pylint: disable=unused-import # noqa:F401 from solana.publickey import PublicKey +from solana.rpc.api import Client as conn # pylint: disable=unused-import # noqa:F401 + from .market.types import MarketInfo, TokenInfo LIVE_MARKETS_URL = "https://raw.githubusercontent.com/project-serum/serum-ts/master/packages/serum/src/markets.json" diff --git a/pyserum/instructions.py b/pyserum/instructions.py index f69f806..6c61d1a 100644 --- a/pyserum/instructions.py +++ b/pyserum/instructions.py @@ -1,12 +1,12 @@ """Serum Dex Instructions.""" from typing import Dict, List, NamedTuple, Optional +from construct import Container from solana.publickey import PublicKey from solana.sysvar import SYSVAR_RENT_PUBKEY from solana.transaction import AccountMeta, TransactionInstruction from solana.utils.validate import validate_instruction_keys, validate_instruction_type from spl.token.constants import TOKEN_PROGRAM_ID -from construct import Container from ._layouts.instructions import INSTRUCTIONS_LAYOUT, InstructionType from .enums import OrderType, SelfTradeBehavior, Side @@ -268,6 +268,36 @@ class CancelOrderByClientIDV2Params(NamedTuple): """""" +class CloseOpenOrdersParams(NamedTuple): + """Cancel order by client ID params.""" + + open_orders: PublicKey + """""" + owner: PublicKey + """""" + sol_wallet: PublicKey + """""" + market: PublicKey + """""" + program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID + """""" + + +class InitOpenOrdersParams(NamedTuple): + """Cancel order by client ID params.""" + + open_orders: PublicKey + """""" + owner: PublicKey + """""" + market: PublicKey + """""" + market_authority: Optional[PublicKey] = None + """""" + program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID + """""" + + def __parse_and_validate_instruction( instruction: TransactionInstruction, instruction_type: InstructionType ) -> Container: @@ -282,6 +312,8 @@ def __parse_and_validate_instruction( InstructionType.NEW_ORDER_V3: 12, InstructionType.CANCEL_ORDER_V2: 6, InstructionType.CANCEL_ORDER_BY_CLIENT_ID_V2: 6, + InstructionType.CLOSE_OPEN_ORDERS: 4, + InstructionType.INIT_OPEN_ORDERS: 3, } validate_instruction_keys(instruction, instruction_type_to_length_map[instruction_type]) data = INSTRUCTIONS_LAYOUT.parse(instruction.data) @@ -289,7 +321,9 @@ def __parse_and_validate_instruction( return data -def decode_initialize_market(instruction: TransactionInstruction) -> InitializeMarketParams: +def decode_initialize_market( + instruction: TransactionInstruction, +) -> InitializeMarketParams: """Decode an instialize market instruction and retrieve the instruction params.""" data = __parse_and_validate_instruction(instruction, InstructionType.INITIALIZE_MARKET) return InitializeMarketParams( @@ -382,7 +416,9 @@ def decode_settle_funds(instruction: TransactionInstruction) -> SettleFundsParam ) -def decode_cancel_order_by_client_id(instruction: TransactionInstruction) -> CancelOrderByClientIDParams: +def decode_cancel_order_by_client_id( + instruction: TransactionInstruction, +) -> CancelOrderByClientIDParams: data = __parse_and_validate_instruction(instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID) return CancelOrderByClientIDParams( market=instruction.keys[0].pubkey, @@ -445,6 +481,29 @@ def decode_cancel_order_by_client_id_v2(instruction: TransactionInstruction) -> ) +def decode_close_open_orders( + instruction: TransactionInstruction, +) -> CloseOpenOrdersParams: + return CloseOpenOrdersParams( + open_orders=instruction.keys[0].pubkey, + owner=instruction.keys[1].pubkey, + sol_wallet=instruction.keys[2].pubkey, + market=instruction.keys[3].pubkey, + ) + + +def decode_init_open_orders( + instruction: TransactionInstruction, +) -> InitOpenOrdersParams: + market_authority = instruction.keys[3].pubkey if len(instruction.keys) == 4 else None + return InitOpenOrdersParams( + open_orders=instruction.keys[0].pubkey, + owner=instruction.keys[1].pubkey, + market=instruction.keys[2].pubkey, + market_authority=market_authority, + ) + + def initialize_market(params: InitializeMarketParams) -> TransactionInstruction: """Generate a transaction instruction to initialize a Serum market.""" return TransactionInstruction( @@ -519,7 +578,10 @@ def match_orders(params: MatchOrdersParams) -> TransactionInstruction: ], program_id=params.program_id, data=INSTRUCTIONS_LAYOUT.build( - dict(instruction_type=InstructionType.MATCH_ORDER, args=dict(limit=params.limit)) + dict( + instruction_type=InstructionType.MATCH_ORDER, + args=dict(limit=params.limit), + ) ), ) @@ -534,7 +596,10 @@ def consume_events(params: ConsumeEventsParams) -> TransactionInstruction: keys=keys, program_id=params.program_id, data=INSTRUCTIONS_LAYOUT.build( - dict(instruction_type=InstructionType.CONSUME_EVENTS, args=dict(limit=params.limit)) + dict( + instruction_type=InstructionType.CONSUME_EVENTS, + args=dict(limit=params.limit), + ) ), ) @@ -582,7 +647,9 @@ def settle_funds(params: SettleFundsParams) -> TransactionInstruction: ) -def cancel_order_by_client_id(params: CancelOrderByClientIDParams) -> TransactionInstruction: +def cancel_order_by_client_id( + params: CancelOrderByClientIDParams, +) -> TransactionInstruction: """Generate a transaction instruction to cancel order by client id.""" return TransactionInstruction( keys=[ @@ -668,7 +735,9 @@ def cancel_order_v2(params: CancelOrderV2Params) -> TransactionInstruction: ) -def cancel_order_by_client_id_v2(params: CancelOrderByClientIDV2Params) -> TransactionInstruction: +def cancel_order_by_client_id_v2( + params: CancelOrderByClientIDV2Params, +) -> TransactionInstruction: """Generate a transaction instruction to cancel order by client id.""" return TransactionInstruction( keys=[ @@ -689,3 +758,35 @@ def cancel_order_by_client_id_v2(params: CancelOrderByClientIDV2Params) -> Trans ) ), ) + + +def close_open_orders(params: CloseOpenOrdersParams) -> TransactionInstruction: + """Generate a transaction instruction to close open orders account.""" + return TransactionInstruction( + keys=[ + AccountMeta(pubkey=params.open_orders, is_signer=False, is_writable=True), + AccountMeta(pubkey=params.owner, is_signer=True, is_writable=False), + AccountMeta(pubkey=params.sol_wallet, is_signer=False, is_writable=True), + AccountMeta(pubkey=params.market, is_signer=False, is_writable=False), + ], + program_id=params.program_id, + data=INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.CLOSE_OPEN_ORDERS, args=dict())), + ) + + +def init_open_orders(params: InitOpenOrdersParams) -> TransactionInstruction: + """Generate a transaction instruction to initialize open orders account.""" + touched_keys = [ + AccountMeta(pubkey=params.open_orders, is_signer=False, is_writable=True), + AccountMeta(pubkey=params.owner, is_signer=True, is_writable=False), + AccountMeta(pubkey=params.market, is_signer=False, is_writable=False), + ] + if params.market_authority: + touched_keys.append( + AccountMeta(pubkey=params.market_authority, is_signer=False, is_writable=False), + ) + return TransactionInstruction( + keys=touched_keys, + program_id=params.program_id, + data=INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.INIT_OPEN_ORDERS, args=dict())), + ) diff --git a/pyserum/market/__init__.py b/pyserum/market/__init__.py index 9a2aadd..2f53a13 100644 --- a/pyserum/market/__init__.py +++ b/pyserum/market/__init__.py @@ -1,4 +1,4 @@ -from .market import Market # noqa: F401 from .async_market import AsyncMarket # noqa: F401 +from .market import Market # noqa: F401 from .orderbook import OrderBook # noqa: F401 from .state import MarketState as State # noqa: F401 diff --git a/pyserum/market/async_market.py b/pyserum/market/async_market.py index c0d11f5..df92520 100644 --- a/pyserum/market/async_market.py +++ b/pyserum/market/async_market.py @@ -9,17 +9,17 @@ from solana.rpc.async_api import AsyncClient from solana.rpc.types import RPCResponse, TxOpts from solana.transaction import Transaction -from pyserum import instructions import pyserum.market.types as t +from pyserum import instructions from .._layouts.open_orders import OPEN_ORDERS_LAYOUT -from ..enums import OrderType, Side from ..async_open_orders_account import AsyncOpenOrdersAccount from ..async_utils import load_bytes_data +from ..enums import OrderType, Side from ._internal.queue import decode_event_queue, decode_request_queue +from .core import MarketCore from .orderbook import OrderBook from .state import MarketState -from .core import MarketCore LAMPORTS_PER_SOL = 1000000000 diff --git a/pyserum/market/core.py b/pyserum/market/core.py index d85efe0..45ffbcd 100644 --- a/pyserum/market/core.py +++ b/pyserum/market/core.py @@ -11,15 +11,14 @@ from solana.rpc.types import RPCResponse from solana.system_program import CreateAccountParams, create_account from solana.transaction import Transaction, TransactionInstruction from spl.token.constants import ACCOUNT_LEN, TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT -from spl.token.instructions import CloseAccountParams -from spl.token.instructions import InitializeAccountParams, close_account, initialize_account +from spl.token.instructions import CloseAccountParams, InitializeAccountParams, close_account, initialize_account -from pyserum import instructions import pyserum.market.types as t +from pyserum import instructions +from ..async_open_orders_account import AsyncOpenOrdersAccount from ..enums import OrderType, SelfTradeBehavior, Side from ..open_orders_account import OpenOrdersAccount, make_create_account_instruction -from ..async_open_orders_account import AsyncOpenOrdersAccount from ._internal.queue import decode_event_queue from .orderbook import OrderBook from .state import MarketState diff --git a/pyserum/market/market.py b/pyserum/market/market.py index cba0048..ecd91e4 100644 --- a/pyserum/market/market.py +++ b/pyserum/market/market.py @@ -9,17 +9,17 @@ from solana.rpc.api import Client from solana.rpc.types import RPCResponse, TxOpts from solana.transaction import Transaction -from pyserum import instructions import pyserum.market.types as t +from pyserum import instructions from .._layouts.open_orders import OPEN_ORDERS_LAYOUT from ..enums import OrderType, Side from ..open_orders_account import OpenOrdersAccount from ..utils import load_bytes_data from ._internal.queue import decode_event_queue, decode_request_queue +from .core import MarketCore from .orderbook import OrderBook from .state import MarketState -from .core import MarketCore LAMPORTS_PER_SOL = 1000000000 diff --git a/pyserum/market/state.py b/pyserum/market/state.py index 4e3861e..5b1a153 100644 --- a/pyserum/market/state.py +++ b/pyserum/market/state.py @@ -7,7 +7,7 @@ from solana.publickey import PublicKey from solana.rpc.api import Client from solana.rpc.async_api import AsyncClient -from pyserum import utils, async_utils +from pyserum import async_utils, utils from .._layouts.market import MARKET_LAYOUT from .types import AccountFlags diff --git a/pyserum/open_orders_account.py b/pyserum/open_orders_account.py index c973a56..a9cc4c3 100644 --- a/pyserum/open_orders_account.py +++ b/pyserum/open_orders_account.py @@ -1,7 +1,7 @@ from __future__ import annotations import base64 -from typing import List, NamedTuple, TypeVar, Type, Tuple +from typing import List, NamedTuple, Tuple, Type, TypeVar from solana.publickey import PublicKey from solana.rpc.api import Client diff --git a/tests/conftest.py b/tests/conftest.py index f532d4d..2f32025 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ -from typing import Dict import asyncio +from typing import Dict import pytest from solana.keypair import Keypair @@ -7,8 +7,8 @@ from solana.publickey import PublicKey from solana.rpc.api import Client from solana.rpc.async_api import AsyncClient -from pyserum.connection import conn from pyserum.async_connection import async_conn +from pyserum.connection import conn @pytest.mark.integration diff --git a/tests/integration/test_async_connection.py b/tests/integration/test_async_connection.py index 699170f..85c7821 100644 --- a/tests/integration/test_async_connection.py +++ b/tests/integration/test_async_connection.py @@ -1,6 +1,6 @@ # pylint: disable=R0801 -import pytest import httpx +import pytest from pyserum.async_connection import get_live_markets, get_token_mints from pyserum.market.types import MarketInfo, TokenInfo diff --git a/tests/test_instructions.py b/tests/test_instructions.py index 2983a01..8d6b776 100644 --- a/tests/test_instructions.py +++ b/tests/test_instructions.py @@ -113,3 +113,36 @@ def test_settle_funds(): ) instruction = inlib.settle_funds(params) assert inlib.decode_settle_funds(instruction) == params + + +def test_close_open_orders(): + """Test settle funds.""" + params = inlib.CloseOpenOrdersParams( + open_orders=PublicKey(0), + owner=PublicKey(1), + sol_wallet=PublicKey(2), + market=PublicKey(3), + ) + instruction = inlib.close_open_orders(params) + assert inlib.decode_close_open_orders(instruction) == params + + +def test_init_open_orders(): + """Test settle funds.""" + params = inlib.InitOpenOrdersParams( + open_orders=PublicKey(0), owner=PublicKey(1), market=PublicKey(2), market_authority=None + ) + instruction = inlib.init_open_orders(params) + assert inlib.decode_init_open_orders(instruction) == params + + +def test_init_open_orders_with_authority(): + """Test settle funds.""" + params = inlib.InitOpenOrdersParams( + open_orders=PublicKey(0), + owner=PublicKey(1), + market=PublicKey(2), + market_authority=PublicKey(3), + ) + instruction = inlib.init_open_orders(params) + assert inlib.decode_init_open_orders(instruction) == params