Added reflink support in placing perp orders and with new commands register-referrer-id and set-referrer.
This commit is contained in:
parent
34199f0091
commit
9fdccca3a3
|
@ -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}")
|
|
@ -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}")
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
»"""
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue