Skip to content

[codex] Add Lukka KYT checks for NEAR wallets - #874

Draft
think-in-universe wants to merge 6 commits into
mainfrom
codex/lukka-kyt-near-wallets
Draft

[codex] Add Lukka KYT checks for NEAR wallets#874
think-in-universe wants to merge 6 commits into
mainfrom
codex/lukka-kyt-near-wallets

Conversation

@think-in-universe

Copy link
Copy Markdown
Contributor

Summary

Implements backend support for NEAR wallet KYT checks for house-of-stake-contracts issue 56.

  • Adds env-driven KYT/Lukka configuration with disabled-by-default behavior and secret-file support.
  • Adds a server-side Lukka AML score client for GET /v3/reports/aml/score/{account_id}?address_type=NEAR.
  • Normalizes provider responses into Cloud API risk fields with LOW, MEDIUM, HIGH, and UNKNOWN levels plus status and warning state.
  • Adds an authenticated GET /v1/users/me/kyt/near endpoint for the connected NEAR wallet.
  • Includes optional KYT data in user-facing staking farm get/sync responses so the frontend can warn before continuing.
  • Caches recent checks per network/account/provider with a configurable TTL and maps provider failures to UNKNOWN/unavailable without marking accounts safe.

Configuration

New env vars:

  • KYT_ENABLED
  • KYT_PROVIDER
  • LUKKA_BASE_URL
  • LUKKA_BEARER_TOKEN or LUKKA_BEARER_TOKEN_FILE
  • KYT_TIMEOUT_SECONDS
  • KYT_RETRIES
  • KYT_CACHE_TTL_SECONDS

Validation

  • cargo test -p services kyt
  • cargo test -p config kyt
  • cargo check -p api

Refs nearai/house-of-stake-contracts#56

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Comment on lines +345 to +347
expires_at: Some(
checked_at + ChronoDuration::seconds(self.config.cache_ttl_seconds),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
expires_at: Some(
checked_at + ChronoDuration::seconds(self.config.cache_ttl_seconds),
),
expires_at: Some(
checked_at + ChronoDuration::seconds(10),
),

Comment on lines +145 to +150
Ok(response) => {
last_error = Some(anyhow::anyhow!(
"Lukka AML score request failed with status {}",
response.status()
));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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;
}
}

Comment on lines +108 to +111
timeout_seconds: env::var("KYT_TIMEOUT_SECONDS")
.ok()
.and_then(|value| value.parse::<u64>().ok())
.unwrap_or(10),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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
  1. 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.

Base automatically changed from codex/staking-farm-credits to main July 9, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant