Added reflink support in placing perp orders and with new commands register-referrer-id and set-referrer.

This commit is contained in:
Geoff Taylor 2022-02-09 18:31:29 +00:00
parent 34199f0091
commit 9fdccca3a3
14 changed files with 329 additions and 35 deletions

36
bin/register-referrer-id Executable file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
import argparse
import os
import os.path
import sys
from solana.publickey import PublicKey
sys.path.insert(0, os.path.abspath(
os.path.join(os.path.dirname(__file__), "..")))
import mango # nopep8
parser = argparse.ArgumentParser(description="Register a referrer ID for a Mango Account.")
mango.ContextBuilder.add_command_line_parameters(parser)
mango.Wallet.add_command_line_parameters(parser)
parser.add_argument("--account-address", type=PublicKey,
help="address of the specific account to use, if more than one available")
parser.add_argument("--id", type=str, required=True,
help="referrer ID to register - must be no longer than 32 characters")
args: argparse.Namespace = mango.parse_args(parser)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_address(context, wallet.address, group, args.account_address)
all_instructions: mango.CombinableInstructions = mango.CombinableInstructions.from_signers([wallet.keypair])
referrer_record_address: PublicKey = group.derive_referrer_record_address(context, args.id)
set_delegate_instructions = mango.build_register_referrer_id_instructions(
context, wallet, group, account, referrer_record_address, args.id)
all_instructions += set_delegate_instructions
transaction_ids = all_instructions.execute(context)
mango.output(f"Transaction IDs: {transaction_ids}")

37
bin/set-referrer Executable file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
import argparse
import os
import os.path
import sys
from solana.publickey import PublicKey
sys.path.insert(0, os.path.abspath(
os.path.join(os.path.dirname(__file__), "..")))
import mango # nopep8
parser = argparse.ArgumentParser(description="Sets the referrer for a Mango Account.")
mango.ContextBuilder.add_command_line_parameters(parser)
mango.Wallet.add_command_line_parameters(parser)
parser.add_argument("--account-address", type=PublicKey,
help="address of the specific account to use, if more than one available")
parser.add_argument("--referrer-address", type=PublicKey, required=True,
help="address of the referrer's Mango Account")
args: argparse.Namespace = mango.parse_args(parser)
context = mango.ContextBuilder.from_command_line_parameters(args)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_address)
account = mango.Account.load_for_owner_by_address(context, wallet.address, group, args.account_address)
referrer_account = mango.Account.load(context, args.referrer_address, group)
all_instructions: mango.CombinableInstructions = mango.CombinableInstructions.from_signers([wallet.keypair])
referrer_memory_address: PublicKey = account.derive_referrer_memory_address(context)
set_delegate_instructions = mango.build_set_referrer_memory_instructions(
context, wallet, group, account, referrer_memory_address, referrer_account.address)
all_instructions += set_delegate_instructions
transaction_ids = all_instructions.execute(context)
mango.output(f"Transaction IDs: {transaction_ids}")

View File

@ -87,10 +87,12 @@ from .instructions import build_faucet_airdrop_instructions as build_faucet_aird
from .instructions import build_mango_consume_events_instructions as build_mango_consume_events_instructions
from .instructions import build_place_perp_order_instructions as build_place_perp_order_instructions
from .instructions import build_redeem_accrued_mango_instructions as build_redeem_accrued_mango_instructions
from .instructions import build_register_referrer_id_instructions as build_register_referrer_id_instructions
from .instructions import build_serum_consume_events_instructions as build_serum_consume_events_instructions
from .instructions import build_serum_place_order_instructions as build_serum_place_order_instructions
from .instructions import build_serum_settle_instructions as build_serum_settle_instructions
from .instructions import build_set_account_delegate_instructions as build_set_account_delegate_instructions
from .instructions import build_set_referrer_memory_instructions as build_set_referrer_memory_instructions
from .instructions import build_spot_place_order_instructions as build_spot_place_order_instructions
from .instructions import build_transfer_spl_tokens_instructions as build_transfer_spl_tokens_instructions
from .instructions import build_unset_account_delegate_instructions as build_unset_account_delegate_instructions

