2021-06-25 02:33:40 -07:00
|
|
|
|
# # ⚠ 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 typing
|
|
|
|
|
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
from solana.publickey import PublicKey
|
|
|
|
|
from solana.rpc.types import MemcmpOpts
|
|
|
|
|
|
|
|
|
|
from .accountinfo import AccountInfo
|
|
|
|
|
from .addressableaccount import AddressableAccount
|
|
|
|
|
from .context import Context
|
|
|
|
|
from .encoding import encode_key
|
2021-06-25 07:50:37 -07:00
|
|
|
|
from .group import Group
|
2021-06-25 02:33:40 -07:00
|
|
|
|
from .layouts import layouts
|
|
|
|
|
from .metadata import Metadata
|
2021-07-01 13:24:08 -07:00
|
|
|
|
from .tokenvalue import TokenValue
|
2021-06-25 02:33:40 -07:00
|
|
|
|
from .version import Version
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
# # 🥭 Account class
|
2021-06-25 02:33:40 -07:00
|
|
|
|
#
|
2021-06-25 07:50:37 -07:00
|
|
|
|
# `Account` holds information about the account for a particular user/wallet for a particualr `Group`.
|
2021-06-25 02:33:40 -07:00
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
class Account(AddressableAccount):
|
2021-06-25 02:33:40 -07:00
|
|
|
|
def __init__(self, account_info: AccountInfo, version: Version,
|
2021-07-12 02:57:05 -07:00
|
|
|
|
meta_data: Metadata, group: Group, owner: PublicKey, in_margin_basket: typing.Sequence[Decimal],
|
2021-07-01 13:24:08 -07:00
|
|
|
|
deposits: typing.Sequence[typing.Optional[TokenValue]], borrows: typing.Sequence[typing.Optional[TokenValue]],
|
|
|
|
|
net_assets: typing.Sequence[typing.Optional[TokenValue]], spot_open_orders: typing.Sequence[PublicKey],
|
2021-07-13 04:43:50 -07:00
|
|
|
|
perp_accounts: typing.Sequence[typing.Any], msrm_amount: Decimal, being_liquidated: bool,
|
|
|
|
|
is_bankrupt: bool):
|
2021-06-25 02:33:40 -07:00
|
|
|
|
super().__init__(account_info)
|
|
|
|
|
self.version: Version = version
|
|
|
|
|
|
|
|
|
|
self.meta_data: Metadata = meta_data
|
2021-07-01 13:24:08 -07:00
|
|
|
|
self.group: Group = group
|
2021-06-25 02:33:40 -07:00
|
|
|
|
self.owner: PublicKey = owner
|
2021-07-12 02:57:05 -07:00
|
|
|
|
self.in_margin_basket: typing.Sequence[Decimal] = in_margin_basket
|
2021-07-01 13:24:08 -07:00
|
|
|
|
self.deposits: typing.Sequence[typing.Optional[TokenValue]] = deposits
|
|
|
|
|
self.borrows: typing.Sequence[typing.Optional[TokenValue]] = borrows
|
|
|
|
|
self.net_assets: typing.Sequence[typing.Optional[TokenValue]] = net_assets
|
2021-06-25 07:50:37 -07:00
|
|
|
|
self.spot_open_orders: typing.Sequence[PublicKey] = spot_open_orders
|
|
|
|
|
self.perp_accounts: typing.Sequence[layouts.PERP_ACCOUNT] = perp_accounts
|
2021-07-13 04:43:50 -07:00
|
|
|
|
self.msrm_amount: Decimal = msrm_amount
|
|
|
|
|
self.being_liquidated: bool = being_liquidated
|
2021-07-01 13:24:08 -07:00
|
|
|
|
self.is_bankrupt: bool = is_bankrupt
|
2021-06-25 02:33:40 -07:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2021-07-01 13:24:08 -07:00
|
|
|
|
def from_layout(layout: layouts.MANGO_ACCOUNT, account_info: AccountInfo, version: Version, group: Group) -> "Account":
|
2021-06-25 02:33:40 -07:00
|
|
|
|
meta_data = Metadata.from_layout(layout.meta_data)
|
|
|
|
|
owner: PublicKey = layout.owner
|
2021-07-12 02:57:05 -07:00
|
|
|
|
in_margin_basket: typing.Sequence[Decimal] = layout.in_margin_basket
|
2021-07-01 13:24:08 -07:00
|
|
|
|
deposits: typing.List[typing.Optional[TokenValue]] = []
|
|
|
|
|
borrows: typing.List[typing.Optional[TokenValue]] = []
|
|
|
|
|
net_assets: typing.List[typing.Optional[TokenValue]] = []
|
|
|
|
|
for index, token_info in enumerate(group.tokens):
|
|
|
|
|
if token_info:
|
|
|
|
|
intrinsic_deposit = token_info.root_bank.deposit_index * layout.deposits[index]
|
|
|
|
|
deposit = token_info.token.shift_to_decimals(intrinsic_deposit)
|
|
|
|
|
deposits += [TokenValue(token_info.token, deposit)]
|
|
|
|
|
intrinsic_borrow = token_info.root_bank.borrow_index * layout.borrows[index]
|
|
|
|
|
borrow = token_info.token.shift_to_decimals(intrinsic_borrow)
|
|
|
|
|
borrows += [TokenValue(token_info.token, borrow)]
|
|
|
|
|
net_assets += [TokenValue(token_info.token, deposit - borrow)]
|
|
|
|
|
else:
|
|
|
|
|
deposits += [None]
|
|
|
|
|
borrows += [None]
|
|
|
|
|
net_assets += [None]
|
|
|
|
|
|
2021-06-25 07:50:37 -07:00
|
|
|
|
spot_open_orders: typing.Sequence[PublicKey] = layout.spot_open_orders
|
|
|
|
|
perp_accounts: typing.Sequence[typing.Any] = layout.perp_accounts
|
2021-07-13 04:43:50 -07:00
|
|
|
|
msrm_amount: Decimal = layout.msrm_amount
|
|
|
|
|
being_liquidated: bool = layout.being_liquidated
|
2021-07-01 13:24:08 -07:00
|
|
|
|
is_bankrupt: bool = layout.is_bankrupt
|
2021-06-25 02:33:40 -07:00
|
|
|
|
|
2021-07-13 04:43:50 -07:00
|
|
|
|
return Account(account_info, version, meta_data, group, owner, in_margin_basket, deposits, borrows, net_assets, spot_open_orders, perp_accounts, msrm_amount, being_liquidated, is_bankrupt)
|
2021-06-25 02:33:40 -07:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2021-07-01 13:24:08 -07:00
|
|
|
|
def parse(context: Context, account_info: AccountInfo, group: Group) -> "Account":
|
2021-06-25 02:33:40 -07:00
|
|
|
|
data = account_info.data
|
|
|
|
|
if len(data) != layouts.MANGO_ACCOUNT.sizeof():
|
|
|
|
|
raise Exception(
|
2021-07-12 02:57:05 -07:00
|
|
|
|
f"Account data length ({len(data)}) does not match expected size ({layouts.MANGO_ACCOUNT.sizeof()})")
|
2021-06-25 02:33:40 -07:00
|
|
|
|
|
|
|
|
|
layout = layouts.MANGO_ACCOUNT.parse(data)
|
2021-07-01 13:24:08 -07:00
|
|
|
|
return Account.from_layout(layout, account_info, Version.V1, group)
|
2021-06-25 02:33:40 -07:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2021-07-01 13:24:08 -07:00
|
|
|
|
def load(context: Context, address: PublicKey, group: Group) -> "Account":
|
2021-06-25 02:33:40 -07:00
|
|
|
|
account_info = AccountInfo.load(context, address)
|
|
|
|
|
if account_info is None:
|
2021-06-25 07:50:37 -07:00
|
|
|
|
raise Exception(f"Account account not found at address '{address}'")
|
2021-07-01 13:24:08 -07:00
|
|
|
|
return Account.parse(context, account_info, group)
|
2021-06-25 02:33:40 -07:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2021-06-25 07:50:37 -07:00
|
|
|
|
def load_all_for_owner(context: Context, owner: PublicKey, group: Group) -> typing.Sequence["Account"]:
|
2021-06-25 02:33:40 -07:00
|
|
|
|
# mango_group is just after the METADATA, which is the first entry.
|
|
|
|
|
group_offset = layouts.METADATA.sizeof()
|
|
|
|
|
# owner is just after mango_group in the layout, and it's a PublicKey which is 32 bytes.
|
|
|
|
|
owner_offset = group_offset + 32
|
|
|
|
|
filters = [
|
|
|
|
|
MemcmpOpts(
|
|
|
|
|
offset=group_offset,
|
|
|
|
|
bytes=encode_key(group.address)
|
|
|
|
|
),
|
|
|
|
|
MemcmpOpts(
|
|
|
|
|
offset=owner_offset,
|
|
|
|
|
bytes=encode_key(owner)
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
response = context.client.get_program_accounts(
|
|
|
|
|
context.program_id, memcmp_opts=filters, commitment=context.commitment, encoding="base64")
|
|
|
|
|
accounts = []
|
|
|
|
|
for account_data in response["result"]:
|
|
|
|
|
address = PublicKey(account_data["pubkey"])
|
|
|
|
|
account_info = AccountInfo._from_response_values(account_data["account"], address)
|
2021-07-01 13:24:08 -07:00
|
|
|
|
account = Account.parse(context, account_info, group)
|
2021-06-25 02:33:40 -07:00
|
|
|
|
accounts += [account]
|
|
|
|
|
return accounts
|
|
|
|
|
|
2021-07-23 03:42:22 -07:00
|
|
|
|
@staticmethod
|
|
|
|
|
def load_primary_for_owner(context: Context, owner: PublicKey, group: Group) -> "Account":
|
|
|
|
|
# Don't try to do anything smart (yet). Just return the first account. Might need to be smarter in the future.
|
|
|
|
|
return Account.load_all_for_owner(context, owner, group)[0]
|
|
|
|
|
|
2021-06-25 02:33:40 -07:00
|
|
|
|
def __str__(self):
|
2021-07-01 13:24:08 -07:00
|
|
|
|
deposits = "\n ".join(
|
2021-07-01 14:10:52 -07:00
|
|
|
|
[f"{deposit}" for deposit in self.deposits if deposit is not None and deposit.value != Decimal(0)] or ["None"])
|
|
|
|
|
borrows = "\n ".join(
|
|
|
|
|
[f"{borrow}" for borrow in self.borrows if borrow is not None and borrow.value != Decimal(0)] or ["None"])
|
2021-07-01 13:24:08 -07:00
|
|
|
|
net_assets = "\n ".join(
|
2021-07-01 14:10:52 -07:00
|
|
|
|
[f"{net_asset}" for net_asset in self.net_assets if net_asset is not None and net_asset.value != Decimal(0)] or ["None"])
|
2021-06-25 02:33:40 -07:00
|
|
|
|
spot_open_orders = ", ".join([f"{oo}" for oo in self.spot_open_orders if oo is not None])
|
|
|
|
|
perp_accounts = ", ".join(
|
2021-07-19 07:04:53 -07:00
|
|
|
|
[f"{perp}".replace("\n", "\n ") for perp in self.perp_accounts if perp.open_orders.free_slot_bits != 0xFFFFFFFF])
|
2021-06-30 06:08:37 -07:00
|
|
|
|
return f"""« 𝙰𝚌𝚌𝚘𝚞𝚗𝚝 {self.version} [{self.address}]
|
2021-06-25 02:33:40 -07:00
|
|
|
|
{self.meta_data}
|
2021-07-13 04:43:50 -07:00
|
|
|
|
Bankrupt? {self.is_bankrupt}
|
|
|
|
|
Being Liquidated? {self.being_liquidated}
|
2021-06-25 02:33:40 -07:00
|
|
|
|
Owner: {self.owner}
|
2021-07-29 04:38:34 -07:00
|
|
|
|
Group: « 𝙶𝚛𝚘𝚞𝚙 '{self.group.name}' {self.group.version} [{self.group.address}] »
|
2021-06-25 02:33:40 -07:00
|
|
|
|
Deposits:
|
|
|
|
|
{deposits}
|
|
|
|
|
Borrows:
|
|
|
|
|
{borrows}
|
2021-07-01 13:24:08 -07:00
|
|
|
|
Net Assets:
|
|
|
|
|
{net_assets}
|
2021-06-25 02:33:40 -07:00
|
|
|
|
Spot Open Orders:
|
|
|
|
|
{spot_open_orders}
|
|
|
|
|
Perp Accounts:
|
|
|
|
|
{perp_accounts}
|
2021-07-13 04:43:50 -07:00
|
|
|
|
MSRM: {self.msrm_amount}
|
2021-06-25 02:33:40 -07:00
|
|
|
|
»"""
|
2021-07-12 02:57:05 -07:00
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
|
return f"{self}"
|