Compare commits

...

6 Commits

Author SHA1 Message Date
waterquarks 1e173beb7e Ignore .idea 2023-04-24 22:22:39 +00:00
waterquarks 6ccb0e3b44 Remove unused 2023-04-24 22:22:06 +00:00
Ennio Nasca cbb92d96b2 docs: update readme with release instructions 2023-02-02 15:16:30 +01:00
Ennio Nasca 2dc5b3a5ce
Merge pull request #3 from ennnas/chore/solanapy_0.29
chore: update to solanapy 0.29
2023-02-02 15:09:17 +01:00
Ennio Nasca 595946a215 chore: update to solanapy 0.29 2023-02-02 15:08:41 +01:00
Ennio Nasca 74152dd8a2 chore: update to solanapy 0.29 2023-02-02 12:41:55 +01:00
47 changed files with 1748 additions and 2403 deletions

View File

@ -1,26 +0,0 @@
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "70...100"
status:
project:
default:
target: auto
threshold: 1.5%
patch: no
changes: no
comment:
layout: "header, diff"
behavior: default
require_changes: no
ignore:
- tests
- docs/conf.py
- setup.py

View File

@ -1,2 +0,0 @@
[flake8]
max-line-length=120

View File

@ -1,41 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: Docs
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [alpha]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: 3.7
- uses: dschep/install-pipenv-action@v1
- name: Doc dependencies
run: pipenv lock -r > requirements.txt
- name: Sphinx Build
uses: ammaraskar/sphinx-action@0.4
with:
pre-build-command: pip install sphinxemoji
docs-folder: .
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./_build/html
force_orphan: true

View File

@ -1,40 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: DEX Integration
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the alpha branch
on:
push:
branches: [alpha, master]
pull_request:
branches: [alpha, master]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
test:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install pipenv
uses: dschep/install-pipenv-action@v1
- name: Install test dependencies
run: pipenv install --skip-lock pytest
- name: Run integration tests
run: scripts/run_int_tests.sh
- name: Run async integration tests
run: scripts/run_async_int_tests.sh

View File

@ -1,78 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [alpha, master]
pull_request:
branches: [alpha, master]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
test:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install pipenv
uses: dschep/install-pipenv-action@v1
- name: Install CI dependencies
run: |
pipenv run install-ci-deps
- name: Run linters
run: |
pipenv run black --check --diff --line-length=120 setup.py pyserum tests
pipenv run pylint --rcfile=.pylintrc setup.py pyserum tests
pipenv run flake8 setup.py pyserum tests
pipenv run mypy pyserum
- name: Run unit tests
run: |
pipenv run pytest -v -m "not integration and not async_integration"
coverage:
# The type of runner that the job will run on
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install pipenv
uses: dschep/install-pipenv-action@v1
- name: Generate coverage report
run: |
pipenv install --dev --skip-lock pytest pytest-cov
scripts/run_coverage.sh
- name: Upload coverage to Codecov
# You may pin to the exact commit or the version.
# uses: codecov/codecov-action@6004246f47ab62d32be025ce173b241cd84ac58e
uses: codecov/codecov-action@v1.0.13
with:
token: ${{ secrets.CODECOV_TOKEN }}
# Path to coverage file to upload
file: ./coverage.xml
env_vars: OS,PYTHON
# Specify whether or not CI build should fail if Codecov runs into an error during upload
fail_ci_if_error: true

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
.idea
# notebook checkpoints
.ipynb_checkpoints

View File

@ -1,2 +0,0 @@
[pydocstyle]
ignore=D401,D203,D213

598
.pylintrc
View File

@ -1,598 +0,0 @@
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=
# Specify a score threshold to be exceeded before program exits with error.
fail-under=10
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=missing-class-docstring,
missing-function-docstring,
missing-module-docstring,
parameter-unpacking,
unpacking-in-except,
old-raise-syntax,
bad-continuation,
backtick,
long-suffix,
old-ne-operator,
old-octal-literal,
import-star-module-level,
non-ascii-bytes-literal,
raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
apply-builtin,
basestring-builtin,
buffer-builtin,
cmp-builtin,
coerce-builtin,
execfile-builtin,
file-builtin,
long-builtin,
raw_input-builtin,
reduce-builtin,
standarderror-builtin,
unicode-builtin,
xrange-builtin,
coerce-method,
delslice-method,
getslice-method,
setslice-method,
no-absolute-import,
old-division,
dict-iter-method,
dict-view-method,
next-method-called,
metaclass-assignment,
indexing-exception,
raising-string,
reload-builtin,
oct-method,
hex-method,
nonzero-method,
cmp-method,
input-builtin,
round-builtin,
intern-builtin,
unichr-builtin,
map-builtin-not-iterating,
zip-builtin-not-iterating,
range-builtin-not-iterating,
filter-builtin-not-iterating,
using-cmp-argument,
eq-without-hash,
div-method,
idiv-method,
rdiv-method,
exception-message-attribute,
invalid-str-codec,
sys-max-int,
bad-python3-import,
deprecated-string-function,
deprecated-str-translate-call,
deprecated-itertools-function,
deprecated-types-field,
next-method-defined,
dict-items-not-iterating,
dict-keys-not-iterating,
dict-values-not-iterating,
deprecated-operator-function,
deprecated-urllib-function,
xreadlines-attribute,
deprecated-sys-function,
exception-escape,
comprehension-escape,
duplicate-code
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'error', 'warning', 'refactor', and 'convention'
# which contain the number of messages in each category, as well as 'statement'
# which is the total number of statements analyzed. This score is used by the
# global evaluation report (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: none. To make it work,
# install the python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
# Regular expression of note tags to take in consideration.
#notes-rgx=
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# List of decorators that change the signature of a decorated function.
signature-mutators=
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=120
# Maximum number of lines in a module.
max-module-lines=1000
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[SIMILARITIES]
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
# Minimum lines number of a similarity.
min-similarity-lines=4
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style.
#variable-rgx=
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[DESIGN]
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=BaseException,
Exception

14
.vscode/settings.json vendored
View File

@ -1,14 +0,0 @@
{
"python.envFile": "${workspaceFolder}/.env",
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.nosetestsEnabled": false,
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--check", "--line-length", "120"],
"python.linting.pylintEnabled": true,
"python.linting.pylintArgs": ["--max-line-length", "120"],
"python.venvPath": "~/.local/share/virtualenvs/",
"editor.formatOnSave": true,
"python.languageServer": "Jedi",
"python.jediEnabled": true
}

View File

@ -28,7 +28,7 @@ pytest-asyncio = "*"
types-requests = "*"
[packages]
solana = {version = ">=0.27.0"}
solana = {version = ">=0.29.0"}
construct = "*"
flake8 = "*"
construct-typing = "*"

