mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-11 07:23:32 -07:00
server: documentation part 1
This commit is contained in:
Generated
+43
-5
@@ -514,6 +514,27 @@ dependencies = [
|
||||
"brk_structs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brk-aide"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b25c11ed19c06e037fb0f6ab12f218ec79ed53c7daa7a5b55865112e2320097f"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"http",
|
||||
"indexmap 2.11.4",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"thiserror 2.0.17",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brk-file-id"
|
||||
version = "0.2.3"
|
||||
@@ -710,7 +731,7 @@ dependencies = [
|
||||
name = "brk_mcp"
|
||||
version = "0.0.111"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"brk-aide",
|
||||
"brk_interface",
|
||||
"brk_rmcp",
|
||||
"log",
|
||||
@@ -1132,6 +1153,7 @@ dependencies = [
|
||||
"axum",
|
||||
"bitcoin",
|
||||
"bitcoincore-rpc",
|
||||
"brk-aide",
|
||||
"brk_computer",
|
||||
"brk_error",
|
||||
"brk_fetcher",
|
||||
@@ -1144,6 +1166,7 @@ dependencies = [
|
||||
"jiff",
|
||||
"log",
|
||||
"quick_cache",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
"sonic-rs 0.5.5",
|
||||
"tokio",
|
||||
@@ -1193,6 +1216,7 @@ dependencies = [
|
||||
"num_enum",
|
||||
"rapidhash",
|
||||
"ryu",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"strum",
|
||||
@@ -1939,9 +1963,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04bcaeafafdd3cd1cb5d986ff32096ad1136630207c49b9091e3ae541090d938"
|
||||
checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
@@ -4136,6 +4160,7 @@ checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"indexmap 2.11.4",
|
||||
"ref-cast",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
@@ -4305,6 +4330,19 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"futures",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.2"
|
||||
@@ -5042,9 +5080,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
|
||||
@@ -41,6 +41,7 @@ panic = "abort"
|
||||
debug-assertions = false
|
||||
|
||||
[workspace.dependencies]
|
||||
aide = { version = "0.15.1", features = ["axum-json"], package = "brk-aide" }
|
||||
allocative = { version = "0.3.4", features = ["parking_lot"] }
|
||||
axum = "0.8.6"
|
||||
bitcoin = { version = "0.32.7", features = ["serde"] }
|
||||
|
||||
@@ -1342,7 +1342,7 @@ impl Vecs {
|
||||
.height_to_supply
|
||||
.into_iter()
|
||||
.unwrap_get_inner(prev_height);
|
||||
state.supply.utxos = *self
|
||||
state.supply.utxo_count = *self
|
||||
.height_to_utxo_count
|
||||
.into_iter()
|
||||
.unwrap_get_inner(prev_height);
|
||||
@@ -1579,7 +1579,7 @@ impl Vecs {
|
||||
|
||||
self.height_to_utxo_count.forced_push_at(
|
||||
height,
|
||||
StoredU64::from(state.supply.utxos),
|
||||
StoredU64::from(state.supply.utxo_count),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -1899,7 +1899,7 @@ impl AddressTypeToVec<(TypeIndex, Sats)> {
|
||||
|
||||
let addressdata = addressdata_withsource.deref_mut();
|
||||
|
||||
let prev_amount = addressdata.amount();
|
||||
let prev_amount = addressdata.balance();
|
||||
|
||||
let amount = prev_amount + value;
|
||||
|
||||
@@ -2000,11 +2000,11 @@ impl HeightToAddressTypeToVec<(TypeIndex, Sats)> {
|
||||
|
||||
let addressdata = addressdata_withsource.deref_mut();
|
||||
|
||||
let prev_amount = addressdata.amount();
|
||||
let prev_amount = addressdata.balance();
|
||||
|
||||
let amount = prev_amount.checked_sub(value).unwrap();
|
||||
|
||||
let will_be_empty = addressdata.utxos - 1 == 0;
|
||||
let will_be_empty = addressdata.utxo_count - 1 == 0;
|
||||
|
||||
if will_be_empty
|
||||
|| vecs.amount_range.get_mut(amount).0.clone()
|
||||
|
||||
@@ -44,19 +44,22 @@ impl AddressCohortState {
|
||||
|
||||
let prev_realized_price = compute_price.then(|| addressdata.realized_price());
|
||||
let prev_supply_state = SupplyState {
|
||||
utxos: addressdata.utxos as u64,
|
||||
value: addressdata.amount(),
|
||||
utxo_count: addressdata.utxo_count as u64,
|
||||
value: addressdata.balance(),
|
||||
};
|
||||
|
||||
addressdata.send(value, prev_price)?;
|
||||
|
||||
let supply_state = SupplyState {
|
||||
utxos: addressdata.utxos as u64,
|
||||
value: addressdata.amount(),
|
||||
utxo_count: addressdata.utxo_count as u64,
|
||||
value: addressdata.balance(),
|
||||
};
|
||||
|
||||
self.inner.send_(
|
||||
&SupplyState { utxos: 1, value },
|
||||
&SupplyState {
|
||||
utxo_count: 1,
|
||||
value,
|
||||
},
|
||||
current_price,
|
||||
prev_price,
|
||||
blocks_old,
|
||||
@@ -79,19 +82,22 @@ impl AddressCohortState {
|
||||
|
||||
let prev_realized_price = compute_price.then(|| address_data.realized_price());
|
||||
let prev_supply_state = SupplyState {
|
||||
utxos: address_data.utxos as u64,
|
||||
value: address_data.amount(),
|
||||
utxo_count: address_data.utxo_count as u64,
|
||||
value: address_data.balance(),
|
||||
};
|
||||
|
||||
address_data.receive(value, price);
|
||||
|
||||
let supply_state = SupplyState {
|
||||
utxos: address_data.utxos as u64,
|
||||
value: address_data.amount(),
|
||||
utxo_count: address_data.utxo_count as u64,
|
||||
value: address_data.balance(),
|
||||
};
|
||||
|
||||
self.inner.receive_(
|
||||
&SupplyState { utxos: 1, value },
|
||||
&SupplyState {
|
||||
utxo_count: 1,
|
||||
value,
|
||||
},
|
||||
price,
|
||||
compute_price.then(|| (address_data.realized_price(), &supply_state)),
|
||||
prev_realized_price.map(|prev_price| (prev_price, &prev_supply_state)),
|
||||
|
||||
@@ -204,7 +204,7 @@ impl CohortState {
|
||||
price_to_amount_increment: Option<(Dollars, &SupplyState)>,
|
||||
price_to_amount_decrement: Option<(Dollars, &SupplyState)>,
|
||||
) {
|
||||
if supply_state.utxos == 0 {
|
||||
if supply_state.utxo_count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
#[derive(Debug, Default, Clone, FromBytes, Immutable, IntoBytes, KnownLayout, Serialize)]
|
||||
pub struct SupplyState {
|
||||
pub utxos: u64,
|
||||
pub utxo_count: u64,
|
||||
pub value: Sats,
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ impl Add<SupplyState> for SupplyState {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: SupplyState) -> Self::Output {
|
||||
Self {
|
||||
utxos: self.utxos + rhs.utxos,
|
||||
utxo_count: self.utxo_count + rhs.utxo_count,
|
||||
value: self.value + rhs.value,
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,14 @@ impl AddAssign<SupplyState> for SupplyState {
|
||||
|
||||
impl AddAssign<&SupplyState> for SupplyState {
|
||||
fn add_assign(&mut self, rhs: &Self) {
|
||||
self.utxos += rhs.utxos;
|
||||
self.utxo_count += rhs.utxo_count;
|
||||
self.value += rhs.value;
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<&SupplyState> for SupplyState {
|
||||
fn sub_assign(&mut self, rhs: &Self) {
|
||||
self.utxos = self.utxos.checked_sub(rhs.utxos).unwrap();
|
||||
self.utxo_count = self.utxo_count.checked_sub(rhs.utxo_count).unwrap();
|
||||
self.value = self.value.checked_sub(rhs.value).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,14 @@ impl SubAssign<&SupplyState> for SupplyState {
|
||||
impl From<&LoadedAddressData> for SupplyState {
|
||||
fn from(value: &LoadedAddressData) -> Self {
|
||||
Self {
|
||||
utxos: value.utxos as u64,
|
||||
value: value.amount(),
|
||||
utxo_count: value.utxo_count as u64,
|
||||
value: value.balance(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SupplyState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "utxos: {}, value: {}", self.utxos, self.value)
|
||||
write!(f, "utxos: {}, value: {}", self.utxo_count, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ pub struct Transacted {
|
||||
impl Transacted {
|
||||
#[allow(clippy::inconsistent_digit_grouping)]
|
||||
pub fn iterate(&mut self, value: Sats, _type: OutputType) {
|
||||
let supply = SupplyState { utxos: 1, value };
|
||||
let supply = SupplyState {
|
||||
utxo_count: 1,
|
||||
value,
|
||||
};
|
||||
|
||||
*self.by_type.get_mut(_type) += &supply;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ rust-version.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
axum = { workspace = true }
|
||||
aide = { workspace = true }
|
||||
brk_interface = { workspace = true }
|
||||
brk_rmcp = { version = "0.8.0", features = [
|
||||
"transport-worker",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::Router;
|
||||
use aide::axum::ApiRouter;
|
||||
use brk_interface::Interface;
|
||||
use brk_rmcp::transport::{
|
||||
StreamableHttpServerConfig,
|
||||
@@ -13,7 +13,7 @@ pub trait MCPRoutes {
|
||||
fn add_mcp_routes(self, interface: &'static Interface<'static>, mcp: bool) -> Self;
|
||||
}
|
||||
|
||||
impl<T> MCPRoutes for Router<T>
|
||||
impl<T> MCPRoutes for ApiRouter<T>
|
||||
where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ rust-version.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
aide = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
bitcoin = { workspace = true }
|
||||
bitcoincore-rpc = { workspace = true }
|
||||
@@ -26,7 +27,9 @@ vecdb = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
log = { workspace = true }
|
||||
quick_cache = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
# serde_json = { workspace = true }
|
||||
sonic-rs = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tower-http = { version = "0.6.6", features = ["compression-full", "trace"] }
|
||||
|
||||
@@ -4,19 +4,27 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use aide::{
|
||||
axum::{ApiRouter, IntoApiResponse, routing::get_with},
|
||||
transform::TransformOperation,
|
||||
};
|
||||
use axum::{
|
||||
Json, Router,
|
||||
Json,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
};
|
||||
use bitcoin::{Address, Network, Transaction, consensus::Decodable};
|
||||
use bitcoin::{
|
||||
Address as BitcoinAddress, Network, Transaction as BitcoinTransaction, consensus::Decodable,
|
||||
};
|
||||
use brk_parser::XORIndex;
|
||||
use brk_structs::{
|
||||
AddressBytesHash, AnyAddressDataIndexEnum, Bitcoin, OutputType, TxIndex, Txid, TxidPrefix,
|
||||
AddressBytesHash, AnyAddressDataIndexEnum, Bitcoin, Dollars, OutputType, Sats, TxIndex, Txid,
|
||||
TxidPrefix, TypeIndex,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use sonic_rs::{Number, Value};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{AnyIterableVec, VecIterator};
|
||||
|
||||
use super::AppState;
|
||||
@@ -25,127 +33,244 @@ pub trait ApiExplorerRoutes {
|
||||
fn add_api_explorer_routes(self) -> Self;
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
struct AddressDetails {
|
||||
/// Bitcoin address string
|
||||
address: String,
|
||||
|
||||
r#type: OutputType,
|
||||
|
||||
type_index: TypeIndex,
|
||||
|
||||
/// Total satoshis ever sent from this address
|
||||
total_sent: Sats,
|
||||
|
||||
/// Total satoshis ever received by this address
|
||||
total_received: Sats,
|
||||
|
||||
/// Number of unspent transaction outputs (UTXOs)
|
||||
utxo_count: u32,
|
||||
|
||||
/// Current spendable balance in satoshis (total_received - total_sent)
|
||||
balance: Sats,
|
||||
|
||||
/// Current balance value in USD at current market price
|
||||
balance_usd: Option<Dollars>,
|
||||
|
||||
/// Estimated total USD value at time of deposit for coins currently in this address (not including coins that were later sent out). Not suitable for tax calculations
|
||||
estimated_total_invested: Option<Dollars>,
|
||||
|
||||
/// Estimated average BTC price at time of deposit for coins currently in this address (USD). Not suitable for tax calculations
|
||||
estimated_avg_entry_price: Option<Dollars>,
|
||||
//
|
||||
// Transaction count?
|
||||
// First/last activity timestamps?
|
||||
// Realized/unrealized gains?
|
||||
// Current value (balance × current price)?
|
||||
// "address": address,
|
||||
// "type": output_type,
|
||||
// "index": addri,
|
||||
// "chain_stats": {
|
||||
// "funded_txo_count": null,
|
||||
// "funded_txo_sum": addr_data.received,
|
||||
// "spent_txo_count": null,
|
||||
// "spent_txo_sum": addr_data.sent,
|
||||
// "utxo_count": addr_data.utxos,
|
||||
// "balance": amount,
|
||||
// "balance_usd": price.map_or(Value::new(), |p| {
|
||||
// Value::from(Number::from_f64(*(p * Bitcoin::from(amount))).unwrap())
|
||||
// }),
|
||||
// "realized_value": addr_data.realized_cap,
|
||||
// "tx_count": null,
|
||||
// "avg_cost_basis": addr_data.realized_price()
|
||||
// },
|
||||
// "mempool_stats": null
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TxResponse {
|
||||
txid: Txid,
|
||||
index: TxIndex,
|
||||
tx: Transaction,
|
||||
tx: BitcoinTransaction,
|
||||
}
|
||||
|
||||
impl ApiExplorerRoutes for Router<AppState> {
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
struct AddressPath {
|
||||
/// Bitcoin address string
|
||||
address: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
struct TypeIndexPath {
|
||||
/// Index of type
|
||||
index: TypeIndex,
|
||||
}
|
||||
|
||||
async fn get_address_details_from_address(
|
||||
Path(AddressPath { address }): Path<AddressPath>,
|
||||
state: State<AppState>,
|
||||
) -> impl IntoApiResponse {
|
||||
let Ok(address) = BitcoinAddress::from_str(&address) else {
|
||||
return StatusCode::BAD_REQUEST.into_response();
|
||||
};
|
||||
if !address.is_valid_for_network(Network::Bitcoin) {
|
||||
return StatusCode::BAD_REQUEST.into_response();
|
||||
}
|
||||
let address = address.assume_checked();
|
||||
let interface = state.interface;
|
||||
let indexer = interface.indexer();
|
||||
let computer = interface.computer();
|
||||
let stores = &indexer.stores;
|
||||
let hash = AddressBytesHash::from(&address);
|
||||
|
||||
let r#type = OutputType::from(&address);
|
||||
|
||||
let Ok(Some(type_index)) = stores
|
||||
.addressbyteshash_to_typeindex
|
||||
.get(&hash)
|
||||
.map(|opt| opt.map(|cow| cow.into_owned()))
|
||||
else {
|
||||
return StatusCode::NOT_FOUND.into_response();
|
||||
};
|
||||
|
||||
let stateful = &computer.stateful;
|
||||
let price = computer.price.as_ref().map(|v| {
|
||||
*v.timeindexes_to_price_close
|
||||
.dateindex
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.1
|
||||
.into_owned()
|
||||
});
|
||||
|
||||
let any_address_index = match r#type {
|
||||
OutputType::P2PK33 => stateful
|
||||
.p2pk33addressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(type_index.into()),
|
||||
OutputType::P2PK65 => stateful
|
||||
.p2pk65addressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(type_index.into()),
|
||||
OutputType::P2PKH => stateful
|
||||
.p2pkhaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(type_index.into()),
|
||||
OutputType::P2SH => stateful
|
||||
.p2shaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(type_index.into()),
|
||||
OutputType::P2TR => stateful
|
||||
.p2traddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(type_index.into()),
|
||||
OutputType::P2WPKH => stateful
|
||||
.p2wpkhaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(type_index.into()),
|
||||
OutputType::P2WSH => stateful
|
||||
.p2wshaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(type_index.into()),
|
||||
OutputType::P2A => stateful
|
||||
.p2aaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(type_index.into()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let address_data = match any_address_index.to_enum() {
|
||||
AnyAddressDataIndexEnum::Loaded(index) => stateful
|
||||
.loadedaddressindex_to_loadedaddressdata
|
||||
.iter()
|
||||
.unwrap_get_inner(index),
|
||||
AnyAddressDataIndexEnum::Empty(index) => stateful
|
||||
.emptyaddressindex_to_emptyaddressdata
|
||||
.iter()
|
||||
.unwrap_get_inner(index)
|
||||
.into(),
|
||||
};
|
||||
|
||||
let balance = address_data.balance();
|
||||
|
||||
Json(AddressDetails {
|
||||
address: address.to_string(),
|
||||
r#type,
|
||||
type_index,
|
||||
utxo_count: address_data.utxo_count,
|
||||
total_sent: address_data.sent,
|
||||
total_received: address_data.received,
|
||||
balance,
|
||||
balance_usd: price.map(|p| p * Bitcoin::from(balance)),
|
||||
estimated_total_invested: price.map(|_| address_data.realized_cap),
|
||||
estimated_avg_entry_price: price.map(|_| address_data.realized_price()),
|
||||
})
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn get_address_docs(op: TransformOperation) -> TransformOperation {
|
||||
op.tag("Chain")
|
||||
.summary("Get address information")
|
||||
.description("Get Bitcoin address details")
|
||||
.response_with::<200, Json<AddressDetails>, _>(|res| {
|
||||
res.example(AddressDetails {
|
||||
address: "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38"
|
||||
.to_string(),
|
||||
r#type: OutputType::P2WSH,
|
||||
type_index: TypeIndex::new(26158889),
|
||||
total_sent: Sats::new(5498948012620),
|
||||
total_received: Sats::new(5557954331207),
|
||||
utxo_count: 195,
|
||||
balance: Sats::new(59006318587),
|
||||
balance_usd: Some(Dollars::mint(73757839.22)),
|
||||
estimated_total_invested: Some(Dollars::mint(71943052.66)),
|
||||
estimated_avg_entry_price: Some(Dollars::mint(121900.0)),
|
||||
})
|
||||
})
|
||||
.response_with::<400, (), _>(|res| res.description("The address provided was invalid"))
|
||||
.response_with::<404, (), _>(|res| res.description("The address provided was not found"))
|
||||
}
|
||||
|
||||
impl ApiExplorerRoutes for ApiRouter<AppState> {
|
||||
fn add_api_explorer_routes(self) -> Self {
|
||||
self.route(
|
||||
self.api_route(
|
||||
"/api/chain/address/{address}",
|
||||
get(
|
||||
async |Path(address): Path<String>, state: State<AppState>| -> Response {
|
||||
let Ok(address) = Address::from_str(&address) else {
|
||||
return "Invalid address".into_response();
|
||||
};
|
||||
if !address.is_valid_for_network(Network::Bitcoin) {
|
||||
return "Invalid address".into_response();
|
||||
}
|
||||
let address = address.assume_checked();
|
||||
let interface = state.interface;
|
||||
let indexer = interface.indexer();
|
||||
let computer = interface.computer();
|
||||
let stores = &indexer.stores;
|
||||
let hash = AddressBytesHash::from(&address);
|
||||
|
||||
let Ok(Some(addri)) = stores
|
||||
.addressbyteshash_to_typeindex
|
||||
.get(&hash)
|
||||
.map(|opt| opt.map(|cow| cow.into_owned()))
|
||||
else {
|
||||
return "Unknown address".into_response();
|
||||
};
|
||||
|
||||
let output_type = OutputType::from(&address);
|
||||
let stateful = &computer.stateful;
|
||||
let price = computer.price.as_ref().map(|v| {
|
||||
*v.timeindexes_to_price_close
|
||||
.dateindex
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.1
|
||||
.into_owned()
|
||||
});
|
||||
|
||||
let anyaddri = match output_type {
|
||||
OutputType::P2PK33 => stateful
|
||||
.p2pk33addressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2PK65 => stateful
|
||||
.p2pk65addressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2PKH => stateful
|
||||
.p2pkhaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2SH => stateful
|
||||
.p2shaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2TR => stateful
|
||||
.p2traddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2WPKH => stateful
|
||||
.p2wpkhaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2WSH => stateful
|
||||
.p2wshaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
OutputType::P2A => stateful
|
||||
.p2aaddressindex_to_anyaddressindex
|
||||
.iter()
|
||||
.unwrap_get_inner(addri.into()),
|
||||
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let addr_data = match anyaddri.to_enum() {
|
||||
AnyAddressDataIndexEnum::Loaded(loadedi) => stateful
|
||||
.loadedaddressindex_to_loadedaddressdata
|
||||
.iter()
|
||||
.unwrap_get_inner(loadedi),
|
||||
AnyAddressDataIndexEnum::Empty(emptyi) => stateful
|
||||
.emptyaddressindex_to_emptyaddressdata
|
||||
.iter()
|
||||
.unwrap_get_inner(emptyi)
|
||||
.into(),
|
||||
};
|
||||
|
||||
let amount = addr_data.amount();
|
||||
Json(sonic_rs::json!({
|
||||
"address": address,
|
||||
"type": output_type,
|
||||
"index": addri,
|
||||
"chain_stats": {
|
||||
"funded_txo_count": null,
|
||||
"funded_txo_sum": addr_data.received,
|
||||
"spent_txo_count": null,
|
||||
"spent_txo_sum": addr_data.sent,
|
||||
"utxo_count": addr_data.utxos,
|
||||
"balance": amount,
|
||||
"balance_usd": price.map_or(Value::new(), |p| {
|
||||
Value::from(Number::from_f64(*(p * Bitcoin::from(amount))).unwrap())
|
||||
}),
|
||||
"realized_value": addr_data.realized_cap,
|
||||
"tx_count": null,
|
||||
"avg_cost_basis": addr_data.realized_price()
|
||||
},
|
||||
"mempool_stats": null
|
||||
}))
|
||||
.into_response()
|
||||
},
|
||||
),
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.api_route(
|
||||
"/api/chain/address/p2pk65/{index}",
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.api_route(
|
||||
"/api/chain/address/p2pk33/{index}",
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.api_route(
|
||||
"/api/chain/address/p2pkh/{index}",
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.api_route(
|
||||
"/api/chain/address/p2sh/{index}",
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.api_route(
|
||||
"/api/chain/address/p2wpkh/{index}",
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.api_route(
|
||||
"/api/chain/address/p2wsh/{index}",
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.api_route(
|
||||
"/api/chain/address/p2tr/{index}",
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.api_route(
|
||||
"/api/chain/address/p2a/{index}",
|
||||
get_with(get_address_details_from_address, get_address_docs),
|
||||
)
|
||||
.route(
|
||||
"/api/chain/tx/{txid}",
|
||||
@@ -215,7 +340,7 @@ impl ApiExplorerRoutes for Router<AppState> {
|
||||
xori.bytes(&mut buffer, parser.xor_bytes());
|
||||
|
||||
let mut reader = Cursor::new(buffer);
|
||||
let Ok(tx) = Transaction::consensus_decode(&mut reader) else {
|
||||
let Ok(tx) = BitcoinTransaction::consensus_decode(&mut reader) else {
|
||||
return "Error decoding transaction".into_response();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use aide::axum::ApiRouter;
|
||||
use axum::{
|
||||
Json, Router,
|
||||
Json,
|
||||
extract::{Path, Query, State},
|
||||
http::{HeaderMap, Uri},
|
||||
response::{IntoResponse, Response},
|
||||
@@ -22,7 +23,7 @@ pub trait ApiMetricsRoutes {
|
||||
|
||||
const TO_SEPARATOR: &str = "_to_";
|
||||
|
||||
impl ApiMetricsRoutes for Router<AppState> {
|
||||
impl ApiMetricsRoutes for ApiRouter<AppState> {
|
||||
fn add_api_metrics_routes(self) -> Self {
|
||||
self.route(
|
||||
"/api/metrics/count",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use axum::{Router, response::Redirect, routing::get};
|
||||
use aide::axum::ApiRouter;
|
||||
use axum::{response::Html, routing::get};
|
||||
|
||||
use crate::api::{chain::ApiExplorerRoutes, metrics::ApiMetricsRoutes};
|
||||
|
||||
@@ -11,17 +12,10 @@ pub trait ApiRoutes {
|
||||
fn add_api_routes(self) -> Self;
|
||||
}
|
||||
|
||||
impl ApiRoutes for Router<AppState> {
|
||||
impl ApiRoutes for ApiRouter<AppState> {
|
||||
fn add_api_routes(self) -> Self {
|
||||
self.add_api_explorer_routes()
|
||||
.add_api_metrics_routes()
|
||||
.route(
|
||||
"/api",
|
||||
get(|| async {
|
||||
Redirect::temporary(
|
||||
"https://github.com/bitcoinresearchkit/brk/tree/main/crates/brk_server#api",
|
||||
)
|
||||
}),
|
||||
)
|
||||
.route("/api", get(Html::from(include_str!("./scalar.html"))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Scalar API Reference</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- https://app.unpkg.com/@scalar/api-reference@1.37.0/files/dist/browser/standalone.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
|
||||
<script>
|
||||
Scalar.createApiReference("#app", {
|
||||
url: "/api.json",
|
||||
metaData: {
|
||||
title: "BRK API",
|
||||
description: "Bitcoin Research Kit's API documentation via Scalar",
|
||||
hideClientButton: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use axum::{Router, routing::get};
|
||||
use aide::axum::ApiRouter;
|
||||
use axum::routing::get;
|
||||
|
||||
use super::AppState;
|
||||
|
||||
@@ -12,7 +13,7 @@ pub trait FilesRoutes {
|
||||
fn add_files_routes(self, path: Option<&PathBuf>) -> Self;
|
||||
}
|
||||
|
||||
impl FilesRoutes for Router<AppState> {
|
||||
impl FilesRoutes for ApiRouter<AppState> {
|
||||
fn add_files_routes(self, path: Option<&PathBuf>) -> Self {
|
||||
if path.is_some() {
|
||||
self.route("/{*path}", get(file_handler))
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use aide::{
|
||||
axum::{ApiRouter, IntoApiResponse},
|
||||
openapi::{Info, OpenApi},
|
||||
};
|
||||
use api::ApiRoutes;
|
||||
use axum::{
|
||||
Json, Router,
|
||||
Extension, Json,
|
||||
body::{Body, Bytes},
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
middleware::Next,
|
||||
@@ -94,11 +98,11 @@ impl Server {
|
||||
.on_failure(())
|
||||
.on_eos(());
|
||||
|
||||
let router = Router::new()
|
||||
let router = ApiRouter::new()
|
||||
.add_api_routes()
|
||||
.add_files_routes(state.path.as_ref())
|
||||
.add_mcp_routes(state.interface, mcp)
|
||||
.route("/version", get(Json(VERSION)))
|
||||
.api_route("/version", aide::axum::routing::get(version))
|
||||
.route(
|
||||
"/health",
|
||||
get(Json(sonic_rs::json!({
|
||||
@@ -126,6 +130,7 @@ impl Server {
|
||||
get(Redirect::temporary("https://github.com/bitcoinresearchkit/brk?tab=readme-ov-file#hosting-as-a-service")),
|
||||
)
|
||||
.route("/nostr", get(Redirect::temporary("https://primal.net/p/npub1jagmm3x39lmwfnrtvxcs9ac7g300y3dusv9lgzhk2e4x5frpxlrqa73v44")))
|
||||
.route("/api.json", get(serve_api))
|
||||
.with_state(state)
|
||||
.layer(compression_layer)
|
||||
.layer(response_uri_layer)
|
||||
@@ -142,12 +147,36 @@ impl Server {
|
||||
port += 1;
|
||||
}
|
||||
|
||||
let mut api = OpenApi {
|
||||
info: Info {
|
||||
title: "Bitcoin Research Kit API".to_string(),
|
||||
description: Some("A documentation for using BRK's API".to_string()),
|
||||
..Info::default()
|
||||
},
|
||||
..OpenApi::default()
|
||||
};
|
||||
|
||||
info!("Starting server on port {port}...");
|
||||
|
||||
let listener = listener.unwrap();
|
||||
|
||||
serve(listener, router).await?;
|
||||
serve(
|
||||
listener,
|
||||
router
|
||||
.finish_api(&mut api)
|
||||
.layer(Extension(Arc::new(api)))
|
||||
.into_make_service(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_api(Extension(api): Extension<Arc<OpenApi>>) -> impl IntoApiResponse {
|
||||
Json(api)
|
||||
}
|
||||
|
||||
async fn version() -> impl IntoApiResponse {
|
||||
Json(VERSION)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ jiff = { workspace = true }
|
||||
num_enum = "0.7.4"
|
||||
rapidhash = "4.1.0"
|
||||
ryu = "1.0.20"
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_bytes = { workspace = true }
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::{
|
||||
|
||||
use allocative::Allocative;
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, StoredCompressed};
|
||||
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
@@ -29,6 +30,7 @@ use super::{Bitcoin, Cents, Close, High, Sats, StoredF32, StoredF64};
|
||||
Deserialize,
|
||||
StoredCompressed,
|
||||
Allocative,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct Dollars(f64);
|
||||
|
||||
|
||||
@@ -11,23 +11,23 @@ pub struct LoadedAddressData {
|
||||
pub sent: Sats,
|
||||
pub received: Sats,
|
||||
pub realized_cap: Dollars,
|
||||
pub utxos: u32,
|
||||
pub utxo_count: u32,
|
||||
#[serde(skip)]
|
||||
padding: u32,
|
||||
}
|
||||
|
||||
impl LoadedAddressData {
|
||||
pub fn amount(&self) -> Sats {
|
||||
pub fn balance(&self) -> Sats {
|
||||
(u64::from(self.received) - u64::from(self.sent)).into()
|
||||
}
|
||||
|
||||
pub fn realized_price(&self) -> Dollars {
|
||||
let p = (self.realized_cap / Bitcoin::from(self.amount())).round_to(4);
|
||||
let p = (self.realized_cap / Bitcoin::from(self.balance())).round_to(4);
|
||||
if p.is_negative() {
|
||||
dbg!((
|
||||
self.realized_cap,
|
||||
self.amount(),
|
||||
Bitcoin::from(self.amount()),
|
||||
self.balance(),
|
||||
Bitcoin::from(self.balance()),
|
||||
p
|
||||
));
|
||||
panic!("");
|
||||
@@ -37,17 +37,17 @@ impl LoadedAddressData {
|
||||
|
||||
#[inline]
|
||||
pub fn has_0_sats(&self) -> bool {
|
||||
self.amount() == Sats::ZERO
|
||||
self.balance() == Sats::ZERO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_0_utxos(&self) -> bool {
|
||||
self.utxos == 0
|
||||
self.utxo_count == 0
|
||||
}
|
||||
|
||||
pub fn receive(&mut self, amount: Sats, price: Option<Dollars>) {
|
||||
self.received += amount;
|
||||
self.utxos += 1;
|
||||
self.utxo_count += 1;
|
||||
if let Some(price) = price {
|
||||
let added = price * amount;
|
||||
self.realized_cap += added;
|
||||
@@ -59,11 +59,11 @@ impl LoadedAddressData {
|
||||
}
|
||||
|
||||
pub fn send(&mut self, amount: Sats, previous_price: Option<Dollars>) -> Result<()> {
|
||||
if self.amount() < amount {
|
||||
if self.balance() < amount {
|
||||
return Err(Error::Str("Previous_amount smaller than sent amount"));
|
||||
}
|
||||
self.sent += amount;
|
||||
self.utxos -= 1;
|
||||
self.utxo_count -= 1;
|
||||
if let Some(previous_price) = previous_price {
|
||||
let subtracted = previous_price * amount;
|
||||
let realized_cap = self.realized_cap.checked_sub(subtracted).unwrap();
|
||||
@@ -96,7 +96,7 @@ impl From<&EmptyAddressData> for LoadedAddressData {
|
||||
sent: value.transfered,
|
||||
received: value.transfered,
|
||||
realized_cap: Dollars::ZERO,
|
||||
utxos: 0,
|
||||
utxo_count: 0,
|
||||
padding: 0,
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ impl std::fmt::Display for LoadedAddressData {
|
||||
write!(
|
||||
f,
|
||||
"sent: {}, received: {}, realized_cap: {}, utxos: {}",
|
||||
self.sent, self.received, self.realized_cap, self.utxos
|
||||
self.sent, self.received, self.realized_cap, self.utxo_count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use bitcoin::{Address, AddressType, ScriptBuf, opcodes::all::OP_PUSHBYTES_2};
|
||||
use brk_error::Error;
|
||||
use serde::Serialize;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
@@ -18,10 +19,12 @@ use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
Serialize,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[repr(u8)]
|
||||
/// Type (P2PKH, P2WPKH, P2SH, P2TR, etc.)
|
||||
pub enum OutputType {
|
||||
P2PK65,
|
||||
P2PK33,
|
||||
@@ -33,249 +36,493 @@ pub enum OutputType {
|
||||
P2WSH,
|
||||
P2TR,
|
||||
P2A,
|
||||
#[schemars(skip)]
|
||||
Dummy10,
|
||||
#[schemars(skip)]
|
||||
Dummy11,
|
||||
#[schemars(skip)]
|
||||
Dummy12,
|
||||
#[schemars(skip)]
|
||||
Dummy13,
|
||||
#[schemars(skip)]
|
||||
Dummy14,
|
||||
#[schemars(skip)]
|
||||
Dummy15,
|
||||
#[schemars(skip)]
|
||||
Dummy16,
|
||||
#[schemars(skip)]
|
||||
Dummy17,
|
||||
#[schemars(skip)]
|
||||
Dummy18,
|
||||
#[schemars(skip)]
|
||||
Dummy19,
|
||||
#[schemars(skip)]
|
||||
Dummy20,
|
||||
#[schemars(skip)]
|
||||
Dummy21,
|
||||
#[schemars(skip)]
|
||||
Dummy22,
|
||||
#[schemars(skip)]
|
||||
Dummy23,
|
||||
#[schemars(skip)]
|
||||
Dummy24,
|
||||
#[schemars(skip)]
|
||||
Dummy25,
|
||||
#[schemars(skip)]
|
||||
Dummy26,
|
||||
#[schemars(skip)]
|
||||
Dummy27,
|
||||
#[schemars(skip)]
|
||||
Dummy28,
|
||||
#[schemars(skip)]
|
||||
Dummy29,
|
||||
#[schemars(skip)]
|
||||
Dummy30,
|
||||
#[schemars(skip)]
|
||||
Dummy31,
|
||||
#[schemars(skip)]
|
||||
Dummy32,
|
||||
#[schemars(skip)]
|
||||
Dummy33,
|
||||
#[schemars(skip)]
|
||||
Dummy34,
|
||||
#[schemars(skip)]
|
||||
Dummy35,
|
||||
#[schemars(skip)]
|
||||
Dummy36,
|
||||
#[schemars(skip)]
|
||||
Dummy37,
|
||||
#[schemars(skip)]
|
||||
Dummy38,
|
||||
#[schemars(skip)]
|
||||
Dummy39,
|
||||
#[schemars(skip)]
|
||||
Dummy40,
|
||||
#[schemars(skip)]
|
||||
Dummy41,
|
||||
#[schemars(skip)]
|
||||
Dummy42,
|
||||
#[schemars(skip)]
|
||||
Dummy43,
|
||||
#[schemars(skip)]
|
||||
Dummy44,
|
||||
#[schemars(skip)]
|
||||
Dummy45,
|
||||
#[schemars(skip)]
|
||||
Dummy46,
|
||||
#[schemars(skip)]
|
||||
Dummy47,
|
||||
#[schemars(skip)]
|
||||
Dummy48,
|
||||
#[schemars(skip)]
|
||||
Dummy49,
|
||||
#[schemars(skip)]
|
||||
Dummy50,
|
||||
#[schemars(skip)]
|
||||
Dummy51,
|
||||
#[schemars(skip)]
|
||||
Dummy52,
|
||||
#[schemars(skip)]
|
||||
Dummy53,
|
||||
#[schemars(skip)]
|
||||
Dummy54,
|
||||
#[schemars(skip)]
|
||||
Dummy55,
|
||||
#[schemars(skip)]
|
||||
Dummy56,
|
||||
#[schemars(skip)]
|
||||
Dummy57,
|
||||
#[schemars(skip)]
|
||||
Dummy58,
|
||||
#[schemars(skip)]
|
||||
Dummy59,
|
||||
#[schemars(skip)]
|
||||
Dummy60,
|
||||
#[schemars(skip)]
|
||||
Dummy61,
|
||||
#[schemars(skip)]
|
||||
Dummy62,
|
||||
#[schemars(skip)]
|
||||
Dummy63,
|
||||
#[schemars(skip)]
|
||||
Dummy64,
|
||||
#[schemars(skip)]
|
||||
Dummy65,
|
||||
#[schemars(skip)]
|
||||
Dummy66,
|
||||
#[schemars(skip)]
|
||||
Dummy67,
|
||||
#[schemars(skip)]
|
||||
Dummy68,
|
||||
#[schemars(skip)]
|
||||
Dummy69,
|
||||
#[schemars(skip)]
|
||||
Dummy70,
|
||||
#[schemars(skip)]
|
||||
Dummy71,
|
||||
#[schemars(skip)]
|
||||
Dummy72,
|
||||
#[schemars(skip)]
|
||||
Dummy73,
|
||||
#[schemars(skip)]
|
||||
Dummy74,
|
||||
#[schemars(skip)]
|
||||
Dummy75,
|
||||
#[schemars(skip)]
|
||||
Dummy76,
|
||||
#[schemars(skip)]
|
||||
Dummy77,
|
||||
#[schemars(skip)]
|
||||
Dummy78,
|
||||
#[schemars(skip)]
|
||||
Dummy79,
|
||||
#[schemars(skip)]
|
||||
Dummy80,
|
||||
#[schemars(skip)]
|
||||
Dummy81,
|
||||
#[schemars(skip)]
|
||||
Dummy82,
|
||||
#[schemars(skip)]
|
||||
Dummy83,
|
||||
#[schemars(skip)]
|
||||
Dummy84,
|
||||
#[schemars(skip)]
|
||||
Dummy85,
|
||||
#[schemars(skip)]
|
||||
Dummy86,
|
||||
#[schemars(skip)]
|
||||
Dummy87,
|
||||
#[schemars(skip)]
|
||||
Dummy88,
|
||||
#[schemars(skip)]
|
||||
Dummy89,
|
||||
#[schemars(skip)]
|
||||
Dummy90,
|
||||
#[schemars(skip)]
|
||||
Dummy91,
|
||||
#[schemars(skip)]
|
||||
Dummy92,
|
||||
#[schemars(skip)]
|
||||
Dummy93,
|
||||
#[schemars(skip)]
|
||||
Dummy94,
|
||||
#[schemars(skip)]
|
||||
Dummy95,
|
||||
#[schemars(skip)]
|
||||
Dummy96,
|
||||
#[schemars(skip)]
|
||||
Dummy97,
|
||||
#[schemars(skip)]
|
||||
Dummy98,
|
||||
#[schemars(skip)]
|
||||
Dummy99,
|
||||
#[schemars(skip)]
|
||||
Dummy100,
|
||||
#[schemars(skip)]
|
||||
Dummy101,
|
||||
#[schemars(skip)]
|
||||
Dummy102,
|
||||
#[schemars(skip)]
|
||||
Dummy103,
|
||||
#[schemars(skip)]
|
||||
Dummy104,
|
||||
#[schemars(skip)]
|
||||
Dummy105,
|
||||
#[schemars(skip)]
|
||||
Dummy106,
|
||||
#[schemars(skip)]
|
||||
Dummy107,
|
||||
#[schemars(skip)]
|
||||
Dummy108,
|
||||
#[schemars(skip)]
|
||||
Dummy109,
|
||||
#[schemars(skip)]
|
||||
Dummy110,
|
||||
#[schemars(skip)]
|
||||
Dummy111,
|
||||
#[schemars(skip)]
|
||||
Dummy112,
|
||||
#[schemars(skip)]
|
||||
Dummy113,
|
||||
#[schemars(skip)]
|
||||
Dummy114,
|
||||
#[schemars(skip)]
|
||||
Dummy115,
|
||||
#[schemars(skip)]
|
||||
Dummy116,
|
||||
#[schemars(skip)]
|
||||
Dummy117,
|
||||
#[schemars(skip)]
|
||||
Dummy118,
|
||||
#[schemars(skip)]
|
||||
Dummy119,
|
||||
#[schemars(skip)]
|
||||
Dummy120,
|
||||
#[schemars(skip)]
|
||||
Dummy121,
|
||||
#[schemars(skip)]
|
||||
Dummy122,
|
||||
#[schemars(skip)]
|
||||
Dummy123,
|
||||
#[schemars(skip)]
|
||||
Dummy124,
|
||||
#[schemars(skip)]
|
||||
Dummy125,
|
||||
#[schemars(skip)]
|
||||
Dummy126,
|
||||
#[schemars(skip)]
|
||||
Dummy127,
|
||||
#[schemars(skip)]
|
||||
Dummy128,
|
||||
#[schemars(skip)]
|
||||
Dummy129,
|
||||
#[schemars(skip)]
|
||||
Dummy130,
|
||||
#[schemars(skip)]
|
||||
Dummy131,
|
||||
#[schemars(skip)]
|
||||
Dummy132,
|
||||
#[schemars(skip)]
|
||||
Dummy133,
|
||||
#[schemars(skip)]
|
||||
Dummy134,
|
||||
#[schemars(skip)]
|
||||
Dummy135,
|
||||
#[schemars(skip)]
|
||||
Dummy136,
|
||||
#[schemars(skip)]
|
||||
Dummy137,
|
||||
#[schemars(skip)]
|
||||
Dummy138,
|
||||
#[schemars(skip)]
|
||||
Dummy139,
|
||||
#[schemars(skip)]
|
||||
Dummy140,
|
||||
#[schemars(skip)]
|
||||
Dummy141,
|
||||
#[schemars(skip)]
|
||||
Dummy142,
|
||||
#[schemars(skip)]
|
||||
Dummy143,
|
||||
#[schemars(skip)]
|
||||
Dummy144,
|
||||
#[schemars(skip)]
|
||||
Dummy145,
|
||||
#[schemars(skip)]
|
||||
Dummy146,
|
||||
#[schemars(skip)]
|
||||
Dummy147,
|
||||
#[schemars(skip)]
|
||||
Dummy148,
|
||||
#[schemars(skip)]
|
||||
Dummy149,
|
||||
#[schemars(skip)]
|
||||
Dummy150,
|
||||
#[schemars(skip)]
|
||||
Dummy151,
|
||||
#[schemars(skip)]
|
||||
Dummy152,
|
||||
#[schemars(skip)]
|
||||
Dummy153,
|
||||
#[schemars(skip)]
|
||||
Dummy154,
|
||||
#[schemars(skip)]
|
||||
Dummy155,
|
||||
#[schemars(skip)]
|
||||
Dummy156,
|
||||
#[schemars(skip)]
|
||||
Dummy157,
|
||||
#[schemars(skip)]
|
||||
Dummy158,
|
||||
#[schemars(skip)]
|
||||
Dummy159,
|
||||
#[schemars(skip)]
|
||||
Dummy160,
|
||||
#[schemars(skip)]
|
||||
Dummy161,
|
||||
#[schemars(skip)]
|
||||
Dummy162,
|
||||
#[schemars(skip)]
|
||||
Dummy163,
|
||||
#[schemars(skip)]
|
||||
Dummy164,
|
||||
#[schemars(skip)]
|
||||
Dummy165,
|
||||
#[schemars(skip)]
|
||||
Dummy166,
|
||||
#[schemars(skip)]
|
||||
Dummy167,
|
||||
#[schemars(skip)]
|
||||
Dummy168,
|
||||
#[schemars(skip)]
|
||||
Dummy169,
|
||||
#[schemars(skip)]
|
||||
Dummy170,
|
||||
#[schemars(skip)]
|
||||
Dummy171,
|
||||
#[schemars(skip)]
|
||||
Dummy172,
|
||||
#[schemars(skip)]
|
||||
Dummy173,
|
||||
#[schemars(skip)]
|
||||
Dummy174,
|
||||
#[schemars(skip)]
|
||||
Dummy175,
|
||||
#[schemars(skip)]
|
||||
Dummy176,
|
||||
#[schemars(skip)]
|
||||
Dummy177,
|
||||
#[schemars(skip)]
|
||||
Dummy178,
|
||||
#[schemars(skip)]
|
||||
Dummy179,
|
||||
#[schemars(skip)]
|
||||
Dummy180,
|
||||
#[schemars(skip)]
|
||||
Dummy181,
|
||||
#[schemars(skip)]
|
||||
Dummy182,
|
||||
#[schemars(skip)]
|
||||
Dummy183,
|
||||
#[schemars(skip)]
|
||||
Dummy184,
|
||||
#[schemars(skip)]
|
||||
Dummy185,
|
||||
#[schemars(skip)]
|
||||
Dummy186,
|
||||
#[schemars(skip)]
|
||||
Dummy187,
|
||||
#[schemars(skip)]
|
||||
Dummy188,
|
||||
#[schemars(skip)]
|
||||
Dummy189,
|
||||
#[schemars(skip)]
|
||||
Dummy190,
|
||||
#[schemars(skip)]
|
||||
Dummy191,
|
||||
#[schemars(skip)]
|
||||
Dummy192,
|
||||
#[schemars(skip)]
|
||||
Dummy193,
|
||||
#[schemars(skip)]
|
||||
Dummy194,
|
||||
#[schemars(skip)]
|
||||
Dummy195,
|
||||
#[schemars(skip)]
|
||||
Dummy196,
|
||||
#[schemars(skip)]
|
||||
Dummy197,
|
||||
#[schemars(skip)]
|
||||
Dummy198,
|
||||
#[schemars(skip)]
|
||||
Dummy199,
|
||||
#[schemars(skip)]
|
||||
Dummy200,
|
||||
#[schemars(skip)]
|
||||
Dummy201,
|
||||
#[schemars(skip)]
|
||||
Dummy202,
|
||||
#[schemars(skip)]
|
||||
Dummy203,
|
||||
#[schemars(skip)]
|
||||
Dummy204,
|
||||
#[schemars(skip)]
|
||||
Dummy205,
|
||||
#[schemars(skip)]
|
||||
Dummy206,
|
||||
#[schemars(skip)]
|
||||
Dummy207,
|
||||
#[schemars(skip)]
|
||||
Dummy208,
|
||||
#[schemars(skip)]
|
||||
Dummy209,
|
||||
#[schemars(skip)]
|
||||
Dummy210,
|
||||
#[schemars(skip)]
|
||||
Dummy211,
|
||||
#[schemars(skip)]
|
||||
Dummy212,
|
||||
#[schemars(skip)]
|
||||
Dummy213,
|
||||
#[schemars(skip)]
|
||||
Dummy214,
|
||||
#[schemars(skip)]
|
||||
Dummy215,
|
||||
#[schemars(skip)]
|
||||
Dummy216,
|
||||
#[schemars(skip)]
|
||||
Dummy217,
|
||||
#[schemars(skip)]
|
||||
Dummy218,
|
||||
#[schemars(skip)]
|
||||
Dummy219,
|
||||
#[schemars(skip)]
|
||||
Dummy220,
|
||||
#[schemars(skip)]
|
||||
Dummy221,
|
||||
#[schemars(skip)]
|
||||
Dummy222,
|
||||
#[schemars(skip)]
|
||||
Dummy223,
|
||||
#[schemars(skip)]
|
||||
Dummy224,
|
||||
#[schemars(skip)]
|
||||
Dummy225,
|
||||
#[schemars(skip)]
|
||||
Dummy226,
|
||||
#[schemars(skip)]
|
||||
Dummy227,
|
||||
#[schemars(skip)]
|
||||
Dummy228,
|
||||
#[schemars(skip)]
|
||||
Dummy229,
|
||||
#[schemars(skip)]
|
||||
Dummy230,
|
||||
#[schemars(skip)]
|
||||
Dummy231,
|
||||
#[schemars(skip)]
|
||||
Dummy232,
|
||||
#[schemars(skip)]
|
||||
Dummy233,
|
||||
#[schemars(skip)]
|
||||
Dummy234,
|
||||
#[schemars(skip)]
|
||||
Dummy235,
|
||||
#[schemars(skip)]
|
||||
Dummy236,
|
||||
#[schemars(skip)]
|
||||
Dummy237,
|
||||
#[schemars(skip)]
|
||||
Dummy238,
|
||||
#[schemars(skip)]
|
||||
Dummy239,
|
||||
#[schemars(skip)]
|
||||
Dummy240,
|
||||
#[schemars(skip)]
|
||||
Dummy241,
|
||||
#[schemars(skip)]
|
||||
Dummy242,
|
||||
#[schemars(skip)]
|
||||
Dummy243,
|
||||
#[schemars(skip)]
|
||||
Dummy244,
|
||||
#[schemars(skip)]
|
||||
Dummy245,
|
||||
#[schemars(skip)]
|
||||
Dummy246,
|
||||
#[schemars(skip)]
|
||||
Dummy247,
|
||||
#[schemars(skip)]
|
||||
Dummy248,
|
||||
#[schemars(skip)]
|
||||
Dummy249,
|
||||
#[schemars(skip)]
|
||||
Dummy250,
|
||||
#[schemars(skip)]
|
||||
Dummy251,
|
||||
#[schemars(skip)]
|
||||
Dummy252,
|
||||
#[schemars(skip)]
|
||||
Dummy253,
|
||||
Empty = 254,
|
||||
Unknown = 255,
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
use allocative::Allocative;
|
||||
use bitcoin::Amount;
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, StoredCompressed};
|
||||
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
@@ -32,6 +33,7 @@ use super::{Bitcoin, Cents, Dollars, Height};
|
||||
Deserialize,
|
||||
StoredCompressed,
|
||||
Allocative,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct Sats(u64);
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use byteview::ByteView;
|
||||
use serde::Serialize;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, StoredCompressed};
|
||||
use zerocopy::IntoBytes;
|
||||
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::copy_first_4bytes;
|
||||
|
||||
/// Index within its type (e.g., 0 for first P2WPKH address)
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
@@ -22,11 +24,17 @@ use crate::copy_first_4bytes;
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
StoredCompressed,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct TypeIndex(u32);
|
||||
|
||||
impl TypeIndex {
|
||||
pub fn new(i: u32) -> Self {
|
||||
Self(i)
|
||||
}
|
||||
|
||||
pub fn increment(&mut self) {
|
||||
self.0 += 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user