server: openapi fixes

This commit is contained in:
nym21
2025-12-16 16:41:25 +01:00
parent 032f3cb66b
commit 593af69230
16 changed files with 215 additions and 145 deletions
+7 -9
View File
@@ -62,21 +62,19 @@ fn run() -> Result<()> {
.approximate_len()
);
let _ = dbg!(query.address(Address {
address: "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string(),
}));
let _ = dbg!(query.address(Address::from(
"bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string(),
)));
let _ = dbg!(query.address_txids(
Address {
address: "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string(),
},
Address::from("bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string()),
None,
25
));
let _ = dbg!(query.address_utxos(Address {
address: "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string(),
}));
let _ = dbg!(query.address_utxos(Address::from(
"bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string()
)));
// dbg!(query.search_and_format(MetricSelection {
// index: Index::Height,
+4 -4
View File
@@ -15,7 +15,7 @@ use crate::Query;
const MAX_MEMPOOL_TXIDS: usize = 50;
impl Query {
pub fn address(&self, Address { address }: Address) -> Result<AddressStats> {
pub fn address(&self, address: Address) -> Result<AddressStats> {
let indexer = self.indexer();
let computer = self.computer();
let stores = &indexer.stores;
@@ -74,7 +74,7 @@ impl Query {
};
Ok(AddressStats {
address: address.into(),
address,
chain_stats: AddressChainStats {
type_index,
funded_txo_count: address_data.funded_txo_count,
@@ -204,7 +204,7 @@ impl Query {
pub fn address_mempool_txids(&self, address: Address) -> Result<Vec<Txid>> {
let mempool = self.mempool().ok_or(Error::Str("Mempool not available"))?;
let bytes = AddressBytes::from_str(&address.address)?;
let bytes = AddressBytes::from_str(&address)?;
let addresses = mempool.get_addresses();
let txids: Vec<Txid> = addresses
@@ -219,7 +219,7 @@ impl Query {
fn resolve_address(&self, address: &Address) -> Result<(OutputType, TypeIndex)> {
let stores = &self.indexer().stores;
let bytes = AddressBytes::from_str(&address.address)?;
let bytes = AddressBytes::from_str(address)?;
let outputtype = OutputType::from(&bytes);
let hash = AddressHash::from(&bytes);
+1
View File
@@ -116,6 +116,7 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
},
|op| op
.metrics_tag()
// .path_param::<Metric>()
.summary("Search metrics")
.description("Fuzzy search for metrics by name. Supports partial matches and typos.")
.ok_response::<Vec<String>>()
+15 -15
View File
@@ -8,7 +8,7 @@ use axum::{
use brk_types::{
BlockCountPath, BlockFeeRatesEntry, BlockFeesEntry, BlockRewardsEntry, BlockSizesWeights,
DifficultyAdjustment, DifficultyAdjustmentEntry, HashrateSummary, PoolDetail, PoolInfo,
PoolSlugPath, PoolsSummary, RewardStats, TimePeriodPath,
PoolSlugPath, PoolsSummary, RewardStats, TimePeriod,
};
use crate::{CacheStrategy, extended::TransformResponseExtended};
@@ -61,8 +61,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/pools/{time_period}",
get_with(
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("{:?}", path.time_period)), move |q| q.mining_pools(path.time_period)).await
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("{:?}", time_period)), move |q| q.mining_pools(time_period)).await
},
|op| {
op.mining_tag()
@@ -110,8 +110,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/hashrate/{time_period}",
get_with(
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("hashrate-{:?}", path.time_period)), move |q| q.hashrate(Some(path.time_period))).await
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("hashrate-{:?}", time_period)), move |q| q.hashrate(Some(time_period))).await
},
|op| {
op.mining_tag()
@@ -142,8 +142,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/difficulty-adjustments/{time_period}",
get_with(
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("diff-adj-{:?}", path.time_period)), move |q| q.difficulty_adjustments(Some(path.time_period))).await
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("diff-adj-{:?}", time_period)), move |q| q.difficulty_adjustments(Some(time_period))).await
},
|op| {
op.mining_tag()
@@ -158,8 +158,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/blocks/fees/{time_period}",
get_with(
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("fees-{:?}", path.time_period)), move |q| q.block_fees(path.time_period)).await
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("fees-{:?}", time_period)), move |q| q.block_fees(time_period)).await
},
|op| {
op.mining_tag()
@@ -174,8 +174,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/blocks/rewards/{time_period}",
get_with(
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("rewards-{:?}", path.time_period)), move |q| q.block_rewards(path.time_period)).await
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("rewards-{:?}", time_period)), move |q| q.block_rewards(time_period)).await
},
|op| {
op.mining_tag()
@@ -190,8 +190,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/blocks/fee-rates/{time_period}",
get_with(
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("feerates-{:?}", path.time_period)), move |q| q.block_fee_rates(path.time_period)).await
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("feerates-{:?}", time_period)), move |q| q.block_fee_rates(time_period)).await
},
|op| {
op.mining_tag()
@@ -206,8 +206,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/blocks/sizes-weights/{time_period}",
get_with(
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("sizes-{:?}", path.time_period)), move |q| q.block_sizes_weights(path.time_period)).await
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("sizes-{:?}", time_period)), move |q| q.block_sizes_weights(time_period)).await
},
|op| {
op.mining_tag()
+5 -1
View File
@@ -51,7 +51,11 @@ impl ApiRoutes for ApiRouter<AppState> {
"/version",
get_with(
async |headers: HeaderMap, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Static, |_| Ok(env!("CARGO_PKG_VERSION"))).await
state
.cached_json(&headers, CacheStrategy::Static, |_| {
Ok(env!("CARGO_PKG_VERSION"))
})
.await
},
|op| {
op.server_tag()
+30 -17
View File
@@ -1,32 +1,49 @@
use std::{fmt, str::FromStr};
use std::{borrow::Cow, fmt, str::FromStr};
use bitcoin::ScriptBuf;
use brk_error::Error;
use derive_deref::Deref;
use schemars::JsonSchema;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::{Deserialize, Serialize, Serializer};
use crate::AddressBytes;
use super::OutputType;
#[derive(Debug, Deref, Deserialize, JsonSchema)]
pub struct Address {
/// Bitcoin address string
#[schemars(example = &"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")]
pub address: String,
/// Bitcoin address string
#[derive(Debug, Deref, Deserialize)]
pub struct Address(String);
impl JsonSchema for Address {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("Address")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "object",
"required": ["address"],
"properties": {
"address": {
"type": "string",
"description": "Bitcoin address string",
"examples": ["04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"]
}
}
})
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.address)
f.write_str(&self.0)
}
}
impl From<String> for Address {
#[inline]
fn from(address: String) -> Self {
Self { address }
Self(address)
}
}
@@ -41,9 +58,7 @@ impl TryFrom<(&ScriptBuf, OutputType)> for Address {
type Error = Error;
fn try_from((script, outputtype): (&ScriptBuf, OutputType)) -> Result<Self, Self::Error> {
if outputtype.is_address() {
Ok(Self {
address: script.to_hex_string(),
})
Ok(Self(script.to_hex_string()))
} else {
Err(Error::InvalidAddress)
}
@@ -78,7 +93,7 @@ impl Serialize for Address {
where
S: Serializer,
{
serializer.collect_str(&self.address)
serializer.collect_str(&self.0)
}
}
@@ -87,15 +102,13 @@ impl FromStr for Address {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let _ = AddressBytes::address_to_script(s)?;
Ok(Self {
address: s.to_string(),
})
Ok(Self(s.to_string()))
}
}
impl Address {
/// Get the script for this address
pub fn script(&self) -> Result<ScriptBuf, Error> {
AddressBytes::address_to_script(&self.address)
AddressBytes::address_to_script(&self.0)
}
}
+8 -3
View File
@@ -1,19 +1,24 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{de_unquote_i64, de_unquote_usize};
/// Range parameters for slicing data
#[derive(Default, Debug, Deserialize, JsonSchema)]
pub struct DataRange {
/// Inclusive starting index, if negative will be from the end
#[serde(default, alias = "f")]
#[serde(default, alias = "f", deserialize_with = "de_unquote_i64")]
#[schemars(example = 0, example = -1, example = -10, example = -1000)]
from: Option<i64>,
/// Exclusive ending index, if negative will be from the end, overrides 'count'
#[serde(default, alias = "t")]
#[serde(default, alias = "t", deserialize_with = "de_unquote_i64")]
#[schemars(example = 1000)]
to: Option<i64>,
/// Number of values requested
#[serde(default, alias = "c")]
#[serde(default, alias = "c", deserialize_with = "de_unquote_usize")]
#[schemars(example = 1, example = 10, example = 100)]
count: Option<usize>,
}
+52
View File
@@ -0,0 +1,52 @@
use serde::{Deserialize, Deserializer};
use serde_json::Value;
pub fn de_unquote_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
where
D: Deserializer<'de>,
{
let value: Option<Value> = Option::deserialize(deserializer)?;
if value.is_none() {
return Ok(None);
}
let value = value.unwrap();
if let Some(mut s) = value.as_str().map(|s| s.to_string()) {
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
s = s[1..s.len() - 1].to_string();
}
s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
} else if let Some(n) = value.as_i64() {
Ok(Some(n))
} else {
Err(serde::de::Error::custom("expected a string or number"))
}
}
pub fn de_unquote_usize<'de, D>(deserializer: D) -> Result<Option<usize>, D::Error>
where
D: Deserializer<'de>,
{
let value: Option<Value> = Option::deserialize(deserializer)?;
if value.is_none() {
return Ok(None);
}
let value = value.unwrap();
if let Some(mut s) = value.as_str().map(|s| s.to_string()) {
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
s = s[1..s.len() - 1].to_string();
}
s.parse::<usize>()
.map(Some)
.map_err(serde::de::Error::custom)
} else if let Some(n) = value.as_u64() {
Ok(Some(n as usize))
} else {
Err(serde::de::Error::custom("expected a string or number"))
}
}
+2 -28
View File
@@ -13,64 +13,38 @@ use super::{
SemesterIndex, TxIndex, UnknownOutputIndex, WeekIndex, YearIndex,
};
/// Aggregation dimension for querying Bitcoin blockchain data
/// Aggregation dimension for querying metrics. Includes time-based (date, week, month, year),
/// block-based (height, txindex), and address/output type indexes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
#[schemars(example = Index::DateIndex)]
pub enum Index {
/// Date/day index
DateIndex,
/// Decade index
DecadeIndex,
/// Difficulty epoch index (equivalent to ~2 weeks)
DifficultyEpoch,
/// Empty output index
EmptyOutputIndex,
/// Halving epoch index (equivalent to ~4 years)
HalvingEpoch,
/// Height/block index
Height,
/// Transaction input index (based on total)
TxInIndex,
/// Month index
MonthIndex,
/// Op return index
OpReturnIndex,
/// Transaction output index (based on total)
TxOutIndex,
/// Index of P2A address
P2AAddressIndex,
/// Index of P2MS output
P2MSOutputIndex,
/// Index of P2PK (33 bytes) address
P2PK33AddressIndex,
/// Index of P2PK (65 bytes) address
P2PK65AddressIndex,
/// Index of P2PKH address
P2PKHAddressIndex,
/// Index of P2SH address
P2SHAddressIndex,
/// Index of P2TR address
P2TRAddressIndex,
/// Index of P2WPKH address
P2WPKHAddressIndex,
/// Index of P2WSH address
P2WSHAddressIndex,
/// Quarter index
QuarterIndex,
/// Semester index
SemesterIndex,
/// Transaction index
TxIndex,
/// Unknown output index
UnknownOutputIndex,
/// Week index
WeekIndex,
/// Year index
YearIndex,
/// Loaded Address Index
LoadedAddressIndex,
/// Empty Address Index
EmptyAddressIndex,
}
+2
View File
@@ -39,6 +39,7 @@ mod datarangeformat;
mod date;
mod dateindex;
mod decadeindex;
mod deser;
mod difficultyadjustment;
mod difficultyadjustmententry;
mod difficultyentry;
@@ -187,6 +188,7 @@ pub use datarangeformat::*;
pub use date::*;
pub use dateindex::*;
pub use decadeindex::*;
pub use deser::*;
pub use difficultyadjustment::*;
pub use difficultyadjustmententry::*;
pub use difficultyentry::*;
+31 -18
View File
@@ -1,26 +1,39 @@
use std::borrow::Cow;
use derive_deref::Deref;
use schemars::JsonSchema;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::Deserialize;
#[derive(Debug, Deref, Deserialize, JsonSchema)]
pub struct Limit {
/// Maximum number of results to return. Defaults to 100 if not specified.
#[serde(default = "default_search_limit")]
#[schemars(
example = "1",
example = "10",
example = "100",
example = "1000",
example = "10000",
example = "100000"
)]
limit: usize,
}
/// Maximum number of results to return. Defaults to 100 if not specified.
#[derive(Debug, Deref, Deserialize)]
#[serde(default = "default_search_limit")]
pub struct Limit(usize);
impl Limit {
pub const MIN: Self = Self { limit: 1 };
pub const MIN: Self = Self(1);
pub const DEFAULT: Self = Self(100);
}
fn default_search_limit() -> usize {
100
fn default_search_limit() -> Limit {
Limit::DEFAULT
}
impl JsonSchema for Limit {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("Limit")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of results to return. Defaults to 100 if not specified.",
"default": 100,
"examples": [1, 10, 100, 1000, 10000, 100000]
}
}
})
}
}
+23 -13
View File
@@ -1,34 +1,44 @@
use std::fmt::Display;
use std::{borrow::Cow, fmt::Display};
use derive_deref::Deref;
use schemars::JsonSchema;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::Deserialize;
#[derive(Debug, Clone, Deref, Deserialize, JsonSchema)]
pub struct Metric {
/// Metric name
#[schemars(example = &"price_close", example = &"market_cap", example = &"realized_price")]
metric: String,
}
/// Metric name
#[derive(Debug, Clone, Deref, Deserialize)]
#[serde(transparent)]
pub struct Metric(String);
impl From<String> for Metric {
#[inline]
fn from(metric: String) -> Self {
Self { metric }
Self(metric)
}
}
impl From<&str> for Metric {
#[inline]
fn from(metric: &str) -> Self {
Self {
metric: metric.to_string(),
}
Self(metric.to_owned())
}
}
impl Display for Metric {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.metric)
write!(f, "{}", self.0)
}
}
impl JsonSchema for Metric {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("Metric")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"description": "Metric name",
"examples": ["price_close", "market_cap", "realized_price"]
})
}
}
+14 -17
View File
@@ -6,11 +6,10 @@ use serde::Deserialize;
use super::Metric;
/// A list of metrics
#[derive(Debug, Deref, JsonSchema)]
pub struct Metrics {
/// A list of metrics
metrics: Vec<Metric>,
}
#[schemars(transparent)]
pub struct Metrics(Vec<Metric>);
const MAX_VECS: usize = 32;
const MAX_STRING_SIZE: usize = 64 * MAX_VECS;
@@ -18,9 +17,7 @@ const MAX_STRING_SIZE: usize = 64 * MAX_VECS;
impl From<Metric> for Metrics {
#[inline]
fn from(metric: Metric) -> Self {
Self {
metrics: vec![metric],
}
Self(vec![metric])
}
}
@@ -34,12 +31,12 @@ impl From<String> for Metrics {
impl<'a> From<Vec<&'a str>> for Metrics {
#[inline]
fn from(value: Vec<&'a str>) -> Self {
Self {
metrics: value
Self(
value
.iter()
.map(|s| Metric::from(s.replace("-", "_").to_lowercase()))
.collect::<Vec<_>>(),
}
)
}
}
@@ -52,23 +49,23 @@ impl<'de> Deserialize<'de> for Metrics {
if let Some(str) = value.as_str() {
if str.len() <= MAX_STRING_SIZE {
Ok(Self {
metrics: sanitize(str.split(",").map(|s| s.to_string()))
Ok(Self(
sanitize(str.split(",").map(|s| s.to_string()))
.into_iter()
.map(Metric::from)
.collect(),
})
))
} else {
Err(serde::de::Error::custom("Given parameter is too long"))
}
} else if let Some(vec) = value.as_array() {
if vec.len() <= MAX_VECS {
Ok(Self {
metrics: sanitize(vec.iter().map(|s| s.as_str().unwrap().to_string()))
Ok(Self(
sanitize(vec.iter().map(|s| s.as_str().unwrap().to_string()))
.into_iter()
.map(Metric::from)
.collect(),
})
))
} else {
Err(serde::de::Error::custom("Given parameter is too long"))
}
@@ -81,7 +78,7 @@ impl<'de> Deserialize<'de> for Metrics {
impl fmt::Display for Metrics {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = self
.metrics
.0
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
+1 -2
View File
@@ -4,8 +4,7 @@ use serde::{Deserialize, Serialize};
use strum::Display;
use vecdb::{Bytes, Formattable};
// Created from the list in `pools.rs`
// Can be used as index for said list
// Slug of a mining pool
#[allow(clippy::upper_case_acronyms)]
#[derive(
Debug,
+19 -18
View File
@@ -5,34 +5,35 @@ use serde::{Deserialize, Serialize};
///
/// Used to specify the lookback window for pool statistics, hashrate calculations,
/// and other time-based mining metrics.
///
/// - `Day` (alias: 24h) - Last 24 hours (~144 blocks)
/// - `ThreeDays` (alias: 3d) - Last 3 days (~432 blocks)
/// - `Week` (alias: 1w) - Last week (~1008 blocks)
/// - `Month` (alias: 1m) - Last month (~4320 blocks)
/// - `ThreeMonths` (alias: 3m) - Last 3 months (~12960 blocks)
/// - `SixMonths` (alias: 6m) - Last 6 months (~25920 blocks)
/// - `Year` (alias: 1y) - Last year (~52560 blocks)
/// - `TwoYears` (alias: 2y) - Last 2 years (~105120 blocks)
/// - `ThreeYears` (alias: 3y) - Last 3 years (~157680 blocks)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum TimePeriod {
/// Last 24 hours (~144 blocks)
#[serde(rename = "24h")]
#[serde(alias = "24h")]
Day,
/// Last 3 days (~432 blocks)
#[serde(rename = "3d")]
#[serde(alias = "3d")]
ThreeDays,
/// Last week (~1008 blocks)
#[serde(rename = "1w")]
#[serde(alias = "1w")]
Week,
/// Last month (~4320 blocks)
#[serde(rename = "1m")]
#[serde(alias = "1m")]
Month,
/// Last 3 months (~12960 blocks)
#[serde(rename = "3m")]
#[serde(alias = "3m")]
ThreeMonths,
/// Last 6 months (~25920 blocks)
#[serde(rename = "6m")]
#[serde(alias = "6m")]
SixMonths,
/// Last year (~52560 blocks)
#[serde(rename = "1y")]
#[serde(alias = "1y")]
Year,
/// Last 2 years (~105120 blocks)
#[serde(rename = "2y")]
#[serde(alias = "2y")]
TwoYears,
/// Last 3 years (~157680 blocks)
#[serde(rename = "3y")]
#[serde(alias = "3y")]
ThreeYears,
}
+1
View File
@@ -14,6 +14,7 @@ use vecdb::{Bytes, Formattable};
example = "9a0b3b8305bb30cacf9e8443a90d53a76379fb3305047fdeaa4e4b0934a2a1ba"
)]
#[repr(C)]
#[schemars(transparent, with = "String")]
pub struct Txid([u8; 32]);
impl Txid {