[codex] Add Lukka KYT checks for NEAR wallets - #874
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new Know Your Transaction (KYT) service with a Lukka provider integration to check the risk level of connected NEAR wallets, integrating it into API routes and OpenAPI schemas. The review feedback highlights several important improvements: replacing the unbounded cache HashMap with a size-bounded cache to prevent memory leaks, reducing the cache TTL for provider failures to avoid long-lasting negative caching of transient errors, avoiding retries on 4xx client errors, and lowering the default timeout from 10 seconds to 2 seconds to prevent degrading user-facing latency on the critical path.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| pub struct KytService { | ||
| config: KytConfig, | ||
| provider: Arc<dyn KytProvider>, | ||
| cache: Arc<RwLock<HashMap<KytCacheKey, KytCheckResponse>>>, |
There was a problem hiding this comment.
The cache is implemented as an unbounded HashMap wrapped in a RwLock. Since there is no eviction policy, size limit, or cleanup mechanism, the cache will grow indefinitely as new NEAR accounts are checked. This can lead to a memory leak and potential Denial of Service (DoS) if a large number of unique accounts are queried.
Consider using a size-bounded cache with support for time-to-live (TTL) eviction, such as mini-moka or a custom LRU cache, to prevent unbounded memory growth.
| expires_at: Some( | ||
| checked_at + ChronoDuration::seconds(self.config.cache_ttl_seconds), | ||
| ), |
There was a problem hiding this comment.
Caching provider failures (KytRiskStatus::Unavailable) for the full cache_ttl_seconds (which defaults to 1 hour) is highly problematic. If the Lukka API experiences a brief 1-second network blip or temporary outage, any user who attempts a check during that window will have their failure cached for an hour, preventing them from successfully completing the check even after the provider recovers.
Consider using a much shorter negative cache TTL (e.g., 5 to 10 seconds) or not caching failures at all, so the system can recover quickly once the downstream service is back online.
| expires_at: Some( | |
| checked_at + ChronoDuration::seconds(self.config.cache_ttl_seconds), | |
| ), | |
| expires_at: Some( | |
| checked_at + ChronoDuration::seconds(10), | |
| ), |
| Ok(response) => { | ||
| last_error = Some(anyhow::anyhow!( | ||
| "Lukka AML score request failed with status {}", | ||
| response.status() | ||
| )); | ||
| } |
There was a problem hiding this comment.
The client currently retries the request for any non-success status code, including 4xx client errors (such as 400 Bad Request, 401 Unauthorized, or 404 Not Found). Retrying on client errors is futile and unnecessarily wastes resources, rate limits, or can even trigger account lockouts.
Consider checking the status code and only retrying on transient server errors (5xx) or network errors, while immediately breaking the loop on 4xx client errors.
| Ok(response) => { | |
| last_error = Some(anyhow::anyhow!( | |
| "Lukka AML score request failed with status {}", | |
| response.status() | |
| )); | |
| } | |
| Ok(response) => { | |
| let status = response.status(); | |
| last_error = Some(anyhow::anyhow!( | |
| "Lukka AML score request failed with status {status}" | |
| )); | |
| if !status.is_server_error() { | |
| break; | |
| } | |
| } |
| timeout_seconds: env::var("KYT_TIMEOUT_SECONDS") | ||
| .ok() | ||
| .and_then(|value| value.parse::<u64>().ok()) | ||
| .unwrap_or(10), |
There was a problem hiding this comment.
The default timeout for KYT checks is set to 10 seconds. Since the KYT check is performed inline on the critical path of user-facing endpoints (such as getting or syncing staking farm credits), a slow response or timeout from the Lukka API will directly degrade user-facing latency and cause requests to hang for up to 10 seconds.
Per the general rules, when implementing best-effort or inline metadata optimization calls on the critical path, we should use a low timeout (e.g., 1 or 2 seconds) to fail-open fast and prevent degrading user-facing latency.
Please reduce the default fallback timeout to 2 seconds.
| timeout_seconds: env::var("KYT_TIMEOUT_SECONDS") | |
| .ok() | |
| .and_then(|value| value.parse::<u64>().ok()) | |
| .unwrap_or(10), | |
| timeout_seconds: env::var("KYT_TIMEOUT_SECONDS") | |
| .ok() | |
| .and_then(|value| value.parse::<u64>().ok()) | |
| .unwrap_or(2), |
References
- When implementing best-effort or inline metadata optimization calls on the critical path, use a low timeout (e.g., 1 or 2 seconds) to fail-open fast and prevent degrading user-facing latency.
Summary
Implements backend support for NEAR wallet KYT checks for house-of-stake-contracts issue 56.
GET /v3/reports/aml/score/{account_id}?address_type=NEAR.LOW,MEDIUM,HIGH, andUNKNOWNlevels plus status and warning state.GET /v1/users/me/kyt/nearendpoint for the connected NEAR wallet.UNKNOWN/unavailablewithout marking accounts safe.Configuration
New env vars:
KYT_ENABLEDKYT_PROVIDERLUKKA_BASE_URLLUKKA_BEARER_TOKENorLUKKA_BEARER_TOKEN_FILEKYT_TIMEOUT_SECONDSKYT_RETRIESKYT_CACHE_TTL_SECONDSValidation
cargo test -p services kytcargo test -p config kytcargo check -p apiRefs nearai/house-of-stake-contracts#56