618 lines
25 KiB
Python
618 lines
25 KiB
Python
# # ⚠ Warning
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
# [🥭 Mango Markets](https://mango.markets/) support is available at:
|
|
# [Docs](https://docs.mango.markets/)
|
|
# [Discord](https://discord.gg/67jySBhxrg)
|
|
# [Twitter](https://twitter.com/mangomarkets)
|
|
# [Github](https://github.com/blockworks-foundation)
|
|
# [Email](mailto:hello@blockworks.foundation)
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import typing
|
|
|
|
from decimal import Decimal
|
|
from solana.publickey import PublicKey
|
|
from solana.rpc.commitment import Commitment, Finalized
|
|
|
|
from .client import (
|
|
AbstractSlotHolder,
|
|
CheckingSlotHolder,
|
|
ClusterUrlData,
|
|
NullSlotHolder,
|
|
TransactionMonitor,
|
|
NullTransactionMonitor,
|
|
)
|
|
from .constants import MangoConstants
|
|
from .context import Context
|
|
from .idsjsonmarketlookup import IdsJsonMarketLookup
|
|
from .instrumentlookup import (
|
|
InstrumentLookup,
|
|
CompoundInstrumentLookup,
|
|
IdsJsonTokenLookup,
|
|
NonSPLInstrumentLookup,
|
|
SPLTokenLookup,
|
|
)
|
|
from .marketlookup import CompoundMarketLookup, MarketLookup
|
|
from .serummarketlookup import SerumMarketLookup
|
|
from .transactionmonitoring import (
|
|
DequeTransactionStatusCollector,
|
|
WebSocketTransactionMonitor,
|
|
)
|
|
|
|
|
|
# # 🥭 ContextBuilder
|
|
#
|
|
# ## Environment Variables
|
|
#
|
|
# It's possible to override the values in the `Context` variables provided. This can be easier than creating
|
|
# the `Context` in code or introducing dependencies and configuration.
|
|
#
|
|
# The following environment variables are read:
|
|
# * NAME
|
|
# * CLUSTER
|
|
# * CLUSTER_URL
|
|
# * GROUP_NAME
|
|
# * GROUP_ADDRESS
|
|
# * MANGO_PROGRAM_ADDRESS
|
|
# * SERUM_PROGRAM_ADDRESS
|
|
# * MANGO_REFLINK_ADDRESS
|
|
|
|
|
|
# # 🥭 ContextBuilder class
|
|
#
|
|
# A `ContextBuilder` class to allow building `Context` objects without introducing circular dependencies.
|
|
#
|
|
class ContextBuilder:
|
|
|
|
# Utility class for parsing cluster-url command line parameter
|
|
#
|
|
class ParseClusterUrls(argparse.Action):
|
|
cluster_urls: typing.List[ClusterUrlData] = []
|
|
|
|
def __call__(
|
|
self,
|
|
parser: argparse.ArgumentParser,
|
|
namespace: object,
|
|
values: typing.Any,
|
|
option_string: typing.Optional[str] = None,
|
|
) -> None:
|
|
if values:
|
|
if len(values) == 1:
|
|
self.cluster_urls.append(ClusterUrlData(rpc=values[0]))
|
|
elif len(values) == 2:
|
|
self.cluster_urls.append(
|
|
ClusterUrlData(rpc=values[0], ws=values[1])
|
|
)
|
|
else:
|
|
raise parser.error(
|
|
"Argument --cluster-url permits maximal two parameters. The first one configures HTTP connection url, the second one "
|
|
"configures the WS connection url. Example: --cluster-url https://localhost:8181 wss://localhost:8282"
|
|
)
|
|
setattr(namespace, self.dest, self.cluster_urls)
|
|
|
|
# Configuring a `Context` is a common operation for command-line programs and can involve a
|
|
# lot of duplicate code.
|
|
#
|
|
# This function centralises some of it to ensure consistency and readability.
|
|
#
|
|
@staticmethod
|
|
def add_command_line_parameters(
|
|
parser: argparse.ArgumentParser, monitor_transactions_default: bool = False
|
|
) -> None:
|
|
parser.add_argument(
|
|
"--name",
|
|
type=str,
|
|
default="Mango Explorer",
|
|
help="Name of the program (used in reports and alerts)",
|
|
)
|
|
parser.add_argument(
|
|
"--cluster-name", type=str, default=None, help="Solana RPC cluster name"
|
|
)
|
|
parser.add_argument(
|
|
"--cluster-url",
|
|
nargs="*",
|
|
type=str,
|
|
action=ContextBuilder.ParseClusterUrls,
|
|
default=[],
|
|
help="Solana RPC cluster URL (can be specified multiple times to provide failover when one errors; optional second parameter value defines websocket connection)",
|
|
)
|
|
parser.add_argument(
|
|
"--group-name", type=str, default=None, help="Mango group name"
|
|
)
|
|
parser.add_argument(
|
|
"--group-address", type=PublicKey, default=None, help="Mango group address"
|
|
)
|
|
parser.add_argument(
|
|
"--mango-program-address",
|
|
type=PublicKey,
|
|
default=None,
|
|
help="Mango program address",
|
|
)
|
|
parser.add_argument(
|
|
"--serum-program-address",
|
|
type=PublicKey,
|
|
default=None,
|
|
help="Serum program address",
|
|
)
|
|
parser.add_argument(
|
|
"--skip-preflight",
|
|
default=False,
|
|
action="store_true",
|
|
help="Skip pre-flight checks",
|
|
)
|
|
parser.add_argument(
|
|
"--tpu-retransmissions",
|
|
default=-1,
|
|
type=int,
|
|
help="Number of attempts the RPC node runs to deliver a transaction to TPU (default -1 means redelivering until success or recent blockhash expires)",
|
|
)
|
|
parser.add_argument(
|
|
"--commitment",
|
|
type=str,
|
|
default=None,
|
|
help="Commitment to use when sending transactions (can be 'finalized', 'confirmed' or 'processed')",
|
|
)
|
|
parser.add_argument(
|
|
"--encoding",
|
|
type=str,
|
|
default=None,
|
|
help="Encoding to request when receiving data from Solana (options are 'base58' (slow), 'base64', 'base64+zstd', or 'jsonParsed')",
|
|
)
|
|
parser.add_argument(
|
|
"--blockhash-cache-duration",
|
|
type=int,
|
|
help="How long (in seconds) to cache 'recent' blockhashes",
|
|
)
|
|
parser.add_argument(
|
|
"--http-request-timeout",
|
|
type=float,
|
|
default=20,
|
|
help="What is the timeout for HTTP requests to when calling to RPC nodes (in seconds), -1 means no timeout",
|
|
)
|
|
parser.add_argument(
|
|
"--stale-data-pause-before-retry",
|
|
type=Decimal,
|
|
help="How long (in seconds, e.g. 0.1) to pause after retrieving stale data before retrying",
|
|
)
|
|
parser.add_argument(
|
|
"--stale-data-maximum-retries",
|
|
type=int,
|
|
default=0,
|
|
help="How many times to retry fetching data after being given stale data before giving up",
|
|
)
|
|
parser.add_argument(
|
|
"--monitor-transactions",
|
|
default=monitor_transactions_default,
|
|
action="store_true",
|
|
help="Watch transactions and log their results",
|
|
)
|
|
parser.add_argument(
|
|
"--monitor-transactions-commitment",
|
|
type=Commitment,
|
|
default=Finalized,
|
|
help="Monitor transactions until they reach this commitment level",
|
|
)
|
|
parser.add_argument(
|
|
"--monitor-transactions-timeout",
|
|
type=float,
|
|
default=90,
|
|
help="Time after which to assume a transaction will not reach the specified commitment level",
|
|
)
|
|
parser.add_argument(
|
|
"--gma-chunk-size",
|
|
type=Decimal,
|
|
default=None,
|
|
help="Maximum number of addresses to send in a single call to getMultipleAccounts()",
|
|
)
|
|
parser.add_argument(
|
|
"--gma-chunk-pause",
|
|
type=Decimal,
|
|
default=None,
|
|
help="Number of seconds to pause between successive getMultipleAccounts() calls to avoid rate limiting",
|
|
)
|
|
parser.add_argument(
|
|
"--reflink", type=PublicKey, default=None, help="Referral public key"
|
|
)
|
|
|
|
# This function is the converse of `add_command_line_parameters()` - it takes
|
|
# an argument of parsed command-line parameters and expects to see the ones it added
|
|
# to that collection in the `add_command_line_parameters()` call.
|
|
#
|
|
# It then uses those parameters to create a properly-configured `Context` object.
|
|
#
|
|
@staticmethod
|
|
def from_command_line_parameters(args: argparse.Namespace) -> Context:
|
|
name: typing.Optional[str] = args.name
|
|
cluster_name: typing.Optional[str] = args.cluster_name
|
|
cluster_urls: typing.Optional[
|
|
typing.Sequence[ClusterUrlData]
|
|
] = args.cluster_url
|
|
group_name: typing.Optional[str] = args.group_name
|
|
group_address: typing.Optional[PublicKey] = args.group_address
|
|
mango_program_address: typing.Optional[PublicKey] = args.mango_program_address
|
|
serum_program_address: typing.Optional[PublicKey] = args.serum_program_address
|
|
skip_preflight: bool = bool(args.skip_preflight)
|
|
tpu_retransmissions: int = int(args.tpu_retransmissions)
|
|
commitment: typing.Optional[str] = args.commitment
|
|
encoding: typing.Optional[str] = args.encoding
|
|
blockhash_cache_duration: typing.Optional[int] = args.blockhash_cache_duration
|
|
http_request_timeout: typing.Optional[float] = args.http_request_timeout
|
|
stale_data_pause_before_retry: typing.Optional[
|
|
Decimal
|
|
] = args.stale_data_pause_before_retry
|
|
stale_data_maximum_retries: typing.Optional[
|
|
int
|
|
] = args.stale_data_maximum_retries
|
|
gma_chunk_size: typing.Optional[Decimal] = args.gma_chunk_size
|
|
gma_chunk_pause: typing.Optional[Decimal] = args.gma_chunk_pause
|
|
reflink: typing.Optional[PublicKey] = args.reflink
|
|
monitor_transactions: bool = bool(args.monitor_transactions)
|
|
monitor_transactions_commitment: typing.Optional[
|
|
Commitment
|
|
] = args.monitor_transactions_commitment
|
|
monitor_transactions_timeout: typing.Optional[
|
|
float
|
|
] = args.monitor_transactions_timeout
|
|
|
|
# Do this here so build() only ever has to handle the sequence of retry times. (It gets messy
|
|
# passing around the sequnce *plus* the data to reconstruct it for build().)
|
|
actual_stale_data_pauses_before_retry: typing.Sequence[float]
|
|
actual_slot_holder: AbstractSlotHolder
|
|
if stale_data_maximum_retries == 0:
|
|
# Stale data checking is disabled
|
|
actual_stale_data_pauses_before_retry = []
|
|
actual_slot_holder = NullSlotHolder()
|
|
else:
|
|
retries: int = stale_data_maximum_retries or 10
|
|
pause: Decimal = stale_data_pause_before_retry or Decimal("0.1")
|
|
actual_stale_data_pauses_before_retry = [float(pause)] * retries
|
|
actual_slot_holder = CheckingSlotHolder()
|
|
|
|
context: Context = ContextBuilder.build(
|
|
name,
|
|
cluster_name,
|
|
cluster_urls,
|
|
skip_preflight,
|
|
tpu_retransmissions,
|
|
commitment,
|
|
encoding,
|
|
blockhash_cache_duration,
|
|
http_request_timeout,
|
|
actual_stale_data_pauses_before_retry,
|
|
group_name,
|
|
group_address,
|
|
mango_program_address,
|
|
serum_program_address,
|
|
gma_chunk_size,
|
|
gma_chunk_pause,
|
|
reflink,
|
|
monitor_transactions,
|
|
monitor_transactions_commitment,
|
|
monitor_transactions_timeout,
|
|
actual_slot_holder,
|
|
)
|
|
|
|
logging.debug(f"{context}")
|
|
|
|
return context
|
|
|
|
@staticmethod
|
|
def default() -> Context:
|
|
return ContextBuilder.build()
|
|
|
|
@staticmethod
|
|
def from_group_name(context: Context, group_name: str) -> Context:
|
|
return ContextBuilder.build(
|
|
context.name,
|
|
context.client.cluster_name,
|
|
context.client.cluster_urls,
|
|
context.client.skip_preflight,
|
|
context.client.tpu_retransmissions,
|
|
context.client.commitment,
|
|
context.client.encoding,
|
|
context.client.blockhash_cache_duration,
|
|
None,
|
|
context.client.stale_data_pauses_before_retry,
|
|
group_name,
|
|
None,
|
|
None,
|
|
None,
|
|
context.gma_chunk_size,
|
|
context.gma_chunk_pause,
|
|
context.reflink,
|
|
not isinstance(context.client.transaction_monitor, NullTransactionMonitor),
|
|
context.client.transaction_monitor.commitment,
|
|
context.client.transaction_monitor.transaction_timeout,
|
|
context.client.transaction_monitor.slot_holder,
|
|
)
|
|
|
|
@staticmethod
|
|
def force_to_cluster(context: Context, cluster_name: str) -> Context:
|
|
return ContextBuilder.build(
|
|
context.name,
|
|
cluster_name,
|
|
None, # Can't use parameter cluster URLs if we're switching network
|
|
context.client.skip_preflight,
|
|
context.client.tpu_retransmissions,
|
|
context.client.commitment,
|
|
context.client.encoding,
|
|
context.client.blockhash_cache_duration,
|
|
context.client.rpc_caller.all_providers[0].http_request_timeout,
|
|
context.client.stale_data_pauses_before_retry,
|
|
None, # Use new cluster's default Group name
|
|
None, # Use new cluster's default Group address
|
|
None, # Use new cluster's default Program address
|
|
None, # Use new cluster's default Serum address
|
|
context.gma_chunk_size,
|
|
context.gma_chunk_pause,
|
|
context.reflink,
|
|
False, # Don't try to watch transactions on a switched Context
|
|
None,
|
|
None,
|
|
NullSlotHolder(),
|
|
)
|
|
|
|
@staticmethod
|
|
def build(
|
|
name: typing.Optional[str] = None,
|
|
cluster_name: typing.Optional[str] = None,
|
|
cluster_urls: typing.Optional[typing.Sequence[ClusterUrlData]] = None,
|
|
skip_preflight: bool = False,
|
|
tpu_retransmissions: int = -1,
|
|
commitment: typing.Optional[str] = None,
|
|
encoding: typing.Optional[str] = None,
|
|
blockhash_cache_duration: typing.Optional[int] = None,
|
|
http_request_timeout: typing.Optional[float] = None,
|
|
stale_data_pauses_before_retry: typing.Optional[typing.Sequence[float]] = None,
|
|
group_name: typing.Optional[str] = None,
|
|
group_address: typing.Optional[PublicKey] = None,
|
|
program_address: typing.Optional[PublicKey] = None,
|
|
serum_program_address: typing.Optional[PublicKey] = None,
|
|
gma_chunk_size: typing.Optional[Decimal] = None,
|
|
gma_chunk_pause: typing.Optional[Decimal] = None,
|
|
reflink: typing.Optional[PublicKey] = None,
|
|
monitor_transactions: typing.Optional[bool] = None,
|
|
monitor_transactions_commitment: typing.Optional[Commitment] = None,
|
|
monitor_transactions_timeout: typing.Optional[float] = None,
|
|
slot_holder: typing.Optional[AbstractSlotHolder] = None,
|
|
) -> "Context":
|
|
def __public_key_or_none(
|
|
address: typing.Optional[str],
|
|
) -> typing.Optional[PublicKey]:
|
|
if address is not None and address != "":
|
|
return PublicKey(address)
|
|
return None
|
|
|
|
# The first group is only used to determine the default cluster if it is not otherwise specified.
|
|
first_group_data = MangoConstants["groups"][0]
|
|
actual_name: str = name or os.environ.get("NAME") or "Mango Explorer"
|
|
actual_cluster: str = (
|
|
cluster_name
|
|
or os.environ.get("CLUSTER_NAME")
|
|
or first_group_data["cluster"]
|
|
)
|
|
|
|
# Now that we have the actual cluster name, taking environment variables and defaults into account,
|
|
# we can decide what we want as the default group.
|
|
for group_data in MangoConstants["groups"]:
|
|
if group_data["cluster"] == actual_cluster:
|
|
default_group_data = group_data
|
|
break
|
|
|
|
actual_commitment: str = commitment or "processed"
|
|
actual_encoding: str = encoding or "base64"
|
|
actual_blockhash_cache_duration: int = blockhash_cache_duration or 0
|
|
actual_stale_data_pauses_before_retry: typing.Sequence[float] = (
|
|
stale_data_pauses_before_retry or []
|
|
)
|
|
actual_http_request_timeout: float = http_request_timeout or -1
|
|
actual_tpu_retransmissions: int = int(tpu_retransmissions)
|
|
|
|
actual_cluster_urls: typing.Optional[
|
|
typing.Sequence[ClusterUrlData]
|
|
] = cluster_urls
|
|
if actual_cluster_urls is None or len(actual_cluster_urls) == 0:
|
|
cluster_url_from_environment: typing.Optional[str] = os.environ.get(
|
|
"CLUSTER_URL"
|
|
)
|
|
if (
|
|
cluster_url_from_environment is not None
|
|
and cluster_url_from_environment != ""
|
|
):
|
|
actual_cluster_urls = [ClusterUrlData(rpc=cluster_url_from_environment)]
|
|
else:
|
|
actual_cluster_urls = [
|
|
ClusterUrlData(rpc=MangoConstants["cluster_urls"][actual_cluster])
|
|
]
|
|
|
|
actual_skip_preflight: bool = skip_preflight
|
|
actual_group_name: str = (
|
|
group_name or os.environ.get("GROUP_NAME") or default_group_data["name"]
|
|
)
|
|
|
|
found_group_data: typing.Any = None
|
|
for group in MangoConstants["groups"]:
|
|
if (
|
|
group["cluster"] == actual_cluster
|
|
and group["name"].upper() == actual_group_name.upper()
|
|
):
|
|
found_group_data = group
|
|
|
|
if found_group_data is None:
|
|
raise Exception(
|
|
f"Could not find group named '{actual_group_name}' in cluster '{actual_cluster}'."
|
|
)
|
|
|
|
actual_group_address: PublicKey = (
|
|
group_address
|
|
or __public_key_or_none(os.environ.get("GROUP_ADDRESS"))
|
|
or PublicKey(found_group_data["publicKey"])
|
|
)
|
|
actual_program_address: PublicKey = (
|
|
program_address
|
|
or __public_key_or_none(os.environ.get("MANGO_PROGRAM_ADDRESS"))
|
|
or PublicKey(found_group_data["mangoProgramId"])
|
|
)
|
|
actual_serum_program_address: PublicKey = (
|
|
serum_program_address
|
|
or __public_key_or_none(os.environ.get("SERUM_PROGRAM_ADDRESS"))
|
|
or PublicKey(found_group_data["serumProgramId"])
|
|
)
|
|
|
|
actual_gma_chunk_size: Decimal = gma_chunk_size or Decimal(100)
|
|
actual_gma_chunk_pause: Decimal = gma_chunk_pause or Decimal(0)
|
|
|
|
actual_reflink: typing.Optional[PublicKey] = reflink or __public_key_or_none(
|
|
os.environ.get("MANGO_REFLINK_ADDRESS")
|
|
)
|
|
|
|
ids_json_token_lookup: InstrumentLookup = IdsJsonTokenLookup(
|
|
actual_cluster, actual_group_name
|
|
)
|
|
instrument_lookup: InstrumentLookup = ids_json_token_lookup
|
|
if actual_cluster == "mainnet":
|
|
# 'Overrides' are for problematic situations.
|
|
#
|
|
# We want to be able to use the community-owned SPL Token Registry JSON file. It holds details
|
|
# of most tokens and allows our Serum code to work with any of them and their markets.
|
|
#
|
|
# The problems come when they decide to rename symbols, like they did with "ETH".
|
|
#
|
|
# Mango uses "ETH" with a mint 2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk and for a long time
|
|
# the SPL JSON file also used "ETH" with a mint of 2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk.
|
|
#
|
|
# Then the SPL JSON file was updated and the mint 2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk now
|
|
# has the symbol 'soETH' for 'Wrapped Ethereum (Sollet)', and "ETH" is now 'Wrapped Ether (Wormhole)'
|
|
# with a mint of 7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs.
|
|
#
|
|
# 'Overrides' allows us to put the details we expect for 'ETH' into our loader, ahead of the SPL
|
|
# JSON, so that our code and users can continue to use, for example, ETH/USDT, as they expect.
|
|
mainnet_overrides_token_lookup: InstrumentLookup = SPLTokenLookup.load(
|
|
SPLTokenLookup.OverridesDataFilepath
|
|
)
|
|
mainnet_spl_token_lookup: InstrumentLookup = SPLTokenLookup.load(
|
|
SPLTokenLookup.DefaultDataFilepath
|
|
)
|
|
mainnet_non_spl_instrument_lookup: InstrumentLookup = (
|
|
NonSPLInstrumentLookup.load(
|
|
NonSPLInstrumentLookup.DefaultMainnetDataFilepath
|
|
)
|
|
)
|
|
instrument_lookup = CompoundInstrumentLookup(
|
|
[
|
|
ids_json_token_lookup,
|
|
mainnet_overrides_token_lookup,
|
|
mainnet_non_spl_instrument_lookup,
|
|
mainnet_spl_token_lookup,
|
|
]
|
|
)
|
|
elif actual_cluster == "devnet":
|
|
devnet_overrides_token_lookup: InstrumentLookup = SPLTokenLookup.load(
|
|
SPLTokenLookup.DevnetOverridesDataFilepath
|
|
)
|
|
devnet_spl_token_lookup: InstrumentLookup = SPLTokenLookup.load(
|
|
SPLTokenLookup.DevnetDataFilepath
|
|
)
|
|
devnet_non_spl_instrument_lookup: InstrumentLookup = (
|
|
NonSPLInstrumentLookup.load(
|
|
NonSPLInstrumentLookup.DefaultDevnetDataFilepath
|
|
)
|
|
)
|
|
instrument_lookup = CompoundInstrumentLookup(
|
|
[
|
|
ids_json_token_lookup,
|
|
devnet_overrides_token_lookup,
|
|
devnet_non_spl_instrument_lookup,
|
|
devnet_spl_token_lookup,
|
|
]
|
|
)
|
|
|
|
ids_json_market_lookup: MarketLookup = IdsJsonMarketLookup(
|
|
actual_cluster, instrument_lookup
|
|
)
|
|
all_market_lookup = ids_json_market_lookup
|
|
if actual_cluster == "mainnet":
|
|
mainnet_overrides_serum_market_lookup: SerumMarketLookup = (
|
|
SerumMarketLookup.load(
|
|
actual_serum_program_address, SPLTokenLookup.OverridesDataFilepath
|
|
)
|
|
)
|
|
mainnet_serum_market_lookup: SerumMarketLookup = SerumMarketLookup.load(
|
|
actual_serum_program_address, SPLTokenLookup.DefaultDataFilepath
|
|
)
|
|
all_market_lookup = CompoundMarketLookup(
|
|
[
|
|
ids_json_market_lookup,
|
|
mainnet_overrides_serum_market_lookup,
|
|
mainnet_serum_market_lookup,
|
|
]
|
|
)
|
|
elif actual_cluster == "devnet":
|
|
devnet_overrides_serum_market_lookup: SerumMarketLookup = (
|
|
SerumMarketLookup.load(
|
|
actual_serum_program_address,
|
|
SPLTokenLookup.DevnetOverridesDataFilepath,
|
|
)
|
|
)
|
|
devnet_serum_market_lookup: SerumMarketLookup = SerumMarketLookup.load(
|
|
actual_serum_program_address, SPLTokenLookup.DevnetDataFilepath
|
|
)
|
|
all_market_lookup = CompoundMarketLookup(
|
|
[
|
|
ids_json_market_lookup,
|
|
devnet_overrides_serum_market_lookup,
|
|
devnet_serum_market_lookup,
|
|
]
|
|
)
|
|
market_lookup: MarketLookup = all_market_lookup
|
|
|
|
actual_monitor_transactions_commitment: Commitment = (
|
|
monitor_transactions_commitment or Finalized
|
|
)
|
|
actual_slot_holder: AbstractSlotHolder = slot_holder or NullSlotHolder()
|
|
actual_monitor_transactions_timeout = monitor_transactions_timeout or 90
|
|
actual_transaction_monitor: TransactionMonitor = NullTransactionMonitor(
|
|
slot_holder=actual_slot_holder
|
|
)
|
|
if monitor_transactions:
|
|
actual_transaction_monitor = WebSocketTransactionMonitor(
|
|
actual_cluster_urls[0].ws,
|
|
commitment=actual_monitor_transactions_commitment,
|
|
transaction_timeout=actual_monitor_transactions_timeout,
|
|
collector=DequeTransactionStatusCollector(),
|
|
slot_holder=actual_slot_holder,
|
|
)
|
|
|
|
context = Context(
|
|
actual_name,
|
|
actual_cluster,
|
|
actual_cluster_urls,
|
|
actual_skip_preflight,
|
|
actual_tpu_retransmissions,
|
|
actual_commitment,
|
|
actual_encoding,
|
|
actual_blockhash_cache_duration,
|
|
actual_http_request_timeout,
|
|
actual_stale_data_pauses_before_retry,
|
|
actual_program_address,
|
|
actual_serum_program_address,
|
|
actual_group_name,
|
|
actual_group_address,
|
|
actual_gma_chunk_size,
|
|
actual_gma_chunk_pause,
|
|
actual_reflink,
|
|
instrument_lookup,
|
|
market_lookup,
|
|
actual_transaction_monitor,
|
|
)
|
|
|
|
return context
|