User accounts are stored only on the IDS in this map and all accounts operations could be found in this module. There is no concept of accounts for EDS and there are no actions performed by an account on EDS. The only action a user performs directly on EDS is the deposit but this does not create a user account on EDS, just records the entry of a deposit in deposits table which is later removed once the deposit is processed by IDS.
A bluefin Pro account is created off-chain in margining engine (ME) when a user deposits funds/assets to the EDS and its AssetBankDeposit event is received by the ME.
// asset bank deposit event
struct AssetBankDeposit has copy, drop {
eds_id: ID,
asset:String,
from: address,
to: address,
amount: u64,
nonce: u128,
sequence_number: u128
}
Once a request from ME is sent to sequencer to perform deposit to the internal bank or IDS only then an account is added to the table of accounts in the IDS.
The structure below shows the state of account stored on-chain:
/// For storing an account's information in the system
struct Account has store {
// Address of the Account. This is the owner of the account.
address: address,
// Addresses of users/wallets that are authorized to perform
// actions on the account's behalf ( everything excluding withdrawal)
authorized: vector<address>,
// the list of assets currently collateralizing the user cross position.
assets: vector<DepositedAsset>,
// list of cross positions the account has
cross_positions: vector<Position>,
// list of isolated positions
isolated_positions: vector<Position>,
// trading fees to be applied on the user
trading_fees: TradeFee,
is_institution: bool,
// symbol of the asset to be used for payment of trade fees
fee_asset: String
}
address: This is the address of the account
authorized: On Pro, an account can whitelist or authorize a set of wallets that can sign the following requests to be submitted on behalf of their parent account.
A single account can have N whitelisted wallets and a single wallet can be whitelisted by M different parent accounts. The child/whitelisted account just needs to sign payloads with the account as one of its parent address when creating signed payload for their parent account(s)
assets: When a deposit is made to the internal bank or IDS, the amount of funds user deposited into EDS and the symbol of the deposited asset is stored on account level under assets. All the assets in the account collaterize all open cross positions of an account. When an asset X is depleted its entry is removed from the assets vector reducing its size and ensuring we are not storing user assets with ZERO balance.
cross_positions: This is the list of current open cross positions of the account. A position has following structure:
/// A single account position
struct Position has copy, drop, store {
// The address of the perpetual to which the position belongs
perpetual: String,
// The size of the current open position
size: u64,
// average entry price for current open position
average_entry_price: u64,
// True if long, false otherwise
is_long: bool,
// Leverage being used for the position. This will be zero for cross
leverage: u64,
// The amount of margin locked in the position.
// for cross positions this is zero.
// for isolated the margin represents the amount of USDC
margin: u64,
// flag indicating if the position is isolated or not
is_isolated: bool,
// The last funding rate that got applied to the position
funding: FundingRate,
// Any pending funding payment that is needed to be paid
pending_funding_payment: u64,
}
When a cross position is opened, its always opened with the max leverage allowed by the perpetual so on position level the leverage for cross positions is always zero ( could have been set to max leverage of market as well). Since all the assets in the account collaterize the cross position, there is no margin stored with in the position.
isolated_positions: This isolated positions are same as cross with the only difference being that there is a non-zero leverage on each isolated position and when an isolated position is opened or its size is increased funds/assets are moved from the assets vector on account level into the position as margin. Only the margin of the position collaterizes it so if it gets liquidated only that margin is lost. Other than that the AdjustMargin and Adjustleverage calls can only be performed on an isolated position.
trading_fees: By default, all accounts pay the same maker/taker fee that is stored on perpetual level. But the fee operator can have special fee tier for certain accounts. For instance the default maker/taker fee may be 5 and 3 bips on perpetual level for maker and taker but an account could have their fee teir set to 2 and 1 bps. During a trade, we determine if the user needs to pay the default fee or they have a special trading fee assigned to them and charge for trade fee accordingly.
/// For storing fee information of an account
struct TradeFee has store, copy, drop {
// maker side fee percentage
maker: Number,
// taker side fee percentage
taker: Number,
// True if the above fee percentages are to be applied, False will revert
// to perpetual default fees
applied: bool
}
is_institution: Every account is retail account by default but the guardian of the protocol can whitelist an account as institution account. An institution account does not pay and gas charges fee for a trade
fee_asset: An account can chose the asset they want to pay trade/gas fees in. By default they pay the trade/gas fee in USDC/USDT. (Note. There is only provisioning to support this feature on-chain but its not actually implemented. Currently there is no way for a user to set their fee asset)
<aside> 💡
The protocol allows a user to have both a cross and isolated position at the same time for same market. Both these positions are treated completely differently.
</aside>