This commit is contained in:
2026-05-05 05:03:11 +02:00
parent 3e994995d7
commit f2c227e08f
132 changed files with 5767 additions and 4461 deletions

View File

@@ -55,3 +55,4 @@
0.7.22 - Ajout dune première fenêtre `Demo Pipeline` dans `kb_app` pour linspection en lecture seule du pipeline `0.7.x`, avec recherche par signature, token mint, pair id ou pool address, affichage structuré des transactions résolues, événements DEX décodés, pools, paires, listings, launch origins, pool origins, wallets et holdings observés, trade events, pair metrics, candles et signaux analytiques déjà persistés, ainsi que conservation dune instance partagée de la base SQLite pour éviter la réouverture et la réinitialisation du schéma à chaque commande UI
0.7.23 - Ajout du pilotage UI du backfill historique ciblé par `token mint` dans `kb_app`, avec saisie du rôle HTTP et des limites de signatures, affichage du résumé de backfill, réinspection automatique du token dans `Demo Pipeline` lorsque des objets persistés sont effectivement reconstruits, et gestion explicite du cas où le backfill réussit sans matérialiser de token exploitable dans la base locale
0.7.24 - Ajout de laffichage graphique des candles / OHLCV dans `kb_app` via `echarts`, avec sélection de paire et de timeframe, rendu chandelier + volume, et prise en charge des candles matérialisées ou régénérées à la demande depuis `Demo Pipeline`
0.7.25 - En cours : préparation de lenrichissement metadata des tokens, avec résolution locale limitée à SOL / WSOL, résolution des autres mints via comptes on-chain, Token-2022, Metaplex ou payloads DEX, et conservation explicite des cas non résolus

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "0.7.24"
version = "0.7.25"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
@@ -70,3 +70,17 @@ lto = true # Enables link-time-optimizations.
opt-level = 3 # s Prioritizes small binary size. Use `3` if you prefer speed.
panic = "abort" # Higher performance by disabling panic handlers.
strip = true # Ensures debug symbols are removed.
[workspace.lints.clippy]
unwrap_used = "deny"
expect_used = "deny"
implicit_return = "deny"
needless_return = "allow"
useless_vec = "deny"
question_mark = "deny"
question_mark_used = "deny"
needless_match = "allow"
manual_ok_err = "allow"
manual_unwrap_or = "allow"
manual_map = "allow"
match_like_matches_macro = "allow"

View File

@@ -696,19 +696,20 @@ Réalisé :
- intégration du rendu graphique directement dans `Demo Pipeline`.
### 6.057. Version `0.7.25` — Enrichissement metadata des tokens
Objectif : rendre le catalogue local lisible et exploitable en associant les mints à des métadonnées minimales fiables.
Réalisé :
À faire :
- ajouter une couche `token_metadata` dans `kb_lib`, distincte du décodage DEX et de lUI,
- enrichir `kb_tokens` avec `symbol`, `name`, `decimals`, `metadata_uri`, `metadata_source`, `metadata_status` et `metadata_updated_at` lorsque le schéma le permet,
- ajouter une résolution locale des mints connus (`SOL`, `USDC`, `USDT`, `RAY`, `JitoSOL` et autres références stables),
- réutiliser les payloads déjà décodés des événements `pump_fun.create` / `pump_fun.create_v2_token` pour alimenter `name`, `symbol`, `uri` et `creator`,
- ajouter une résolution Metaplex Token Metadata PDA pour les tokens SPL classiques,
- ajouter une résolution Token-2022 via metadata pointer / extensions lorsque le mint utilise ce standard,
- stocker explicitement les cas non résolus afin déviter les tentatives répétées inutiles,
- exposer une commande UI ou un service de backfill permettant denrichir les tokens déjà présents en base,
- maintenir une politique de priorité claire entre sources : `known_mint`, `pump_fun_create`, `metaplex`, `token_2022`, puis `unresolved`.
- Ajout :
- Relecture locale du pipeline à partir des transactions brutes persistantes de la chaîne.
- Actualisation optionnelle des métadonnées de jetons manquantes lors de la relecture locale.
- Reconstruction des symboles de paires à partir des métadonnées des jetons.
- Commandes d'interface utilisateur dans le pipeline de démonstration 2 pour la relecture locale.
- Flux de travail d'actualisation du catalogue de jetons/paires piloté par les métadonnées.
- Modifications :
- Les symboles de paires sont désormais dérivés comme `BASE/QUOTE` lorsque les deux symboles de jetons sont disponibles.
- L'actualisation des métadonnées évite de nécessiter un remplissage complet de la blockchain lorsque les données de transaction brutes existent déjà localement.
- Corrections :
- Suppression des cycles complets de suppression/remplissage répétés pour les métadonnées et les entités locales dérivées.
- Conservation de l'accès SQL dans les modules de requêtes de base de données au lieu du SQL brut au niveau du service.
### 6.058. Version `0.7.26` — Validation multi-DEX et non-régression du pipeline
Objectif : vérifier que les connecteurs déjà branchés restent cohérents avant douvrir la phase danalyse `0.8.x`.
@@ -933,7 +934,7 @@ Le projet doit maintenir au minimum :
La priorité immédiate est désormais la suivante :
1. ajouter lenrichissement metadata des tokens afin que le catalogue affiche au minimum les symboles/noms connus et les métadonnées résolues,
1. ajouter lenrichissement metadata des tokens afin que le catalogue affiche au minimum les symboles/noms résolus, sans hardcoder de mints hors SOL / WSOL,
2. rejouer une campagne de validation multi-DEX sur bases neuves pour `pump_fun`, `pump_swap`, `raydium_cpmm` et `raydium_clmm`,
3. constituer un corpus ciblé pour `raydium_amm_v4` legacy au lieu de sappuyer sur des labels Raydium trop génériques,
4. conserver les événements non-candle enrichis en payload pour lanalyse future, sans créer de trades invalides,
@@ -942,4 +943,3 @@ La priorité immédiate est désormais la suivante :
7. stabiliser lergonomie, les filtres et la navigation de lUI dinspection,
8. préparer ensuite louverture de `0.8.x` pour lanalyse, les filtres, les patterns et les projections graphiques,
9. préparer enfin Yellowstone gRPC comme extension de capacité, et non comme remplacement du socle HTTP / WS existant.

38
clippy.toml Normal file
View File

@@ -0,0 +1,38 @@
# file: clippy.toml
msrv = "1.85.0"
# The project favors explicit control flow and visible intent.
# These settings complement the coding rules already enforced manually
# in code review: no `?`, no `unwrap`, no `expect`, explicit error paths.
too-many-arguments-threshold = 16
type-complexity-threshold = 250
single-char-binding-names-threshold = 3
trivial-copy-size-limit = 16
pass-by-value-size-limit = 256
stack-size-threshold = 512000
vec-box-size-threshold = 4096
max-fn-params-bools = 2
max-include-file-size = 1048576
cognitive-complexity-threshold = 25
too-large-for-stack = 2048
enum-variant-size-threshold = 200
large-error-threshold = 128
avoid-breaking-exported-api = true
allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-useless-vec-in-tests = true
disallowed-macros = []
disallowed-methods = []
disallowed-names = ["foo", "bar", "baz", "tmp"]
disallowed-types = []
allowed-idents-below-min-chars = [
"id",
"tx",
"rx",
"ms",
"pcm",
"vad",
]

View File

@@ -26,9 +26,15 @@
<div class="container vcentered sketchy-translucid py-4">
<div class="row g-4">
<div class="col-12 col-xxl-4">
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h1 class="h4 mb-3">Catalogue local</h1>
<div class="accordion" id="demoPipeline2LeftAccordion">
<div class="accordion-item border-0 shadow-sm mb-3">
<h1 class="accordion-header" id="demoPipeline2CatalogHeading">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2CatalogCollapse" aria-expanded="true" aria-controls="demoPipeline2CatalogCollapse">
Catalogue local
</button>
</h1>
<div id="demoPipeline2CatalogCollapse" class="accordion-collapse collapse show" aria-labelledby="demoPipeline2CatalogHeading" data-bs-parent="#demoPipeline2LeftAccordion">
<div class="accordion-body">
<div class="d-flex gap-2 mb-3">
<button id="demoPipeline2RefreshCatalogButton" type="button" class="btn btn-primary">
Refresh catalog
@@ -51,11 +57,16 @@
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h2 class="h5 mb-3">Backfill ciblé</h2>
<div class="accordion-item border-0 shadow-sm mb-3">
<h2 class="accordion-header" id="demoPipeline2BackfillHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2BackfillCollapse" aria-expanded="false" aria-controls="demoPipeline2BackfillCollapse">
Backfill ciblé
</button>
</h2>
<div id="demoPipeline2BackfillCollapse" class="accordion-collapse collapse" aria-labelledby="demoPipeline2BackfillHeading" data-bs-parent="#demoPipeline2LeftAccordion">
<div class="accordion-body">
<div class="mb-3">
<label for="demoPipeline2HttpRoleInput" class="form-label">HTTP role</label>
<input id="demoPipeline2HttpRoleInput" type="text" class="form-control" value="history_backfill" spellcheck="false" />
@@ -100,11 +111,54 @@
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5 mb-3">Chargement candles</h2>
<div class="accordion-item border-0 shadow-sm mb-3">
<h2 class="accordion-header" id="demoPipeline2ReplayHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2ReplayCollapse" aria-expanded="false" aria-controls="demoPipeline2ReplayCollapse">
Replay local
</button>
</h2>
<div id="demoPipeline2ReplayCollapse" class="accordion-collapse collapse" aria-labelledby="demoPipeline2ReplayHeading" data-bs-parent="#demoPipeline2LeftAccordion">
<div class="accordion-body">
<p class="small text-body-secondary mb-3">
Rejoue le pipeline depuis les transactions déjà stockées en base sans refaire de getTransaction.
</p>
<div class="mb-3">
<label for="demoPipeline2ReplayLimitInput" class="form-label">Transaction limit</label>
<input id="demoPipeline2ReplayLimitInput" type="number" min="1" step="1" class="form-control" value="10000" />
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="demoPipeline2ReplayMetadataCheckbox" />
<label class="form-check-label" for="demoPipeline2ReplayMetadataCheckbox">
Refresh missing token metadata
</label>
</div>
<div class="mb-3">
<label for="demoPipeline2ReplayMetadataLimitInput" class="form-label">Metadata limit</label>
<input id="demoPipeline2ReplayMetadataLimitInput" type="number" min="1" step="1" class="form-control" value="250" />
</div>
<div class="d-flex gap-2">
<button id="demoPipeline2ReplayLocalPipelineButton" type="button" class="btn btn-outline-primary">
Replay local pipeline
</button>
</div>
</div>
</div>
</div>
<div class="accordion-item border-0 shadow-sm">
<h2 class="accordion-header" id="demoPipeline2CandlesControlHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2CandlesControlCollapse" aria-expanded="false" aria-controls="demoPipeline2CandlesControlCollapse">
Chargement candles
</button>
</h2>
<div id="demoPipeline2CandlesControlCollapse" class="accordion-collapse collapse" aria-labelledby="demoPipeline2CandlesControlHeading" data-bs-parent="#demoPipeline2LeftAccordion">
<div class="accordion-body">
<div class="mb-3">
<label for="demoPipeline2PairSelect" class="form-label">Pair</label>
<select id="demoPipeline2PairSelect" class="form-select">
@@ -143,38 +197,58 @@
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-xxl-8">
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h2 class="h5 mb-3">Backfill summary</h2>
<textarea id="demoPipeline2BackfillSummaryTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
<div class="accordion" id="demoPipeline2ContentAccordion">
<div class="accordion-item border-0 shadow-sm mb-3">
<h2 class="accordion-header" id="demoPipeline2SummaryHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2SummaryCollapse" aria-expanded="false" aria-controls="demoPipeline2SummaryCollapse">
Backfill / Replay summary
</button>
</h2>
<div id="demoPipeline2SummaryCollapse" class="accordion-collapse collapse" aria-labelledby="demoPipeline2SummaryHeading" data-bs-parent="#demoPipeline2ContentAccordion">
<div class="accordion-body">
<textarea id="demoPipeline2BackfillSummaryTextarea" class="form-control font-monospace" rows="12" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<div class="accordion-item border-0 shadow-sm mb-3">
<h2 class="accordion-header" id="demoPipeline2ChartHeading">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2ChartCollapse" aria-expanded="true" aria-controls="demoPipeline2ChartCollapse">
Candles / OHLCV
</button>
</h2>
<div id="demoPipeline2ChartCollapse" class="accordion-collapse collapse show" aria-labelledby="demoPipeline2ChartHeading" data-bs-parent="#demoPipeline2ContentAccordion">
<div class="accordion-body">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-3">
<div>
<h2 class="h5 mb-1">Candles / OHLCV</h2>
<div id="demoPipeline2ChartMeta" class="small text-body-secondary">
Aucun jeu de candles chargé.
</div>
</div>
</div>
<div id="demoPipeline2Chart" class="w-100 border rounded bg-body" style="height: 560px;"></div>
</div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h5 mb-0">Log UI</h2>
<div class="accordion-item border-0 shadow-sm">
<h2 class="accordion-header" id="demoPipeline2LogHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2LogCollapse" aria-expanded="false" aria-controls="demoPipeline2LogCollapse">
Log UI
</button>
</h2>
<div id="demoPipeline2LogCollapse" class="accordion-collapse collapse" aria-labelledby="demoPipeline2LogHeading" data-bs-parent="#demoPipeline2ContentAccordion">
<div class="accordion-body">
<div class="d-flex justify-content-end align-items-center mb-3">
<button id="demoPipeline2ClearLogButton" type="button" class="btn btn-outline-secondary btn-sm">
Clear log
</button>
</div>
<textarea id="demoPipeline2LogTextarea" class="form-control font-monospace" rows="12" readonly spellcheck="false"></textarea>
<textarea id="demoPipeline2LogTextarea" class="form-control font-monospace" rows="14" readonly spellcheck="false"></textarea>
</div>
</div>
</div>
</div>
</div>

View File

