0.7.27 +Refactor

This commit is contained in:
2026-05-10 00:33:01 +02:00
parent cb2e8e7096
commit 1f0137b9de
261 changed files with 12308 additions and 8928 deletions

View File

@@ -6,17 +6,17 @@
//! It intentionally stays independent from DEX-specific decoding so every DEX
//! benefits from the same metadata path.
const KB_NATIVE_SOL_MINT_ALIAS: &str = "SOL";
const KB_NATIVE_SOL_SYMBOL: &str = "SOL";
const KB_NATIVE_SOL_NAME: &str = "Solana";
const KB_WRAPPED_SOL_SYMBOL: &str = "WSOL";
const KB_WRAPPED_SOL_NAME: &str = "Wrapped SOL";
const KB_METAPLEX_TOKEN_METADATA_PROGRAM_ID: &str = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
const NATIVE_SOL_MINT_ALIAS: &str = "SOL";
const NATIVE_SOL_SYMBOL: &str = "SOL";
const NATIVE_SOL_NAME: &str = "Solana";
const WRAPPED_SOL_SYMBOL: &str = "WSOL";
const WRAPPED_SOL_NAME: &str = "Wrapped SOL";
const METAPLEX_TOKEN_METADATA_PROGRAM_ID: &str = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
/// Summary produced by a token metadata backfill pass.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct KbTokenMetadataBackfillResult {
pub struct TokenMetadataBackfillResult {
/// Number of tokens considered by this pass.
pub total_token_count: usize,
/// Number of tokens for which a resolution attempt was made.
@@ -43,8 +43,8 @@ pub struct KbTokenMetadataBackfillResult {
pub pair_symbol_missing_token_count: usize,
}
impl KbTokenMetadataBackfillResult {
fn merge(&mut self, other: &KbTokenMetadataBackfillResult) {
impl TokenMetadataBackfillResult {
fn merge(&mut self, other: &TokenMetadataBackfillResult) {
self.total_token_count += other.total_token_count;
self.attempted_token_count += other.attempted_token_count;
self.local_metadata_count += other.local_metadata_count;
@@ -62,17 +62,17 @@ impl KbTokenMetadataBackfillResult {
/// Service that enriches persisted token rows with mint and display metadata.
#[derive(Debug, Clone)]
pub struct KbTokenMetadataBackfillService {
database: std::sync::Arc<crate::KbDatabase>,
pub struct TokenMetadataBackfillService {
database: std::sync::Arc<crate::Database>,
http_pool: std::option::Option<std::sync::Arc<crate::HttpEndpointPool>>,
http_role: std::string::String,
}
impl KbTokenMetadataBackfillService {
impl TokenMetadataBackfillService {
/// Creates a metadata backfill service backed by Solana HTTP RPC.
pub fn new(
http_pool: std::sync::Arc<crate::HttpEndpointPool>,
database: std::sync::Arc<crate::KbDatabase>,
database: std::sync::Arc<crate::Database>,
http_role: std::string::String,
) -> Self {
return Self {
@@ -83,7 +83,7 @@ impl KbTokenMetadataBackfillService {
}
/// Creates a metadata backfill service that can only apply local and DB-derived metadata.
pub fn new_local(database: std::sync::Arc<crate::KbDatabase>) -> Self {
pub fn new_local(database: std::sync::Arc<crate::Database>) -> Self {
return Self {
database,
http_pool: None,
@@ -95,14 +95,14 @@ impl KbTokenMetadataBackfillService {
pub async fn backfill_missing_token_metadata(
&self,
limit: std::option::Option<i64>,
) -> Result<crate::KbTokenMetadataBackfillResult, crate::KbError> {
) -> Result<crate::TokenMetadataBackfillResult, crate::Error> {
let tokens_result =
crate::list_tokens_missing_metadata(self.database.as_ref(), limit).await;
crate::query_tokens_list_missing_metadata(self.database.as_ref(), limit).await;
let tokens = match tokens_result {
Ok(tokens) => tokens,
Err(error) => return Err(error),
};
let mut result = crate::KbTokenMetadataBackfillResult::default();
let mut result = crate::TokenMetadataBackfillResult::default();
for token in tokens {
let token_result = self.backfill_token_metadata_by_mint(token.mint.as_str()).await;
match token_result {
@@ -119,7 +119,7 @@ impl KbTokenMetadataBackfillService {
},
}
}
let pair_symbol_result = crate::kb_refresh_pair_symbols(self.database.as_ref()).await;
let pair_symbol_result = crate::refresh_pair_symbols(self.database.as_ref()).await;
match pair_symbol_result {
Ok(pair_symbol_result) => {
result.pair_symbol_updated_count += pair_symbol_result.updated_pair_count;
@@ -141,14 +141,12 @@ impl KbTokenMetadataBackfillService {
pub async fn backfill_token_metadata_by_mint(
&self,
mint: &str,
) -> Result<crate::KbTokenMetadataBackfillResult, crate::KbError> {
) -> Result<crate::TokenMetadataBackfillResult, crate::Error> {
let trimmed_mint = mint.trim();
if trimmed_mint.is_empty() {
return Err(crate::KbError::Config(
"token metadata mint must not be empty".to_string(),
));
return Err(crate::Error::Config("token metadata mint must not be empty".to_string()));
}
let mut result = crate::KbTokenMetadataBackfillResult {
let mut result = crate::TokenMetadataBackfillResult {
total_token_count: 1,
attempted_token_count: 1,
local_metadata_count: 0,
@@ -162,7 +160,8 @@ impl KbTokenMetadataBackfillService {
pair_symbol_skipped_count: 0,
pair_symbol_missing_token_count: 0,
};
let token_result = crate::get_token_by_mint(self.database.as_ref(), trimmed_mint).await;
let token_result =
crate::query_tokens_get_by_mint(self.database.as_ref(), trimmed_mint).await;
let token_option = match token_result {
Ok(token_option) => token_option,
Err(error) => return Err(error),
@@ -170,17 +169,17 @@ impl KbTokenMetadataBackfillService {
let token_existed = token_option.is_some();
let mut token = match token_option {
Some(token) => token,
None => kb_build_empty_token_dto(trimmed_mint),
None => build_empty_token_dto(trimmed_mint),
};
let original_token = token.clone();
let mut resolved = KbResolvedTokenMetadata::default();
let local_metadata_option = kb_resolve_local_token_metadata(trimmed_mint);
let mut resolved = ResolvedTokenMetadata::default();
let local_metadata_option = resolve_local_token_metadata(trimmed_mint);
if let Some(local_metadata) = local_metadata_option {
result.local_metadata_count += 1;
resolved.merge(local_metadata);
}
let pump_fun_metadata_result =
kb_resolve_pump_fun_metadata_from_decoded_events(self.database.as_ref(), trimmed_mint)
resolve_pump_fun_metadata_from_decoded_events(self.database.as_ref(), trimmed_mint)
.await;
match pump_fun_metadata_result {
Ok(pump_fun_metadata_option) => {
@@ -199,7 +198,7 @@ impl KbTokenMetadataBackfillService {
},
}
if let Some(http_pool) = &self.http_pool {
let mint_account_result = kb_fetch_mint_account_metadata(
let mint_account_result = fetch_mint_account_metadata(
http_pool.as_ref(),
self.http_role.as_str(),
trimmed_mint,
@@ -221,8 +220,8 @@ impl KbTokenMetadataBackfillService {
);
},
}
if kb_should_try_metaplex_metadata(&token, &resolved) {
let metaplex_result = kb_fetch_metaplex_metadata(
if should_try_metaplex_metadata(&token, &resolved) {
let metaplex_result = fetch_metaplex_metadata(
http_pool.as_ref(),
self.http_role.as_str(),
trimmed_mint,
@@ -246,14 +245,14 @@ impl KbTokenMetadataBackfillService {
}
}
}
let changed = kb_apply_resolved_metadata_to_token(&mut token, &resolved);
let changed = apply_resolved_metadata_to_token(&mut token, &resolved);
if !token_existed && !changed {
result.skipped_token_count += 1;
return Ok(result);
}
if changed || token.id.is_none() {
token.updated_at = chrono::Utc::now();
let upsert_result = crate::upsert_token(self.database.as_ref(), &token).await;
let upsert_result = crate::query_tokens_upsert(self.database.as_ref(), &token).await;
match upsert_result {
Ok(_) => {
if changed || original_token.id.is_none() {
@@ -272,7 +271,7 @@ impl KbTokenMetadataBackfillService {
}
#[derive(Debug, Clone, Default)]
struct KbResolvedTokenMetadata {
struct ResolvedTokenMetadata {
symbol: std::option::Option<std::string::String>,
name: std::option::Option<std::string::String>,
decimals: std::option::Option<u8>,
@@ -280,19 +279,18 @@ struct KbResolvedTokenMetadata {
is_quote_token: std::option::Option<bool>,
}
impl KbResolvedTokenMetadata {
fn merge(&mut self, other: KbResolvedTokenMetadata) {
if kb_option_string_is_missing(self.symbol.as_deref()) && other.symbol.is_some() {
impl ResolvedTokenMetadata {
fn merge(&mut self, other: ResolvedTokenMetadata) {
if option_string_is_missing(self.symbol.as_deref()) && other.symbol.is_some() {
self.symbol = other.symbol;
}
if kb_option_string_is_missing(self.name.as_deref()) && other.name.is_some() {
if option_string_is_missing(self.name.as_deref()) && other.name.is_some() {
self.name = other.name;
}
if self.decimals.is_none() && other.decimals.is_some() {
self.decimals = other.decimals;
}
if kb_option_string_is_missing(self.token_program.as_deref())
&& other.token_program.is_some()
if option_string_is_missing(self.token_program.as_deref()) && other.token_program.is_some()
{
self.token_program = other.token_program;
}
@@ -302,8 +300,8 @@ impl KbResolvedTokenMetadata {
}
}
fn kb_build_empty_token_dto(mint: &str) -> crate::KbTokenDto {
return crate::KbTokenDto::new(
fn build_empty_token_dto(mint: &str) -> crate::TokenDto {
return crate::TokenDto::new(
mint.to_string(),
None,
None,
@@ -313,11 +311,11 @@ fn kb_build_empty_token_dto(mint: &str) -> crate::KbTokenDto {
);
}
fn kb_resolve_local_token_metadata(mint: &str) -> std::option::Option<KbResolvedTokenMetadata> {
if mint == KB_NATIVE_SOL_MINT_ALIAS {
return Some(KbResolvedTokenMetadata {
symbol: Some(KB_NATIVE_SOL_SYMBOL.to_string()),
name: Some(KB_NATIVE_SOL_NAME.to_string()),
fn resolve_local_token_metadata(mint: &str) -> std::option::Option<ResolvedTokenMetadata> {
if mint == NATIVE_SOL_MINT_ALIAS {
return Some(ResolvedTokenMetadata {
symbol: Some(NATIVE_SOL_SYMBOL.to_string()),
name: Some(NATIVE_SOL_NAME.to_string()),
decimals: Some(9),
token_program: Some(crate::SYSTEM_PROGRAM_ID.to_string()),
is_quote_token: Some(true),
@@ -325,9 +323,9 @@ fn kb_resolve_local_token_metadata(mint: &str) -> std::option::Option<KbResolved
}
let wsol_mint = crate::WSOL_MINT_ID.to_string();
if mint == wsol_mint {
return Some(KbResolvedTokenMetadata {
symbol: Some(KB_WRAPPED_SOL_SYMBOL.to_string()),
name: Some(KB_WRAPPED_SOL_NAME.to_string()),
return Some(ResolvedTokenMetadata {
symbol: Some(WRAPPED_SOL_SYMBOL.to_string()),
name: Some(WRAPPED_SOL_NAME.to_string()),
decimals: Some(9),
token_program: Some(crate::SPL_TOKEN_PROGRAM_ID.to_string()),
is_quote_token: Some(true),
@@ -336,11 +334,13 @@ fn kb_resolve_local_token_metadata(mint: &str) -> std::option::Option<KbResolved
return None;
}
async fn kb_resolve_pump_fun_metadata_from_decoded_events(
database: &crate::KbDatabase,
async fn resolve_pump_fun_metadata_from_decoded_events(
database: &crate::Database,
mint: &str,
) -> Result<std::option::Option<KbResolvedTokenMetadata>, crate::KbError> {
let payload_result = crate::get_latest_pump_fun_create_payload_by_mint(database, mint).await;
) -> Result<std::option::Option<ResolvedTokenMetadata>, crate::Error> {
let payload_result =
crate::query_dex_decoded_events_get_latest_pump_fun_create_payload_by_mint(database, mint)
.await;
let payload_option = match payload_result {
Ok(payload_option) => payload_option,
Err(error) => return Err(error),
@@ -353,25 +353,25 @@ async fn kb_resolve_pump_fun_metadata_from_decoded_events(
let payload = match payload_result {
Ok(payload) => payload,
Err(error) => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"cannot parse pump.fun metadata payload for mint '{}': {}",
mint, error
)));
},
};
return Ok(kb_extract_pump_fun_metadata_from_payload(&payload));
return Ok(extract_pump_fun_metadata_from_payload(&payload));
}
fn kb_extract_pump_fun_metadata_from_payload(
fn extract_pump_fun_metadata_from_payload(
payload: &serde_json::Value,
) -> std::option::Option<KbResolvedTokenMetadata> {
let symbol = kb_extract_string_by_candidate_keys(payload, &["symbol", "tokenSymbol"]);
let name = kb_extract_string_by_candidate_keys(payload, &["name", "tokenName"]);
let decimals = kb_extract_u8_by_candidate_keys(payload, &["decimals", "tokenDecimals"]);
) -> std::option::Option<ResolvedTokenMetadata> {
let symbol = extract_string_by_candidate_keys(payload, &["symbol", "tokenSymbol"]);
let name = extract_string_by_candidate_keys(payload, &["name", "tokenName"]);
let decimals = extract_u8_by_candidate_keys(payload, &["decimals", "tokenDecimals"]);
if symbol.is_none() && name.is_none() && decimals.is_none() {
return None;
}
return Some(KbResolvedTokenMetadata {
return Some(ResolvedTokenMetadata {
symbol,
name,
decimals,
@@ -380,23 +380,20 @@ fn kb_extract_pump_fun_metadata_from_payload(
});
}
fn kb_should_try_metaplex_metadata(
token: &crate::KbTokenDto,
resolved: &KbResolvedTokenMetadata,
) -> bool {
let missing_symbol = kb_option_string_is_missing(token.symbol.as_deref())
&& kb_option_string_is_missing(resolved.symbol.as_deref());
let missing_name = kb_option_string_is_missing(token.name.as_deref())
&& kb_option_string_is_missing(resolved.name.as_deref());
fn should_try_metaplex_metadata(token: &crate::TokenDto, resolved: &ResolvedTokenMetadata) -> bool {
let missing_symbol = option_string_is_missing(token.symbol.as_deref())
&& option_string_is_missing(resolved.symbol.as_deref());
let missing_name = option_string_is_missing(token.name.as_deref())
&& option_string_is_missing(resolved.name.as_deref());
return missing_symbol || missing_name;
}
fn kb_apply_resolved_metadata_to_token(
token: &mut crate::KbTokenDto,
resolved: &KbResolvedTokenMetadata,
fn apply_resolved_metadata_to_token(
token: &mut crate::TokenDto,
resolved: &ResolvedTokenMetadata,
) -> bool {
let mut changed = false;
if kb_option_string_is_missing(token.symbol.as_deref()) {
if option_string_is_missing(token.symbol.as_deref()) {
if let Some(symbol) = &resolved.symbol {
if !symbol.trim().is_empty() {
token.symbol = Some(symbol.trim().to_string());
@@ -404,7 +401,7 @@ fn kb_apply_resolved_metadata_to_token(
}
}
}
if kb_option_string_is_missing(token.name.as_deref()) {
if option_string_is_missing(token.name.as_deref()) {
if let Some(name) = &resolved.name {
if !name.trim().is_empty() {
token.name = Some(name.trim().to_string());
@@ -433,11 +430,11 @@ fn kb_apply_resolved_metadata_to_token(
return changed;
}
async fn kb_fetch_mint_account_metadata(
async fn fetch_mint_account_metadata(
http_pool: &crate::HttpEndpointPool,
http_role: &str,
mint: &str,
) -> Result<std::option::Option<KbResolvedTokenMetadata>, crate::KbError> {
) -> Result<std::option::Option<ResolvedTokenMetadata>, crate::Error> {
let config = serde_json::json!({
"encoding": "jsonParsed",
"commitment": "confirmed"
@@ -467,9 +464,9 @@ async fn kb_fetch_mint_account_metadata(
.and_then(|parsed| return parsed.get("info"))
.and_then(|info| return info.get("decimals"))
.and_then(serde_json::Value::as_u64)
.and_then(kb_u64_to_u8);
let token_2022_metadata = kb_extract_token_2022_metadata_from_mint_account_value(value);
let mut resolved = KbResolvedTokenMetadata {
.and_then(u64_to_u8);
let token_2022_metadata = extract_token_2022_metadata_from_mint_account_value(value);
let mut resolved = ResolvedTokenMetadata {
symbol: None,
name: None,
decimals,
@@ -489,9 +486,9 @@ async fn kb_fetch_mint_account_metadata(
return Ok(Some(resolved));
}
fn kb_extract_token_2022_metadata_from_mint_account_value(
fn extract_token_2022_metadata_from_mint_account_value(
value: &serde_json::Value,
) -> std::option::Option<KbResolvedTokenMetadata> {
) -> std::option::Option<ResolvedTokenMetadata> {
let extensions_option = value
.get("data")
.and_then(|data| return data.get("parsed"))
@@ -519,12 +516,12 @@ fn kb_extract_token_2022_metadata_from_mint_account_value(
Some(state) => state,
None => continue,
};
let symbol = kb_extract_string_by_candidate_keys(state, &["symbol", "tokenSymbol"]);
let name = kb_extract_string_by_candidate_keys(state, &["name", "tokenName"]);
let symbol = extract_string_by_candidate_keys(state, &["symbol", "tokenSymbol"]);
let name = extract_string_by_candidate_keys(state, &["name", "tokenName"]);
if symbol.is_none() && name.is_none() {
continue;
}
return Some(KbResolvedTokenMetadata {
return Some(ResolvedTokenMetadata {
symbol,
name,
decimals: None,
@@ -535,12 +532,12 @@ fn kb_extract_token_2022_metadata_from_mint_account_value(
return None;
}
async fn kb_fetch_metaplex_metadata(
async fn fetch_metaplex_metadata(
http_pool: &crate::HttpEndpointPool,
http_role: &str,
mint: &str,
) -> Result<std::option::Option<KbResolvedTokenMetadata>, crate::KbError> {
let metadata_address_result = kb_derive_metaplex_metadata_address(mint);
) -> Result<std::option::Option<ResolvedTokenMetadata>, crate::Error> {
let metadata_address_result = derive_metaplex_metadata_address(mint);
let metadata_address = match metadata_address_result {
Ok(metadata_address) => metadata_address,
Err(error) => return Err(error),
@@ -564,29 +561,29 @@ async fn kb_fetch_metaplex_metadata(
if value.is_null() {
return Ok(None);
}
let data_text_option = kb_extract_base64_account_data(value);
let data_text_option = extract_base64_account_data(value);
let data_text = match data_text_option {
Some(data_text) => data_text,
None => return Ok(None),
};
let bytes_result = kb_decode_base64_standard(data_text.as_str());
let bytes_result = decode_base64_standard(data_text.as_str());
let bytes = match bytes_result {
Ok(bytes) => bytes,
Err(error) => return Err(error),
};
return Ok(kb_parse_metaplex_token_metadata_account(&bytes));
return Ok(parse_metaplex_token_metadata_account(&bytes));
}
fn kb_derive_metaplex_metadata_address(mint: &str) -> Result<std::string::String, crate::KbError> {
fn derive_metaplex_metadata_address(mint: &str) -> Result<std::string::String, crate::Error> {
let program_id_result = <solana_sdk::pubkey::Pubkey as std::str::FromStr>::from_str(
KB_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
METAPLEX_TOKEN_METADATA_PROGRAM_ID,
);
let program_id = match program_id_result {
Ok(program_id) => program_id,
Err(error) => {
return Err(crate::KbError::Config(format!(
return Err(crate::Error::Config(format!(
"invalid metaplex metadata program id '{}': {}",
KB_METAPLEX_TOKEN_METADATA_PROGRAM_ID, error
METAPLEX_TOKEN_METADATA_PROGRAM_ID, error
)));
},
};
@@ -594,10 +591,7 @@ fn kb_derive_metaplex_metadata_address(mint: &str) -> Result<std::string::String
let mint_pubkey = match mint_pubkey_result {
Ok(mint_pubkey) => mint_pubkey,
Err(error) => {
return Err(crate::KbError::Config(format!(
"invalid token mint '{}': {}",
mint, error
)));
return Err(crate::Error::Config(format!("invalid token mint '{}': {}", mint, error)));
},
};
let (metadata_address, _) = solana_sdk::pubkey::Pubkey::find_program_address(
@@ -607,7 +601,7 @@ fn kb_derive_metaplex_metadata_address(mint: &str) -> Result<std::string::String
return Ok(metadata_address.to_string());
}
fn kb_extract_base64_account_data(
fn extract_base64_account_data(
value: &serde_json::Value,
) -> std::option::Option<std::string::String> {
let data = match value.get("data") {
@@ -624,28 +618,28 @@ fn kb_extract_base64_account_data(
return data.as_str().map(|value| return value.to_string());
}
fn kb_parse_metaplex_token_metadata_account(
fn parse_metaplex_token_metadata_account(
bytes: &[u8],
) -> std::option::Option<KbResolvedTokenMetadata> {
) -> std::option::Option<ResolvedTokenMetadata> {
if bytes.len() < 69 {
return None;
}
let mut offset = 65usize;
let name_option = kb_read_borsh_string(bytes, &mut offset);
let symbol_option = kb_read_borsh_string(bytes, &mut offset);
let _uri_option = kb_read_borsh_string(bytes, &mut offset);
let name_option = read_borsh_string(bytes, &mut offset);
let symbol_option = read_borsh_string(bytes, &mut offset);
let _uri_option = read_borsh_string(bytes, &mut offset);
let name = match name_option {
Some(name) => kb_clean_metaplex_string(name.as_str()),
Some(name) => clean_metaplex_string(name.as_str()),
None => None,
};
let symbol = match symbol_option {
Some(symbol) => kb_clean_metaplex_string(symbol.as_str()),
Some(symbol) => clean_metaplex_string(symbol.as_str()),
None => None,
};
if name.is_none() && symbol.is_none() {
return None;
}
return Some(KbResolvedTokenMetadata {
return Some(ResolvedTokenMetadata {
symbol,
name,
decimals: None,
@@ -654,10 +648,7 @@ fn kb_parse_metaplex_token_metadata_account(
});
}
fn kb_read_borsh_string(
bytes: &[u8],
offset: &mut usize,
) -> std::option::Option<std::string::String> {
fn read_borsh_string(bytes: &[u8], offset: &mut usize) -> std::option::Option<std::string::String> {
if bytes.len() < *offset + 4 {
return None;
}
@@ -680,12 +671,12 @@ fn kb_read_borsh_string(
}
}
fn kb_clean_metaplex_string(value: &str) -> std::option::Option<std::string::String> {
fn clean_metaplex_string(value: &str) -> std::option::Option<std::string::String> {
let cleaned = value.trim_matches(char::from(0)).trim().to_string();
if cleaned.is_empty() { return None } else { return Some(cleaned) }
}
fn kb_decode_base64_standard(text: &str) -> Result<std::vec::Vec<u8>, crate::KbError> {
fn decode_base64_standard(text: &str) -> Result<std::vec::Vec<u8>, crate::Error> {
let mut output = std::vec::Vec::new();
let mut group = [0u8; 4];
let mut group_len = 0usize;
@@ -694,11 +685,11 @@ fn kb_decode_base64_standard(text: &str) -> Result<std::vec::Vec<u8>, crate::KbE
if byte == b'\r' || byte == b'\n' || byte == b'\t' || byte == b' ' {
continue;
}
let value_option = kb_base64_value(byte);
let value_option = base64_value(byte);
let value = match value_option {
Some(value) => value,
None => {
return Err(crate::KbError::Json(format!(
return Err(crate::Error::Json(format!(
"invalid base64 character '{}'",
byte as char
)));
@@ -723,14 +714,14 @@ fn kb_decode_base64_standard(text: &str) -> Result<std::vec::Vec<u8>, crate::KbE
}
}
if group_len != 0 {
return Err(crate::KbError::Json(
return Err(crate::Error::Json(
"invalid base64 length: trailing partial group".to_string(),
));
}
return Ok(output);
}
fn kb_base64_value(byte: u8) -> std::option::Option<u8> {
fn base64_value(byte: u8) -> std::option::Option<u8> {
match byte {
b'A'..=b'Z' => return Some(byte - b'A'),
b'a'..=b'z' => return Some(byte - b'a' + 26),
@@ -742,7 +733,7 @@ fn kb_base64_value(byte: u8) -> std::option::Option<u8> {
}
}
fn kb_extract_string_by_candidate_keys(
fn extract_string_by_candidate_keys(
payload: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
@@ -766,7 +757,7 @@ fn kb_extract_string_by_candidate_keys(
return None;
}
fn kb_extract_u8_by_candidate_keys(
fn extract_u8_by_candidate_keys(
payload: &serde_json::Value,
candidate_keys: &[&str],
) -> std::option::Option<u8> {
@@ -777,7 +768,7 @@ fn kb_extract_u8_by_candidate_keys(
None => continue,
};
if let Some(number) = value.as_u64() {
let converted = kb_u64_to_u8(number);
let converted = u64_to_u8(number);
if converted.is_some() {
return converted;
}
@@ -788,7 +779,7 @@ fn kb_extract_u8_by_candidate_keys(
Ok(parsed) => parsed,
Err(_) => continue,
};
let converted = kb_u64_to_u8(parsed);
let converted = u64_to_u8(parsed);
if converted.is_some() {
return converted;
}
@@ -797,14 +788,14 @@ fn kb_extract_u8_by_candidate_keys(
return None;
}
fn kb_option_string_is_missing(value: std::option::Option<&str>) -> bool {
fn option_string_is_missing(value: std::option::Option<&str>) -> bool {
match value {
Some(value) => return value.trim().is_empty(),
None => return true,
}
}
fn kb_u64_to_u8(value: u64) -> std::option::Option<u8> {
fn u64_to_u8(value: u64) -> std::option::Option<u8> {
let converted_result = u8::try_from(value);
match converted_result {
Ok(converted) => return Some(converted),
@@ -819,29 +810,29 @@ mod tests {
bytes.push(4_u8);
bytes.extend_from_slice(&[1_u8; 32]);
bytes.extend_from_slice(&[2_u8; 32]);
kb_push_borsh_string(&mut bytes, name);
kb_push_borsh_string(&mut bytes, symbol);
kb_push_borsh_string(&mut bytes, uri);
push_borsh_string(&mut bytes, name);
push_borsh_string(&mut bytes, symbol);
push_borsh_string(&mut bytes, uri);
return bytes;
}
fn kb_push_borsh_string(bytes: &mut std::vec::Vec<u8>, value: &str) {
fn push_borsh_string(bytes: &mut std::vec::Vec<u8>, value: &str) {
let length = value.len() as u32;
bytes.extend_from_slice(&length.to_le_bytes());
bytes.extend_from_slice(value.as_bytes());
}
async fn make_database() -> std::sync::Arc<crate::KbDatabase> {
async fn make_database() -> std::sync::Arc<crate::Database> {
let tempdir_result = tempfile::tempdir();
let tempdir = match tempdir_result {
Ok(tempdir) => tempdir,
Err(error) => panic!("tempdir must succeed: {}", error),
};
let database_path = tempdir.path().join("token_metadata.sqlite3");
let config = crate::KbDatabaseConfig {
let config = crate::DatabaseConfig {
enabled: true,
backend: crate::KbDatabaseBackend::Sqlite,
sqlite: crate::KbSqliteDatabaseConfig {
backend: crate::DatabaseBackend::Sqlite,
sqlite: crate::SqliteDatabaseConfig {
path: database_path.to_string_lossy().to_string(),
create_if_missing: true,
busy_timeout_ms: 5000,
@@ -850,7 +841,7 @@ mod tests {
use_wal: true,
},
};
let database_result = crate::KbDatabase::connect_and_initialize(&config).await;
let database_result = crate::Database::connect_and_initialize(&config).await;
let database = match database_result {
Ok(database) => database,
Err(error) => panic!("database init must succeed: {}", error),
@@ -861,7 +852,7 @@ mod tests {
#[test]
fn local_metadata_resolves_wsol() {
let mint = crate::WSOL_MINT_ID.to_string();
let metadata_option = super::kb_resolve_local_token_metadata(mint.as_str());
let metadata_option = super::resolve_local_token_metadata(mint.as_str());
let metadata = match metadata_option {
Some(metadata) => metadata,
None => panic!("WSOL metadata must resolve"),
@@ -880,7 +871,7 @@ mod tests {
"name": "Pump Example",
"uri": "https://example.invalid/pump.json"
});
let metadata_option = super::kb_extract_pump_fun_metadata_from_payload(&payload);
let metadata_option = super::extract_pump_fun_metadata_from_payload(&payload);
let metadata = match metadata_option {
Some(metadata) => metadata,
None => panic!("pump.fun metadata must parse"),
@@ -908,8 +899,7 @@ mod tests {
}
}
});
let metadata_option =
super::kb_extract_token_2022_metadata_from_mint_account_value(&account);
let metadata_option = super::extract_token_2022_metadata_from_mint_account_value(&account);
let metadata = match metadata_option {
Some(metadata) => metadata,
None => panic!("token-2022 metadata must parse"),
@@ -920,7 +910,7 @@ mod tests {
#[test]
fn base64_decoder_decodes_standard_payload() {
let decoded_result = super::kb_decode_base64_standard("AQIDBA==");
let decoded_result = super::decode_base64_standard("AQIDBA==");
let decoded = match decoded_result {
Ok(decoded) => decoded,
Err(error) => panic!("base64 decode must succeed: {}", error),
@@ -935,7 +925,7 @@ mod tests {
"EXM\0\0",
"https://example.invalid/token.json",
);
let metadata_option = super::kb_parse_metaplex_token_metadata_account(&bytes);
let metadata_option = super::parse_metaplex_token_metadata_account(&bytes);
let metadata = match metadata_option {
Some(metadata) => metadata,
None => panic!("metadata must parse"),
@@ -947,7 +937,7 @@ mod tests {
#[tokio::test]
async fn local_backfill_updates_wsol_without_http() {
let database = make_database().await;
let token = crate::KbTokenDto::new(
let token = crate::TokenDto::new(
crate::WSOL_MINT_ID.to_string(),
None,
None,
@@ -955,20 +945,22 @@ mod tests {
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let upsert_result = crate::upsert_token(database.as_ref(), &token).await;
let upsert_result = crate::query_tokens_upsert(database.as_ref(), &token).await;
if let Err(error) = upsert_result {
panic!("token upsert must succeed: {}", error);
}
let service = crate::KbTokenMetadataBackfillService::new_local(database.clone());
let service = crate::TokenMetadataBackfillService::new_local(database.clone());
let result = service.backfill_missing_token_metadata(Some(10)).await;
let result = match result {
Ok(result) => result,
Err(error) => panic!("metadata backfill must succeed: {}", error),
};
assert_eq!(result.updated_token_count, 1);
let fetched_result =
crate::get_token_by_mint(database.as_ref(), crate::WSOL_MINT_ID.to_string().as_str())
.await;
let fetched_result = crate::query_tokens_get_by_mint(
database.as_ref(),
crate::WSOL_MINT_ID.to_string().as_str(),
)
.await;
let fetched_option = match fetched_result {
Ok(fetched_option) => fetched_option,
Err(error) => panic!("token fetch must succeed: {}", error),
@@ -986,7 +978,7 @@ mod tests {
#[tokio::test]
async fn local_backfill_does_not_overwrite_existing_display_metadata() {
let database = make_database().await;
let token = crate::KbTokenDto::new(
let token = crate::TokenDto::new(
crate::WSOL_MINT_ID.to_string(),
Some("SOL".to_string()),
Some("Custom Sol".to_string()),
@@ -994,20 +986,22 @@ mod tests {
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let upsert_result = crate::upsert_token(database.as_ref(), &token).await;
let upsert_result = crate::query_tokens_upsert(database.as_ref(), &token).await;
if let Err(error) = upsert_result {
panic!("token upsert must succeed: {}", error);
}
let service = crate::KbTokenMetadataBackfillService::new_local(database.clone());
let service = crate::TokenMetadataBackfillService::new_local(database.clone());
let result = service
.backfill_token_metadata_by_mint(crate::WSOL_MINT_ID.to_string().as_str())
.await;
if let Err(error) = result {
panic!("metadata backfill must succeed: {}", error);
}
let fetched_result =
crate::get_token_by_mint(database.as_ref(), crate::WSOL_MINT_ID.to_string().as_str())
.await;
let fetched_result = crate::query_tokens_get_by_mint(
database.as_ref(),
crate::WSOL_MINT_ID.to_string().as_str(),
)
.await;
let fetched_option = match fetched_result {
Ok(fetched_option) => fetched_option,
Err(error) => panic!("token fetch must succeed: {}", error),