View File

@ -430,6 +430,17 @@ class Account(AddressableAccount):
raise Exception(f"Could not find AccountBasketItem in Account {self.address} at index {spot_market_index}.")
item_to_update.spot_open_orders = spot_open_orders
def derive_referrer_memory_address(self, context: Context) -> PublicKey:
referrer_memory_address_and_nonce: typing.Tuple[PublicKey, int] = PublicKey.find_program_address(
[
bytes(self.address),
b"ReferrerMemory"
],
context.mango_program_address
)
return referrer_memory_address_and_nonce[0]
def to_dataframe(self, group: Group, all_spot_open_orders: typing.Dict[str, OpenOrders], cache: Cache) -> pandas.DataFrame:
asset_data = []
for slot in self.slots:

View File

@ -41,8 +41,9 @@ class Context:
commitment: str, encoding: str, blockhash_cache_duration: int, http_request_timeout: float,
stale_data_pauses_before_retry: typing.Sequence[float], mango_program_address: PublicKey,
serum_program_address: PublicKey, group_name: str, group_address: PublicKey,
gma_chunk_size: Decimal, gma_chunk_pause: Decimal, instrument_lookup: InstrumentLookup,
market_lookup: MarketLookup, transaction_status_collector: TransactionStatusCollector = NullTransactionStatusCollector()) -> None:
gma_chunk_size: Decimal, gma_chunk_pause: Decimal, reflink: typing.Optional[PublicKey],
instrument_lookup: InstrumentLookup, market_lookup: MarketLookup,
transaction_status_collector: TransactionStatusCollector = NullTransactionStatusCollector()) -> None:
self._logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.name: str = name
instruction_reporter: InstructionReporter = CompoundInstructionReporter.from_addresses(
@ -55,6 +56,7 @@ class Context:
self.group_address: PublicKey = group_address
self.gma_chunk_size: Decimal = gma_chunk_size
self.gma_chunk_pause: Decimal = gma_chunk_pause
self.reflink: typing.Optional[PublicKey] = reflink
self.instrument_lookup: InstrumentLookup = instrument_lookup
self.market_lookup: MarketLookup = market_lookup

View File

@ -46,6 +46,7 @@ from .serummarketlookup import SerumMarketLookup
# * GROUP_ADDRESS
# * MANGO_PROGRAM_ADDRESS
# * SERUM_PROGRAM_ADDRESS
# * MANGO_REFLINK_ADDRESS
# # 🥭 ContextBuilder class
@ -105,6 +106,7 @@ class ContextBuilder:
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
@ -130,6 +132,7 @@ class ContextBuilder:
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
# 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().)
@ -146,7 +149,8 @@ class ContextBuilder:
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)
serum_program_address, gma_chunk_size, gma_chunk_pause,
reflink)
logging.debug(f"{context}")
return context
@ -162,7 +166,8 @@ class ContextBuilder:
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.gma_chunk_size, context.gma_chunk_pause,
context.reflink)
@staticmethod
def forced_to_devnet(context: Context) -> Context:
@ -213,6 +218,7 @@ class ContextBuilder:
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,
transaction_status_collector: TransactionStatusCollector = NullTransactionStatusCollector()) -> "Context":
def __public_key_or_none(address: typing.Optional[str]) -> typing.Optional[PublicKey]:
if address is not None and address != "":
@ -265,6 +271,9 @@ class ContextBuilder:
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":
@ -327,4 +336,4 @@ class ContextBuilder:
devnet_serum_market_lookup])
market_lookup: MarketLookup = all_market_lookup
return Context(actual_name, actual_cluster, actual_cluster_urls, actual_skip_preflight, 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, instrument_lookup, market_lookup, transaction_status_collector)
return Context(actual_name, actual_cluster, actual_cluster_urls, actual_skip_preflight, 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, transaction_status_collector)

View File

