Skip to content

Commit

Permalink
feat: add tokens for quick search
Browse files Browse the repository at this point in the history
  • Loading branch information
lok52 committed Dec 25, 2024
1 parent 0fc1982 commit 002f740
Show file tree
Hide file tree
Showing 15 changed files with 196 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod dapp;
pub mod token_info;
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::{error::ServiceError, ChainId};
use serde::Deserialize;
use url::Url;

pub struct TokenInfoClient {
http: reqwest::Client,
url: Url,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenInfo {
pub token_address: String,
pub chain_id: String,
pub icon_url: String,
pub token_name: Option<String>,
pub token_symbol: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct TokenInfoSearchResponse {
pub token_infos: Vec<TokenInfo>,
pub next_page_params: Option<String>,
}

impl TokenInfoClient {
pub fn new(url: Url) -> Self {
let http = reqwest::Client::new();
Self { http, url }
}

pub async fn search_tokens(
&self,
query: &str,
chain_id: Option<ChainId>,
page_size: Option<u32>,
page_token: Option<String>,
) -> Result<TokenInfoSearchResponse, ServiceError> {
let mut url = self.url.clone();
url.set_path("/api/v1/token-infos:search");
url.query_pairs_mut().append_pair("query", query);

if let Some(chain_id) = chain_id {
url.query_pairs_mut()
.append_pair("chain_id", &chain_id.to_string());
}
if let Some(page_size) = page_size {
url.query_pairs_mut()
.append_pair("page_size", &page_size.to_string());
}
if let Some(page_token) = page_token {
url.query_pairs_mut().append_pair("page_token", &page_token);
}

self.http
.get(url)
.send()
.await
.map_err(|e| ServiceError::Internal(e.into()))?
.json::<TokenInfoSearchResponse>()
.await
.map_err(|e| ServiceError::Internal(e.into()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub enum ParseError {
ParseUuid(#[from] uuid::Error),
#[error("parse error: invalid slice")]
TryFromSlice(#[from] core::array::TryFromSliceError),
#[error("parse error: {0}")]
Custom(String),
}

impl From<ServiceError> for tonic::Status {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod api_key_manager;
pub mod dapp_client;
pub mod clients;
pub mod error;
mod import;
mod proto;
Expand Down
23 changes: 20 additions & 3 deletions multichain-aggregator/multichain-aggregator-logic/src/search.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::{
dapp_client::DappClient,
clients::{dapp::DappClient, token_info::TokenInfoClient},
error::ServiceError,
repository::{addresses, block_ranges, hashes},
types::{
chains::Chain,
dapp::MarketplaceDapp,
search_results::{ChainSearchResult, SearchResults},
token_info::Token,
ChainId,
},
};
Expand All @@ -29,20 +30,22 @@ macro_rules! populate_search_results {
};
}

#[instrument(skip(db, dapp_client, chains), level = "info")]
#[instrument(skip_all, level = "info", fields(query = query))]
pub async fn quick_search(
db: &DatabaseConnection,
dapp_client: &DappClient,
token_info_client: &TokenInfoClient,
query: String,
chains: &[Chain],
) -> Result<SearchResults, ServiceError> {
let raw_query = query.trim();

let (hashes, block_numbers, addresses, dapps) = join!(
let (hashes, block_numbers, addresses, dapps, token_infos) = join!(
hashes::search_by_query(db, raw_query),
block_ranges::search_by_query(db, raw_query),
addresses::search_by_query(db, raw_query),
dapp_client.search_dapps(raw_query),
token_info_client.search_tokens(raw_query, None, Some(100), None),
);

let explorers: BTreeMap<ChainId, String> = chains
Expand Down Expand Up @@ -93,5 +96,19 @@ pub async fn quick_search(
}
}

match token_infos {
Ok(token_infos) => {
let tokens: Vec<Token> = token_infos
.token_infos
.into_iter()
.filter_map(|t| t.try_into().ok())
.collect();
populate_search_results!(results, explorers, tokens, tokens);
}
Err(err) => {
tracing::error!(error = ?err, "failed to search token infos");
}
}

Ok(results)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::ChainId;
use crate::{dapp_client::DappWithChainId, error::ParseError, proto};
use crate::{clients::dapp::DappWithChainId, error::ParseError, proto};

#[derive(Debug)]
pub struct MarketplaceDapp {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ pub mod chains;
pub mod dapp;
pub mod hashes;
pub mod search_results;
pub mod token_info;
pub type ChainId = i64;
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use super::{block_ranges::ChainBlockNumber, ChainId};
use crate::{
proto,
types::{addresses::Address, dapp::MarketplaceDapp, hashes::Hash},
types::{
addresses::Address, block_ranges::ChainBlockNumber, dapp::MarketplaceDapp, hashes::Hash,
token_info::Token, ChainId,
},
};
use std::collections::BTreeMap;

Expand All @@ -13,6 +15,7 @@ pub struct ChainSearchResult {
pub transactions: Vec<Hash>,
pub block_numbers: Vec<ChainBlockNumber>,
pub dapps: Vec<MarketplaceDapp>,
pub tokens: Vec<Token>,
}

impl From<ChainSearchResult> for proto::quick_search_response::ChainSearchResult {
Expand All @@ -24,6 +27,7 @@ impl From<ChainSearchResult> for proto::quick_search_response::ChainSearchResult
transactions: v.transactions.into_iter().map(|t| t.into()).collect(),
block_numbers: v.block_numbers.into_iter().map(|b| b.into()).collect(),
dapps: v.dapps.into_iter().map(|d| d.into()).collect(),
tokens: v.tokens.into_iter().map(|t| t.into()).collect(),
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use super::ChainId;
use crate::{clients, error::ParseError, proto};

#[derive(Debug)]
pub struct Token {
pub address: alloy_primitives::Address,
pub icon_url: String,
pub name: String,
pub symbol: String,
pub chain_id: ChainId,
}

impl TryFrom<clients::token_info::TokenInfo> for Token {
type Error = ParseError;

fn try_from(v: clients::token_info::TokenInfo) -> Result<Self, Self::Error> {
Ok(Self {
address: v.token_address.parse().map_err(ParseError::from)?,
icon_url: v.icon_url,
name: v
.token_name
.ok_or_else(|| ParseError::Custom("token name is required".to_string()))?,
symbol: v
.token_symbol
.ok_or_else(|| ParseError::Custom("token symbol is required".to_string()))?,
chain_id: v.chain_id.parse().map_err(ParseError::from)?,
})
}
}

impl From<Token> for proto::Token {
fn from(v: Token) -> Self {
Self {
address: v.address.to_string(),
name: v.name,
symbol: v.symbol,
icon_url: v.icon_url,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ message MarketplaceDapp {
string short_description = 4;
}

message Token {
string address = 1;
string icon_url = 2;
string name = 3;
string symbol = 4;
}

message BatchImportRequest {
string chain_id = 1;
repeated Address addresses = 2;
Expand All @@ -83,6 +90,7 @@ message QuickSearchResponse {
repeated Hash transactions = 4;
repeated ChainBlockNumber block_numbers = 5;
repeated MarketplaceDapp dapps = 6;
repeated Token tokens = 7;
}

map<string, ChainSearchResult> items = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ definitions:
items:
type: object
$ref: '#/definitions/v1MarketplaceDapp'
tokens:
type: array
items:
type: object
$ref: '#/definitions/v1Token'
protobufAny:
type: object
properties:
Expand Down Expand Up @@ -285,3 +290,14 @@ definitions:
type: object
additionalProperties:
$ref: '#/definitions/QuickSearchResponseChainSearchResult'
v1Token:
type: object
properties:
address:
type: string
icon_url:
type: string
name:
type: string
symbol:
type: string
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use crate::{
use blockscout_chains::BlockscoutChainsClient;
use blockscout_service_launcher::{database, launcher, launcher::LaunchSettings};
use migration::Migrator;
use multichain_aggregator_logic::{dapp_client::DappClient, repository};
use multichain_aggregator_logic::{
clients::{dapp::DappClient, token_info::TokenInfoClient},
repository,
};
use std::sync::Arc;

const SERVICE_NAME: &str = "multichain_aggregator";
Expand Down Expand Up @@ -73,11 +76,13 @@ pub async fn run(settings: Settings) -> Result<(), anyhow::Error> {
repository::chains::upsert_many(&db, blockscout_chains.clone()).await?;

let dapp_client = DappClient::new(settings.service.dapp_client.url);
let token_info_client = TokenInfoClient::new(settings.service.token_info_client.url);

let multichain_aggregator = Arc::new(MultichainAggregator::new(
db,
blockscout_chains,
dapp_client,
token_info_client,
settings.service.api,
));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use crate::{
settings::ApiSettings,
};
use multichain_aggregator_logic::{
self as logic, api_key_manager::ApiKeyManager, dapp_client::DappClient, error::ServiceError,
self as logic,
api_key_manager::ApiKeyManager,
clients::{dapp::DappClient, token_info::TokenInfoClient},
error::ServiceError,
Chain,
};
use sea_orm::DatabaseConnection;
Expand All @@ -20,6 +23,7 @@ pub struct MultichainAggregator {
// Cached chains
chains: Vec<Chain>,
dapp_client: DappClient,
token_info_client: TokenInfoClient,
api_settings: ApiSettings,
}

Expand All @@ -28,13 +32,15 @@ impl MultichainAggregator {
db: DatabaseConnection,
chains: Vec<Chain>,
dapp_client: DappClient,
token_info_client: TokenInfoClient,
api_settings: ApiSettings,
) -> Self {
Self {
db: db.clone(),
api_key_manager: ApiKeyManager::new(db),
chains,
dapp_client,
token_info_client,
api_settings,
}
}
Expand Down Expand Up @@ -112,13 +118,18 @@ impl MultichainAggregatorService for MultichainAggregator {
) -> Result<Response<QuickSearchResponse>, Status> {
let inner = request.into_inner();

let results =
logic::search::quick_search(&self.db, &self.dapp_client, inner.q, &self.chains)
.await
.map_err(|err| {
tracing::error!(error = ?err, "failed to quick search");
Status::internal("failed to quick search")
})?;
let results = logic::search::quick_search(
&self.db,
&self.dapp_client,
&self.token_info_client,
inner.q,
&self.chains,
)
.await
.map_err(|err| {
tracing::error!(error = ?err, "failed to quick search");
Status::internal("failed to quick search")
})?;

Ok(Response::new(results.into()))
}
Expand Down
10 changes: 10 additions & 0 deletions multichain-aggregator/multichain-aggregator-server/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct Settings {
#[serde(deny_unknown_fields)]
pub struct ServiceSettings {
pub dapp_client: DappClientSettings,
pub token_info_client: TokenInfoClientSettings,
#[serde(default)]
pub api: ApiSettings,
}
Expand Down Expand Up @@ -54,6 +55,12 @@ pub struct DappClientSettings {
pub url: Url,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct TokenInfoClientSettings {
pub url: Url,
}

impl ConfigSettings for Settings {
const SERVICE_NAME: &'static str = "MULTICHAIN_AGGREGATOR";
}
Expand All @@ -74,6 +81,9 @@ impl Settings {
dapp_client: DappClientSettings {
url: Url::parse("http://localhost:8050").unwrap(),
},
token_info_client: TokenInfoClientSettings {
url: Url::parse("http://localhost:8051").unwrap(),
},
api: ApiSettings {
default_page_size: default_default_page_size(),
max_page_size: default_max_page_size(),
Expand Down

0 comments on commit 002f740

Please sign in to comment.