Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 4ecad7b

Browse files
authored
feat: add claiming endpoint to anlytics/v2 (#692)
* feat: add `claiming` endpoint to `anlytics/v2` * Fmt * rename to `claimed-tokens/`
1 parent c090594 commit 4ecad7b

File tree

5 files changed

+194
-5
lines changed

5 files changed

+194
-5
lines changed

documentation/api/api-analytics.yml

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,24 @@ paths:
145145
$ref: "#/components/responses/BadRequest"
146146
"500":
147147
$ref: "#/components/responses/InternalError"
148+
/api/analytics/v2/activity/claimed-tokens:
149+
get:
150+
tags:
151+
- activity
152+
summary: Returns claiming activity.
153+
description: >-
154+
Returns the amount of tokens claimed within the specified milestone.
155+
parameters:
156+
- $ref: "#/components/parameters/ledgerIndex"
157+
responses:
158+
"200":
159+
$ref: "#/components/responses/Claiming"
160+
"400":
161+
$ref: "#/components/responses/BadRequest"
162+
"404":
163+
$ref: "#/components/responses/NoResults"
164+
"500":
165+
$ref: "#/components/responses/InternalError"
148166
/api/analytics/v2/activity/milestones/{milestoneId}:
149167
get:
150168
tags:
@@ -406,9 +424,9 @@ components:
406424
properties:
407425
address:
408426
oneOf:
409-
- $ref: "https://raw.githubusercontent.com/iotaledger/tips/stardust-api/tips/TIP-0025/core-rest-api.yaml#/components/schemas/Ed25519Address"
410-
- $ref: "https://raw.githubusercontent.com/iotaledger/tips/stardust-api/tips/TIP-0025/core-rest-api.yaml#/components/schemas/AliasAddress"
411-
- $ref: "https://raw.githubusercontent.com/iotaledger/tips/stardust-api/tips/TIP-0025/core-rest-api.yaml#/components/schemas/NFTAddress"
427+
- $ref: "https://raw.githubusercontent.com/iotaledger/tips/main/tips/TIP-0025/core-rest-api.yaml#/components/schemas/Ed25519Address"
428+
- $ref: "https://raw.githubusercontent.com/iotaledger/tips/main/tips/TIP-0025/core-rest-api.yaml#/components/schemas/AliasAddress"
429+
- $ref: "https://raw.githubusercontent.com/iotaledger/tips/main/tips/TIP-0025/core-rest-api.yaml#/components/schemas/NFTAddress"
412430
balance:
413431
type: string
414432
description: The total balance within this range.
@@ -449,6 +467,14 @@ components:
449467
- totalBalance
450468
required:
451469
- distribution
470+
ClaimedTokensAnalyticsResponse:
471+
description: Compiled statistics about claimed tokens.
472+
properties:
473+
count:
474+
type: string
475+
description: The total number of claimed tokens.
476+
required:
477+
- count
452478
responses:
453479
Output:
454480
description: Successful operation.
@@ -468,6 +494,15 @@ components:
468494
examples:
469495
default:
470496
$ref: "#/components/examples/addresses-example"
497+
Claiming:
498+
description: Successful operation.
499+
content:
500+
application/json:
501+
schema:
502+
$ref: "#/components/schemas/ClaimedTokensAnalyticsResponse"
503+
examples:
504+
default:
505+
$ref: "#/components/examples/claiming-example"
471506
StorageDeposit:
472507
description: Successful operation.
473508
content:
@@ -626,3 +661,6 @@ components:
626661
addressCount: "27"
627662
totalBalance: "25486528000"
628663
ledgerIndex: 1005429
664+
claiming-example:
665+
value:
666+
count: 567543

src/bin/inx-chronicle/api/stardust/analytics/responses.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,11 @@ pub struct ActivityPerInclusionStateDto {
122122
}
123123

124124
impl_success_response!(MilestoneAnalyticsResponse);
125+
126+
#[derive(Clone, Debug, Serialize, Deserialize)]
127+
#[serde(rename_all = "camelCase")]
128+
pub struct ClaimedTokensAnalyticsResponse {
129+
pub count: String,
130+
}
131+
132+
impl_success_response!(ClaimedTokensAnalyticsResponse);

src/bin/inx-chronicle/api/stardust/analytics/routes.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ use super::{
2323
extractors::{LedgerIndex, MilestoneRange, RichestAddressesQuery},
2424
responses::{
2525
ActivityPerInclusionStateDto, ActivityPerPayloadTypeDto, AddressAnalyticsResponse, AddressStatDto,
26-
MilestoneAnalyticsResponse, OutputAnalyticsResponse, OutputDiffAnalyticsResponse, RichestAddressesResponse,
27-
StorageDepositAnalyticsResponse, TokenDistributionResponse,
26+
ClaimedTokensAnalyticsResponse, MilestoneAnalyticsResponse, OutputAnalyticsResponse,
27+
OutputDiffAnalyticsResponse, RichestAddressesResponse, StorageDepositAnalyticsResponse,
28+
TokenDistributionResponse,
2829
},
2930
};
3031
use crate::api::{error::InternalApiError, router::Router, ApiError, ApiResult};
@@ -44,6 +45,7 @@ pub fn routes() -> Router {
4445
"/activity",
4546
Router::new()
4647
.route("/addresses", get(address_activity_analytics))
48+
.route("/claimed-tokens", get(claimed_tokens_analytics))
4749
.nest(
4850
"/milestones",
4951
Router::new()
@@ -299,3 +301,16 @@ async fn resolve_ledger_index(database: &MongoDb, ledger_index: Option<Milestone
299301
.ok_or(ApiError::NoResults)?
300302
})
301303
}
304+
305+
async fn claimed_tokens_analytics(
306+
database: Extension<MongoDb>,
307+
LedgerIndex { ledger_index }: LedgerIndex,
308+
) -> ApiResult<ClaimedTokensAnalyticsResponse> {
309+
let res = database
310+
.collection::<OutputCollection>()
311+
.get_claimed_token_analytics(ledger_index)
312+
.await?
313+
.ok_or(ApiError::NoResults)?;
314+
315+
Ok(ClaimedTokensAnalyticsResponse { count: res.count })
316+
}

src/db/collections/outputs/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,3 +863,42 @@ impl OutputCollection {
863863
Ok(TokenDistribution { distribution })
864864
}
865865
}
866+
867+
#[derive(Clone, Debug, Default, Deserialize)]
868+
#[allow(missing_docs)]
869+
pub struct ClaimedTokensResult {
870+
pub count: String,
871+
}
872+
873+
impl OutputCollection {
874+
/// Gets the number of claimed tokens.
875+
pub async fn get_claimed_token_analytics(
876+
&self,
877+
index: Option<MilestoneIndex>,
878+
) -> Result<Option<ClaimedTokensResult>, Error> {
879+
let spent_query = match index {
880+
Some(index) => doc! { "$eq": index },
881+
None => doc! { "$exists": true },
882+
};
883+
884+
self.aggregate(
885+
vec![
886+
doc! { "$match": {
887+
"metadata.booked.milestone_index": { "$eq": 0 },
888+
"metadata.spent_metadata.spent.milestone_index": spent_query,
889+
} },
890+
doc! { "$group": {
891+
"_id": null,
892+
"count": { "$sum": { "$toDecimal": "$output.amount" } },
893+
} },
894+
doc! { "$project": {
895+
"count": { "$toString": "$count" },
896+
} },
897+
],
898+
None,
899+
)
900+
.await?
901+
.try_next()
902+
.await
903+
}
904+
}