@ -182,7 +182,9 @@ class Group(AddressableAccount):
signer_nonce: Decimal, signer_key: PublicKey,
admin: PublicKey, serum_program_address: PublicKey, cache: PublicKey, valid_interval: Decimal,
insurance_vault: PublicKey, srm_vault: PublicKey, msrm_vault: PublicKey, fees_vault: PublicKey,
max_mango_accounts: Decimal, num_mango_accounts: Decimal) -> None:
max_mango_accounts: Decimal, num_mango_accounts: Decimal,
referral_surcharge_centibps: Decimal, referral_share_centibps: Decimal,
referral_mngo_required: Decimal) -> None:
super().__init__(account_info)
self.version: Version = version
self.name: str = name
@ -203,6 +205,9 @@ class Group(AddressableAccount):
self.fees_vault: PublicKey = fees_vault
self.max_mango_accounts: Decimal = max_mango_accounts
self.num_mango_accounts: Decimal = num_mango_accounts
self.referral_surcharge_centibps: Decimal = referral_surcharge_centibps
self.referral_share_centibps: Decimal = referral_share_centibps
self.referral_mngo_required: Decimal = referral_mngo_required
@property
def shared_quote_token(self) -> Token:
@ -334,7 +339,11 @@ class Group(AddressableAccount):
max_mango_accounts: Decimal = layout.max_mango_accounts
num_mango_accounts: Decimal = layout.num_mango_accounts
return Group(account_info, version, name, meta_data, quote_token_bank, in_slots, slots, signer_nonce, signer_key, admin, serum_program_address, cache_address, valid_interval, insurance_vault, srm_vault, msrm_vault, fees_vault, max_mango_accounts, num_mango_accounts)
referral_surcharge_centibps: Decimal = layout.referral_surcharge_centibps
referral_share_centibps: Decimal = layout.referral_share_centibps
referral_mngo_required: Decimal = layout.referral_mngo_required
return Group(account_info, version, name, meta_data, quote_token_bank, in_slots, slots, signer_nonce, signer_key, admin, serum_program_address, cache_address, valid_interval, insurance_vault, srm_vault, msrm_vault, fees_vault, max_mango_accounts, num_mango_accounts, referral_surcharge_centibps, referral_share_centibps, referral_mngo_required)
@staticmethod
def parse(account_info: AccountInfo, name: str, instrument_lookup: InstrumentLookup, market_lookup: MarketLookup) -> "Group":
@ -424,6 +433,27 @@ class Group(AddressableAccount):
def fetch_cache(self, context: Context) -> Cache:
return Cache.load(context, self.cache)
def derive_referrer_record_address(self, context: Context, id: str) -> PublicKey:
if not isinstance(id, str):
raise Exception(f"Referrer ID '{id}' is not a string")
id_bytes = id.encode('utf-8')
if len(id_bytes) > 32:
raise Exception(f"Referrer ID '{id}' is too long - maximum is 32 bytes")
id_bytes_padded = id_bytes.ljust(32, b"\0")
referrer_record_address_and_nonce: typing.Tuple[PublicKey, int] = PublicKey.find_program_address(
[
bytes(self.address),
b"ReferrerIdRecord",
id_bytes_padded
],
context.mango_program_address
)
return referrer_record_address_and_nonce[0]
def __str__(self) -> str:
slot_count = len(self.slots)
slots = "\n ".join([f"{item}".replace("\n", "\n ") for item in self.slots])
@ -440,6 +470,10 @@ class Group(AddressableAccount):
Fees Vault: {self.fees_vault}
Num Accounts: {self.num_mango_accounts:,} out of {self.max_mango_accounts:,}
Valid Interval: {self.valid_interval}
Referral:
Surcharge: {self.referral_surcharge_centibps}
Share: {self.referral_share_centibps}
MNGO Required: {self.referral_mngo_required}
Basket [{slot_count} markets]:
{slots}
»"""

View File

@ -345,7 +345,7 @@ def build_cancel_perp_order_instructions(context: Context, wallet: Wallet, accou
return CombinableInstructions(signers=[], instructions=instructions)
def build_place_perp_order_instructions(context: Context, wallet: Wallet, group: Group, account: Account, perp_market_details: PerpMarketDetails, price: Decimal, quantity: Decimal, client_order_id: int, side: Side, order_type: OrderType, reduce_only: bool = False) -> CombinableInstructions:
def build_place_perp_order_instructions(context: Context, wallet: Wallet, group: Group, account: Account, perp_market_details: PerpMarketDetails, price: Decimal, quantity: Decimal, client_order_id: int, side: Side, order_type: OrderType, reduce_only: bool = False, reflink: typing.Optional[PublicKey] = None) -> CombinableInstructions:
# { buy: 0, sell: 1 }
raw_side: int = 1 if side == Side.SELL else 0
raw_order_type: int = order_type.to_perp()
@ -369,20 +369,24 @@ def build_place_perp_order_instructions(context: Context, wallet: Wallet, group:
# /// 5. `[writable]` bids_ai - TODO
# /// 6. `[writable]` asks_ai - TODO
# /// 7. `[writable]` event_queue_ai - TODO
keys = [
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
AccountMeta(is_signer=False, is_writable=True, pubkey=account.address),
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=group.cache),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.address),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.bids),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.asks),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.event_queue),
*list([AccountMeta(is_signer=False, is_writable=False,
pubkey=oo_address or SYSTEM_PROGRAM_ADDRESS) for oo_address in account.spot_open_orders_by_index[:-1]])
]
if reflink is not None:
keys += [AccountMeta(is_signer=False, is_writable=True, pubkey=reflink)]
instructions = [
TransactionInstruction(
keys=[
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
AccountMeta(is_signer=False, is_writable=True, pubkey=account.address),
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=group.cache),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.address),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.bids),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.asks),
AccountMeta(is_signer=False, is_writable=True, pubkey=perp_market_details.event_queue),
*list([AccountMeta(is_signer=False, is_writable=False,
pubkey=oo_address or SYSTEM_PROGRAM_ADDRESS) for oo_address in account.spot_open_orders_by_index[:-1]])
],
keys=keys,
program_id=context.mango_program_address,
data=layouts.PLACE_PERP_ORDER.build(
{
@ -923,3 +927,70 @@ def build_set_account_delegate_instructions(context: Context, wallet: Wallet, gr
def build_unset_account_delegate_instructions(context: Context, wallet: Wallet, group: Group, account: Account) -> CombinableInstructions:
return build_set_account_delegate_instructions(context, wallet, group, account, SYSTEM_PROGRAM_ADDRESS)
# # 🥭 build_set_referrer_memory_instructions function
#
# Creates an instruction to store the referrer's MangoAccount pubkey on the Referrer account
# and create the Referrer account as a PDA of user's MangoAccount if it doesn't exist
#
def build_set_referrer_memory_instructions(context: Context, wallet: Wallet, group: Group, account: Account, referrer_memory_address: PublicKey, referrer_account_address: PublicKey) -> CombinableInstructions:
# /// Store the referrer's MangoAccount pubkey on the Referrer account
# /// It will create the Referrer account as a PDA of user's MangoAccount if it doesn't exist
# /// This is primarily useful for the UI; the referrer address stored here is not necessarily
# /// who earns the ref fees.
# ///
# /// Accounts expected by this instruction (7):
# ///
# /// 0. `[]` mango_group_ai - MangoGroup that this mango account is for
# /// 1. `[]` mango_account_ai - MangoAccount of the referred
# /// 2. `[signer]` owner_ai - MangoAccount owner or delegate
# /// 3. `[writable]` referrer_memory_ai - ReferrerMemory struct; will be initialized if required
# /// 4. `[]` referrer_mango_account_ai - referrer's MangoAccount
# /// 5. `[signer, writable]` payer_ai - payer for PDA; can be same as owner
# /// 6. `[]` system_prog_ai - System program
set_referrer_memory_instruction = TransactionInstruction(
keys=[
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=account.address),
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=True, pubkey=referrer_memory_address),
AccountMeta(is_signer=False, is_writable=True, pubkey=referrer_account_address),
AccountMeta(is_signer=True, is_writable=True, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=SYSTEM_PROGRAM_ADDRESS)
],
program_id=context.mango_program_address,
data=layouts.SET_REFERRER_MEMORY.build({})
)
return CombinableInstructions(signers=[], instructions=[set_referrer_memory_instruction])
# # 🥭 build_register_referrer_id_instructions function
#
# Creates an instruction to register a 'referrer ID' for a Mango Account
#
def build_register_referrer_id_instructions(context: Context, wallet: Wallet, group: Group, account: Account, referrer_record_address: PublicKey, referrer_id: str) -> CombinableInstructions:
# /// Associate the referrer's MangoAccount with a human readable `referrer_id` which can be used
# /// in a ref link. This is primarily useful for the UI.
# /// Create the `ReferrerIdRecord` PDA; if it already exists throw error
# ///
# /// Accounts expected by this instruction (5):
# /// 0. `[]` mango_group_ai - MangoGroup
# /// 1. `[]` referrer_mango_account_ai - MangoAccount
# /// 2. `[writable]` referrer_id_record_ai - The PDA to store the record on
# /// 3. `[signer, writable]` payer_ai - payer for PDA; can be same as owner
# /// 4. `[]` system_prog_ai - System program
register_referrer_id_instruction = TransactionInstruction(
keys=[
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=account.address),
AccountMeta(is_signer=False, is_writable=True, pubkey=referrer_record_address),
AccountMeta(is_signer=True, is_writable=True, pubkey=wallet.address),
AccountMeta(is_signer=False, is_writable=False, pubkey=SYSTEM_PROGRAM_ADDRESS)
],
program_id=context.mango_program_address,
data=layouts.REGISTER_REFERRER_ID.build({
"info": referrer_id
})
)
return CombinableInstructions(signers=[], instructions=[register_referrer_id_instruction])

View File

@ -85,6 +85,9 @@ class InstructionType(enum.IntEnum):
SetDelegate = 58
ChangeSpotMarketParams = 59
CreateSpotOpenOrders = 60
ChangeReferralFeeParams = 61
SetReferrerMemory = 62
RegisterReferrerId = 63
def __str__(self) -> str:
return self.name

View File

@ -541,7 +541,10 @@ GROUP = construct.Struct(
"fees_vault" / PublicKeyAdapter(),
"max_mango_accounts" / DecimalAdapter(4),
"num_mango_accounts" / DecimalAdapter(4),
construct.Padding(24)
"referral_surcharge_centibps" / DecimalAdapter(4),
"referral_share_centibps" / DecimalAdapter(4),
"referral_mngo_required" / DecimalAdapter(),
construct.Padding(8)
)
# # 🥭 ROOT_BANK
@ -1594,6 +1597,40 @@ CREATE_SPOT_OPEN_ORDERS = construct.Struct(
"variant" / construct.Const(60, construct.BytesInteger(4, swapped=True))
)
# /// Store the referrer's MangoAccount pubkey on the Referrer account
# /// It will create the Referrer account as a PDA of user's MangoAccount if it doesn't exist
# /// This is primarily useful for the UI; the referrer address stored here is not necessarily
# /// who earns the ref fees.
# ///
# /// Accounts expected by this instruction (7):
# ///
# /// 0. `[]` mango_group_ai - MangoGroup that this mango account is for
# /// 1. `[]` mango_account_ai - MangoAccount of the referred
# /// 2. `[signer]` owner_ai - MangoAccount owner or delegate
# /// 3. `[writable]` referrer_memory_ai - ReferrerMemory struct; will be initialized if required
# /// 4. `[]` referrer_mango_account_ai - referrer's MangoAccount
# /// 5. `[signer, writable]` payer_ai - payer for PDA; can be same as owner
# /// 6. `[]` system_prog_ai - System program
SET_REFERRER_MEMORY = construct.Struct(
"variant" / construct.Const(62, construct.BytesInteger(4, swapped=True)),
)
# /// Associate the referrer's MangoAccount with a human readable `referrer_id` which can be used
# /// in a ref link. This is primarily useful for the UI.
# /// Create the `ReferrerIdRecord` PDA; if it already exists throw error
# ///
# /// Accounts expected by this instruction (5):
# /// 0. `[]` mango_group_ai - MangoGroup
# /// 1. `[]` referrer_mango_account_ai - MangoAccount
# /// 2. `[writable]` referrer_id_record_ai - The PDA to store the record on
# /// 3. `[signer, writable]` payer_ai - payer for PDA; can be same as owner
# /// 4. `[]` system_prog_ai - System program
REGISTER_REFERRER_ID = construct.Struct(
"variant" / construct.Const(63, construct.BytesInteger(4, swapped=True)),
"info" / construct.PaddedString(32, "utf8"),
)
UNSPECIFIED = construct.Struct(
"variant" / DecimalAdapter(4)
@ -1662,4 +1699,7 @@ InstructionParsersByVariant = {
58: SET_DELEGATE, # SET_DELEGATE,
59: UNSPECIFIED, # CHANGE_SPOT_MARKET_PARAMS,
60: CREATE_SPOT_OPEN_ORDERS, # CREATE_SPOT_OPEN_ORDERS,
61: UNSPECIFIED, # CHANGE_REFERRAL_FEE_PARAMS,
62: SET_REFERRER_MEMORY, # SET_REFERRER_MEMORY,
63: REGISTER_REFERRER_ID, # REGISTER_REFERRER_ID,
}

View File

@ -66,7 +66,7 @@ class PerpMarketInstructionBuilder(MarketInstructionBuilder):
if self.perp_market.underlying_perp_market is None:
raise Exception(f"PerpMarket {self.perp_market.symbol} has not been loaded.")
return build_place_perp_order_instructions(
self.context, self.wallet, self.perp_market.underlying_perp_market.group, self.account, self.perp_market.underlying_perp_market, order.price, order.quantity, order.client_id, order.side, order.order_type, order.reduce_only)
self.context, self.wallet, self.perp_market.underlying_perp_market.group, self.account, self.perp_market.underlying_perp_market, order.price, order.quantity, order.client_id, order.side, order.order_type, order.reduce_only, self.context.reflink)
def build_settle_instructions(self) -> CombinableInstructions:
return CombinableInstructions.empty()

View File

@ -29,7 +29,8 @@ class MockCompatibleClient(Client):
class MockClient(mango.BetterClient):
def __init__(self) -> None:
rpc = mango.RPCCaller("fake", "http://localhost", "ws://localhost", -1, [], mango.SlotHolder(), mango.InstructionReporter())
rpc = mango.RPCCaller("fake", "http://localhost", "ws://localhost", -1,
[], mango.SlotHolder(), mango.InstructionReporter())
compound = mango.CompoundRPCCaller("fake", [rpc])
super().__init__(MockCompatibleClient(), "test", "local", Commitment("processed"),
False, "base64", 0, compound)
@ -43,7 +44,7 @@ def fake_seeded_public_key(seed: str) -> PublicKey:
return PublicKey.create_with_seed(PublicKey("11111111111111111111111111111112"), seed, PublicKey("11111111111111111111111111111111"))
def fake_context() -> mango.Context:
def fake_context(mango_program_address: typing.Optional[PublicKey] = None) -> mango.Context:
context = mango.Context(name="Mango Test",
cluster_name="test",
cluster_urls=[
@ -56,19 +57,23 @@ def fake_context() -> mango.Context:
blockhash_cache_duration=0,
http_request_timeout=-1,
stale_data_pauses_before_retry=[],
mango_program_address=fake_seeded_public_key("Mango program address"),
mango_program_address=mango_program_address or fake_seeded_public_key(
"Mango program address"),
serum_program_address=fake_seeded_public_key("Serum program address"),
group_name="TEST_GROUP",
group_address=fake_seeded_public_key("group ID"),
gma_chunk_size=Decimal(20),
gma_chunk_pause=Decimal(25),
reflink=None,
instrument_lookup=mango.IdsJsonTokenLookup("devnet", "devnet.2"),
market_lookup=mango.NullMarketLookup())
context.client = MockClient()
return context
def fake_account_info(address: PublicKey = fake_public_key(), executable: bool = False, lamports: Decimal = Decimal(0), owner: PublicKey = fake_public_key(), rent_epoch: Decimal = Decimal(0), data: bytes = bytes([0])) -> mango.AccountInfo:
def fake_account_info(address: typing.Optional[PublicKey] = None, executable: bool = False, lamports: Decimal = Decimal(0), owner: PublicKey = fake_public_key(), rent_epoch: Decimal = Decimal(0), data: bytes = bytes([0])) -> mango.AccountInfo:
if address is None:
address = fake_public_key()
return mango.AccountInfo(address, executable, lamports, owner, rent_epoch, data)
@ -174,10 +179,10 @@ def fake_account_slot() -> mango.AccountSlot:
fake_seeded_public_key("open_orders"), None)
def fake_account() -> mango.Account:
def fake_account(address: typing.Optional[PublicKey] = None) -> mango.Account:
meta_data = mango.Metadata(mango.layouts.DATA_TYPE.Account, mango.Version.V1, True)
quote = fake_account_slot()
return mango.Account(fake_account_info(), mango.Version.V1, meta_data, "GROUPNAME",
return mango.Account(fake_account_info(address=address), mango.Version.V1, meta_data, "GROUPNAME",
fake_seeded_public_key("group"), fake_seeded_public_key("owner"), "INFO",
quote, [], [], [], Decimal(1), False, False, fake_seeded_public_key("advanced_orders"),
False, fake_seeded_public_key("delegate"))
@ -198,8 +203,8 @@ def fake_root_bank_cache() -> mango.RootBankCache:
return mango.RootBankCache(Decimal(1), Decimal(2), datetime.datetime.now())
def fake_group() -> mango.Group:
account_info = fake_account_info()
def fake_group(address: typing.Optional[PublicKey] = None) -> mango.Group:
account_info = fake_account_info(address=address)
name = "FAKE_GROUP"
meta_data = mango.Metadata(mango.layouts.DATA_TYPE.Group, mango.Version.V1, True)
instrument_lookup = fake_context().instrument_lookup
@ -217,11 +222,15 @@ def fake_group() -> mango.Group:
fees_vault = fake_seeded_public_key("fees vault")
max_mango_accounts = Decimal(1000000)
num_mango_accounts = Decimal(1)
referral_surcharge_centibps = Decimal(7)
referral_share_centibps = Decimal(8)
referral_mngo_required = Decimal(9)
return mango.Group(account_info, mango.Version.V1, name, meta_data, quote_info, [], [],
signer_nonce, signer_key, admin_key, serum_program_address, cache_key,
valid_interval, insurance_vault, srm_vault, msrm_vault, fees_vault,
max_mango_accounts, num_mango_accounts)
max_mango_accounts, num_mango_accounts, referral_surcharge_centibps,
referral_share_centibps, referral_mngo_required)
def fake_prices(prices: typing.Sequence[str]) -> typing.Sequence[mango.InstrumentValue]:

View File

@ -2,10 +2,11 @@ import pytest
from .context import mango
from .data import load_data_from_directory
from .fakes import fake_account_info, fake_seeded_public_key, fake_token_bank, fake_instrument, fake_instrument_value, fake_perp_account, fake_token
from .fakes import fake_account, fake_account_info, fake_context, fake_seeded_public_key, fake_token_bank, fake_instrument, fake_instrument_value, fake_perp_account, fake_token
from decimal import Decimal
from mango.layouts import layouts
from solana.publickey import PublicKey
def test_construction() -> None:
@ -285,3 +286,14 @@ def test_loaded_account_slot_lookups() -> None:
assert account.slots_by_index[14] is None
assert account.slots_by_index[15] is not None
assert account.slots_by_index[15].base_instrument.symbol == "USDC"
def test_derive_referrer_memory_address() -> None:
context = fake_context(mango_program_address=PublicKey("4skJ85cdxQAFVKbcGgfun8iZPL7BadVYXG3kGEGkufqA"))
account = fake_account(address=PublicKey("FG99s25HS1UKcP1jMx72Gezg6KZCC7DuKXhNW51XC1qi"))
actual = account.derive_referrer_memory_address(context)
# Value derived using mango-client-v3: 3CMpC1UzdLrAnGz6HZVoBsDLAHpTABkUJr8iPyEHwehr
expected = PublicKey("3CMpC1UzdLrAnGz6HZVoBsDLAHpTABkUJr8iPyEHwehr")
assert actual == expected

View File

@ -3,10 +3,11 @@ import typing
from .context import mango
from .data import load_group
from .fakes import fake_account_info, fake_seeded_public_key, fake_token_bank, fake_instrument
from .fakes import fake_account_info, fake_context, fake_group, fake_seeded_public_key, fake_token_bank, fake_instrument
from decimal import Decimal
from mango.layouts import layouts
from solana.publickey import PublicKey
def test_construction() -> None:
@ -28,11 +29,15 @@ def test_construction() -> None:
fees_vault = fake_seeded_public_key("fees vault")
max_mango_accounts = Decimal(50)
num_mango_accounts = Decimal(49)
referral_surcharge_centibps = Decimal(7)
referral_share_centibps = Decimal(8)
referral_mngo_required = Decimal(9)
actual = mango.Group(account_info, mango.Version.V1, name, meta_data, shared_quote_token, in_basket,
slots, signer_nonce, signer_key, admin_key, serum_program_address,
cache_key, valid_interval, insurance_vault, srm_vault, msrm_vault, fees_vault,
max_mango_accounts, num_mango_accounts)
max_mango_accounts, num_mango_accounts, referral_surcharge_centibps,
referral_share_centibps, referral_mngo_required)
assert actual is not None
assert actual.name == name
@ -72,6 +77,9 @@ def test_slot_lookups() -> None:
fees_vault = fake_seeded_public_key("fees vault")
max_mango_accounts = Decimal(30)
num_mango_accounts = Decimal(4)
referral_surcharge_centibps = Decimal(7)
referral_share_centibps = Decimal(8)
referral_mngo_required = Decimal(9)
# This is the more relevant stuff here.
shared_quote_token_bank = fake_token_bank("FAKEQUOTE")
@ -99,7 +107,8 @@ def test_slot_lookups() -> None:
actual = mango.Group(account_info, mango.Version.V1, name, meta_data, shared_quote_token_bank, in_basket,
slots, signer_nonce, signer_key, admin_key, serum_program_address,
cache_key, valid_interval, insurance_vault, srm_vault, msrm_vault, fees_vault,
max_mango_accounts, num_mango_accounts)
max_mango_accounts, num_mango_accounts, referral_surcharge_centibps,
referral_share_centibps, referral_mngo_required)
assert actual.shared_quote == shared_quote_token_bank
assert actual.liquidity_incentive_token_bank == mngo_token_bank
@ -263,3 +272,22 @@ def test_loaded_group_slot_lookups() -> None:
assert group.slots_by_index[12].base_instrument.symbol == "MATIC"
assert group.slots_by_index[13] is None
assert group.slots_by_index[14] is None
def test_derive_referrer_record_address() -> None:
context = fake_context(mango_program_address=PublicKey("4skJ85cdxQAFVKbcGgfun8iZPL7BadVYXG3kGEGkufqA"))
group = fake_group(address=PublicKey("Ec2enZyoC4nGpEfu2sUNAa2nUGJHWxoUWYSEJ2hNTWTA"))
actual = group.derive_referrer_record_address(context, "Test")
# Value derived using mango-client-v3: 2rZyTeG2K45oLWiGHBZKdcsWig5PL5c3yUa9Fc35mY48
expected = PublicKey("7bPLkq9kmFACvpEps1sUjqY1a6ormFxa9LctD1RWnzDd")
assert actual == expected
# 'daffy' is the referrer ID used in example/registerRefId.ts in mango-client-v3
actual_daffy = group.derive_referrer_record_address(context, "daffy")
# Value derived using mango-client-v3: 6T3vGwbLcS87vuthXomRv5W1TYe82rttNC8kWoDC93JD
expected_daffy = PublicKey("6T3vGwbLcS87vuthXomRv5W1TYe82rttNC8kWoDC93JD")
assert actual_daffy == expected_daffy