update solanapy
This commit is contained in:
parent
49271da70f
commit
2954a7779f
4
Pipfile
4
Pipfile
|
@ -10,7 +10,7 @@ allow_prereleases = true
|
|||
install-ci-deps = "pipenv install --dev --skip-lock black pydocstyle flake8 pylint mypy pytest"
|
||||
|
||||
[dev-packages]
|
||||
jupyterlab = "*"
|
||||
notebook = "*"
|
||||
black = "*"
|
||||
pytest = "*"
|
||||
pylint = "*"
|
||||
|
@ -28,7 +28,7 @@ pytest-asyncio = "*"
|
|||
types-requests = "*"
|
||||
|
||||
[packages]
|
||||
solana = {version = ">=0.15.0"}
|
||||
solana = {version = ">=0.27.0"}
|
||||
construct = "*"
|
||||
flake8 = "*"
|
||||
construct-typing = "*"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,8 +6,9 @@ from typing import List
|
|||
from solana.keypair import Keypair
|
||||
from solana.publickey import PublicKey
|
||||
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 solders.rpc.responses import SendTransactionResp
|
||||
|
||||
import pyserum.market.types as t
|
||||
from pyserum import instructions
|
||||
|
@ -28,8 +29,15 @@ LAMPORTS_PER_SOL = 1000000000
|
|||
class AsyncMarket(MarketCore):
|
||||
"""Represents a Serum Market."""
|
||||
|
||||
def __init__(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)
|
||||
def __init__(
|
||||
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
|
||||
|
||||
@classmethod
|
||||
|
@ -50,7 +58,9 @@ class AsyncMarket(MarketCore):
|
|||
market_state = await MarketState.async_load(conn, market_address, program_id)
|
||||
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(
|
||||
self._conn, self.state.public_key(), owner_address, self.state.program_id()
|
||||
)
|
||||
|
@ -69,7 +79,9 @@ class AsyncMarket(MarketCore):
|
|||
"""Load orders for owner."""
|
||||
bids = await self.load_bids()
|
||||
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)
|
||||
|
||||
async def load_event_queue(self) -> List[t.Event]:
|
||||
|
@ -98,16 +110,23 @@ class AsyncMarket(MarketCore):
|
|||
max_quantity: float,
|
||||
client_id: int = 0,
|
||||
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()
|
||||
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:
|
||||
place_order_open_order_account = open_order_accounts[0].address
|
||||
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(
|
||||
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: Handle fee_discount_pubkey
|
||||
|
@ -128,18 +147,26 @@ class AsyncMarket(MarketCore):
|
|||
return await self._conn.send_transaction(transaction, *signers, opts=opts)
|
||||
|
||||
async def cancel_order_by_client_id(
|
||||
self, owner: Keypair, open_orders_account: PublicKey, client_id: int, opts: TxOpts = TxOpts()
|
||||
) -> RPCResponse:
|
||||
self,
|
||||
owner: Keypair,
|
||||
open_orders_account: PublicKey,
|
||||
client_id: int,
|
||||
opts: TxOpts = TxOpts(),
|
||||
) -> SendTransactionResp:
|
||||
txs = self._build_cancel_order_by_client_id_tx(
|
||||
owner=owner, open_orders_account=open_orders_account, client_id=client_id
|
||||
)
|
||||
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)
|
||||
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)
|
||||
return await self._conn.send_transaction(txn, fee_payer, opts=opts)
|
||||
|
||||
|
@ -150,12 +177,12 @@ class AsyncMarket(MarketCore):
|
|||
base_wallet: PublicKey,
|
||||
quote_wallet: PublicKey, # TODO: add referrer_quote_wallet.
|
||||
opts: TxOpts = TxOpts(),
|
||||
) -> RPCResponse:
|
||||
) -> SendTransactionResp:
|
||||
# TODO: Handle wrapped sol accounts
|
||||
should_wrap_sol = self._settle_funds_should_wrap_sol()
|
||||
if should_wrap_sol:
|
||||
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:
|
||||
min_bal_for_rent_exemption = 0 # value only matters if should_wrap_sol
|
||||
signers = [owner]
|
||||
|
|
|
@ -7,11 +7,16 @@ from typing import List, Union
|
|||
|
||||
from solana.keypair import Keypair
|
||||
from solana.publickey import PublicKey
|
||||
from solana.rpc.types import RPCResponse
|
||||
from solana.system_program import CreateAccountParams, create_account
|
||||
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.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
|
||||
from pyserum import instructions
|
||||
|
@ -32,20 +37,25 @@ class MarketCore:
|
|||
|
||||
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.force_use_request_queue = force_use_request_queue
|
||||
|
||||
def _use_request_queue(self) -> bool:
|
||||
return (
|
||||
# DEX Version 1
|
||||
self.state.program_id == PublicKey("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn")
|
||||
self.state.program_id
|
||||
== PublicKey("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn")
|
||||
or
|
||||
# DEX Version 1
|
||||
self.state.program_id == PublicKey("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg")
|
||||
self.state.program_id
|
||||
== PublicKey("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg")
|
||||
or
|
||||
# DEX Version 2
|
||||
self.state.program_id == PublicKey("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o")
|
||||
self.state.program_id
|
||||
== PublicKey("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o")
|
||||
or self.force_use_request_queue
|
||||
)
|
||||
|
||||
|
@ -58,7 +68,9 @@ class MarketCore:
|
|||
def find_best_fee_discount_key(self, owner: PublicKey, cache_duration: int):
|
||||
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")
|
||||
|
||||
def _parse_bids_or_asks(self, bytes_data: bytes) -> OrderBook:
|
||||
|
@ -71,7 +83,9 @@ class MarketCore:
|
|||
|
||||
all_orders = itertools.chain(bids.orders(), asks.orders())
|
||||
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
|
||||
|
||||
def load_base_token_for_owner(self):
|
||||
|
@ -110,11 +124,16 @@ class MarketCore:
|
|||
side=side,
|
||||
price=price,
|
||||
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(
|
||||
self, owner: Keypair, balance_needed: int, signers: List[Keypair], transaction: Transaction
|
||||
self,
|
||||
owner: Keypair,
|
||||
balance_needed: int,
|
||||
signers: List[Keypair],
|
||||
transaction: Transaction,
|
||||
) -> PublicKey:
|
||||
# new_open_orders_account = Account()
|
||||
new_open_orders_account = Keypair()
|
||||
|
@ -141,7 +160,9 @@ class MarketCore:
|
|||
limit_price: float,
|
||||
max_quantity: float,
|
||||
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,
|
||||
) -> None:
|
||||
# unwrapped SOL cannot be used for payment
|
||||
|
@ -149,9 +170,9 @@ class MarketCore:
|
|||
raise ValueError("Invalid payer account. Cannot use unwrapped SOL.")
|
||||
|
||||
# TODO: add integration test for SOL wrapping.
|
||||
should_wrap_sol = (side == Side.BUY and self.state.quote_mint() == WRAPPED_SOL_MINT) or (
|
||||
side == Side.SELL and self.state.base_mint() == WRAPPED_SOL_MINT
|
||||
)
|
||||
should_wrap_sol = (
|
||||
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:
|
||||
# wrapped_sol_account = Account()
|
||||
|
@ -208,10 +229,16 @@ class MarketCore:
|
|||
)
|
||||
|
||||
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:
|
||||
balance_needed = mbfre_resp["result"]
|
||||
place_order_open_order_account = self._prepare_new_oo_account(owner, balance_needed, signers, transaction)
|
||||
balance_needed = mbfre_resp.value
|
||||
place_order_open_order_account = self._prepare_new_oo_account(
|
||||
owner, balance_needed, signers, transaction
|
||||
)
|
||||
return place_order_open_order_account
|
||||
|
||||
@staticmethod
|
||||
|
@ -219,7 +246,9 @@ class MarketCore:
|
|||
price: float,
|
||||
size: float,
|
||||
side: Side,
|
||||
open_orders_accounts: Union[List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]],
|
||||
open_orders_accounts: Union[
|
||||
List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]
|
||||
],
|
||||
) -> int:
|
||||
lamports = 0
|
||||
if side == Side.BUY:
|
||||
|
@ -246,9 +275,9 @@ class MarketCore:
|
|||
fee_discount_pubkey: PublicKey = None,
|
||||
) -> TransactionInstruction:
|
||||
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:
|
||||
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():
|
||||
return instructions.new_order(
|
||||
instructions.NewOrderParams(
|
||||
|
@ -297,7 +326,11 @@ class MarketCore:
|
|||
def _build_cancel_order_by_client_id_tx(
|
||||
self, owner: Keypair, open_orders_account: PublicKey, client_id: int
|
||||
) -> 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(
|
||||
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:
|
||||
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():
|
||||
return instructions.cancel_order(
|
||||
instructions.CancelOrderParams(
|
||||
|
@ -389,7 +426,10 @@ class MarketCore:
|
|||
if open_orders.owner != owner.public_key:
|
||||
raise Exception("Invalid open orders account")
|
||||
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(),
|
||||
)
|
||||
transaction = Transaction()
|
||||
|
@ -425,8 +465,12 @@ class MarketCore:
|
|||
transaction.add(
|
||||
self.make_settle_funds_instruction(
|
||||
open_orders,
|
||||
base_wallet 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,
|
||||
base_wallet
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
@ -446,7 +490,9 @@ class MarketCore:
|
|||
return transaction
|
||||
|
||||
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(
|
||||
self,
|
||||
|
|
|
@ -6,8 +6,9 @@ from typing import List
|
|||
from solana.keypair import Keypair
|
||||
from solana.publickey import PublicKey
|
||||
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 solders.rpc.responses import SendTransactionResp
|
||||
|
||||
import pyserum.market.types as t
|
||||
from pyserum import instructions
|
||||
|
@ -28,8 +29,15 @@ LAMPORTS_PER_SOL = 1000000000
|
|||
class Market(MarketCore):
|
||||
"""Represents a Serum Market."""
|
||||
|
||||
def __init__(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)
|
||||
def __init__(
|
||||
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
|
||||
|
||||
@classmethod
|
||||
|
@ -50,7 +58,9 @@ class Market(MarketCore):
|
|||
market_state = MarketState.load(conn, market_address, program_id)
|
||||
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(
|
||||
self._conn, self.state.public_key(), owner_address, self.state.program_id()
|
||||
)
|
||||
|
@ -98,16 +108,21 @@ class Market(MarketCore):
|
|||
max_quantity: float,
|
||||
client_id: int = 0,
|
||||
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()
|
||||
signers: List[Keypair] = [owner]
|
||||
open_order_accounts = self.find_open_orders_accounts_for_owner(owner.public_key)
|
||||
if open_order_accounts:
|
||||
place_order_open_order_account = open_order_accounts[0].address
|
||||
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(
|
||||
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: Handle fee_discount_pubkey
|
||||
|
@ -128,18 +143,26 @@ class Market(MarketCore):
|
|||
return self._conn.send_transaction(transaction, *signers, opts=opts)
|
||||
|
||||
def cancel_order_by_client_id(
|
||||
self, owner: Keypair, open_orders_account: PublicKey, client_id: int, opts: TxOpts = TxOpts()
|
||||
) -> RPCResponse:
|
||||
self,
|
||||
owner: Keypair,
|
||||
open_orders_account: PublicKey,
|
||||
client_id: int,
|
||||
opts: TxOpts = TxOpts(),
|
||||
) -> SendTransactionResp:
|
||||
txs = self._build_cancel_order_by_client_id_tx(
|
||||
owner=owner, open_orders_account=open_orders_account, client_id=client_id
|
||||
)
|
||||
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)
|
||||
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)
|
||||
return self._conn.send_transaction(txn, fee_payer, opts=opts)
|
||||
|
||||
|
@ -150,11 +173,13 @@ class Market(MarketCore):
|
|||
base_wallet: PublicKey,
|
||||
quote_wallet: PublicKey, # TODO: add referrer_quote_wallet.
|
||||
opts: TxOpts = TxOpts(),
|
||||
) -> RPCResponse:
|
||||
) -> SendTransactionResp:
|
||||
# TODO: Handle wrapped sol accounts
|
||||
should_wrap_sol = self._settle_funds_should_wrap_sol()
|
||||
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
|
||||
signers = [owner]
|
||||
transaction = self._build_settle_funds_tx(
|
||||
|
|
|
@ -126,10 +126,10 @@ class MarketState: # pylint: disable=too-many-public-methods
|
|||
return self._quote_mint_decimals
|
||||
|
||||
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:
|
||||
return 10 ** self._quote_mint_decimals
|
||||
return 10**self._quote_mint_decimals
|
||||
|
||||
def base_spl_size_to_number(self, size: int) -> float:
|
||||
return size / self.base_spl_token_multiplier()
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from typing import List, NamedTuple, Tuple, Type, TypeVar
|
||||
|
||||
from solana.publickey import PublicKey
|
||||
from solana.rpc.api import Client
|
||||
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.transaction import TransactionInstruction
|
||||
from solders.rpc.responses import GetProgramAccountsResp
|
||||
|
||||
from ._layouts.open_orders import OPEN_ORDERS_LAYOUT
|
||||
from .instructions import DEFAULT_DEX_PROGRAM_ID
|
||||
|
@ -57,7 +57,10 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
|
|||
@classmethod
|
||||
def from_bytes(cls: Type[_T], address: PublicKey, buffer: bytes) -> _T:
|
||||
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.")
|
||||
|
||||
return cls(
|
||||
|
@ -70,38 +73,49 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
|
|||
quote_token_total=open_order_decoded.quote_token_total,
|
||||
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"),
|
||||
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,
|
||||
)
|
||||
|
||||
@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 = []
|
||||
for account in resp["result"]:
|
||||
account_details = account["account"]
|
||||
for keyed_account in resp.value:
|
||||
account_details = keyed_account.account
|
||||
accounts.append(
|
||||
ProgramAccount(
|
||||
public_key=PublicKey(account["pubkey"]),
|
||||
data=base64.decodebytes(account_details["data"][0].encode("ascii")),
|
||||
is_executablable=bool(account_details["executable"]),
|
||||
owner=PublicKey(account_details["owner"]),
|
||||
lamports=int(account_details["lamports"]),
|
||||
public_key=PublicKey(keyed_account.pubkey),
|
||||
data=account_details.data,
|
||||
is_executablable=account_details.executable,
|
||||
owner=PublicKey(account_details.owner),
|
||||
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
|
||||
def _build_get_program_accounts_args(
|
||||
market: PublicKey, program_id: PublicKey, owner: PublicKey, commitment: Commitment
|
||||
) -> Tuple[PublicKey, Commitment, str, None, int, List[MemcmpOpts]]:
|
||||
market: PublicKey,
|
||||
program_id: PublicKey,
|
||||
owner: PublicKey,
|
||||
commitment: Commitment,
|
||||
) -> Tuple[PublicKey, Commitment, str, None, List[MemcmpOpts]]:
|
||||
filters = [
|
||||
MemcmpOpts(
|
||||
offset=5 + 8, # 5 bytes of padding, 8 bytes of account flag
|
||||
bytes=str(market),
|
||||
),
|
||||
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),
|
||||
),
|
||||
]
|
||||
|
@ -111,7 +125,6 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
|
|||
commitment,
|
||||
"base64",
|
||||
data_slice,
|
||||
OPEN_ORDERS_LAYOUT.sizeof(),
|
||||
filters,
|
||||
)
|
||||
|
||||
|
@ -119,7 +132,12 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
|
|||
class OpenOrdersAccount(_OpenOrdersAccountCore):
|
||||
@classmethod
|
||||
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]:
|
||||
args = cls._build_get_program_accounts_args(
|
||||
market=market, program_id=program_id, owner=owner, commitment=commitment
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import base64
|
||||
|
||||
from solana.publickey import PublicKey
|
||||
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 pyserum._layouts.market import MINT_LAYOUT
|
||||
|
||||
|
||||
def parse_bytes_data(res: RPCResponse) -> bytes:
|
||||
if ("result" not in res) or ("value" not in res["result"]) or ("data" not in res["result"]["value"]):
|
||||
def parse_bytes_data(res: GetAccountInfoResp) -> bytes:
|
||||
if not isinstance(res.value, Account):
|
||||
raise Exception("Cannot load byte data.")
|
||||
data = res["result"]["value"]["data"][0]
|
||||
return base64.decodebytes(data.encode("ascii"))
|
||||
return res.value.data
|
||||
|
||||
|
||||
def load_bytes_data(addr: PublicKey, conn: Client) -> bytes:
|
||||
|
|
Loading…
Reference in New Issue