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.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.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.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] [workspace.package]
version = "0.7.24" version = "0.7.25"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot" 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. opt-level = 3 # s Prioritizes small binary size. Use `3` if you prefer speed.
panic = "abort" # Higher performance by disabling panic handlers. panic = "abort" # Higher performance by disabling panic handlers.
strip = true # Ensures debug symbols are removed. 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`. - intégration du rendu graphique directement dans `Demo Pipeline`.
### 6.057. Version `0.7.25` — Enrichissement metadata des tokens ### 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 : - Ajout :
- Relecture locale du pipeline à partir des transactions brutes persistantes de la chaîne.
- ajouter une couche `token_metadata` dans `kb_lib`, distincte du décodage DEX et de lUI, - Actualisation optionnelle des métadonnées de jetons manquantes lors de la relecture locale.
- enrichir `kb_tokens` avec `symbol`, `name`, `decimals`, `metadata_uri`, `metadata_source`, `metadata_status` et `metadata_updated_at` lorsque le schéma le permet, - Reconstruction des symboles de paires à partir des métadonnées des jetons.
- ajouter une résolution locale des mints connus (`SOL`, `USDC`, `USDT`, `RAY`, `JitoSOL` et autres références stables), - Commandes d'interface utilisateur dans le pipeline de démonstration 2 pour la relecture locale.
- 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`, - Flux de travail d'actualisation du catalogue de jetons/paires piloté par les métadonnées.
- ajouter une résolution Metaplex Token Metadata PDA pour les tokens SPL classiques, - Modifications :
- ajouter une résolution Token-2022 via metadata pointer / extensions lorsque le mint utilise ce standard, - Les symboles de paires sont désormais dérivés comme `BASE/QUOTE` lorsque les deux symboles de jetons sont disponibles.
- stocker explicitement les cas non résolus afin déviter les tentatives répétées inutiles, - 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.
- exposer une commande UI ou un service de backfill permettant denrichir les tokens déjà présents en base, - Corrections :
- maintenir une politique de priorité claire entre sources : `known_mint`, `pump_fun_create`, `metaplex`, `token_2022`, puis `unresolved`. - 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 ### 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`. 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 : 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`, 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, 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, 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, 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, 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. 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,155 +26,229 @@
<div class="container vcentered sketchy-translucid py-4"> <div class="container vcentered sketchy-translucid py-4">
<div class="row g-4"> <div class="row g-4">
<div class="col-12 col-xxl-4"> <div class="col-12 col-xxl-4">
<div class="card shadow-sm border-0 mb-4"> <div class="accordion" id="demoPipeline2LeftAccordion">
<div class="card-body"> <div class="accordion-item border-0 shadow-sm mb-3">
<h1 class="h4 mb-3">Catalogue local</h1> <h1 class="accordion-header" id="demoPipeline2CatalogHeading">
<div class="d-flex gap-2 mb-3"> <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2CatalogCollapse" aria-expanded="true" aria-controls="demoPipeline2CatalogCollapse">
<button id="demoPipeline2RefreshCatalogButton" type="button" class="btn btn-primary"> Catalogue local
Refresh catalog
</button> </button>
</div> </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
</button>
</div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Mints</label> <label class="form-label">Mints</label>
<textarea id="demoPipeline2TokensTextarea" class="form-control font-monospace" rows="8" readonly spellcheck="false"></textarea> <textarea id="demoPipeline2TokensTextarea" class="form-control font-monospace" rows="8" readonly spellcheck="false"></textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Pools</label> <label class="form-label">Pools</label>
<textarea id="demoPipeline2PoolsTextarea" class="form-control font-monospace" rows="8" readonly spellcheck="false"></textarea> <textarea id="demoPipeline2PoolsTextarea" class="form-control font-monospace" rows="8" readonly spellcheck="false"></textarea>
</div> </div>
<div> <div>
<label class="form-label">Pairs</label> <label class="form-label">Pairs</label>
<textarea id="demoPipeline2PairsTextarea" class="form-control font-monospace" rows="8" readonly spellcheck="false"></textarea> <textarea id="demoPipeline2PairsTextarea" class="form-control font-monospace" rows="8" readonly spellcheck="false"></textarea>
</div>
</div>
</div> </div>
</div> </div>
</div>
<div class="card shadow-sm border-0 mb-4"> <div class="accordion-item border-0 shadow-sm mb-3">
<div class="card-body"> <h2 class="accordion-header" id="demoPipeline2BackfillHeading">
<h2 class="h5 mb-3">Backfill ciblé</h2> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2BackfillCollapse" aria-expanded="false" aria-controls="demoPipeline2BackfillCollapse">
Backfill ciblé
<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" />
</div>
<div class="mb-3">
<label for="demoPipeline2MintInput" class="form-label">Token mint</label>
<input id="demoPipeline2MintInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="Mint SPL" />
</div>
<div class="row g-2 mb-3">
<div class="col-6">
<label for="demoPipeline2MintSignatureLimitInput" class="form-label">Mint sigs</label>
<input id="demoPipeline2MintSignatureLimitInput" type="number" min="1" step="1" class="form-control" value="20" />
</div>
<div class="col-6">
<label for="demoPipeline2MintPoolLimitInput" class="form-label">Pool sigs</label>
<input id="demoPipeline2MintPoolLimitInput" type="number" min="1" step="1" class="form-control" value="20" />
</div>
</div>
<div class="d-flex gap-2 mb-4">
<button id="demoPipeline2BackfillMintButton" type="button" class="btn btn-outline-primary">
Backfill mint
</button> </button>
</div> </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" />
</div>
<div class="mb-3"> <div class="mb-3">
<label for="demoPipeline2PoolInput" class="form-label">Pool address</label> <label for="demoPipeline2MintInput" class="form-label">Token mint</label>
<input id="demoPipeline2PoolInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="Pool / pair on-chain" /> <input id="demoPipeline2MintInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="Mint SPL" />
</div> </div>
<div class="mb-3"> <div class="row g-2 mb-3">
<label for="demoPipeline2PoolSignatureLimitInput" class="form-label">Pool sigs</label> <div class="col-6">
<input id="demoPipeline2PoolSignatureLimitInput" type="number" min="1" step="1" class="form-control" value="20" /> <label for="demoPipeline2MintSignatureLimitInput" class="form-label">Mint sigs</label>
</div> <input id="demoPipeline2MintSignatureLimitInput" type="number" min="1" step="1" class="form-control" value="20" />
</div>
<div class="col-6">
<label for="demoPipeline2MintPoolLimitInput" class="form-label">Pool sigs</label>
<input id="demoPipeline2MintPoolLimitInput" type="number" min="1" step="1" class="form-control" value="20" />
</div>
</div>
<div class="d-flex gap-2"> <div class="d-flex gap-2 mb-4">
<button id="demoPipeline2BackfillPoolButton" type="button" class="btn btn-outline-primary"> <button id="demoPipeline2BackfillMintButton" type="button" class="btn btn-outline-primary">
Backfill pool Backfill mint
</button> </button>
</div>
<div class="mb-3">
<label for="demoPipeline2PoolInput" class="form-label">Pool address</label>
<input id="demoPipeline2PoolInput" type="text" class="form-control font-monospace" spellcheck="false" placeholder="Pool / pair on-chain" />
</div>
<div class="mb-3">
<label for="demoPipeline2PoolSignatureLimitInput" class="form-label">Pool sigs</label>
<input id="demoPipeline2PoolSignatureLimitInput" type="number" min="1" step="1" class="form-control" value="20" />
</div>
<div class="d-flex gap-2">
<button id="demoPipeline2BackfillPoolButton" type="button" class="btn btn-outline-primary">
Backfill pool
</button>
</div>
</div>
</div> </div>
</div> </div>
</div>
<div class="card shadow-sm border-0"> <div class="accordion-item border-0 shadow-sm mb-3">
<div class="card-body"> <h2 class="accordion-header" id="demoPipeline2ReplayHeading">
<h2 class="h5 mb-3">Chargement candles</h2> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2ReplayCollapse" aria-expanded="false" aria-controls="demoPipeline2ReplayCollapse">
Replay local
<div class="mb-3">
<label for="demoPipeline2PairSelect" class="form-label">Pair</label>
<select id="demoPipeline2PairSelect" class="form-select">
<option value="">Aucune</option>
</select>
</div>
<div class="row g-2 mb-3">
<div class="col-7">
<label for="demoPipeline2TimeframeSelect" class="form-label">Timeframe</label>
<select id="demoPipeline2TimeframeSelect" class="form-select">
<option value="60">1m</option>
<option value="300">5m</option>
<option value="900">15m</option>
<option value="3600">1h</option>
</select>
</div>
<div class="col-5">
<label for="demoPipeline2CustomTimeframeInput" class="form-label">Custom</label>
<input id="demoPipeline2CustomTimeframeInput" type="number" min="1" step="1" class="form-control" placeholder="120" />
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="demoPipeline2PreferMaterializedInput" />
<label class="form-check-label" for="demoPipeline2PreferMaterializedInput">
Prefer materialized candles
</label>
</div>
<div class="d-flex gap-2">
<button id="demoPipeline2LoadCandlesButton" type="button" class="btn btn-primary">
Load candles
</button> </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">
<option value="">Aucune</option>
</select>
</div>
<div class="row g-2 mb-3">
<div class="col-7">
<label for="demoPipeline2TimeframeSelect" class="form-label">Timeframe</label>
<select id="demoPipeline2TimeframeSelect" class="form-select">
<option value="60">1m</option>
<option value="300">5m</option>
<option value="900">15m</option>
<option value="3600">1h</option>
</select>
</div>
<div class="col-5">
<label for="demoPipeline2CustomTimeframeInput" class="form-label">Custom</label>
<input id="demoPipeline2CustomTimeframeInput" type="number" min="1" step="1" class="form-control" placeholder="120" />
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="demoPipeline2PreferMaterializedInput" />
<label class="form-check-label" for="demoPipeline2PreferMaterializedInput">
Prefer materialized candles
</label>
</div>
<div class="d-flex gap-2">
<button id="demoPipeline2LoadCandlesButton" type="button" class="btn btn-primary">
Load candles
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-xxl-8"> <div class="col-12 col-xxl-8">
<div class="card shadow-sm border-0 mb-4"> <div class="accordion" id="demoPipeline2ContentAccordion">
<div class="card-body"> <div class="accordion-item border-0 shadow-sm mb-3">
<h2 class="h5 mb-3">Backfill summary</h2> <h2 class="accordion-header" id="demoPipeline2SummaryHeading">
<textarea id="demoPipeline2BackfillSummaryTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2SummaryCollapse" aria-expanded="false" aria-controls="demoPipeline2SummaryCollapse">
</div> Backfill / Replay summary
</div> </button>
</h2>
<div class="card shadow-sm border-0 mb-4"> <div id="demoPipeline2SummaryCollapse" class="accordion-collapse collapse" aria-labelledby="demoPipeline2SummaryHeading" data-bs-parent="#demoPipeline2ContentAccordion">
<div class="card-body"> <div class="accordion-body">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-3"> <textarea id="demoPipeline2BackfillSummaryTextarea" class="form-control font-monospace" rows="12" readonly spellcheck="false"></textarea>
<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> </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="accordion-item border-0 shadow-sm mb-3">
<div class="card-body"> <h2 class="accordion-header" id="demoPipeline2ChartHeading">
<div class="d-flex justify-content-between align-items-center mb-3"> <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#demoPipeline2ChartCollapse" aria-expanded="true" aria-controls="demoPipeline2ChartCollapse">
<h2 class="h5 mb-0">Log UI</h2> Candles / OHLCV
<button id="demoPipeline2ClearLogButton" type="button" class="btn btn-outline-secondary btn-sm">
Clear log
</button> </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 id="demoPipeline2ChartMeta" class="small text-body-secondary">
Aucun jeu de candles chargé.
</div>
</div>
<div id="demoPipeline2Chart" class="w-100 border rounded bg-body" style="height: 560px;"></div>
</div>
</div>
</div>
<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="14" readonly spellcheck="false"></textarea>
</div>
</div> </div>
<textarea id="demoPipeline2LogTextarea" class="form-control font-monospace" rows="12" readonly spellcheck="false"></textarea>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -38,6 +38,24 @@ interface PairCandle {
updated_at: string; 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 { function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
const now = new Date(); const now = new Date();
const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false }); const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false });
@@ -94,6 +112,25 @@ function readPositiveIntegerInput(
return parsed; 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( function refreshPairSelect(
catalog: KbDemoPipeline2CatalogPayload, catalog: KbDemoPipeline2CatalogPayload,
select: HTMLSelectElement, select: HTMLSelectElement,
@@ -308,6 +345,11 @@ document.addEventListener("DOMContentLoaded", async () => {
const poolSignatureLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2PoolSignatureLimitInput"); const poolSignatureLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2PoolSignatureLimitInput");
const backfillPoolButton = document.querySelector<HTMLButtonElement>("#demoPipeline2BackfillPoolButton"); 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 pairSelect = document.querySelector<HTMLSelectElement>("#demoPipeline2PairSelect");
const timeframeSelect = document.querySelector<HTMLSelectElement>("#demoPipeline2TimeframeSelect"); const timeframeSelect = document.querySelector<HTMLSelectElement>("#demoPipeline2TimeframeSelect");
const customTimeframeInput = document.querySelector<HTMLInputElement>("#demoPipeline2CustomTimeframeInput"); const customTimeframeInput = document.querySelector<HTMLInputElement>("#demoPipeline2CustomTimeframeInput");
@@ -334,6 +376,10 @@ document.addEventListener("DOMContentLoaded", async () => {
!poolInput || !poolInput ||
!poolSignatureLimitInput || !poolSignatureLimitInput ||
!backfillPoolButton || !backfillPoolButton ||
!replayLimitInput ||
!replayMetadataCheckbox ||
!replayMetadataLimitInput ||
!replayLocalPipelineButton ||
!pairSelect || !pairSelect ||
!timeframeSelect || !timeframeSelect ||
!customTimeframeInput || !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 () => { loadCandlesButton.addEventListener("click", async () => {
const pairIdText = pairSelect.value.trim(); const pairIdText = pairSelect.value.trim();
if (pairIdText === "") { 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( fn kb_demo_pipeline2_normalize_http_role(
role: std::option::Option<std::string::String>, role: std::option::Option<std::string::String>,
) -> 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_token_mint,
crate::demo_pipeline2::demo_pipeline2_backfill_pool_address, crate::demo_pipeline2::demo_pipeline2_backfill_pool_address,
crate::demo_pipeline2::demo_pipeline2_get_pair_candles, 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.plugin(tracing_builder.build::<tauri::Wry>());
tauri_builder = tauri_builder.setup(|app| { tauri_builder = tauri_builder.setup(|app| {

View File

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

View File

@@ -20,7 +20,7 @@ pub struct KbConfig {
impl KbConfig { impl KbConfig {
/// Returns the default path of the JSON configuration file. /// Returns the default path of the JSON configuration file.
pub fn default_path() -> std::path::PathBuf { 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. /// Loads a configuration from a JSON file and validates it.
@@ -34,7 +34,7 @@ impl KbConfig {
"cannot read configuration file '{}': {error}", "cannot read configuration file '{}': {error}",
path_ref.display() path_ref.display()
))); )));
} },
}; };
let config_result = serde_json::from_str::<Self>(&content); let config_result = serde_json::from_str::<Self>(&content);
let config = match config_result { let config = match config_result {
@@ -44,36 +44,28 @@ impl KbConfig {
"cannot parse configuration file '{}': {error}", "cannot parse configuration file '{}': {error}",
path_ref.display() path_ref.display()
))); )));
} },
}; };
let validation_result = config.validate(); let validation_result = config.validate();
match validation_result { match validation_result {
Ok(()) => Ok(config), Ok(()) => return Ok(config),
Err(error) => Err(error), Err(error) => return Err(error),
} }
} }
/// Validates the current configuration. /// Validates the current configuration.
pub fn validate(&self) -> Result<(), crate::KbError> { pub fn validate(&self) -> Result<(), crate::KbError> {
if self.app.name.trim().is_empty() { if self.app.name.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("app.name must not be empty".to_string()));
"app.name must not be empty".to_string(),
));
} }
if self.app.environment.trim().is_empty() { if self.app.environment.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("app.environment must not be empty".to_string()));
"app.environment must not be empty".to_string(),
));
} }
if self.logging.level.trim().is_empty() { if self.logging.level.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("logging.level must not be empty".to_string()));
"logging.level must not be empty".to_string(),
));
} }
if self.logging.directory.trim().is_empty() { if self.logging.directory.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("logging.directory must not be empty".to_string()));
"logging.directory must not be empty".to_string(),
));
} }
if self.logging.file_prefix.trim().is_empty() { if self.logging.file_prefix.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config(
@@ -81,9 +73,7 @@ impl KbConfig {
)); ));
} }
if self.data.sqlite_path.trim().is_empty() { if self.data.sqlite_path.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("data.sqlite_path must not be empty".to_string()));
"data.sqlite_path must not be empty".to_string(),
));
} }
if self.data.wallets_directory.trim().is_empty() { if self.data.wallets_directory.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config(
@@ -131,7 +121,7 @@ impl KbConfig {
return Err(error); return Err(error);
} }
} }
Ok(()) return Ok(());
} }
/// Creates the basic runtime directories required by the current configuration. /// 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. /// Finds one HTTP endpoint by its logical name.
@@ -173,10 +163,11 @@ impl KbConfig {
&self, &self,
endpoint_name: &str, endpoint_name: &str,
) -> std::option::Option<&KbHttpEndpointConfig> { ) -> std::option::Option<&KbHttpEndpointConfig> {
self.solana return self
.solana
.http_endpoints .http_endpoints
.iter() .iter()
.find(|endpoint| endpoint.name == endpoint_name) .find(|endpoint| return endpoint.name == endpoint_name);
} }
/// Returns a named WebSocket endpoint by reference. /// Returns a named WebSocket endpoint by reference.
@@ -184,10 +175,11 @@ impl KbConfig {
&self, &self,
endpoint_name: &str, endpoint_name: &str,
) -> std::option::Option<&KbWsEndpointConfig> { ) -> std::option::Option<&KbWsEndpointConfig> {
self.solana return self
.solana
.ws_endpoints .ws_endpoints
.iter() .iter()
.find(|endpoint| endpoint.name == endpoint_name) .find(|endpoint| return endpoint.name == endpoint_name);
} }
fn validate_http_endpoint( fn validate_http_endpoint(
@@ -196,11 +188,9 @@ impl KbConfig {
endpoint_names: &mut std::vec::Vec<std::string::String>, endpoint_names: &mut std::vec::Vec<std::string::String>,
) -> Result<(), crate::KbError> { ) -> Result<(), crate::KbError> {
if endpoint.name.trim().is_empty() { if endpoint.name.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("http endpoint name must not be empty".to_string()));
"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!( return Err(crate::KbError::Config(format!(
"duplicated endpoint name '{}'", "duplicated endpoint name '{}'",
endpoint.name endpoint.name
@@ -237,7 +227,7 @@ impl KbConfig {
))); )));
} }
endpoint_names.push(endpoint.name.clone()); endpoint_names.push(endpoint.name.clone());
Ok(()) return Ok(());
} }
fn validate_ws_endpoint( fn validate_ws_endpoint(
@@ -246,11 +236,9 @@ impl KbConfig {
endpoint_names: &mut std::vec::Vec<std::string::String>, endpoint_names: &mut std::vec::Vec<std::string::String>,
) -> Result<(), crate::KbError> { ) -> Result<(), crate::KbError> {
if endpoint.name.trim().is_empty() { if endpoint.name.trim().is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("ws endpoint name must not be empty".to_string()));
"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!( return Err(crate::KbError::Config(format!(
"duplicated endpoint name '{}'", "duplicated endpoint name '{}'",
endpoint.name endpoint.name
@@ -299,7 +287,7 @@ impl KbConfig {
))); )));
} }
endpoint_names.push(endpoint.name.clone()); endpoint_names.push(endpoint.name.clone());
Ok(()) return Ok(());
} }
} }
@@ -350,7 +338,7 @@ pub struct KbLoggingConfig {
impl KbLoggingConfig { impl KbLoggingConfig {
/// Returns the resolved logging directory path. /// Returns the resolved logging directory path.
pub fn directory_path(&self) -> std::path::PathBuf { 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 { impl KbDataConfig {
/// Returns the resolved SQLite database path. /// Returns the resolved SQLite database path.
pub fn sqlite_path_buf(&self) -> std::path::PathBuf { 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. /// Returns the resolved wallets directory path.
pub fn wallets_directory_path(&self) -> std::path::PathBuf { 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, Some(env_var_name) => env_var_name,
None => { None => {
return Ok(self.url.clone()); return Ok(self.url.clone());
} },
}; };
let api_key_result = std::env::var(env_var_name); let api_key_result = std::env::var(env_var_name);
let api_key = match api_key_result { let api_key = match api_key_result {
@@ -443,13 +431,13 @@ impl KbHttpEndpointConfig {
"cannot resolve api key env var '{}' for http endpoint '{}': {}", "cannot resolve api key env var '{}' for http endpoint '{}': {}",
env_var_name, self.name, error env_var_name, self.name, error
))); )));
} },
}; };
let placeholder = format!("${{{}}}", env_var_name); let placeholder = format!("${{{}}}", env_var_name);
if self.url.contains(&placeholder) { if self.url.contains(&placeholder) {
return Ok(self.url.replace(&placeholder, &api_key)); 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 { impl KbWsEndpointConfig {
/// Returns the resolved endpoint URL. /// Returns the resolved endpoint URL.
pub fn resolved_url(&self) -> Result<std::string::String, crate::KbError> { 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 { impl KbSqliteDatabaseConfig {
/// Returns the resolved SQLite database path. /// Returns the resolved SQLite database path.
pub fn path_buf(&self) -> std::path::PathBuf { 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 { fn kb_workspace_root_dir() -> std::path::PathBuf {
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
match manifest_dir.parent() { match manifest_dir.parent() {
Some(parent) => parent.to_path_buf(), Some(parent) => return parent.to_path_buf(),
None => manifest_dir, 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() { if input_path.is_absolute() {
return input_path; return input_path;
} }
kb_workspace_root_dir().join(input_path) return kb_workspace_root_dir().join(input_path);
} }
fn kb_resolve_endpoint_url( fn kb_resolve_endpoint_url(
@@ -553,7 +541,7 @@ fn kb_resolve_endpoint_url(
Some(env_var_name) => env_var_name, Some(env_var_name) => env_var_name,
None => { None => {
return Ok(url.to_string()); return Ok(url.to_string());
} },
}; };
let placeholder = format!("${{{env_var_name}}}"); let placeholder = format!("${{{env_var_name}}}");
if !url.contains(&placeholder) { if !url.contains(&placeholder) {
@@ -567,7 +555,7 @@ fn kb_resolve_endpoint_url(
"environment variable '{}' is required to resolve endpoint url '{}': {error}", "environment variable '{}' is required to resolve endpoint url '{}': {error}",
env_var_name, url 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_dex_decoded_event_by_key;
pub use queries::get_known_http_endpoint; pub use queries::get_known_http_endpoint;
pub use queries::get_known_ws_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_attribution_by_decoded_event_id;
pub use queries::get_launch_surface_by_code; pub use queries::get_launch_surface_by_code;
pub use queries::get_launch_surface_key_by_match; 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_by_address;
pub use queries::get_pool_listing_by_pool_id; pub use queries::get_pool_listing_by_pool_id;
pub use queries::get_pool_origin_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_token_by_mint;
pub use queries::get_trade_event_by_decoded_event_id; pub use queries::get_trade_event_by_decoded_event_id;
pub use queries::get_wallet_by_address; 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_db_runtime_event;
pub use queries::insert_onchain_observation; pub use queries::insert_onchain_observation;
pub use queries::list_chain_instructions_by_transaction_id; 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_db_metadata;
pub use queries::list_dex_decoded_events_by_transaction_id; pub use queries::list_dex_decoded_events_by_transaction_id;
pub use queries::list_dexes; 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_burn_events;
pub use queries::list_recent_token_mint_events; pub use queries::list_recent_token_mint_events;
pub use queries::list_tokens; 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_pair_id;
pub use queries::list_trade_events_by_transaction_id; pub use queries::list_trade_events_by_transaction_id;
pub use queries::list_wallet_holdings_by_wallet_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_pool_id;
pub use queries::list_wallet_participations_by_wallet_id; pub use queries::list_wallet_participations_by_wallet_id;
pub use queries::list_wallets; pub use queries::list_wallets;
pub use queries::update_pair_symbol;
pub use queries::upsert_chain_slot; pub use queries::upsert_chain_slot;
pub use queries::upsert_chain_transaction; pub use queries::upsert_chain_transaction;
pub use queries::upsert_db_metadata; pub use queries::upsert_db_metadata;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -52,8 +52,7 @@ impl KbWalletParticipationDto {
pair_id, pair_id,
role.as_str(), role.as_str(),
); );
return Self {
Self {
id: None, id: None,
wallet_id, wallet_id,
transaction_id, transaction_id,
@@ -66,7 +65,7 @@ impl KbWalletParticipationDto {
source_endpoint_name, source_endpoint_name,
created_at: now, created_at: now,
updated_at: now, updated_at: now,
} };
} }
} }
@@ -87,7 +86,7 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
"cannot parse wallet_participation created_at '{}': {}", "cannot parse wallet_participation created_at '{}': {}",
entity.created_at, error entity.created_at, error
))); )));
} },
}; };
let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at); let updated_at_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at);
let updated_at = match updated_at_result { let updated_at = match updated_at_result {
@@ -97,9 +96,9 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
"cannot parse wallet_participation updated_at '{}': {}", "cannot parse wallet_participation updated_at '{}': {}",
entity.updated_at, error entity.updated_at, error
))); )));
} },
}; };
Ok(Self { return Ok(Self {
id: Some(entity.id), id: Some(entity.id),
wallet_id: entity.wallet_id, wallet_id: entity.wallet_id,
transaction_id: entity.transaction_id, transaction_id: entity.transaction_id,
@@ -112,7 +111,7 @@ impl TryFrom<crate::KbWalletParticipationEntity> for KbWalletParticipationDto {
source_endpoint_name: entity.source_endpoint_name, source_endpoint_name: entity.source_endpoint_name,
created_at, created_at,
updated_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 decoded_event_id_value = decoded_event_id.unwrap_or_default();
let pool_id_value = pool_id.unwrap_or_default(); let pool_id_value = pool_id.unwrap_or_default();
let pair_id_value = pair_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 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::list_recent_chain_slots;
pub use chain_slot::upsert_chain_slot; pub use chain_slot::upsert_chain_slot;
pub use chain_transaction::get_chain_transaction_by_signature; 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::list_recent_chain_transactions;
pub use chain_transaction::upsert_chain_transaction; pub use chain_transaction::upsert_chain_transaction;
pub use db_metadata::get_db_metadata; 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::list_dexes;
pub use dex::upsert_dex; pub use dex::upsert_dex;
pub use dex_decoded_event::get_dex_decoded_event_by_key; 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::list_dex_decoded_events_by_transaction_id;
pub use dex_decoded_event::upsert_dex_decoded_event; pub use dex_decoded_event::upsert_dex_decoded_event;
pub use known_http_endpoint::get_known_http_endpoint; 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 onchain_observation::list_recent_onchain_observations;
pub use pair::get_pair_by_pool_id; pub use pair::get_pair_by_pool_id;
pub use pair::list_pairs; pub use pair::list_pairs;
pub use pair::update_pair_symbol;
pub use pair::upsert_pair; pub use pair::upsert_pair;
pub use pair_analytic_signal::get_pair_analytic_signal_by_key; pub use pair_analytic_signal::get_pair_analytic_signal_by_key;
pub use pair_analytic_signal::list_pair_analytic_signals_by_pair_id; 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 pool_token::upsert_pool_token;
pub use swap::list_recent_swaps; pub use swap::list_recent_swaps;
pub use swap::upsert_swap; pub use swap::upsert_swap;
pub use token::get_token_by_id;
pub use token::get_token_by_mint; pub use token::get_token_by_mint;
pub use token::list_tokens; pub use token::list_tokens;
pub use token::list_tokens_missing_metadata;
pub use token::upsert_token; pub use token::upsert_token;
pub use token_burn_event::list_recent_token_burn_events; pub use token_burn_event::list_recent_token_burn_events;
pub use token_burn_event::upsert_token_burn_event; 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: {}", "cannot serialize analysis signal payload: {}",
error error
))); )));
} },
}; };
match database.connection() { match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => { crate::KbDatabaseConnection::Sqlite(pool) => {
@@ -49,10 +49,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_analysis_signals on sqlite: {}", "cannot insert kb_analysis_signals on sqlite: {}",
error 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: {}", "cannot list analysis signals on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -103,8 +103,8 @@ LIMIT ?
}; };
dtos.push(dto); dtos.push(dto);
} }
Ok(dtos) return Ok(dtos);
} },
} }
} }

View File

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

View File

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

View File

@@ -17,9 +17,9 @@ pub async fn upsert_chain_transaction(
"cannot convert chain transaction slot '{}' to i64: {}", "cannot convert chain transaction slot '{}' to i64: {}",
slot, error slot, error
))); )));
} },
} }
} },
None => None, None => None,
}; };
match database.connection() { match database.connection() {
@@ -80,13 +80,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_chain_transactions id for signature '{}' on sqlite: {}", return Err(crate::KbError::Db(format!(
dto.signature, error "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: {}", "cannot fetch kb_chain_transactions for signature '{}' on sqlite: {}",
signature, error signature, error
))); )));
} },
}; };
match entity_option { match entity_option {
Some(entity) => { Some(entity) => {
let dto_result = crate::KbChainTransactionDto::try_from(entity); let dto_result = crate::KbChainTransactionDto::try_from(entity);
match dto_result { match dto_result {
Ok(dto) => Ok(Some(dto)), Ok(dto) => return Ok(Some(dto)),
Err(error) => Err(error), Err(error) => return Err(error),
} }
} },
None => Ok(None), None => return Ok(None),
} }
} },
} }
} }
@@ -181,7 +183,7 @@ LIMIT ?
"cannot list kb_chain_transactions on sqlite: {}", "cannot list kb_chain_transactions on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -192,8 +194,49 @@ LIMIT ?
}; };
dtos.push(dto); 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, use_wal: true,
}, },
}; };
crate::KbDatabase::connect_and_initialize(&config) return crate::KbDatabase::connect_and_initialize(&config)
.await .await
.expect("database init must succeed") .expect("database init must succeed");
} }
#[tokio::test] #[tokio::test]
@@ -249,10 +292,7 @@ mod tests {
assert_eq!(fetched.signature, "sig-chain-transaction-1"); assert_eq!(fetched.signature, "sig-chain-transaction-1");
assert_eq!(fetched.slot, Some(515151)); assert_eq!(fetched.slot, Some(515151));
assert_eq!(fetched.block_time_unix, Some(1_700_000_001)); assert_eq!(fetched.block_time_unix, Some(1_700_000_001));
assert_eq!( assert_eq!(fetched.source_endpoint_name, Some("helius_primary_http".to_string()));
fetched.source_endpoint_name,
Some("helius_primary_http".to_string())
);
assert_eq!(fetched.version_text, Some("0".to_string())); assert_eq!(fetched.version_text, Some("0".to_string()));
assert_eq!(fetched.meta_json, Some(r#"{"fee":5000}"#.to_string())); assert_eq!(fetched.meta_json, Some(r#"{"fee":5000}"#.to_string()));
let listed = crate::list_recent_chain_transactions(&database, 10) let listed = crate::list_recent_chain_transactions(&database, 10)

View File

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

View File

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

View File

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

View File

@@ -77,13 +77,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_dex_decoded_events id on sqlite: {}", return Err(crate::KbError::Db(format!(
error "cannot fetch kb_dex_decoded_events id on sqlite: {}",
))), error
)));
},
} }
} },
} }
} }
@@ -135,19 +137,19 @@ LIMIT 1
"cannot fetch kb_dex_decoded_events on sqlite: {}", "cannot fetch kb_dex_decoded_events on sqlite: {}",
error error
))); )));
} },
}; };
match entity_option { match entity_option {
Some(entity) => { Some(entity) => {
let dto_result = crate::KbDexDecodedEventDto::try_from(entity); let dto_result = crate::KbDexDecodedEventDto::try_from(entity);
match dto_result { match dto_result {
Ok(dto) => Ok(Some(dto)), Ok(dto) => return Ok(Some(dto)),
Err(error) => Err(error), Err(error) => return Err(error),
} }
} },
None => Ok(None), None => return Ok(None),
} }
} },
} }
} }
@@ -189,7 +191,7 @@ ORDER BY id ASC
"cannot list kb_dex_decoded_events on sqlite: {}", "cannot list kb_dex_decoded_events on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -200,8 +202,48 @@ ORDER BY id ASC
}; };
dtos.push(dto); 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; let database_result = crate::KbDatabase::connect_and_initialize(&config).await;
match database_result { match database_result {
Ok(database) => database, Ok(database) => return database,
Err(error) => panic!("database init must succeed: {}", error), Err(error) => panic!("database init must succeed: {}", error),
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,13 +50,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_launch_surface_keys id for '{}:{}' on sqlite: {}", return Err(crate::KbError::Db(format!(
dto.match_kind, dto.match_value, error "cannot fetch kb_launch_surface_keys id for '{}:{}' on sqlite: {}",
))), dto.match_kind, dto.match_value, error
)));
},
} }
} },
} }
} }
@@ -68,9 +70,8 @@ pub async fn get_launch_surface_key_by_match(
) -> Result<std::option::Option<crate::KbLaunchSurfaceKeyDto>, crate::KbError> { ) -> Result<std::option::Option<crate::KbLaunchSurfaceKeyDto>, crate::KbError> {
match database.connection() { match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => { crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>( r#"
r#"
SELECT SELECT
id, id,
launch_surface_id, launch_surface_id,
@@ -82,11 +83,11 @@ FROM kb_launch_surface_keys
WHERE match_kind = ? AND match_value = ? WHERE match_kind = ? AND match_value = ?
LIMIT 1 LIMIT 1
"#, "#,
) )
.bind(match_kind) .bind(match_kind)
.bind(match_value) .bind(match_value)
.fetch_optional(pool) .fetch_optional(pool)
.await; .await;
let entity_option = match query_result { let entity_option = match query_result {
Ok(entity_option) => entity_option, Ok(entity_option) => entity_option,
Err(error) => { Err(error) => {
@@ -94,13 +95,13 @@ LIMIT 1
"cannot read kb_launch_surface_keys '{}:{}' on sqlite: {}", "cannot read kb_launch_surface_keys '{}:{}' on sqlite: {}",
match_kind, match_value, error match_kind, match_value, error
))); )));
} },
}; };
match entity_option { match entity_option {
Some(entity) => crate::KbLaunchSurfaceKeyDto::try_from(entity).map(Some), Some(entity) => return crate::KbLaunchSurfaceKeyDto::try_from(entity).map(Some),
None => Ok(None), None => return Ok(None),
} }
} },
} }
} }
@@ -111,9 +112,8 @@ pub async fn list_launch_surface_keys_by_surface_id(
) -> Result<std::vec::Vec<crate::KbLaunchSurfaceKeyDto>, crate::KbError> { ) -> Result<std::vec::Vec<crate::KbLaunchSurfaceKeyDto>, crate::KbError> {
match database.connection() { match database.connection() {
crate::KbDatabaseConnection::Sqlite(pool) => { crate::KbDatabaseConnection::Sqlite(pool) => {
let query_result = let query_result = sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>(
sqlx::query_as::<sqlx::Sqlite, crate::KbLaunchSurfaceKeyEntity>( r#"
r#"
SELECT SELECT
id, id,
launch_surface_id, launch_surface_id,
@@ -125,10 +125,10 @@ FROM kb_launch_surface_keys
WHERE launch_surface_id = ? WHERE launch_surface_id = ?
ORDER BY match_kind ASC, match_value ASC ORDER BY match_kind ASC, match_value ASC
"#, "#,
) )
.bind(launch_surface_id) .bind(launch_surface_id)
.fetch_all(pool) .fetch_all(pool)
.await; .await;
let entities = match query_result { let entities = match query_result {
Ok(entities) => entities, Ok(entities) => entities,
Err(error) => { Err(error) => {
@@ -136,7 +136,7 @@ ORDER BY match_kind ASC, match_value ASC
"cannot list kb_launch_surface_keys by surface_id '{}' on sqlite: {}", "cannot list kb_launch_surface_keys by surface_id '{}' on sqlite: {}",
launch_surface_id, error launch_surface_id, error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -147,7 +147,7 @@ ORDER BY match_kind ASC, match_value ASC
}; };
dtos.push(dto); 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: {}", "cannot convert liquidity event slot '{}' to i64: {}",
slot, error slot, error
))); )));
} },
} }
} },
None => None, None => None,
}; };
match database.connection() { match database.connection() {
@@ -96,13 +96,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_liquidity_events id for signature '{}' and instruction_index '{}' on sqlite: {}", return Err(crate::KbError::Db(format!(
dto.signature, dto.instruction_index, error "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: {}", "cannot list kb_liquidity_events on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -161,7 +163,7 @@ LIMIT ?
}; };
dtos.push(dto); dtos.push(dto);
} }
Ok(dtos) return Ok(dtos);
} },
} }
} }

View File

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

View File

@@ -15,7 +15,7 @@ pub async fn insert_onchain_observation(
"cannot serialize on-chain observation payload: {}", "cannot serialize on-chain observation payload: {}",
error error
))); )));
} },
}; };
let slot_i64 = match dto.slot { let slot_i64 = match dto.slot {
Some(slot) => { Some(slot) => {
@@ -27,9 +27,9 @@ pub async fn insert_onchain_observation(
"cannot convert on-chain observation slot '{}' to i64: {}", "cannot convert on-chain observation slot '{}' to i64: {}",
slot, error slot, error
))); )));
} },
} }
} },
None => None, None => None,
}; };
match database.connection() { match database.connection() {
@@ -64,10 +64,10 @@ VALUES (?, ?, ?, ?, ?, ?, ?)
"cannot insert kb_onchain_observations on sqlite: {}", "cannot insert kb_onchain_observations on sqlite: {}",
error 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: {}", "cannot list on-chain observations on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -118,8 +118,8 @@ LIMIT ?
}; };
dtos.push(dto); 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, dex_id = excluded.dex_id,
base_token_id = excluded.base_token_id, base_token_id = excluded.base_token_id,
quote_token_id = excluded.quote_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 updated_at = excluded.updated_at
"#, "#,
) )
@@ -56,13 +60,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_pairs id for pool_id '{}' on sqlite: {}", return Err(crate::KbError::Db(format!(
dto.pool_id, error "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: {}", "cannot read kb_pairs by pool_id '{}' on sqlite: {}",
pool_id, error pool_id, error
))); )));
} },
}; };
match entity_option { match entity_option {
Some(entity) => { Some(entity) => {
let dto_result = crate::KbPairDto::try_from(entity); let dto_result = crate::KbPairDto::try_from(entity);
match dto_result { match dto_result {
Ok(dto) => Ok(Some(dto)), Ok(dto) => return Ok(Some(dto)),
Err(error) => Err(error), Err(error) => return Err(error),
} }
} },
None => Ok(None), None => return 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: {}", "cannot list kb_pairs on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -156,8 +197,8 @@ ORDER BY id ASC
}; };
dtos.push(dto); dtos.push(dto);
} }
Ok(dtos) return Ok(dtos);
} },
} }
} }
@@ -184,13 +225,7 @@ mod tests {
.expect("database init must succeed"); .expect("database init must succeed");
let dex_id = crate::upsert_dex( let dex_id = crate::upsert_dex(
&database, &database,
&crate::KbDexDto::new( &crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
) )
.await .await
.expect("dex upsert must succeed"); .expect("dex upsert must succeed");
@@ -248,13 +283,8 @@ mod tests {
.await .await
.expect("get pair must succeed"); .expect("get pair must succeed");
assert!(pair.is_some()); assert!(pair.is_some());
assert_eq!( assert_eq!(pair.expect("pair must exist").symbol.as_deref(), Some("BASE/WSOL"));
pair.expect("pair must exist").symbol.as_deref(), let pairs = crate::list_pairs(&database).await.expect("list pairs must succeed");
Some("BASE/WSOL")
);
let pairs = crate::list_pairs(&database)
.await
.expect("list pairs must succeed");
assert_eq!(pairs.len(), 1); assert_eq!(pairs.len(), 1);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -60,13 +60,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_tokens id for mint '{}' on sqlite: {}", return Err(crate::KbError::Db(format!(
dto.mint, error "cannot fetch kb_tokens id for mint '{}' on sqlite: {}",
))), dto.mint, error
)));
},
} }
} },
} }
} }
@@ -104,22 +106,71 @@ LIMIT 1
"cannot read kb_tokens '{}' on sqlite: {}", "cannot read kb_tokens '{}' on sqlite: {}",
mint, error mint, error
))); )));
} },
}; };
match entity_option { match entity_option {
Some(entity) => { Some(entity) => {
let dto_result = crate::KbTokenDto::try_from(entity); let dto_result = crate::KbTokenDto::try_from(entity);
match dto_result { match dto_result {
Ok(dto) => Ok(Some(dto)), Ok(dto) => return Ok(Some(dto)),
Err(error) => Err(error), Err(error) => return Err(error),
} }
} },
None => Ok(None), 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. /// Lists all normalized token rows ordered by mint.
pub async fn list_tokens( pub async fn list_tokens(
@@ -152,7 +203,7 @@ ORDER BY mint ASC, id ASC
"cannot list kb_tokens on sqlite: {}", "cannot list kb_tokens on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -163,7 +214,161 @@ ORDER BY mint ASC, id ASC
}; };
dtos.push(dto); 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: {}", "cannot convert token burn event slot '{}' to i64: {}",
slot, error slot, error
))); )));
} },
} }
} },
None => None, None => None,
}; };
match database.connection() { match database.connection() {
@@ -78,13 +78,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_token_burn_events id for signature '{}' and instruction_index '{}' on sqlite: {}", return Err(crate::KbError::Db(format!(
dto.signature, dto.instruction_index, error "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: {}", "cannot list kb_token_burn_events on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -137,8 +139,8 @@ LIMIT ?
}; };
dtos.push(dto); dtos.push(dto);
} }
Ok(dtos) return Ok(dtos);
} },
} }
} }
@@ -165,13 +167,7 @@ mod tests {
.expect("database init must succeed"); .expect("database init must succeed");
let dex_id = crate::upsert_dex( let dex_id = crate::upsert_dex(
&database, &database,
&crate::KbDexDto::new( &crate::KbDexDto::new("raydium".to_string(), "Raydium".to_string(), None, None, true),
"raydium".to_string(),
"Raydium".to_string(),
None,
None,
true,
),
) )
.await .await
.expect("dex upsert must succeed"); .expect("dex upsert must succeed");
@@ -299,9 +295,7 @@ mod tests {
assert!(liquidity_id > 0); assert!(liquidity_id > 0);
assert!(mint_id > 0); assert!(mint_id > 0);
assert!(burn_id > 0); assert!(burn_id > 0);
let swaps = crate::list_recent_swaps(&database, 10) let swaps = crate::list_recent_swaps(&database, 10).await.expect("swaps list must succeed");
.await
.expect("swaps list must succeed");
let liquidity_events = crate::list_recent_liquidity_events(&database, 10) let liquidity_events = crate::list_recent_liquidity_events(&database, 10)
.await .await
.expect("liquidity list must succeed"); .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: {}", "cannot convert token mint event slot '{}' to i64: {}",
slot, error slot, error
))); )));
} },
} }
} },
None => None, None => None,
}; };
match database.connection() { match database.connection() {
@@ -78,13 +78,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_token_mint_events id for signature '{}' and instruction_index '{}' on sqlite: {}", return Err(crate::KbError::Db(format!(
dto.signature, dto.instruction_index, error "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: {}", "cannot list kb_token_mint_events on sqlite: {}",
error error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -137,7 +139,7 @@ LIMIT ?
}; };
dtos.push(dto); dtos.push(dto);
} }
Ok(dtos) return Ok(dtos);
} },
} }
} }

View File

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

View File

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

View File

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

View File

@@ -60,13 +60,15 @@ LIMIT 1
.fetch_one(pool) .fetch_one(pool)
.await; .await;
match id_result { match id_result {
Ok(id) => Ok(id), Ok(id) => return Ok(id),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot fetch kb_wallet_participations id for unique_key '{}' on sqlite: {}", return Err(crate::KbError::Db(format!(
dto.unique_key, error "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: {}", "cannot read kb_wallet_participations by unique_key '{}' on sqlite: {}",
unique_key, error unique_key, error
))); )));
} },
}; };
match entity_option { match entity_option {
Some(entity) => crate::KbWalletParticipationDto::try_from(entity).map(Some), Some(entity) => return crate::KbWalletParticipationDto::try_from(entity).map(Some),
None => Ok(None), 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: {}", "cannot list kb_wallet_participations by wallet_id '{}' on sqlite: {}",
wallet_id, error wallet_id, error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -165,8 +167,8 @@ ORDER BY created_at ASC, id ASC
}; };
dtos.push(dto); 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: {}", "cannot list kb_wallet_participations by pool_id '{}' on sqlite: {}",
pool_id, error pool_id, error
))); )));
} },
}; };
let mut dtos = std::vec::Vec::new(); let mut dtos = std::vec::Vec::new();
for entity in entities { for entity in entities {
@@ -218,7 +220,7 @@ ORDER BY created_at ASC, id ASC
}; };
dtos.push(dto); 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> { ) -> Result<std::string::String, crate::KbError> {
let path = config.sqlite.path.trim(); let path = config.sqlite.path.trim();
if path.is_empty() { if path.is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("database.sqlite.path must not be empty".to_string()));
"database.sqlite.path must not be empty".to_string(),
));
} }
let database_path = config.sqlite.path_buf(); 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. /// Opens a SQLite pool according to configuration.
@@ -22,9 +20,7 @@ pub(crate) async fn connect_sqlite(
) -> Result<sqlx::SqlitePool, crate::KbError> { ) -> Result<sqlx::SqlitePool, crate::KbError> {
let path = config.sqlite.path.trim(); let path = config.sqlite.path.trim();
if path.is_empty() { if path.is_empty() {
return Err(crate::KbError::Config( return Err(crate::KbError::Config("database.sqlite.path must not be empty".to_string()));
"database.sqlite.path must not be empty".to_string(),
));
} }
if config.sqlite.max_connections == 0 { if config.sqlite.max_connections == 0 {
return Err(crate::KbError::Config( return Err(crate::KbError::Config(
@@ -49,9 +45,7 @@ pub(crate) async fn connect_sqlite(
.filename(&database_path) .filename(&database_path)
.create_if_missing(config.sqlite.create_if_missing) .create_if_missing(config.sqlite.create_if_missing)
.foreign_keys(true) .foreign_keys(true)
.busy_timeout(std::time::Duration::from_millis( .busy_timeout(std::time::Duration::from_millis(config.sqlite.busy_timeout_ms));
config.sqlite.busy_timeout_ms,
));
if config.sqlite.use_wal { if config.sqlite.use_wal {
connect_options = connect_options.journal_mode(sqlx::sqlite::SqliteJournalMode::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); sqlx::sqlite::SqlitePoolOptions::new().max_connections(config.sqlite.max_connections);
let connect_result = pool_options.connect_with(connect_options).await; let connect_result = pool_options.connect_with(connect_options).await;
match connect_result { match connect_result {
Ok(pool) => Ok(pool), Ok(pool) => return Ok(pool),
Err(error) => Err(crate::KbError::Db(format!( Err(error) => {
"cannot open sqlite database '{}': {}", return Err(crate::KbError::Db(format!(
database_path.display(), error "cannot open sqlite database '{}': {}",
))), database_path.display(),
error
)));
},
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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