You can get all markets by calling the getProgramAccounts RPC, filtering for accounts that match the market discriminant. Sample code available here.
Viewing the state of the order book
To get the state of a market's order book, call SDKClient.get_market_orderbook(&market_pubkey). The order book can be pretty-printed with orderbook.print_ladder().
let client = EllipsisClient::from_rpc(
RpcClient::new_with_commitment(
"https://api.mainnet-beta.solana.com".to_string(),
CommitmentConfig::confirmed(),
),
&payer,
)?;
let sdk_client = SDKClient::new_from_ellipsis_client(client).await?;
let sol_market = Pubkey::from_str("4DoNfFBfF7UokCC2FQzriy7yHK6DY6NVdYpuekQ5pRgg")?;
let orderbook = sdk_client.get_market_orderbook(&sol_market).await?;
orderbook.print_ladder(5, 4);
All market events are logged for easy data indexing and trader convenience. Events are recorded in instruction data via an authorized self-CPI to ensure no logs are dropped.
By listening to all transactions that touch a given market, one can create a stream of all market events as they are confirmed by the blockchain with a polling loop. For latency sensitive operations, look into running a Geyser Plugin.
let payer = Keypair::new();
let client = EllipsisClient::from_rpc(
RpcClient::new_with_commitment(
"https://api.mainnet-beta.solana.com".to_string(),
CommitmentConfig::confirmed(),
),
&payer,
)?;
let sdk_client = Arc::new(SDKClient::new_from_ellipsis_client(client).await?);
let sol_market = Pubkey::from_str("4DoNfFBfF7UokCC2FQzriy7yHK6DY6NVdYpuekQ5pRgg")?;
let mut until = None;
loop {
let config = match until {
None => GetConfirmedSignaturesForAddress2Config {
before: None,
until: None,
limit: Some(1),
commitment: Some(CommitmentConfig::confirmed()),
},
Some(until) => GetConfirmedSignaturesForAddress2Config {
before: None,
until: Some(until),
limit: None,
commitment: Some(CommitmentConfig::confirmed()),
},
};
let signatures = sdk_client
.client
.get_signatures_for_address_with_config(&sol_market, config)
.await
.unwrap_or_default()
.iter()
.map(|tx| Signature::from_str(&tx.signature).unwrap())
.rev()
.collect::<Vec<_>>();
if !signatures.is_empty() {
until = Some(signatures[0]);
}
let mut handles = vec![];
for signature in signatures {
let sdk = sdk_client.clone();
let handle =
tokio::spawn(
async move { sdk.parse_events_from_transaction(&signature).await }
);
handles.push(handle);
}
for handle in handles {
let events = handle.await?;
events.map(|events| {
events.iter().for_each(|e| {
// Here we only print the event, but in practice, you can do
// a lot more
println!("{:#?}", e);
});
});
}
// Note: this is a basic polling loop, if there are >1000 signatures in 200ms
// events will get dropped
tokio::time::sleep(Duration::from_millis(200)).await;
}
Directly call the send_ioc function exposed by the SDK.
In order to use these helper functions to send transactions, you must first add the market_key to an instance of the SDKClient with the add_market function
Alternatively, to batch multiple instructions into a single atomic transaction, use get_ioc_ix to create instructions and send them manually.
let ixs = ioc_orders_to_place
.iter()
.map(|(price, side, size)| sdk.get_ioc_ix(market_key, price, side, size))
.collect::<Vec>();
let signature = sdk
.client
.sign_send_instructions(ixs, vec![])
.await
.ok()?;
let fills = sdk.parse_fills(&signature).await;
Placing limit orders
Directly call the send_limit_order function exposed by the SDK. Alternatively, use get_limit_order_ix to create instructions and send them manually.
Canceling orders
Use send_cancel_ids, send_cancel_multiple, and send_cancel_all functions to cancel orders.
These functions will never fail if there are no orders to be found for the user who attempted to cancel. Instead the Phoenix program will skip over all orders that are missing or already filled.
This enables greater composability as the full transaction will never revert from a failed cancel attempt.
Place and cancel example
Here is an end-to-end example of how to use a generated client_order_id to send a limit order and find its corresponding exchange order ID. After sending the order, we immediately cancel it using the ID that we fetched from the previous step.
// Replace with a keypair that has SOL in it
// The corresponding pubkey must have a seat on the market
let payer = Keypair::new();
let client = EllipsisClient::from_rpc(
RpcClient::new_with_commitment(
"https://api.mainnet-beta.solana.com".to_string(),
CommitmentConfig::confirmed(),
),
&payer,
)?;
let mut sdk_client = SDKClient::new_from_ellipsis_client(client).await?;
let mut rng = StdRng::from_entropy();
let sol_market =
Pubkey::from_str("4DoNfFBfF7UokCC2FQzriy7yHK6DY6NVdYpuekQ5pRgg")?;
sdk_client.add_market(&sol_market).await?;
// Pulls the fair price for SOL/USD from the Coinbase API
let response = reqwest::get("https://api.coinbase.com/v2/prices/SOL-USD/spot")
.await?
.json::<serde_json::Value>()
.await?;
let sol_price = f64::from_str(response["data"]["amount"].as_str().unwrap())?;
let client_order_id = sdk_client.get_next_client_order_id(&mut rng);
let tick_price =
sdk_client.float_price_to_ticks_rounded_down(&sol_market, sol_price)?;
let base_lots_to_quote =
sdk_client.raw_base_units_to_base_lots_rounded_down(&sol_market, 0.01)?;
let place_ix = sdk_client.get_post_only_ix_from_tick_price(
&sol_market, // Market pubkey
tick_price, // Price of the order to place in ticks
Side::Bid, // Side of the order
base_lots_to_quote, // Number of base lots to quote
client_order_id, // Client order id (used to lookup the exchange order id)
false, // Will fail the transaction if the order crosses
)?;
// Returns the signature from the transaction to place limit order
let txid = sdk_client
.client
.sign_send_instructions(vec![place_ix], vec![])
.await?;
// Fetches the emitted events from the place order transaction
let phoenix_events = sdk_client
.parse_events_from_transaction(&txid)
.await
.ok_or_else(|| anyhow::anyhow!("No events found"))?;
// Iterates through the events and finds the exchange order id
// that matches the client order id
let mut order_id = None;
phoenix_events.iter().for_each(|e| match e.details {
MarketEventDetails::Place(ref place) => {
if place.client_order_id == client_order_id {
if order_id.is_some() {
// If you encounter a duplicate in general, you should handle it
// more gracefully
panic!("Duplicate order id");
}
order_id = Some(FIFOOrderId::new_from_untyped(
place.price_in_ticks,
place.order_sequence_number,
));
}
}
_ => unreachable!(),
});
println!("Client order id: {}", client_order_id);
println!("Order placed: {:#?}", order_id,);
// Immediately cancel the order that was just placed
let (_, reduce_events) = sdk_client
.send_cancel_ids(
&sol_market,
order_id.map(|o_id| vec![o_id]).unwrap_or_default(),
)
.await
.ok_or_else(|| anyhow::anyhow!("Failed to send cancel"))?;
for event in reduce_events {
println!("Reduce event: {:#?}", event);
}