1715 lines
65 KiB
Python
1715 lines
65 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 pyserum.enums
|
|
import typing
|
|
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from pyserum._layouts.instructions import (
|
|
INSTRUCTIONS_LAYOUT as PYSERUM_INSTRUCTIONS_LAYOUT,
|
|
InstructionType as PySerumInstructionType,
|
|
)
|
|
from pyserum.enums import OrderType as PySerumOrderType, Side as PySerumSide
|
|
from pyserum.instructions import (
|
|
settle_funds as pyserum_settle_funds,
|
|
SettleFundsParams as PySerumSettleFundsParams,
|
|
)
|
|
from pyserum.market.market import Market as PySerumMarket
|
|
from pyserum.open_orders_account import (
|
|
make_create_account_instruction as pyserum_make_create_account_instruction,
|
|
)
|
|
from solana.keypair import Keypair
|
|
from solana.publickey import PublicKey
|
|
from solana.system_program import CreateAccountParams, create_account
|
|
from solana.transaction import AccountMeta, TransactionInstruction
|
|
from spl.token.constants import (
|
|
ACCOUNT_LEN,
|
|
TOKEN_PROGRAM_ID,
|
|
)
|
|
from spl.token.instructions import (
|
|
CloseAccountParams,
|
|
InitializeAccountParams,
|
|
TransferParams,
|
|
close_account,
|
|
create_associated_token_account,
|
|
initialize_account,
|
|
transfer,
|
|
)
|
|
|
|
from .combinableinstructions import CombinableInstructions
|
|
from .constants import I64_MAX, SYSTEM_PROGRAM_ADDRESS
|
|
from .context import Context
|
|
from .layouts import layouts
|
|
from .orders import Order, OrderType, Side
|
|
|
|
from .perpmarketdetails import PerpMarketDetails
|
|
|
|
# from .spotmarket import SpotMarket
|
|
from .tokens import Token
|
|
from .tokenaccount import TokenAccount
|
|
from .tokenbank import TokenBank, NodeBank, RootBank
|
|
from .wallet import Wallet
|
|
|
|
# 🥭 Interfaces
|
|
#
|
|
# To avoid circular dependencies, here we specify the bare interfaces we need for some
|
|
# of the objects. For instance, where a function takes an IAccount, you can (and probably
|
|
# should) safely pass in an IAccount.
|
|
#
|
|
|
|
|
|
class IAccount(typing.Protocol):
|
|
group_address: PublicKey
|
|
|
|
@property
|
|
def address(self) -> PublicKey:
|
|
raise NotImplementedError(
|
|
"IAccount.address is not implemented on the Protocol."
|
|
)
|
|
|
|
@property
|
|
def spot_open_orders_by_index(self) -> typing.Sequence[typing.Optional[PublicKey]]:
|
|
raise NotImplementedError(
|
|
"IAccount.spot_open_orders_by_index is not implemented on the Protocol."
|
|
)
|
|
|
|
@property
|
|
def spot_open_orders(self) -> typing.Sequence[PublicKey]:
|
|
raise NotImplementedError(
|
|
"IAccount.spot_open_orders is not implemented on the Protocol."
|
|
)
|
|
|
|
def update_spot_open_orders_for_market(
|
|
self, spot_market_index: int, spot_open_orders: PublicKey
|
|
) -> None:
|
|
raise NotImplementedError(
|
|
"IAccount.update_spot_open_orders_for_market() is not implemented on the Protocol."
|
|
)
|
|
|
|
|
|
class IGroupSlot(typing.Protocol):
|
|
index: int
|
|
|
|
|
|
class IGroup(typing.Protocol):
|
|
cache: PublicKey
|
|
signer_key: PublicKey
|
|
shared_quote: TokenBank
|
|
|
|
@property
|
|
def address(self) -> PublicKey:
|
|
raise NotImplementedError("IGroup.address is not implemented on the Protocol.")
|
|
|
|
@property
|
|
def tokens_by_index(self) -> typing.Sequence[typing.Optional[TokenBank]]:
|
|
raise NotImplementedError(
|
|
"IGroup.tokens_by_index is not implemented on the Protocol."
|
|
)
|
|
|
|
@property
|
|
def base_tokens_by_index(self) -> typing.Sequence[typing.Optional[TokenBank]]:
|
|
raise NotImplementedError(
|
|
"IGroup.base_tokens_by_index is not implemented on the Protocol."
|
|
)
|
|
|
|
def slot_by_spot_market_address(self, spot_market_address: PublicKey) -> IGroupSlot:
|
|
raise NotImplementedError(
|
|
"IGroup.slot_by_spot_market_address() is not implemented on the Protocol."
|
|
)
|
|
|
|
|
|
class IPerpMarket(typing.Protocol):
|
|
underlying_perp_market: PerpMarketDetails
|
|
|
|
@property
|
|
def address(self) -> PublicKey:
|
|
raise NotImplementedError(
|
|
"IPerpMarket.address is not implemented on the Protocol."
|
|
)
|
|
|
|
|
|
class ISpotMarket(typing.Protocol):
|
|
underlying_serum_market: PySerumMarket
|
|
|
|
@property
|
|
def address(self) -> PublicKey:
|
|
raise NotImplementedError(
|
|
"ISpotMarket.address is not implemented on the Protocol."
|
|
)
|
|
|
|
@property
|
|
def bids_address(self) -> PublicKey:
|
|
raise NotImplementedError(
|
|
"ISpotMarket.bids_address is not implemented on the Protocol."
|
|
)
|
|
|
|
@property
|
|
def asks_address(self) -> PublicKey:
|
|
raise NotImplementedError(
|
|
"ISpotMarket.asks_address is not implemented on the Protocol."
|
|
)
|
|
|
|
@property
|
|
def event_queue_address(self) -> PublicKey:
|
|
raise NotImplementedError(
|
|
"ISpotMarket.event_queue_address is not implemented on the Protocol."
|
|
)
|
|
|
|
|
|
# 🥭 Instructions
|
|
#
|
|
# This file contains the low-level instruction functions that build the raw instructions
|
|
# to send to Solana.
|
|
#
|
|
# One important distinction between these functions and the more common `create instruction functions` in
|
|
# Solana is that these functions *all return a combinable of instructions and signers*.
|
|
#
|
|
# It's likely that some operations will require actions split across multiple instructions because of
|
|
# instruction size limitiations, so all our functions are prepared for this without having to change
|
|
# the function signature in future.
|
|
#
|
|
|
|
# #
|
|
# # 🥭 SOLANA instruction builders
|
|
# #
|
|
|
|
# # 🥭 build_solana_create_account_instructions function
|
|
#
|
|
# Creates and initializes an SPL token account. Can add additional lamports too but that's usually not
|
|
# necesary.
|
|
#
|
|
def build_solana_create_account_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
mango_program_address: PublicKey,
|
|
size: int,
|
|
lamports: int = 0,
|
|
) -> CombinableInstructions:
|
|
minimum_balance = context.client.get_minimum_balance_for_rent_exemption(size)
|
|
account = Keypair()
|
|
create_instruction = create_account(
|
|
CreateAccountParams(
|
|
wallet.address,
|
|
account.public_key,
|
|
lamports + minimum_balance,
|
|
size,
|
|
mango_program_address,
|
|
)
|
|
)
|
|
return CombinableInstructions(signers=[account], instructions=[create_instruction])
|
|
|
|
|
|
# #
|
|
# # 🥭 SPL instruction builders
|
|
# #
|
|
|
|
|
|
# # 🥭 build_spl_create_account_instructions function
|
|
#
|
|
# Creates and initializes an SPL token account. Can add additional lamports too but that's usually not
|
|
# necesary.
|
|
#
|
|
# Prefer `build_spl_create_account_instructions()` over this function. This function should be
|
|
# reserved for cases where you specifically don't want the associated token account.
|
|
#
|
|
def build_spl_create_account_instructions(
|
|
context: Context, wallet: Wallet, token: Token, lamports: int = 0
|
|
) -> CombinableInstructions:
|
|
create_account_instructions = build_solana_create_account_instructions(
|
|
context, wallet, TOKEN_PROGRAM_ID, ACCOUNT_LEN, lamports
|
|
)
|
|
initialize_instruction = initialize_account(
|
|
InitializeAccountParams(
|
|
TOKEN_PROGRAM_ID,
|
|
create_account_instructions.signers[0].public_key,
|
|
token.mint,
|
|
wallet.address,
|
|
)
|
|
)
|
|
return create_account_instructions + CombinableInstructions(
|
|
signers=[], instructions=[initialize_instruction]
|
|
)
|
|
|
|
|
|
# # 🥭 build_spl_create_associated_account_instructions function
|
|
#
|
|
# Creates and initializes an 'associated' SPL token account. This is the usual way of creating a
|
|
# token account now. `build_spl_create_account_instructions()` should be reserved for cases where
|
|
# you specifically don't want the associated token account.
|
|
#
|
|
def build_spl_create_associated_account_instructions(
|
|
context: Context, wallet: Wallet, owner: PublicKey, token: Token
|
|
) -> CombinableInstructions:
|
|
create_account_instructions = create_associated_token_account(
|
|
wallet.address, owner, token.mint
|
|
)
|
|
return CombinableInstructions(
|
|
signers=[], instructions=[create_account_instructions]
|
|
)
|
|
|
|
|
|
# # 🥭 build_spl_transfer_tokens_instructions function
|
|
#
|
|
# Creates an instruction to transfer SPL tokens from one account to another.
|
|
#
|
|
def build_spl_transfer_tokens_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
token: Token,
|
|
source: PublicKey,
|
|
destination: PublicKey,
|
|
quantity: Decimal,
|
|
) -> CombinableInstructions:
|
|
amount = int(token.shift_to_native(quantity))
|
|
instructions = [
|
|
transfer(
|
|
TransferParams(
|
|
TOKEN_PROGRAM_ID, source, destination, wallet.address, amount, []
|
|
)
|
|
)
|
|
]
|
|
return CombinableInstructions(signers=[], instructions=instructions)
|
|
|
|
|
|
# # 🥭 build_spl_close_account_instructions function
|
|
#
|
|
# Creates an instructio to close an SPL token account and transfers any remaining lamports to the wallet.
|
|
#
|
|
def build_spl_close_account_instructions(
|
|
context: Context, wallet: Wallet, address: PublicKey
|
|
) -> CombinableInstructions:
|
|
return CombinableInstructions(
|
|
signers=[],
|
|
instructions=[
|
|
close_account(
|
|
CloseAccountParams(
|
|
TOKEN_PROGRAM_ID, address, wallet.address, wallet.address
|
|
)
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
# # 🥭 build_spl_faucet_airdrop_instructions function
|
|
#
|
|
# Creates an airdrop instruction for compatible faucets (those based on https://github.com/paul-schaaf/spl-token-faucet)
|
|
#
|
|
def build_spl_faucet_airdrop_instructions(
|
|
token_mint: PublicKey, destination: PublicKey, faucet: PublicKey, quantity: Decimal
|
|
) -> CombinableInstructions:
|
|
faucet_program_address: PublicKey = PublicKey(
|
|
"4bXpkKSV8swHSnwqtzuboGPaPDeEgAn4Vt8GfarV5rZt"
|
|
)
|
|
authority_and_nonce: typing.Tuple[PublicKey, int] = PublicKey.find_program_address(
|
|
[b"faucet"], faucet_program_address
|
|
)
|
|
authority: PublicKey = authority_and_nonce[0]
|
|
|
|
# Mints and airdrops tokens from a faucet.
|
|
#
|
|
# SPL instruction is at:
|
|
# https://github.com/paul-schaaf/spl-token-faucet/blob/main/src/program/src/instruction.rs
|
|
#
|
|
# ///
|
|
# /// Mints Tokens
|
|
# ///
|
|
# /// 0. `[]` The mint authority - Program Derived Address
|
|
# /// 1. `[writable]` Token Mint IAccount
|
|
# /// 2. `[writable]` Destination IAccount
|
|
# /// 3. `[]` The SPL Token Program
|
|
# /// 4. `[]` The Faucet IAccount
|
|
# /// 5. `[optional/signer]` Admin IAccount
|
|
faucet_airdrop_instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=authority),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=token_mint),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=destination),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=faucet),
|
|
],
|
|
program_id=faucet_program_address,
|
|
data=layouts.FAUCET_AIRDROP.build({"quantity": quantity}),
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[faucet_airdrop_instruction])
|
|
|
|
|
|
# #
|
|
# # 🥭 Serum instruction builders
|
|
# #
|
|
|
|
|
|
# # 🥭 build_serum_create_openorders_instructions function
|
|
#
|
|
# Creates a Serum openorders-creating instruction.
|
|
#
|
|
def build_serum_create_openorders_instructions(
|
|
context: Context, wallet: Wallet, market: PySerumMarket
|
|
) -> CombinableInstructions:
|
|
new_open_orders_account = Keypair()
|
|
minimum_balance = context.client.get_minimum_balance_for_rent_exemption(
|
|
layouts.OPEN_ORDERS.sizeof()
|
|
)
|
|
instruction = pyserum_make_create_account_instruction(
|
|
owner_address=wallet.address,
|
|
new_account_address=new_open_orders_account.public_key,
|
|
lamports=minimum_balance,
|
|
program_id=market.state.program_id(),
|
|
)
|
|
|
|
return CombinableInstructions(
|
|
signers=[new_open_orders_account], instructions=[instruction]
|
|
)
|
|
|
|
|
|
# # 🥭 build_serum_place_order_instructions function
|
|
#
|
|
# Creates a Serum order-placing instruction using V3 of the NewOrder instruction.
|
|
#
|
|
def build_serum_place_order_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
market: PySerumMarket,
|
|
source: PublicKey,
|
|
open_orders_address: PublicKey,
|
|
order_type: OrderType,
|
|
side: Side,
|
|
price: Decimal,
|
|
quantity: Decimal,
|
|
client_id: int,
|
|
fee_discount_address: PublicKey,
|
|
) -> CombinableInstructions:
|
|
serum_order_type: PySerumOrderType = (
|
|
PySerumOrderType.POST_ONLY
|
|
if order_type == OrderType.POST_ONLY
|
|
else PySerumOrderType.IOC
|
|
if order_type == OrderType.IOC
|
|
else PySerumOrderType.LIMIT
|
|
)
|
|
serum_side: PySerumSide = PySerumSide.SELL if side == Side.SELL else PySerumSide.BUY
|
|
|
|
instruction = market.make_place_order_instruction(
|
|
source,
|
|
wallet.keypair,
|
|
serum_order_type,
|
|
serum_side,
|
|
float(price),
|
|
float(quantity),
|
|
client_id,
|
|
open_orders_address,
|
|
fee_discount_address,
|
|
)
|
|
|
|
return CombinableInstructions(signers=[], instructions=[instruction])
|
|
|
|
|
|
# # 🥭 build_serum_consume_events_instructions function
|
|
#
|
|
# Creates an event-consuming 'crank' instruction.
|
|
#
|
|
def build_serum_consume_events_instructions(
|
|
context: Context,
|
|
market_address: PublicKey,
|
|
event_queue_address: PublicKey,
|
|
open_orders_addresses: typing.Sequence[PublicKey],
|
|
limit: int = 32,
|
|
) -> CombinableInstructions:
|
|
instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(pubkey=pubkey, is_signer=False, is_writable=True)
|
|
for pubkey in [*open_orders_addresses, market_address, event_queue_address]
|
|
],
|
|
program_id=context.serum_program_address,
|
|
data=PYSERUM_INSTRUCTIONS_LAYOUT.build(
|
|
dict(
|
|
instruction_type=PySerumInstructionType.CONSUME_EVENTS,
|
|
args=dict(limit=limit),
|
|
)
|
|
),
|
|
)
|
|
|
|
# The interface accepts (and currently requires) two accounts at the end, but
|
|
# it doesn't actually use them.
|
|
random_account = Keypair().public_key
|
|
instruction.keys.append(
|
|
AccountMeta(random_account, is_signer=False, is_writable=True)
|
|
)
|
|
instruction.keys.append(
|
|
AccountMeta(random_account, is_signer=False, is_writable=True)
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[instruction])
|
|
|
|
|
|
# # 🥭 build_serum_settle_instructions function
|
|
#
|
|
# Creates a 'settle' instruction.
|
|
#
|
|
def build_serum_settle_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
market: PySerumMarket,
|
|
open_orders_address: PublicKey,
|
|
base_token_account_address: PublicKey,
|
|
quote_token_account_address: PublicKey,
|
|
) -> CombinableInstructions:
|
|
vault_signer = PublicKey.create_program_address(
|
|
[
|
|
bytes(market.state.public_key()),
|
|
market.state.vault_signer_nonce().to_bytes(8, byteorder="little"),
|
|
],
|
|
market.state.program_id(),
|
|
)
|
|
instruction = pyserum_settle_funds(
|
|
PySerumSettleFundsParams(
|
|
market=market.state.public_key(),
|
|
open_orders=open_orders_address,
|
|
owner=wallet.address,
|
|
base_vault=market.state.base_vault(),
|
|
quote_vault=market.state.quote_vault(),
|
|
base_wallet=base_token_account_address,
|
|
quote_wallet=quote_token_account_address,
|
|
vault_signer=vault_signer,
|
|
program_id=market.state.program_id(),
|
|
)
|
|
)
|
|
|
|
return CombinableInstructions(signers=[], instructions=[instruction])
|
|
|
|
|
|
# #
|
|
# # 🥭 Spot instruction builders
|
|
# #
|
|
|
|
|
|
# # 🥭 build_spot_settle_instructions function
|
|
#
|
|
# Creates a 'settle' instruction for spot markets.
|
|
#
|
|
# /// Settle all funds from serum dex open orders
|
|
# ///
|
|
# /// Accounts expected by this instruction (18):
|
|
# ///
|
|
# /// 0. `[]` mango_group_ai - MangoGroup that this mango account is for
|
|
# /// 1. `[]` mango_cache_ai - MangoCache for this MangoGroup
|
|
# /// 2. `[signer]` owner_ai - MangoAccount owner
|
|
# /// 3. `[writable]` mango_account_ai - MangoAccount
|
|
# /// 4. `[]` dex_prog_ai - program id of serum dex
|
|
# /// 5. `[writable]` spot_market_ai - dex MarketState account
|
|
# /// 6. `[writable]` open_orders_ai - open orders for this market for this MangoAccount
|
|
# /// 7. `[]` signer_ai - MangoGroup signer key
|
|
# /// 8. `[writable]` dex_base_ai - base vault for dex MarketState
|
|
# /// 9. `[writable]` dex_quote_ai - quote vault for dex MarketState
|
|
# /// 10. `[]` base_root_bank_ai - MangoGroup base vault acc
|
|
# /// 11. `[writable]` base_node_bank_ai - MangoGroup quote vault acc
|
|
# /// 12. `[]` quote_root_bank_ai - MangoGroup quote vault acc
|
|
# /// 13. `[writable]` quote_node_bank_ai - MangoGroup quote vault acc
|
|
# /// 14. `[writable]` base_vault_ai - MangoGroup base vault acc
|
|
# /// 15. `[writable]` quote_vault_ai - MangoGroup quote vault acc
|
|
# /// 16. `[]` dex_signer_ai - dex PySerumMarket signer account
|
|
# /// 17. `[]` spl token program
|
|
#
|
|
def build_spot_settle_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
account: IAccount,
|
|
market: PySerumMarket,
|
|
group: IGroup,
|
|
open_orders_address: PublicKey,
|
|
base_rootbank: RootBank,
|
|
base_nodebank: NodeBank,
|
|
quote_rootbank: RootBank,
|
|
quote_nodebank: NodeBank,
|
|
) -> CombinableInstructions:
|
|
vault_signer = PublicKey.create_program_address(
|
|
[
|
|
bytes(market.state.public_key()),
|
|
market.state.vault_signer_nonce().to_bytes(8, byteorder="little"),
|
|
],
|
|
market.state.program_id(),
|
|
)
|
|
|
|
settle_instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=group.cache),
|
|
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=account.address),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=context.serum_program_address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=market.state.public_key()
|
|
),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=open_orders_address),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=market.state.base_vault()
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=market.state.quote_vault()
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=base_rootbank.address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=base_nodebank.address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=quote_rootbank.address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=quote_nodebank.address
|
|
),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=base_nodebank.vault),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=quote_nodebank.vault),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=vault_signer),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.SETTLE_FUNDS.build({}),
|
|
)
|
|
|
|
return CombinableInstructions(signers=[], instructions=[settle_instruction])
|
|
|
|
|
|
def build_spot_create_openorders_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
spot_market: ISpotMarket,
|
|
open_orders_address: PublicKey,
|
|
) -> CombinableInstructions:
|
|
instructions: CombinableInstructions = CombinableInstructions.empty()
|
|
|
|
# /// Accounts expected by this instruction (8):
|
|
# ///
|
|
# /// 0. `[]` mango_group_ai - MangoGroup that this mango account is for
|
|
# /// 1. `[writable]` mango_account_ai - MangoAccount
|
|
# /// 2. `[signer]` owner_ai - MangoAccount owner
|
|
# /// 3. `[]` dex_prog_ai - program id of serum dex
|
|
# /// 4. `[writable]` open_orders_ai - open orders PDA
|
|
# /// 5. `[]` spot_market_ai - dex MarketState account
|
|
# /// 6. `[]` signer_ai - IGroup Signer IAccount
|
|
# /// 7. `[]` system_prog_ai - System program
|
|
create_open_orders_instruction = 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=context.serum_program_address
|
|
),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=open_orders_address),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=spot_market.address),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=SYSTEM_PROGRAM_ADDRESS
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CREATE_SPOT_OPEN_ORDERS.build({}),
|
|
)
|
|
instructions += CombinableInstructions(
|
|
signers=[], instructions=[create_open_orders_instruction]
|
|
)
|
|
return instructions
|
|
|
|
|
|
# # 🥭 build_spot_place_order_instructions function
|
|
#
|
|
# Creates a Mango order-placing instruction using the Serum instruction as the inner instruction. Will create
|
|
# the necessary OpenOrders account if it doesn't already exist.
|
|
#
|
|
# /// Accounts expected by PLACE_SPOT_ORDER_2 instruction (22+openorders):
|
|
# { isSigner: false, isWritable: false, pubkey: mangoGroupPk },
|
|
# { isSigner: false, isWritable: true, pubkey: mangoAccountPk },
|
|
# { isSigner: true, isWritable: false, pubkey: ownerPk },
|
|
# { isSigner: false, isWritable: false, pubkey: mangoCachePk },
|
|
# { isSigner: false, isWritable: false, pubkey: serumDexPk },
|
|
# { isSigner: false, isWritable: true, pubkey: spotMarketPk },
|
|
# { isSigner: false, isWritable: true, pubkey: bidsPk },
|
|
# { isSigner: false, isWritable: true, pubkey: asksPk },
|
|
# { isSigner: false, isWritable: true, pubkey: requestQueuePk },
|
|
# { isSigner: false, isWritable: true, pubkey: eventQueuePk },
|
|
# { isSigner: false, isWritable: true, pubkey: spotMktBaseVaultPk },
|
|
# { isSigner: false, isWritable: true, pubkey: spotMktQuoteVaultPk },
|
|
# { isSigner: false, isWritable: false, pubkey: baseRootBankPk },
|
|
# { isSigner: false, isWritable: true, pubkey: baseNodeBankPk },
|
|
# { isSigner: false, isWritable: true, pubkey: baseVaultPk },
|
|
# { isSigner: false, isWritable: false, pubkey: quoteRootBankPk },
|
|
# { isSigner: false, isWritable: true, pubkey: quoteNodeBankPk },
|
|
# { isSigner: false, isWritable: true, pubkey: quoteVaultPk },
|
|
# { isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID },
|
|
# { isSigner: false, isWritable: false, pubkey: signerPk },
|
|
# { isSigner: false, isWritable: false, pubkey: dexSignerPk },
|
|
# { isSigner: false, isWritable: false, pubkey: msrmOrSrmVaultPk },
|
|
# ...openOrders.map(({ pubkey, isWritable }) => ({
|
|
# isSigner: false,
|
|
# isWritable,
|
|
# pubkey,
|
|
# })),
|
|
def build_spot_place_order_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
spot_market: ISpotMarket,
|
|
open_orders_address: PublicKey,
|
|
order_type: OrderType,
|
|
side: Side,
|
|
price: Decimal,
|
|
quantity: Decimal,
|
|
client_id: int,
|
|
fee_discount_address: PublicKey,
|
|
) -> CombinableInstructions:
|
|
instructions: CombinableInstructions = CombinableInstructions.empty()
|
|
|
|
pyserum_market: PySerumMarket = spot_market.underlying_serum_market
|
|
serum_order_type: pyserum.enums.OrderType = order_type.to_serum()
|
|
serum_side: pyserum.enums.Side = side.to_serum()
|
|
intrinsic_price = pyserum_market.state.price_number_to_lots(float(price))
|
|
max_base_quantity = pyserum_market.state.base_size_number_to_lots(float(quantity))
|
|
max_quote_quantity = (
|
|
pyserum_market.state.base_size_number_to_lots(float(quantity))
|
|
* pyserum_market.state.quote_lot_size()
|
|
* pyserum_market.state.price_number_to_lots(float(price))
|
|
)
|
|
|
|
base_token_banks = [
|
|
token_bank
|
|
for token_bank in group.base_tokens_by_index
|
|
if token_bank is not None
|
|
and token_bank.token.mint == pyserum_market.state.base_mint()
|
|
]
|
|
if len(base_token_banks) != 1:
|
|
raise Exception(
|
|
f"Could not find base token info for group {group.address} - length was {len(base_token_banks)} when it should be 1."
|
|
)
|
|
base_token_bank = base_token_banks[0]
|
|
quote_token_bank = group.shared_quote
|
|
|
|
base_root_bank: RootBank = base_token_bank.ensure_root_bank(context)
|
|
base_node_bank: NodeBank = base_root_bank.pick_node_bank(context)
|
|
quote_root_bank: RootBank = quote_token_bank.ensure_root_bank(context)
|
|
quote_node_bank: NodeBank = quote_root_bank.pick_node_bank(context)
|
|
|
|
vault_signer = PublicKey.create_program_address(
|
|
[
|
|
bytes(spot_market.address),
|
|
pyserum_market.state.vault_signer_nonce().to_bytes(8, byteorder="little"),
|
|
],
|
|
pyserum_market.state.program_id(),
|
|
)
|
|
|
|
place_spot_instruction = 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=False, pubkey=context.serum_program_address
|
|
),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=spot_market.address),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=spot_market.bids_address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=spot_market.asks_address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=True,
|
|
pubkey=pyserum_market.state.request_queue(),
|
|
),
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=True,
|
|
pubkey=spot_market.event_queue_address,
|
|
),
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=True,
|
|
pubkey=pyserum_market.state.base_vault(),
|
|
),
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=True,
|
|
pubkey=pyserum_market.state.quote_vault(),
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=base_root_bank.address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=base_node_bank.address
|
|
),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=base_node_bank.vault),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=quote_root_bank.address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=quote_node_bank.address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=quote_node_bank.vault
|
|
),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=vault_signer),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=fee_discount_address
|
|
),
|
|
*list(
|
|
[
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=(oo_address == open_orders_address),
|
|
pubkey=oo_address,
|
|
)
|
|
for oo_address in account.spot_open_orders
|
|
]
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.PLACE_SPOT_ORDER_2.build(
|
|
dict(
|
|
side=serum_side,
|
|
limit_price=intrinsic_price,
|
|
max_base_quantity=max_base_quantity,
|
|
max_quote_quantity=max_quote_quantity,
|
|
self_trade_behavior=pyserum.enums.SelfTradeBehavior.DECREMENT_TAKE,
|
|
order_type=serum_order_type,
|
|
client_id=client_id,
|
|
limit=65535,
|
|
)
|
|
),
|
|
)
|
|
|
|
return instructions + CombinableInstructions(
|
|
signers=[], instructions=[place_spot_instruction]
|
|
)
|
|
|
|
|
|
# # 🥭 build_spot_cancel_order_instructions function
|
|
#
|
|
# Builds the instructions necessary for cancelling a spot order.
|
|
#
|
|
def build_spot_cancel_order_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
market: PySerumMarket,
|
|
order: Order,
|
|
open_orders_address: PublicKey,
|
|
) -> CombinableInstructions:
|
|
# { buy: 0, sell: 1 }
|
|
raw_side: int = 1 if order.side == Side.SELL else 0
|
|
|
|
# Accounts expected by this instruction:
|
|
# { isSigner: false, isWritable: false, pubkey: mangoGroupPk },
|
|
# { isSigner: true, isWritable: false, pubkey: ownerPk },
|
|
# { isSigner: false, isWritable: false, pubkey: mangoAccountPk },
|
|
# { isSigner: false, isWritable: false, pubkey: dexProgramId },
|
|
# { isSigner: false, isWritable: true, pubkey: spotMarketPk },
|
|
# { isSigner: false, isWritable: true, pubkey: bidsPk },
|
|
# { isSigner: false, isWritable: true, pubkey: asksPk },
|
|
# { isSigner: false, isWritable: true, pubkey: openOrdersPk },
|
|
# { isSigner: false, isWritable: false, pubkey: signerKey },
|
|
# { isSigner: false, isWritable: true, pubkey: eventQueuePk },
|
|
|
|
instructions = [
|
|
TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
|
|
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=account.address),
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=False,
|
|
pubkey=context.serum_program_address,
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=market.state.public_key()
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=market.state.bids()
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=market.state.asks()
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=open_orders_address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=group.signer_key
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=market.state.event_queue()
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CANCEL_SPOT_ORDER.build(
|
|
{"order_id": order.id, "side": raw_side}
|
|
),
|
|
)
|
|
]
|
|
return CombinableInstructions(signers=[], instructions=instructions)
|
|
|
|
|
|
# #
|
|
# # 🥭 Perp instruction builders
|
|
# #
|
|
|
|
|
|
# # 🥭 build_perp_cancel_order_instructions function
|
|
#
|
|
# Builds the instructions necessary for cancelling a perp order.
|
|
#
|
|
def build_perp_cancel_order_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
account: IAccount,
|
|
perp_market_details: PerpMarketDetails,
|
|
order: Order,
|
|
invalid_id_ok: bool,
|
|
) -> CombinableInstructions:
|
|
# Prefer cancelling by client ID so we don't have to keep track of the order side.
|
|
if order.client_id is not None and order.client_id != 0:
|
|
data: bytes = layouts.CANCEL_PERP_ORDER_BY_CLIENT_ID.build(
|
|
{"client_order_id": order.client_id, "invalid_id_ok": invalid_id_ok}
|
|
)
|
|
else:
|
|
data = layouts.CANCEL_PERP_ORDER.build(
|
|
{"order_id": order.id, "invalid_id_ok": invalid_id_ok}
|
|
)
|
|
|
|
# Accounts expected by this instruction (both CANCEL_PERP_ORDER and CANCEL_PERP_ORDER_BY_CLIENT_ID are the same):
|
|
# { isSigner: false, isWritable: false, pubkey: mangoGroupPk },
|
|
# { isSigner: false, isWritable: true, pubkey: mangoAccountPk },
|
|
# { isSigner: true, isWritable: false, pubkey: ownerPk },
|
|
# { isSigner: false, isWritable: true, pubkey: perpMarketPk },
|
|
# { isSigner: false, isWritable: true, pubkey: bidsPk },
|
|
# { isSigner: false, isWritable: true, pubkey: asksPk },
|
|
|
|
instructions = [
|
|
TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=account.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=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
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=data,
|
|
)
|
|
]
|
|
return CombinableInstructions(signers=[], instructions=instructions)
|
|
|
|
|
|
# # 🥭 build_perp_cancel_all_orders_instructions function
|
|
#
|
|
# Builds the instructions necessary for cancelling all perp orders.
|
|
#
|
|
def build_perp_cancel_all_orders_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
account: IAccount,
|
|
perp_market_details: PerpMarketDetails,
|
|
limit: Decimal = Decimal(32),
|
|
) -> CombinableInstructions:
|
|
# Accounts expected by this instruction (seems to be the same as CANCEL_PERP_ORDER and CANCEL_PERP_ORDER_BY_CLIENT_ID):
|
|
# { isSigner: false, isWritable: false, pubkey: mangoGroupPk },
|
|
# { isSigner: false, isWritable: true, pubkey: mangoAccountPk },
|
|
# { isSigner: true, isWritable: false, pubkey: ownerPk },
|
|
# { isSigner: false, isWritable: true, pubkey: perpMarketPk },
|
|
# { isSigner: false, isWritable: true, pubkey: bidsPk },
|
|
# { isSigner: false, isWritable: true, pubkey: asksPk },
|
|
instructions = [
|
|
TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=account.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=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
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CANCEL_ALL_PERP_ORDERS.build({"limit": limit}),
|
|
)
|
|
]
|
|
return CombinableInstructions(signers=[], instructions=instructions)
|
|
|
|
|
|
# # 🥭 build_perp_cancel_all_side_instructions function
|
|
#
|
|
# Builds the instructions necessary for cancelling all perp orders on a given side.
|
|
#
|
|
def build_perp_cancel_all_side_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
account: IAccount,
|
|
perp_market_details: PerpMarketDetails,
|
|
side: Side,
|
|
limit: Decimal = Decimal(32),
|
|
) -> CombinableInstructions:
|
|
# { buy: 0, sell: 1 }
|
|
raw_side: int = 1 if side == Side.SELL else 0
|
|
|
|
# /// Cancel all perp open orders for one side of the book
|
|
# ///
|
|
# /// Accounts expected: 6
|
|
# /// 0. `[]` mango_group_ai - MangoGroup
|
|
# /// 1. `[writable]` mango_account_ai - MangoAccount
|
|
# /// 2. `[signer]` owner_ai - Owner of Mango Account
|
|
# /// 3. `[writable]` perp_market_ai - PerpMarket
|
|
# /// 4. `[writable]` bids_ai - Bids acc
|
|
# /// 5. `[writable]` asks_ai - Asks acc
|
|
instructions = [
|
|
TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=account.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=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
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CANCEL_PERP_ORDER_SIDE.build(
|
|
{"side": raw_side, "limit": limit}
|
|
),
|
|
)
|
|
]
|
|
return CombinableInstructions(signers=[], instructions=instructions)
|
|
|
|
|
|
# /// Place an order on a perp market
|
|
# ///
|
|
# /// In case this order is matched, the corresponding order structs on both
|
|
# /// PerpAccounts (taker & maker) will be adjusted, and the position size
|
|
# /// will be adjusted w/o accounting for fees.
|
|
# /// In addition a FillEvent will be placed on the event queue.
|
|
# /// Through a subsequent invocation of ConsumeEvents the FillEvent can be
|
|
# /// executed and the perp account balances (base/quote) and fees will be
|
|
# /// paid from the quote position. Only at this point the position balance
|
|
# /// is 100% reflecting the trade.
|
|
# ///
|
|
# /// Accounts expected by this instruction (9 + `NUM_IN_MARGIN_BASKET`):
|
|
# /// 0. `[]` mango_group_ai - MangoGroup
|
|
# /// 1. `[writable]` mango_account_ai - the MangoAccount of owner
|
|
# /// 2. `[signer]` owner_ai - owner of MangoAccount
|
|
# /// 3. `[]` mango_cache_ai - MangoCache for this MangoGroup
|
|
# /// 4. `[writable]` perp_market_ai
|
|
# /// 5. `[writable]` bids_ai - bids account for this PerpMarket
|
|
# /// 6. `[writable]` asks_ai - asks account for this PerpMarket
|
|
# /// 7. `[writable]` event_queue_ai - EventQueue for this PerpMarket
|
|
# /// 8. `[writable]` referrer_mango_account_ai - referrer's mango account;
|
|
# /// pass in mango_account_ai as duplicate if you don't have a referrer
|
|
# /// 9..9 + NUM_IN_MARGIN_BASKET `[]` open_orders_ais - pass in open orders in margin basket
|
|
def build_perp_place_order_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
perp_market_details: PerpMarketDetails,
|
|
price: Decimal,
|
|
quantity: Decimal,
|
|
client_order_id: int,
|
|
side: Side,
|
|
order_type: OrderType,
|
|
reduce_only: bool = False,
|
|
expiration: datetime = Order.NoExpiration,
|
|
match_limit: int = 20,
|
|
max_quote_quantity: Decimal = Decimal(0),
|
|
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()
|
|
|
|
base_decimals = perp_market_details.base_instrument.decimals
|
|
quote_decimals = perp_market_details.quote_token.token.decimals
|
|
|
|
base_factor = Decimal(10) ** base_decimals
|
|
quote_factor = Decimal(10) ** quote_decimals
|
|
|
|
native_price = ((price * quote_factor) * perp_market_details.base_lot_size) / (
|
|
perp_market_details.quote_lot_size * base_factor
|
|
)
|
|
native_quantity = (quantity * base_factor) / perp_market_details.base_lot_size
|
|
native_max_quote_quantity = (
|
|
(max_quote_quantity * quote_factor) / perp_market_details.quote_lot_size
|
|
) or I64_MAX
|
|
|
|
# /// Accounts expected by this instruction (9 + `NUM_IN_MARGIN_BASKET`):
|
|
# /// 0. `[]` mango_group_ai - MangoGroup
|
|
# /// 1. `[writable]` mango_account_ai - the MangoAccount of owner
|
|
# /// 2. `[signer]` owner_ai - owner of MangoAccount
|
|
# /// 3. `[]` mango_cache_ai - MangoCache for this MangoGroup
|
|
# /// 4. `[writable]` perp_market_ai
|
|
# /// 5. `[writable]` bids_ai - bids account for this PerpMarket
|
|
# /// 6. `[writable]` asks_ai - asks account for this PerpMarket
|
|
# /// 7. `[writable]` event_queue_ai - EventQueue for this PerpMarket
|
|
# /// 8. `[writable]` referrer_mango_account_ai - referrer's mango account;
|
|
# /// pass in mango_account_ai as duplicate if you don't have a referrer
|
|
# /// 9..9 + NUM_IN_MARGIN_BASKET `[]` open_orders_ais - pass in open orders in margin basket
|
|
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
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=reflink or account.address
|
|
),
|
|
*list(
|
|
[
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=False,
|
|
pubkey=oo_address,
|
|
)
|
|
for oo_address in account.spot_open_orders
|
|
]
|
|
),
|
|
]
|
|
|
|
instructions = [
|
|
TransactionInstruction(
|
|
keys=keys,
|
|
program_id=context.mango_program_address,
|
|
data=layouts.PLACE_PERP_ORDER_2.build(
|
|
{
|
|
"price": native_price,
|
|
"max_base_quantity": native_quantity,
|
|
"max_quote_quantity": native_max_quote_quantity,
|
|
"client_order_id": client_order_id,
|
|
"expiry_timestamp": expiration,
|
|
"side": raw_side,
|
|
"order_type": raw_order_type,
|
|
"reduce_only": reduce_only,
|
|
"limit": match_limit,
|
|
}
|
|
),
|
|
)
|
|
]
|
|
|
|
return CombinableInstructions(signers=[], instructions=instructions)
|
|
|
|
|
|
def build_perp_consume_events_instructions(
|
|
context: Context,
|
|
group: IGroup,
|
|
perp_market_details: PerpMarketDetails,
|
|
account_addresses: typing.Sequence[PublicKey],
|
|
limit: Decimal = Decimal(32),
|
|
) -> CombinableInstructions:
|
|
# Accounts expected by this instruction:
|
|
# { isSigner: false, isWritable: false, pubkey: mangoGroupPk },
|
|
# { isSigner: false, isWritable: false, pubkey: mangoCachePk },
|
|
# { isSigner: false, isWritable: true, pubkey: perpMarketPk },
|
|
# { isSigner: false, isWritable: true, pubkey: eventQueuePk },
|
|
# ...mangoAccountPks.sort().map((pubkey) => ({
|
|
# isSigner: false,
|
|
# isWritable: true,
|
|
# pubkey,
|
|
# })),
|
|
|
|
instructions = [
|
|
TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.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.event_queue,
|
|
),
|
|
*list(
|
|
[
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=account_address
|
|
)
|
|
for account_address in account_addresses
|
|
]
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CONSUME_EVENTS.build(
|
|
{
|
|
"limit": limit,
|
|
}
|
|
),
|
|
)
|
|
]
|
|
return CombinableInstructions(signers=[], instructions=instructions)
|
|
|
|
|
|
# #
|
|
# # 🥭 Mango instruction builders
|
|
# #
|
|
|
|
|
|
# The old INIT_MANGO_ACCOUNT instruction is now superseded by CREATE_MANGO_ACCOUNT
|
|
def build_mango_create_account_instructions(
|
|
context: Context, wallet: Wallet, group: IGroup, account_num: Decimal = Decimal(1)
|
|
) -> CombinableInstructions:
|
|
mango_account_address_and_nonce: typing.Tuple[
|
|
PublicKey, int
|
|
] = PublicKey.find_program_address(
|
|
[
|
|
bytes(group.address),
|
|
bytes(wallet.address),
|
|
int(account_num).to_bytes(8, "little"),
|
|
],
|
|
context.mango_program_address,
|
|
)
|
|
mango_account_address: PublicKey = mango_account_address_and_nonce[0]
|
|
|
|
# /// 0. `[writable]` mango_group_ai - MangoGroup that this mango account is for
|
|
# /// 1. `[writable]` mango_account_ai - the mango account data
|
|
# /// 2. `[signer]` owner_ai - Solana account of owner of the mango account
|
|
# /// 3. `[]` system_prog_ai - System program
|
|
create = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=group.address),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=mango_account_address
|
|
),
|
|
AccountMeta(is_signer=True, is_writable=False, pubkey=wallet.address),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=SYSTEM_PROGRAM_ADDRESS
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CREATE_MANGO_ACCOUNT.build({"account_num": account_num}),
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[create])
|
|
|
|
|
|
# /// Deposit funds into mango account
|
|
# ///
|
|
# /// Accounts expected by this instruction (8):
|
|
# ///
|
|
# /// 0. `[]` mango_group_ai - MangoGroup that this mango account is for
|
|
# /// 1. `[writable]` mango_account_ai - the mango account for this user
|
|
# /// 2. `[signer]` owner_ai - Solana account of owner of the mango account
|
|
# /// 3. `[]` mango_cache_ai - MangoCache
|
|
# /// 4. `[]` root_bank_ai - RootBank owned by MangoGroup
|
|
# /// 5. `[writable]` node_bank_ai - NodeBank owned by RootBank
|
|
# /// 6. `[writable]` vault_ai - TokenAccount owned by MangoGroup
|
|
# /// 7. `[]` token_prog_ai - acc pointed to by SPL token program id
|
|
# /// 8. `[writable]` owner_token_account_ai - TokenAccount owned by user which will be sending the funds
|
|
# Deposit {
|
|
# quantity: u64,
|
|
# },
|
|
def build_mango_deposit_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
root_bank: RootBank,
|
|
node_bank: NodeBank,
|
|
token_account: TokenAccount,
|
|
) -> CombinableInstructions:
|
|
value = token_account.value.shift_to_native().value
|
|
deposit = 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=False, pubkey=root_bank.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=node_bank.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=node_bank.vault),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=token_account.address
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.DEPOSIT.build({"quantity": value}),
|
|
)
|
|
|
|
return CombinableInstructions(signers=[], instructions=[deposit])
|
|
|
|
|
|
# /// Withdraw funds that were deposited earlier.
|
|
# ///
|
|
# /// Accounts expected by this instruction (10):
|
|
# ///
|
|
# /// 0. `[read]` mango_group_ai, -
|
|
# /// 1. `[write]` mango_account_ai, -
|
|
# /// 2. `[read]` owner_ai, -
|
|
# /// 3. `[read]` mango_cache_ai, -
|
|
# /// 4. `[read]` root_bank_ai, -
|
|
# /// 5. `[write]` node_bank_ai, -
|
|
# /// 6. `[write]` vault_ai, -
|
|
# /// 7. `[write]` token_account_ai, -
|
|
# /// 8. `[read]` signer_ai, -
|
|
# /// 9. `[read]` token_prog_ai, -
|
|
# /// 10. `[read]` clock_ai, -
|
|
# /// 11..+ `[]` open_orders_accs - open orders for each of the spot market
|
|
# Withdraw {
|
|
# quantity: u64,
|
|
# allow_borrow: bool,
|
|
# },
|
|
def build_mango_withdraw_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
root_bank: RootBank,
|
|
node_bank: NodeBank,
|
|
token_account: TokenAccount,
|
|
allow_borrow: bool,
|
|
) -> CombinableInstructions:
|
|
value = token_account.value.shift_to_native().value
|
|
withdraw = 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=False, pubkey=root_bank.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=node_bank.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=node_bank.vault),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=token_account.address
|
|
),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID),
|
|
*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
|
|
]
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.WITHDRAW.build({"quantity": value, "allow_borrow": allow_borrow}),
|
|
)
|
|
|
|
return CombinableInstructions(signers=[], instructions=[withdraw])
|
|
|
|
|
|
# # 🥭 build_mango_redeem_accrued_instructions function
|
|
#
|
|
# Creates a 'RedeemMngo' instruction for Mango accounts.
|
|
#
|
|
def build_mango_redeem_accrued_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
perp_market: IPerpMarket,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
mngo: TokenBank,
|
|
) -> CombinableInstructions:
|
|
node_bank: NodeBank = mngo.pick_node_bank(context)
|
|
# /// Redeem the mngo_accrued in a PerpAccount for MNGO in MangoAccount deposits
|
|
# ///
|
|
# /// Accounts expected by this instruction (11):
|
|
# /// 0. `[]` mango_group_ai - MangoGroup that this mango account is for
|
|
# /// 1. `[]` mango_cache_ai - MangoCache
|
|
# /// 2. `[writable]` mango_account_ai - MangoAccount
|
|
# /// 3. `[signer]` owner_ai - MangoAccount owner
|
|
# /// 4. `[]` perp_market_ai - IPerpMarket
|
|
# /// 5. `[writable]` mngo_perp_vault_ai
|
|
# /// 6. `[]` mngo_root_bank_ai
|
|
# /// 7. `[writable]` mngo_node_bank_ai
|
|
# /// 8. `[writable]` mngo_bank_vault_ai
|
|
# /// 9. `[]` signer_ai - IGroup Signer IAccount
|
|
# /// 10. `[]` token_prog_ai - SPL Token program id
|
|
redeem_accrued_mango_instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.cache),
|
|
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=perp_market.address),
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=True,
|
|
pubkey=perp_market.underlying_perp_market.mngo_vault,
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=mngo.root_bank_address
|
|
),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=node_bank.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=node_bank.vault),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.signer_key),
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=TOKEN_PROGRAM_ID),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.REDEEM_MNGO.build({}),
|
|
)
|
|
return CombinableInstructions(
|
|
signers=[], instructions=[redeem_accrued_mango_instruction]
|
|
)
|
|
|
|
|
|
# # 🥭 build_mango_set_account_delegate_instructions function
|
|
#
|
|
# Creates an instruction to delegate account operations (except Withdraw and CloseAccount) to a
|
|
# different account.
|
|
#
|
|
# Set to SYSTEM_PROGRAM_ADDRESS to revoke delegate.
|
|
#
|
|
def build_mango_set_account_delegate_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
delegate: PublicKey,
|
|
) -> CombinableInstructions:
|
|
# /// https://github.com/blockworks-foundation/mango-v3/pull/97/
|
|
# /// Set delegate authority to mango account which can do everything regular account can do
|
|
# /// except Withdraw and CloseMangoAccount. Set to Pubkey::default() to revoke delegate
|
|
# ///
|
|
# /// Accounts expected: 4
|
|
# /// 0. `[]` mango_group_ai - MangoGroup
|
|
# /// 1. `[writable]` mango_account_ai - MangoAccount
|
|
# /// 2. `[signer]` owner_ai - Owner of Mango IAccount
|
|
# /// 3. `[]` delegate_ai - delegate
|
|
set_delegate_instruction = 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=delegate),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.SET_DELEGATE.build({}),
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[set_delegate_instruction])
|
|
|
|
|
|
def build_mango_unset_account_delegate_instructions(
|
|
context: Context, wallet: Wallet, group: IGroup, account: IAccount
|
|
) -> CombinableInstructions:
|
|
return build_mango_set_account_delegate_instructions(
|
|
context, wallet, group, account, SYSTEM_PROGRAM_ADDRESS
|
|
)
|
|
|
|
|
|
# # 🥭 build_mango_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_mango_set_referrer_memory_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
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_mango_register_referrer_id_instructions function
|
|
#
|
|
# Creates an instruction to register a 'referrer ID' for a Mango IAccount
|
|
#
|
|
def build_mango_register_referrer_id_instructions(
|
|
context: Context,
|
|
wallet: Wallet,
|
|
group: IGroup,
|
|
account: IAccount,
|
|
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]
|
|
)
|
|
|
|
|
|
def build_mango_cache_root_banks_instructions(
|
|
context: Context,
|
|
group: IGroup,
|
|
root_banks: typing.Sequence[PublicKey],
|
|
) -> CombinableInstructions:
|
|
# /// DEPRECATED - caching of root banks now happens in update index
|
|
# /// Cache root banks
|
|
# ///
|
|
# /// Accounts expected: 2 + Root Banks
|
|
# /// 0. `[]` mango_group_ai
|
|
# /// 1. `[writable]` mango_cache_ai
|
|
instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=group.cache),
|
|
*list(
|
|
[
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=False,
|
|
pubkey=root_bank,
|
|
)
|
|
for root_bank in root_banks
|
|
]
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CACHE_ROOT_BANKS.build({}),
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[instruction])
|
|
|
|
|
|
def build_mango_cache_prices_instructions(
|
|
context: Context,
|
|
group: IGroup,
|
|
oracle_addresses: typing.Sequence[PublicKey],
|
|
) -> CombinableInstructions:
|
|
# /// Cache prices
|
|
# ///
|
|
# /// Accounts expected: 3 + Oracles
|
|
# /// 0. `[]` mango_group_ai -
|
|
# /// 1. `[writable]` mango_cache_ai -
|
|
# /// 2+... `[]` oracle_ais - flux aggregator feed accounts
|
|
instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=group.cache),
|
|
*list(
|
|
[
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=False,
|
|
pubkey=oracle,
|
|
)
|
|
for oracle in oracle_addresses
|
|
]
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CACHE_PRICES.build({}),
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[instruction])
|
|
|
|
|
|
def build_mango_cache_perp_markets_instructions(
|
|
context: Context,
|
|
group: IGroup,
|
|
perp_market_addresses: typing.Sequence[PublicKey],
|
|
) -> CombinableInstructions:
|
|
# /// Cache perp markets
|
|
# ///
|
|
# /// Accounts expected: 2 + Perp Markets
|
|
# /// 0. `[]` mango_group_ai
|
|
# /// 1. `[writable]` mango_cache_ai
|
|
instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=group.cache),
|
|
*map(
|
|
lambda address: AccountMeta(
|
|
is_signer=False,
|
|
is_writable=False,
|
|
pubkey=address,
|
|
),
|
|
perp_market_addresses,
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.CACHE_PERP_MARKETS.build({}),
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[instruction])
|
|
|
|
|
|
def build_mango_update_root_bank_instructions(
|
|
context: Context,
|
|
group: IGroup,
|
|
root_bank: PublicKey,
|
|
node_banks: typing.Sequence[PublicKey],
|
|
) -> CombinableInstructions:
|
|
# /// Update a root bank's indexes by providing all it's node banks
|
|
# ///
|
|
# /// Accounts expected: 2 + Node Banks
|
|
# /// 0. `[]` mango_group_ai - MangoGroup
|
|
# /// 1. `[]` root_bank_ai - RootBank
|
|
# /// 2+... `[]` node_bank_ais - NodeBanks
|
|
#
|
|
# Note: The above doesn't seem quite right. It seems to need the Cache as [1], and
|
|
# cache+banks all seem to need to be writable.
|
|
instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=group.cache),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=root_bank),
|
|
*list(
|
|
[
|
|
AccountMeta(
|
|
is_signer=False,
|
|
is_writable=True,
|
|
pubkey=node_bank,
|
|
)
|
|
for node_bank in node_banks
|
|
]
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.UPDATE_ROOT_BANK.build({}),
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[instruction])
|
|
|
|
|
|
def build_mango_update_funding_instructions(
|
|
context: Context,
|
|
group: IGroup,
|
|
perp_market_details: PerpMarketDetails,
|
|
) -> CombinableInstructions:
|
|
# /// Update funding related variables
|
|
#
|
|
# Seems to take:
|
|
# const keys = [
|
|
# { isSigner: false, isWritable: false, pubkey: mangoGroupPk },
|
|
# { isSigner: false, isWritable: true, pubkey: mangoCachePk },
|
|
# { isSigner: false, isWritable: true, pubkey: perpMarketPk },
|
|
# { isSigner: false, isWritable: false, pubkey: bidsPk },
|
|
# { isSigner: false, isWritable: false, pubkey: asksPk },
|
|
# ];
|
|
instruction = TransactionInstruction(
|
|
keys=[
|
|
AccountMeta(is_signer=False, is_writable=False, pubkey=group.address),
|
|
AccountMeta(is_signer=False, is_writable=True, pubkey=group.cache),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=True, pubkey=perp_market_details.address
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=perp_market_details.bids
|
|
),
|
|
AccountMeta(
|
|
is_signer=False, is_writable=False, pubkey=perp_market_details.asks
|
|
),
|
|
],
|
|
program_id=context.mango_program_address,
|
|
data=layouts.UPDATE_FUNDING.build({}),
|
|
)
|
|
return CombinableInstructions(signers=[], instructions=[instruction])
|