use serde::{Deserialize, Deserializer};
use crate::client::Network;
use crate::client::http::{ApiError, Call, Transport, codes, read};
use crate::utils::ids::whole;
use crate::utils::sign::PublicKey;
use crate::utils::{AccountIndex, ApiKeyIndex};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Slot {
#[serde(rename = "account_index")]
pub account: AccountIndex,
#[serde(rename = "api_key_index")]
pub slot: ApiKeyIndex,
#[serde(deserialize_with = "whole")]
pub nonce: u64,
#[serde(deserialize_with = "registered")]
pub public_key: Option<PublicKey>,
#[serde(deserialize_with = "whole")]
pub transaction_time: u64,
}
impl Slot {
#[must_use]
pub fn holds(&self, key: &PublicKey) -> bool {
self.public_key.as_ref() == Some(key)
}
}
fn registered<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<PublicKey>, D::Error> {
let text = String::deserialize(deserializer)?;
match text.trim_start_matches("0x").bytes().all(|digit| digit == b'0') {
true => Ok(None),
false => PublicKey::from_hex(&text).map(Some).map_err(serde::de::Error::custom),
}
}
#[derive(Deserialize)]
struct Slots {
api_keys: Vec<Slot>,
}
#[derive(Deserialize)]
struct NextNonce {
#[serde(deserialize_with = "whole")]
nonce: u64,
}
#[must_use]
pub fn api_keys_call(account: AccountIndex, slot: Option<ApiKeyIndex>) -> Call {
Call::get("apikeys").field("account_index", account).field("api_key_index", slot.map_or(255, ApiKeyIndex::get))
}
pub async fn api_keys(transport: &impl Transport, account: AccountIndex) -> Result<Vec<Slot>, ApiError> {
listed(transport, api_keys_call(account, None)).await
}
pub async fn api_key(
transport: &impl Transport,
account: AccountIndex,
slot: ApiKeyIndex,
) -> Result<Option<Slot>, ApiError> {
let slots = listed(transport, api_keys_call(account, Some(slot))).await?;
Ok(slots.into_iter().find(|listed| listed.slot == slot))
}
async fn listed(transport: &impl Transport, call: Call) -> Result<Vec<Slot>, ApiError> {
match read::<Slots>(transport, call).await {
Ok(slots) => Ok(slots.api_keys),
Err(error) if error.code() == Some(codes::API_KEY_NOT_FOUND) => Ok(Vec::new()),
Err(error) => Err(error),
}
}
const LAST_SLOT: u8 = 254;
#[must_use]
pub fn pick_slot(
network: Network,
listed: &[Slot],
preferred: Option<(ApiKeyIndex, &PublicKey)>,
) -> Option<ApiKeyIndex> {
let holder = |slot: ApiKeyIndex| listed.iter().find(|one| one.slot == slot).and_then(|one| one.public_key);
let free = |slot: ApiKeyIndex| !network.reserves_api_key(slot);
if let Some((slot, key)) = preferred
&& free(slot)
&& holder(slot).is_none_or(|held| held == *key)
{
return Some(slot);
}
(0..=LAST_SLOT).filter_map(|index| ApiKeyIndex::new(index).ok()).find(|slot| free(*slot) && holder(*slot).is_none())
}
#[must_use]
pub fn next_nonce_call(account: AccountIndex, slot: ApiKeyIndex) -> Call {
Call::get("nextNonce").field("account_index", account).field("api_key_index", slot)
}
pub async fn next_nonce(transport: &impl Transport, account: AccountIndex, slot: ApiKeyIndex) -> Result<u64, ApiError> {
Ok(read::<NextNonce>(transport, next_nonce_call(account, slot)).await?.nonce)
}
#[cfg(test)]
#[path = "tests/slots.rs"]
mod tests;