Skip to content

Commit 62b90dd

Browse files
committed
feat: address totals endpoint handler
Signed-off-by: William Hankins <[email protected]>
1 parent 2f254c0 commit 62b90dd

File tree

3 files changed

+66
-31
lines changed

3 files changed

+66
-31
lines changed

modules/rest_blockfrost/src/handlers/accounts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,8 +668,8 @@ pub async fn handle_account_totals_blockfrost(
668668
msg,
669669
|message| match message {
670670
Message::StateQueryResponse(StateQueryResponse::Addresses(
671-
AddressStateQueryResponse::AddressesTotals(utxos),
672-
)) => Ok(utxos),
671+
AddressStateQueryResponse::AddressesTotals(totals),
672+
)) => Ok(totals),
673673
Message::StateQueryResponse(StateQueryResponse::Addresses(
674674
AddressStateQueryResponse::Error(e),
675675
)) => Err(e),

modules/rest_blockfrost/src/handlers/addresses.rs

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::sync::Arc;
22

3+
use crate::types::AddressTotalsREST;
34
use crate::{handlers_config::HandlersConfig, types::AddressInfoREST};
45
use acropolis_common::queries::errors::QueryError;
56
use acropolis_common::rest_error::RESTError;
@@ -20,35 +21,19 @@ pub async fn handle_address_single_blockfrost(
2021
params: Vec<String>,
2122
handlers_config: Arc<HandlersConfig>,
2223
) -> Result<RESTResponse, RESTError> {
23-
let [address_str] = &params[..] else {
24-
return Err(RESTError::param_missing("address"));
25-
};
26-
27-
let (address, stake_address) = match Address::from_string(address_str) {
28-
Ok(Address::None) | Ok(Address::Stake(_)) => {
29-
return Err(RESTError::invalid_param(
30-
"address",
31-
"must be a payment address",
32-
));
33-
}
34-
Ok(Address::Byron(byron)) => (Address::Byron(byron), None),
35-
Ok(Address::Shelley(shelley)) => {
36-
let stake_addr = shelley
37-
.stake_address_string()
38-
.map_err(|e| RESTError::invalid_param("address", &e.to_string()))?;
39-
40-
(Address::Shelley(shelley), stake_addr)
41-
}
42-
Err(e) => {
43-
return Err(RESTError::invalid_param("address", &e.to_string()));
44-
}
24+
let address = parse_address(&params)?;
25+
let stake_address = match address {
26+
Address::Shelley(ref addr) => addr.stake_address_string()?,
27+
_ => None,
4528
};
4629

4730
let address_type = address.kind().to_string();
4831
let is_script = address.is_script();
4932

5033
let address_query_msg = Arc::new(Message::StateQuery(StateQuery::Addresses(
51-
AddressStateQuery::GetAddressUTxOs { address },
34+
AddressStateQuery::GetAddressUTxOs {
35+
address: address.clone(),
36+
},
5237
)));
5338

5439
let utxo_identifiers = query_state(
@@ -77,7 +62,7 @@ pub async fn handle_address_single_blockfrost(
7762
None => {
7863
// Empty address - return zero balance (Blockfrost behavior)
7964
let rest_response = AddressInfoREST {
80-
address: address_str.to_string(),
65+
address: address.to_string()?,
8166
amount: Value {
8267
lovelace: 0,
8368
assets: Vec::new(),
@@ -116,7 +101,7 @@ pub async fn handle_address_single_blockfrost(
116101
.await?;
117102

118103
let rest_response = AddressInfoREST {
119-
address: address_str.to_string(),
104+
address: address.to_string()?,
120105
amount: address_balance.into(),
121106
stake_address,
122107
address_type,
@@ -138,11 +123,45 @@ pub async fn handle_address_extended_blockfrost(
138123

139124
/// Handle `/addresses/{address}/totals` Blockfrost-compatible endpoint
140125
pub async fn handle_address_totals_blockfrost(
141-
_context: Arc<Context<Message>>,
142-
_params: Vec<String>,
143-
_handlers_config: Arc<HandlersConfig>,
126+
context: Arc<Context<Message>>,
127+
params: Vec<String>,
128+
handlers_config: Arc<HandlersConfig>,
144129
) -> Result<RESTResponse, RESTError> {
145-
Err(RESTError::not_implemented("Address totals endpoint"))
130+
let address = parse_address(&params)?;
131+
132+
// Get totals from address state
133+
let msg = Arc::new(Message::StateQuery(StateQuery::Addresses(
134+
AddressStateQuery::GetAddressTotals {
135+
address: address.clone(),
136+
},
137+
)));
138+
let totals = query_state(
139+
&context,
140+
&handlers_config.addresses_query_topic,
141+
msg,
142+
|message| match message {
143+
Message::StateQueryResponse(StateQueryResponse::Addresses(
144+
AddressStateQueryResponse::AddressTotals(totals),
145+
)) => Ok(totals),
146+
Message::StateQueryResponse(StateQueryResponse::Addresses(
147+
AddressStateQueryResponse::Error(e),
148+
)) => Err(e),
149+
_ => Err(QueryError::internal_error(
150+
"Unexpected message type while retrieving address totals",
151+
)),
152+
},
153+
)
154+
.await?;
155+
156+
let rest_response = AddressTotalsREST {
157+
address: address.to_string()?,
158+
received_sum: totals.received.into(),
159+
sent_sum: totals.sent.into(),
160+
tx_count: totals.tx_count,
161+
};
162+
163+
let json = serde_json::to_string_pretty(&rest_response)?;
164+
Ok(RESTResponse::with_json(200, &json))
146165
}
147166

148167
/// Handle `/addresses/{address}/utxos` Blockfrost-compatible endpoint
@@ -171,3 +190,11 @@ pub async fn handle_address_transactions_blockfrost(
171190
) -> Result<RESTResponse, RESTError> {
172191
Err(RESTError::not_implemented("Address transactions endpoint"))
173192
}
193+
194+
fn parse_address(params: &[String]) -> Result<Address, RESTError> {
195+
let Some(address_str) = params.first() else {
196+
return Err(RESTError::param_missing("address"));
197+
};
198+
199+
Ok(Address::from_string(address_str)?)
200+
}

modules/rest_blockfrost/src/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,3 +945,11 @@ pub struct AccountTotalsREST {
945945
pub sent_sum: AmountList,
946946
pub tx_count: u64,
947947
}
948+
949+
#[derive(serde::Serialize)]
950+
pub struct AddressTotalsREST {
951+
pub address: String,
952+
pub received_sum: AmountList,
953+
pub sent_sum: AmountList,
954+
pub tx_count: u64,
955+
}

0 commit comments

Comments
 (0)