diff --git a/.github/workflows/automated-deploy.yml b/.github/workflows/automated-deploy.yml
index 80c16d6..11c0eeb 100644
--- a/.github/workflows/automated-deploy.yml
+++ b/.github/workflows/automated-deploy.yml
@@ -11,7 +11,7 @@ env:
jobs:
build-and-deploy:
- name: Build and deploy Gravity Info Server
+ name: Build and deploy Althea Info Server
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d07f049..60d6a3e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,4 +1,4 @@
-name: Tests for Gravity Info
+name: Tests for Althea Info
on:
push:
diff --git a/althea-info-dash/public/manifest.json b/althea-info-dash/public/manifest.json
index 4f10115..93c0509 100644
--- a/althea-info-dash/public/manifest.json
+++ b/althea-info-dash/public/manifest.json
@@ -1,6 +1,6 @@
{
- "short_name": "Gravity Info",
- "name": "Gravity Information",
+ "short_name": "Althea Info",
+ "name": "Althea Information",
"icons": [
{
"src": "favicon.ico",
diff --git a/althea-info-server/src/config.rs b/althea-info-server/src/config.rs
new file mode 100644
index 0000000..34cd227
--- /dev/null
+++ b/althea-info-server/src/config.rs
@@ -0,0 +1,183 @@
+use std::collections::HashMap;
+
+use clarity::Address;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
+#[allow(non_snake_case)]
+pub struct Token {
+ /// The Althea L1 EVM address of the token
+ pub althea_evm_address: Address,
+ /// The denom of this token on Althea L1 on the Cosmos layer (IBC tokens will have ibc/ prefix)
+ pub althea_denom: String,
+ /// The erc20 address of this token on Ethereum, may be null if this token does not exist there
+ pub eth_address: Option
,
+ /// the source ibc channel of this token, may be null if this token is not an IBC token
+ /// IBC port is assumed to be transfer for all tokens
+ pub ibc_channel: Option,
+ /// The number of decimal places the token uses
+ pub decimals: u32,
+ /// The name of the token (multiple words)
+ pub name: String,
+ /// The symbol of the token (small number of letters)
+ pub symbol: String,
+ /// The coingecko ID of this token for price lookups, essential for the DefiLlama integration
+ pub coingecko_id: String,
+}
+
+pub fn get_token(token: &str) -> Option {
+ let tokens = get_tokens();
+ tokens
+ .values()
+ .find(|t| {
+ t.althea_evm_address.to_string() == token
+ || t.althea_denom == token
+ || {
+ if let Some(eth_address) = &t.eth_address {
+ eth_address.to_string() == token
+ } else {
+ false
+ }
+ }
+ || t.name == token
+ || t.symbol == token
+ })
+ .cloned()
+}
+
+pub fn get_tokens() -> HashMap {
+ let mut tokens = HashMap::new();
+ let althea = Token {
+ althea_evm_address: "0x0000000000000000000000000000000000000000"
+ .parse()
+ .unwrap(),
+ althea_denom: "aalthea".to_string(),
+ ibc_channel: Some("channel-0".to_string()),
+ eth_address: Some(
+ "0xF9e595BC0aF20cfa1561dfE085E3DE9Fcf9Fbfa2"
+ .parse()
+ .unwrap(),
+ ),
+ decimals: 18,
+ name: "ALTHEA".to_string(),
+ symbol: "ALTHEA".to_string(),
+ coingecko_id: "althea".to_string(),
+ };
+ tokens.insert(althea.symbol.clone(), althea);
+
+ let usdc = Token {
+ althea_evm_address: "0x80b5a32E4F032B2a058b4F29EC95EEfEEB87aDcd"
+ .parse()
+ .unwrap(),
+ eth_address: Some(
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
+ .parse()
+ .unwrap(),
+ ),
+ althea_denom: "ibc/17CD484EE7D9723B847D95015FA3EBD1572FD13BC84FB838F55B18A57450F25B"
+ .to_string(),
+ ibc_channel: Some("channel-0".to_string()),
+ decimals: 6,
+ name: "Circle USD Stablecoin".to_string(),
+ symbol: "USDC".to_string(),
+ coingecko_id: "usdc".to_string(),
+ };
+ tokens.insert(usdc.symbol.clone(), usdc);
+
+ let usdt = Token {
+ althea_evm_address: "0xecEEEfCEE421D8062EF8d6b4D814efe4dc898265"
+ .parse()
+ .unwrap(),
+ eth_address: Some(
+ "0xdAC17F958D2ee523a2206206994597C13D831ec7"
+ .parse()
+ .unwrap(),
+ ),
+ althea_denom: "ibc/4F6A2DEFEA52CD8D90966ADCB2BD0593D3993AB0DF7F6AEB3EFD6167D79237B0"
+ .to_string(),
+ ibc_channel: Some("channel-0".to_string()),
+ decimals: 6,
+ name: "Tether Stablecoin".to_string(),
+ symbol: "USDT".to_string(),
+ coingecko_id: "tether".to_string(),
+ };
+ tokens.insert(usdt.symbol.clone(), usdt);
+
+ let usds = Token {
+ althea_evm_address: "0xd567B3d7B8FE3C79a1AD8dA978812cfC4Fa05e75"
+ .parse()
+ .unwrap(),
+ althea_denom: "ibc/AE1B617F7F329ED83C20AC584B03579EEFE3322EF601CE88936A0271BE1157DD"
+ .to_string(),
+ eth_address: Some(
+ "0xdC035D45d973E3EC169d2276DDab16f1e407384F"
+ .parse()
+ .unwrap(),
+ ),
+ ibc_channel: Some("channel-0".to_string()),
+ decimals: 18,
+ name: "USDS".to_string(),
+ symbol: "USDS".to_string(),
+ coingecko_id: "usds".to_string(),
+ };
+ tokens.insert(usds.symbol.clone(), usds);
+
+ let susds = Token {
+ althea_evm_address: "0x5FD55A1B9FC24967C4dB09C513C3BA0DFa7FF687"
+ .parse()
+ .unwrap(),
+ eth_address: Some(
+ "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD"
+ .parse()
+ .unwrap(),
+ ),
+ althea_denom: "ibc/576150049104D47DFD447482EEED2FC8B44AB0D9A772D673717062B49D9820C5"
+ .to_string(),
+ ibc_channel: Some("channel-0".to_string()),
+ decimals: 18,
+ name: "Savings USDS".to_string(),
+ symbol: "sUSDS".to_string(),
+ coingecko_id: "susds".to_string(),
+ };
+ tokens.insert(susds.symbol.clone(), susds);
+
+ let grav = Token {
+ althea_evm_address: "0x1D54EcB8583Ca25895c512A8308389fFD581F9c9"
+ .parse()
+ .unwrap(),
+ eth_address: Some(
+ "0x9f2ef66a09A5d2dAB13D84A7638668EA36679e03"
+ .parse()
+ .unwrap(),
+ ),
+ althea_denom: "ibc/FC9D92EC12BC974E8B6179D411351524CD5C2EBC3CE29D5BA856414FEFA47093"
+ .to_string(),
+ ibc_channel: Some("channel-0".to_string()),
+ decimals: 6,
+ name: "Graviton".to_string(),
+ symbol: "GRAV".to_string(),
+ coingecko_id: "graviton".to_string(),
+ };
+ tokens.insert(grav.symbol.clone(), grav);
+
+ let weth = Token {
+ althea_evm_address: "0xc03345448969Dd8C00e9E4A85d2d9722d093aF8E"
+ .parse()
+ .unwrap(),
+ eth_address: Some(
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
+ .parse()
+ .unwrap(),
+ ),
+ althea_denom: "ibc/DC186CA7A8C009B43774EBDC825C935CABA9743504CE6037507E6E5CCE12858A"
+ .to_string(),
+ ibc_channel: Some("channel-0".to_string()),
+ decimals: 18,
+ name: "Ethereum".to_string(),
+ symbol: "WETH".to_string(),
+ coingecko_id: "weth".to_string(),
+ };
+ tokens.insert(weth.symbol.clone(), weth);
+
+ tokens
+}
diff --git a/althea-info-server/src/endpoints.rs b/althea-info-server/src/endpoints.rs
new file mode 100644
index 0000000..fd74012
--- /dev/null
+++ b/althea-info-server/src/endpoints.rs
@@ -0,0 +1,65 @@
+use actix_web::{get, HttpResponse, Responder};
+use log::error;
+
+use crate::{total_suppy::get_supply_info, tvl::get_unpriced_tvl, ALTHEA_NODE_GRPC};
+
+#[get("/total_supply")]
+async fn endpoint_get_total_supply() -> impl Responder {
+ // if we have already computed supply info return it, if not return an error
+ match get_supply_info() {
+ Some(v) => HttpResponse::Ok().json(v.total_supply),
+ None => HttpResponse::InternalServerError()
+ .json("Info not yet generated, please query in 5 minutes"),
+ }
+}
+
+/// If the liquid supply is lower than this value it's stale or otherwise invalid and we should
+/// return an error.
+pub const SUPPLY_CHECKPOINT: u128 = 500000000000000;
+#[get("/total_liquid_supply")]
+async fn endpoint_get_total_liquid_supply() -> impl Responder {
+ // if we have already computed supply info return it, if not return an error
+ match get_supply_info() {
+ Some(v) => {
+ if v.total_liquid_supply > SUPPLY_CHECKPOINT.into() {
+ HttpResponse::Ok().json(v.total_liquid_supply)
+ } else {
+ error!("Invalid supply data, got total liquid supply of {:#?}", v);
+ HttpResponse::InternalServerError()
+ .json("Invalid supply data, Althea fullnode is stale")
+ }
+ }
+ None => HttpResponse::InternalServerError()
+ .json("Info not yet generated, please query in 5 minutes"),
+ }
+}
+
+#[get("/supply_info")]
+async fn endpoint_get_all_supply_info() -> impl Responder {
+ // if we have already computed supply info return it, if not return an error
+ match get_supply_info() {
+ Some(v) => {
+ if v.total_liquid_supply > SUPPLY_CHECKPOINT.into() {
+ HttpResponse::Ok().json(v)
+ } else {
+ error!("Invalid supply data, got total liquid supply of {:#?}", v);
+ HttpResponse::InternalServerError()
+ .json("Invalid supply data, Althea fullnode is stale")
+ }
+ }
+ None => HttpResponse::InternalServerError()
+ .json("Info not yet generated, please query in 5 minutes"),
+ }
+}
+
+#[get("/unpriced_tvl")]
+async fn endpoint_get_unpriced_tvl() -> impl Responder {
+ // Try to get the TVL, on failure return an error
+ match get_unpriced_tvl(ALTHEA_NODE_GRPC.to_string()).await {
+ Ok(v) => HttpResponse::Ok().json(v),
+ Err(e) => {
+ error!("Error getting unpriced TVL: {:#?}", e);
+ HttpResponse::InternalServerError().json("Error getting unpriced TVL")
+ }
+ }
+}
diff --git a/althea-info-server/src/main.rs b/althea-info-server/src/main.rs
index d8799ae..e9e8f48 100644
--- a/althea-info-server/src/main.rs
+++ b/althea-info-server/src/main.rs
@@ -1,8 +1,11 @@
#[macro_use]
extern crate lazy_static;
+pub mod config;
+pub mod endpoints;
pub mod tls;
pub mod total_suppy;
+pub mod tvl;
const DEVELOPMENT: bool = cfg!(feature = "development");
const SSL: bool = !DEVELOPMENT;
@@ -15,13 +18,16 @@ const DOMAIN: &str = if cfg!(test) || DEVELOPMENT {
const INFO_SERVER_PORT: u16 = 9000;
use crate::{
+ endpoints::{
+ endpoint_get_all_supply_info, endpoint_get_total_liquid_supply, endpoint_get_total_supply,
+ endpoint_get_unpriced_tvl,
+ },
tls::{load_certs, load_private_key},
- total_suppy::get_supply_info,
};
use actix_cors::Cors;
-use actix_web::{get, App, HttpResponse, HttpServer, Responder};
+use actix_web::{App, HttpServer};
use env_logger::Env;
-use log::{error, info};
+use log::info;
use rustls::ServerConfig;
use total_suppy::chain_total_supply_thread;
@@ -29,55 +35,6 @@ pub const ALTHEA_NODE_GRPC: &str = "https://rpc.althea.zone:9090";
pub const ALTHEA_PREFIX: &str = "althea";
pub const REQUEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
-#[get("/total_supply")]
-async fn get_total_supply() -> impl Responder {
- // if we have already computed supply info return it, if not return an error
- match get_supply_info() {
- Some(v) => HttpResponse::Ok().json(v.total_supply),
- None => HttpResponse::InternalServerError()
- .json("Info not yet generated, please query in 5 minutes"),
- }
-}
-
-/// If the liquid supply is lower than this value it's stale or otherwise invalid and we should
-/// return an error.
-pub const SUPPLY_CHECKPOINT: u128 = 500000000000000;
-#[get("/total_liquid_supply")]
-async fn get_total_liquid_supply() -> impl Responder {
- // if we have already computed supply info return it, if not return an error
- match get_supply_info() {
- Some(v) => {
- if v.total_liquid_supply > SUPPLY_CHECKPOINT.into() {
- HttpResponse::Ok().json(v.total_liquid_supply)
- } else {
- error!("Invalid supply data, got total liquid supply of {:#?}", v);
- HttpResponse::InternalServerError()
- .json("Invalid supply data, Althea fullnode is stale")
- }
- }
- None => HttpResponse::InternalServerError()
- .json("Info not yet generated, please query in 5 minutes"),
- }
-}
-
-#[get("/supply_info")]
-async fn get_all_supply_info() -> impl Responder {
- // if we have already computed supply info return it, if not return an error
- match get_supply_info() {
- Some(v) => {
- if v.total_liquid_supply > SUPPLY_CHECKPOINT.into() {
- HttpResponse::Ok().json(v)
- } else {
- error!("Invalid supply data, got total liquid supply of {:#?}", v);
- HttpResponse::InternalServerError()
- .json("Invalid supply data, Althea fullnode is stale")
- }
- }
- None => HttpResponse::InternalServerError()
- .json("Info not yet generated, please query in 5 minutes"),
- }
-}
-
#[actix_web::main]
async fn main() -> std::io::Result<()> {
openssl_probe::init_ssl_cert_env_vars();
@@ -93,9 +50,10 @@ async fn main() -> std::io::Result<()> {
.allow_any_header()
.allow_any_method(),
)
- .service(get_total_supply)
- .service(get_total_liquid_supply)
- .service(get_all_supply_info)
+ .service(endpoint_get_total_supply)
+ .service(endpoint_get_total_liquid_supply)
+ .service(endpoint_get_all_supply_info)
+ .service(endpoint_get_unpriced_tvl)
});
let info_server = if SSL {
diff --git a/althea-info-server/src/total_suppy.rs b/althea-info-server/src/total_suppy.rs
index b724ec3..e0f82a0 100644
--- a/althea-info-server/src/total_suppy.rs
+++ b/althea-info-server/src/total_suppy.rs
@@ -32,9 +32,9 @@ pub const ALTHEA_DENOM: &str = "aalthea";
#[derive(Debug, Clone, Serialize)]
pub struct ChainTotalSupplyNumbers {
- /// The total amount of Graviton tokens currently in existance including those vesting and in the community pool
+ /// The total amount of Althea tokens currently in existance including those vesting and in the community pool
pub total_supply: Uint256,
- /// The total amount of Gravition in the community pool
+ /// The total amount of Althea in the community pool
pub community_pool: Uint256,
/// All tokens that are 'liquid' meaning in a balance, claimable now as rewards
/// or staked and eligeable to withdraw and spend, essentially just exludes vesting
@@ -200,11 +200,12 @@ async fn compute_liquid_supply(
// Account for tokens staked beyond original vesting (e.g., staked rewards)
// These are fully liquid and not tracked by the vesting module
- let staked_beyond_vesting = if user.total_staked > total_delegated_vesting + total_delegated_free {
- user.total_staked - total_delegated_vesting - total_delegated_free
- } else {
- 0u8.into()
- };
+ let staked_beyond_vesting =
+ if user.total_staked > total_delegated_vesting + total_delegated_free {
+ user.total_staked - total_delegated_vesting - total_delegated_free
+ } else {
+ 0u8.into()
+ };
total_liquid_supply += staked_beyond_vesting;
total_nonvesting_staked += staked_beyond_vesting;
diff --git a/althea-info-server/src/tvl.rs b/althea-info-server/src/tvl.rs
new file mode 100644
index 0000000..0246c86
--- /dev/null
+++ b/althea-info-server/src/tvl.rs
@@ -0,0 +1,302 @@
+use cosmos_sdk_proto_althea::cosmos::{
+ bank::v1beta1::{query_client::QueryClient as BankQueryClient, QueryTotalSupplyRequest},
+ base::{query::v1beta1::PageRequest, v1beta1::Coin},
+};
+use cosmos_sdk_proto_althea::ibc::applications::transfer::v1::{
+ query_client::QueryClient as IbcTransferQueryClient, QueryEscrowAddressRequest,
+};
+use num256::Uint256;
+use rust_decimal::prelude::Zero;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use tokio::time::sleep;
+
+use crate::{
+ config::{get_token, get_tokens, Token},
+ total_suppy::get_supply_info,
+};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Tvl {
+ pub althea_on_chain: TokenAmount,
+ pub ibc_tokens_on_chain: Vec,
+ pub althea_native_erc20s_on_chain: Vec,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct TokenAmount {
+ pub token: Token,
+ pub amount: Uint256,
+}
+
+/// Type alias for the total supply result from the Cosmos bank module
+pub type TotalSupply = Vec;
+
+pub const ALTHEA_TOKEN_DENOM: &str = "aalthea";
+
+// Fetches and computes the supply of bridged IBC tokens, native althea (from the total supply thread info), and altheaL1-native erc20s
+pub async fn get_unpriced_tvl(grpc: String) -> Result {
+ let supply = get_total_supply(&grpc).await?;
+ let supply = filter_supply_by_tokens(supply);
+ let tokens_on_chain = get_tokens_on_chain(&supply, &grpc).await?;
+ let althea_supply =
+ get_supply_info().map_or_else(Uint256::zero, |info| info.total_liquid_supply);
+
+ let althea_on_chain = TokenAmount {
+ token: get_token(ALTHEA_TOKEN_DENOM).unwrap(),
+ amount: althea_supply,
+ };
+
+ let mut ibc_tokens_on_chain: Vec = Vec::new();
+ let mut althea_native_erc20s_on_chain: Vec = Vec::new();
+ for (t, v) in tokens_on_chain {
+ if t.althea_denom.starts_with("ibc/") {
+ ibc_tokens_on_chain.push(TokenAmount {
+ token: t,
+ amount: v,
+ });
+ } else if t.althea_denom != ALTHEA_TOKEN_DENOM {
+ althea_native_erc20s_on_chain.push(TokenAmount {
+ token: t,
+ amount: v,
+ });
+ }
+ }
+ let tvl = Tvl {
+ althea_on_chain,
+ ibc_tokens_on_chain,
+ althea_native_erc20s_on_chain,
+ };
+ Ok(tvl)
+}
+
+pub fn filter_supply_by_tokens(supply: TotalSupply) -> TotalSupply {
+ let tokens = get_tokens();
+ supply
+ .into_iter()
+ .filter(|coin| {
+ tokens
+ .values()
+ .any(|token| token.althea_denom == coin.denom)
+ })
+ .collect()
+}
+
+/// Gets the total supply of all tokens from the Cosmos bank module
+pub async fn get_total_supply(grpc: &str) -> Result {
+ let mut next_key: Option> = None;
+ let mut all_supply = Vec::new();
+
+ // Create a client and connect to the appropriate URL
+ let mut client = BankQueryClient::connect(grpc.to_string())
+ .await
+ .map_err(|e| format!("Failed to connect to gRPC endpoint: {e}"))?;
+
+ // Loop until we've retrieved all supply entries
+ loop {
+ let request = QueryTotalSupplyRequest {
+ pagination: Some(PageRequest {
+ key: next_key.unwrap_or_default(),
+ offset: 0,
+ limit: 100, // Request a reasonable batch size
+ count_total: false,
+ reverse: false,
+ }),
+ };
+
+ // Retry logic for the total_supply request
+ const MAX_RETRIES: u32 = 3;
+ let mut response = None;
+
+ for attempt in 1..=MAX_RETRIES {
+ match client.total_supply(request.clone()).await {
+ Ok(resp) => {
+ response = Some(resp);
+ break;
+ }
+ Err(e) => {
+ if attempt < MAX_RETRIES {
+ log::warn!(
+ "Failed to query total supply (attempt {}/{}): {}, retrying...",
+ attempt,
+ MAX_RETRIES,
+ e
+ );
+ sleep(tokio::time::Duration::from_millis(100 * attempt as u64)).await;
+ } else {
+ return Err(format!(
+ "Error querying total supply after {} attempts: {}",
+ MAX_RETRIES, e
+ ));
+ }
+ }
+ }
+ }
+
+ let response_inner = response
+ .expect("Response should be Some after successful retry")
+ .into_inner();
+
+ // Add supply from this page to our result
+ all_supply.extend(response_inner.supply);
+
+ // Check if there are more pages
+ if let Some(page_response) = response_inner.pagination {
+ if !page_response.next_key.is_empty() {
+ next_key = Some(page_response.next_key);
+ } else {
+ break; // No more pages
+ }
+ } else {
+ break; // No pagination response means no more pages
+ }
+ }
+
+ Ok(all_supply)
+}
+
+pub async fn get_tokens_on_chain(
+ supply: &TotalSupply,
+ grpc: &str,
+) -> Result, String> {
+ let mut result = HashMap::new();
+
+ // Create clients for IBC transfer and bank queries
+ let mut ibc_client = IbcTransferQueryClient::connect(grpc.to_string())
+ .await
+ .map_err(|e| format!("Failed to connect to IBC transfer gRPC endpoint: {e}"))?;
+
+ let ibc_port = "transfer".to_string();
+ let channel_0_id = "channel-0".to_string();
+ let mut channel0_escrow_address: String = String::new();
+ const MAX_RETRIES: u32 = 3;
+ for _ in 0..MAX_RETRIES {
+ if let Ok(response) = ibc_client
+ .escrow_address(QueryEscrowAddressRequest {
+ port_id: ibc_port.clone(),
+ channel_id: channel_0_id.clone(),
+ })
+ .await
+ {
+ channel0_escrow_address = response.into_inner().escrow_address;
+ break;
+ }
+ }
+ if channel0_escrow_address.is_empty() {
+ return Err("Failed to get escrow address for channel-0 after retries".to_string());
+ }
+
+ let mut bank_client = BankQueryClient::connect(grpc.to_string())
+ .await
+ .map_err(|e| format!("Failed to connect to bank gRPC endpoint: {e}"))?;
+
+ // Process each coin in the supply
+ for coin in supply {
+ // Find the matching token from our config
+ let token = get_token(&coin.denom);
+
+ if let Some(token) = token {
+ // Parse the total supply amount
+ let total_supply: Uint256 = match coin.amount.parse() {
+ Ok(amount) => amount,
+ Err(e) => {
+ log::error!(
+ "Failed to parse supply amount '{}' for denom {}: {}",
+ coin.amount,
+ coin.denom,
+ e
+ );
+ continue;
+ }
+ };
+
+ // Get the escrow address for this token
+ // Standard IBC transfer port is "transfer", channel needs to come from token config
+ let port_id = "transfer".to_string();
+ let channel_id = if let Some(ref channel) = token.ibc_channel {
+ channel.clone()
+ } else {
+ // If no channel configured, assume all tokens are on-chain (no escrow)
+ result.insert(token.clone(), total_supply);
+ continue;
+ };
+
+ let escrow_address: String = if port_id == ibc_port && channel_id == channel_0_id {
+ channel0_escrow_address.clone()
+ } else {
+ let escrow_request = QueryEscrowAddressRequest {
+ port_id: port_id.clone(),
+ channel_id: channel_id.clone(),
+ };
+
+ match ibc_client.escrow_address(escrow_request).await {
+ Ok(response) => response.into_inner().escrow_address,
+ Err(e) => {
+ log::warn!(
+ "Failed to get escrow address for {}/{}: {}, assuming no escrow",
+ port_id,
+ channel_id,
+ e
+ );
+ // If we can't get escrow address, assume all tokens are on-chain
+ result.insert(token.clone(), total_supply);
+ continue;
+ }
+ }
+ };
+
+ // Query the balance at the escrow address for this denom
+ let balance_request =
+ cosmos_sdk_proto_althea::cosmos::bank::v1beta1::QueryBalanceRequest {
+ address: escrow_address.clone(),
+ denom: coin.denom.clone(),
+ };
+
+ let escrowed_amount: Uint256 = match bank_client.balance(balance_request).await {
+ Ok(response) => {
+ if let Some(balance) = response.into_inner().balance {
+ match balance.amount.parse() {
+ Ok(amount) => amount,
+ Err(e) => {
+ log::error!(
+ "Failed to parse escrowed amount '{}' for denom {}: {}",
+ balance.amount,
+ coin.denom,
+ e
+ );
+ Uint256::zero()
+ }
+ }
+ } else {
+ Uint256::zero()
+ }
+ }
+ Err(e) => {
+ log::warn!(
+ "Failed to query balance for escrow address {}: {}, assuming zero escrow",
+ escrow_address,
+ e
+ );
+ Uint256::zero()
+ }
+ };
+
+ // Calculate on-chain balance: total supply - escrowed amount
+ let on_chain_amount = if total_supply >= escrowed_amount {
+ total_supply - escrowed_amount
+ } else {
+ log::error!(
+ "Escrowed amount ({}) exceeds total supply ({}) for denom {}",
+ escrowed_amount,
+ total_supply,
+ coin.denom
+ );
+ Uint256::zero()
+ };
+
+ result.insert(token.clone(), on_chain_amount);
+ }
+ }
+
+ Ok(result)
+}
diff --git a/scripts/deploy-info-server.yml b/scripts/deploy-info-server.yml
index ce76ebc..826d67c 100644
--- a/scripts/deploy-info-server.yml
+++ b/scripts/deploy-info-server.yml
@@ -1,7 +1,7 @@
-# Sets up the gravity info server plus an nginx server to host the frontend
+# Sets up the Althea info server plus an nginx server to host the frontend
-- name: Install Gravity Info server
- hosts: gravity_info_server
+- name: Install Althea Info server
+ hosts: althea_info_server
user: root
roles:
- install-deps
diff --git a/scripts/hosts b/scripts/hosts
index 3a11a60..0cec5d2 100644
--- a/scripts/hosts
+++ b/scripts/hosts
@@ -1,2 +1,2 @@
-[gravity_info_server]
+[althea_info_server]
info.althea.zone
diff --git a/scripts/roles/setup-info-server/tasks/systemd.yml b/scripts/roles/setup-info-server/tasks/systemd.yml
index 224c148..136d307 100644
--- a/scripts/roles/setup-info-server/tasks/systemd.yml
+++ b/scripts/roles/setup-info-server/tasks/systemd.yml
@@ -21,13 +21,13 @@
command: systemctl status althea-info-server
ignore_errors: true
changed_when: false
- register: service_gravity_info_server_status
+ register: service_althea_info_server_status
- name: Report status of Althea info server
fail:
msg: |
Service althea-info-server is not running.
Output of `systemctl status althea-info-server`:
- {{ service_gravity_info_server_status.stdout }}
- {{ service_gravity_info_server_status.stderr }}
- when: service_gravity_info_server_status is failed
\ No newline at end of file
+ {{ service_althea_info_server_status.stdout }}
+ {{ service_althea_info_server_status.stderr }}
+ when: service_althea_info_server_status is failed
\ No newline at end of file