update solanapy

This commit is contained in:
Ennio Nasca 2022-10-18 16:51:11 +02:00
parent 49271da70f
commit 2954a7779f
8 changed files with 1359 additions and 963 deletions

View File

@ -10,7 +10,7 @@ allow_prereleases = true
install-ci-deps = "pipenv install --dev --skip-lock black pydocstyle flake8 pylint mypy pytest" install-ci-deps = "pipenv install --dev --skip-lock black pydocstyle flake8 pylint mypy pytest"
[dev-packages] [dev-packages]
jupyterlab = "*" notebook = "*"
black = "*" black = "*"
pytest = "*" pytest = "*"
pylint = "*" pylint = "*"
@ -28,7 +28,7 @@ pytest-asyncio = "*"
types-requests = "*" types-requests = "*"
[packages] [packages]
solana = {version = ">=0.15.0"} solana = {version = ">=0.27.0"}
construct = "*" construct = "*"
flake8 = "*" flake8 = "*"
construct-typing = "*" construct-typing = "*"

2040
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,9 @@ from typing import List
from solana.keypair import Keypair from solana.keypair import Keypair
from solana.publickey import PublicKey from solana.publickey import PublicKey
from solana.rpc.async_api import AsyncClient from solana.rpc.async_api import AsyncClient
from solana.rpc.types import RPCResponse, TxOpts from solana.rpc.types import TxOpts
from solana.transaction import Transaction from solana.transaction import Transaction
from solders.rpc.responses import SendTransactionResp
import pyserum.market.types as t import pyserum.market.types as t
from pyserum import instructions from pyserum import instructions
@ -28,8 +29,15 @@ LAMPORTS_PER_SOL = 1000000000
class AsyncMarket(MarketCore): class AsyncMarket(MarketCore):
"""Represents a Serum Market.""" """Represents a Serum Market."""
def __init__(self, conn: AsyncClient, market_state: MarketState, force_use_request_queue: bool = False) -> None: def __init__(
super().__init__(market_state=market_state, force_use_request_queue=force_use_request_queue) self,
conn: AsyncClient,
market_state: MarketState,
force_use_request_queue: bool = False,
) -> None:
super().__init__(
market_state=market_state, force_use_request_queue=force_use_request_queue
)
self._conn = conn self._conn = conn
@classmethod @classmethod
@ -50,7 +58,9 @@ class AsyncMarket(MarketCore):
market_state = await MarketState.async_load(conn, market_address, program_id) market_state = await MarketState.async_load(conn, market_address, program_id)
return cls(conn, market_state, force_use_request_queue) return cls(conn, market_state, force_use_request_queue)
async def find_open_orders_accounts_for_owner(self, owner_address: PublicKey) -> List[AsyncOpenOrdersAccount]: async def find_open_orders_accounts_for_owner(
self, owner_address: PublicKey
) -> List[AsyncOpenOrdersAccount]:
return await AsyncOpenOrdersAccount.find_for_market_and_owner( return await AsyncOpenOrdersAccount.find_for_market_and_owner(
self._conn, self.state.public_key(), owner_address, self.state.program_id() self._conn, self.state.public_key(), owner_address, self.state.program_id()
) )
@ -69,7 +79,9 @@ class AsyncMarket(MarketCore):
"""Load orders for owner.""" """Load orders for owner."""
bids = await self.load_bids() bids = await self.load_bids()
asks = await self.load_asks() asks = await self.load_asks()
open_orders_accounts = await self.find_open_orders_accounts_for_owner(owner_address) open_orders_accounts = await self.find_open_orders_accounts_for_owner(
owner_address
)
return self._parse_orders_for_owner(bids, asks, open_orders_accounts) return self._parse_orders_for_owner(bids, asks, open_orders_accounts)
async def load_event_queue(self) -> List[t.Event]: async def load_event_queue(self) -> List[t.Event]:
@ -98,16 +110,23 @@ class AsyncMarket(MarketCore):
max_quantity: float, max_quantity: float,
client_id: int = 0, client_id: int = 0,
opts: TxOpts = TxOpts(), opts: TxOpts = TxOpts(),
) -> RPCResponse: # TODO: Add open_orders_address_key param and fee_discount_pubkey ) -> SendTransactionResp: # TODO: Add open_orders_address_key param and fee_discount_pubkey
transaction = Transaction() transaction = Transaction()
signers: List[Keypair] = [owner] signers: List[Keypair] = [owner]
open_order_accounts = await self.find_open_orders_accounts_for_owner(owner.public_key) open_order_accounts = await self.find_open_orders_accounts_for_owner(
owner.public_key
)
if open_order_accounts: if open_order_accounts:
place_order_open_order_account = open_order_accounts[0].address place_order_open_order_account = open_order_accounts[0].address
else: else:
mbfre_resp = await self._conn.get_minimum_balance_for_rent_exemption(OPEN_ORDERS_LAYOUT.sizeof()) mbfre_resp = await self._conn.get_minimum_balance_for_rent_exemption(
OPEN_ORDERS_LAYOUT.sizeof()
)
place_order_open_order_account = self._after_oo_mbfre_resp( place_order_open_order_account = self._after_oo_mbfre_resp(
mbfre_resp=mbfre_resp, owner=owner, signers=signers, transaction=transaction mbfre_resp=mbfre_resp,
owner=owner,
signers=signers,
transaction=transaction,
) )
# TODO: Cache new_open_orders_account # TODO: Cache new_open_orders_account
# TODO: Handle fee_discount_pubkey # TODO: Handle fee_discount_pubkey
@ -128,18 +147,26 @@ class AsyncMarket(MarketCore):
return await self._conn.send_transaction(transaction, *signers, opts=opts) return await self._conn.send_transaction(transaction, *signers, opts=opts)
async def cancel_order_by_client_id( async def cancel_order_by_client_id(
self, owner: Keypair, open_orders_account: PublicKey, client_id: int, opts: TxOpts = TxOpts() self,
) -> RPCResponse: owner: Keypair,
open_orders_account: PublicKey,
client_id: int,
opts: TxOpts = TxOpts(),
) -> SendTransactionResp:
txs = self._build_cancel_order_by_client_id_tx( txs = self._build_cancel_order_by_client_id_tx(
owner=owner, open_orders_account=open_orders_account, client_id=client_id owner=owner, open_orders_account=open_orders_account, client_id=client_id
) )
return await self._conn.send_transaction(txs, owner, opts=opts) return await self._conn.send_transaction(txs, owner, opts=opts)
async def cancel_order(self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()) -> RPCResponse: async def cancel_order(
self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()
) -> SendTransactionResp:
txn = self._build_cancel_order_tx(owner=owner, order=order) txn = self._build_cancel_order_tx(owner=owner, order=order)
return await self._conn.send_transaction(txn, owner, opts=opts) return await self._conn.send_transaction(txn, owner, opts=opts)
async def match_orders(self, fee_payer: Keypair, limit: int, opts: TxOpts = TxOpts()) -> RPCResponse: async def match_orders(
self, fee_payer: Keypair, limit: int, opts: TxOpts = TxOpts()
) -> SendTransactionResp:
txn = self._build_match_orders_tx(limit) txn = self._build_match_orders_tx(limit)
return await self._conn.send_transaction(txn, fee_payer, opts=opts) return await self._conn.send_transaction(txn, fee_payer, opts=opts)
@ -150,12 +177,12 @@ class AsyncMarket(MarketCore):
base_wallet: PublicKey, base_wallet: PublicKey,
quote_wallet: PublicKey, # TODO: add referrer_quote_wallet. quote_wallet: PublicKey, # TODO: add referrer_quote_wallet.
opts: TxOpts = TxOpts(), opts: TxOpts = TxOpts(),
) -> RPCResponse: ) -> SendTransactionResp:
# TODO: Handle wrapped sol accounts # TODO: Handle wrapped sol accounts
should_wrap_sol = self._settle_funds_should_wrap_sol() should_wrap_sol = self._settle_funds_should_wrap_sol()
if should_wrap_sol: if should_wrap_sol:
mbfre_resp = await self._conn.get_minimum_balance_for_rent_exemption(165) mbfre_resp = await self._conn.get_minimum_balance_for_rent_exemption(165)
min_bal_for_rent_exemption = mbfre_resp["result"] min_bal_for_rent_exemption = mbfre_resp.value
else: else:
min_bal_for_rent_exemption = 0 # value only matters if should_wrap_sol min_bal_for_rent_exemption = 0 # value only matters if should_wrap_sol
signers = [owner] signers = [owner]

