Skip to content

Circulating supply #373

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,24 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/TokenSupply"
/api/v1/chain/circulating-supply:
get:
summary: Get the circulating supply of the native token at the given epoch
description: Calculates circulating supply by subtracting locked amounts from multiple excluded addresses from the effective supply
parameters:
- in: query
name: epoch
schema:
type: integer
minimum: 0
description: Epoch to query
responses:
"200":
description: Circulating supply of the native token
content:
application/json:
schema:
$ref: "#/components/schemas/CirculatingSupply"
/api/v1/chain/parameters:
get:
summary: Get chain parameters
Expand Down Expand Up @@ -1295,6 +1313,13 @@ components:
type: string
effectiveSupply:
type: string
CirculatingSupply:
type: object
required: [circulatingSupply]
properties:
circulatingSupply:
type: string
description: Circulating supply of the native token (effective supply minus locked supply)
Parameters:
type: object
required:
Expand Down
4 changes: 4 additions & 0 deletions webserver/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ impl ApplicationServer {
"/chain/token-supply",
get(chain_handlers::get_token_supply),
)
.route(
"/chain/circulating-supply",
get(chain_handlers::get_circulating_supply),
)
.route(
"/chain/block/latest",
get(chain_handlers::get_last_processed_block),
Expand Down
7 changes: 7 additions & 0 deletions webserver/src/dto/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ pub struct TokenSupply {
pub epoch: Option<i32>,
pub address: String,
}

#[derive(Clone, Serialize, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct CirculatingSupply {
#[validate(range(min = 0))]
pub epoch: Option<i32>,
}
18 changes: 16 additions & 2 deletions webserver/src/handler/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ use axum_extra::extract::Query;
use futures::Stream;
use tokio_stream::StreamExt;

use crate::dto::chain::TokenSupply as TokenSupplyDto;
use crate::dto::chain::{
CirculatingSupply as CirculatingSupplyDto, TokenSupply as TokenSupplyDto,
};
use crate::error::api::ApiError;
use crate::response::chain::{
LastProcessedBlock, LastProcessedEpoch, Parameters, RpcUrl, Token,
CirculatingSupply as CirculatingSupplyRsp, LastProcessedBlock,
LastProcessedEpoch, Parameters, RpcUrl, Token,
TokenSupply as TokenSupplyRsp,
};
use crate::state::common::CommonState;
Expand Down Expand Up @@ -113,3 +116,14 @@ pub async fn get_token_supply(
.await?;
Ok(Json(supply))
}

pub async fn get_circulating_supply(
Query(query): Query<CirculatingSupplyDto>,
State(state): State<CommonState>,
) -> Result<Json<CirculatingSupplyRsp>, ApiError> {
let supply = state
.chain_service
.get_circulating_supply(query.epoch)
.await?;
Ok(Json(supply))
}
6 changes: 6 additions & 0 deletions webserver/src/response/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,9 @@ pub struct TokenSupply {
pub total_supply: String,
pub effective_supply: Option<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CirculatingSupply {
pub circulating_supply: String,
}
65 changes: 64 additions & 1 deletion webserver/src/service/chain.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use bigdecimal::BigDecimal;
use shared::id::Id;
use shared::token::{IbcToken, Token};

use crate::appstate::AppState;
use crate::error::chain::ChainError;
use crate::repository::balance::{BalanceRepo, BalanceRepoTrait};
use crate::repository::chain::{ChainRepository, ChainRepositoryTrait};
use crate::response::chain::{Parameters, TokenSupply};
use crate::response::chain::{CirculatingSupply, Parameters, TokenSupply};

#[derive(Clone)]
pub struct ChainService {
Expand Down Expand Up @@ -83,4 +85,65 @@ impl ChainService {
effective_supply: supply.effective.map(|s| s.to_string()),
}))
}

pub async fn get_circulating_supply(
&self,
epoch: Option<i32>,
) -> Result<CirculatingSupply, ChainError> {
// Native token address and addresses to exclude from circulating supply
let native_token_address =
"tnam1q9gr66cvu4hrzm0sd5kmlnjje82gs3xlfg3v6nu7";
let excluded_addresses = vec![
"tnam1qxdzup2hcvhswcgw5kerd5lfkf04t64y3scgqm5v",
"tnam1qxt7uxhj9r00mfm4u870e7ghz6j20jrdz58gm5kj",
"tnam1qyez9fd9nkaxfj4u2f2k0vavr8mm69azcgds45rr",
"tnam1qqp69rzwsgnqdm0d4qfhw4qa4s6v3tlzm5069f4j",
"tnam1qrucghh3hw2zq8xtqzdj44nh5nrmnkn0usqng8yq",
];

// Get total supply of native token
let total_supply_result = self
.chain_repo
.get_token_supply(native_token_address.to_string(), epoch)
.await
.map_err(ChainError::Database)?;

let total_supply = total_supply_result.ok_or_else(|| {
ChainError::Unknown("Native token supply not found".to_string())
})?;

// Get balances for all excluded addresses and sum them up
let balance_repo = BalanceRepo::new(self.chain_repo.app_state.clone());
let mut total_locked_amount = BigDecimal::from(0);

for address in excluded_addresses {
let balances = balance_repo
.get_address_balances(address.to_string())
.await
.map_err(ChainError::Database)?;

// Find the balance of the native token for this excluded address
let locked_amount = balances
.iter()
.find(|balance| balance.token == native_token_address)
.map(|balance| balance.raw_amount.clone())
.unwrap_or_else(|| BigDecimal::from(0));

total_locked_amount += locked_amount;
}

// Calculate circulating supply = effective supply - total locked amount
let effective_supply_amount =
total_supply.effective.ok_or_else(|| {
ChainError::Unknown(
"Effective supply not found for native token".to_string(),
)
})?;
let circulating_amount =
&effective_supply_amount - &total_locked_amount;

Ok(CirculatingSupply {
circulating_supply: circulating_amount.to_string(),
})
}
}