tests/claiming.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2022 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
mod common;
5+
6+
#[cfg(feature = "rand")]
7+
mod test_rand {
8+
9+
use chronicle::{
10+
db::collections::OutputCollection,
11+
types::{
12+
ledger::{LedgerOutput, LedgerSpent, MilestoneIndexTimestamp, SpentMetadata},
13+
stardust::block::{
14+
output::{BasicOutput, OutputAmount, OutputId},
15+
payload::TransactionId,
16+
BlockId, Output,
17+
},
18+
},
19+
};
20+
21+
use super::common::connect_to_test_db;
22+
23+
fn rand_output_with_value(amount: OutputAmount) -> Output {
24+
// We use `BasicOutput`s in the genesis.
25+
let mut output = BasicOutput::rand();
26+
output.amount = amount;
27+
Output::Basic(output)
28+
}
29+
30+
#[tokio::test]
31+
async fn test_claiming() {
32+
let db = connect_to_test_db("test-claiming").await.unwrap();
33+
db.clear().await.unwrap();
34+
let collection = db.collection::<OutputCollection>();
35+
collection.create_indexes().await.unwrap();
36+
37+
let unspent_outputs = (1..=5)
38+
.map(|i| LedgerOutput {
39+
output_id: OutputId::rand(),
40+
output: rand_output_with_value(i.into()),
41+
block_id: BlockId::rand(),
42+
booked: MilestoneIndexTimestamp {
43+
milestone_index: 0.into(),
44+
milestone_timestamp: 0.into(),
45+
},
46+
})
47+
.collect::<Vec<_>>();
48+
49+
collection.insert_unspent_outputs(&unspent_outputs).await.unwrap();
50+
51+
let spent_outputs = unspent_outputs
52+
.into_iter()
53+
.take(4) // we spent only the first 4 outputs
54+
.map(|output| {
55+
let i = output.output.amount().0;
56+
LedgerSpent {
57+
output,
58+
spent_metadata: SpentMetadata {
59+
transaction_id: TransactionId::rand(),
60+
spent: MilestoneIndexTimestamp {
61+
milestone_index: (i as u32).into(),
62+
milestone_timestamp: (i as u32 + 10000).into(),
63+
},
64+
},
65+
}
66+
})
67+
.collect::<Vec<_>>();
68+
69+
collection.update_spent_outputs(&spent_outputs).await.unwrap();
70+
71+
let total = collection
72+
.get_claimed_token_analytics(None)
73+
.await
74+
.unwrap()
75+
.unwrap()
76+
.count;
77+
assert_eq!(total, (1 + 2 + 3 + 4).to_string());
78+
79+
let third = collection
80+
.get_claimed_token_analytics(Some(3.into()))
81+
.await
82+
.unwrap()
83+
.unwrap()
84+
.count;
85+
assert_eq!(third, "3");
86+
87+
db.drop().await.unwrap();
88+
}
89+
}

0 commit comments

Comments
 (0)