View File

@ -7,11 +7,16 @@ from typing import List, Union
from solana.keypair import Keypair from solana.keypair import Keypair
from solana.publickey import PublicKey from solana.publickey import PublicKey
from solana.rpc.types import RPCResponse
from solana.system_program import CreateAccountParams, create_account from solana.system_program import CreateAccountParams, create_account
from solana.transaction import Transaction, TransactionInstruction from solana.transaction import Transaction, TransactionInstruction
from solders.rpc.responses import GetMinimumBalanceForRentExemptionResp
from spl.token.constants import ACCOUNT_LEN, TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT from spl.token.constants import ACCOUNT_LEN, TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT
from spl.token.instructions import CloseAccountParams, InitializeAccountParams, close_account, initialize_account from spl.token.instructions import (
CloseAccountParams,
InitializeAccountParams,
close_account,
initialize_account,
)
import pyserum.market.types as t import pyserum.market.types as t
from pyserum import instructions from pyserum import instructions
@ -32,20 +37,25 @@ class MarketCore:
logger = logging.getLogger("pyserum.market.Market") logger = logging.getLogger("pyserum.market.Market")
def __init__(self, market_state: MarketState, force_use_request_queue: bool = False) -> None: def __init__(
self, market_state: MarketState, force_use_request_queue: bool = False
) -> None:
self.state = market_state self.state = market_state
self.force_use_request_queue = force_use_request_queue self.force_use_request_queue = force_use_request_queue
def _use_request_queue(self) -> bool: def _use_request_queue(self) -> bool:
return ( return (
# DEX Version 1 # DEX Version 1
self.state.program_id == PublicKey("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn") self.state.program_id
== PublicKey("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn")
or or
# DEX Version 1 # DEX Version 1
self.state.program_id == PublicKey("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg") self.state.program_id
== PublicKey("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg")
or or
# DEX Version 2 # DEX Version 2
self.state.program_id == PublicKey("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o") self.state.program_id
== PublicKey("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o")
or self.force_use_request_queue or self.force_use_request_queue
) )
@ -58,7 +68,9 @@ class MarketCore:
def find_best_fee_discount_key(self, owner: PublicKey, cache_duration: int): def find_best_fee_discount_key(self, owner: PublicKey, cache_duration: int):
raise NotImplementedError("find_best_fee_discount_key not implemented") raise NotImplementedError("find_best_fee_discount_key not implemented")
def find_quote_token_accounts_for_owner(self, owner_address: PublicKey, include_unwrapped_sol: bool = False): 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") raise NotImplementedError("find_quote_token_accounts_for_owner not implemented")
def _parse_bids_or_asks(self, bytes_data: bytes) -> OrderBook: def _parse_bids_or_asks(self, bytes_data: bytes) -> OrderBook:
@ -71,7 +83,9 @@ class MarketCore:
all_orders = itertools.chain(bids.orders(), asks.orders()) all_orders = itertools.chain(bids.orders(), asks.orders())
open_orders_addresses = {str(o.address) for o in open_orders_accounts} open_orders_addresses = {str(o.address) for o in open_orders_accounts}
orders = [o for o in all_orders if str(o.open_order_address) in open_orders_addresses] orders = [
o for o in all_orders if str(o.open_order_address) in open_orders_addresses
]
return orders return orders
def load_base_token_for_owner(self): def load_base_token_for_owner(self):
@ -110,11 +124,16 @@ class MarketCore:
side=side, side=side,
price=price, price=price,
size=size, size=size,
fee_cost=event.native_fee_or_rebate * (1 if event.event_flags.maker else -1), fee_cost=event.native_fee_or_rebate
* (1 if event.event_flags.maker else -1),
) )
def _prepare_new_oo_account( def _prepare_new_oo_account(
self, owner: Keypair, balance_needed: int, signers: List[Keypair], transaction: Transaction self,
owner: Keypair,
balance_needed: int,
signers: List[Keypair],
transaction: Transaction,
) -> PublicKey: ) -> PublicKey:
# new_open_orders_account = Account() # new_open_orders_account = Account()
new_open_orders_account = Keypair() new_open_orders_account = Keypair()
@ -141,7 +160,9 @@ class MarketCore:
limit_price: float, limit_price: float,
max_quantity: float, max_quantity: float,
client_id: int, client_id: int,
open_order_accounts: Union[List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]], open_order_accounts: Union[
List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]
],
place_order_open_order_account: PublicKey, place_order_open_order_account: PublicKey,
) -> None: ) -> None:
# unwrapped SOL cannot be used for payment # unwrapped SOL cannot be used for payment
@ -149,9 +170,9 @@ class MarketCore:
raise ValueError("Invalid payer account. Cannot use unwrapped SOL.") raise ValueError("Invalid payer account. Cannot use unwrapped SOL.")
# TODO: add integration test for SOL wrapping. # TODO: add integration test for SOL wrapping.
should_wrap_sol = (side == Side.BUY and self.state.quote_mint() == WRAPPED_SOL_MINT) or ( should_wrap_sol = (
side == Side.SELL and self.state.base_mint() == WRAPPED_SOL_MINT side == Side.BUY and self.state.quote_mint() == WRAPPED_SOL_MINT
) ) or (side == Side.SELL and self.state.base_mint() == WRAPPED_SOL_MINT)
if should_wrap_sol: if should_wrap_sol:
# wrapped_sol_account = Account() # wrapped_sol_account = Account()
@ -208,10 +229,16 @@ class MarketCore:
) )
def _after_oo_mbfre_resp( def _after_oo_mbfre_resp(
self, mbfre_resp: RPCResponse, owner: Keypair, signers: List[Keypair], transaction: Transaction self,
mbfre_resp: GetMinimumBalanceForRentExemptionResp,
owner: Keypair,
signers: List[Keypair],
transaction: Transaction,
) -> PublicKey: ) -> PublicKey:
balance_needed = mbfre_resp["result"] balance_needed = mbfre_resp.value
place_order_open_order_account = self._prepare_new_oo_account(owner, balance_needed, signers, transaction) place_order_open_order_account = self._prepare_new_oo_account(
owner, balance_needed, signers, transaction
)
return place_order_open_order_account return place_order_open_order_account
@staticmethod @staticmethod
@ -219,7 +246,9 @@ class MarketCore:
price: float, price: float,
size: float, size: float,
side: Side, side: Side,
open_orders_accounts: Union[List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]], open_orders_accounts: Union[
List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]
],
) -> int: ) -> int:
lamports = 0 lamports = 0
if side == Side.BUY: if side == Side.BUY:
@ -246,9 +275,9 @@ class MarketCore:
fee_discount_pubkey: PublicKey = None, fee_discount_pubkey: PublicKey = None,
) -> TransactionInstruction: ) -> TransactionInstruction:
if self.state.base_size_number_to_lots(max_quantity) < 0: if self.state.base_size_number_to_lots(max_quantity) < 0:
raise Exception("Size lot %d is too small" % max_quantity) raise Exception(f"Size lot %d is too small {max_quantity}")
if self.state.price_number_to_lots(limit_price) < 0: if self.state.price_number_to_lots(limit_price) < 0:
raise Exception("Price lot %d is too small" % limit_price) raise Exception(f"Price lot %d is too small {limit_price}")
if self._use_request_queue(): if self._use_request_queue():
return instructions.new_order( return instructions.new_order(
instructions.NewOrderParams( instructions.NewOrderParams(
@ -297,7 +326,11 @@ class MarketCore:
def _build_cancel_order_by_client_id_tx( def _build_cancel_order_by_client_id_tx(
self, owner: Keypair, open_orders_account: PublicKey, client_id: int self, owner: Keypair, open_orders_account: PublicKey, client_id: int
) -> Transaction: ) -> Transaction:
return Transaction().add(self.make_cancel_order_by_client_id_instruction(owner, open_orders_account, client_id)) return Transaction().add(
self.make_cancel_order_by_client_id_instruction(
owner, open_orders_account, client_id
)
)
def make_cancel_order_by_client_id_instruction( def make_cancel_order_by_client_id_instruction(
self, owner: Keypair, open_orders_account: PublicKey, client_id: int self, owner: Keypair, open_orders_account: PublicKey, client_id: int
@ -327,9 +360,13 @@ class MarketCore:
) )
def _build_cancel_order_tx(self, owner: Keypair, order: t.Order) -> Transaction: def _build_cancel_order_tx(self, owner: Keypair, order: t.Order) -> Transaction:
return Transaction().add(self.make_cancel_order_instruction(owner.public_key, order)) return Transaction().add(
self.make_cancel_order_instruction(owner.public_key, order)
)
def make_cancel_order_instruction(self, owner: PublicKey, order: t.Order) -> TransactionInstruction: def make_cancel_order_instruction(
self, owner: PublicKey, order: t.Order
) -> TransactionInstruction:
if self._use_request_queue(): if self._use_request_queue():
return instructions.cancel_order( return instructions.cancel_order(
instructions.CancelOrderParams( instructions.CancelOrderParams(
@ -389,7 +426,10 @@ class MarketCore:
if open_orders.owner != owner.public_key: if open_orders.owner != owner.public_key:
raise Exception("Invalid open orders account") raise Exception("Invalid open orders account")
vault_signer = PublicKey.create_program_address( vault_signer = PublicKey.create_program_address(
[bytes(self.state.public_key()), self.state.vault_signer_nonce().to_bytes(8, byteorder="little")], [
bytes(self.state.public_key()),
self.state.vault_signer_nonce().to_bytes(8, byteorder="little"),
],
self.state.program_id(), self.state.program_id(),
) )
transaction = Transaction() transaction = Transaction()
@ -425,8 +465,12 @@ class MarketCore:
transaction.add( transaction.add(
self.make_settle_funds_instruction( self.make_settle_funds_instruction(
open_orders, open_orders,
base_wallet if self.state.base_mint() != WRAPPED_SOL_MINT else wrapped_sol_account.public_key, base_wallet
quote_wallet if self.state.quote_mint() != WRAPPED_SOL_MINT else wrapped_sol_account.public_key, if self.state.base_mint() != WRAPPED_SOL_MINT
else wrapped_sol_account.public_key,
quote_wallet
if self.state.quote_mint() != WRAPPED_SOL_MINT
else wrapped_sol_account.public_key,
vault_signer, vault_signer,
) )
) )
@ -446,7 +490,9 @@ class MarketCore:
return transaction return transaction
def _settle_funds_should_wrap_sol(self) -> bool: def _settle_funds_should_wrap_sol(self) -> bool:
return (self.state.quote_mint() == WRAPPED_SOL_MINT) or (self.state.base_mint() == WRAPPED_SOL_MINT) return (self.state.quote_mint() == WRAPPED_SOL_MINT) or (
self.state.base_mint() == WRAPPED_SOL_MINT
)
def make_settle_funds_instruction( def make_settle_funds_instruction(
self, self,

View File

@ -6,8 +6,9 @@ from typing import List
from solana.keypair import Keypair from solana.keypair import Keypair
from solana.publickey import PublicKey from solana.publickey import PublicKey
from solana.rpc.api import Client from solana.rpc.api import Client
from solana.rpc.types import RPCResponse, TxOpts from solana.rpc.types import TxOpts
from solana.transaction import Transaction from solana.transaction import Transaction
from solders.rpc.responses import SendTransactionResp
import pyserum.market.types as t import pyserum.market.types as t
from pyserum import instructions from pyserum import instructions
@ -28,8 +29,15 @@ LAMPORTS_PER_SOL = 1000000000
class Market(MarketCore): class Market(MarketCore):
"""Represents a Serum Market.""" """Represents a Serum Market."""
def __init__(self, conn: Client, market_state: MarketState, force_use_request_queue: bool = False) -> None: def __init__(
super().__init__(market_state=market_state, force_use_request_queue=force_use_request_queue) self,
conn: Client,
market_state: MarketState,
force_use_request_queue: bool = False,
) -> None:
super().__init__(
market_state=market_state, force_use_request_queue=force_use_request_queue
)
self._conn = conn self._conn = conn
@classmethod @classmethod
@ -50,7 +58,9 @@ class Market(MarketCore):
market_state = MarketState.load(conn, market_address, program_id) market_state = MarketState.load(conn, market_address, program_id)
return cls(conn, market_state, force_use_request_queue) return cls(conn, market_state, force_use_request_queue)
def find_open_orders_accounts_for_owner(self, owner_address: PublicKey) -> List[OpenOrdersAccount]: def find_open_orders_accounts_for_owner(
self, owner_address: PublicKey
) -> List[OpenOrdersAccount]:
return OpenOrdersAccount.find_for_market_and_owner( return OpenOrdersAccount.find_for_market_and_owner(
self._conn, self.state.public_key(), owner_address, self.state.program_id() self._conn, self.state.public_key(), owner_address, self.state.program_id()
) )
@ -98,16 +108,21 @@ class Market(MarketCore):
max_quantity: float, max_quantity: float,
client_id: int = 0, client_id: int = 0,
opts: TxOpts = TxOpts(), opts: TxOpts = TxOpts(),
) -> RPCResponse: # TODO: Add open_orders_address_key param and fee_discount_pubkey ) -> SendTransactionResp: # TODO: Add open_orders_address_key param and fee_discount_pubkey
transaction = Transaction() transaction = Transaction()
signers: List[Keypair] = [owner] signers: List[Keypair] = [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 open_order_accounts: if open_order_accounts:
place_order_open_order_account = open_order_accounts[0].address place_order_open_order_account = open_order_accounts[0].address
else: else:
mbfre_resp = self._conn.get_minimum_balance_for_rent_exemption(OPEN_ORDERS_LAYOUT.sizeof()) mbfre_resp = self._conn.get_minimum_balance_for_rent_exemption(
OPEN_ORDERS_LAYOUT.sizeof()
)
place_order_open_order_account = self._after_oo_mbfre_resp( place_order_open_order_account = self._after_oo_mbfre_resp(
mbfre_resp=mbfre_resp, owner=owner, signers=signers, transaction=transaction mbfre_resp=mbfre_resp,
owner=owner,
signers=signers,
transaction=transaction,
) )
# TODO: Cache new_open_orders_account # TODO: Cache new_open_orders_account
# TODO: Handle fee_discount_pubkey # TODO: Handle fee_discount_pubkey
@ -128,18 +143,26 @@ class Market(MarketCore):
return self._conn.send_transaction(transaction, *signers, opts=opts) return self._conn.send_transaction(transaction, *signers, opts=opts)
def cancel_order_by_client_id( def cancel_order_by_client_id(
self, owner: Keypair, open_orders_account: PublicKey, client_id: int, opts: TxOpts = TxOpts() self,
) -> RPCResponse: owner: Keypair,
open_orders_account: PublicKey,
client_id: int,
opts: TxOpts = TxOpts(),
) -> SendTransactionResp:
txs = self._build_cancel_order_by_client_id_tx( txs = self._build_cancel_order_by_client_id_tx(
owner=owner, open_orders_account=open_orders_account, client_id=client_id owner=owner, open_orders_account=open_orders_account, client_id=client_id
) )
return self._conn.send_transaction(txs, owner, opts=opts) return self._conn.send_transaction(txs, owner, opts=opts)
def cancel_order(self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()) -> RPCResponse: def cancel_order(
self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()
) -> SendTransactionResp:
txn = self._build_cancel_order_tx(owner=owner, order=order) txn = self._build_cancel_order_tx(owner=owner, order=order)
return self._conn.send_transaction(txn, owner, opts=opts) return self._conn.send_transaction(txn, owner, opts=opts)
def match_orders(self, fee_payer: Keypair, limit: int, opts: TxOpts = TxOpts()) -> RPCResponse: def match_orders(
self, fee_payer: Keypair, limit: int, opts: TxOpts = TxOpts()
) -> SendTransactionResp:
txn = self._build_match_orders_tx(limit) txn = self._build_match_orders_tx(limit)
return self._conn.send_transaction(txn, fee_payer, opts=opts) return self._conn.send_transaction(txn, fee_payer, opts=opts)
@ -150,11 +173,13 @@ class Market(MarketCore):
base_wallet: PublicKey, base_wallet: PublicKey,
quote_wallet: PublicKey, # TODO: add referrer_quote_wallet. quote_wallet: PublicKey, # TODO: add referrer_quote_wallet.
opts: TxOpts = TxOpts(), opts: TxOpts = TxOpts(),
) -> RPCResponse: ) -> SendTransactionResp:
# TODO: Handle wrapped sol accounts # TODO: Handle wrapped sol accounts
should_wrap_sol = self._settle_funds_should_wrap_sol() should_wrap_sol = self._settle_funds_should_wrap_sol()
min_bal_for_rent_exemption = ( min_bal_for_rent_exemption = (
self._conn.get_minimum_balance_for_rent_exemption(165)["result"] if should_wrap_sol else 0 self._conn.get_minimum_balance_for_rent_exemption(165).value
if should_wrap_sol
else 0
) # value only matters if should_wrap_sol ) # value only matters if should_wrap_sol
signers = [owner] signers = [owner]
transaction = self._build_settle_funds_tx( transaction = self._build_settle_funds_tx(

View File

@ -126,10 +126,10 @@ class MarketState: # pylint: disable=too-many-public-methods
return self._quote_mint_decimals return self._quote_mint_decimals
def base_spl_token_multiplier(self) -> int: def base_spl_token_multiplier(self) -> int:
return 10 ** self._base_mint_decimals return 10**self._base_mint_decimals
def quote_spl_token_multiplier(self) -> int: def quote_spl_token_multiplier(self) -> int:
return 10 ** self._quote_mint_decimals return 10**self._quote_mint_decimals
def base_spl_size_to_number(self, size: int) -> float: def base_spl_size_to_number(self, size: int) -> float:
return size / self.base_spl_token_multiplier() return size / self.base_spl_token_multiplier()

View File

@ -1,14 +1,14 @@
from __future__ import annotations from __future__ import annotations
import base64
from typing import List, NamedTuple, Tuple, Type, TypeVar from typing import List, NamedTuple, Tuple, Type, TypeVar
from solana.publickey import PublicKey from solana.publickey import PublicKey
from solana.rpc.api import Client from solana.rpc.api import Client
from solana.rpc.commitment import Recent from solana.rpc.commitment import Recent
from solana.rpc.types import Commitment, MemcmpOpts, RPCResponse from solana.rpc.types import Commitment, MemcmpOpts
from solana.system_program import CreateAccountParams, create_account from solana.system_program import CreateAccountParams, create_account
from solana.transaction import TransactionInstruction from solana.transaction import TransactionInstruction
from solders.rpc.responses import GetProgramAccountsResp
from ._layouts.open_orders import OPEN_ORDERS_LAYOUT from ._layouts.open_orders import OPEN_ORDERS_LAYOUT
from .instructions import DEFAULT_DEX_PROGRAM_ID from .instructions import DEFAULT_DEX_PROGRAM_ID
@ -57,7 +57,10 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
@classmethod @classmethod
def from_bytes(cls: Type[_T], address: PublicKey, buffer: bytes) -> _T: def from_bytes(cls: Type[_T], address: PublicKey, buffer: bytes) -> _T:
open_order_decoded = OPEN_ORDERS_LAYOUT.parse(buffer) open_order_decoded = OPEN_ORDERS_LAYOUT.parse(buffer)
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 cls( return cls(
@ -70,38 +73,49 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
quote_token_total=open_order_decoded.quote_token_total, quote_token_total=open_order_decoded.quote_token_total,
free_slot_bits=int.from_bytes(open_order_decoded.free_slot_bits, "little"), free_slot_bits=int.from_bytes(open_order_decoded.free_slot_bits, "little"),
is_bid_bits=int.from_bytes(open_order_decoded.is_bid_bits, "little"), is_bid_bits=int.from_bytes(open_order_decoded.is_bid_bits, "little"),
orders=[int.from_bytes(order, "little") for order in open_order_decoded.orders], orders=[
int.from_bytes(order, "little") for order in open_order_decoded.orders
],
client_ids=open_order_decoded.client_ids, client_ids=open_order_decoded.client_ids,
) )
@classmethod @classmethod
def _process_get_program_accounts_resp(cls: Type[_T], resp: RPCResponse) -> List[_T]: def _process_get_program_accounts_resp(
cls: Type[_T], resp: GetProgramAccountsResp
) -> List[_T]:
accounts = [] accounts = []
for account in resp["result"]: for keyed_account in resp.value:
account_details = account["account"] account_details = keyed_account.account
accounts.append( accounts.append(
ProgramAccount( ProgramAccount(
public_key=PublicKey(account["pubkey"]), public_key=PublicKey(keyed_account.pubkey),
data=base64.decodebytes(account_details["data"][0].encode("ascii")), data=account_details.data,
is_executablable=bool(account_details["executable"]), is_executablable=account_details.executable,
owner=PublicKey(account_details["owner"]), owner=PublicKey(account_details.owner),
lamports=int(account_details["lamports"]), lamports=account_details.lamports,
) )
) )
return [cls.from_bytes(account.public_key, account.data) for account in accounts] return [
cls.from_bytes(account.public_key, account.data) for account in accounts
]
@staticmethod @staticmethod
def _build_get_program_accounts_args( def _build_get_program_accounts_args(
market: PublicKey, program_id: PublicKey, owner: PublicKey, commitment: Commitment market: PublicKey,
) -> Tuple[PublicKey, Commitment, str, None, int, List[MemcmpOpts]]: program_id: PublicKey,
owner: PublicKey,
commitment: Commitment,
) -> Tuple[PublicKey, Commitment, str, None, List[MemcmpOpts]]:
filters = [ filters = [
MemcmpOpts( MemcmpOpts(
offset=5 + 8, # 5 bytes of padding, 8 bytes of account flag offset=5 + 8, # 5 bytes of padding, 8 bytes of account flag
bytes=str(market), bytes=str(market),
), ),
MemcmpOpts( MemcmpOpts(
offset=5 + 8 + 32, # 5 bytes of padding, 8 bytes of account flag, 32 bytes of market public key offset=5
+ 8
+ 32, # 5 bytes of padding, 8 bytes of account flag, 32 bytes of market public key
bytes=str(owner), bytes=str(owner),
), ),
] ]
@ -111,7 +125,6 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
commitment, commitment,
"base64", "base64",
data_slice, data_slice,
OPEN_ORDERS_LAYOUT.sizeof(),
filters, filters,
) )
@ -119,7 +132,12 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
class OpenOrdersAccount(_OpenOrdersAccountCore): class OpenOrdersAccount(_OpenOrdersAccountCore):
@classmethod @classmethod
def find_for_market_and_owner( # pylint: disable=too-many-arguments def find_for_market_and_owner( # pylint: disable=too-many-arguments
cls, conn: Client, market: PublicKey, owner: PublicKey, program_id: PublicKey, commitment: Commitment = Recent cls,
conn: Client,
market: PublicKey,
owner: PublicKey,
program_id: PublicKey,
commitment: Commitment = Recent,
) -> List[OpenOrdersAccount]: ) -> List[OpenOrdersAccount]:
args = cls._build_get_program_accounts_args( args = cls._build_get_program_accounts_args(
market=market, program_id=program_id, owner=owner, commitment=commitment market=market, program_id=program_id, owner=owner, commitment=commitment

View File

@ -1,18 +1,16 @@
import base64
from solana.publickey import PublicKey from solana.publickey import PublicKey
from solana.rpc.api import Client from solana.rpc.api import Client
from solana.rpc.types import RPCResponse from solders.account import Account
from solders.rpc.responses import GetAccountInfoResp
from spl.token.constants import WRAPPED_SOL_MINT from spl.token.constants import WRAPPED_SOL_MINT
from pyserum._layouts.market import MINT_LAYOUT from pyserum._layouts.market import MINT_LAYOUT
def parse_bytes_data(res: RPCResponse) -> bytes: def parse_bytes_data(res: GetAccountInfoResp) -> bytes:
if ("result" not in res) or ("value" not in res["result"]) or ("data" not in res["result"]["value"]): if not isinstance(res.value, Account):
raise Exception("Cannot load byte data.") raise Exception("Cannot load byte data.")
data = res["result"]["value"]["data"][0] return res.value.data
return base64.decodebytes(data.encode("ascii"))
def load_bytes_data(addr: PublicKey, conn: Client) -> bytes: def load_bytes_data(addr: PublicKey, conn: Client) -> bytes: