DFlow
Search…
Market Making on DFlow
This page contains examples of how market makers can interact with the DFlow protocol.

Overview

Market makers can interact with the DFlow protocol in order to incorporate decentralized retail volume execution strategies into their market making.
In these examples, the market maker is able to use the DFlow SDK.

Creating an account

Market makers are required to have an on-chain account, which contains market maker-specific state, including a broadcasted RSA public key used for encryption of retail orders.
The structure of the market maker account is as follows.
#[account]
pub struct MarketMakerDataAccount {
pub owner: Pubkey,
pub rsa_public_key: String,
pub orders: Vec<Pubkey>,
pub max_orders_supported: u64,
pub fill_nonce: u64,
pub events_head: u8,
pub events: Vec<MarketMakerEvent>,
}
In the above, the orders field will contain a list of orders that are outstanding and that the market maker must fill. Each element of that orders field is a public key corresponding to an on-chain account that contains the order details of the retail trader.
async def create_account():
"""Create account and publish our RSA key"""
tx = await create_and_init_market_maker_account_tx(
dflow,
owner=market_maker.public_key,
max_orders_supported=100,
rsa_pub_key=rsa_public_key.save_pkcs1("PEM").decode("utf-8"),
)
await provider.send(tx, signers=[market_maker])

Searching for order flow contract auctions

Market makers can obtain a list of active order flow contract auctions by searching through an Auction Mapper account, which contains a single field that is strictly a list of non-expired auctions.
async def find_trading_auction():
"""Find an `AuctionState` that is trading.
This example shows how to iterate the `AuctionMapper` account
"""
# Get auction mapper, which lists all auctions
mapper = await get_auction_mapper(dflow)
auction_ids: List[int] = mapper.auction_ids
# Get the state of all available auction contracts
auction_states = await asyncio.gather(
*(get_auction_state_account(dflow, auction_id=i) for i in auction_ids)
)
# Find a `Trading` auction contract
AuctionStatus = dflow.type["AuctionStatus"]
return next(a for a in auction_states if a.status == AuctionStatus.Trading())

Submitting bids into an auction

In order to win the order flow contract auction, and begin receiving delivery of retail order flow, market makers must have the highest bid in an auction at the time the auction ends. In order to submit an auction bid, the market maker must have an auction ID and details of the auction.

Fetching an auction given an auction ID

Given an auction_id, the market maker must first fetch the auction state account:
state = await get_auction_state_account(dflow, auction_id=auction_id)
Once an auction state account has been retrieved by a given auction_id, the market maker can view state specific to that auction such as which tokens will be included in the retail order flow.

Viewing the tokens in the auction

for idx, currency_pair in enumerate(state.pairs):
quote: PublicKey = currency_pair.quote
base: PublicKey = currency_pair.base
print(
f"{[idx]}: quote_mint: {quote.to_base58()} / base_mint: {base.to_base58()}"
)

Viewing the auction leader

The market maker can also view who the current auction leader is.
auction_leader: Optional[PublicKey] = state.auction_leader

Submitting bids into an auction

If the auction_leader, an Optional value is not set, this means that no bids have been received in the current auction and the market maker will be the first market maker to bid in the auction. Bids will always be placed in the token specified by the bid_mint in the auction state account.
submit_bid_tx = Transaction()
# Initialize an auction epoch if we are the first bid
auction_leader: Optional[PublicKey] = state.auction_leader
if auction_leader is None:
epoch_ix = init_auction_epoch_state_account(
dflow,
auction_id=state.auction_id,
auction_epoch=state.epoch,
bid_mint_account=state.bid_mint,
market_maker_account_owner=market_maker.public_key,
)
submit_bid_tx.add(epoch_ix)
bid_ix = submit_auction_bid(
dflow,
auction_id=state.auction_id,
auction_epoch=state.epoch,
bid_size=250000,
bid_mint_account=state.bid_mint,
market_maker_data_account=get_market_maker_account_address(
dflow, owner=market_maker.public_key
),
market_maker_auction_token_account=None, # FIXME getAssociatedTokenAccount(...)
market_maker_account_owner=market_maker.public_key,
)
submit_bid_tx.add(bid_ix)
await provider.send(submit_bid_tx, signers=[market_maker])

Full example of bidding into an auction