1579
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,3 @@
[![Actions
Status](https://github.com/serum-community/pyserum/workflows/CI/badge.svg)](https://github.com/serum-community/pyserum/actions?query=workflow%3ACI)
[![Codecov](https://codecov.io/gh/serum-community/pyserum/branch/alpha/graph/badge.svg)](https://codecov.io/gh/serum-community/pyserum/branches/alpha)
# PySerum
Python client library for interacting with the [Project Serum](https://projectserum.com/) DEX.
@ -147,3 +143,16 @@ make notebook
This will start a docker container with `solana` image and deploy a serum DEX which you can use for testing.
The market address, program id, and wallet addresses can be found in the new `crank.log` file after the script runs successfully.
### Release
To release a new version remember to update the version in the `setup.py` file and then run on the latest commit:
```sh
git tag -a v0.7.0a0 -m "[MESSAGE]"
git push origin --tags
python setup.py sdist bdist_wheel
```
Go on github and create the new release with the latest tag and upload the artifacts from the `dist` folder.

View File

@ -1,8 +0,0 @@
version: '3'
services:
localnet:
image: "solanalabs/solana:v1.4.18"
ports:
- "8899:8899"
- "8900:8900"
- "9900:9900"

View File

@ -1,52 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath(".."))
# -- Project information -----------------------------------------------------
project = "pyserum"
copyright = "2020, serum-community"
author = "Michael Huang, Leonard Ge"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx.ext.coverage",
"sphinx.ext.doctest",
"sphinxemoji.sphinxemoji",
]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "alabaster"

View File

@ -1,27 +0,0 @@
.. pyserum documentation master file, created by
sphinx-quickstart on Sun Oct 4 18:54:35 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to pyserum's documentation!
===================================
|:construction:| |:construction:| |:construction:| |:construction:| |:construction:| **Under Construction!** |:construction:| |:construction:| |:construction:| |:construction:| |:construction:|
.. toctree::
:maxdepth: 2
:caption: Contents:
market
instructions
|:construction:| |:construction:| |:construction:| |:construction:| |:construction:| **Under Construction!** |:construction:| |:construction:| |:construction:| |:construction:| |:construction:|
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -1,11 +0,0 @@
DEX Instructions
================
.. automodule:: pyserum.instructions
:members:
Enums
=====
.. automodule:: pyserum.enums
:members:

View File

@ -1,25 +0,0 @@
Serum Market
============
.. automodule:: pyserum.market
.. autoclass:: pyserum.market.Market
:members:
Market State
------------
.. automodule:: pyserum.market.state
.. autoclass:: pyserum.market.state.MarketState
:members:
Orderbook
---------
.. automodule:: pyserum.market.orderbook
:members:
Types
-----
.. automodule:: pyserum.market.types
:members:

View File

@ -1,46 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from solana.rpc.api import Client\n",
"from solana.publickey import PublicKey\n",
"\n",
"local_client = Client()\n",
"local_dex = PublicKey(\"35xWbYPmt7hzyjsmJ9m3hZekN63mmeQDTygd7i8zudiy\")\n",
"local_client.get_account_info(local_dex)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -1,110 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pyserum.market import Market\n",
"\n",
"market = Market.load(\"https://api.mainnet-beta.solana.com\", \"CAgAeMD7quTdnr6RPa7JySQpjf3irAmefYNdTb6anemq\", None)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert market is not None\n",
"assert isinstance(market, Market)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"market"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"asks = market.load_asks()\n",
"asks"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pyserum.market import Market\n",
"\n",
"endpoint = \"https://api.mainnet-beta.solana.com/\"\n",
"market_address = \"CAgAeMD7quTdnr6RPa7JySQpjf3irAmefYNdTb6anemq\" # Address for BTC/USDC\n",
"\n",
"# Load the given market\n",
"market = Market.load(endpoint, market_address, None)\n",
"asks = market.load_asks()\n",
"# Show all current ask order\n",
"print(\"Ask Orders:\")\n",
"for ask in asks:\n",
" print(\"Order id: %d, price: %f, size: %f.\" % (\n",
" ask.order_id, ask.order_info.price, ask.order_info.size))\n",
"\n",
"print(\"\\n\")\n",
"# Show all current bid order\n",
"print(\"Bid Orders:\")\n",
"bids = market.load_bids()\n",
"for bid in bids:\n",
" print(\"Order id: %d, price: %f, size: %f.\" % (\n",
" bid.order_id, bid.order_info.price, bid.order_info.size))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"market.load_fills()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -1,4 +1,15 @@
from construct import BitsInteger, BitsSwapped, BitStruct, 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

View File

@ -33,7 +33,9 @@ class NodeType(IntEnum):
# Different node types, we pad it all to size of 68 bytes.
UNINTIALIZED = cStruct(Padding(68))
INNER_NODE = cStruct("prefix_len" / Int32ul, "key" / KEY, "children" / Int32ul[2], Padding(40))
INNER_NODE = cStruct(
"prefix_len" / Int32ul, "key" / KEY, "children" / Int32ul[2], Padding(40)
)
LEAF_NODE = cStruct(
"owner_slot" / Int8ul,
"fee_tier" / Int8ul,
@ -61,6 +63,14 @@ SLAB_NODE_LAYOUT = cStruct(
),
)
SLAB_LAYOUT = cStruct("header" / SLAB_HEADER_LAYOUT, "nodes" / SLAB_NODE_LAYOUT[lambda this: this.header.bump_index])
SLAB_LAYOUT = cStruct(
"header" / SLAB_HEADER_LAYOUT,
"nodes" / SLAB_NODE_LAYOUT[lambda this: this.header.bump_index],
)
ORDER_BOOK_LAYOUT = cStruct(Padding(5), "account_flags" / ACCOUNT_FLAGS_LAYOUT, "slab_layout" / SLAB_LAYOUT, Padding(7))
ORDER_BOOK_LAYOUT = cStruct(
Padding(5),
"account_flags" / ACCOUNT_FLAGS_LAYOUT,
"slab_layout" / SLAB_LAYOUT,
Padding(7),
)

View File

@ -1,9 +1,16 @@
from typing import List
import httpx
from solana.rpc.async_api import AsyncClient as async_conn # pylint: disable=unused-import # noqa:F401
from solana.rpc.async_api import (
AsyncClient as async_conn,
) # pylint: disable=unused-import # noqa:F401
from .connection import LIVE_MARKETS_URL, TOKEN_MINTS_URL, parse_live_markets, parse_token_mints
from .connection import (
LIVE_MARKETS_URL,
TOKEN_MINTS_URL,
parse_live_markets,
parse_token_mints,
)
from .market.types import MarketInfo, TokenInfo

View File

@ -2,7 +2,7 @@ from __future__ import annotations
from typing import List
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Processed
from solana.rpc.types import Commitment
@ -16,9 +16,9 @@ class AsyncOpenOrdersAccount(_OpenOrdersAccountCore):
async def find_for_market_and_owner( # pylint: disable=too-many-arguments
cls,
conn: AsyncClient,
market: PublicKey,
owner: PublicKey,
program_id: PublicKey,
market: Pubkey,
owner: Pubkey,
program_id: Pubkey,
commitment: Commitment = Processed,
) -> List[AsyncOpenOrdersAccount]:
args = cls._build_get_program_accounts_args(
@ -29,6 +29,6 @@ class AsyncOpenOrdersAccount(_OpenOrdersAccountCore):
@classmethod
async def load(cls, conn: AsyncClient, address: str) -> AsyncOpenOrdersAccount:
addr_pub_key = PublicKey(address)
addr_pub_key = Pubkey.from_string(address)
bytes_data = await load_bytes_data(addr_pub_key, conn)
return cls.from_bytes(addr_pub_key, bytes_data)

View File

@ -1,16 +1,16 @@
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from solana.rpc.async_api import AsyncClient
from spl.token.constants import WRAPPED_SOL_MINT
from pyserum.utils import parse_bytes_data, parse_mint_decimals
async def load_bytes_data(addr: PublicKey, conn: AsyncClient) -> bytes:
async def load_bytes_data(addr: Pubkey, conn: AsyncClient) -> bytes:
res = await conn.get_account_info(addr)
return parse_bytes_data(res)
async def get_mint_decimals(conn: AsyncClient, mint_pub_key: PublicKey) -> int:
async def get_mint_decimals(conn: AsyncClient, mint_pub_key: Pubkey) -> int:
"""Get the mint decimals for a token mint"""
if mint_pub_key == WRAPPED_SOL_MINT:
return 9

View File

@ -1,7 +1,7 @@
from typing import Any, Dict, List
import requests
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from solana.rpc.api import Client as conn # pylint: disable=unused-import # noqa:F401
from .market.types import MarketInfo, TokenInfo
@ -12,12 +12,17 @@ TOKEN_MINTS_URL = "https://raw.githubusercontent.com/project-serum/serum-ts/mast
def parse_live_markets(data: List[Dict[str, Any]]) -> List[MarketInfo]:
return [
MarketInfo(name=m["name"], address=m["address"], program_id=m["programId"]) for m in data if not m["deprecated"]
MarketInfo(name=m["name"], address=m["address"], program_id=m["programId"])
for m in data
if not m["deprecated"]
]
def parse_token_mints(data: List[Dict[str, str]]) -> List[TokenInfo]:
return [TokenInfo(name=t["name"], address=PublicKey(t["address"])) for t in data]
return [
TokenInfo(name=t["name"], address=Pubkey.from_string(t["address"]))
for t in data
]
def get_live_markets() -> List[MarketInfo]:

View File

@ -2,39 +2,42 @@
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 solders.sysvar import RENT
from solana.transaction import AccountMeta
from solders.instruction import Instruction
from solana.utils.validate import validate_instruction_keys, validate_instruction_type
from solders.pubkey import Pubkey
from spl.token.constants import TOKEN_PROGRAM_ID
from ._layouts.instructions import INSTRUCTIONS_LAYOUT, InstructionType
from .enums import OrderType, SelfTradeBehavior, Side
# V3
DEFAULT_DEX_PROGRAM_ID = PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
DEFAULT_DEX_PROGRAM_ID = Pubkey.from_string(
"9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
)
class InitializeMarketParams(NamedTuple):
"""Initalize market params."""
market: PublicKey
market: Pubkey
""""""
request_queue: PublicKey
request_queue: Pubkey
""""""
event_queue: PublicKey
event_queue: Pubkey
""""""
bids: PublicKey
bids: Pubkey
""""""
asks: PublicKey
asks: Pubkey
""""""
base_vault: PublicKey
base_vault: Pubkey
""""""
quote_vault: PublicKey
quote_vault: Pubkey
""""""
base_mint: PublicKey
base_mint: Pubkey
""""""
quote_mint: PublicKey
quote_mint: Pubkey
""""""
base_lot_size: int
""""""
@ -46,25 +49,25 @@ class InitializeMarketParams(NamedTuple):
""""""
quote_dust_threshold: int
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
class NewOrderParams(NamedTuple):
"""New order params."""
market: PublicKey
market: Pubkey
""""""
open_orders: PublicKey
open_orders: Pubkey
""""""
payer: PublicKey
payer: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
request_queue: PublicKey
request_queue: Pubkey
""""""
base_vault: PublicKey
base_vault: Pubkey
""""""
quote_vault: PublicKey
quote_vault: Pubkey
""""""
side: Side
""""""
@ -76,58 +79,58 @@ class NewOrderParams(NamedTuple):
""""""
client_id: int = 0
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
class MatchOrdersParams(NamedTuple):
"""Match order params."""
market: PublicKey
market: Pubkey
""""""
request_queue: PublicKey
request_queue: Pubkey
""""""
event_queue: PublicKey
event_queue: Pubkey
""""""
bids: PublicKey
bids: Pubkey
""""""
asks: PublicKey
asks: Pubkey
""""""
base_vault: PublicKey
base_vault: Pubkey
""""""
quote_vault: PublicKey
quote_vault: Pubkey
""""""
limit: int
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
class ConsumeEventsParams(NamedTuple):
"""Consume events params."""
market: PublicKey
market: Pubkey
""""""
event_queue: PublicKey
event_queue: Pubkey
""""""
open_orders_accounts: List[PublicKey]
open_orders_accounts: List[Pubkey]
""""""
limit: int
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
class CancelOrderParams(NamedTuple):
"""Cancel order params."""
market: PublicKey
market: Pubkey
""""""
open_orders: PublicKey
open_orders: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
request_queue: PublicKey
request_queue: Pubkey
""""""
side: Side
""""""
@ -135,71 +138,71 @@ class CancelOrderParams(NamedTuple):
""""""
open_orders_slot: int
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
class CancelOrderByClientIDParams(NamedTuple):
"""Cancel order by client ID params."""
market: PublicKey
market: Pubkey
""""""
open_orders: PublicKey
open_orders: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
request_queue: PublicKey
request_queue: Pubkey
""""""
client_id: int
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
class SettleFundsParams(NamedTuple):
"""Settle fund params."""
market: PublicKey
market: Pubkey
""""""
open_orders: PublicKey
open_orders: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
base_vault: PublicKey
base_vault: Pubkey
""""""
quote_vault: PublicKey
quote_vault: Pubkey
""""""
base_wallet: PublicKey
base_wallet: Pubkey
""""""
quote_wallet: PublicKey
quote_wallet: Pubkey
""""""
vault_signer: PublicKey
vault_signer: Pubkey
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
class NewOrderV3Params(NamedTuple):
"""New order params."""
market: PublicKey
market: Pubkey
""""""
open_orders: PublicKey
open_orders: Pubkey
""""""
payer: PublicKey
payer: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
request_queue: PublicKey
request_queue: Pubkey
""""""
event_queue: PublicKey
event_queue: Pubkey
""""""
bids: PublicKey
bids: Pubkey
""""""
asks: PublicKey
asks: Pubkey
""""""
base_vault: PublicKey
base_vault: Pubkey
""""""
quote_vault: PublicKey
quote_vault: Pubkey
""""""
side: Side
""""""
@ -217,25 +220,25 @@ class NewOrderV3Params(NamedTuple):
""""""
client_id: int = 0
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
fee_discount_pubkey: Optional[PublicKey] = None
fee_discount_pubkey: Optional[Pubkey] = None
class CancelOrderV2Params(NamedTuple):
"""Cancel order params."""
market: PublicKey
market: Pubkey
""""""
bids: PublicKey
bids: Pubkey
""""""
asks: PublicKey
asks: Pubkey
""""""
event_queue: PublicKey
event_queue: Pubkey
""""""
open_orders: PublicKey
open_orders: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
side: Side
""""""
@ -243,63 +246,63 @@ class CancelOrderV2Params(NamedTuple):
""""""
open_orders_slot: int
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
class CancelOrderByClientIDV2Params(NamedTuple):
"""Cancel order by client ID params."""
market: PublicKey
market: Pubkey
""""""
bids: PublicKey
bids: Pubkey
""""""
asks: PublicKey
asks: Pubkey
""""""
event_queue: PublicKey
event_queue: Pubkey
""""""
open_orders: PublicKey
open_orders: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
client_id: int
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
class CloseOpenOrdersParams(NamedTuple):
"""Cancel order by client ID params."""
open_orders: PublicKey
open_orders: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
sol_wallet: PublicKey
sol_wallet: Pubkey
""""""
market: PublicKey
market: Pubkey
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
class InitOpenOrdersParams(NamedTuple):
"""Cancel order by client ID params."""
open_orders: PublicKey
open_orders: Pubkey
""""""
owner: PublicKey
owner: Pubkey
""""""
market: PublicKey
market: Pubkey
""""""
market_authority: Optional[PublicKey] = None
market_authority: Optional[Pubkey] = None
""""""
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID
""""""
def __parse_and_validate_instruction(
instruction: TransactionInstruction, instruction_type: InstructionType
instruction: Instruction, instruction_type: InstructionType
) -> Container:
instruction_type_to_length_map: Dict[InstructionType, int] = {
InstructionType.INITIALIZE_MARKET: 9,
@ -315,27 +318,31 @@ def __parse_and_validate_instruction(
InstructionType.CLOSE_OPEN_ORDERS: 4,
InstructionType.INIT_OPEN_ORDERS: 3,
}
validate_instruction_keys(instruction, instruction_type_to_length_map[instruction_type])
validate_instruction_keys(
instruction, instruction_type_to_length_map[instruction_type]
)
data = INSTRUCTIONS_LAYOUT.parse(instruction.data)
validate_instruction_type(data, instruction_type)
return data
def decode_initialize_market(
instruction: TransactionInstruction,
instruction: Instruction,
) -> InitializeMarketParams:
"""Decode an instialize market instruction and retrieve the instruction params."""
data = __parse_and_validate_instruction(instruction, InstructionType.INITIALIZE_MARKET)
data = __parse_and_validate_instruction(
instruction, InstructionType.INITIALIZE_MARKET
)
return InitializeMarketParams(
market=instruction.keys[0].pubkey,
request_queue=instruction.keys[1].pubkey,
event_queue=instruction.keys[2].pubkey,
bids=instruction.keys[3].pubkey,
asks=instruction.keys[4].pubkey,
base_vault=instruction.keys[5].pubkey,
quote_vault=instruction.keys[6].pubkey,
base_mint=instruction.keys[7].pubkey,
quote_mint=instruction.keys[8].pubkey,
market=instruction.accounts[0].pubkey,
request_queue=instruction.accounts[1].pubkey,
event_queue=instruction.accounts[2].pubkey,
bids=instruction.accounts[3].pubkey,
asks=instruction.accounts[4].pubkey,
base_vault=instruction.accounts[5].pubkey,
quote_vault=instruction.accounts[6].pubkey,
base_mint=instruction.accounts[7].pubkey,
quote_mint=instruction.accounts[8].pubkey,
base_lot_size=data.args.base_lot_size,
quote_lot_size=data.args.quote_lot_size,
fee_rate_bps=data.args.fee_rate_bps,
@ -345,16 +352,16 @@ def decode_initialize_market(
)
def decode_new_order(instruction: TransactionInstruction) -> NewOrderParams:
def decode_new_order(instruction: Instruction) -> NewOrderParams:
data = __parse_and_validate_instruction(instruction, InstructionType.NEW_ORDER)
return NewOrderParams(
market=instruction.keys[0].pubkey,
open_orders=instruction.keys[1].pubkey,
request_queue=instruction.keys[2].pubkey,
payer=instruction.keys[3].pubkey,
owner=instruction.keys[4].pubkey,
base_vault=instruction.keys[5].pubkey,
quote_vault=instruction.keys[6].pubkey,
market=instruction.accounts[0].pubkey,
open_orders=instruction.accounts[1].pubkey,
request_queue=instruction.accounts[2].pubkey,
payer=instruction.accounts[3].pubkey,
owner=instruction.accounts[4].pubkey,
base_vault=instruction.accounts[5].pubkey,
quote_vault=instruction.accounts[6].pubkey,
side=data.args.side,
limit_price=data.args.limit_price,
max_quantity=data.args.max_quantity,
@ -363,86 +370,88 @@ def decode_new_order(instruction: TransactionInstruction) -> NewOrderParams:
)
def decode_match_orders(instruction: TransactionInstruction) -> MatchOrdersParams:
def decode_match_orders(instruction: Instruction) -> MatchOrdersParams:
"""Decode a match orders instruction and retrieve the instruction params."""
data = __parse_and_validate_instruction(instruction, InstructionType.MATCH_ORDER)
return MatchOrdersParams(
market=instruction.keys[0].pubkey,
request_queue=instruction.keys[1].pubkey,
event_queue=instruction.keys[2].pubkey,
bids=instruction.keys[3].pubkey,
asks=instruction.keys[4].pubkey,
base_vault=instruction.keys[5].pubkey,
quote_vault=instruction.keys[6].pubkey,
market=instruction.accounts[0].pubkey,
request_queue=instruction.accounts[1].pubkey,
event_queue=instruction.accounts[2].pubkey,
bids=instruction.accounts[3].pubkey,
asks=instruction.accounts[4].pubkey,
base_vault=instruction.accounts[5].pubkey,
quote_vault=instruction.accounts[6].pubkey,
limit=data.args.limit,
)
def decode_consume_events(instruction: TransactionInstruction) -> ConsumeEventsParams:
def decode_consume_events(instruction: Instruction) -> ConsumeEventsParams:
"""Decode a consume events instruction and retrieve the instruction params."""
data = __parse_and_validate_instruction(instruction, InstructionType.CONSUME_EVENTS)
return ConsumeEventsParams(
open_orders_accounts=[a_m.pubkey for a_m in instruction.keys[:-4]],
market=instruction.keys[-4].pubkey,
event_queue=instruction.keys[-3].pubkey,
open_orders_accounts=[a_m.pubkey for a_m in instruction.accounts[:-4]],
market=instruction.accounts[-4].pubkey,
event_queue=instruction.accounts[-3].pubkey,
# NOTE - ignoring pc_fee and coin_fee as unused
limit=data.args.limit,
)
def decode_cancel_order(instruction: TransactionInstruction) -> CancelOrderParams:
def decode_cancel_order(instruction: Instruction) -> CancelOrderParams:
data = __parse_and_validate_instruction(instruction, InstructionType.CANCEL_ORDER)
return CancelOrderParams(
market=instruction.keys[0].pubkey,
open_orders=instruction.keys[1].pubkey,
request_queue=instruction.keys[2].pubkey,
owner=instruction.keys[3].pubkey,
market=instruction.accounts[0].pubkey,
open_orders=instruction.accounts[1].pubkey,
request_queue=instruction.accounts[2].pubkey,
owner=instruction.accounts[3].pubkey,
side=Side(data.args.side),
order_id=int.from_bytes(data.args.order_id, "little"),
open_orders_slot=data.args.open_orders_slot,
)
def decode_settle_funds(instruction: TransactionInstruction) -> SettleFundsParams:
def decode_settle_funds(instruction: Instruction) -> SettleFundsParams:
# data = __parse_and_validate_instruction(instruction, InstructionType.SettleFunds)
return SettleFundsParams(
market=instruction.keys[0].pubkey,
open_orders=instruction.keys[1].pubkey,
owner=instruction.keys[2].pubkey,
base_vault=instruction.keys[3].pubkey,
quote_vault=instruction.keys[4].pubkey,
base_wallet=instruction.keys[5].pubkey,
quote_wallet=instruction.keys[6].pubkey,
vault_signer=instruction.keys[7].pubkey,
market=instruction.accounts[0].pubkey,
open_orders=instruction.accounts[1].pubkey,
owner=instruction.accounts[2].pubkey,
base_vault=instruction.accounts[3].pubkey,
quote_vault=instruction.accounts[4].pubkey,
base_wallet=instruction.accounts[5].pubkey,
quote_wallet=instruction.accounts[6].pubkey,
vault_signer=instruction.accounts[7].pubkey,
)
def decode_cancel_order_by_client_id(
instruction: TransactionInstruction,
instruction: Instruction,
) -> CancelOrderByClientIDParams:
data = __parse_and_validate_instruction(instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID)
data = __parse_and_validate_instruction(
instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID
)
return CancelOrderByClientIDParams(
market=instruction.keys[0].pubkey,
open_orders=instruction.keys[1].pubkey,
request_queue=instruction.keys[2].pubkey,
owner=instruction.keys[3].pubkey,
market=instruction.accounts[0].pubkey,
open_orders=instruction.accounts[1].pubkey,
request_queue=instruction.accounts[2].pubkey,
owner=instruction.accounts[3].pubkey,
client_id=data.args.client_id,
)
def decode_new_order_v3(instruction: TransactionInstruction) -> NewOrderV3Params:
def decode_new_order_v3(instruction: Instruction) -> NewOrderV3Params:
data = __parse_and_validate_instruction(instruction, InstructionType.NEW_ORDER_V3)
return NewOrderV3Params(
market=instruction.keys[0].pubkey,
open_orders=instruction.keys[1].pubkey,
request_queue=instruction.keys[2].pubkey,
event_queue=instruction.keys[3].pubkey,
bids=instruction.keys[4].pubkey,
asks=instruction.keys[5].pubkey,
payer=instruction.keys[6].pubkey,
owner=instruction.keys[7].pubkey,
base_vault=instruction.keys[8].pubkey,
quote_vault=instruction.keys[9].pubkey,
market=instruction.accounts[0].pubkey,
open_orders=instruction.accounts[1].pubkey,
request_queue=instruction.accounts[2].pubkey,
event_queue=instruction.accounts[3].pubkey,
bids=instruction.accounts[4].pubkey,
asks=instruction.accounts[5].pubkey,
payer=instruction.accounts[6].pubkey,
owner=instruction.accounts[7].pubkey,
base_vault=instruction.accounts[8].pubkey,
quote_vault=instruction.accounts[9].pubkey,
side=data.args.side,
limit_price=data.args.limit_price,
max_base_quantity=data.args.max_base_quantity,
@ -454,61 +463,69 @@ def decode_new_order_v3(instruction: TransactionInstruction) -> NewOrderV3Params
)
def decode_cancel_order_v2(instruction: TransactionInstruction) -> CancelOrderV2Params:
data = __parse_and_validate_instruction(instruction, InstructionType.CANCEL_ORDER_V2)
def decode_cancel_order_v2(instruction: Instruction) -> CancelOrderV2Params:
data = __parse_and_validate_instruction(
instruction, InstructionType.CANCEL_ORDER_V2
)
return CancelOrderV2Params(
market=instruction.keys[0].pubkey,
bids=instruction.keys[1].pubkey,
asks=instruction.keys[2].pubkey,
open_orders=instruction.keys[3].pubkey,
owner=instruction.keys[4].pubkey,
event_queue=instruction.keys[5].pubkey,
market=instruction.accounts[0].pubkey,
bids=instruction.accounts[1].pubkey,
asks=instruction.accounts[2].pubkey,
open_orders=instruction.accounts[3].pubkey,
owner=instruction.accounts[4].pubkey,
event_queue=instruction.accounts[5].pubkey,
side=Side(data.args.side),
order_id=int.from_bytes(data.args.order_id, "little"),
open_orders_slot=data.args.open_orders_slot,
)
def decode_cancel_order_by_client_id_v2(instruction: TransactionInstruction) -> CancelOrderByClientIDV2Params:
data = __parse_and_validate_instruction(instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID_V2)
def decode_cancel_order_by_client_id_v2(
instruction: Instruction,
) -> CancelOrderByClientIDV2Params:
data = __parse_and_validate_instruction(
instruction, InstructionType.CANCEL_ORDER_BY_CLIENT_ID_V2
)
return CancelOrderByClientIDV2Params(
market=instruction.keys[0].pubkey,
bids=instruction.keys[1].pubkey,
asks=instruction.keys[2].pubkey,
open_orders=instruction.keys[3].pubkey,
owner=instruction.keys[4].pubkey,
event_queue=instruction.keys[5].pubkey,
market=instruction.accounts[0].pubkey,
bids=instruction.accounts[1].pubkey,
asks=instruction.accounts[2].pubkey,
open_orders=instruction.accounts[3].pubkey,
owner=instruction.accounts[4].pubkey,
event_queue=instruction.accounts[5].pubkey,
client_id=data.args.client_id,
)
def decode_close_open_orders(
instruction: TransactionInstruction,
instruction: Instruction,
) -> 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,
open_orders=instruction.accounts[0].pubkey,
owner=instruction.accounts[1].pubkey,
sol_wallet=instruction.accounts[2].pubkey,
market=instruction.accounts[3].pubkey,
)
def decode_init_open_orders(
instruction: TransactionInstruction,
instruction: Instruction,
) -> InitOpenOrdersParams:
market_authority = instruction.keys[-1].pubkey if len(instruction.keys) == 5 else None
market_authority = (
instruction.accounts[-1].pubkey if len(instruction.accounts) == 5 else None
)
return InitOpenOrdersParams(
open_orders=instruction.keys[0].pubkey,
owner=instruction.keys[1].pubkey,
market=instruction.keys[2].pubkey,
open_orders=instruction.accounts[0].pubkey,
owner=instruction.accounts[1].pubkey,
market=instruction.accounts[2].pubkey,
market_authority=market_authority,
)
def initialize_market(params: InitializeMarketParams) -> TransactionInstruction:
def initialize_market(params: InitializeMarketParams) -> Instruction:
"""Generate a transaction instruction to initialize a Serum market."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
AccountMeta(pubkey=params.market, is_signer=False, is_writable=False),
AccountMeta(pubkey=params.request_queue, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.event_queue, is_signer=False, is_writable=True),
@ -535,10 +552,10 @@ def initialize_market(params: InitializeMarketParams) -> TransactionInstruction:
)
def new_order(params: NewOrderParams) -> TransactionInstruction:
def new_order(params: NewOrderParams) -> Instruction:
"""Generate a transaction instruction to place new order."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
AccountMeta(pubkey=params.market, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.open_orders, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.request_queue, is_signer=False, is_writable=True),
@ -547,7 +564,7 @@ def new_order(params: NewOrderParams) -> TransactionInstruction:
AccountMeta(pubkey=params.base_vault, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.quote_vault, is_signer=False, is_writable=True),
AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False),
AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False),
AccountMeta(pubkey=RENT, is_signer=False, is_writable=False),
],
program_id=params.program_id,
data=INSTRUCTIONS_LAYOUT.build(
@ -565,10 +582,10 @@ def new_order(params: NewOrderParams) -> TransactionInstruction:
)
def match_orders(params: MatchOrdersParams) -> TransactionInstruction:
def match_orders(params: MatchOrdersParams) -> Instruction:
"""Generate a transaction instruction to match order."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
AccountMeta(pubkey=params.market, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.request_queue, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.event_queue, is_signer=False, is_writable=True),
@ -587,15 +604,16 @@ def match_orders(params: MatchOrdersParams) -> TransactionInstruction:
)
def consume_events(params: ConsumeEventsParams) -> TransactionInstruction:
def consume_events(params: ConsumeEventsParams) -> Instruction:
"""Generate a transaction instruction to consume market events."""
keys = [
accounts = [
AccountMeta(pubkey=pubkey, is_signer=False, is_writable=True)
# NOTE - last two accounts are required for backwards compatibility but are ignored
for pubkey in params.open_orders_accounts + (2 * [params.market, params.event_queue])
for pubkey in params.open_orders_accounts
+ (2 * [params.market, params.event_queue])
]
return TransactionInstruction(
keys=keys,
return Instruction(
accounts=accounts,
program_id=params.program_id,
data=INSTRUCTIONS_LAYOUT.build(
dict(
@ -606,10 +624,10 @@ def consume_events(params: ConsumeEventsParams) -> TransactionInstruction:
)
def cancel_order(params: CancelOrderParams) -> TransactionInstruction:
def cancel_order(params: CancelOrderParams) -> Instruction:
"""Generate a transaction instruction to cancel order."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
AccountMeta(pubkey=params.market, is_signer=False, is_writable=False),
AccountMeta(pubkey=params.open_orders, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.request_queue, is_signer=False, is_writable=True),
@ -630,10 +648,10 @@ def cancel_order(params: CancelOrderParams) -> TransactionInstruction:
)
def settle_funds(params: SettleFundsParams) -> TransactionInstruction:
def settle_funds(params: SettleFundsParams) -> Instruction:
"""Generate a transaction instruction to settle fund."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
AccountMeta(pubkey=params.market, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.open_orders, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.owner, is_signer=True, is_writable=False),
@ -645,16 +663,18 @@ def settle_funds(params: SettleFundsParams) -> TransactionInstruction:
AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False),
],
program_id=params.program_id,
data=INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.SETTLE_FUNDS, args=dict())),
data=INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.SETTLE_FUNDS, args=dict())
),
)
def cancel_order_by_client_id(
params: CancelOrderByClientIDParams,
) -> TransactionInstruction:
) -> Instruction:
"""Generate a transaction instruction to cancel order by client id."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
AccountMeta(pubkey=params.market, is_signer=False, is_writable=False),
AccountMeta(pubkey=params.open_orders, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.request_queue, is_signer=False, is_writable=True),
@ -672,9 +692,9 @@ def cancel_order_by_client_id(
)
def new_order_v3(params: NewOrderV3Params) -> TransactionInstruction:
def new_order_v3(params: NewOrderV3Params) -> Instruction:
"""Generate a transaction instruction to place new order."""
touched_keys = [
touched_accounts = [
AccountMeta(pubkey=params.market, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.open_orders, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.request_queue, is_signer=False, is_writable=True),
@ -686,14 +706,16 @@ def new_order_v3(params: NewOrderV3Params) -> TransactionInstruction:
AccountMeta(pubkey=params.base_vault, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.quote_vault, is_signer=False, is_writable=True),
AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False),
AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False),
AccountMeta(pubkey=RENT, is_signer=False, is_writable=False),
]
if params.fee_discount_pubkey:
touched_keys.append(
AccountMeta(pubkey=params.fee_discount_pubkey, is_signer=False, is_writable=False),
touched_accounts.append(
AccountMeta(
pubkey=params.fee_discount_pubkey, is_signer=False, is_writable=False
),
)
return TransactionInstruction(
keys=touched_keys,
return Instruction(
accounts=touched_accounts,
program_id=params.program_id,
data=INSTRUCTIONS_LAYOUT.build(
dict(
@ -713,10 +735,10 @@ def new_order_v3(params: NewOrderV3Params) -> TransactionInstruction:
)
def cancel_order_v2(params: CancelOrderV2Params) -> TransactionInstruction:
def cancel_order_v2(params: CancelOrderV2Params) -> Instruction:
"""Generate a transaction instruction to cancel order."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
AccountMeta(pubkey=params.market, is_signer=False, is_writable=False),
AccountMeta(pubkey=params.bids, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.asks, is_signer=False, is_writable=True),
@ -739,10 +761,10 @@ def cancel_order_v2(params: CancelOrderV2Params) -> TransactionInstruction:
def cancel_order_by_client_id_v2(
params: CancelOrderByClientIDV2Params,
) -> TransactionInstruction:
) -> Instruction:
"""Generate a transaction instruction to cancel order by client id."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
AccountMeta(pubkey=params.market, is_signer=False, is_writable=False),
AccountMeta(pubkey=params.bids, is_signer=False, is_writable=True),
AccountMeta(pubkey=params.asks, is_signer=False, is_writable=True),
@ -762,34 +784,40 @@ def cancel_order_by_client_id_v2(
)
def close_open_orders(params: CloseOpenOrdersParams) -> TransactionInstruction:
def close_open_orders(params: CloseOpenOrdersParams) -> Instruction:
"""Generate a transaction instruction to close open orders account."""
return TransactionInstruction(
keys=[
return Instruction(
accounts=[
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())),
data=INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.CLOSE_OPEN_ORDERS, args=dict())
),
)
def init_open_orders(params: InitOpenOrdersParams) -> TransactionInstruction:
def init_open_orders(params: InitOpenOrdersParams) -> Instruction:
"""Generate a transaction instruction to initialize open orders account."""
touched_keys = [
touched_accounts = [
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),
AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False),
AccountMeta(pubkey=RENT, is_signer=False, is_writable=False),
]
if params.market_authority:
touched_keys.append(
AccountMeta(pubkey=params.market_authority, is_signer=False, is_writable=False),
touched_accounts.append(
AccountMeta(
pubkey=params.market_authority, is_signer=False, is_writable=False
),
)
return TransactionInstruction(
keys=touched_keys,
return Instruction(
accounts=touched_accounts,
program_id=params.program_id,
data=INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.INIT_OPEN_ORDERS, args=dict())),
data=INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.INIT_OPEN_ORDERS, args=dict())
),
)