@@ -38,6 +38,24 @@ interface PairCandle {
updated_at: string;
}
interface KbLocalPipelineReplayResult {
selectedTransactionCount: number;
replayedTransactionCount: number;
decodeErrorCount: number;
detectErrorCount: number;
tradeAggregationErrorCount: number;
pairCandleErrorCount: number;
analyticSignalErrorCount: number;
decodedEventCount: number;
detectionCount: number;
tradeEventCount: number;
pairCandleCount: number;
analyticSignalCount: number;
tokenMetadataUpdatedCount: number;
pairSymbolUpdatedCount: number;
globalErrorCount: number;
}
function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
const now = new Date();
const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false });
@@ -94,6 +112,25 @@ function readPositiveIntegerInput(
return parsed;
}
function readOptionalPositiveIntegerInput(
input: HTMLInputElement,
logTextarea: HTMLTextAreaElement,
label: string,
): number | null | undefined {
const text = input.value.trim();
if (text === "") {
return null;
}
const parsed = Number.parseInt(text, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
appendLogLine(logTextarea, `[ui] invalid ${label} '${text}'`);
return undefined;
}
return parsed;
}
function refreshPairSelect(
catalog: KbDemoPipeline2CatalogPayload,
select: HTMLSelectElement,
@@ -308,6 +345,11 @@ document.addEventListener("DOMContentLoaded", async () => {
const poolSignatureLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2PoolSignatureLimitInput");
const backfillPoolButton = document.querySelector<HTMLButtonElement>("#demoPipeline2BackfillPoolButton");
const replayLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayLimitInput");
const replayMetadataCheckbox = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayMetadataCheckbox");
const replayMetadataLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayMetadataLimitInput");
const replayLocalPipelineButton = document.querySelector<HTMLButtonElement>("#demoPipeline2ReplayLocalPipelineButton");
const pairSelect = document.querySelector<HTMLSelectElement>("#demoPipeline2PairSelect");
const timeframeSelect = document.querySelector<HTMLSelectElement>("#demoPipeline2TimeframeSelect");
const customTimeframeInput = document.querySelector<HTMLInputElement>("#demoPipeline2CustomTimeframeInput");
@@ -334,6 +376,10 @@ document.addEventListener("DOMContentLoaded", async () => {
!poolInput ||
!poolSignatureLimitInput ||
!backfillPoolButton ||
!replayLimitInput ||
!replayMetadataCheckbox ||
!replayMetadataLimitInput ||
!replayLocalPipelineButton ||
!pairSelect ||
!timeframeSelect ||
!customTimeframeInput ||
@@ -497,6 +543,53 @@ document.addEventListener("DOMContentLoaded", async () => {
}
});
replayLocalPipelineButton.addEventListener("click", async () => {
const replayLimit = readOptionalPositiveIntegerInput(
replayLimitInput,
logTextarea,
"replayLimit",
);
if (replayLimit === undefined) {
return;
}
const tokenMetadataLimit = readOptionalPositiveIntegerInput(
replayMetadataLimitInput,
logTextarea,
"tokenMetadataLimit",
);
if (tokenMetadataLimit === undefined) {
return;
}
appendLogLine(
logTextarea,
`[ui] launching local pipeline replay limit='${replayLimit ?? "none"}' metadata='${replayMetadataCheckbox.checked ? "yes" : "no"}'`,
);
try {
const result = await invoke<KbLocalPipelineReplayResult>(
"demo_pipeline2_replay_local_pipeline",
{
limit: replayLimit,
refreshMissingTokenMetadata: replayMetadataCheckbox.checked,
tokenMetadataLimit,
},
);
backfillSummaryTextarea.value = JSON.stringify(result, null, 2);
appendLogLine(
logTextarea,
`[ui] local pipeline replay completed: ${result.replayedTransactionCount.toString()} replayed, ${result.tradeEventCount.toString()} trades, ${result.pairCandleCount.toString()} candles`,
);
await refreshCatalog();
} catch (error) {
appendLogLine(logTextarea, `[ui] local pipeline replay error: ${String(error)}`);
}
});
loadCandlesButton.addEventListener("click", async () => {
const pairIdText = pairSelect.value.trim();
if (pairIdText === "") {

View File

@@ -507,6 +507,36 @@ async fn kb_demo_pipeline2_build_catalog(
})
}
/// Replays the local pipeline from persisted raw transactions.
#[tauri::command]
pub(crate) async fn demo_pipeline2_replay_local_pipeline(
state: tauri::State<'_, crate::KbAppState>,
limit: std::option::Option<i64>,
refresh_missing_token_metadata: bool,
token_metadata_limit: std::option::Option<i64>,
) -> Result<kb_lib::KbLocalPipelineReplayResult, std::string::String> {
let config = kb_lib::KbLocalPipelineReplayConfig {
limit,
refresh_missing_token_metadata,
token_metadata_limit,
};
let database = state.database.clone();
let service = if refresh_missing_token_metadata {
kb_lib::KbLocalPipelineReplayService::new_with_http_pool(
std::sync::Arc::new(state.http_pool.clone()),
database,
"history_backfill".to_string(),
)
} else {
kb_lib::KbLocalPipelineReplayService::new(database)
};
let replay_result = service.replay_local_pipeline(&config).await;
match replay_result {
Ok(result) => Ok(result),
Err(error) => Err(format!("local pipeline replay failed: {}", error)),
}
}
fn kb_demo_pipeline2_normalize_http_role(
role: std::option::Option<std::string::String>,
) -> std::string::String {

View File

@@ -150,6 +150,7 @@ pub async fn run() -> Result<(), kb_lib::KbError> {
crate::demo_pipeline2::demo_pipeline2_backfill_token_mint,
crate::demo_pipeline2::demo_pipeline2_backfill_pool_address,
crate::demo_pipeline2::demo_pipeline2_get_pair_candles,
crate::demo_pipeline2::demo_pipeline2_replay_local_pipeline,
]);
tauri_builder = tauri_builder.plugin(tracing_builder.build::<tauri::Wry>());
tauri_builder = tauri_builder.setup(|app| {

View File

@@ -31,3 +31,5 @@ tracing-subscriber.workspace = true
[dev-dependencies]
tempfile.workspace = true
[lints]
workspace = true

View File

@@ -20,7 +20,7 @@ pub struct KbConfig {
impl KbConfig {
/// Returns the default path of the JSON configuration file.
pub fn default_path() -> std::path::PathBuf {
kb_workspace_root_dir().join("config.json")
return kb_workspace_root_dir().join("config.json");
}
/// Loads a configuration from a JSON file and validates it.
@@ -34,7 +34,7 @@ impl KbConfig {
"cannot read configuration file '{}': {error}",
path_ref.display()
)));
}
},
};
let config_result = serde_json::from_str::<Self>(&content);
let config = match config_result {
@@ -44,36 +44,28 @@ impl KbConfig {
"cannot parse configuration file '{}': {error}",
path_ref.display()
)));
}
},
};
let validation_result = config.validate();
match validation_result {
Ok(()) => Ok(config),
Err(error) => Err(error),
Ok(()) => return Ok(config),
Err(error) => return Err(error),
}
}
/// Validates the current configuration.
pub fn validate(&self) -> Result<(), crate::KbError> {
if self.app.name.trim().is_empty() {
return Err(crate::KbError::Config(
"app.name must not be empty".to_string(),
));
return Err(crate::KbError::Config("app.name must not be empty".to_string()));
}
if self.app.environment.trim().is_empty() {
return Err(crate::KbError::Config(
"app.environment must not be empty".to_string(),
));
return Err(crate::KbError::Config("app.environment must not be empty".to_string()));
}
if self.logging.level.trim().is_empty() {
return Err(crate::KbError::Config(
"logging.level must not be empty".to_string(),
));
return Err(crate::KbError::Config("logging.level must not be empty".to_string()));
}
if self.logging.directory.trim().is_empty() {
return Err(crate::KbError::Config(
"logging.directory must not be empty".to_string(),
));
return Err(crate::KbError::Config("logging.directory must not be empty".to_string()));
}
if self.logging.file_prefix.trim().is_empty() {
return Err(crate::KbError::Config(
@@ -81,9 +73,7 @@ impl KbConfig {
));
}
if self.data.sqlite_path.trim().is_empty() {
return Err(crate::KbError::Config(
"data.sqlite_path must not be empty".to_string(),
));
return Err(crate::KbError::Config("data.sqlite_path must not be empty".to_string()));
}
if self.data.wallets_directory.trim().is_empty() {
return Err(crate::KbError::Config(
@@ -131,7 +121,7 @@ impl KbConfig {
return Err(error);
}
}
Ok(())
return Ok(());
}
/// Creates the basic runtime directories required by the current configuration.
@@ -165,7 +155,7 @@ impl KbConfig {
}
}
}
Ok(())
return Ok(());
}
/// Finds one HTTP endpoint by its logical name.
@@ -173,10 +163,11 @@ impl KbConfig {
&self,
endpoint_name: &str,
) -> std::option::Option<&KbHttpEndpointConfig> {
self.solana
return self
.solana
.http_endpoints
.iter()
.find(|endpoint| endpoint.name == endpoint_name)
.find(|endpoint| return endpoint.name == endpoint_name);
}
/// Returns a named WebSocket endpoint by reference.
@@ -184,10 +175,11 @@ impl KbConfig {
&self,
endpoint_name: &str,
) -> std::option::Option<&KbWsEndpointConfig> {
self.solana
return self
.solana
.ws_endpoints
.iter()
.find(|endpoint| endpoint.name == endpoint_name)
.find(|endpoint| return endpoint.name == endpoint_name);
}
fn validate_http_endpoint(
@@ -196,11 +188,9 @@ impl KbConfig {
endpoint_names: &mut std::vec::Vec<std::string::String>,
) -> Result<(), crate::KbError> {
if endpoint.name.trim().is_empty() {
return Err(crate::KbError::Config(
"http endpoint name must not be empty".to_string(),
));
return Err(crate::KbError::Config("http endpoint name must not be empty".to_string()));
}
if endpoint_names.iter().any(|name| name == &endpoint.name) {
if endpoint_names.iter().any(|name| return name == &endpoint.name) {
return Err(crate::KbError::Config(format!(
"duplicated endpoint name '{}'",
endpoint.name
@@ -237,7 +227,7 @@ impl KbConfig {
)));
}
endpoint_names.push(endpoint.name.clone());
Ok(())
return Ok(());
}
fn validate_ws_endpoint(
@@ -246,11 +236,9 @@ impl KbConfig {
endpoint_names: &mut std::vec::Vec<std::string::String>,
) -> Result<(), crate::KbError> {
if endpoint.name.trim().is_empty() {
return Err(crate::KbError::Config(
"ws endpoint name must not be empty".to_string(),
));
return Err(crate::KbError::Config("ws endpoint name must not be empty".to_string()));
}
if endpoint_names.iter().any(|name| name == &endpoint.name) {
if endpoint_names.iter().any(|name| return name == &endpoint.name) {
return Err(crate::KbError::Config(format!(
"duplicated endpoint name '{}'",
endpoint.name
@@ -299,7 +287,7 @@ impl KbConfig {
)));
}
endpoint_names.push(endpoint.name.clone());
Ok(())
return Ok(());
}
}
@@ -350,7 +338,7 @@ pub struct KbLoggingConfig {
impl KbLoggingConfig {
/// Returns the resolved logging directory path.
pub fn directory_path(&self) -> std::path::PathBuf {
kb_resolve_workspace_relative_path(&self.directory)
return kb_resolve_workspace_relative_path(&self.directory);
}
}
@@ -366,12 +354,12 @@ pub struct KbDataConfig {
impl KbDataConfig {
/// Returns the resolved SQLite database path.
pub fn sqlite_path_buf(&self) -> std::path::PathBuf {
kb_resolve_workspace_relative_path(&self.sqlite_path)
return kb_resolve_workspace_relative_path(&self.sqlite_path);
}
/// Returns the resolved wallets directory path.
pub fn wallets_directory_path(&self) -> std::path::PathBuf {
kb_resolve_workspace_relative_path(&self.wallets_directory)
return kb_resolve_workspace_relative_path(&self.wallets_directory);
}
}
@@ -433,7 +421,7 @@ impl KbHttpEndpointConfig {
Some(env_var_name) => env_var_name,
None => {
return Ok(self.url.clone());
}
},
};
let api_key_result = std::env::var(env_var_name);
let api_key = match api_key_result {
@@ -443,13 +431,13 @@ impl KbHttpEndpointConfig {
"cannot resolve api key env var '{}' for http endpoint '{}': {}",
env_var_name, self.name, error
)));
}
},
};
let placeholder = format!("${{{}}}", env_var_name);
if self.url.contains(&placeholder) {
return Ok(self.url.replace(&placeholder, &api_key));
}
Ok(self.url.clone())
return Ok(self.url.clone());
}
}
@@ -487,7 +475,7 @@ pub struct KbWsEndpointConfig {
impl KbWsEndpointConfig {
/// Returns the resolved endpoint URL.
pub fn resolved_url(&self) -> Result<std::string::String, crate::KbError> {
kb_resolve_endpoint_url(&self.url, &self.api_key_env_var)
return kb_resolve_endpoint_url(&self.url, &self.api_key_env_var);
}
}
@@ -512,7 +500,7 @@ pub struct KbSqliteDatabaseConfig {
impl KbSqliteDatabaseConfig {
/// Returns the resolved SQLite database path.
pub fn path_buf(&self) -> std::path::PathBuf {
kb_resolve_workspace_relative_path(&self.path)
return kb_resolve_workspace_relative_path(&self.path);
}
}
@@ -531,8 +519,8 @@ pub struct KbDatabaseConfig {
fn kb_workspace_root_dir() -> std::path::PathBuf {
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
match manifest_dir.parent() {
Some(parent) => parent.to_path_buf(),
None => manifest_dir,
Some(parent) => return parent.to_path_buf(),
None => return manifest_dir,
}
}
@@ -541,7 +529,7 @@ fn kb_resolve_workspace_relative_path<P: AsRef<std::path::Path>>(path: P) -> std
if input_path.is_absolute() {
return input_path;
}
kb_workspace_root_dir().join(input_path)
return kb_workspace_root_dir().join(input_path);
}
fn kb_resolve_endpoint_url(
@@ -553,7 +541,7 @@ fn kb_resolve_endpoint_url(
Some(env_var_name) => env_var_name,
None => {
return Ok(url.to_string());
}
},
};
let placeholder = format!("${{{env_var_name}}}");
if !url.contains(&placeholder) {
@@ -567,7 +555,7 @@ fn kb_resolve_endpoint_url(
"environment variable '{}' is required to resolve endpoint url '{}': {error}",
env_var_name, url
)));
}
},
};
Ok(url.replace(&placeholder, &env_value))
return Ok(url.replace(&placeholder, &env_value));
}

View File

@@ -87,6 +87,7 @@ pub use queries::get_dex_by_code;
pub use queries::get_dex_decoded_event_by_key;
pub use queries::get_known_http_endpoint;
pub use queries::get_known_ws_endpoint;
pub use queries::get_latest_pump_fun_create_payload_by_mint;
pub use queries::get_launch_attribution_by_decoded_event_id;
pub use queries::get_launch_surface_by_code;
pub use queries::get_launch_surface_key_by_match;
@@ -98,6 +99,7 @@ pub use queries::get_pair_metric_by_pair_id;
pub use queries::get_pool_by_address;
pub use queries::get_pool_listing_by_pool_id;
pub use queries::get_pool_origin_by_pool_id;
pub use queries::get_token_by_id;
pub use queries::get_token_by_mint;
pub use queries::get_trade_event_by_decoded_event_id;
pub use queries::get_wallet_by_address;
@@ -108,6 +110,7 @@ pub use queries::insert_chain_instruction;
pub use queries::insert_db_runtime_event;
pub use queries::insert_onchain_observation;
pub use queries::list_chain_instructions_by_transaction_id;
pub use queries::list_chain_transaction_signatures_for_replay;
pub use queries::list_db_metadata;
pub use queries::list_dex_decoded_events_by_transaction_id;
pub use queries::list_dexes;
@@ -135,12 +138,14 @@ pub use queries::list_recent_swaps;
pub use queries::list_recent_token_burn_events;
pub use queries::list_recent_token_mint_events;
pub use queries::list_tokens;
pub use queries::list_tokens_missing_metadata;
pub use queries::list_trade_events_by_pair_id;
pub use queries::list_trade_events_by_transaction_id;
pub use queries::list_wallet_holdings_by_wallet_id;
pub use queries::list_wallet_participations_by_pool_id;
pub use queries::list_wallet_participations_by_wallet_id;
pub use queries::list_wallets;
pub use queries::update_pair_symbol;
pub use queries::upsert_chain_slot;
pub use queries::upsert_chain_transaction;
pub use queries::upsert_db_metadata;

View File

@@ -19,9 +19,7 @@ pub struct KbDatabase {
impl KbDatabase {
/// Opens a database connection without initializing the schema.
pub async fn connect(
config: &crate::KbDatabaseConfig,
) -> Result<Self, crate::KbError> {
pub async fn connect(config: &crate::KbDatabaseConfig) -> Result<Self, crate::KbError> {
if !config.enabled {
return Err(crate::KbError::Config(
"database is disabled in configuration".to_string(),
@@ -40,11 +38,11 @@ impl KbDatabase {
Ok(pool) => pool,
Err(error) => return Err(error),
};
Ok(Self {
return Ok(Self {
backend: crate::KbDatabaseBackend::Sqlite,
database_url,
connection: KbDatabaseConnection::Sqlite(pool),
})
});
},
}
}
@@ -64,47 +62,40 @@ impl KbDatabase {
return Err(error);
}
}
Ok(database)
return Ok(database);
}
/// Returns the configured backend.
pub fn backend(
&self,
) -> crate::KbDatabaseBackend {
self.backend
pub fn backend(&self) -> crate::KbDatabaseBackend {
return self.backend;
}
/// Returns a displayable database URL-like string.
pub fn database_url(
&self,
) -> &str {
&self.database_url
pub fn database_url(&self) -> &str {
return &self.database_url;
}
/// Pings the database.
pub async fn ping(
&self,
) -> Result<(), crate::KbError> {
pub async fn ping(&self) -> Result<(), crate::KbError> {
match &self.connection {
KbDatabaseConnection::Sqlite(pool) => {
let ping_result = sqlx::query("SELECT 1").execute(pool).await;
match ping_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot ping sqlite database '{}': {}",
self.database_url,
error
))),
self.database_url, error
)));
},
}
},
}
}
/// Returns the underlying connection enum.
pub(crate) fn connection(
&self,
) -> &KbDatabaseConnection {
&self.connection
pub(crate) fn connection(&self) -> &KbDatabaseConnection {
return &self.connection;
}
}

View File

@@ -33,7 +33,7 @@ impl KbAnalysisSignalDto {
score: std::option::Option<f64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
id: None,
signal_kind,
severity,
@@ -42,7 +42,7 @@ impl KbAnalysisSignalDto {
score,
payload,
created_at: chrono::Utc::now(),
}
};
}
}
@@ -63,7 +63,7 @@ impl TryFrom<crate::KbAnalysisSignalEntity> for KbAnalysisSignalDto {
"cannot parse analysis signal created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let payload_result = serde_json::from_str::<serde_json::Value>(&entity.payload_json);
let payload = match payload_result {
@@ -73,9 +73,9 @@ impl TryFrom<crate::KbAnalysisSignalEntity> for KbAnalysisSignalDto {
"cannot parse analysis signal payload_json '{}': {}",
entity.payload_json, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
signal_kind: entity.signal_kind,
severity,
@@ -84,6 +84,6 @@ impl TryFrom<crate::KbAnalysisSignalEntity> for KbAnalysisSignalDto {
score: entity.score,
payload,
created_at,
})
});
}
}

View File

@@ -49,7 +49,7 @@ impl KbChainInstructionDto {
parsed_type: std::option::Option<std::string::String>,
parsed_json: std::option::Option<std::string::String>,
) -> Self {
Self {
return Self {
id: None,
transaction_id,
parent_instruction_id,
@@ -63,7 +63,7 @@ impl KbChainInstructionDto {
parsed_type,
parsed_json,
created_at: chrono::Utc::now(),
}
};
}
}
@@ -79,7 +79,7 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
"cannot convert chain instruction instruction_index '{}' to u32: {}",
entity.instruction_index, error
)));
}
},
};
let inner_instruction_index = match entity.inner_instruction_index {
Some(inner_instruction_index) => {
@@ -91,9 +91,9 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
"cannot convert chain instruction inner_instruction_index '{}' to u32: {}",
inner_instruction_index, error
)));
},
}
}
}
},
None => None,
};
let stack_height = match entity.stack_height {
@@ -106,9 +106,9 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
"cannot convert chain instruction stack_height '{}' to u32: {}",
stack_height, error
)));
},
}
}
}
},
None => None,
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
@@ -119,9 +119,9 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
"cannot parse chain instruction created_at '{}': {}",
entity.created_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
transaction_id: entity.transaction_id,
parent_instruction_id: entity.parent_instruction_id,
@@ -135,6 +135,6 @@ impl TryFrom<crate::KbChainInstructionEntity> for KbChainInstructionDto {
parsed_type: entity.parsed_type,
parsed_json: entity.parsed_json,
created_at,
})
});
}
}

View File

@@ -25,13 +25,13 @@ impl KbChainSlotDto {
block_time_unix: std::option::Option<i64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
slot,
parent_slot,
block_time_unix,
created_at: now,
updated_at: now,
}
};
}
}
@@ -47,7 +47,7 @@ impl TryFrom<crate::KbChainSlotEntity> for KbChainSlotDto {
"cannot convert chain slot '{}' to u64: {}",
entity.slot, error
)));
}
},
};
let parent_slot = match entity.parent_slot {
Some(parent_slot) => {
@@ -59,9 +59,9 @@ impl TryFrom<crate::KbChainSlotEntity> for KbChainSlotDto {
"cannot convert chain parent_slot '{}' to u64: {}",
parent_slot, error
)));
},
}
}
}
},
None => None,
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
@@ -72,7 +72,7 @@ impl TryFrom<crate::KbChainSlotEntity> for KbChainSlotDto {
"cannot parse chain slot created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -82,14 +82,14 @@ impl TryFrom<crate::KbChainSlotEntity> for KbChainSlotDto {
"cannot parse chain slot updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
slot,
parent_slot,
block_time_unix: entity.block_time_unix,
created_at,
updated_at,
})
});
}
}

View File

@@ -43,7 +43,7 @@ impl KbChainTransactionDto {
transaction_json: std::string::String,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
signature,
slot,
@@ -55,7 +55,7 @@ impl KbChainTransactionDto {
transaction_json,
created_at: now,
updated_at: now,
}
};
}
}
@@ -73,9 +73,9 @@ impl TryFrom<crate::KbChainTransactionEntity> for KbChainTransactionDto {
"cannot convert chain transaction slot '{}' to u64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
@@ -86,7 +86,7 @@ impl TryFrom<crate::KbChainTransactionEntity> for KbChainTransactionDto {
"cannot parse chain transaction created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -96,9 +96,9 @@ impl TryFrom<crate::KbChainTransactionEntity> for KbChainTransactionDto {
"cannot parse chain transaction updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
signature: entity.signature,
slot,
@@ -110,6 +110,6 @@ impl TryFrom<crate::KbChainTransactionEntity> for KbChainTransactionDto {
transaction_json: entity.transaction_json,
created_at,
updated_at,
})
});
}
}

View File

@@ -16,11 +16,11 @@ pub struct KbDbMetadataDto {
impl KbDbMetadataDto {
/// Creates a new metadata DTO with the current UTC timestamp.
pub fn new(key: std::string::String, value: std::string::String) -> Self {
Self {
return Self {
key,
value,
updated_at: chrono::Utc::now(),
}
};
}
}
@@ -36,22 +36,22 @@ impl TryFrom<crate::KbDbMetadataEntity> for KbDbMetadataDto {
"cannot parse db metadata timestamp '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
key: entity.key,
value: entity.value,
updated_at: parsed.with_timezone(&chrono::Utc),
})
});
}
}
impl From<KbDbMetadataDto> for crate::KbDbMetadataEntity {
fn from(dto: KbDbMetadataDto) -> Self {
Self {
return Self {
key: dto.key,
value: dto.value,
updated_at: dto.updated_at.to_rfc3339(),
}
};
}
}

View File

@@ -27,14 +27,14 @@ impl KbDbRuntimeEventDto {
source: std::string::String,
message: std::string::String,
) -> Self {
Self {
return Self {
id: None,
event_kind,
level,
source,
message,
created_at: chrono::Utc::now(),
}
};
}
}
@@ -50,20 +50,20 @@ impl TryFrom<crate::KbDbRuntimeEventEntity> for KbDbRuntimeEventDto {
"cannot parse runtime event created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let level_result = crate::KbDbRuntimeEventLevel::from_i16(entity.level);
let level = match level_result {
Ok(level) => level,
Err(error) => return Err(error),
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
event_kind: entity.event_kind,
level,
source: entity.source,
message: entity.message,
created_at,
})
});
}
}

View File

@@ -33,7 +33,7 @@ impl KbDexDto {
is_enabled: bool,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
code,
name,
@@ -42,7 +42,7 @@ impl KbDexDto {
is_enabled,
created_at: now,
updated_at: now,
}
};
}
}
@@ -58,7 +58,7 @@ impl TryFrom<crate::KbDexEntity> for KbDexDto {
"cannot parse dex created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -68,9 +68,9 @@ impl TryFrom<crate::KbDexEntity> for KbDexDto {
"cannot parse dex updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
code: entity.code,
name: entity.name,
@@ -79,6 +79,6 @@ impl TryFrom<crate::KbDexEntity> for KbDexDto {
is_enabled: entity.is_enabled != 0,
created_at,
updated_at,
})
});
}
}

View File