A full example of the above code is shown below:
async def submit_bid(auction_id: int):
"""Submit a bid to an auction
This example shows how to check an auction's leader, initialize an auction
epoch, and bid on an auction.
"""
state = await get_auction_state_account(dflow, auction_id=auction_id)
for idx, currency_pair in enumerate(state.pairs):
quote: PublicKey = currency_pair.quote
base: PublicKey = currency_pair.base
print(
f"{[idx]}: quote_mint: {quote.to_base58()} / base_mint: {base.to_base58()}"
)
submit_bid_tx = Transaction()
# Initialize an auction epoch if we are the first bid
auction_leader: Optional[PublicKey] = state.auction_leader
if auction_leader is None:
epoch_ix = init_auction_epoch_state_account(
dflow,
auction_id=state.auction_id,
auction_epoch=state.epoch,
bid_mint_account=state.bid_mint,
market_maker_account_owner=market_maker.public_key,
)
submit_bid_tx.add(epoch_ix)
bid_ix = submit_auction_bid(
dflow,
auction_id=state.auction_id,
auction_epoch=state.epoch,
bid_size=250000,
bid_mint_account=state.bid_mint,
market_maker_data_account=get_market_maker_account_address(
dflow, owner=market_maker.public_key
),
market_maker_auction_token_account=None, # FIXME getAssociatedTokenAccount(...)
market_maker_account_owner=market_maker.public_key,
)
submit_bid_tx.add(bid_ix)
await provider.send(submit_bid_tx, signers=[market_maker])

Decrypting and Executing Retail Orders

Market makers are required to fill marketable orders immediately, and fill non-marketable orders as soon as they become marketable.

Fetching outstanding orders

Each market maker is required to initialize a market maker account which contains a list of orders. In order to view the orders in the market maker account, the market maker must fetch their account from the network.
market_maker_data = await get_market_maker_account_by_owner(
dflow, owner=market_maker.public_key
)
The list of outstanding orders to be filled will then be viewable.
orders: List[PublicKey] = market_maker_data.orders
In the above List, each element is a fetchable PublicKey address containing the details of the order.
vault_metas = await dflow.account["VaultMetaAccount"].fetch_multiple(orders)

Decrypting outstanding orders

All order details are encrypted with the market maker's public RSA key. The associated RSA private key will allow the market maker to decrypt the orders.
details = OrderDetails.decrypt(
vault_meta.encrypted_order_details, priv_key=rsa_private_key
)

Filling outstanding orders

Once decrypted, retail orders can be filled. Note that the decrypted details object contains the mint of the asset which the retail trader is receiving. That mint is encrypted to all other market makers, and only decryptable by the market maker who has won the auction.
x_token = AsyncToken(
client=provider.connection,
pubkey=vault_meta.x_mint,
program_id=TOKEN_PROGRAM_ID,
payer=None,
)
x_token.get_account_info(vault_meta.vault_token_account)
fill_ix = fill_order(
dflow,
retail_account_owner=vault_meta.retail_account_owner,
nonce=vault_meta.nonce,
market_maker_data_account_fill_nonce=market_maker_data.fill_nonce,
auction_id=vault_meta.auction_id,
auction_epoch=vault_meta.auction_epoch,
order_details=details.to_string(),
retail_sig=details.retail_sig,
fill_amount=1000,
fill_price=150000,
retail_data_account=vault_meta.retail_data_account,
market_maker_data_account=get_market_maker_account_address(
dflow, owner=market_maker.public_key
),
x_mint=vault_meta.x_mint,
y_mint=details.y_mint
)

Full example of fetching and filling outstanding orders

async def fill_orders():
"""Fill retail orders
This example shows how to check a `MarketMakerData` account for orders and
how to fill them.
"""
market_maker_data = await get_market_maker_account_by_owner(
dflow, owner=market_maker.public_key
)
orders: List[PublicKey] = market_maker_data.orders
if orders:
return
# Fetch the `VaultMetaAccount`, which contains order details
vault_metas = await dflow.account["VaultMetaAccount"].fetch_multiple(orders)
for vault_meta in filter(None, vault_metas):
details = OrderDetails.decrypt(
vault_meta.encrypted_order_details, priv_key=rsa_private_key
)
x_token = AsyncToken(
client=provider.connection,
pubkey=vault_meta.x_mint,
program_id=TOKEN_PROGRAM_ID,
payer=None,
)
x_token.get_account_info(vault_meta.vault_token_account)
fill_ix = fill_order(
dflow,
retail_account_owner=vault_meta.retail_account_owner,
nonce=vault_meta.nonce,
market_maker_data_account_fill_nonce=market_maker_data.fill_nonce,
auction_id=vault_meta.auction_id,
auction_epoch=vault_meta.auction_epoch,
order_details=details.to_string(),
retail_sig=details.retail_sig,
fill_amount=1000,
fill_price=150000,
retail_data_account=vault_meta.retail_data_account,
market_maker_data_account=get_market_maker_account_address(
dflow, owner=market_maker.public_key
),
x_mint=vault_meta.x_mint,
y_mint=details.y_mint
)
Last modified 3mo ago