View File

@ -3,8 +3,7 @@ from enum import IntEnum
from typing import List, Optional, Tuple, Union, cast
from construct import Container
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from ..._layouts.queue import EVENT_LAYOUT, QUEUE_HEADER_LAYOUT, REQUEST_LAYOUT
from ..types import Event, EventFlags, Request, ReuqestFlags
@ -18,19 +17,27 @@ def __from_bytes(
buffer: bytes, queue_type: QueueType, history: Optional[int]
) -> Tuple[Container, List[Union[Event, Request]]]:
header = QUEUE_HEADER_LAYOUT.parse(buffer)
layout_size = EVENT_LAYOUT.sizeof() if queue_type == QueueType.EVENT else REQUEST_LAYOUT.sizeof()
layout_size = (
EVENT_LAYOUT.sizeof()
if queue_type == QueueType.EVENT
else REQUEST_LAYOUT.sizeof()
)
alloc_len = math.floor((len(buffer) - QUEUE_HEADER_LAYOUT.sizeof()) / layout_size)
nodes: List[Union[Event, Request]] = []
if history:
for i in range(min(history, alloc_len)):
node_index = (header.head + header.count + alloc_len - 1 - i) % alloc_len
offset = QUEUE_HEADER_LAYOUT.sizeof() + node_index * layout_size
nodes.append(__parse_queue_item(buffer[offset : offset + layout_size], queue_type)) # noqa: E203
nodes.append(
__parse_queue_item(buffer[offset : offset + layout_size], queue_type)
) # noqa: E203
else:
for i in range(header.count):
node_index = (header.head + i) % alloc_len
offset = QUEUE_HEADER_LAYOUT.sizeof() + node_index * layout_size
nodes.append(__parse_queue_item(buffer[offset : offset + layout_size], queue_type)) # noqa: E203
nodes.append(
__parse_queue_item(buffer[offset : offset + layout_size], queue_type)
) # noqa: E203
return header, nodes
@ -53,7 +60,7 @@ def __parse_queue_item(buffer: bytes, queue_type: QueueType) -> Union[Event, Req
native_quantity_paid=parsed_item.native_quantity_paid,
native_fee_or_rebate=parsed_item.native_fee_or_rebate,
order_id=int.from_bytes(parsed_item.order_id, "little"),
public_key=PublicKey(parsed_item.public_key),
public_key=Pubkey.from_bytes(parsed_item.public_key),
client_order_id=parsed_item.client_order_id,
)
else:
@ -74,7 +81,7 @@ def __parse_queue_item(buffer: bytes, queue_type: QueueType) -> Union[Event, Req
max_base_size_or_cancel_id=parsed_item.max_base_size_or_cancel_id,
native_quote_quantity_locked=parsed_item.native_quote_quantity_locked,
order_id=int.from_bytes(parsed_item.order_id, "little"),
open_orders=PublicKey(parsed_item.open_orders),
open_orders=Pubkey.from_bytes(parsed_item.open_orders),
client_order_id=parsed_item.client_order_id,
)
@ -82,12 +89,16 @@ def __parse_queue_item(buffer: bytes, queue_type: QueueType) -> Union[Event, Req
def decode_request_queue(buffer: bytes, history: Optional[int] = None) -> List[Request]:
header, nodes = __from_bytes(buffer, QueueType.REQUEST, history)
if not header.account_flags.initialized or not header.account_flags.request_queue:
raise Exception("Invalid requests queue, either not initialized or not a request queue.")
raise Exception(
"Invalid requests queue, either not initialized or not a request queue."
)
return cast(List[Request], nodes)
def decode_event_queue(buffer: bytes, history: Optional[int] = None) -> List[Event]:
header, nodes = __from_bytes(buffer, QueueType.EVENT, history)
if not header.account_flags.initialized or not header.account_flags.event_queue:
raise Exception("Invalid events queue, either not initialized or not a event queue.")
raise Exception(
"Invalid events queue, either not initialized or not a event queue."
)
return cast(List[Event], nodes)

View File

@ -4,8 +4,7 @@ from dataclasses import dataclass
from typing import Iterable, List, NamedTuple, Optional
from construct import ListContainer
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from ..._layouts.slab import SLAB_LAYOUT, NodeType
@ -33,7 +32,7 @@ class SlabLeafNode(SlabNode):
owner_slot: int
fee_tier: int
key: int
owner: PublicKey
owner: Pubkey
quantity: int
client_order_id: int
@ -64,7 +63,7 @@ class Slab:
owner_slot=node.owner_slot,
fee_tier=node.fee_tier,
key=int.from_bytes(node.key, "little"),
owner=PublicKey(node.owner),
owner=Pubkey.from_bytes(node.owner),
quantity=node.quantity,
client_order_id=node.client_order_id,
is_initialized=True,

View File

@ -3,8 +3,8 @@ from __future__ import annotations
from typing import List
from solana.keypair import Keypair
from solana.publickey import PublicKey
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solana.rpc.async_api import AsyncClient
from solana.rpc.types import TxOpts
from solana.transaction import Transaction
@ -35,7 +35,9 @@ class AsyncMarket(MarketCore):
market_state: MarketState,
force_use_request_queue: bool = False,
) -> None:
super().__init__(market_state=market_state, force_use_request_queue=force_use_request_queue)
super().__init__(
market_state=market_state, force_use_request_queue=force_use_request_queue
)
self._conn = conn
@classmethod
@ -43,8 +45,8 @@ class AsyncMarket(MarketCore):
async def load(
cls,
conn: AsyncClient,
market_address: PublicKey,
program_id: PublicKey = instructions.DEFAULT_DEX_PROGRAM_ID,
market_address: Pubkey,
program_id: Pubkey = instructions.DEFAULT_DEX_PROGRAM_ID,
force_use_request_queue: bool = False,
) -> AsyncMarket:
"""Factory method to create a Market.
@ -56,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: Pubkey
) -> List[AsyncOpenOrdersAccount]:
return await AsyncOpenOrdersAccount.find_for_market_and_owner(
self._conn, self.state.public_key(), owner_address, self.state.program_id()
)
@ -71,11 +75,13 @@ class AsyncMarket(MarketCore):
bytes_data = await load_bytes_data(self.state.asks(), self._conn)
return self._parse_bids_or_asks(bytes_data)
async def load_orders_for_owner(self, owner_address: PublicKey) -> List[t.Order]:
async def load_orders_for_owner(self, owner_address: Pubkey) -> List[t.Order]:
"""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]:
@ -96,7 +102,7 @@ class AsyncMarket(MarketCore):
async def place_order( # pylint: disable=too-many-arguments,too-many-locals
self,
payer: PublicKey,
payer: Pubkey,
owner: Keypair,
order_type: OrderType,
side: Side,
@ -107,11 +113,15 @@ class AsyncMarket(MarketCore):
) -> 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.pubkey()
)
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,
@ -139,7 +149,7 @@ class AsyncMarket(MarketCore):
async def cancel_order_by_client_id(
self,
owner: Keypair,
open_orders_account: PublicKey,
open_orders_account: Pubkey,
client_id: int,
opts: TxOpts = TxOpts(),
) -> SendTransactionResp:
@ -148,11 +158,15 @@ class AsyncMarket(MarketCore):
)
return await self._conn.send_transaction(txs, owner, opts=opts)
async def cancel_order(self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()) -> SendTransactionResp:
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()) -> SendTransactionResp:
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)
@ -160,8 +174,8 @@ class AsyncMarket(MarketCore):
self,
owner: Keypair,
open_orders: AsyncOpenOrdersAccount,
base_wallet: PublicKey,
quote_wallet: PublicKey, # TODO: add referrer_quote_wallet.
base_wallet: Pubkey,
quote_wallet: Pubkey, # TODO: add referrer_quote_wallet.
opts: TxOpts = TxOpts(),
) -> SendTransactionResp:
# TODO: Handle wrapped sol accounts

View File

@ -5,10 +5,11 @@ import itertools
import logging
from typing import List, Union
from solana.keypair import Keypair
from solana.publickey import PublicKey
from solana.system_program import CreateAccountParams, create_account
from solana.transaction import Transaction, TransactionInstruction
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.system_program import CreateAccountParams, create_account
from solana.transaction import Transaction
from solders.instruction import Instruction
from solders.rpc.responses import GetMinimumBalanceForRentExemptionResp
from spl.token.constants import ACCOUNT_LEN, TOKEN_PROGRAM_ID, WRAPPED_SOL_MINT
from spl.token.instructions import (
@ -37,33 +38,40 @@ 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
== Pubkey.from_string("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn")
or
# DEX Version 1
self.state.program_id == PublicKey("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg")
self.state.program_id
== Pubkey.from_string("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg")
or
# DEX Version 2
self.state.program_id == PublicKey("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o")
self.state.program_id
== Pubkey.from_string("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o")
or self.force_use_request_queue
)
def support_srm_fee_discounts(self) -> bool:
raise NotImplementedError("support_srm_fee_discounts not implemented")
def find_fee_discount_keys(self, owner: PublicKey, cache_duration: int):
def find_fee_discount_keys(self, owner: Pubkey, cache_duration: int):
raise NotImplementedError("find_fee_discount_keys not implemented")
def find_best_fee_discount_key(self, owner: PublicKey, cache_duration: int):
def find_best_fee_discount_key(self, owner: Pubkey, 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: Pubkey, 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:
@ -76,7 +84,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):
@ -115,7 +125,8 @@ 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(
@ -124,14 +135,14 @@ class MarketCore:
balance_needed: int,
signers: List[Keypair],
transaction: Transaction,
) -> PublicKey:
) -> Pubkey:
# new_open_orders_account = Account()
new_open_orders_account = Keypair()
place_order_open_order_account = new_open_orders_account.public_key
place_order_open_order_account = new_open_orders_account.pubkey()
transaction.add(
make_create_account_instruction(
owner_address=owner.public_key,
new_account_address=new_open_orders_account.public_key,
owner_address=owner.pubkey(),
new_account_address=new_open_orders_account.pubkey(),
lamports=balance_needed,
program_id=self.state.program_id(),
)
@ -142,7 +153,7 @@ class MarketCore:
def _prepare_order_transaction( # pylint: disable=too-many-arguments,too-many-locals
self,
transaction: Transaction,
payer: PublicKey,
payer: Pubkey,
owner: Keypair,
order_type: OrderType,
side: Side,
@ -150,42 +161,44 @@ class MarketCore:
limit_price: float,
max_quantity: float,
client_id: int,
open_order_accounts: Union[List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]],
place_order_open_order_account: PublicKey,
open_order_accounts: Union[
List[OpenOrdersAccount], List[AsyncOpenOrdersAccount]
],
place_order_open_order_account: Pubkey,
) -> None:
# unwrapped SOL cannot be used for payment
if payer == owner.public_key:
if payer == owner.pubkey():
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()
wrapped_sol_account = Keypair()
payer = wrapped_sol_account.public_key
payer = wrapped_sol_account.pubkey()
signers.append(wrapped_sol_account)
transaction.add(
create_account(
CreateAccountParams(
from_pubkey=owner.public_key,
new_account_pubkey=wrapped_sol_account.public_key,
from_pubkey=owner.pubkey(),
to_pubkey=wrapped_sol_account.pubkey(),
lamports=self._get_lamport_need_for_sol_wrapping(
limit_price, max_quantity, side, open_order_accounts
),
space=ACCOUNT_LEN,
program_id=TOKEN_PROGRAM_ID,
owner=TOKEN_PROGRAM_ID,
)
)
)
transaction.add(
initialize_account(
InitializeAccountParams(
account=wrapped_sol_account.public_key,
account=wrapped_sol_account.pubkey(),
mint=WRAPPED_SOL_MINT,
owner=owner.public_key,
owner=owner.pubkey(),
program_id=TOKEN_PROGRAM_ID,
)
)
@ -208,9 +221,9 @@ class MarketCore:
transaction.add(
close_account(
CloseAccountParams(
account=wrapped_sol_account.public_key,
owner=owner.public_key,
dest=owner.public_key,
account=wrapped_sol_account.pubkey(),
owner=owner.pubkey(),
dest=owner.pubkey(),
program_id=TOKEN_PROGRAM_ID,
)
)
@ -222,9 +235,11 @@ class MarketCore:
owner: Keypair,
signers: List[Keypair],
transaction: Transaction,
) -> PublicKey:
) -> Pubkey:
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
@staticmethod
@ -232,7 +247,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:
@ -248,16 +265,16 @@ class MarketCore:
def make_place_order_instruction( # pylint: disable=too-many-arguments
self,
payer: PublicKey,
payer: Pubkey,
owner: Keypair,
order_type: OrderType,
side: Side,
limit_price: float,
max_quantity: float,
client_id: int,
open_order_account: PublicKey,
fee_discount_pubkey: PublicKey = None,
) -> TransactionInstruction:
open_order_account: Pubkey,
fee_discount_pubkey: Pubkey = None,
) -> Instruction:
if self.state.base_size_number_to_lots(max_quantity) < 0:
raise Exception(f"Size lot %d is too small {max_quantity}")
if self.state.price_number_to_lots(limit_price) < 0:
@ -268,7 +285,7 @@ class MarketCore:
market=self.state.public_key(),
open_orders=open_order_account,
payer=payer,
owner=owner.public_key,
owner=owner.pubkey(),
request_queue=self.state.request_queue(),
base_vault=self.state.base_vault(),
quote_vault=self.state.quote_vault(),
@ -285,7 +302,7 @@ class MarketCore:
market=self.state.public_key(),
open_orders=open_order_account,
payer=payer,
owner=owner.public_key,
owner=owner.pubkey(),
request_queue=self.state.request_queue(),
event_queue=self.state.event_queue(),
bids=self.state.bids(),
@ -308,18 +325,22 @@ class MarketCore:
)
def _build_cancel_order_by_client_id_tx(
self, owner: Keypair, open_orders_account: PublicKey, client_id: int
self, owner: Keypair, open_orders_account: Pubkey, 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
) -> TransactionInstruction:
self, owner: Keypair, open_orders_account: Pubkey, client_id: int
) -> Instruction:
if self._use_request_queue():
return instructions.cancel_order_by_client_id(
instructions.CancelOrderByClientIDParams(
market=self.state.public_key(),
owner=owner.public_key,
owner=owner.pubkey(),
open_orders=open_orders_account,
request_queue=self.state.request_queue(),
client_id=client_id,
@ -329,7 +350,7 @@ class MarketCore:
return instructions.cancel_order_by_client_id_v2(
instructions.CancelOrderByClientIDV2Params(
market=self.state.public_key(),
owner=owner.public_key,
owner=owner.pubkey(),
open_orders=open_orders_account,
bids=self.state.bids(),
asks=self.state.asks(),
@ -340,9 +361,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.pubkey(), order)
)
def make_cancel_order_instruction(self, owner: PublicKey, order: t.Order) -> TransactionInstruction:
def make_cancel_order_instruction(
self, owner: Pubkey, order: t.Order
) -> Instruction:
if self._use_request_queue():
return instructions.cancel_order(
instructions.CancelOrderParams(
@ -374,7 +399,7 @@ class MarketCore:
def _build_match_orders_tx(self, limit: int) -> Transaction:
return Transaction().add(self.make_match_orders_instruction(limit))
def make_match_orders_instruction(self, limit: int) -> TransactionInstruction:
def make_match_orders_instruction(self, limit: int) -> Instruction:
params = instructions.MatchOrdersParams(
market=self.state.public_key(),
request_queue=self.state.request_queue(),
@ -393,15 +418,15 @@ class MarketCore:
owner: Keypair,
signers: List[Keypair],
open_orders: Union[OpenOrdersAccount, AsyncOpenOrdersAccount],
base_wallet: PublicKey,
quote_wallet: PublicKey, # TODO: add referrer_quote_wallet.
base_wallet: Pubkey,
quote_wallet: Pubkey, # TODO: add referrer_quote_wallet.
min_bal_for_rent_exemption: int,
should_wrap_sol: bool,
) -> Transaction:
# TODO: Handle wrapped sol accounts
if open_orders.owner != owner.public_key:
if open_orders.owner != owner.pubkey():
raise Exception("Invalid open orders account")
vault_signer = PublicKey.create_program_address(
vault_signer = Pubkey.create_program_address(
[
bytes(self.state.public_key()),
self.state.vault_signer_nonce().to_bytes(8, byteorder="little"),
@ -418,11 +443,11 @@ class MarketCore:
transaction.add(
create_account(
CreateAccountParams(
from_pubkey=owner.public_key,
new_account_pubkey=wrapped_sol_account.public_key,
from_pubkey=owner.pubkey(),
to_pubkey=wrapped_sol_account.pubkey(),
lamports=min_bal_for_rent_exemption,
space=ACCOUNT_LEN,
program_id=TOKEN_PROGRAM_ID,
owner=TOKEN_PROGRAM_ID,
)
)
)
@ -430,9 +455,9 @@ class MarketCore:
transaction.add(
initialize_account(
InitializeAccountParams(
account=wrapped_sol_account.public_key,
account=wrapped_sol_account.pubkey(),
mint=WRAPPED_SOL_MINT,
owner=owner.public_key,
owner=owner.pubkey(),
program_id=TOKEN_PROGRAM_ID,
)
)
@ -441,8 +466,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.pubkey(),
quote_wallet
if self.state.quote_mint() != WRAPPED_SOL_MINT
else wrapped_sol_account.pubkey(),
vault_signer,
)
)
@ -452,9 +481,9 @@ class MarketCore:
transaction.add(
close_account(
CloseAccountParams(
account=wrapped_sol_account.public_key,
owner=owner.public_key,
dest=owner.public_key,
account=wrapped_sol_account.pubkey(),
owner=owner.pubkey(),
dest=owner.pubkey(),
program_id=TOKEN_PROGRAM_ID,
)
)
@ -462,15 +491,17 @@ 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,
open_orders_account: Union[OpenOrdersAccount, AsyncOpenOrdersAccount],
base_wallet: PublicKey,
quote_wallet: PublicKey,
vault_signer: PublicKey,
) -> TransactionInstruction:
base_wallet: Pubkey,
quote_wallet: Pubkey,
vault_signer: Pubkey,
) -> Instruction:
if base_wallet == self.state.base_vault():
raise ValueError("base_wallet should not be a vault address")
if quote_wallet == self.state.quote_vault():

View File

@ -3,8 +3,8 @@ from __future__ import annotations
from typing import List
from solana.keypair import Keypair
from solana.publickey import PublicKey
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solana.rpc.types import TxOpts
from solana.transaction import Transaction
@ -35,7 +35,9 @@ class Market(MarketCore):
market_state: MarketState,
force_use_request_queue: bool = False,
) -> None:
super().__init__(market_state=market_state, force_use_request_queue=force_use_request_queue)
super().__init__(
market_state=market_state, force_use_request_queue=force_use_request_queue
)
self._conn = conn
@classmethod
@ -43,8 +45,8 @@ class Market(MarketCore):
def load(
cls,
conn: Client,
market_address: PublicKey,
program_id: PublicKey = instructions.DEFAULT_DEX_PROGRAM_ID,
market_address: Pubkey,
program_id: Pubkey = instructions.DEFAULT_DEX_PROGRAM_ID,
force_use_request_queue: bool = False,
) -> Market:
"""Factory method to create a Market.
@ -56,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: Pubkey
) -> List[OpenOrdersAccount]:
return OpenOrdersAccount.find_for_market_and_owner(
self._conn, self.state.public_key(), owner_address, self.state.program_id()
)
@ -71,7 +75,7 @@ class Market(MarketCore):
bytes_data = load_bytes_data(self.state.asks(), self._conn)
return self._parse_bids_or_asks(bytes_data)
def load_orders_for_owner(self, owner_address: PublicKey) -> List[t.Order]:
def load_orders_for_owner(self, owner_address: Pubkey) -> List[t.Order]:
"""Load orders for owner."""
bids = self.load_bids()
asks = self.load_asks()
@ -96,7 +100,7 @@ class Market(MarketCore):
def place_order( # pylint: disable=too-many-arguments,too-many-locals
self,
payer: PublicKey,
payer: Pubkey,
owner: Keypair,
order_type: OrderType,
side: Side,
@ -107,11 +111,13 @@ class Market(MarketCore):
) -> 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)
open_order_accounts = self.find_open_orders_accounts_for_owner(owner.pubkey())
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,
@ -139,7 +145,7 @@ class Market(MarketCore):
def cancel_order_by_client_id(
self,
owner: Keypair,
open_orders_account: PublicKey,
open_orders_account: Pubkey,
client_id: int,
opts: TxOpts = TxOpts(),
) -> SendTransactionResp:
@ -148,11 +154,15 @@ class Market(MarketCore):
)
return self._conn.send_transaction(txs, owner, opts=opts)
def cancel_order(self, owner: Keypair, order: t.Order, opts: TxOpts = TxOpts()) -> SendTransactionResp:
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()) -> SendTransactionResp:
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)
@ -160,14 +170,16 @@ class Market(MarketCore):
self,
owner: Keypair,
open_orders: OpenOrdersAccount,
base_wallet: PublicKey,
quote_wallet: PublicKey, # TODO: add referrer_quote_wallet.
base_wallet: Pubkey,
quote_wallet: Pubkey, # TODO: add referrer_quote_wallet.
opts: TxOpts = TxOpts(),
) -> 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).value 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(

View File

@ -16,9 +16,13 @@ class OrderBook:
_is_bids: bool
_slab: Slab
def __init__(self, market_state: MarketState, account_flags: t.AccountFlags, slab: Slab) -> None:
def __init__(
self, market_state: MarketState, account_flags: t.AccountFlags, slab: Slab
) -> None:
if not account_flags.initialized or not account_flags.bids ^ account_flags.asks:
raise Exception("Invalid order book, either not initialized or neither of bids or asks")
raise Exception(
"Invalid order book, either not initialized or neither of bids or asks"
)
self._market_state = market_state
self._is_bids = account_flags.bids
self._slab = slab

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import math
from construct import Container, Struct
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solana.rpc.async_api import AsyncClient
@ -15,7 +15,11 @@ from .types import AccountFlags
class MarketState: # pylint: disable=too-many-public-methods
def __init__(
self, parsed_market: Container, program_id: PublicKey, base_mint_decimals: int, quote_mint_decimals: int
self,
parsed_market: Container,
program_id: Pubkey,
base_mint_decimals: int,
quote_mint_decimals: int,
) -> None:
self._decoded = parsed_market
self._program_id = program_id
@ -32,77 +36,99 @@ class MarketState: # pylint: disable=too-many-public-methods
parsed_market = MARKET_LAYOUT.parse(bytes_data)
# TODO: add ownAddress check!
if not parsed_market.account_flags.initialized or not parsed_market.account_flags.market:
if (
not parsed_market.account_flags.initialized
or not parsed_market.account_flags.market
):
raise Exception("Invalid market")
return parsed_market
@classmethod
def load(cls, conn: Client, market_address: PublicKey, program_id: PublicKey) -> MarketState:
def load(
cls, conn: Client, market_address: Pubkey, program_id: Pubkey
) -> MarketState:
bytes_data = utils.load_bytes_data(market_address, conn)
parsed_market = cls._make_parsed_market(bytes_data)
base_mint_decimals = utils.get_mint_decimals(conn, PublicKey(parsed_market.base_mint))
quote_mint_decimals = utils.get_mint_decimals(conn, PublicKey(parsed_market.quote_mint))
base_mint_decimals = utils.get_mint_decimals(
conn, Pubkey.from_bytes(parsed_market.base_mint)
)
quote_mint_decimals = utils.get_mint_decimals(
conn, Pubkey.from_bytes(parsed_market.quote_mint)
)
return cls(parsed_market, program_id, base_mint_decimals, quote_mint_decimals)
@classmethod
async def async_load(cls, conn: AsyncClient, market_address: PublicKey, program_id: PublicKey) -> MarketState:
async def async_load(
cls, conn: AsyncClient, market_address: Pubkey, program_id: Pubkey
) -> MarketState:
bytes_data = await async_utils.load_bytes_data(market_address, conn)
parsed_market = cls._make_parsed_market(bytes_data)
base_mint_decimals = await async_utils.get_mint_decimals(conn, PublicKey(parsed_market.base_mint))
quote_mint_decimals = await async_utils.get_mint_decimals(conn, PublicKey(parsed_market.quote_mint))
base_mint_decimals = await async_utils.get_mint_decimals(
conn, Pubkey.from_bytes(parsed_market.base_mint)
)
quote_mint_decimals = await async_utils.get_mint_decimals(
conn, Pubkey.from_bytes(parsed_market.quote_mint)
)
return cls(parsed_market, program_id, base_mint_decimals, quote_mint_decimals)
@classmethod
def from_bytes(
cls, program_id: PublicKey, base_mint_decimals: int, quote_mint_decimals: int, buffer: bytes
cls,
program_id: Pubkey,
base_mint_decimals: int,
quote_mint_decimals: int,
buffer: bytes,
) -> MarketState:
parsed_market = MARKET_LAYOUT.parse(buffer)
# TODO: add ownAddress check!
if not parsed_market.account_flags.initialized or not parsed_market.account_flags.market:
if (
not parsed_market.account_flags.initialized
or not parsed_market.account_flags.market
):
raise Exception("Invalid market")
return cls(parsed_market, program_id, base_mint_decimals, quote_mint_decimals)
def program_id(self) -> PublicKey:
def program_id(self) -> Pubkey:
return self._program_id
def public_key(self) -> PublicKey:
return PublicKey(self._decoded.own_address)
def public_key(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.own_address)
def account_flags(self) -> AccountFlags:
return AccountFlags(**self._decoded.account_flags)
def asks(self) -> PublicKey:
return PublicKey(self._decoded.asks)
def asks(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.asks)
def bids(self) -> PublicKey:
return PublicKey(self._decoded.bids)
def bids(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.bids)
def fee_rate_bps(self) -> int:
return self._decoded.fee_rate_bps
def event_queue(self) -> PublicKey:
return PublicKey(self._decoded.event_queue)
def event_queue(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.event_queue)
def request_queue(self) -> PublicKey:
return PublicKey(self._decoded.request_queue)
def request_queue(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.request_queue)
def vault_signer_nonce(self) -> int:
return self._decoded.vault_signer_nonce
def base_mint(self) -> PublicKey:
return PublicKey(self._decoded.base_mint)
def base_mint(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.base_mint)
def quote_mint(self) -> PublicKey:
return PublicKey(self._decoded.quote_mint)
def quote_mint(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.quote_mint)
def base_vault(self) -> PublicKey:
return PublicKey(self._decoded.base_vault)
def base_vault(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.base_vault)
def quote_vault(self) -> PublicKey:
return PublicKey(self._decoded.quote_vault)
def quote_vault(self) -> Pubkey:
return Pubkey.from_bytes(self._decoded.quote_vault)
def base_deposits_total(self) -> int:
return self._decoded.base_deposits_total
@ -144,9 +170,9 @@ class MarketState: # pylint: disable=too-many-public-methods
return self._decoded.quote_lot_size
def price_lots_to_number(self, price: int) -> float:
return float(price * self.quote_lot_size() * self.base_spl_token_multiplier()) / (
self.base_lot_size() * self.quote_spl_token_multiplier()
)
return float(
price * self.quote_lot_size() * self.base_spl_token_multiplier()
) / (self.base_lot_size() * self.quote_spl_token_multiplier())
def price_number_to_lots(self, price: float) -> int:
return int(
@ -160,10 +186,14 @@ class MarketState: # pylint: disable=too-many-public-methods
return float(size * self.base_lot_size()) / self.base_spl_token_multiplier()
def base_size_number_to_lots(self, size: float) -> int:
return int(math.floor(size * self.base_spl_token_multiplier()) / self.base_lot_size())
return int(
math.floor(size * self.base_spl_token_multiplier()) / self.base_lot_size()
)
def quote_size_lots_to_number(self, size: int) -> float:
return float(size * self.quote_lot_size()) / self.quote_spl_token_multiplier()
def quote_size_number_to_lots(self, size: float) -> int:
return int(math.floor(size * self.quote_spl_token_multiplier()) / self.quote_lot_size())
return int(
math.floor(size * self.quote_spl_token_multiplier()) / self.quote_lot_size()
)

View File

@ -2,7 +2,7 @@ from __future__ import annotations
from typing import NamedTuple
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from .._layouts.account_flags import ACCOUNT_FLAGS_LAYOUT
from ..enums import Side
@ -67,7 +67,7 @@ class Order(NamedTuple):
""""""
client_id: int
""""""
open_order_address: PublicKey
open_order_address: Pubkey
""""""
open_order_slot: int
""""""
@ -100,7 +100,7 @@ class Request(NamedTuple):
""""""
order_id: int
""""""
open_orders: PublicKey
open_orders: Pubkey
""""""
client_order_id: int
""""""
@ -128,7 +128,7 @@ class Event(NamedTuple):
""""""
order_id: int
""""""
public_key: PublicKey
public_key: Pubkey
""""""
client_order_id: int
""""""
@ -137,14 +137,14 @@ class Event(NamedTuple):
class MarketInfo(NamedTuple):
name: str
""""""
address: PublicKey
address: Pubkey
""""""
program_id: PublicKey
program_id: Pubkey
""""""
class TokenInfo(NamedTuple):
name: str
""""""
address: PublicKey
address: Pubkey
""""""

View File

@ -2,12 +2,12 @@ from __future__ import annotations
from typing import List, NamedTuple, Tuple, Type, TypeVar
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solana.rpc.commitment import Processed
from solana.rpc.types import Commitment, MemcmpOpts
from solana.system_program import CreateAccountParams, create_account
from solana.transaction import TransactionInstruction
from solders.system_program import CreateAccountParams, create_account
from solders.instruction import Instruction
from solders.rpc.responses import GetProgramAccountsResp
from ._layouts.open_orders import OPEN_ORDERS_LAYOUT
@ -16,11 +16,11 @@ from .utils import load_bytes_data
class ProgramAccount(NamedTuple):
public_key: PublicKey
public_key: Pubkey
data: bytes
is_executablable: bool
lamports: int
owner: PublicKey
owner: Pubkey
_T = TypeVar("_T", bound="_OpenOrdersAccountCore")
@ -30,9 +30,9 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
# pylint: disable=too-many-arguments
def __init__(
self,
address: PublicKey,
market: PublicKey,
owner: PublicKey,
address: Pubkey,
market: Pubkey,
owner: Pubkey,
base_token_free: int,
base_token_total: int,
quote_token_free: int,
@ -55,56 +55,67 @@ class _OpenOrdersAccountCore: # pylint: disable=too-many-instance-attributes,to
self.client_ids = client_ids
@classmethod
def from_bytes(cls: Type[_T], address: PublicKey, buffer: bytes) -> _T:
def from_bytes(cls: Type[_T], address: Pubkey, 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(
address=address,
market=PublicKey(open_order_decoded.market),
owner=PublicKey(open_order_decoded.owner),
market=Pubkey.from_bytes(open_order_decoded.market),
owner=Pubkey.from_bytes(open_order_decoded.owner),
base_token_free=open_order_decoded.base_token_free,
base_token_total=open_order_decoded.base_token_total,
quote_token_free=open_order_decoded.quote_token_free,
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: GetProgramAccountsResp) -> List[_T]:
def _process_get_program_accounts_resp(
cls: Type[_T], resp: GetProgramAccountsResp
) -> List[_T]:
accounts = []
for keyed_account in resp.value:
account_details = keyed_account.account
accounts.append(
ProgramAccount(
public_key=PublicKey(keyed_account.pubkey),
public_key=keyed_account.pubkey,
data=account_details.data,
is_executablable=account_details.executable,
owner=PublicKey(account_details.owner),
owner=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,
market: Pubkey,
program_id: Pubkey,
owner: Pubkey,
commitment: Commitment,
) -> Tuple[PublicKey, Commitment, str, None, List[MemcmpOpts]]:
) -> Tuple[Pubkey, 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),
),
]
@ -123,9 +134,9 @@ class OpenOrdersAccount(_OpenOrdersAccountCore):
def find_for_market_and_owner( # pylint: disable=too-many-arguments
cls,
conn: Client,
market: PublicKey,
owner: PublicKey,
program_id: PublicKey,
market: Pubkey,
owner: Pubkey,
program_id: Pubkey,
commitment: Commitment = Processed,
) -> List[OpenOrdersAccount]:
args = cls._build_get_program_accounts_args(
@ -136,23 +147,23 @@ class OpenOrdersAccount(_OpenOrdersAccountCore):
@classmethod
def load(cls, conn: Client, address: str) -> OpenOrdersAccount:
addr_pub_key = PublicKey(address)
addr_pub_key = Pubkey.from_string(address)
bytes_data = load_bytes_data(addr_pub_key, conn)
return cls.from_bytes(addr_pub_key, bytes_data)
def make_create_account_instruction(
owner_address: PublicKey,
new_account_address: PublicKey,
owner_address: Pubkey,
new_account_address: Pubkey,
lamports: int,
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID,
) -> TransactionInstruction:
program_id: Pubkey = DEFAULT_DEX_PROGRAM_ID,
) -> Instruction:
return create_account(
CreateAccountParams(
from_pubkey=owner_address,
new_account_pubkey=new_account_address,
to_pubkey=new_account_address,
lamports=lamports,
space=OPEN_ORDERS_LAYOUT.sizeof(),
program_id=program_id,
owner=program_id,
)
)

View File

@ -1,4 +1,4 @@
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solders.account import Account
from solders.rpc.responses import GetAccountInfoResp
@ -13,7 +13,7 @@ def parse_bytes_data(res: GetAccountInfoResp) -> bytes:
return res.value.data
def load_bytes_data(addr: PublicKey, conn: Client) -> bytes:
def load_bytes_data(addr: Pubkey, conn: Client) -> bytes:
res = conn.get_account_info(addr)
return parse_bytes_data(res)
@ -22,7 +22,7 @@ def parse_mint_decimals(bytes_data: bytes) -> int:
return MINT_LAYOUT.parse(bytes_data).decimals
def get_mint_decimals(conn: Client, mint_pub_key: PublicKey) -> int:
def get_mint_decimals(conn: Client, mint_pub_key: Pubkey) -> int:
"""Get the mint decimals for a token mint"""
if mint_pub_key == WRAPPED_SOL_MINT:
return 9

View File

@ -4,7 +4,7 @@ from setuptools import find_packages, setup
setup(
name="pyserum",
version="0.6.0a",
version="0.7.0a",
author="serum-community",
description="""Python client library for interacting with the Project Serum DEX.""",
install_requires=[

View File

@ -2,8 +2,8 @@ import asyncio
from typing import Dict
import pytest
from solana.keypair import Keypair
from solana.publickey import PublicKey
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solana.rpc.async_api import AsyncClient
@ -29,16 +29,16 @@ def __bs_params() -> Dict[str, str]:
def __bootstrap_account(pubkey: str, secretkey: str) -> Keypair:
secret = [int(b) for b in secretkey[1:-1].split(" ")]
secret_bytes = bytes(secret)
keypair = Keypair.from_secret_key(secret_bytes)
assert str(keypair.public_key) == pubkey, "account must map to provided public key"
keypair = Keypair.from_bytes(secret_bytes)
assert str(keypair.pubkey()) == pubkey, "account must map to provided public key"
return keypair
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_dex_program_pk(__bs_params) -> PublicKey:
def stubbed_dex_program_pk(__bs_params) -> Pubkey:
"""Bootstrapped dex program id."""
return PublicKey(__bs_params["dex_program_id"])
return Pubkey.from_string(__bs_params["dex_program_id"])
@pytest.mark.integration
@ -52,7 +52,9 @@ def stubbed_payer(__bs_params) -> Keypair:
@pytest.fixture(scope="session")
def stubbed_base_mint(__bs_params) -> Keypair:
"""Bootstrapped base mint account."""
return __bootstrap_account(__bs_params["coin_mint"], __bs_params["coin_mint_secret"])
return __bootstrap_account(
__bs_params["coin_mint"], __bs_params["coin_mint_secret"]
)
@pytest.mark.integration
@ -66,84 +68,88 @@ def stubbed_quote_mint(__bs_params) -> Keypair:
@pytest.fixture(scope="session")
def stubbed_base_wallet(__bs_params) -> Keypair:
"""Bootstrapped base mint account."""
return __bootstrap_account(__bs_params["coin_wallet"], __bs_params["coin_wallet_secret"])
return __bootstrap_account(
__bs_params["coin_wallet"], __bs_params["coin_wallet_secret"]
)
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_quote_wallet(__bs_params) -> Keypair:
"""Bootstrapped quote mint account."""
return __bootstrap_account(__bs_params["pc_wallet"], __bs_params["pc_wallet_secret"])
return __bootstrap_account(
__bs_params["pc_wallet"], __bs_params["pc_wallet_secret"]
)
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_market_pk(__bs_params) -> PublicKey:
def stubbed_market_pk(__bs_params) -> Pubkey:
"""Public key of the boostrapped market."""
return PublicKey(__bs_params["market"])
return Pubkey.from_string(__bs_params["market"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_req_q_pk(__bs_params) -> PublicKey:
def stubbed_req_q_pk(__bs_params) -> Pubkey:
"""Public key of the bootstrapped request queue."""
return PublicKey(__bs_params["req_q"])
return Pubkey.from_string(__bs_params["req_q"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_event_q_pk(__bs_params) -> PublicKey:
def stubbed_event_q_pk(__bs_params) -> Pubkey:
"""Public key of the bootstrapped request queue."""
return PublicKey(__bs_params["event_q"])
return Pubkey.from_string(__bs_params["event_q"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_bids_pk(__bs_params) -> PublicKey:
def stubbed_bids_pk(__bs_params) -> Pubkey:
"""Public key of the bootstrapped bids book."""
return PublicKey(__bs_params["bids"])
return Pubkey.from_string(__bs_params["bids"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_asks_pk(__bs_params) -> PublicKey:
def stubbed_asks_pk(__bs_params) -> Pubkey:
"""Public key of the bootstrapped asks book."""
return PublicKey(__bs_params["asks"])
return Pubkey.from_string(__bs_params["asks"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_base_vault_pk(__bs_params) -> PublicKey:
def stubbed_base_vault_pk(__bs_params) -> Pubkey:
"""Public key of the base vault account."""
return PublicKey(__bs_params["coin_vault"])
return Pubkey.from_string(__bs_params["coin_vault"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_quote_vault_pk(__bs_params) -> PublicKey:
def stubbed_quote_vault_pk(__bs_params) -> Pubkey:
"""Public key of the quote vault account."""
return PublicKey(__bs_params["pc_vault"])
return Pubkey.from_string(__bs_params["pc_vault"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_vault_signer_pk(__bs_params) -> PublicKey:
def stubbed_vault_signer_pk(__bs_params) -> Pubkey:
"""Public key of the bootstrapped vault signer."""
return PublicKey(__bs_params["vault_signer_key"])
return Pubkey.from_string(__bs_params["vault_signer_key"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_bid_account_pk(__bs_params) -> PublicKey:
def stubbed_bid_account_pk(__bs_params) -> Pubkey:
"""Public key of the initial bid order account."""
return PublicKey(__bs_params["bid_account"])
return Pubkey.from_string(__bs_params["bid_account"])
@pytest.mark.integration
@pytest.fixture(scope="session")
def stubbed_ask_account_pk(__bs_params) -> PublicKey:
def stubbed_ask_account_pk(__bs_params) -> Pubkey:
"""Public key of the initial ask order account."""
return PublicKey(__bs_params["ask_account"])
return Pubkey.from_string(__bs_params["ask_account"])
@pytest.mark.integration
@ -152,7 +158,9 @@ def http_client() -> Client:
"""Solana http client."""
cc = conn("http://localhost:8899") # pylint: disable=invalid-name
if not cc.is_connected():
raise Exception("Could not connect to local node. Please run `make int-tests` to run integration tests.")
raise Exception(
"Could not connect to local node. Please run `make int-tests` to run integration tests."
)
return cc
@ -166,7 +174,9 @@ def event_loop():
@pytest.mark.async_integration
@pytest.fixture(scope="session")
def async_http_client(event_loop) -> AsyncClient: # pylint: disable=redefined-outer-name
def async_http_client(
event_loop,
) -> AsyncClient: # pylint: disable=redefined-outer-name
"""Solana async http client."""
cc = async_conn("http://localhost:8899") # pylint: disable=invalid-name
if not event_loop.run_until_complete(cc.is_connected()):

View File

@ -1,8 +1,8 @@
# pylint: disable=redefined-outer-name
import pytest
from solana.keypair import Keypair
from solana.publickey import PublicKey
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solana.rpc.async_api import AsyncClient
from solana.rpc.types import TxOpts
@ -13,10 +13,18 @@ from pyserum.market import AsyncMarket
@pytest.mark.async_integration
@pytest.fixture(scope="module")
def bootstrapped_market(
async_http_client: AsyncClient, stubbed_market_pk: PublicKey, stubbed_dex_program_pk: PublicKey, event_loop
async_http_client: AsyncClient,
stubbed_market_pk: Pubkey,
stubbed_dex_program_pk: Pubkey,
event_loop,
) -> AsyncMarket:
return event_loop.run_until_complete(
AsyncMarket.load(async_http_client, stubbed_market_pk, stubbed_dex_program_pk, force_use_request_queue=True)
AsyncMarket.load(
async_http_client,
stubbed_market_pk,
stubbed_dex_program_pk,
force_use_request_queue=True,
)
)
@ -24,16 +32,16 @@ def bootstrapped_market(
@pytest.mark.asyncio
async def test_bootstrapped_market(
bootstrapped_market: AsyncMarket,
stubbed_market_pk: PublicKey,
stubbed_dex_program_pk: PublicKey,
stubbed_market_pk: Pubkey,
stubbed_dex_program_pk: Pubkey,
stubbed_base_mint: Keypair,
stubbed_quote_mint: Keypair,
):
assert isinstance(bootstrapped_market, AsyncMarket)
assert bootstrapped_market.state.public_key() == stubbed_market_pk
assert bootstrapped_market.state.pubkey()() == stubbed_market_pk
assert bootstrapped_market.state.program_id() == stubbed_dex_program_pk
assert bootstrapped_market.state.base_mint() == stubbed_base_mint.public_key
assert bootstrapped_market.state.quote_mint() == stubbed_quote_mint.public_key
assert bootstrapped_market.state.base_mint() == stubbed_base_mint.pubkey()
assert bootstrapped_market.state.quote_mint() == stubbed_quote_mint.pubkey()
@pytest.mark.async_integration
@ -70,7 +78,9 @@ async def test_market_load_requests(bootstrapped_market: AsyncMarket):
@pytest.mark.async_integration
@pytest.mark.asyncio
async def test_match_order(bootstrapped_market: AsyncMarket, stubbed_payer: Keypair):
await bootstrapped_market.match_orders(stubbed_payer, 2, TxOpts(skip_confirmation=False))
await bootstrapped_market.match_orders(
stubbed_payer, 2, TxOpts(skip_confirmation=False)
)
request_queue = await bootstrapped_market.load_request_queue()
# 0 request after matching.
@ -97,7 +107,9 @@ async def test_settle_fund(
stubbed_quote_wallet: Keypair,
stubbed_base_wallet: Keypair,
):
open_order_accounts = await bootstrapped_market.find_open_orders_accounts_for_owner(stubbed_payer.public_key)
open_order_accounts = await bootstrapped_market.find_open_orders_accounts_for_owner(
stubbed_payer.pubkey()
)
with pytest.raises(ValueError):
# Should not allow base_wallet to be base_vault
@ -105,7 +117,7 @@ async def test_settle_fund(
stubbed_payer,
open_order_accounts[0],
bootstrapped_market.state.base_vault(),
stubbed_quote_wallet.public_key,
stubbed_quote_wallet.pubkey(),
)
with pytest.raises(ValueError):
@ -113,7 +125,7 @@ async def test_settle_fund(
await bootstrapped_market.settle_funds(
stubbed_payer,
open_order_accounts[0],
stubbed_base_wallet.public_key,
stubbed_base_wallet.pubkey(),
bootstrapped_market.state.quote_vault(),
)
@ -121,8 +133,8 @@ async def test_settle_fund(
assert "error" not in await bootstrapped_market.settle_funds(
stubbed_payer,
open_order_account,
stubbed_base_wallet.public_key,
stubbed_quote_wallet.public_key,
stubbed_base_wallet.pubkey(),
stubbed_quote_wallet.pubkey(),
opts=TxOpts(skip_confirmation=False),
)
@ -139,7 +151,7 @@ async def test_order_placement_cancellation_cycle(
):
initial_request_len = len(await bootstrapped_market.load_request_queue())
await bootstrapped_market.place_order(
payer=stubbed_quote_wallet.public_key,
payer=stubbed_quote_wallet.pubkey(),
owner=stubbed_payer,
side=Side.BUY,
order_type=OrderType.LIMIT,
@ -161,7 +173,7 @@ async def test_order_placement_cancellation_cycle(
assert sum(1 for _ in asks) == 0
await bootstrapped_market.place_order(
payer=stubbed_base_wallet.public_key,
payer=stubbed_base_wallet.pubkey(),
owner=stubbed_payer,
side=Side.SELL,
order_type=OrderType.LIMIT,
@ -186,18 +198,26 @@ async def test_order_placement_cancellation_cycle(
assert sum(1 for _ in asks) == 1
for bid in bids:
await bootstrapped_market.cancel_order(stubbed_payer, bid, opts=TxOpts(skip_confirmation=False))
await bootstrapped_market.cancel_order(
stubbed_payer, bid, opts=TxOpts(skip_confirmation=False)
)
await bootstrapped_market.match_orders(stubbed_payer, 1, opts=TxOpts(skip_confirmation=False))
await bootstrapped_market.match_orders(
stubbed_payer, 1, opts=TxOpts(skip_confirmation=False)
)
# All bid order should have been cancelled.
bids = await bootstrapped_market.load_bids()
assert sum(1 for _ in bids) == 0
for ask in asks:
await bootstrapped_market.cancel_order(stubbed_payer, ask, opts=TxOpts(skip_confirmation=False))
await bootstrapped_market.cancel_order(
stubbed_payer, ask, opts=TxOpts(skip_confirmation=False)
)
await bootstrapped_market.match_orders(stubbed_payer, 1, opts=TxOpts(skip_confirmation=False))
await bootstrapped_market.match_orders(
stubbed_payer, 1, opts=TxOpts(skip_confirmation=False)
)
# All ask order should have been cancelled.
asks = await bootstrapped_market.load_asks()

View File

@ -1,8 +1,7 @@
import pytest
# from solana.account import Keypair
from solana.keypair import Keypair
from solana.publickey import PublicKey
from solders.keypair import Keypair
from solders.pubkey import Pubkey
@pytest.mark.integration
@ -22,7 +21,7 @@ def test_base_wallet(stubbed_base_wallet):
@pytest.mark.integration
def test_base_vault_pk(stubbed_base_vault_pk):
assert isinstance(stubbed_base_vault_pk, PublicKey)
assert isinstance(stubbed_base_vault_pk, Pubkey)
@pytest.mark.integration
@ -37,44 +36,44 @@ def test_quote_wallet(stubbed_quote_wallet):
@pytest.mark.integration
def test_quote_vault_pk(stubbed_quote_vault_pk):
assert isinstance(stubbed_quote_vault_pk, PublicKey)
assert isinstance(stubbed_quote_vault_pk, Pubkey)
@pytest.mark.integration
def test_market_pk(stubbed_market_pk):
assert isinstance(stubbed_market_pk, PublicKey)
assert isinstance(stubbed_market_pk, Pubkey)
@pytest.mark.integration
def test_event_q_pk(stubbed_event_q_pk):
assert isinstance(stubbed_event_q_pk, PublicKey)
assert isinstance(stubbed_event_q_pk, Pubkey)
@pytest.mark.integration
def test_req_q_pk(stubbed_req_q_pk):
assert isinstance(stubbed_req_q_pk, PublicKey)
assert isinstance(stubbed_req_q_pk, Pubkey)
@pytest.mark.integration
def test_bids_pk(stubbed_bids_pk):
assert isinstance(stubbed_bids_pk, PublicKey)
assert isinstance(stubbed_bids_pk, Pubkey)
@pytest.mark.integration
def test_asks_pk(stubbed_asks_pk):
assert isinstance(stubbed_asks_pk, PublicKey)
assert isinstance(stubbed_asks_pk, Pubkey)
@pytest.mark.integration
def test_bid_account_pk(stubbed_bid_account_pk):
assert isinstance(stubbed_bid_account_pk, PublicKey)
assert isinstance(stubbed_bid_account_pk, Pubkey)
@pytest.mark.integration
def test_ask_account_pk(stubbed_ask_account_pk):
assert isinstance(stubbed_ask_account_pk, PublicKey)
assert isinstance(stubbed_ask_account_pk, Pubkey)
@pytest.mark.integration
def test_dex_program_pk(stubbed_dex_program_pk):
assert isinstance(stubbed_dex_program_pk, PublicKey)
assert isinstance(stubbed_dex_program_pk, Pubkey)

View File

@ -7,7 +7,9 @@ from pyserum.market.types import MarketInfo, TokenInfo
@pytest.mark.integration
def test_get_live_markets():
"""Test get_live_markets."""
assert all(isinstance(market_info, MarketInfo) for market_info in get_live_markets())
assert all(
isinstance(market_info, MarketInfo) for market_info in get_live_markets()
)
@pytest.mark.integration

View File

@ -1,8 +1,8 @@
# pylint: disable=redefined-outer-name
import pytest
from solana.keypair import Keypair
from solana.publickey import PublicKey
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solana.rpc.types import TxOpts
@ -12,23 +12,30 @@ from pyserum.market import Market
@pytest.mark.integration
@pytest.fixture(scope="module")
def bootstrapped_market(http_client: Client, stubbed_market_pk: PublicKey, stubbed_dex_program_pk: PublicKey) -> Market:
return Market.load(http_client, stubbed_market_pk, stubbed_dex_program_pk, force_use_request_queue=True)
def bootstrapped_market(
http_client: Client, stubbed_market_pk: Pubkey, stubbed_dex_program_pk: Pubkey
) -> Market:
return Market.load(
http_client,
stubbed_market_pk,
stubbed_dex_program_pk,
force_use_request_queue=True,
)
@pytest.mark.integration
def test_bootstrapped_market(
bootstrapped_market: Market,
stubbed_market_pk: PublicKey,
stubbed_dex_program_pk: PublicKey,
stubbed_market_pk: Pubkey,
stubbed_dex_program_pk: Pubkey,
stubbed_base_mint: Keypair,
stubbed_quote_mint: Keypair,
):
assert isinstance(bootstrapped_market, Market)
assert bootstrapped_market.state.public_key() == stubbed_market_pk
assert bootstrapped_market.state.program_id() == stubbed_dex_program_pk
assert bootstrapped_market.state.base_mint() == stubbed_base_mint.public_key
assert bootstrapped_market.state.quote_mint() == stubbed_quote_mint.public_key
assert bootstrapped_market.state.base_mint() == stubbed_base_mint.pubkey()
assert bootstrapped_market.state.quote_mint() == stubbed_quote_mint.pubkey()
@pytest.mark.integration
@ -86,7 +93,9 @@ def test_settle_fund(
stubbed_quote_wallet: Keypair,
stubbed_base_wallet: Keypair,
):
open_order_accounts = bootstrapped_market.find_open_orders_accounts_for_owner(stubbed_payer.public_key)
open_order_accounts = bootstrapped_market.find_open_orders_accounts_for_owner(
stubbed_payer.pubkey()
)
with pytest.raises(ValueError):
# Should not allow base_wallet to be base_vault
@ -94,7 +103,7 @@ def test_settle_fund(
stubbed_payer,
open_order_accounts[0],
bootstrapped_market.state.base_vault(),
stubbed_quote_wallet.public_key,
stubbed_quote_wallet.pubkey(),
)
with pytest.raises(ValueError):
@ -102,7 +111,7 @@ def test_settle_fund(
bootstrapped_market.settle_funds(
stubbed_payer,
open_order_accounts[0],
stubbed_base_wallet.public_key,
stubbed_base_wallet.pubkey(),
bootstrapped_market.state.quote_vault(),
)
@ -110,8 +119,8 @@ def test_settle_fund(
assert "error" not in bootstrapped_market.settle_funds(
stubbed_payer,
open_order_account,
stubbed_base_wallet.public_key,
stubbed_quote_wallet.public_key,
stubbed_base_wallet.pubkey(),
stubbed_quote_wallet.pubkey(),
opts=TxOpts(skip_confirmation=False),
)
@ -127,7 +136,7 @@ def test_order_placement_cancellation_cycle(
):
initial_request_len = len(bootstrapped_market.load_request_queue())
bootstrapped_market.place_order(
payer=stubbed_quote_wallet.public_key,
payer=stubbed_quote_wallet.pubkey(),
owner=stubbed_payer,
side=Side.BUY,
order_type=OrderType.LIMIT,
@ -149,7 +158,7 @@ def test_order_placement_cancellation_cycle(
assert sum(1 for _ in asks) == 0
bootstrapped_market.place_order(
payer=stubbed_base_wallet.public_key,
payer=stubbed_base_wallet.pubkey(),
owner=stubbed_payer,
side=Side.SELL,
order_type=OrderType.LIMIT,
@ -174,18 +183,26 @@ def test_order_placement_cancellation_cycle(
assert sum(1 for _ in asks) == 1
for bid in bids:
bootstrapped_market.cancel_order(stubbed_payer, bid, opts=TxOpts(skip_confirmation=False))
bootstrapped_market.cancel_order(
stubbed_payer, bid, opts=TxOpts(skip_confirmation=False)
)
bootstrapped_market.match_orders(stubbed_payer, 1, opts=TxOpts(skip_confirmation=False))
bootstrapped_market.match_orders(
stubbed_payer, 1, opts=TxOpts(skip_confirmation=False)
)
# All bid order should have been cancelled.
bids = bootstrapped_market.load_bids()
assert sum(1 for _ in bids) == 0
for ask in asks:
bootstrapped_market.cancel_order(stubbed_payer, ask, opts=TxOpts(skip_confirmation=False))
bootstrapped_market.cancel_order(
stubbed_payer, ask, opts=TxOpts(skip_confirmation=False)
)
bootstrapped_market.match_orders(stubbed_payer, 1, opts=TxOpts(skip_confirmation=False))
bootstrapped_market.match_orders(
stubbed_payer, 1, opts=TxOpts(skip_confirmation=False)
)
# All ask order should have been cancelled.
asks = bootstrapped_market.load_asks()

View File

@ -1,5 +1,5 @@
"""Tests for instruction layouts."""
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from pyserum._layouts.instructions import _VERSION, INSTRUCTIONS_LAYOUT, InstructionType
from pyserum.enums import OrderType, Side
@ -27,7 +27,12 @@ def test_parse_initialize_market():
expected = bytes.fromhex(
"000000000001000000000000000200000000000000030004000000000000000500000000000000"
) # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.INITIALIZE_MARKET, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.INITIALIZE_MARKET, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.INITIALIZE_MARKET, args, expected)
@ -43,7 +48,12 @@ def test_parse_new_order():
expected = bytes.fromhex(
"00010000000100000001000000000000000200000000000000020000000300000000000000"
) # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.NEW_ORDER, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.NEW_ORDER, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.NEW_ORDER, args, expected)
@ -51,7 +61,12 @@ def test_parse_match_orders():
"""Test parsing raw match orders data."""
args = {"limit": 1}
expected = bytes.fromhex("00020000000100") # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.MATCH_ORDER, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.MATCH_ORDER, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.MATCH_ORDER, args, expected)
@ -59,7 +74,12 @@ def test_parse_consume_events():
"""Test parsing raw consume events data."""
args = {"limit": 1}
expected = bytes.fromhex("00030000000100") # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.CONSUME_EVENTS, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.CONSUME_EVENTS, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.CONSUME_EVENTS, args, expected)
@ -69,20 +89,30 @@ def test_parse_cancel_order():
"side": Side.BUY,
"order_id": (1234567890).to_bytes(16, "little"),
"open_orders_slot": 123,
"open_orders": bytes(PublicKey(123)),
"open_orders": bytes(Pubkey.from_string("111111111111111111111111111111138")),
}
expected = bytes.fromhex(
"000400000000000000d202964900000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000007b7b"
) # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.CANCEL_ORDER, args=args)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.CANCEL_ORDER, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.CANCEL_ORDER, args, expected)
def test_parse_settle_funds():
"""Test parsing raw settle funds data."""
expected = bytes.fromhex("0005000000") # Raw hex from serum.js
assert INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.SETTLE_FUNDS, args=None)) == expected
assert (
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.SETTLE_FUNDS, args=None)
)
== expected
)
assert_parsed_layout(InstructionType.SETTLE_FUNDS, None, expected)
@ -91,7 +121,9 @@ def test_parse_cancel_order_by_client_id():
args = {"client_id": 123}
expected = bytes.fromhex("00060000007b00000000000000") # Raw hex from serum.js
assert (
INSTRUCTIONS_LAYOUT.build(dict(instruction_type=InstructionType.CANCEL_ORDER_BY_CLIENT_ID, args=args))
INSTRUCTIONS_LAYOUT.build(
dict(instruction_type=InstructionType.CANCEL_ORDER_BY_CLIENT_ID, args=args)
)
== expected
)
assert_parsed_layout(InstructionType.CANCEL_ORDER_BY_CLIENT_ID, args, expected)

View File

@ -1,6 +1,6 @@
"""Test instructions."""
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
import pyserum.instructions as inlib
from pyserum.enums import OrderType, Side
@ -9,15 +9,15 @@ from pyserum.enums import OrderType, Side
def test_initialize_market():
"""Test initialize market."""
params = inlib.InitializeMarketParams(
market=PublicKey(0),
request_queue=PublicKey(1),
event_queue=PublicKey(2),
bids=PublicKey(3),
asks=PublicKey(4),
base_vault=PublicKey(5),
quote_vault=PublicKey(6),
base_mint=PublicKey(7),
quote_mint=PublicKey(8),
market=Pubkey.from_string("11111111111111111111111111111112"),
request_queue=Pubkey.from_string("11111111111111111111111111111113"),
event_queue=Pubkey.from_string("11111111111111111111111111111114"),
bids=Pubkey.from_string("11111111111111111111111111111115"),
asks=Pubkey.from_string("11111111111111111111111111111116"),
base_vault=Pubkey.from_string("11111111111111111111111111111117"),
quote_vault=Pubkey.from_string("11111111111111111111111111111118"),
base_mint=Pubkey.from_string("11111111111111111111111111111119"),
quote_mint=Pubkey.from_string("1111111111111111111111111111111A"),
base_lot_size=1,
quote_lot_size=2,
fee_rate_bps=3,
@ -31,13 +31,13 @@ def test_initialize_market():
def test_new_orders():
"""Test match orders."""
params = inlib.NewOrderParams(
market=PublicKey(0),
open_orders=PublicKey(1),
payer=PublicKey(2),
owner=PublicKey(3),
request_queue=PublicKey(4),
base_vault=PublicKey(5),
quote_vault=PublicKey(6),
market=Pubkey.from_string("11111111111111111111111111111112"),
open_orders=Pubkey.from_string("11111111111111111111111111111113"),
payer=Pubkey.from_string("11111111111111111111111111111114"),
owner=Pubkey.from_string("11111111111111111111111111111115"),
request_queue=Pubkey.from_string("11111111111111111111111111111116"),
base_vault=Pubkey.from_string("11111111111111111111111111111117"),
quote_vault=Pubkey.from_string("11111111111111111111111111111118"),
side=Side.BUY,
limit_price=1,
max_quantity=1,
@ -51,13 +51,13 @@ def test_new_orders():
def test_match_orders():
"""Test match orders."""
params = inlib.MatchOrdersParams(
market=PublicKey(0),
request_queue=PublicKey(1),
event_queue=PublicKey(2),
bids=PublicKey(3),
asks=PublicKey(4),
base_vault=PublicKey(5),
quote_vault=PublicKey(6),
market=Pubkey.from_string("11111111111111111111111111111112"),
request_queue=Pubkey.from_string("11111111111111111111111111111113"),
event_queue=Pubkey.from_string("11111111111111111111111111111114"),
bids=Pubkey.from_string("11111111111111111111111111111115"),
asks=Pubkey.from_string("11111111111111111111111111111116"),
base_vault=Pubkey.from_string("11111111111111111111111111111117"),
quote_vault=Pubkey.from_string("11111111111111111111111111111118"),
limit=1,
)
instruction = inlib.match_orders(params)
@ -66,9 +66,12 @@ def test_match_orders():
def test_consume_events():
params = inlib.ConsumeEventsParams(
market=PublicKey(0),
event_queue=PublicKey(1),
open_orders_accounts=[PublicKey(i + 2) for i in range(8)],
market=Pubkey.from_string("11111111111111111111111111111112"),
event_queue=Pubkey.from_string("11111111111111111111111111111113"),
open_orders_accounts=[
Pubkey.from_string("1111111111111111111111111111111{:X}".format(i + 2))
for i in range(8)
],
limit=1,
)
instruction = inlib.consume_events(params)
@ -78,10 +81,10 @@ def test_consume_events():
def test_cancel_order():
"""Test cancel order."""
params = inlib.CancelOrderParams(
market=PublicKey(0),
request_queue=PublicKey(1),
owner=PublicKey(2),
open_orders=PublicKey(3),
market=Pubkey.from_string("11111111111111111111111111111112"),
request_queue=Pubkey.from_string("11111111111111111111111111111113"),
owner=Pubkey.from_string("11111111111111111111111111111114"),
open_orders=Pubkey.from_string("11111111111111111111111111111115"),
side=Side.BUY,
order_id=1,
open_orders_slot=1,
@ -93,7 +96,11 @@ def test_cancel_order():
def test_cancel_order_by_client_id():
"""Test cancel order by client id."""
params = inlib.CancelOrderByClientIDParams(
market=PublicKey(0), request_queue=PublicKey(1), owner=PublicKey(2), open_orders=PublicKey(3), client_id=1
market=Pubkey.from_string("11111111111111111111111111111112"),
request_queue=Pubkey.from_string("11111111111111111111111111111113"),
owner=Pubkey.from_string("11111111111111111111111111111114"),
open_orders=Pubkey.from_string("11111111111111111111111111111115"),
client_id=1,
)
instruction = inlib.cancel_order_by_client_id(params)
assert inlib.decode_cancel_order_by_client_id(instruction) == params
@ -102,14 +109,14 @@ def test_cancel_order_by_client_id():
def test_settle_funds():
"""Test settle funds."""
params = inlib.SettleFundsParams(
market=PublicKey(0),
owner=PublicKey(1),
open_orders=PublicKey(2),
base_vault=PublicKey(3),
quote_vault=PublicKey(4),
base_wallet=PublicKey(5),
quote_wallet=PublicKey(6),
vault_signer=PublicKey(7),
market=Pubkey.from_string("11111111111111111111111111111112"),
owner=Pubkey.from_string("11111111111111111111111111111113"),
open_orders=Pubkey.from_string("11111111111111111111111111111114"),
base_vault=Pubkey.from_string("11111111111111111111111111111115"),
quote_vault=Pubkey.from_string("11111111111111111111111111111116"),
base_wallet=Pubkey.from_string("11111111111111111111111111111117"),
quote_wallet=Pubkey.from_string("11111111111111111111111111111118"),
vault_signer=Pubkey.from_string("11111111111111111111111111111119"),
)
instruction = inlib.settle_funds(params)
assert inlib.decode_settle_funds(instruction) == params
@ -118,10 +125,10 @@ def test_settle_funds():
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),
open_orders=Pubkey.from_string("11111111111111111111111111111112"),
owner=Pubkey.from_string("11111111111111111111111111111113"),
sol_wallet=Pubkey.from_string("11111111111111111111111111111114"),
market=Pubkey.from_string("11111111111111111111111111111115"),
)
instruction = inlib.close_open_orders(params)
assert inlib.decode_close_open_orders(instruction) == params
@ -130,7 +137,10 @@ def test_close_open_orders():
def test_init_open_orders():
"""Test settle funds."""
params = inlib.InitOpenOrdersParams(
open_orders=PublicKey(0), owner=PublicKey(1), market=PublicKey(2), market_authority=None
open_orders=Pubkey.from_string("11111111111111111111111111111112"),
owner=Pubkey.from_string("11111111111111111111111111111113"),
market=Pubkey.from_string("11111111111111111111111111111114"),
market_authority=None,
)
instruction = inlib.init_open_orders(params)
assert inlib.decode_init_open_orders(instruction) == params
@ -139,10 +149,10 @@ def test_init_open_orders():
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),
open_orders=Pubkey.from_string("11111111111111111111111111111112"),
owner=Pubkey.from_string("11111111111111111111111111111113"),
market=Pubkey.from_string("11111111111111111111111111111114"),
market_authority=Pubkey.from_string("11111111111111111111111111111115"),
)
instruction = inlib.init_open_orders(params)
assert inlib.decode_init_open_orders(instruction) == params

View File

@ -1,7 +1,7 @@
import base64
import pytest
from solana.publickey import PublicKey
from solders.pubkey import Pubkey
from pyserum.open_orders_account import OPEN_ORDERS_LAYOUT, OpenOrdersAccount
@ -19,10 +19,23 @@ def test_decode_open_order_account_layout():
open_order_account = OPEN_ORDERS_LAYOUT.parse(data)
assert open_order_account.account_flags.open_orders
assert open_order_account.account_flags.initialized
assert PublicKey(open_order_account.market) == PublicKey("4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2")
assert PublicKey(open_order_account.owner) == PublicKey("7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q")
assert Pubkey.from_bytes(open_order_account.market) == Pubkey.from_string(
"4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2"
)
assert Pubkey.from_bytes(open_order_account.owner) == Pubkey.from_string(
"7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q"
)
# if there is no order the byte returned here will be all 0. In this case we have three orders.
assert len([order for order in open_order_account.orders if int.from_bytes(order, "little") != 0]) == 3
assert (
len(
[
order
for order in open_order_account.orders
if int.from_bytes(order, "little") != 0
]
)
== 3
)
# the first three order are bid order
assert int.from_bytes(open_order_account.is_bid_bits, "little") == 0b111
@ -35,9 +48,15 @@ def test_decode_open_order_account():
with open(OPEN_ORDER_ACCOUNT_BIN_PATH, "r") as input_file:
base64_res = input_file.read()
data = base64.decodebytes(base64_res.encode("ascii"))
open_order_account = OpenOrdersAccount.from_bytes(PublicKey(1), data)
assert open_order_account.market == PublicKey("4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2")
assert open_order_account.owner == PublicKey("7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q")
open_order_account = OpenOrdersAccount.from_bytes(
Pubkey.from_string("11111111111111111111111111111112"), data
)
assert open_order_account.market == Pubkey.from_string(
"4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2"
)
assert open_order_account.owner == Pubkey.from_string(
"7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q"
)
assert len([order for order in open_order_account.orders if order != 0]) == 3
# the first three order are bid order
assert open_order_account.is_bid_bits == 0b111

View File

@ -2,7 +2,12 @@
import base64
from pyserum._layouts.slab import ORDER_BOOK_LAYOUT, SLAB_HEADER_LAYOUT, SLAB_LAYOUT, SLAB_NODE_LAYOUT
from pyserum._layouts.slab import (
ORDER_BOOK_LAYOUT,
SLAB_HEADER_LAYOUT,
SLAB_LAYOUT,
SLAB_NODE_LAYOUT,
)
from pyserum.market._internal.slab import Slab
from .binary_file_path import ASK_ORDER_BIN_PATH