@@ -49,7 +49,7 @@ impl KbDexDecodedEventDto {
market_account: std::option::Option<std::string::String>,
payload_json: std::string::String,
) -> Self {
Self {
return Self {
id: None,
transaction_id,
instruction_id,
@@ -63,7 +63,7 @@ impl KbDexDecodedEventDto {
market_account,
payload_json,
created_at: chrono::Utc::now(),
}
};
}
}
@@ -79,9 +79,9 @@ impl TryFrom<crate::KbDexDecodedEventEntity> for KbDexDecodedEventDto {
"cannot parse dex decoded event created_at '{}': {}",
entity.created_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
transaction_id: entity.transaction_id,
instruction_id: entity.instruction_id,
@@ -95,6 +95,6 @@ impl TryFrom<crate::KbDexDecodedEventEntity> for KbDexDecodedEventDto {
market_account: entity.market_account,
payload_json: entity.payload_json,
created_at,
})
});
}
}

View File

@@ -30,7 +30,7 @@ impl KbKnownHttpEndpointDto {
enabled: bool,
roles: std::vec::Vec<std::string::String>,
) -> Self {
Self {
return Self {
name,
provider,
url,
@@ -38,7 +38,7 @@ impl KbKnownHttpEndpointDto {
roles,
last_seen_at: None,
updated_at: chrono::Utc::now(),
}
};
}
}
@@ -55,7 +55,7 @@ impl TryFrom<crate::KbKnownHttpEndpointEntity> for KbKnownHttpEndpointDto {
"cannot parse known http endpoint roles_json '{}': {}",
entity.roles_json, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -65,7 +65,7 @@ impl TryFrom<crate::KbKnownHttpEndpointEntity> for KbKnownHttpEndpointDto {
"cannot parse known http endpoint updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
let last_seen_at = match entity.last_seen_at {
Some(last_seen_at_text) => {
@@ -77,12 +77,12 @@ impl TryFrom<crate::KbKnownHttpEndpointEntity> for KbKnownHttpEndpointDto {
"cannot parse known http endpoint last_seen_at '{}': {}",
last_seen_at_text, error
)));
},
}
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
name: entity.name,
provider: entity.provider,
url: entity.url,
@@ -90,7 +90,7 @@ impl TryFrom<crate::KbKnownHttpEndpointEntity> for KbKnownHttpEndpointDto {
roles,
last_seen_at,
updated_at,
})
});
}
}
@@ -106,16 +106,16 @@ impl TryFrom<KbKnownHttpEndpointDto> for crate::KbKnownHttpEndpointEntity {
"cannot serialize known http endpoint roles: {}",
error
)));
}
},
};
Ok(Self {
return Ok(Self {
name: dto.name,
provider: dto.provider,
url: dto.url,
enabled: if dto.enabled { 1 } else { 0 },
roles_json,
last_seen_at: dto.last_seen_at.map(|value| value.to_rfc3339()),
last_seen_at: dto.last_seen_at.map(|value| return value.to_rfc3339()),
updated_at: dto.updated_at.to_rfc3339(),
})
});
}
}

View File

@@ -30,7 +30,7 @@ impl KbKnownWsEndpointDto {
enabled: bool,
roles: std::vec::Vec<std::string::String>,
) -> Self {
Self {
return Self {
name,
provider,
url,
@@ -38,7 +38,7 @@ impl KbKnownWsEndpointDto {
roles,
last_seen_at: None,
updated_at: chrono::Utc::now(),
}
};
}
}
@@ -55,7 +55,7 @@ impl TryFrom<crate::KbKnownWsEndpointEntity> for KbKnownWsEndpointDto {
"cannot parse known ws endpoint roles_json '{}': {}",
entity.roles_json, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -65,7 +65,7 @@ impl TryFrom<crate::KbKnownWsEndpointEntity> for KbKnownWsEndpointDto {
"cannot parse known ws endpoint updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
let last_seen_at = match entity.last_seen_at {
Some(last_seen_at_text) => {
@@ -77,12 +77,12 @@ impl TryFrom<crate::KbKnownWsEndpointEntity> for KbKnownWsEndpointDto {
"cannot parse known ws endpoint last_seen_at '{}': {}",
last_seen_at_text, error
)));
},
}
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
name: entity.name,
provider: entity.provider,
url: entity.url,
@@ -90,7 +90,7 @@ impl TryFrom<crate::KbKnownWsEndpointEntity> for KbKnownWsEndpointDto {
roles,
last_seen_at,
updated_at,
})
});
}
}
@@ -106,16 +106,16 @@ impl TryFrom<KbKnownWsEndpointDto> for crate::KbKnownWsEndpointEntity {
"cannot serialize known ws endpoint roles: {}",
error
)));
}
},
};
Ok(Self {
return Ok(Self {
name: dto.name,
provider: dto.provider,
url: dto.url,
enabled: if dto.enabled { 1 } else { 0 },
roles_json,
last_seen_at: dto.last_seen_at.map(|value| value.to_rfc3339()),
last_seen_at: dto.last_seen_at.map(|value| return value.to_rfc3339()),
updated_at: dto.updated_at.to_rfc3339(),
})
});
}
}

View File

@@ -45,7 +45,7 @@ impl KbLaunchAttributionDto {
matched_value: std::string::String,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
launch_surface_id,
transaction_id,
@@ -58,7 +58,7 @@ impl KbLaunchAttributionDto {
matched_value,
attributed_at: now,
updated_at: now,
}
};
}
}
@@ -74,7 +74,7 @@ impl TryFrom<crate::KbLaunchAttributionEntity> for KbLaunchAttributionDto {
"cannot parse launch_attribution attributed_at '{}': {}",
entity.attributed_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -84,9 +84,9 @@ impl TryFrom<crate::KbLaunchAttributionEntity> for KbLaunchAttributionDto {
"cannot parse launch_attribution updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
launch_surface_id: entity.launch_surface_id,
transaction_id: entity.transaction_id,
@@ -99,6 +99,6 @@ impl TryFrom<crate::KbLaunchAttributionEntity> for KbLaunchAttributionDto {
matched_value: entity.matched_value,
attributed_at,
updated_at,
})
});
}
}

View File

@@ -30,7 +30,7 @@ impl KbLaunchSurfaceDto {
is_enabled: bool,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
code,
name,
@@ -38,7 +38,7 @@ impl KbLaunchSurfaceDto {
is_enabled,
created_at: now,
updated_at: now,
}
};
}
}
@@ -54,7 +54,7 @@ impl TryFrom<crate::KbLaunchSurfaceEntity> for KbLaunchSurfaceDto {
"cannot parse launch_surface created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -64,9 +64,9 @@ impl TryFrom<crate::KbLaunchSurfaceEntity> for KbLaunchSurfaceDto {
"cannot parse launch_surface updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
code: entity.code,
name: entity.name,
@@ -74,6 +74,6 @@ impl TryFrom<crate::KbLaunchSurfaceEntity> for KbLaunchSurfaceDto {
is_enabled: entity.is_enabled != 0,
created_at,
updated_at,
})
});
}
}

View File

@@ -27,14 +27,14 @@ impl KbLaunchSurfaceKeyDto {
match_value: std::string::String,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
launch_surface_id,
match_kind,
match_value,
created_at: now,
updated_at: now,
}
};
}
}
@@ -50,7 +50,7 @@ impl TryFrom<crate::KbLaunchSurfaceKeyEntity> for KbLaunchSurfaceKeyDto {
"cannot parse launch_surface_key created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -60,15 +60,15 @@ impl TryFrom<crate::KbLaunchSurfaceKeyEntity> for KbLaunchSurfaceKeyDto {
"cannot parse launch_surface_key updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
launch_surface_id: entity.launch_surface_id,
match_kind: entity.match_kind,
match_value: entity.match_value,
created_at,
updated_at,
})
});
}
}

View File

@@ -57,7 +57,7 @@ impl KbLiquidityEventDto {
quote_amount: std::string::String,
lp_amount: std::option::Option<std::string::String>,
) -> Self {
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -74,7 +74,7 @@ impl KbLiquidityEventDto {
quote_amount,
lp_amount,
executed_at: chrono::Utc::now(),
}
};
}
}
@@ -95,7 +95,7 @@ impl TryFrom<crate::KbLiquidityEventEntity> for KbLiquidityEventDto {
"cannot parse liquidity event executed_at '{}': {}",
entity.executed_at, error
)));
}
},
};
let slot = match entity.slot {
Some(slot) => {
@@ -107,12 +107,12 @@ impl TryFrom<crate::KbLiquidityEventEntity> for KbLiquidityEventDto {
"cannot convert liquidity event slot '{}' to u64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -129,6 +129,6 @@ impl TryFrom<crate::KbLiquidityEventEntity> for KbLiquidityEventDto {
quote_amount: entity.quote_amount,
lp_amount: entity.lp_amount,
executed_at,
})
});
}
}

View File

@@ -38,7 +38,7 @@ impl KbObservedTokenDto {
status: crate::KbObservedTokenStatus,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
mint,
symbol,
@@ -49,16 +49,14 @@ impl KbObservedTokenDto {
first_seen_at: now,
last_seen_at: now,
updated_at: now,
}
};
}
}
impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
type Error = crate::KbError;
fn try_from(
entity: crate::KbObservedTokenEntity,
) -> Result<Self, Self::Error> {
fn try_from(entity: crate::KbObservedTokenEntity) -> Result<Self, Self::Error> {
let status_result = crate::KbObservedTokenStatus::from_i16(entity.status);
let status = match status_result {
Ok(status) => status,
@@ -70,8 +68,7 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse observed token first_seen_at '{}': {}",
entity.first_seen_at,
error
entity.first_seen_at, error
)));
},
};
@@ -81,8 +78,7 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse observed token last_seen_at '{}': {}",
entity.last_seen_at,
error
entity.last_seen_at, error
)));
},
};
@@ -92,8 +88,7 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse observed token updated_at '{}': {}",
entity.updated_at,
error
entity.updated_at, error
)));
},
};
@@ -105,15 +100,14 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot convert observed token decimals '{}' to u8: {}",
decimals,
error
decimals, error
)));
},
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
mint: entity.mint,
symbol: entity.symbol,
@@ -124,6 +118,6 @@ impl TryFrom<crate::KbObservedTokenEntity> for KbObservedTokenDto {
first_seen_at,
last_seen_at,
updated_at,
})
});
}
}

View File

@@ -33,7 +33,7 @@ impl KbOnchainObservationDto {
slot: std::option::Option<u64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
id: None,
observation_kind,
source_kind,
@@ -42,7 +42,7 @@ impl KbOnchainObservationDto {
slot,
payload,
observed_at: chrono::Utc::now(),
}
};
}
}
@@ -63,7 +63,7 @@ impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
"cannot parse on-chain observation observed_at '{}': {}",
entity.observed_at, error
)));
}
},
};
let payload_result = serde_json::from_str::<serde_json::Value>(&entity.payload_json);
let payload = match payload_result {
@@ -73,7 +73,7 @@ impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
"cannot parse on-chain observation payload_json '{}': {}",
entity.payload_json, error
)));
}
},
};
let slot = match entity.slot {
Some(slot) => {
@@ -85,12 +85,12 @@ impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
"cannot convert on-chain observation slot '{}' to u64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
observation_kind: entity.observation_kind,
source_kind,
@@ -99,6 +99,6 @@ impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
slot,
payload,
observed_at,
})
});
}
}

View File

@@ -33,7 +33,7 @@ impl KbPairDto {
symbol: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -42,7 +42,7 @@ impl KbPairDto {
symbol,
first_seen_at: now,
updated_at: now,
}
};
}
}
@@ -58,7 +58,7 @@ impl TryFrom<crate::KbPairEntity> for KbPairDto {
"cannot parse pair first_seen_at '{}': {}",
entity.first_seen_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -68,9 +68,9 @@ impl TryFrom<crate::KbPairEntity> for KbPairDto {
"cannot parse pair updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -79,6 +79,6 @@ impl TryFrom<crate::KbPairEntity> for KbPairDto {
symbol: entity.symbol,
first_seen_at,
updated_at,
})
});
}
}

View File

@@ -45,7 +45,7 @@ impl KbPairAnalyticSignalDto {
last_transaction_id: std::option::Option<i64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
pair_id,
signal_kind,
@@ -58,7 +58,7 @@ impl KbPairAnalyticSignalDto {
last_transaction_id,
created_at: now,
updated_at: now,
}
};
}
}
@@ -80,7 +80,7 @@ impl TryFrom<crate::KbPairAnalyticSignalEntity> for KbPairAnalyticSignalDto {
"cannot parse pair_analytic_signal signal_value_json '{}': {}",
entity.signal_value_json, error
)));
}
},
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(entity.created_at.as_str());
let created_at = match created_at_result {
@@ -90,7 +90,7 @@ impl TryFrom<crate::KbPairAnalyticSignalEntity> for KbPairAnalyticSignalDto {
"cannot parse pair_analytic_signal created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(entity.updated_at.as_str());
let updated_at = match updated_at_result {
@@ -100,9 +100,9 @@ impl TryFrom<crate::KbPairAnalyticSignalEntity> for KbPairAnalyticSignalDto {
"cannot parse pair_analytic_signal updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
pair_id: entity.pair_id,
signal_kind: entity.signal_kind,
@@ -115,6 +115,6 @@ impl TryFrom<crate::KbPairAnalyticSignalEntity> for KbPairAnalyticSignalDto {
last_transaction_id: entity.last_transaction_id,
created_at,
updated_at,
})
});
}
}

View File

@@ -64,7 +64,7 @@ impl KbPairCandleDto {
last_trade_signature: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
pair_id,
timeframe_seconds,
@@ -83,7 +83,7 @@ impl KbPairCandleDto {
last_trade_signature,
created_at: now,
updated_at: now,
}
};
}
}
@@ -99,7 +99,7 @@ impl TryFrom<crate::KbPairCandleEntity> for KbPairCandleDto {
"cannot parse pair_candle created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -109,9 +109,9 @@ impl TryFrom<crate::KbPairCandleEntity> for KbPairCandleDto {
"cannot parse pair_candle updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
pair_id: entity.pair_id,
timeframe_seconds: entity.timeframe_seconds,
@@ -130,6 +130,6 @@ impl TryFrom<crate::KbPairCandleEntity> for KbPairCandleDto {
last_trade_signature: entity.last_trade_signature,
created_at,
updated_at,
})
});
}
}

View File

@@ -39,7 +39,7 @@ impl KbPairMetricDto {
/// Creates a new pair-metric DTO.
pub fn new(pair_id: i64) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
pair_id,
first_slot: None,
@@ -54,7 +54,7 @@ impl KbPairMetricDto {
last_price_quote_per_base: None,
created_at: now,
updated_at: now,
}
};
}
}
@@ -70,7 +70,7 @@ impl TryFrom<crate::KbPairMetricEntity> for KbPairMetricDto {
"cannot parse pair_metric created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -80,9 +80,9 @@ impl TryFrom<crate::KbPairMetricEntity> for KbPairMetricDto {
"cannot parse pair_metric updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
pair_id: entity.pair_id,
first_slot: entity.first_slot,
@@ -97,6 +97,6 @@ impl TryFrom<crate::KbPairMetricEntity> for KbPairMetricDto {
last_price_quote_per_base: entity.last_price_quote_per_base,
created_at,
updated_at,
})
});
}
}

View File

@@ -30,7 +30,7 @@ impl KbPoolDto {
status: crate::KbPoolStatus,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
address,
@@ -38,7 +38,7 @@ impl KbPoolDto {
status,
first_seen_at: now,
updated_at: now,
}
};
}
}
@@ -64,7 +64,7 @@ impl TryFrom<crate::KbPoolEntity> for KbPoolDto {
"cannot parse pool first_seen_at '{}': {}",
entity.first_seen_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -74,9 +74,9 @@ impl TryFrom<crate::KbPoolEntity> for KbPoolDto {
"cannot parse pool updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
address: entity.address,
@@ -84,6 +84,6 @@ impl TryFrom<crate::KbPoolEntity> for KbPoolDto {
status,
first_seen_at,
updated_at,
})
});
}
}

View File

@@ -42,7 +42,7 @@ impl KbPoolListingDto {
initial_price_quote: std::option::Option<f64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -54,7 +54,7 @@ impl KbPoolListingDto {
initial_quote_reserve,
initial_price_quote,
updated_at: now,
}
};
}
}
@@ -75,7 +75,7 @@ impl TryFrom<crate::KbPoolListingEntity> for KbPoolListingDto {
"cannot parse pool_listing detected_at '{}': {}",
entity.detected_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -85,9 +85,9 @@ impl TryFrom<crate::KbPoolListingEntity> for KbPoolListingDto {
"cannot parse pool_listing updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -99,6 +99,6 @@ impl TryFrom<crate::KbPoolListingEntity> for KbPoolListingDto {
initial_quote_reserve: entity.initial_quote_reserve,
initial_price_quote: entity.initial_price_quote,
updated_at,
})
});
}
}

View File

@@ -54,7 +54,7 @@ impl KbPoolOriginDto {
launch_attribution_id: std::option::Option<i64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -70,7 +70,7 @@ impl KbPoolOriginDto {
launch_attribution_id,
created_at: now,
updated_at: now,
}
};
}
}
@@ -91,7 +91,7 @@ impl TryFrom<crate::KbPoolOriginEntity> for KbPoolOriginDto {
"cannot parse pool_origin created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -101,9 +101,9 @@ impl TryFrom<crate::KbPoolOriginEntity> for KbPoolOriginDto {
"cannot parse pool_origin updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -119,6 +119,6 @@ impl TryFrom<crate::KbPoolOriginEntity> for KbPoolOriginDto {
launch_attribution_id: entity.launch_attribution_id,
created_at,
updated_at,
})
});
}
}

View File

@@ -33,7 +33,7 @@ impl KbPoolTokenDto {
token_order: std::option::Option<i64>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
pool_id,
token_id,
@@ -42,7 +42,7 @@ impl KbPoolTokenDto {
token_order,
created_at: now,
updated_at: now,
}
};
}
}
@@ -63,7 +63,7 @@ impl TryFrom<crate::KbPoolTokenEntity> for KbPoolTokenDto {
"cannot parse pool_token created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -73,9 +73,9 @@ impl TryFrom<crate::KbPoolTokenEntity> for KbPoolTokenDto {
"cannot parse pool_token updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
pool_id: entity.pool_id,
token_id: entity.token_id,
@@ -84,6 +84,6 @@ impl TryFrom<crate::KbPoolTokenEntity> for KbPoolTokenDto {
token_order: entity.token_order,
created_at,
updated_at,
})
});
}
}

View File

@@ -54,7 +54,7 @@ impl KbSwapDto {
price_quote: std::option::Option<std::string::String>,
trade_side: crate::KbSwapTradeSide,
) -> Self {
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -70,7 +70,7 @@ impl KbSwapDto {
price_quote,
trade_side,
executed_at: chrono::Utc::now(),
}
};
}
}
@@ -91,7 +91,7 @@ impl TryFrom<crate::KbSwapEntity> for KbSwapDto {
"cannot parse swap executed_at '{}': {}",
entity.executed_at, error
)));
}
},
};
let slot = match entity.slot {
Some(slot) => {
@@ -103,12 +103,12 @@ impl TryFrom<crate::KbSwapEntity> for KbSwapDto {
"cannot convert swap slot '{}' to u64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -124,6 +124,6 @@ impl TryFrom<crate::KbSwapEntity> for KbSwapDto {
price_quote: entity.price_quote,
trade_side,
executed_at,
})
});
}
}

View File

@@ -36,7 +36,7 @@ impl KbTokenDto {
is_quote_token: bool,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
mint,
symbol,
@@ -46,7 +46,7 @@ impl KbTokenDto {
is_quote_token,
first_seen_at: now,
updated_at: now,
}
};
}
}
@@ -62,7 +62,7 @@ impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
"cannot parse token first_seen_at '{}': {}",
entity.first_seen_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -72,7 +72,7 @@ impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
"cannot parse token updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
let decimals = match entity.decimals {
Some(decimals) => {
@@ -84,12 +84,12 @@ impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
"cannot convert token decimals '{}' to u8: {}",
decimals, error
)));
},
}
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
mint: entity.mint,
symbol: entity.symbol,
@@ -99,6 +99,6 @@ impl TryFrom<crate::KbTokenEntity> for KbTokenDto {
is_quote_token: entity.is_quote_token != 0,
first_seen_at,
updated_at,
})
});
}
}

View File

@@ -39,7 +39,7 @@ impl KbTokenBurnEventDto {
amount: std::string::String,
supply_after: std::option::Option<std::string::String>,
) -> Self {
Self {
return Self {
id: None,
token_id,
signature,
@@ -50,24 +50,21 @@ impl KbTokenBurnEventDto {
amount,
supply_after,
executed_at: chrono::Utc::now(),
}
};
}
}
impl TryFrom<crate::KbTokenBurnEventEntity> for KbTokenBurnEventDto {
type Error = crate::KbError;
fn try_from(
entity: crate::KbTokenBurnEventEntity,
) -> Result<Self, Self::Error> {
fn try_from(entity: crate::KbTokenBurnEventEntity) -> Result<Self, Self::Error> {
let executed_at_result = chrono::DateTime::parse_from_rfc3339(&entity.executed_at);
let executed_at = match executed_at_result {
Ok(executed_at) => executed_at.with_timezone(&chrono::Utc),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse token burn event executed_at '{}': {}",
entity.executed_at,
error
entity.executed_at, error
)));
},
};
@@ -79,15 +76,14 @@ impl TryFrom<crate::KbTokenBurnEventEntity> for KbTokenBurnEventDto {
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot convert token burn event slot '{}' to u64: {}",
slot,
error
slot, error
)));
},
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
token_id: entity.token_id,
signature: entity.signature,
@@ -98,6 +94,6 @@ impl TryFrom<crate::KbTokenBurnEventEntity> for KbTokenBurnEventDto {
amount: entity.amount,
supply_after: entity.supply_after,
executed_at,
})
});
}
}

View File

@@ -39,7 +39,7 @@ impl KbTokenMintEventDto {
amount: std::string::String,
supply_after: std::option::Option<std::string::String>,
) -> Self {
Self {
return Self {
id: None,
token_id,
signature,
@@ -50,7 +50,7 @@ impl KbTokenMintEventDto {
amount,
supply_after,
executed_at: chrono::Utc::now(),
}
};
}
}
@@ -66,7 +66,7 @@ impl TryFrom<crate::KbTokenMintEventEntity> for KbTokenMintEventDto {
"cannot parse token mint event executed_at '{}': {}",
entity.executed_at, error
)));
}
},
};
let slot = match entity.slot {
Some(slot) => {
@@ -78,12 +78,12 @@ impl TryFrom<crate::KbTokenMintEventEntity> for KbTokenMintEventDto {
"cannot convert token mint event slot '{}' to u64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
token_id: entity.token_id,
signature: entity.signature,
@@ -94,6 +94,6 @@ impl TryFrom<crate::KbTokenMintEventEntity> for KbTokenMintEventDto {
amount: entity.amount,
supply_after: entity.supply_after,
executed_at,
})
});
}
}

View File

@@ -66,7 +66,7 @@ impl KbTradeEventDto {
payload_json: std::string::String,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
dex_id,
pool_id,
@@ -86,7 +86,7 @@ impl KbTradeEventDto {
payload_json,
created_at: now,
updated_at: now,
}
};
}
}
@@ -108,7 +108,7 @@ impl TryFrom<crate::KbTradeEventEntity> for KbTradeEventDto {
"cannot parse trade_event created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -118,9 +118,9 @@ impl TryFrom<crate::KbTradeEventEntity> for KbTradeEventDto {
"cannot parse trade_event updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
dex_id: entity.dex_id,
pool_id: entity.pool_id,
@@ -140,14 +140,14 @@ impl TryFrom<crate::KbTradeEventEntity> for KbTradeEventDto {
payload_json: entity.payload_json,
created_at,
updated_at,
})
});
}
}
fn kb_trade_side_from_string(value: &str) -> crate::KbSwapTradeSide {
match value {
"BuyBase" => crate::KbSwapTradeSide::BuyBase,
"SellBase" => crate::KbSwapTradeSide::SellBase,
_ => crate::KbSwapTradeSide::Unknown,
"BuyBase" => return crate::KbSwapTradeSide::BuyBase,
"SellBase" => return crate::KbSwapTradeSide::SellBase,
_ => return crate::KbSwapTradeSide::Unknown,
}
}

View File

@@ -24,13 +24,13 @@ impl KbWalletDto {
label: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
address,
label,
first_seen_at: now,
last_seen_at: now,
}
};
}
}
@@ -46,7 +46,7 @@ impl TryFrom<crate::KbWalletEntity> for KbWalletDto {
"cannot parse wallet first_seen_at '{}': {}",
entity.first_seen_at, error
)));
}
},
};
let last_seen_at_result = chrono::DateTime::parse_from_rfc3339(&entity.last_seen_at);
let last_seen_at = match last_seen_at_result {
@@ -56,14 +56,14 @@ impl TryFrom<crate::KbWalletEntity> for KbWalletDto {
"cannot parse wallet last_seen_at '{}': {}",
entity.last_seen_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
address: entity.address,
label: entity.label,
first_seen_at,
last_seen_at,
})
});
}
}

View File

@@ -54,7 +54,7 @@ impl KbWalletHoldingDto {
source_endpoint_name: std::option::Option<std::string::String>,
) -> Self {
let now = chrono::Utc::now();
Self {
return Self {
id: None,
wallet_id,
token_id,
@@ -70,7 +70,7 @@ impl KbWalletHoldingDto {
source_endpoint_name,
created_at: now,
updated_at: now,
}
};
}
}
@@ -91,7 +91,7 @@ impl TryFrom<crate::KbWalletHoldingEntity> for KbWalletHoldingDto {
"cannot parse wallet_holding created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -101,9 +101,9 @@ impl TryFrom<crate::KbWalletHoldingEntity> for KbWalletHoldingDto {
"cannot parse wallet_holding updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
wallet_id: entity.wallet_id,
token_id: entity.token_id,

View File

@@ -52,8 +52,7 @@ impl KbWalletParticipationDto {
pair_id,
role.as_str(),
);
Self {
return Self {
id: None,
wallet_id,
transaction_id,
@@ -66,7 +65,7 @@ impl KbWalletParticipationDto {
source_endpoint_name,
created_at: now,
updated_at: now,
}
};
}
}
@@ -87,7 +86,7 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
"cannot parse wallet_participation created_at '{}': {}",
entity.created_at, error
)));
}
},
};
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result {
@@ -97,9 +96,9 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
"cannot parse wallet_participation updated_at '{}': {}",
entity.updated_at, error
)));
}
},
};
Ok(Self {
return Ok(Self {
id: Some(entity.id),
wallet_id: entity.wallet_id,
transaction_id: entity.transaction_id,
@@ -112,7 +111,7 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
source_endpoint_name: entity.source_endpoint_name,
created_at,
updated_at,
})
});
}
}
@@ -127,8 +126,8 @@ fn kb_build_wallet_participation_unique_key(
let decoded_event_id_value = decoded_event_id.unwrap_or_default();
let pool_id_value = pool_id.unwrap_or_default();
let pair_id_value = pair_id.unwrap_or_default();
format!(
return format!(
"{}:{}:{}:{}:{}:{}",
wallet_id, transaction_id, decoded_event_id_value, pool_id_value, pair_id_value, role
)
);
}

View File

@@ -48,6 +48,7 @@ pub use chain_slot::get_chain_slot;
pub use chain_slot::list_recent_chain_slots;
pub use chain_slot::upsert_chain_slot;
pub use chain_transaction::get_chain_transaction_by_signature;
pub use chain_transaction::list_chain_transaction_signatures_for_replay;
pub use chain_transaction::list_recent_chain_transactions;
pub use chain_transaction::upsert_chain_transaction;
pub use db_metadata::get_db_metadata;
@@ -59,6 +60,7 @@ pub use dex::get_dex_by_code;
pub use dex::list_dexes;
pub use dex::upsert_dex;
pub use dex_decoded_event::get_dex_decoded_event_by_key;
pub use dex_decoded_event::get_latest_pump_fun_create_payload_by_mint;
pub use dex_decoded_event::list_dex_decoded_events_by_transaction_id;
pub use dex_decoded_event::upsert_dex_decoded_event;
pub use known_http_endpoint::get_known_http_endpoint;
@@ -85,6 +87,7 @@ pub use onchain_observation::insert_onchain_observation;
pub use onchain_observation::list_recent_onchain_observations;
pub use pair::get_pair_by_pool_id;
pub use pair::list_pairs;
pub use pair::update_pair_symbol;
pub use pair::upsert_pair;
pub use pair_analytic_signal::get_pair_analytic_signal_by_key;
pub use pair_analytic_signal::list_pair_analytic_signals_by_pair_id;
@@ -108,8 +111,10 @@ pub use pool_token::list_pool_tokens_by_pool_id;
pub use pool_token::upsert_pool_token;
pub use swap::list_recent_swaps;
pub use swap::upsert_swap;
pub use token::get_token_by_id;
pub use token::get_token_by_mint;
pub use token::list_tokens;
pub use token::list_tokens_missing_metadata;
pub use token::upsert_token;
pub use token_burn_event::list_recent_token_burn_events;
pub use token_burn_event::upsert_token_burn_event;

View File

@@ -15,7 +15,7 @@ pub async fn insert_analysis_signal(
"cannot serialize analysis signal payload: {}",
error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -49,10 +49,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_analysis_signals on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -92,7 +92,7 @@ LIMIT ?
"cannot list analysis signals on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -103,8 +103,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -58,10 +58,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_chain_instructions on sqlite: {}",
error
)));
}
},
};
Ok(insert_result.last_insert_rowid())
}
return Ok(insert_result.last_insert_rowid());
},
}
}
@@ -103,7 +103,7 @@ ORDER BY instruction_index ASC, inner_instruction_index ASC, id ASC
"cannot list kb_chain_instructions for transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -114,8 +114,8 @@ ORDER BY instruction_index ASC, inner_instruction_index ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -141,8 +141,8 @@ WHERE transaction_id = ?
transaction_id, error
)));
}
Ok(())
}
return Ok(());
},
}
}
@@ -163,9 +163,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
async fn make_transaction(database: &crate::KbDatabase) -> i64 {
@@ -179,9 +179,9 @@ mod tests {
None,
r#"{"transaction":{"message":{"instructions":[]}}}"#.to_string(),
);
crate::upsert_chain_transaction(database, &dto)
return crate::upsert_chain_transaction(database, &dto)
.await
.expect("chain transaction upsert must succeed")
.expect("chain transaction upsert must succeed");
}
#[tokio::test]

View File

@@ -15,7 +15,7 @@ pub async fn upsert_chain_slot(
"cannot convert chain slot '{}' to i64: {}",
dto.slot, error
)));
}
},
};
let parent_slot = match dto.parent_slot {
Some(parent_slot) => {
@@ -27,9 +27,9 @@ pub async fn upsert_chain_slot(
"cannot convert chain parent_slot '{}' to i64: {}",
parent_slot, error
)));
},
}
}
}
},
None => None,
};
match database.connection() {
@@ -63,8 +63,8 @@ ON CONFLICT(slot) DO UPDATE SET
error
)));
}
Ok(dto.slot)
}
return Ok(dto.slot);
},
}
}
@@ -81,7 +81,7 @@ pub async fn get_chain_slot(
"cannot convert requested chain slot '{}' to i64: {}",
slot, error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -108,19 +108,19 @@ LIMIT 1
"cannot fetch kb_chain_slots for slot '{}' on sqlite: {}",
slot, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbChainSlotDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -157,7 +157,7 @@ LIMIT ?
"cannot list kb_chain_slots on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -168,8 +168,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -190,9 +190,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]

View File

@@ -17,9 +17,9 @@ pub async fn upsert_chain_transaction(
"cannot convert chain transaction slot '{}' to i64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
match database.connection() {
@@ -80,13 +80,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_chain_transactions id for signature '{}' on sqlite: {}",
dto.signature, error
))),
}
)));
},
}
},
}
}
@@ -126,19 +128,19 @@ LIMIT 1
"cannot fetch kb_chain_transactions for signature '{}' on sqlite: {}",
signature, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbChainTransactionDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -181,7 +183,7 @@ LIMIT ?
"cannot list kb_chain_transactions on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -192,8 +194,49 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
return Ok(dtos);
},
}
}
/// Lists persisted chain transaction signatures for local pipeline replay.
pub async fn list_chain_transaction_signatures_for_replay(
database: &crate::KbDatabase,
limit: std::option::Option<i64>,
) -> Result<std::vec::Vec<std::string::String>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let effective_limit = match limit {
Some(limit) => {
if limit <= 0 {
10_000
} else {
limit
}
},
None => 10_000,
};
let query_result = sqlx::query_scalar::<sqlx::Sqlite, std::string::String>(
r#"
SELECT signature
FROM kb_chain_transactions
ORDER BY id ASC
LIMIT ?
"#,
)
.bind(effective_limit)
.fetch_all(pool)
.await;
match query_result {
Ok(signatures) => return Ok(signatures),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot list kb_chain_transactions signatures for local replay on sqlite: {}",
error
)));
},
}
},
}
}
@@ -214,9 +257,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]
@@ -249,10 +292,7 @@ mod tests {
assert_eq!(fetched.signature, "sig-chain-transaction-1");
assert_eq!(fetched.slot, Some(515151));
assert_eq!(fetched.block_time_unix, Some(1_700_000_001));
assert_eq!(
fetched.source_endpoint_name,
Some("helius_primary_http".to_string())
);
assert_eq!(fetched.source_endpoint_name, Some("helius_primary_http".to_string()));
assert_eq!(fetched.version_text, Some("0".to_string()));
assert_eq!(fetched.meta_json, Some(r#"{"fee":5000}"#.to_string()));
let listed = crate::list_recent_chain_transactions(&database, 10)

View File

@@ -29,13 +29,15 @@ ON CONFLICT(key) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_db_metadata on sqlite: {}",
error
))),
}
)));
},
}
},
}
}
@@ -67,19 +69,19 @@ LIMIT 1
"cannot read kb_db_metadata '{}' on sqlite: {}",
key, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDbMetadataDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -108,7 +110,7 @@ ORDER BY key ASC
"cannot list kb_db_metadata on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -119,8 +121,8 @@ ORDER BY key ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -146,9 +148,7 @@ mod tests {
.await
.expect("database init must succeed");
let dto = crate::KbDbMetadataDto::new("schema_version".to_string(), "0.5.0".to_string());
crate::upsert_db_metadata(&database, &dto)
.await
.expect("upsert must succeed");
crate::upsert_db_metadata(&database, &dto).await.expect("upsert must succeed");
let fetched = crate::get_db_metadata(&database, "schema_version")
.await
.expect("fetch must succeed");
@@ -156,9 +156,7 @@ mod tests {
let fetched = fetched.expect("metadata must exist");
assert_eq!(fetched.key, "schema_version");
assert_eq!(fetched.value, "0.5.0");
let listed = crate::list_db_metadata(&database)
.await
.expect("list must succeed");
let listed = crate::list_db_metadata(&database).await.expect("list must succeed");
assert!(!listed.is_empty());
}
}

View File

@@ -35,10 +35,10 @@ VALUES (?, ?, ?, ?, ?)
"cannot insert kb_db_runtime_events on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -76,7 +76,7 @@ LIMIT ?
"cannot list runtime events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -87,8 +87,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -56,13 +56,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_dexes id for code '{}' on sqlite: {}",
dto.code, error
))),
}
)));
},
}
},
}
}
@@ -99,19 +101,19 @@ LIMIT 1
"cannot read kb_dexes '{}' on sqlite: {}",
code, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDexDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -145,7 +147,7 @@ ORDER BY code ASC
"cannot list kb_dexes on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -156,8 +158,8 @@ ORDER BY code ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -184,13 +186,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -200,9 +196,7 @@ mod tests {
.expect("get dex must succeed");
assert!(dex.is_some());
assert_eq!(dex.expect("dex must exist").name, "Raydium");
let dexes = crate::list_dexes(&database)
.await
.expect("list dexes must succeed");
let dexes = crate::list_dexes(&database).await.expect("list dexes must succeed");
assert_eq!(dexes.len(), 1);
}
}

View File

@@ -77,13 +77,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_dex_decoded_events id on sqlite: {}",
error
))),
}
)));
},
}
},
}
}
@@ -135,19 +137,19 @@ LIMIT 1
"cannot fetch kb_dex_decoded_events on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbDexDecodedEventDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -189,7 +191,7 @@ ORDER BY id ASC
"cannot list kb_dex_decoded_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -200,8 +202,48 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
return Ok(dtos);
},
}
}
/// Returns the latest Pump.fun create payload associated with a token mint.
pub async fn get_latest_pump_fun_create_payload_by_mint(
database: &crate::KbDatabase,
mint: &str,
) -> Result<std::option::Option<std::string::String>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let payload_result = sqlx::query_scalar::<sqlx::Sqlite, std::string::String>(
r#"
SELECT payload_json
FROM kb_dex_decoded_events
WHERE protocol_name = 'pump_fun'
AND event_kind IN ('pump_fun.create', 'pump_fun.create_v2_token')
AND (
token_a_mint = ?
OR json_extract(payload_json, '$.mint') = ?
OR json_extract(payload_json, '$.tokenMint') = ?
)
ORDER BY id DESC
LIMIT 1
"#,
)
.bind(mint)
.bind(mint)
.bind(mint)
.fetch_optional(pool)
.await;
match payload_result {
Ok(payload_option) => return Ok(payload_option),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot read latest pump.fun create payload for mint '{}' on sqlite: {}",
mint, error
)));
},
}
},
}
}
@@ -228,7 +270,7 @@ mod tests {
};
let database_result = crate::KbDatabase::connect_and_initialize(&config).await;
match database_result {
Ok(database) => database,
Ok(database) => return database,
Err(error) => panic!("database init must succeed: {}", error),
}
}

View File

@@ -46,13 +46,15 @@ ON CONFLICT(name) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_known_http_endpoints on sqlite: {}",
error
))),
}
)));
},
}
},
}
}
@@ -88,19 +90,19 @@ LIMIT 1
"cannot read known http endpoint '{}' on sqlite: {}",
name, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbKnownHttpEndpointDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -133,7 +135,7 @@ ORDER BY name ASC
"cannot list known http endpoints on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -144,8 +146,8 @@ ORDER BY name ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -187,9 +189,7 @@ mod tests {
let fetched = fetched.expect("endpoint must exist");
assert_eq!(fetched.provider, "helius");
assert_eq!(fetched.roles.len(), 2);
let listed = crate::list_known_http_endpoints(&database)
.await
.expect("list must succeed");
let listed = crate::list_known_http_endpoints(&database).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -12,7 +12,6 @@ pub async fn upsert_known_ws_endpoint(
Ok(entity) => entity,
Err(error) => return Err(error),
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
@@ -46,13 +45,15 @@ ON CONFLICT(name) DO UPDATE SET
.execute(pool)
.await;
match query_result {
Ok(_) => Ok(()),
Err(error) => Err(crate::KbError::Db(format!(
Ok(_) => return Ok(()),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot upsert kb_known_ws_endpoints on sqlite: {}",
error
))),
}
)));
},
}
},
}
}
@@ -88,19 +89,19 @@ LIMIT 1
"cannot read known ws endpoint '{}' on sqlite: {}",
name, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbKnownWsEndpointDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -133,7 +134,7 @@ ORDER BY name ASC
"cannot list known ws endpoints on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -144,8 +145,8 @@ ORDER BY name ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -187,9 +188,7 @@ mod tests {
let fetched = fetched.expect("endpoint must exist");
assert_eq!(fetched.provider, "solana");
assert_eq!(fetched.roles.len(), 2);
let listed = crate::list_known_ws_endpoints(&database)
.await
.expect("list must succeed");
let listed = crate::list_known_ws_endpoints(&database).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -68,13 +68,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_attributions id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
))),
}
)));
},
}
},
}
}
@@ -85,8 +87,7 @@ pub async fn get_launch_attribution_by_decoded_event_id(
) -> Result<std::option::Option<crate::KbLaunchAttributionDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
SELECT
id,
@@ -116,13 +117,13 @@ LIMIT 1
"cannot read kb_launch_attributions by decoded_event_id '{}' on sqlite: {}",
decoded_event_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchAttributionDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbLaunchAttributionDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -133,8 +134,7 @@ pub async fn list_launch_attributions_by_pool_id(
) -> Result<std::vec::Vec<crate::KbLaunchAttributionDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchAttributionEntity>(
r#"
SELECT
id,
@@ -164,7 +164,7 @@ ORDER BY attributed_at ASC, id ASC
"cannot list kb_launch_attributions by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -175,7 +175,7 @@ ORDER BY attributed_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -53,13 +53,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surfaces id for code '{}' on sqlite: {}",
dto.code, error
))),
}
)));
},
}
},
}
}
@@ -70,8 +72,7 @@ pub async fn get_launch_surface_by_code(
) -> Result<std::option::Option<crate::KbLaunchSurfaceDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
SELECT
id,
@@ -96,13 +97,13 @@ LIMIT 1
"cannot read kb_launch_surfaces '{}' on sqlite: {}",
code, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchSurfaceDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbLaunchSurfaceDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -112,8 +113,7 @@ pub async fn list_launch_surfaces(
) -> Result<std::vec::Vec<crate::KbLaunchSurfaceDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceEntity>(
r#"
SELECT
id,
@@ -136,7 +136,7 @@ ORDER BY code ASC
"cannot list kb_launch_surfaces on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -147,7 +147,7 @@ ORDER BY code ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -50,13 +50,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_launch_surface_keys id for '{}:{}' on sqlite: {}",
dto.match_kind, dto.match_value, error
))),
}
)));
},
}
},
}
}
@@ -68,8 +70,7 @@ pub async fn get_launch_surface_key_by_match(
) -> Result<std::option::Option<crate::KbLaunchSurfaceKeyDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
SELECT
id,
@@ -94,13 +95,13 @@ LIMIT 1
"cannot read kb_launch_surface_keys '{}:{}' on sqlite: {}",
match_kind, match_value, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbLaunchSurfaceKeyDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbLaunchSurfaceKeyDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -111,8 +112,7 @@ pub async fn list_launch_surface_keys_by_surface_id(
) -> Result<std::vec::Vec<crate::KbLaunchSurfaceKeyDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
r#"
SELECT
id,
@@ -136,7 +136,7 @@ ORDER BY match_kind ASC, match_value ASC
"cannot list kb_launch_surface_keys by surface_id '{}' on sqlite: {}",
launch_surface_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -147,7 +147,7 @@ ORDER BY match_kind ASC, match_value ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -17,9 +17,9 @@ pub async fn upsert_liquidity_event(
"cannot convert liquidity event slot '{}' to i64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
match database.connection() {
@@ -96,13 +96,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_liquidity_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
}
)));
},
}
},
}
}
@@ -150,7 +152,7 @@ LIMIT ?
"cannot list kb_liquidity_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -161,7 +163,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -63,13 +63,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match select_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_observed_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
))),
}
)));
},
}
},
}
}
@@ -108,19 +110,19 @@ LIMIT 1
"cannot read observed token '{}' on sqlite: {}",
mint, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbObservedTokenDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -162,7 +164,7 @@ LIMIT ?
"cannot list observed tokens on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -173,8 +175,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -222,9 +224,7 @@ mod tests {
assert_eq!(fetched.symbol.as_deref(), Some("WSOL"));
assert_eq!(fetched.decimals, Some(9));
assert_eq!(fetched.status, crate::KbObservedTokenStatus::Active);
let listed = crate::list_observed_tokens(&database, 10)
.await
.expect("list must succeed");
let listed = crate::list_observed_tokens(&database, 10).await.expect("list must succeed");
assert_eq!(listed.len(), 1);
}
}

View File

@@ -15,7 +15,7 @@ pub async fn insert_onchain_observation(
"cannot serialize on-chain observation payload: {}",
error
)));
}
},
};
let slot_i64 = match dto.slot {
Some(slot) => {
@@ -27,9 +27,9 @@ pub async fn insert_onchain_observation(
"cannot convert on-chain observation slot '{}' to i64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
match database.connection() {
@@ -64,10 +64,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_onchain_observations on sqlite: {}",
error
)));
}
},
};
Ok(query_result.last_insert_rowid())
}
return Ok(query_result.last_insert_rowid());
},
}
}
@@ -107,7 +107,7 @@ LIMIT ?
"cannot list on-chain observations on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -118,8 +118,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -25,7 +25,11 @@ ON CONFLICT(pool_id) DO UPDATE SET
dex_id = excluded.dex_id,
base_token_id = excluded.base_token_id,
quote_token_id = excluded.quote_token_id,
symbol = excluded.symbol,
symbol = CASE
WHEN excluded.symbol IS NOT NULL AND trim(excluded.symbol) <> ''
THEN excluded.symbol
ELSE kb_pairs.symbol
END,
updated_at = excluded.updated_at
"#,
)
@@ -56,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pairs id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
}
)));
},
}
},
}
}
@@ -99,19 +105,54 @@ LIMIT 1
"cannot read kb_pairs by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPairDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
None => Ok(None),
},
}
}
/// Updates the display symbol of one normalized pair row.
pub async fn update_pair_symbol(
database: &crate::KbDatabase,
pair_id: i64,
symbol: std::option::Option<&str>,
) -> Result<bool, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
r#"
UPDATE kb_pairs
SET
symbol = ?,
updated_at = ?
WHERE id = ?
"#,
)
.bind(symbol)
.bind(chrono::Utc::now().to_rfc3339())
.bind(pair_id)
.execute(pool)
.await;
match query_result {
Ok(done) => return Ok(done.rows_affected() > 0),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot update kb_pairs symbol for id '{}' on sqlite: {}",
pair_id, error
)));
},
}
},
}
}
@@ -145,7 +186,7 @@ ORDER BY id ASC
"cannot list kb_pairs on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -156,8 +197,8 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -184,13 +225,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -248,13 +283,8 @@ mod tests {
.await
.expect("get pair must succeed");
assert!(pair.is_some());
assert_eq!(
pair.expect("pair must exist").symbol.as_deref(),
Some("BASE/WSOL")
);
let pairs = crate::list_pairs(&database)
.await
.expect("list pairs must succeed");
assert_eq!(pair.expect("pair must exist").symbol.as_deref(), Some("BASE/WSOL"));
let pairs = crate::list_pairs(&database).await.expect("list pairs must succeed");
assert_eq!(pairs.len(), 1);
}
}

View File

@@ -15,7 +15,7 @@ pub async fn upsert_pair_analytic_signal(
"cannot serialize pair analytic signal payload: {}",
error
)));
}
},
};
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -77,13 +77,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_analytic_signals id on sqlite: {}",
error
))),
}
)));
},
}
},
}
}
@@ -97,8 +99,7 @@ pub async fn get_pair_analytic_signal_by_key(
) -> Result<std::option::Option<crate::KbPairAnalyticSignalDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
SELECT
id,
@@ -131,13 +132,13 @@ LIMIT 1
"cannot read kb_pair_analytic_signals by key on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairAnalyticSignalDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbPairAnalyticSignalDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -148,8 +149,7 @@ pub async fn list_pair_analytic_signals_by_pair_id(
) -> Result<std::vec::Vec<crate::KbPairAnalyticSignalDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbPairAnalyticSignalEntity>(
r#"
SELECT
id,
@@ -179,7 +179,7 @@ ORDER BY timeframe_seconds ASC, bucket_start_unix ASC, signal_kind ASC, id ASC
"cannot list kb_pair_analytic_signals by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -190,7 +190,7 @@ ORDER BY timeframe_seconds ASC, bucket_start_unix ASC, signal_kind ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -87,13 +87,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_candles id for pair_id '{}' timeframe '{}' bucket '{}' on sqlite: {}",
dto.pair_id, dto.timeframe_seconds, dto.bucket_start_unix, error
))),
}
)));
},
}
},
}
}
@@ -144,13 +146,13 @@ LIMIT 1
"cannot read kb_pair_candles by key on sqlite: {}",
error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairCandleDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbPairCandleDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -199,7 +201,7 @@ ORDER BY bucket_start_unix ASC, id ASC
"cannot list kb_pair_candles by pair_id '{}' timeframe '{}' on sqlite: {}",
pair_id, timeframe_seconds, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -210,7 +212,7 @@ ORDER BY bucket_start_unix ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -74,13 +74,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pair_metrics id for pair_id '{}' on sqlite: {}",
dto.pair_id, error
))),
}
)));
},
}
},
}
}
@@ -123,13 +125,13 @@ LIMIT 1
"cannot read kb_pair_metrics by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPairMetricDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbPairMetricDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -169,7 +171,7 @@ ORDER BY pair_id ASC
"cannot list kb_pair_metrics on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -180,7 +182,7 @@ ORDER BY pair_id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -53,13 +53,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pools id for address '{}' on sqlite: {}",
dto.address, error
))),
}
)));
},
}
},
}
}
@@ -95,19 +97,19 @@ LIMIT 1
"cannot read kb_pools '{}' on sqlite: {}",
address, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPoolDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -140,7 +142,7 @@ ORDER BY id ASC
"cannot list kb_pools on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -151,8 +153,8 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -179,13 +181,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -206,13 +202,8 @@ mod tests {
.await
.expect("get pool must succeed");
assert!(pool.is_some());
assert_eq!(
pool.expect("pool must exist").pool_kind,
crate::KbPoolKind::Amm
);
let pools = crate::list_pools(&database)
.await
.expect("list pools must succeed");
assert_eq!(pool.expect("pool must exist").pool_kind, crate::KbPoolKind::Amm);
let pools = crate::list_pools(&database).await.expect("list pools must succeed");
assert_eq!(pools.len(), 1);
}
}

View File

@@ -66,13 +66,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_listings id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
}
)));
},
}
},
}
}
@@ -112,19 +114,19 @@ LIMIT 1
"cannot read kb_pool_listings by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbPoolListingDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
@@ -161,7 +163,7 @@ ORDER BY detected_at ASC, id ASC
"cannot list kb_pool_listings on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -172,8 +174,8 @@ ORDER BY detected_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -200,13 +202,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");

View File

@@ -69,13 +69,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_origins id for pool_id '{}' on sqlite: {}",
dto.pool_id, error
))),
}
)));
},
}
},
}
}
@@ -119,13 +121,13 @@ LIMIT 1
"cannot read kb_pool_origins by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbPoolOriginDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbPoolOriginDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -166,7 +168,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_pool_origins on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -177,7 +179,7 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -56,13 +56,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_pool_tokens id on sqlite: {}",
error
))),
}
)));
},
}
},
}
}
@@ -99,7 +101,7 @@ ORDER BY token_order ASC, id ASC
"cannot list kb_pool_tokens for pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -110,8 +112,8 @@ ORDER BY token_order ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -138,13 +140,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");

View File

@@ -17,9 +17,9 @@ pub async fn upsert_swap(
"cannot convert swap slot '{}' to i64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
match database.connection() {
@@ -93,13 +93,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_swaps id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
}
)));
},
}
},
}
}
@@ -146,7 +148,7 @@ LIMIT ?
"cannot list kb_swaps on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -157,7 +159,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -60,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_tokens id for mint '{}' on sqlite: {}",
dto.mint, error
))),
}
)));
},
}
},
}
}
@@ -104,22 +106,71 @@ LIMIT 1
"cannot read kb_tokens '{}' on sqlite: {}",
mint, error
)));
}
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbTokenDto::try_from(entity);
match dto_result {
Ok(dto) => Ok(Some(dto)),
Err(error) => Err(error),
}
}
None => Ok(None),
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
/// Reads one normalized token row by internal id.
pub async fn get_token_by_id(
database: &crate::KbDatabase,
token_id: i64,
) -> Result<std::option::Option<crate::KbTokenDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE id = ?
LIMIT 1
"#,
)
.bind(token_id)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot read kb_tokens id '{}' on sqlite: {}",
token_id, error
)));
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::KbTokenDto::try_from(entity);
match dto_result {
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
/// Lists all normalized token rows ordered by mint.
pub async fn list_tokens(
@@ -152,7 +203,7 @@ ORDER BY mint ASC, id ASC
"cannot list kb_tokens on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -163,7 +214,161 @@ ORDER BY mint ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
/// Lists token rows whose display or mint metadata is incomplete.
pub async fn list_tokens_missing_metadata(
database: &crate::KbDatabase,
limit: std::option::Option<i64>,
) -> Result<std::vec::Vec<crate::KbTokenDto>, crate::KbError> {
match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => {
let entities_result = match limit {
Some(limit) => {
sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE symbol IS NULL
OR trim(symbol) = ''
OR name IS NULL
OR trim(name) = ''
OR decimals IS NULL
OR token_program IS NULL
OR trim(token_program) = ''
ORDER BY updated_at ASC, id ASC
LIMIT ?
"#,
)
.bind(limit)
.fetch_all(pool)
.await
},
None => {
sqlx::query_as::<sqlx::Sqlite, crate::KbTokenEntity>(
r#"
SELECT
id,
mint,
symbol,
name,
decimals,
token_program,
is_quote_token,
first_seen_at,
updated_at
FROM kb_tokens
WHERE symbol IS NULL
OR trim(symbol) = ''
OR name IS NULL
OR trim(name) = ''
OR decimals IS NULL
OR token_program IS NULL
OR trim(token_program) = ''
ORDER BY updated_at ASC, id ASC
"#,
)
.fetch_all(pool)
.await
},
};
let entities = match entities_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot list kb_tokens missing metadata on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
let dto_result = crate::KbTokenDto::try_from(entity);
let dto = match dto_result {
Ok(dto) => dto,
Err(error) => return Err(error),
};
dtos.push(dto);
}
return Ok(dtos);
},
}
}
#[cfg(test)]
mod tests {
async fn make_database() -> std::sync::Arc<crate::KbDatabase> {
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_query.sqlite3");
let config = crate::KbDatabaseConfig {
enabled: true,
backend: crate::KbDatabaseBackend::Sqlite,
sqlite: crate::KbSqliteDatabaseConfig {
path: database_path.to_string_lossy().to_string(),
create_if_missing: true,
busy_timeout_ms: 5000,
max_connections: 1,
auto_initialize_schema: true,
use_wal: true,
},
};
let database_result = crate::KbDatabase::connect_and_initialize(&config).await;
let database = match database_result {
Ok(database) => database,
Err(error) => panic!("database init must succeed: {}", error),
};
return std::sync::Arc::new(database);
}
#[tokio::test]
async fn list_tokens_missing_metadata_only_returns_incomplete_rows() {
let database = make_database().await;
let incomplete = crate::KbTokenDto::new(
"IncompleteMint111".to_string(),
None,
None,
None,
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let complete = crate::KbTokenDto::new(
"CompleteMint111".to_string(),
Some("CMP".to_string()),
Some("Complete".to_string()),
Some(6),
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
false,
);
let incomplete_result = crate::upsert_token(database.as_ref(), &incomplete).await;
if let Err(error) = incomplete_result {
panic!("incomplete token upsert must succeed: {}", error);
}
let complete_result = crate::upsert_token(database.as_ref(), &complete).await;
if let Err(error) = complete_result {
panic!("complete token upsert must succeed: {}", error);
}
let missing_result = crate::list_tokens_missing_metadata(database.as_ref(), None).await;
let missing = match missing_result {
Ok(missing) => missing,
Err(error) => panic!("missing metadata list must succeed: {}", error),
};
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].mint, "IncompleteMint111");
}
}

View File

@@ -17,9 +17,9 @@ pub async fn upsert_token_burn_event(
"cannot convert token burn event slot '{}' to i64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
match database.connection() {
@@ -78,13 +78,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_token_burn_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
}
)));
},
}
},
}
}
@@ -126,7 +128,7 @@ LIMIT ?
"cannot list kb_token_burn_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -137,8 +139,8 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -165,13 +167,7 @@ mod tests {
.expect("database init must succeed");
let dex_id = crate::upsert_dex(
&database,
&crate::KbDexDto::new(
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
&crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
)
.await
.expect("dex upsert must succeed");
@@ -299,9 +295,7 @@ mod tests {
assert!(liquidity_id > 0);
assert!(mint_id > 0);
assert!(burn_id > 0);
let swaps = crate::list_recent_swaps(&database, 10)
.await
.expect("swaps list must succeed");
let swaps = crate::list_recent_swaps(&database, 10).await.expect("swaps list must succeed");
let liquidity_events = crate::list_recent_liquidity_events(&database, 10)
.await
.expect("liquidity list must succeed");

View File

@@ -17,9 +17,9 @@ pub async fn upsert_token_mint_event(
"cannot convert token mint event slot '{}' to i64: {}",
slot, error
)));
},
}
}
}
},
None => None,
};
match database.connection() {
@@ -78,13 +78,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_token_mint_events id for signature '{}' and instruction_index '{}' on sqlite: {}",
dto.signature, dto.instruction_index, error
))),
}
)));
},
}
},
}
}
@@ -126,7 +128,7 @@ LIMIT ?
"cannot list kb_token_mint_events on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -137,7 +139,7 @@ LIMIT ?
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -90,13 +90,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_trade_events id for decoded_event_id '{}' on sqlite: {}",
dto.decoded_event_id, error
))),
}
)));
},
}
},
}
}
@@ -144,13 +146,13 @@ LIMIT 1
"cannot read kb_trade_events by decoded_event_id '{}' on sqlite: {}",
decoded_event_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbTradeEventDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbTradeEventDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -198,7 +200,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_trade_events by pair_id '{}' on sqlite: {}",
pair_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -209,8 +211,8 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -258,7 +260,7 @@ ORDER BY id ASC
"cannot list kb_trade_events by transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -269,15 +271,15 @@ ORDER BY id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
fn kb_trade_side_to_string(value: crate::KbSwapTradeSide) -> &'static str {
match value {
crate::KbSwapTradeSide::BuyBase => "BuyBase",
crate::KbSwapTradeSide::SellBase => "SellBase",
crate::KbSwapTradeSide::Unknown => "Unknown",
crate::KbSwapTradeSide::BuyBase => return "BuyBase",
crate::KbSwapTradeSide::SellBase => return "SellBase",
crate::KbSwapTradeSide::Unknown => return "Unknown",
}
}

View File

@@ -47,13 +47,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallets id for address '{}' on sqlite: {}",
dto.address, error
))),
}
)));
},
}
},
}
}
@@ -87,13 +89,13 @@ LIMIT 1
"cannot read kb_wallets '{}' on sqlite: {}",
address, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbWalletDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -124,7 +126,7 @@ ORDER BY address ASC
"cannot list kb_wallets on sqlite: {}",
error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -135,7 +137,7 @@ ORDER BY address ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -76,13 +76,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_holdings id for wallet_id '{}' token_id '{}' on sqlite: {}",
dto.wallet_id, dto.token_id, error
))),
}
)));
},
}
},
}
}
@@ -128,13 +130,13 @@ LIMIT 1
"cannot read kb_wallet_holdings by wallet_id '{}' token_id '{}' on sqlite: {}",
wallet_id, token_id, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletHoldingDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbWalletHoldingDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -178,7 +180,7 @@ ORDER BY token_id ASC, id ASC
"cannot list kb_wallet_holdings by wallet_id '{}' on sqlite: {}",
wallet_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -189,7 +191,7 @@ ORDER BY token_id ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

View File

@@ -60,13 +60,15 @@ LIMIT 1
.fetch_one(pool)
.await;
match id_result {
Ok(id) => Ok(id),
Err(error) => Err(crate::KbError::Db(format!(
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot fetch kb_wallet_participations id for unique_key '{}' on sqlite: {}",
dto.unique_key, error
))),
}
)));
},
}
},
}
}
@@ -107,13 +109,13 @@ LIMIT 1
"cannot read kb_wallet_participations by unique_key '{}' on sqlite: {}",
unique_key, error
)));
}
},
};
match entity_option {
Some(entity) => crate::KbWalletParticipationDto::try_from(entity).map(Some),
None => Ok(None),
}
Some(entity) => return crate::KbWalletParticipationDto::try_from(entity).map(Some),
None => return Ok(None),
}
},
}
}
@@ -154,7 +156,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_wallet_participations by wallet_id '{}' on sqlite: {}",
wallet_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -165,8 +167,8 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}
@@ -207,7 +209,7 @@ ORDER BY created_at ASC, id ASC
"cannot list kb_wallet_participations by pool_id '{}' on sqlite: {}",
pool_id, error
)));
}
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
@@ -218,7 +220,7 @@ ORDER BY created_at ASC, id ASC
};
dtos.push(dto);
}
Ok(dtos)
}
return Ok(dtos);
},
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,10 @@ pub(crate) fn sqlite_database_url_from_config(
) -> Result<std::string::String, crate::KbError> {
let path = config.sqlite.path.trim();
if path.is_empty() {
return Err(crate::KbError::Config(
"database.sqlite.path must not be empty".to_string(),
));
return Err(crate::KbError::Config("database.sqlite.path must not be empty".to_string()));
}
let database_path = config.sqlite.path_buf();
Ok(format!("sqlite://{}", database_path.display()))
return Ok(format!("sqlite://{}", database_path.display()));
}
/// Opens a SQLite pool according to configuration.
@@ -22,9 +20,7 @@ pub(crate) async fn connect_sqlite(
) -> Result<sqlx::SqlitePool, crate::KbError> {
let path = config.sqlite.path.trim();
if path.is_empty() {
return Err(crate::KbError::Config(
"database.sqlite.path must not be empty".to_string(),
));
return Err(crate::KbError::Config("database.sqlite.path must not be empty".to_string()));
}
if config.sqlite.max_connections == 0 {
return Err(crate::KbError::Config(
@@ -49,9 +45,7 @@ pub(crate) async fn connect_sqlite(
.filename(&database_path)
.create_if_missing(config.sqlite.create_if_missing)
.foreign_keys(true)
.busy_timeout(std::time::Duration::from_millis(
config.sqlite.busy_timeout_ms,
));
.busy_timeout(std::time::Duration::from_millis(config.sqlite.busy_timeout_ms));
if config.sqlite.use_wal {
connect_options = connect_options.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal);
}
@@ -59,10 +53,13 @@ pub(crate) async fn connect_sqlite(
sqlx::sqlite::SqlitePoolOptions::new().max_connections(config.sqlite.max_connections);
let connect_result = pool_options.connect_with(connect_options).await;
match connect_result {
Ok(pool) => Ok(pool),
Err(error) => Err(crate::KbError::Db(format!(
Ok(pool) => return Ok(pool),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot open sqlite database '{}': {}",
database_path.display(), error
))),
database_path.display(),
error
)));
},
}
}

View File

@@ -21,28 +21,28 @@ impl KbAnalysisSignalSeverity {
/// Converts the severity to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Info => 0,
Self::Low => 1,
Self::Medium => 2,
Self::High => 3,
Self::Critical => 4,
Self::Info => return 0,
Self::Low => return 1,
Self::Medium => return 2,
Self::High => return 3,
Self::Critical => return 4,
}
}
/// Restores a severity from its stable integer representation.
pub fn from_i16(
value: i16,
) -> Result<Self, crate::KbError> {
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Info),
1 => Ok(Self::Low),
2 => Ok(Self::Medium),
3 => Ok(Self::High),
4 => Ok(Self::Critical),
_ => Err(crate::KbError::Db(format!(
0 => return Ok(Self::Info),
1 => return Ok(Self::Low),
2 => return Ok(Self::Medium),
3 => return Ok(Self::High),
4 => return Ok(Self::Critical),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbAnalysisSignalSeverity value: {}",
value
))),
)));
},
}
}
}

View File

@@ -15,20 +15,22 @@ impl KbLiquidityEventKind {
/// Converts the event kind to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Add => 0,
Self::Remove => 1,
Self::Add => return 0,
Self::Remove => return 1,
}
}
/// Restores an event kind from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Add),
1 => Ok(Self::Remove),
_ => Err(crate::KbError::Db(format!(
0 => return Ok(Self::Add),
1 => return Ok(Self::Remove),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbLiquidityEventKind value: {}",
value
))),
)));
},
}
}
}

View File

@@ -21,26 +21,28 @@ impl KbObservationSourceKind {
/// Converts the source kind to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::HttpRpc => 0,
Self::WsRpc => 1,
Self::Grpc => 2,
Self::Dex => 3,
Self::Other => 4,
Self::HttpRpc => return 0,
Self::WsRpc => return 1,
Self::Grpc => return 2,
Self::Dex => return 3,
Self::Other => return 4,
}
}
/// Restores a source kind from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::HttpRpc),
1 => Ok(Self::WsRpc),
2 => Ok(Self::Grpc),
3 => Ok(Self::Dex),
4 => Ok(Self::Other),
_ => Err(crate::KbError::Db(format!(
0 => return Ok(Self::HttpRpc),
1 => return Ok(Self::WsRpc),
2 => return Ok(Self::Grpc),
3 => return Ok(Self::Dex),
4 => return Ok(Self::Other),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbObservationSourceKind value: {}",
value
))),
)));
},
}
}
}

View File

@@ -19,24 +19,26 @@ impl KbObservedTokenStatus {
/// Converts the status to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::New => 0,
Self::Active => 1,
Self::Ignored => 2,
Self::Blocked => 3,
Self::New => return 0,
Self::Active => return 1,
Self::Ignored => return 2,
Self::Blocked => return 3,
}
}
/// Restores a status from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::New),
1 => Ok(Self::Active),
2 => Ok(Self::Ignored),
3 => Ok(Self::Blocked),
_ => Err(crate::KbError::Db(format!(
0 => return Ok(Self::New),
1 => return Ok(Self::Active),
2 => return Ok(Self::Ignored),
3 => return Ok(Self::Blocked),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbObservedTokenStatus value: {}",
value
))),
)));
},
}
}
}

View File

@@ -1,4 +1,3 @@
// file: kb_lib/src/db/types/pool_kind.rs
//! Pool kind.
@@ -22,28 +21,23 @@ impl KbPoolKind {
/// Converts the kind to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Unknown => 0,
Self::Amm => 1,
Self::Clmm => 2,
Self::BondingCurve => 3,
Self::OrderBook => 4,
Self::Unknown => return 0,
Self::Amm => return 1,
Self::Clmm => return 2,
Self::BondingCurve => return 3,
Self::OrderBook => return 4,
}
}
/// Restores a kind from its stable integer representation.
pub fn from_i16(
value: i16,
) -> Result<Self, crate::KbError> {
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Unknown),
1 => Ok(Self::Amm),
2 => Ok(Self::Clmm),
3 => Ok(Self::BondingCurve),
4 => Ok(Self::OrderBook),
_ => Err(crate::KbError::Db(format!(
"invalid KbPoolKind value: {}",
value
))),
0 => return Ok(Self::Unknown),
1 => return Ok(Self::Amm),
2 => return Ok(Self::Clmm),
3 => return Ok(Self::BondingCurve),
4 => return Ok(Self::OrderBook),
_ => return Err(crate::KbError::Db(format!("invalid KbPoolKind value: {}", value))),
}
}
}

View File

@@ -21,26 +21,23 @@ impl KbPoolStatus {
/// Converts the status to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Unknown => 0,
Self::Pending => 1,
Self::Active => 2,
Self::Inactive => 3,
Self::Closed => 4,
Self::Unknown => return 0,
Self::Pending => return 1,
Self::Active => return 2,
Self::Inactive => return 3,
Self::Closed => return 4,
}
}
/// Restores a status from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Unknown),
1 => Ok(Self::Pending),
2 => Ok(Self::Active),
3 => Ok(Self::Inactive),
4 => Ok(Self::Closed),
_ => Err(crate::KbError::Db(format!(
"invalid KbPoolStatus value: {}",
value
))),
0 => return Ok(Self::Unknown),
1 => return Ok(Self::Pending),
2 => return Ok(Self::Active),
3 => return Ok(Self::Inactive),
4 => return Ok(Self::Closed),
_ => return Err(crate::KbError::Db(format!("invalid KbPoolStatus value: {}", value))),
}
}
}

View File

@@ -21,26 +21,25 @@ impl KbPoolTokenRole {
/// Converts the role to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Base => 0,
Self::Quote => 1,
Self::LpMint => 2,
Self::Reserve => 3,
Self::Other => 4,
Self::Base => return 0,
Self::Quote => return 1,
Self::LpMint => return 2,
Self::Reserve => return 3,
Self::Other => return 4,
}
}
/// Restores a role from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Base),
1 => Ok(Self::Quote),
2 => Ok(Self::LpMint),
3 => Ok(Self::Reserve),
4 => Ok(Self::Other),
_ => Err(crate::KbError::Db(format!(
"invalid KbPoolTokenRole value: {}",
value
))),
0 => return Ok(Self::Base),
1 => return Ok(Self::Quote),
2 => return Ok(Self::LpMint),
3 => return Ok(Self::Reserve),
4 => return Ok(Self::Other),
_ => {
return Err(crate::KbError::Db(format!("invalid KbPoolTokenRole value: {}", value)));
},
}
}
}

View File

@@ -21,26 +21,28 @@ impl KbDbRuntimeEventLevel {
/// Converts the level to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Trace => 0,
Self::Debug => 1,
Self::Info => 2,
Self::Warn => 3,
Self::Error => 4,
Self::Trace => return 0,
Self::Debug => return 1,
Self::Info => return 2,
Self::Warn => return 3,
Self::Error => return 4,
}
}
/// Restores a level from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Trace),
1 => Ok(Self::Debug),
2 => Ok(Self::Info),
3 => Ok(Self::Warn),
4 => Ok(Self::Error),
_ => Err(crate::KbError::Db(format!(
0 => return Ok(Self::Trace),
1 => return Ok(Self::Debug),
2 => return Ok(Self::Info),
3 => return Ok(Self::Warn),
4 => return Ok(Self::Error),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbDbRuntimeEventLevel value: {}",
value
))),
)));
},
}
}
}

View File

@@ -17,22 +17,24 @@ impl KbSwapTradeSide {
/// Converts the trade side to its stable integer representation.
pub fn to_i16(self) -> i16 {
match self {
Self::Unknown => 0,
Self::BuyBase => 1,
Self::SellBase => 2,
Self::Unknown => return 0,
Self::BuyBase => return 1,
Self::SellBase => return 2,
}
}
/// Restores a trade side from its stable integer representation.
pub fn from_i16(value: i16) -> Result<Self, crate::KbError> {
match value {
0 => Ok(Self::Unknown),
1 => Ok(Self::BuyBase),
2 => Ok(Self::SellBase),
_ => Err(crate::KbError::Db(format!(
0 => return Ok(Self::Unknown),
1 => return Ok(Self::BuyBase),
2 => return Ok(Self::SellBase),
_ => {
return Err(crate::KbError::Db(format!(
"invalid KbSwapTradeSide value: {}",
value
))),
)));
},
}
}
}

View File

@@ -12,11 +12,11 @@ pub struct KbDetectionPersistenceService {
impl KbDetectionPersistenceService {
/// Creates a new detection persistence service.
pub fn new(database: std::sync::Arc<crate::KbDatabase>) -> Self {
Self { database }
return Self { database };
}
/// Returns the shared database handle.
pub fn database(&self) -> &std::sync::Arc<crate::KbDatabase> {
&self.database
return &self.database;
}
/// Persists one on-chain observation.
@@ -32,7 +32,7 @@ impl KbDetectionPersistenceService {
input.slot,
input.payload.clone(),
);
crate::insert_onchain_observation(self.database.as_ref(), &dto).await
return crate::insert_onchain_observation(self.database.as_ref(), &dto).await;
}
/// Persists one analysis signal.
@@ -48,7 +48,7 @@ impl KbDetectionPersistenceService {
input.score,
input.payload.clone(),
);
crate::insert_analysis_signal(self.database.as_ref(), &dto).await
return crate::insert_analysis_signal(self.database.as_ref(), &dto).await;
}
/// Registers one token candidate from a technical source.
@@ -104,11 +104,7 @@ impl KbDetectionPersistenceService {
Ok(signal_id) => signal_id,
Err(error) => return Err(error),
};
Ok(crate::KbDetectionTokenCandidateResult {
token_id,
observation_id,
signal_id,
})
return Ok(crate::KbDetectionTokenCandidateResult { token_id, observation_id, signal_id });
}
/// Registers one pool candidate from a technical source.
@@ -154,7 +150,7 @@ impl KbDetectionPersistenceService {
"cannot register pool candidate: no known dex matches program id '{}'",
input.dex_program_id
)));
}
},
};
let dex_id = match matched_dex.id {
Some(dex_id) => dex_id,
@@ -162,7 +158,7 @@ impl KbDetectionPersistenceService {
return Err(crate::KbError::Db(
"cannot register pool candidate: matched dex has no id".to_string(),
));
}
},
};
let pool_dto = crate::KbPoolDto::new(
dex_id,
@@ -221,13 +217,13 @@ impl KbDetectionPersistenceService {
Ok(signal_id) => signal_id,
Err(error) => return Err(error),
};
Ok(crate::KbDetectionPoolCandidateResult {
return Ok(crate::KbDetectionPoolCandidateResult {
dex_id,
pool_id,
pool_listing_id,
observation_id,
signal_id,
})
});
}
}
@@ -248,9 +244,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]
@@ -293,14 +289,8 @@ mod tests {
.expect("list signals must succeed");
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
observations[0].object_key,
"So11111111111111111111111111111111111111112"
);
assert_eq!(
signals[0].object_key,
"So11111111111111111111111111111111111111112"
);
assert_eq!(observations[0].object_key, "So11111111111111111111111111111111111111112");
assert_eq!(signals[0].object_key, "So11111111111111111111111111111111111111112");
}
#[tokio::test]
@@ -340,10 +330,7 @@ mod tests {
.await
.expect("get token must succeed");
assert!(token.is_some());
assert_eq!(
token.expect("token must exist").symbol.as_deref(),
Some("TEST")
);
assert_eq!(token.expect("token must exist").symbol.as_deref(), Some("TEST"));
let observations = crate::list_recent_onchain_observations(service.database().as_ref(), 10)
.await
.expect("list observations must succeed");
@@ -352,18 +339,9 @@ mod tests {
.expect("list signals must succeed");
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
observations[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].related_observation_id,
Some(result.observation_id)
);
assert_eq!(observations[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].related_observation_id, Some(result.observation_id));
}
#[tokio::test]

View File

@@ -37,12 +37,12 @@ pub struct KbSolanaWsDetectionService {
impl KbSolanaWsDetectionService {
/// Creates a new Solana WebSocket detection service.
pub fn new(persistence: crate::KbDetectionPersistenceService) -> Self {
Self { persistence }
return Self { persistence };
}
/// Returns the shared persistence façade.
pub fn persistence(&self) -> &crate::KbDetectionPersistenceService {
&self.persistence
return &self.persistence;
}
/// Processes one Solana WebSocket JSON-RPC notification.
@@ -57,24 +57,22 @@ impl KbSolanaWsDetectionService {
Some(observation_kind) => observation_kind,
None => return Ok(crate::KbSolanaWsDetectionOutcome::Ignored),
};
let token_candidate_result = self
.try_register_token_candidate(endpoint_name.clone(), notification)
.await;
let token_candidate_result =
self.try_register_token_candidate(endpoint_name.clone(), notification).await;
match token_candidate_result {
Ok(Some(result)) => {
return Ok(crate::KbSolanaWsDetectionOutcome::TokenCandidateRegistered { result });
}
Ok(None) => {}
},
Ok(None) => {},
Err(error) => return Err(error),
}
let pool_candidate_result = self
.try_register_pool_candidate(endpoint_name.clone(), notification)
.await;
let pool_candidate_result =
self.try_register_pool_candidate(endpoint_name.clone(), notification).await;
match pool_candidate_result {
Ok(Some(result)) => {
return Ok(crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { result });
}
Ok(None) => {}
},
Ok(None) => {},
Err(error) => return Err(error),
}
let payload = build_notification_payload(notification);
@@ -93,10 +91,7 @@ impl KbSolanaWsDetectionService {
slot,
payload.clone(),
);
let observation_id_result = self
.persistence
.record_observation(&observation_input)
.await;
let observation_id_result = self.persistence.record_observation(&observation_input).await;
let observation_id = match observation_id_result {
Ok(observation_id) => observation_id,
Err(error) => return Err(error),
@@ -127,7 +122,7 @@ impl KbSolanaWsDetectionService {
return Err(error);
}
}
Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id })
return Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id });
}
/// Tries to register a token candidate from one notification.
@@ -210,8 +205,8 @@ impl KbSolanaWsDetectionService {
);
let result = self.persistence.register_token_candidate(&input).await;
match result {
Ok(result) => Ok(Some(result)),
Err(error) => Err(error),
Ok(result) => return Ok(Some(result)),
Err(error) => return Err(error),
}
}
@@ -288,8 +283,8 @@ impl KbSolanaWsDetectionService {
);
let result = self.persistence.register_pool_candidate(&input).await;
match result {
Ok(result) => Ok(Some(result)),
Err(error) => Err(error),
Ok(result) => return Ok(Some(result)),
Err(error) => return Err(error),
}
}
}
@@ -299,27 +294,27 @@ fn map_notification_method_to_observation_kind(
method: &str,
) -> std::option::Option<std::string::String> {
match method {
"accountNotification" => Some("ws.account_notification".to_string()),
"blockNotification" => Some("ws.block_notification".to_string()),
"logsNotification" => Some("ws.logs_notification".to_string()),
"programNotification" => Some("ws.program_notification".to_string()),
"rootNotification" => Some("ws.root_notification".to_string()),
"signatureNotification" => Some("ws.signature_notification".to_string()),
"slotNotification" => Some("ws.slot_notification".to_string()),
"slotsUpdatesNotification" => Some("ws.slots_updates_notification".to_string()),
"voteNotification" => Some("ws.vote_notification".to_string()),
_ => None,
"accountNotification" => return Some("ws.account_notification".to_string()),
"blockNotification" => return Some("ws.block_notification".to_string()),
"logsNotification" => return Some("ws.logs_notification".to_string()),
"programNotification" => return Some("ws.program_notification".to_string()),
"rootNotification" => return Some("ws.root_notification".to_string()),
"signatureNotification" => return Some("ws.signature_notification".to_string()),
"slotNotification" => return Some("ws.slot_notification".to_string()),
"slotsUpdatesNotification" => return Some("ws.slots_updates_notification".to_string()),
"voteNotification" => return Some("ws.vote_notification".to_string()),
_ => return None,
}
}
/// Wraps one raw notification into a normalized JSON payload.
fn build_notification_payload(notification: &crate::KbJsonRpcWsNotification) -> serde_json::Value {
serde_json::json!({
return serde_json::json!({
"jsonrpc": notification.jsonrpc,
"method": notification.method,
"subscription": notification.params.subscription,
"result": notification.params.result,
})
});
}
/// Builds one logical object key from the notification result.
@@ -340,8 +335,7 @@ fn build_object_key(
if let Some(slot) = slot_option {
return format!("slot:{slot}");
}
format!("subscription:{subscription}")
return format!("subscription:{subscription}");
}
/// Extracts a slot number from one notification result.
@@ -364,7 +358,7 @@ fn extract_slot_from_result(method: &str, result: &serde_json::Value) -> std::op
return Some(slot);
}
}
None
return None;
}
/// Extracts a pubkey from one notification result.
@@ -379,7 +373,7 @@ fn extract_pubkey_from_result(
return Some(pubkey.to_string());
}
}
None
return None;
}
/// Extracts a signature from one notification result.
@@ -394,13 +388,13 @@ fn extract_signature_from_result(
return Some(signature.to_string());
}
}
None
return None;
}
/// Extracts one account-like JSON object from one notification result.
fn extract_account_value_from_result<'a>(
result: &'a serde_json::Value,
) -> std::option::Option<&'a serde_json::Value> {
fn extract_account_value_from_result(
result: &serde_json::Value,
) -> std::option::Option<&serde_json::Value> {
if let Some(account) = result.get("account") {
return Some(account);
}
@@ -412,7 +406,7 @@ fn extract_account_value_from_result<'a>(
return Some(value);
}
}
None
return None;
}
/// Extracts the parsed account type from one account-like JSON object.
@@ -431,8 +425,8 @@ fn extract_parsed_account_type(
};
let type_option = parsed.get("type").and_then(serde_json::Value::as_str);
match type_option {
Some(parsed_type) => Some(parsed_type.to_string()),
None => None,
Some(parsed_type) => return Some(parsed_type.to_string()),
None => return None,
}
}
@@ -440,12 +434,10 @@ fn extract_parsed_account_type(
fn extract_account_owner(
account_value: &serde_json::Value,
) -> std::option::Option<std::string::String> {
let owner_option = account_value
.get("owner")
.and_then(serde_json::Value::as_str);
let owner_option = account_value.get("owner").and_then(serde_json::Value::as_str);
match owner_option {
Some(owner) => Some(owner.to_string()),
None => None,
Some(owner) => return Some(owner.to_string()),
None => return None,
}
}
@@ -475,8 +467,8 @@ fn extract_decimals_from_account_value(
};
let converted = u8::try_from(decimals);
match converted {
Ok(decimals) => Some(decimals),
Err(_) => None,
Ok(decimals) => return Some(decimals),
Err(_) => return None,
}
}
@@ -489,7 +481,7 @@ fn extract_program_notification_owner(
Some(account_value) => account_value,
None => return None,
};
extract_account_owner(account_value)
return extract_account_owner(account_value);
}
/// Extracts the parsed token amount decimals from one parsed token account notification.
@@ -516,17 +508,15 @@ fn extract_token_account_decimals_from_account_value(
Some(token_amount) => token_amount,
None => return None,
};
let decimals_option = token_amount
.get("decimals")
.and_then(serde_json::Value::as_u64);
let decimals_option = token_amount.get("decimals").and_then(serde_json::Value::as_u64);
let decimals = match decimals_option {
Some(decimals) => decimals,
None => return None,
};
let convert_result = u8::try_from(decimals);
match convert_result {
Ok(decimals) => Some(decimals),
Err(_) => None,
Ok(decimals) => return Some(decimals),
Err(_) => return None,
}
}
@@ -551,8 +541,8 @@ fn extract_parsed_account_mint(
};
let mint_option = info.get("mint").and_then(serde_json::Value::as_str);
match mint_option {
Some(mint) => Some(mint.to_string()),
None => None,
Some(mint) => return Some(mint.to_string()),
None => return None,
}
}
@@ -580,7 +570,7 @@ fn extract_logs_lines(result: &serde_json::Value) -> std::vec::Vec<std::string::
lines.push(line.to_string());
}
}
lines
return lines;
}
/// Extracts the error field from a signature notification result.
@@ -593,8 +583,8 @@ fn extract_signature_notification_err(
None => return None,
};
match value.get("err") {
Some(err) => Some(err.clone()),
None => None,
Some(err) => return Some(err.clone()),
None => return None,
}
}
@@ -621,8 +611,8 @@ fn build_signal_kind_for_notification(
}
}
}
"signal.account_notification.generic".to_string()
}
return "signal.account_notification.generic".to_string();
},
"logsNotification" => {
let lines = extract_logs_lines(result);
for line in &lines {
@@ -639,21 +629,21 @@ fn build_signal_kind_for_notification(
return "signal.logs_notification.initialize_account".to_string();
}
}
"signal.logs_notification.generic".to_string()
}
return "signal.logs_notification.generic".to_string();
},
"signatureNotification" => {
let err_option = extract_signature_notification_err(result);
match err_option {
Some(err) => {
if err.is_null() {
"signal.signature_notification.confirmed".to_string()
return "signal.signature_notification.confirmed".to_string();
} else {
"signal.signature_notification.failed".to_string()
}
}
None => "signal.signature_notification.generic".to_string(),
return "signal.signature_notification.failed".to_string();
}
},
None => return "signal.signature_notification.generic".to_string(),
}
},
"programNotification" => {
let owner_option = extract_program_notification_owner(result);
let owner = match owner_option {
@@ -666,12 +656,9 @@ fn build_signal_kind_for_notification(
if owner == crate::SPL_TOKEN_2022_PROGRAM_ID.to_string() {
return "signal.program_notification.spl_token_2022".to_string();
}
"signal.program_notification.generic".to_string()
}
_ => format!(
"signal.{}",
method.replace("Notification", "").to_lowercase()
),
return "signal.program_notification.generic".to_string();
},
_ => return format!("signal.{}", method.replace("Notification", "").to_lowercase()),
}
}
@@ -681,8 +668,8 @@ fn build_signal_severity_for_notification(
result: &serde_json::Value,
) -> crate::KbAnalysisSignalSeverity {
match method {
"programNotification" => crate::KbAnalysisSignalSeverity::Medium,
"accountNotification" => crate::KbAnalysisSignalSeverity::Low,
"programNotification" => return crate::KbAnalysisSignalSeverity::Medium,
"accountNotification" => return crate::KbAnalysisSignalSeverity::Low,
"logsNotification" => {
let lines = extract_logs_lines(result);
for line in &lines {
@@ -690,22 +677,22 @@ fn build_signal_severity_for_notification(
return crate::KbAnalysisSignalSeverity::Medium;
}
}
crate::KbAnalysisSignalSeverity::Low
}
return crate::KbAnalysisSignalSeverity::Low;
},
"signatureNotification" => {
let err_option = extract_signature_notification_err(result);
match err_option {
Some(err) => {
if err.is_null() {
crate::KbAnalysisSignalSeverity::Low
return crate::KbAnalysisSignalSeverity::Low;
} else {
crate::KbAnalysisSignalSeverity::Medium
return crate::KbAnalysisSignalSeverity::Medium;
}
},
None => return crate::KbAnalysisSignalSeverity::Low,
}
None => crate::KbAnalysisSignalSeverity::Low,
}
}
_ => crate::KbAnalysisSignalSeverity::Low,
},
_ => return crate::KbAnalysisSignalSeverity::Low,
}
}
@@ -726,13 +713,13 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
fn build_slot_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "slotNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -743,11 +730,11 @@ mod tests {
}),
subscription: 1008_u64,
},
}
};
}
fn build_program_mint_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "programNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -773,11 +760,11 @@ mod tests {
}),
subscription: 2048_u64,
},
}
};
}
fn build_program_pool_candidate_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "programNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -798,11 +785,11 @@ mod tests {
}),
subscription: 5555_u64,
},
}
};
}
fn build_logs_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "logsNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -820,11 +807,11 @@ mod tests {
}),
subscription: 3001_u64,
},
}
};
}
fn build_signature_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "signatureNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -838,7 +825,7 @@ mod tests {
}),
subscription: 4001_u64,
},
}
};
}
#[tokio::test]
@@ -859,7 +846,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -894,7 +881,7 @@ mod tests {
assert!(result.token_id > 0);
assert!(result.observation_id > 0);
assert!(result.signal_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let token_result = crate::get_token_by_mint(
@@ -923,14 +910,8 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
observations[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(observations[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].object_key, "Mint111111111111111111111111111111111111111");
}
#[tokio::test]
@@ -951,7 +932,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -970,10 +951,7 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
signals[0].signal_kind,
"signal.logs_notification.initialize_mint"
);
assert_eq!(signals[0].signal_kind, "signal.logs_notification.initialize_mint");
}
#[tokio::test]
@@ -994,7 +972,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -1013,10 +991,7 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
signals[0].signal_kind,
"signal.signature_notification.confirmed"
);
assert_eq!(signals[0].signal_kind, "signal.signature_notification.confirmed");
}
#[tokio::test]
@@ -1053,7 +1028,7 @@ mod tests {
assert!(result.pool_id > 0);
assert!(result.pool_listing_id > 0);
result.pool_id
}
},
_ => panic!("unexpected detection outcome"),
};
let pool_result = crate::get_pool_by_address(

View File

@@ -29,14 +29,14 @@ impl KbDetectionObservationInput {
slot: std::option::Option<u64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
observation_kind,
source_kind,
endpoint_name,
object_key,
slot,
payload,
}
};
}
}
@@ -67,14 +67,14 @@ impl KbDetectionSignalInput {
score: std::option::Option<f64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
signal_kind,
severity,
object_key,
related_observation_id,
score,
payload,
}
};
}
}
@@ -133,7 +133,7 @@ impl KbDetectionTokenCandidateInput {
signal_score: std::option::Option<f64>,
signal_payload: std::option::Option<serde_json::Value>,
) -> Self {
Self {
return Self {
mint,
symbol,
name,
@@ -149,7 +149,7 @@ impl KbDetectionTokenCandidateInput {
signal_severity,
signal_score,
signal_payload,
}
};
}
}
@@ -207,7 +207,7 @@ impl KbDetectionPoolCandidateInput {
signal_score: std::option::Option<f64>,
signal_payload: std::option::Option<serde_json::Value>,
) -> Self {
Self {
return Self {
pool_address,
dex_program_id,
source_kind,
@@ -219,7 +219,7 @@ impl KbDetectionPoolCandidateInput {
signal_severity,
signal_score,
signal_payload,
}
};
}
}

View File

@@ -20,10 +20,7 @@ impl KbWsDetectionNotificationEnvelope {
endpoint_name: std::option::Option<std::string::String>,
notification: crate::KbJsonRpcWsNotification,
) -> Self {
Self {
endpoint_name,
notification,
}
return Self { endpoint_name, notification };
}
}
@@ -54,7 +51,7 @@ pub struct KbWsDetectionRelay {
impl KbWsDetectionRelay {
/// Creates a new relay.
pub fn new(detector: crate::KbSolanaWsDetectionService) -> Self {
Self { detector }
return Self { detector };
}
/// Creates a bounded relay channel.
@@ -64,7 +61,7 @@ impl KbWsDetectionRelay {
tokio::sync::mpsc::Sender<crate::KbWsDetectionNotificationEnvelope>,
tokio::sync::mpsc::Receiver<crate::KbWsDetectionNotificationEnvelope>,
) {
tokio::sync::mpsc::channel(capacity)
return tokio::sync::mpsc::channel(capacity);
}
/// Processes one forwarded notification.
@@ -72,9 +69,10 @@ impl KbWsDetectionRelay {
&self,
envelope: &crate::KbWsDetectionNotificationEnvelope,
) -> Result<crate::KbSolanaWsDetectionOutcome, crate::KbError> {
self.detector
return self
.detector
.process_notification(envelope.endpoint_name.clone(), &envelope.notification)
.await
.await;
}
/// Spawns one background relay worker.
@@ -82,7 +80,7 @@ impl KbWsDetectionRelay {
self,
mut receiver: tokio::sync::mpsc::Receiver<crate::KbWsDetectionNotificationEnvelope>,
) -> tokio::task::JoinHandle<crate::KbWsDetectionRelayStats> {
tokio::spawn(async move {
return tokio::spawn(async move {
let mut stats = crate::KbWsDetectionRelayStats::default();
loop {
let recv_result = receiver.recv().await;
@@ -103,25 +101,25 @@ impl KbWsDetectionRelay {
error
);
continue;
}
},
};
match outcome {
crate::KbSolanaWsDetectionOutcome::Ignored => {
stats.ignored_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { .. } => {
stats.observation_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::TokenCandidateRegistered { .. } => {
stats.token_candidate_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { .. } => {
stats.pool_candidate_count += 1;
},
}
}
}
stats
})
return stats;
});
}
}
@@ -142,13 +140,13 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
fn build_slot_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "slotNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -159,7 +157,7 @@ mod tests {
}),
subscription: 1008_u64,
},
}
};
}
#[tokio::test]
@@ -180,7 +178,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected relay outcome"),
}
}

View File

@@ -76,7 +76,7 @@ enum KbDexlabInstructionKind {
impl KbDexlabDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more DexLab events.
@@ -93,7 +93,7 @@ impl KbDexlabDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -104,7 +104,7 @@ impl KbDexlabDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -140,45 +140,24 @@ impl KbDexlabDecoder {
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"pool",
"poolAddress",
"poolAccount",
"amm",
"ammPool",
"poolState",
],
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenA",
"tokenAMint",
"mintA",
"baseMint",
"token0Mint",
"mint0",
],
&["tokenA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenB",
"tokenBMint",
"mintB",
"quoteMint",
"token1Mint",
"mint1",
],
&["tokenB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["payer", "creator", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let fee_tier = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["feeTier", "fee_tier", "tradeFeeTier", "feeRate"],
@@ -246,7 +225,7 @@ impl KbDexlabDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -278,7 +257,7 @@ fn kb_classify_instruction_kind(
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbDexlabInstructionKind::Swap;
}
KbDexlabInstructionKind::Unknown
return KbDexlabInstructionKind::Unknown;
}
fn kb_extract_log_messages(
@@ -305,7 +284,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -316,7 +295,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -326,7 +305,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -340,7 +319,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -349,7 +328,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -361,11 +340,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
)));
},
}
}
@@ -377,7 +358,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -412,7 +393,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_extract_account(
@@ -422,7 +403,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -432,7 +413,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -462,7 +443,7 @@ mod tests {
.to_string(),
);
dto.id = Some(801);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -498,7 +479,7 @@ mod tests {
),
);
dto.id = Some(802);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -526,7 +507,7 @@ mod tests {
.to_string(),
);
dto.id = Some(803);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -559,7 +540,7 @@ mod tests {
),
);
dto.id = Some(804);
dto
return dto;
}
#[test]
@@ -584,10 +565,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.fee_tier, Some("0.3%".to_string()));
}
},
crate::KbDexlabDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -612,10 +593,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbDexlabDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -76,7 +76,7 @@ enum KbFluxbeamInstructionKind {
impl KbFluxbeamDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more FluxBeam events.
@@ -93,7 +93,7 @@ impl KbFluxbeamDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -104,7 +104,7 @@ impl KbFluxbeamDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -140,50 +140,29 @@ impl KbFluxbeamDecoder {
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"pool",
"poolAddress",
"poolAccount",
"amm",
"ammPool",
"poolState",
],
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let lp_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["lpMint", "lp_mint", "lpTokenMint"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenA",
"tokenAMint",
"mintA",
"baseMint",
"token0Mint",
"mint0",
],
&["tokenA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenB",
"tokenBMint",
"mintB",
"quoteMint",
"token1Mint",
"mint1",
],
&["tokenB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["payer", "creator", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbFluxbeamInstructionKind::CreatePool {
let payload_json = serde_json::json!({
"decoder": "fluxbeam",
@@ -247,7 +226,7 @@ impl KbFluxbeamDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -278,7 +257,7 @@ fn kb_classify_instruction_kind(
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbFluxbeamInstructionKind::Swap;
}
KbFluxbeamInstructionKind::Unknown
return KbFluxbeamInstructionKind::Unknown;
}
fn kb_extract_log_messages(
@@ -305,7 +284,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -316,7 +295,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -326,7 +305,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -340,7 +319,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -349,7 +328,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -361,11 +340,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
)));
},
}
}
@@ -377,7 +358,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -412,7 +393,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_extract_account(
@@ -422,7 +403,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -432,7 +413,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -462,7 +443,7 @@ mod tests {
.to_string(),
);
dto.id = Some(701);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -499,7 +480,7 @@ mod tests {
),
);
dto.id = Some(702);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -527,7 +508,7 @@ mod tests {
.to_string(),
);
dto.id = Some(703);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -561,7 +542,7 @@ mod tests {
),
);
dto.id = Some(704);
dto
return dto;
}
#[test]
@@ -586,10 +567,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbFluxbeamDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -614,10 +595,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbFluxbeamDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -79,7 +79,7 @@ enum KbMeteoraDammV1InstructionKind {
impl KbMeteoraDammV1Decoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Meteora DAMM v1 events.
@@ -96,7 +96,7 @@ impl KbMeteoraDammV1Decoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -107,7 +107,7 @@ impl KbMeteoraDammV1Decoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -143,50 +143,29 @@ impl KbMeteoraDammV1Decoder {
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"pool",
"poolAddress",
"poolAccount",
"amm",
"ammPool",
"poolState",
],
&["pool", "poolAddress", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenAMint",
"mintA",
"baseMint",
"token0Mint",
"mint0",
"coinMint",
],
&["tokenAMint", "mintA", "baseMint", "token0Mint", "mint0", "coinMint"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenBMint",
"mintB",
"quoteMint",
"token1Mint",
"mint1",
"pcMint",
],
&["tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1", "pcMint"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["config", "poolConfig", "ammConfig", "tradeFeeConfig"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "payer", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDammV1InstructionKind::CreatePool
|| instruction_kind == KbMeteoraDammV1InstructionKind::CreatePoolWithConfig
{
@@ -257,7 +236,7 @@ impl KbMeteoraDammV1Decoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -297,7 +276,7 @@ fn kb_classify_instruction_kind(
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbMeteoraDammV1InstructionKind::Swap;
}
KbMeteoraDammV1InstructionKind::Unknown
return KbMeteoraDammV1InstructionKind::Unknown;
}
fn kb_extract_log_messages(
@@ -325,7 +304,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -336,7 +315,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -346,7 +325,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -360,7 +339,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -369,7 +348,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -381,11 +360,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
)));
},
}
}
@@ -397,7 +378,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -432,7 +413,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_value_contains_any_key(
@@ -443,7 +424,7 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
kb_value_contains_any_key_inner(value, candidate_keys)
return kb_value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
@@ -467,7 +448,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
}
false
return false;
}
fn kb_extract_account(
@@ -477,7 +458,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -487,7 +468,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -517,7 +498,7 @@ mod tests {
.to_string(),
);
dto.id = Some(501);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -554,7 +535,7 @@ mod tests {
),
);
dto.id = Some(502);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -582,7 +563,7 @@ mod tests {
.to_string(),
);
dto.id = Some(503);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -615,7 +596,7 @@ mod tests {
),
);
dto.id = Some(504);
dto
return dto;
}
#[test]
@@ -640,10 +621,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert!(event.used_config);
}
},
crate::KbMeteoraDammV1DecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -668,10 +649,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbMeteoraDammV1DecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -82,7 +82,7 @@ enum KbMeteoraDammV2InstructionKind {
impl KbMeteoraDammV2Decoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Meteora DAMM v2 events.
@@ -99,7 +99,7 @@ impl KbMeteoraDammV2Decoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -110,7 +110,7 @@ impl KbMeteoraDammV2Decoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -148,27 +148,27 @@ impl KbMeteoraDammV2Decoder {
parsed_json.as_ref(),
&["pool", "poolAddress", "poolAccount", "poolState", "cpAmm"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["staticConfig", "dynamicConfig", "config", "poolConfig"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "payer", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolStatic
|| instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolDynamic
|| instruction_kind == KbMeteoraDammV2InstructionKind::CreatePoolCustomizable
@@ -178,7 +178,7 @@ impl KbMeteoraDammV2Decoder {
KbMeteoraDammV2InstructionKind::CreatePoolDynamic => "dynamic".to_string(),
KbMeteoraDammV2InstructionKind::CreatePoolCustomizable => {
"customizable".to_string()
}
},
_ => "unknown".to_string(),
};
let payload_json = serde_json::json!({
@@ -249,7 +249,7 @@ impl KbMeteoraDammV2Decoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -278,7 +278,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_classify_instruction_kind(
@@ -333,7 +333,7 @@ fn kb_classify_instruction_kind(
{
return KbMeteoraDammV2InstructionKind::Swap;
}
KbMeteoraDammV2InstructionKind::Unknown
return KbMeteoraDammV2InstructionKind::Unknown;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -344,7 +344,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -354,7 +354,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -368,7 +368,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -377,7 +377,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -389,11 +389,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
)));
},
}
}
@@ -405,7 +407,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -440,7 +442,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_value_contains_any_key(
@@ -451,7 +453,7 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
kb_value_contains_any_key_inner(value, candidate_keys)
return kb_value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
@@ -475,7 +477,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
}
false
return false;
}
fn kb_extract_account(
@@ -485,7 +487,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -495,7 +497,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -525,7 +527,7 @@ mod tests {
.to_string(),
);
dto.id = Some(401);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -562,7 +564,7 @@ mod tests {
),
);
dto.id = Some(402);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -590,7 +592,7 @@ mod tests {
.to_string(),
);
dto.id = Some(403);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -623,7 +625,7 @@ mod tests {
),
);
dto.id = Some(404);
dto
return dto;
}
#[test]
@@ -648,10 +650,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.create_kind, "customizable".to_string());
}
},
crate::KbMeteoraDammV2DecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -677,10 +679,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert!(event.used_swap2);
}
},
crate::KbMeteoraDammV2DecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -76,7 +76,7 @@ pub struct KbMeteoraDbcDecoder;
impl KbMeteoraDbcDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Meteora DBC events.
@@ -93,7 +93,7 @@ impl KbMeteoraDbcDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -104,7 +104,7 @@ impl KbMeteoraDbcDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -142,27 +142,27 @@ impl KbMeteoraDbcDecoder {
parsed_json.as_ref(),
&["pool", "poolAccount", "poolState", "virtualPool", "poolKey"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["baseMint", "tokenAMint", "mintA", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["quoteMint", "tokenBMint", "mintB", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["poolConfig", "config", "dbcConfig", "curveConfig"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["creator", "poolCreator", "owner", "user"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbMeteoraDbcInstructionKind::CreatePool {
let payload_json = serde_json::json!({
"decoder": "meteora_dbc",
@@ -228,7 +228,7 @@ impl KbMeteoraDbcDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -257,7 +257,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_any_keyword(
@@ -269,7 +269,7 @@ fn kb_log_messages_contain_any_keyword(
return true;
}
}
false
return false;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -280,7 +280,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_normalize_log_text(value: &str) -> std::string::String {
@@ -290,7 +290,7 @@ fn kb_normalize_log_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -304,7 +304,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -313,7 +313,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -325,11 +325,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
)));
},
}
}
@@ -341,7 +343,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -376,7 +378,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_value_contains_any_key(
@@ -387,7 +389,7 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
kb_value_contains_any_key_inner(value, candidate_keys)
return kb_value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
@@ -411,7 +413,7 @@ fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[
}
}
}
false
return false;
}
fn kb_extract_account(
@@ -421,7 +423,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
@@ -431,7 +433,7 @@ fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTra
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
fn kb_classify_instruction_kind(
@@ -456,32 +458,21 @@ fn kb_classify_instruction_kind(
}
let has_create_config = kb_value_contains_any_key(
parsed_json,
&[
"poolConfig",
"migrationQuoteThreshold",
"curveConfig",
"dbcConfig",
],
&["poolConfig", "migrationQuoteThreshold", "curveConfig", "dbcConfig"],
);
if has_create_config {
return KbMeteoraDbcInstructionKind::CreatePool;
}
if kb_log_messages_contain_any_keyword(
log_messages,
&[
"create_pool",
"createpool",
"initialize_pool",
"initializepool",
"launch_pool",
],
&["create_pool", "createpool", "initialize_pool", "initializepool", "launch_pool"],
) {
return KbMeteoraDbcInstructionKind::CreatePool;
}
if kb_log_messages_contain_any_keyword(log_messages, &["swap2", "swap"]) {
return KbMeteoraDbcInstructionKind::Swap;
}
KbMeteoraDbcInstructionKind::Unknown
return KbMeteoraDbcInstructionKind::Unknown;
}
#[cfg(test)]
@@ -511,7 +502,7 @@ mod tests {
.to_string(),
);
dto.id = Some(301);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -547,7 +538,7 @@ mod tests {
),
);
dto.id = Some(302);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -575,7 +566,7 @@ mod tests {
.to_string(),
);
dto.id = Some(303);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -607,7 +598,7 @@ mod tests {
),
);
dto.id = Some(304);
dto
return dto;
}
#[test]
@@ -632,10 +623,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert_eq!(event.config_account, Some("DbcConfig111".to_string()));
}
},
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -660,10 +651,10 @@ mod tests {
event.token_b_mint,
Some("So11111111111111111111111111111111111111112".to_string())
);
}
},
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
@@ -687,10 +678,10 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {}
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {},
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
@@ -706,10 +697,10 @@ mod tests {
};
assert_eq!(decoded.len(), 1);
match &decoded[0] {
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {}
crate::KbMeteoraDbcDecodedEvent::CreatePool(_) => {},
crate::KbMeteoraDbcDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
}

View File

@@ -3,8 +3,7 @@
//! Orca Whirlpools transaction decoder.
/// Orca Whirlpools program id.
pub const KB_ORCA_WHIRLPOOLS_PROGRAM_ID: &str =
"whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
pub const KB_ORCA_WHIRLPOOLS_PROGRAM_ID: &str = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
/// Decoded Orca Whirlpools create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -83,7 +82,7 @@ enum KbOrcaWhirlpoolsInstructionKind {
impl KbOrcaWhirlpoolsDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Orca Whirlpools events.
@@ -100,7 +99,7 @@ impl KbOrcaWhirlpoolsDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -111,7 +110,7 @@ impl KbOrcaWhirlpoolsDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -137,7 +136,8 @@ impl KbOrcaWhirlpoolsDecoder {
Ok(accounts) => accounts,
Err(error) => return Err(error),
};
let parsed_json_result = kb_parse_optional_parsed_json(instruction.parsed_json.as_ref());
let parsed_json_result =
kb_parse_optional_parsed_json(instruction.parsed_json.as_ref());
let parsed_json = match parsed_json_result {
Ok(parsed_json) => parsed_json,
Err(error) => return Err(error),
@@ -146,59 +146,33 @@ impl KbOrcaWhirlpoolsDecoder {
kb_classify_instruction_kind(parsed_json.as_ref(), &log_messages);
let pool_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"whirlpool",
"pool",
"poolAddress",
"poolAccount",
"whirlpoolAddress",
],
&["whirlpool", "pool", "poolAddress", "poolAccount", "whirlpoolAddress"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenMintA",
"tokenAMint",
"mintA",
"baseMint",
"token0Mint",
"mint0",
],
&["tokenMintA", "tokenAMint", "mintA", "baseMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 1));
.or_else(|| return kb_extract_account(&accounts, 1));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenMintB",
"tokenBMint",
"mintB",
"quoteMint",
"token1Mint",
"mint1",
],
&["tokenMintB", "tokenBMint", "mintB", "quoteMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 2));
.or_else(|| return kb_extract_account(&accounts, 2));
let config_account = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"whirlpoolsConfig",
"config",
"configAccount",
"whirlpoolConfig",
],
&["whirlpoolsConfig", "config", "configAccount", "whirlpoolConfig"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let creator = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&["funder", "creator", "payer", "user", "owner"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
if instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePool
|| instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePoolV2
{
let used_v2 =
instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
let used_v2 = instruction_kind == KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
let payload_json = serde_json::json!({
"decoder": "orca_whirlpools",
"eventKind": "create_pool",
@@ -268,7 +242,7 @@ impl KbOrcaWhirlpoolsDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -295,17 +269,13 @@ fn kb_classify_instruction_kind(
return KbOrcaWhirlpoolsInstructionKind::Swap;
}
}
if kb_value_contains_any_key(
parsed_json,
&["tokenProgramA", "tokenProgramB", "memoProgram"],
) && kb_log_messages_contain_keyword(log_messages, "initialize_pool")
if kb_value_contains_any_key(parsed_json, &["tokenProgramA", "tokenProgramB", "memoProgram"])
&& kb_log_messages_contain_keyword(log_messages, "initialize_pool")
{
return KbOrcaWhirlpoolsInstructionKind::InitializePoolV2;
}
if kb_value_contains_any_key(
parsed_json,
&["tokenProgramA", "tokenProgramB", "memoProgram"],
) && kb_log_messages_contain_keyword(log_messages, "swap")
if kb_value_contains_any_key(parsed_json, &["tokenProgramA", "tokenProgramB", "memoProgram"])
&& kb_log_messages_contain_keyword(log_messages, "swap")
{
return KbOrcaWhirlpoolsInstructionKind::SwapV2;
}
@@ -327,7 +297,7 @@ fn kb_classify_instruction_kind(
if kb_log_messages_contain_keyword(log_messages, "swap") {
return KbOrcaWhirlpoolsInstructionKind::Swap;
}
KbOrcaWhirlpoolsInstructionKind::Unknown
return KbOrcaWhirlpoolsInstructionKind::Unknown;
}
fn kb_extract_log_messages(
@@ -355,13 +325,10 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(
log_messages: &[std::string::String],
keyword: &str,
) -> bool {
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
let keyword_normalized = kb_normalize_text(keyword);
for log_message in log_messages {
let log_normalized = kb_normalize_text(log_message.as_str());
@@ -369,7 +336,7 @@ fn kb_log_messages_contain_keyword(
return true;
}
}
false
return false;
}
fn kb_normalize_text(value: &str) -> std::string::String {
@@ -379,7 +346,7 @@ fn kb_normalize_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
fn kb_parse_accounts_json(
@@ -393,7 +360,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -402,7 +369,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_parse_optional_parsed_json(
@@ -414,11 +381,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
)));
},
}
}
@@ -430,7 +399,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -466,7 +435,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
fn kb_value_contains_any_key(
@@ -477,13 +446,10 @@ fn kb_value_contains_any_key(
Some(value) => value,
None => return false,
};
kb_value_contains_any_key_inner(value, candidate_keys)
return kb_value_contains_any_key_inner(value, candidate_keys);
}
fn kb_value_contains_any_key_inner(
value: &serde_json::Value,
candidate_keys: &[&str],
) -> bool {
fn kb_value_contains_any_key_inner(value: &serde_json::Value, candidate_keys: &[&str]) -> bool {
if let Some(object) = value.as_object() {
for candidate_key in candidate_keys {
if object.contains_key(*candidate_key) {
@@ -504,7 +470,7 @@ fn kb_value_contains_any_key_inner(
}
}
}
false
return false;
}
fn kb_extract_account(
@@ -514,19 +480,17 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_infer_trade_side(
log_messages: &[std::string::String],
) -> crate::KbSwapTradeSide {
fn kb_infer_trade_side(log_messages: &[std::string::String]) -> crate::KbSwapTradeSide {
if kb_log_messages_contain_keyword(log_messages, "buy") {
return crate::KbSwapTradeSide::BuyBase;
}
if kb_log_messages_contain_keyword(log_messages, "sell") {
return crate::KbSwapTradeSide::SellBase;
}
crate::KbSwapTradeSide::Unknown
return crate::KbSwapTradeSide::Unknown;
}
#[cfg(test)]
@@ -556,7 +520,7 @@ mod tests {
.to_string(),
);
dto.id = Some(601);
dto
return dto;
}
fn make_create_instruction() -> crate::KbChainInstructionDto {
@@ -595,7 +559,7 @@ mod tests {
),
);
dto.id = Some(602);
dto
return dto;
}
fn make_swap_transaction() -> crate::KbChainTransactionDto {
@@ -623,7 +587,7 @@ mod tests {
.to_string(),
);
dto.id = Some(603);
dto
return dto;
}
fn make_swap_instruction() -> crate::KbChainInstructionDto {
@@ -656,7 +620,7 @@ mod tests {
),
);
dto.id = Some(604);
dto
return dto;
}
#[test]
@@ -682,10 +646,10 @@ mod tests {
);
assert_eq!(event.config_account, Some("OrcaConfig111".to_string()));
assert!(event.used_v2);
}
},
crate::KbOrcaWhirlpoolsDecodedEvent::Swap(_) => {
panic!("unexpected swap event")
}
},
}
}
@@ -711,10 +675,10 @@ mod tests {
Some("So11111111111111111111111111111111111111112".to_string())
);
assert!(event.used_v2);
}
},
crate::KbOrcaWhirlpoolsDecodedEvent::CreatePool(_) => {
panic!("unexpected create event")
}
},
}
}
}

View File

@@ -80,7 +80,7 @@ pub struct KbPumpFunDecoder;
impl KbPumpFunDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more Pump.fun events.
@@ -97,7 +97,7 @@ impl KbPumpFunDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
if transaction.err_json.is_some() {
return Ok(std::vec::Vec::new());
@@ -111,7 +111,7 @@ impl KbPumpFunDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let has_create_v2_log = kb_log_messages_contain_keyword(&log_messages, "create_v2")
@@ -250,7 +250,7 @@ impl KbPumpFunDecoder {
},
));
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -271,8 +271,8 @@ fn kb_decode_optional_instruction_data(
};
let decode_result = bs58::decode(encoded.as_str()).into_vec();
match decode_result {
Ok(decoded) => Some(decoded),
Err(_) => None,
Ok(decoded) => return Some(decoded),
Err(_) => return None,
}
}
@@ -287,7 +287,7 @@ fn kb_instruction_data_starts_with(
if instruction_data.len() < discriminator.len() {
return false;
}
&instruction_data[0..discriminator.len()] == discriminator
return &instruction_data[0..discriminator.len()] == discriminator;
}
fn kb_extract_u64_argument(
@@ -304,7 +304,7 @@ fn kb_extract_u64_argument(
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&instruction_data[offset..end]);
Some(u64::from_le_bytes(bytes).to_string())
return Some(u64::from_le_bytes(bytes).to_string());
}
fn kb_extract_log_messages(
@@ -332,7 +332,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -343,7 +343,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_parse_accounts_json(
@@ -357,7 +357,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -366,7 +366,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_extract_account(
@@ -376,7 +376,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_normalize_log_text(value: &str) -> std::string::String {
@@ -386,7 +386,7 @@ fn kb_normalize_log_text(value: &str) -> std::string::String {
normalized.push(character.to_ascii_lowercase());
}
}
normalized
return normalized;
}
#[cfg(test)]
@@ -416,7 +416,7 @@ mod tests {
.to_string(),
);
dto.id = Some(91);
dto
return dto;
}
fn make_instruction() -> crate::KbChainInstructionDto {
@@ -445,7 +445,7 @@ mod tests {
None,
);
dto.id = Some(17);
dto
return dto;
}
#[test]
@@ -470,13 +470,13 @@ mod tests {
Some("AssociatedBondingCurve111".to_string())
);
assert_eq!(event.creator, Some("Creator111".to_string()));
}
},
crate::KbPumpFunDecodedEvent::BuyTrade(_) => {
panic!("unexpected pump_fun buy trade event");
}
},
crate::KbPumpFunDecodedEvent::SellTrade(_) => {
panic!("unexpected pump_fun sell trade event");
}
},
}
}

View File

@@ -46,7 +46,7 @@ pub struct KbPumpSwapDecoder;
impl KbPumpSwapDecoder {
/// Creates a new decoder.
pub fn new() -> Self {
Self
return Self;
}
/// Decodes one projected transaction into zero or more PumpSwap events.
@@ -63,7 +63,7 @@ impl KbPumpSwapDecoder {
"chain transaction '{}' has no internal id",
transaction.signature
)));
}
},
};
let transaction_json_result =
serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
@@ -74,7 +74,7 @@ impl KbPumpSwapDecoder {
"cannot parse transaction_json for signature '{}': {}",
transaction.signature, error
)));
}
},
};
let log_messages = kb_extract_log_messages(&transaction_json);
let mut decoded_events = std::vec::Vec::new();
@@ -110,40 +110,20 @@ impl KbPumpSwapDecoder {
parsed_json.as_ref(),
&["pool", "poolAccount", "amm", "ammPool", "poolState"],
)
.or_else(|| kb_extract_account(&accounts, 0));
.or_else(|| return kb_extract_account(&accounts, 0));
let token_a_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenAMint",
"baseMint",
"mintA",
"coinMint",
"token0Mint",
"mint0",
],
&["tokenAMint", "baseMint", "mintA", "coinMint", "token0Mint", "mint0"],
)
.or_else(|| kb_extract_account(&accounts, 3));
.or_else(|| return kb_extract_account(&accounts, 3));
let token_b_mint = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"tokenBMint",
"quoteMint",
"mintB",
"pcMint",
"token1Mint",
"mint1",
],
&["tokenBMint", "quoteMint", "mintB", "pcMint", "token1Mint", "mint1"],
)
.or_else(|| kb_extract_account(&accounts, 4));
.or_else(|| return kb_extract_account(&accounts, 4));
let pool_v2 = kb_extract_string_by_candidate_keys(
parsed_json.as_ref(),
&[
"poolV2",
"pool_v2",
"ammV2",
"bondingCurveV2",
"bonding_curve_v2",
],
&["poolV2", "pool_v2", "ammV2", "bondingCurveV2", "bonding_curve_v2"],
);
let pool_base_token_account = kb_extract_account(&accounts, 7);
let pool_quote_token_account = kb_extract_account(&accounts, 8);
@@ -200,7 +180,7 @@ impl KbPumpSwapDecoder {
));
}
}
Ok(decoded_events)
return Ok(decoded_events);
}
}
@@ -230,7 +210,7 @@ fn kb_extract_log_messages(
messages.push(text.to_string());
}
}
messages
return messages;
}
fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword: &str) -> bool {
@@ -241,7 +221,7 @@ fn kb_log_messages_contain_keyword(log_messages: &[std::string::String], keyword
return true;
}
}
false
return false;
}
fn kb_parse_accounts_json(
@@ -255,7 +235,7 @@ fn kb_parse_accounts_json(
"cannot parse instruction accounts_json '{}': {}",
accounts_json, error
)));
}
},
};
let mut accounts = std::vec::Vec::new();
for value in values {
@@ -264,7 +244,7 @@ fn kb_parse_accounts_json(
accounts.push(text.to_string());
}
}
Ok(accounts)
return Ok(accounts);
}
fn kb_extract_account(
@@ -274,7 +254,7 @@ fn kb_extract_account(
if index >= accounts.len() {
return None;
}
Some(accounts[index].clone())
return Some(accounts[index].clone());
}
fn kb_parse_optional_parsed_json(
@@ -286,11 +266,13 @@ fn kb_parse_optional_parsed_json(
};
let value_result = serde_json::from_str::<serde_json::Value>(parsed_json.as_str());
match value_result {
Ok(value) => Ok(Some(value)),
Err(error) => Err(crate::KbError::Json(format!(
Ok(value) => return Ok(Some(value)),
Err(error) => {
return Err(crate::KbError::Json(format!(
"cannot parse instruction parsed_json '{}': {}",
parsed_json, error
))),
)));
},
}
}
@@ -302,7 +284,7 @@ fn kb_extract_string_by_candidate_keys(
Some(value) => value,
None => return None,
};
kb_extract_string_by_candidate_keys_inner(value, candidate_keys)
return kb_extract_string_by_candidate_keys_inner(value, candidate_keys);
}
fn kb_extract_string_by_candidate_keys_inner(
@@ -337,7 +319,7 @@ fn kb_extract_string_by_candidate_keys_inner(
}
}
}
None
return None;
}
#[cfg(test)]
@@ -367,7 +349,7 @@ mod tests {
.to_string(),
);
dto.id = Some(92);
dto
return dto;
}
fn make_instruction() -> crate::KbChainInstructionDto {
@@ -389,7 +371,8 @@ mod tests {
"UserQuoteAta111",
"PoolBaseVault111",
"PoolQuoteVault111"
]).to_string(),
])
.to_string(),
None,
None,
Some(
@@ -405,7 +388,7 @@ mod tests {
),
);
dto.id = Some(18);
dto
return dto;
}
#[test]
@@ -436,10 +419,10 @@ mod tests {
Some(&serde_json::Value::String("PoolQuoteVault111".to_string()))
);
assert_eq!(event.trade_side, crate::KbSwapTradeSide::BuyBase);
}
},
crate::KbPumpSwapDecodedEvent::SellTrade(_) => {
panic!("unexpected sell event")
}
},
}
}

Some files were not shown because too many files have changed in this diff Show More