Conversation
Implements full telegram identity system integration including: - Add telegram incentive methods to IncentiveManager (telegramLinked/telegramUnlinked) - Add getAccountByTelegramUsername RPC endpoint for external system queries - Implement GCR.getAccountByTelegramUsername() with database JSONB queries - Add complete PointSystem telegram support: - LINK_TELEGRAM: 2 points value - awardTelegramPoints() with ownership verification - deductTelegramPoints() with ownership verification - Updated socialAccounts structure to include telegram - Add telegram support to GCR_Main entity type definitions The telegram bot can now submit identity transactions that are fully processed by the node's incentive and storage systems. Users receive 2 points for linking telegram accounts with proper anti-abuse measures. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
… it is done by bots
WalkthroughAdds Telegram web2 identity (dual‑signature verification and genesis-based bot authorization), PointSystem/incentive Telegram support, genesis identities loader and SharedState field, GitHub Actions to preserve/notify Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant DApp
participant Bot as Telegram Bot
participant Node
participant Genesis
participant GCR
User->>DApp: request link (wallet-signed challenge)
DApp->>Bot: forward challenge
Bot->>Bot: verify user wallet signature
Bot->>Bot: create TelegramSignedAttestation (bot-signed)
Bot->>Node: submit identity tx (identity / subtype: web2_platform {platform: "telegram", payload})
Node->>Genesis: check bot authorization (genesisIdentities + min balance)
Node->>Node: verify bot signature and payload integrity
alt verification ok
Node->>GCR: add Telegram identity
Node->>IncentiveManager: telegramLinked (if first connection)
Node-->>Bot: success
else verification failed
Node-->>Bot: reject (error)
end
sequenceDiagram
autonumber
participant GH as GitHubActions
participant Repo
Note over GH,Repo: On push (merge commit detection)
GH->>Repo: inspect latest commit parents and changed files
alt commit is merge AND .claude/ or .serena/ changed
GH->>Repo: checkout MERGE_BASE -- .claude/ (or .serena/)
GH->>Repo: git add & commit "[skip ci] restore .claude/ or .serena/"
GH->>Repo: push revert to current branch
else
GH-->>Repo: exit (no-op)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/features/incentive/PointSystem.ts (2)
112-120: Normalize socialAccounts shape to always include telegram/github/twitter/discordIf socialAccounts exists but lacks telegram, callers may read undefined. Merge defaults to guarantee keys exist.
breakdown: { web3Wallets: account.points.breakdown?.web3Wallets || {}, - socialAccounts: account.points.breakdown?.socialAccounts || { - twitter: 0, - github: 0, - telegram: 0, - discord: 0, - }, + socialAccounts: { + twitter: 0, + github: 0, + telegram: 0, + discord: 0, + ...(account.points.breakdown?.socialAccounts || {}), + }, referrals: account.points.breakdown?.referrals || 0, demosFollow: account.points.breakdown?.demosFollow || 0,
195-206: Potential NPE: account.points.breakdown.socialAccounts may be undefinedExisting accounts might not have initialized breakdown/socialAccounts. Initialize before mutate.
if ( type === "socialAccounts" && (platform === "twitter" || platform === "github" || platform === "telegram" || platform === "discord") ) { + // Ensure structure exists + account.points.breakdown = + account.points.breakdown || { + web3Wallets: {}, + socialAccounts: { twitter: 0, github: 0, telegram: 0, discord: 0 }, + referrals: 0, + demosFollow: 0, + } + account.points.breakdown.socialAccounts = + account.points.breakdown.socialAccounts || { + twitter: 0, + github: 0, + telegram: 0, + discord: 0, + } const oldPlatformPoints = account.points.breakdown?.socialAccounts?.[platform] || 0 account.points.breakdown.socialAccounts[platform] = oldPlatformPoints + points
🧹 Nitpick comments (26)
src/utilities/validateUint8Array.ts (1)
3-6: Sorting by Number(a)-Number(b) can yield NaN; filter numeric keys explicitlyFor non‑numeric keys,
Number(key)isNaN, making comparisons ill‑defined. Filtering to/^\d+$/and then sorting numerically is safer and avoids surprises. Also, for arrays/typed arrays, sorting is unnecessary; their iteration order is already index order..gitignore (1)
129-139: Deduplicate Serena cache ignoreNow that .serena/.gitignore ignores /cache, the explicit .serena/cache/... pattern here is redundant.
Apply this diff:
- .serena/cache/typescript/document_symbols_cache_v23-06-25.pkl.github/workflows/claude-merge-fix.yml (2)
52-56: Simplify staging logicgit checkout -- .claude/ already updates the index; the staged‑diff guard + add is redundant and inverted. Safe to drop it.
Apply this diff:
- if git diff --staged --quiet; then - git add .claude/ - fi + # index is updated by checkout; no extra add needed
46-47: Name correctness: this is first parent, not merge‑baseVariable MERGE_BASE actually holds the first parent of the merge; consider renaming to PARENT1 for clarity.
Apply this diff:
- MERGE_BASE=$(git log -1 --pretty=format:"%P" | cut -d' ' -f1) - git checkout $MERGE_BASE -- .claude/ 2>/dev/null || echo "No .claude in base commit" + PARENT1=$(git log -1 --pretty=format:"%P" | cut -d' ' -f1) + git checkout "$PARENT1" -- .claude/ 2>/dev/null || echo "No .claude in base commit".github/workflows/notify-serena-merging.yml (1)
27-33: Harden gh usageIf gh isn’t available on some runners, install/setup or fall back to log‑only. Optional but improves reliability.
Apply this diff to install gh if missing:
- gh pr comment ${{ github.event.number }} --body "⚠️ **MCP Memory Files Detected** + command -v gh >/dev/null 2>&1 || sudo apt-get update && sudo apt-get install -y gh || true + gh pr comment ${{ github.event.number }} --body "⚠️ **MCP Memory Files Detected**src/libs/abstraction/index.ts (3)
86-95: Normalize Telegram username casing and ID typesTelegram usernames are case-insensitive; IDs may be string/number. Current strict equality risks false negatives.
Apply this diff:
- if ( - telegramAttestation.payload.telegram_id !== payload.userId || - telegramAttestation.payload.username !== payload.username - ) { + const idMatches = + String(telegramAttestation.payload.telegram_id) === String(payload.userId) + const usernameMatches = + telegramAttestation.payload.username?.toLowerCase() === payload.username?.toLowerCase() + if (!idMatches || !usernameMatches) { return { success: false, message: "Telegram attestation data mismatch", } }
131-138: Authorization model noteGenesis-balance presence is a one-way allow; it can’t handle bot revocation/rotation without a new genesis. Consider augmenting with an on-chain allowlist/param set or a signed bot-registry state.
204-208: Guard string coercion for non-telegram contextsIf payload.proof is unexpectedly non-string, readData will throw. Add a fast type check to fail early with a clearer message.
Apply this diff:
- const { message, type, signature } = await instance.readData( - payload.proof as string, - ) + if (typeof payload.proof !== "string") { + return { success: false, message: "Invalid proof format (expected string)" } + } + const { message, type, signature } = await instance.readData(payload.proof).serena/project.yml (2)
10-16: Consider adding repo-specific ignores instead of relying solely on .gitignoreIf large artifacts (e.g., logs, temp data) aren’t in .gitignore, add them here to prevent tool churn.
62-62: Keep excluded_tools empty (good), but document rationaleA brief inline comment stating “intentionally empty to keep full tool surface” helps future maintainers.
.serena/memories/project_context_consolidated.md (3)
11-22: Fix fenced code block language for the directory treeAdd a language to satisfy markdownlint MD040 and improve rendering. For trees, use “text”.
Apply this diff:
-``` +```text src/ ├── features/ # Feature modules (multichain, incentives) ... -``` +```
25-37: Annotate shell blocks with bashLabel code fences to satisfy MD040 and enable syntax highlighting.
Apply this diff:
-```bash +```bash # Code Quality (REQUIRED after changes) bun run lint:fix # ESLint validation + auto-fix ... -``` +```
8-8: Avoid committing machine-specific absolute pathsReplace personal absolute paths with placeholders (e.g., “/node”) to keep docs portable.
.github/workflows/claude-merge-notify.yml (1)
21-33: Use explicit SHAs for diff to avoid missing refs; safer heredoc for comment bodyorigin/${{ github.base_ref }} may not exist locally; HEREDOC avoids quoting pitfalls.
Apply this diff:
- if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "^\.claude/"; then + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + if git diff --name-only "$BASE_SHA...$HEAD_SHA" | grep -q "^\.claude/"; then echo "⚠️ This PR modifies .claude/ files" - - COMMENT_BODY="⚠️ **Claude Memory Files Detected** - - This PR modifies \`.claude/\` files. After merge, these changes will be **automatically reverted** to preserve branch-specific Claude conversation context. - - **Files that will be reverted:** - $(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '^\.claude/' | sed 's/^/- /' | head -10) - - This is expected behavior to keep Claude conversation context branch-specific. ✅" - - gh pr comment ${{ github.event.number }} --body "$COMMENT_BODY" || echo "Could not post comment" + FILE_LIST="$(git diff --name-only "$BASE_SHA...$HEAD_SHA" | grep '^\.claude/' | sed 's/^/- /' | head -10)" + cat > body.md <<'EOF' +⚠️ **Claude Memory Files Detected** + +This PR modifies `.claude/` files. After merge, these changes will be **automatically reverted** to preserve branch-specific Claude conversation context. + +**Files that will be reverted:** +EOF + echo "$FILE_LIST" >> body.md + echo "" >> body.md + echo "This is expected behavior to keep Claude conversation context branch-specific. ✅" >> body.md + gh pr comment ${{ github.event.number }} --body-file body.md || echo "Could not post comment".github/workflows/fix-serena-conflicts.yml (2)
28-39: Detect changes in merge commits correctlygit log -1 --name-only often yields nothing for merge commits without -m. Use git show -m.
Apply this diff:
- if git log -1 --name-only | grep -q "^\.serena/"; then + if git show -m --name-only --pretty=format: HEAD | grep -q "^\.serena/"; then
47-50: Variable naming and base resolutionThis is the first parent, not a “merge base”. Rename for clarity. Also ensure the commit is present locally (safe with fetch-depth: 0).
Apply this diff:
- MERGE_BASE=$(git log -1 --pretty=format:"%P" | cut -d' ' -f1) + FIRST_PARENT=$(git log -1 --pretty=format:"%P" | cut -d' ' -f1) @@ - git checkout $MERGE_BASE -- .serena/ 2>/dev/null || echo "No .serena in base commit" + git checkout "$FIRST_PARENT" -- .serena/ 2>/dev/null || echo "No .serena in base commit"src/features/incentive/PointSystem.ts (1)
733-746: Guard duplicate-award check against missing keyUse nullish coalescing to avoid undefined comparisons and ensure idempotency.
- // Check if user already has Telegram points specifically - if (userPointsWithIdentities.breakdown.socialAccounts.telegram > 0) { + // Check if user already has Telegram points specifically + const currentTelegram = + userPointsWithIdentities.breakdown.socialAccounts?.telegram ?? 0 + if (currentTelegram > 0) { return { result: 200, response: { pointsAwarded: 0, totalPoints: userPointsWithIdentities.totalPoints, message: "Telegram points already awarded", },.serena/memories/telegram_identity_system_complete.md (2)
24-29: Add fenced code language for the step blockSilences MD040 and improves rendering.
-``` +```text 1. User signs payload in Telegram bot (bot verifies locally) 2. Bot creates TelegramSignedAttestation with bot signature 3. Node verifies bot signature + bot authorization 4. User ownership validated via public key matching--- `13-15`: **Align terminology: it’s bot‑signature validation + sender PK match, not “dual signature”** Doc elsewhere states the attestation signature is from the bot. Rephrase Phase 4a to avoid confusion. ```diff -- **Phase 4a** ✅: Cryptographic Dual Signature Validation +- **Phase 4a** ✅: Bot Signature Validation + Sender Public‑Key MatchTG_IDENTITY_PLAN.md (4)
10-19: Update flow and headings: remove trailing colon and reflect bot‑signature modelPlan still mentions “dual signatures (user + bot)”. Implementation uses bot‑signed attestation with sender PK match. Also fix MD026.
-### Complete Transaction-Based Flow: +### Complete Transaction-Based Flow @@ -6. **BOT** creates attestation with dual signatures (user + bot) +6. **BOT** creates TelegramSignedAttestation (bot signature), referencing the user's signed challenge
99-107: Security section: align wording with implemented verificationReplace “Dual Signature Verification” with precise checks; remove trailing colon in headings flagged by MD026.
-## Security Features +## Security Features @@ -2. **Dual Signature Verification**: User signature + Bot signature required -3. **Bot Authorization**: Only genesis addresses can create valid bot signatures +2. **Bot Signature Verification**: Node verifies bot signature on attestation; user ownership enforced via sender public key match +3. **Bot Authorization**: Only genesis-authorized bot addresses are accepted
135-159: Add language to fenced diagram blockSilences MD040 and improves readability.
-``` +```text ┌─────────────────────────────────────────────────────────────────────────────┐ │ TRANSACTION-BASED FLOW │ └─────────────────────────────────────────────────────────────────────────────┘ @@ **Transaction Flow**: Bot creates transaction → Node validates through GCR → Identity stored **Security**: Dual signatures (user + bot) + Bot authorization via genesis addresses
110-120: Remove trailing colons in headings flagged by markdownlintMD026: “Completed (Bot):”, “To Do (../sdks repo):”, “To Do (Node repo):”.
-### ✅ **Completed (Bot)**: +### ✅ **Completed (Bot)** @@ -### ❌ **To Do (../sdks repo)**: +### ❌ **To Do (../sdks repo)** @@ -### ❌ **To Do (Node repo)**: +### ❌ **To Do (Node repo)**src/libs/network/server_rpc.ts (1)
336-344: Prefer jsonResponse() for consistent headersReturn JSON with correct Content‑Type and match the rest of the server.
- server.get("/", req => { - const clientIP = rateLimiter.getClientIP(req, server.server) - return new Response( - JSON.stringify({ - message: "Hello, World!", - yourIP: clientIP, - }), - ) - }) + server.get("/", req => { + const clientIP = rateLimiter.getClientIP(req, server.server) + return jsonResponse({ message: "Hello, World!", yourIP: clientIP }) + })src/libs/network/manageGCRRoutines.ts (1)
85-96: Normalize Telegram usernames (strip leading '@'); keep input validationPrevents lookup misses when clients include “@”.
- case "getAccountByTelegramUsername": { - const username = params[0] + case "getAccountByTelegramUsername": { + const usernameRaw = params[0] + const username = + typeof usernameRaw === "string" + ? usernameRaw.trim().replace(/^@/, "") + : ""If usernames are case-insensitive in storage, consider switching the DB query to ILIKE for robustness.
src/libs/blockchain/gcr/gcr.ts (1)
571-602: Telegram username lookup works; harden JSONB query and indexing
- Use COALESCE to avoid jsonb_array_elements errors when
identities.web2.telegramis missing.- Consider lower-casing usernames during write and lookup.
Apply COALESCE in the predicate:
- .where( - "EXISTS (SELECT 1 FROM jsonb_array_elements(gcr.identities->'web2'->'telegram') as telegram_id WHERE telegram_id->>'username' = :username)", - { username }, - ) + .where( + "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(gcr.identities->'web2'->'telegram','[]'::jsonb)) AS telegram_id WHERE telegram_id->>'username' = :username)", + { username }, + )Add a GIN index to keep these JSONB lookups fast:
CREATE INDEX IF NOT EXISTS gcrmain_identities_gin ON "GCR_Main" USING GIN ((identities) jsonb_path_ops);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (22)
.eslintignore(1 hunks).eslintrc.cjs(1 hunks).github/workflows/claude-merge-fix.yml(1 hunks).github/workflows/claude-merge-notify.yml(1 hunks).github/workflows/fix-serena-conflicts.yml(1 hunks).github/workflows/notify-serena-merging.yml(1 hunks).gitignore(1 hunks).serena/.gitignore(1 hunks).serena/memories/project_context_consolidated.md(1 hunks).serena/memories/telegram_identity_system_complete.md(1 hunks).serena/project.yml(1 hunks)TG_IDENTITY_PLAN.md(1 hunks)package.json(1 hunks)src/features/incentive/PointSystem.ts(4 hunks)src/libs/abstraction/index.ts(3 hunks)src/libs/blockchain/gcr/gcr.ts(1 hunks)src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts(14 hunks)src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts(1 hunks)src/libs/network/manageGCRRoutines.ts(1 hunks)src/libs/network/server_rpc.ts(1 hunks)src/model/entities/GCRv2/GCR_Main.ts(1 hunks)src/utilities/validateUint8Array.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/libs/network/manageGCRRoutines.ts (1)
src/libs/blockchain/gcr/gcr.ts (1)
GCR(109-979)
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (4)
src/libs/abstraction/index.ts (1)
verifyWeb2Proof(158-238)src/utilities/logger.ts (1)
error(125-132)src/libs/crypto/hashing.ts (1)
Hashing(15-26)src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts (1)
IncentiveManager(9-115)
src/features/incentive/PointSystem.ts (1)
src/libs/blockchain/gcr/gcr_routines/ensureGCRForUser.ts (1)
ensureGCRForUser(8-33)
🪛 markdownlint-cli2 (0.17.2)
.serena/memories/telegram_identity_system_complete.md
24-24: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
.serena/memories/project_context_consolidated.md
11-11: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
TG_IDENTITY_PLAN.md
10-10: Trailing punctuation in heading
Punctuation: ':'
(MD026, no-trailing-punctuation)
110-110: Trailing punctuation in heading
Punctuation: ':'
(MD026, no-trailing-punctuation)
116-116: Trailing punctuation in heading
Punctuation: ':'
(MD026, no-trailing-punctuation)
120-120: Trailing punctuation in heading
Punctuation: ':'
(MD026, no-trailing-punctuation)
129-129: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
135-135: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (15)
src/utilities/validateUint8Array.ts (1)
2-8: Avoid Node-only Buffer; return a true Uint8Array for cross‑env safetyReplace Buffer.from(...) with Uint8Array and fast‑path existing Uint8Array — suggested patch below.
Apply:
-export default function validateIfUint8Array(input: unknown): Uint8Array | unknown { - if (typeof input === "object" && input !== null) { - const txArray = Object.keys(input) - .sort((a, b) => Number(a) - Number(b)) - .map(k => input[k]) - return Buffer.from(txArray) - } - return input -} +export default function validateIfUint8Array<T>(input: T): Uint8Array | T { + // Fast path: already a typed array (Buffer also passes as it extends Uint8Array) + if (input instanceof Uint8Array) return input; + + // Array of numbers -> Uint8Array + if (Array.isArray(input)) { + return Uint8Array.from(input as number[], (v) => (v as number) & 0xff); + } + + // Object with numeric keys -> Uint8Array + if (input !== null && typeof input === "object") { + const obj = input as Record<string, unknown>; + const keys = Object.keys(obj) + .filter((k) => /^\d+$/.test(k)) + .map((k) => Number(k)) + .sort((a, b) => a - b); + const bytes = keys.map((k) => Number((obj as any)[k]) & 0xff); + return Uint8Array.from(bytes); + } + + // Anything else: pass through unchanged + return input; +}ripgrep run returned no matches for 'validateIfUint8Array' — verify imports/usages (or re-run a broader search) to confirm browser impact.
.eslintignore (1)
8-9: LGTM: targeted ESLint ignoresAdding postgres_* and aptos_examples_ts to ESLint ignores is reasonable and scoped.
.serena/.gitignore (1)
1-1: LGTM: ignore Serena cacheKeeps noise out of diffs and workflows.
.eslintrc.cjs (1)
41-41: LGTM: allow UPPER_CASE for variable‑like identifiersMatches new constant naming (e.g., LINK_TELEGRAM).
.gitignore (1)
129-130: Confirm dist ignore is intendeddist is now ignored; since package.json is private this is fine, but ensure any CI artifacts don’t rely on checking in dist.
src/libs/abstraction/index.ts (2)
171-173: LGTM: early exit for telegram pathRouting Telegram to its dedicated verifier avoids parser misuse and simplifies control flow.
117-123: Require explicit bot_public_key for signature verification (don’t use bot_address as the public key)You pass botAddress into ucrypto.verify; if bot_address is an address (not the raw public key) verification will fail. Require attestation.bot_public_key, use it for signature verification, then derive-and-cross-check the address before calling checkBotAuthorization. Location: src/libs/abstraction/index.ts (around lines 117–123).
Apply this diff (assuming attestation includes bot_public_key):
- const { public_key: userPublicKey, bot_address: botAddress } = + const { public_key: userPublicKey, bot_address: botAddress, bot_public_key: botPublicKey } = attestationPayload @@ - const botSignatureValid = await ucrypto.verify({ + const botSignatureValid = await ucrypto.verify({ algorithm: signature.type, message: new TextEncoder().encode(messageToVerify), - publicKey: hexToUint8Array(botAddress), // Bot's public key + publicKey: hexToUint8Array(botPublicKey), signature: hexToUint8Array(signature.data), }) @@ - const botAuthorized = await checkBotAuthorization(botAddress) + // Optional: derive address from public key and cross-check + // const derived = deriveAddressFromPublicKey(botPublicKey) + // if (derived.toLowerCase() !== botAddress.toLowerCase()) { ... } + const botAuthorized = await checkBotAuthorization(botAddress)Repo search (run earlier) found no existing address-derivation helper — implement deriveAddressFromPublicKey or add/rename the attestation field to avoid the footgun.
.serena/project.yml (1)
6-6: LGTM: explicit TypeScript project declarationMatches repo language and aligns with alias usage noted elsewhere.
src/features/incentive/PointSystem.ts (2)
16-16: Add LINK_TELEGRAM constant — LGTMValue aligns with Twitter. No concerns.
703-778: Parity: normalize social point checks & extract shared getSocialPointsTwitter/GitHub award/deduct paths use raw >0 / <=0 checks and can hit undefined/NaN edge cases; implement getSocialPoints(account, platform): number (fallback 0) and use it across all social award/deduct logic to DRY and harden behavior.
- Replace raw comparisons (e.g., breakdown.socialAccounts.twitter > 0) with the helper.
- Add unit tests for missing-key / undefined social breakdown scenarios.
Applies also to: src/features/incentive/PointSystem.ts lines 786–858
Would you like a follow‑up PR to refactor these paths and add tests?
.serena/memories/telegram_identity_system_complete.md (1)
80-86: Consistency check: “Double Protection” wordingIf only the bot signature is verified (plus sender PK match), avoid implying two cryptographic signatures. Consider “Bot signature + genesis authorization”.
Do we still verify a user signature on‑chain, or only inside the bot? If only inside the bot, I can adjust phrasing across docs for consistency.
src/libs/blockchain/gcr/gcr.ts (1)
563-569: Twitter tie-breaker logic remains consistentOptional chaining with a points-based pick is fine; trailing comma in call arg is harmless.
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (2)
343-349: Unlink: store removed identity only for GitHub/Telegram — intentional?Twitter unlinking doesn’t capture
userId, hencetwitterUnlinkedcan’t distinguish multiple Twitter connections historically. Confirm this matches the points deduction contract.
363-381: Unlink deductions — idempotency present; verify clamp to 0
- PointSystem.deductGithubPoints and deductTelegramPoints short‑circuit when userPointsWithIdentities.breakdown.socialAccounts.github/telegram <= 0 (PointSystem.ts — deductGithubPoints ~line 622 with github check ~648–651; deductTelegramPoints ~line 786 with telegram check ~813–815), so repeated calls are no‑ops.
- Verify the persistence/update path these methods call when applying the subtraction actually clamps stored totals at 0 (e.g., uses Math.max or similar) and that the write is atomic to avoid negative balances.
src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts (1)
87-107: Telegram hooks — PointSystem signatures match.
awardTelegramPoints(userId: string, telegramUserId: string, referralCode?: string) and deductTelegramPoints(userId: string, telegramUserId: string) are implemented in src/features/incentive/PointSystem.ts and their parameter order matches the IncentiveManager calls. No changes required.
| jobs: | ||
| preserve-claude: | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
Grant write permissions for push
The job pushes a commit; without explicit permissions, GITHUB_TOKEN may be read‑only and the push will fail.
Apply this diff near the top:
jobs:
preserve-claude:
runs-on: ubuntu-latest
+ permissions:
+ contents: write📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| jobs: | |
| preserve-claude: | |
| runs-on: ubuntu-latest | |
| jobs: | |
| preserve-claude: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write |
🤖 Prompt for AI Agents
.github/workflows/claude-merge-fix.yml lines 7-9: the workflow runs a job that
pushes a commit but doesn’t declare write permissions for GITHUB_TOKEN; add a
permissions block near the top of the file (before jobs) that grants contents:
write so the job can push (e.g., permissions: contents: write), ensuring the
push will succeed.
| name: Claude PR Warning | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: ['**'] | ||
| types: [opened, synchronize] | ||
|
|
||
| jobs: | ||
| claude-warning: |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Grant minimal permissions for PR comments
Explicit permissions avoid surprises on hardened orgs.
Apply this diff:
name: Claude PR Warning
+
+permissions:
+ contents: read
+ pull-requests: write🤖 Prompt for AI Agents
.github/workflows/claude-merge-notify.yml lines 1-9: this workflow needs
explicit, minimal permissions to post PR comments on hardened orgs — add a
permissions block granting only the specific write scope used by your commenting
code (e.g., if you call the Pull Requests API grant pull-requests: write; if you
use the Issues API grant issues: write), include the permissions stanza at the
top-level of the workflow YAML so the runner has only that permission and
nothing broader.
| name: Preserve Branch-Specific Serena Files | ||
|
|
||
| on: | ||
| push: | ||
| branches: ['**'] | ||
|
|
||
| jobs: | ||
| preserve-serena: | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add explicit permissions and concurrency to avoid push races
Required to allow committing and pushing from the workflow, and to serialize runs per branch.
Apply this diff:
name: Preserve Branch-Specific Serena Files
+
+permissions:
+ contents: write
+
+concurrency:
+ group: preserve-serena-${{ github.ref }}
+ cancel-in-progress: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| name: Preserve Branch-Specific Serena Files | |
| on: | |
| push: | |
| branches: ['**'] | |
| jobs: | |
| preserve-serena: | |
| runs-on: ubuntu-latest | |
| name: Preserve Branch-Specific Serena Files | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: preserve-serena-${{ github.ref }} | |
| cancel-in-progress: false | |
| on: | |
| push: | |
| branches: ['**'] | |
| jobs: | |
| preserve-serena: | |
| runs-on: ubuntu-latest |
🤖 Prompt for AI Agents
In .github/workflows/fix-serena-conflicts.yml lines 1-9, the workflow lacks
explicit permissions and concurrency settings; add a top-level permissions block
granting the workflow write access to repository contents (e.g., permissions:
contents: write) so the job can commit and push, and add a top-level concurrency
block to serialize runs per branch (e.g., concurrency.group set to the branch
ref like github.ref and cancel-in-progress set appropriately) so concurrent
pushes on the same branch are serialized.
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 2 | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
There was a problem hiding this comment.
Fetch depth may be insufficient for parent retrieval
fetch-depth: 2 can miss the first parent tree. Fetch full history or at least the parent commit explicitly.
Apply this diff:
- with:
- fetch-depth: 2
- token: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 2 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} |
🤖 Prompt for AI Agents
In .github/workflows/fix-serena-conflicts.yml around lines 12 to 16, the
checkout step uses fetch-depth: 2 which can omit parent commit history; change
the checkout configuration to fetch the full history by setting fetch-depth: 0
(or explicitly fetch the required parent commit) so parent trees are available
for merge/conflict resolution and CI operations.
| # Commit the reversion | ||
| if git diff --staged --quiet; then | ||
| git add .serena/ | ||
| fi | ||
|
|
||
| if ! git diff --cached --quiet; then | ||
| git commit -m "🔒 Preserve branch-specific .serena files | ||
|
|
||
| Reverted .serena/ changes from merge to keep $CURRENT_BRANCH version intact. | ||
| [skip ci]" | ||
|
|
||
| git push origin $CURRENT_BRANCH | ||
| echo "✅ Successfully preserved $CURRENT_BRANCH .serena files" | ||
| else | ||
| echo "ℹ️ No changes to revert" | ||
| fi |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Simplify staging and commit logic
Use a straightforward add + check for staged changes; current staged check is inverted and brittle.
Apply this diff:
- # Commit the reversion
- if git diff --staged --quiet; then
- git add .serena/
- fi
-
- if ! git diff --cached --quiet; then
+ git add .serena/ || true
+ if ! git diff --cached --quiet -- .serena/; then
git commit -m "🔒 Preserve branch-specific .serena files
-
Reverted .serena/ changes from merge to keep $CURRENT_BRANCH version intact.
[skip ci]"
git push origin $CURRENT_BRANCH
echo "✅ Successfully preserved $CURRENT_BRANCH .serena files"
else
echo "ℹ️ No changes to revert"
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Commit the reversion | |
| if git diff --staged --quiet; then | |
| git add .serena/ | |
| fi | |
| if ! git diff --cached --quiet; then | |
| git commit -m "🔒 Preserve branch-specific .serena files | |
| Reverted .serena/ changes from merge to keep $CURRENT_BRANCH version intact. | |
| [skip ci]" | |
| git push origin $CURRENT_BRANCH | |
| echo "✅ Successfully preserved $CURRENT_BRANCH .serena files" | |
| else | |
| echo "ℹ️ No changes to revert" | |
| fi | |
| git add .serena/ || true | |
| if ! git diff --cached --quiet -- .serena/; then | |
| git commit -m "🔒 Preserve branch-specific .serena files | |
| Reverted .serena/ changes from merge to keep $CURRENT_BRANCH version intact. | |
| [skip ci]" | |
| git push origin $CURRENT_BRANCH | |
| echo "✅ Successfully preserved $CURRENT_BRANCH .serena files" | |
| else | |
| echo "ℹ️ No changes to revert" | |
| fi |
🤖 Prompt for AI Agents
In .github/workflows/fix-serena-conflicts.yml around lines 56 to 71 the
staging/commit logic is inverted and brittle; replace the two-step conditional
with a simple sequence: run git add .serena/ unconditionally, then check for
staged changes with if ! git diff --staged --quiet; then perform the git commit
and git push and success echo, else echo no changes; remove the previous
conditional that only ran git add when git diff --staged --quiet to ensure
.serena files are always staged before the staged-changes check.
|
This PR modifies Files that will be reverted:
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/libs/network/manageGCRRoutines.ts (1)
10-13: Tighten payload typing (replaceany[]with a discriminated union).This prevents runtime type guards per method and catches param mistakes at compile time.
Sketch:
type GCRRoutinePayload = | { method: "getAccountByTelegramUsername"; params: [string] } | { method: "getAccountByIdentity"; params: [unknown /* Identity type */] } | { method: "getIdentities"; params: [string] } | { method: "getWeb2Identities"; params: [string] } | { method: "getXmIdentities"; params: [string] } | { method: "getPoints"; params: [string] } | { method: "validateReferralCode"; params: [string] } | { method: "identity_assign_from_write"; params: [unknown /* write */] } | { method: "getReferralInfo"; params: [string] }
🧹 Nitpick comments (1)
src/libs/network/manageGCRRoutines.ts (1)
95-96: Return a safe projection to avoid leaking full account records.
GCR.getAccountByTelegramUsername()likely returns a fullGCRMainentity. Consider returning only whitelisted fields (e.g.,pubkey, minimalidentities.web2.telegram, points summary) to limit exposure.Example (if acceptable here):
const acct = await GCR.getAccountByTelegramUsername(normalized) response.response = acct ? _.pick(acct, ["pubkey", "identities.web2.telegram", "points.total"]) : null
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/libs/blockchain/gcr/gcr.ts(3 hunks)src/libs/network/manageGCRRoutines.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/libs/blockchain/gcr/gcr.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/libs/network/manageGCRRoutines.ts (1)
src/libs/blockchain/gcr/gcr.ts (1)
GCR(109-1050)
🔇 Additional comments (1)
src/libs/network/manageGCRRoutines.ts (1)
86-97: Confirm 200+null vs 404 semantics and downstream expectations.
- Evidence: GCR.getAccountByTelegramUsername returns null on miss (src/libs/blockchain/gcr/gcr.ts:642); manageGCRRoutines forwards that and keeps response.result = 200 (src/libs/network/manageGCRRoutines.ts — case "getAccountByTelegramUsername").
- Repo scan: no in-repo callers of this RPC found; other code typically treats response.result === 200 as success.
- Action: Decide whether "not found" should be a 404 or remain 200+null. If 404 is desired, set response.result = response.response ? 200 : 404 before returning (or have GCR return a full RPCResponse). If 200+null is intentional, document this and confirm external clients check response.response for existence (not just status).
| case "getAccountByTelegramUsername": { | ||
| const username = params[0] | ||
|
|
||
| if (!username) { | ||
| response.result = 400 | ||
| response.response = "No username specified" | ||
| break | ||
| } | ||
|
|
||
| response.response = await GCR.getAccountByTelegramUsername(username) | ||
| break | ||
| } |
There was a problem hiding this comment.
Normalize and validate Telegram usernames; align error shape with existing handlers.
- Accept both "@foo" and "foo", trim, and lowercase to avoid case/format mismatches with DB lookups.
- Validate type and format up front; return 400 with
response=nullandextra.error(same pattern as Line 76-79).
Apply this diff:
case "getAccountByTelegramUsername": {
- const username = params[0]
-
- if (!username) {
- response.result = 400
- response.response = "No username specified"
- break
- }
-
- response.response = await GCR.getAccountByTelegramUsername(username)
+ const raw = params?.[0]
+ if (typeof raw !== "string") {
+ response.result = 400
+ response.response = null
+ response.extra = { error: "Invalid username type" }
+ break
+ }
+ const normalized = raw.trim().replace(/^@/, "").toLowerCase()
+ if (!normalized) {
+ response.result = 400
+ response.response = null
+ response.extra = { error: "No username specified" }
+ break
+ }
+ // Telegram usernames: 5–32 chars, letters/numbers/underscore
+ if (!/^[a-z0-9_]{5,32}$/.test(normalized)) {
+ response.result = 400
+ response.response = null
+ response.extra = { error: "Invalid Telegram username format" }
+ break
+ }
+ response.response = await GCR.getAccountByTelegramUsername(normalized)
break
}Note: For full correctness, also make the DB lookup case‑insensitive in GCR.getAccountByTelegramUsername() (e.g., lower(telegram_id->>'username') = :username) and normalize at write-time. I can draft that change if helpful.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case "getAccountByTelegramUsername": { | |
| const username = params[0] | |
| if (!username) { | |
| response.result = 400 | |
| response.response = "No username specified" | |
| break | |
| } | |
| response.response = await GCR.getAccountByTelegramUsername(username) | |
| break | |
| } | |
| case "getAccountByTelegramUsername": { | |
| const raw = params?.[0] | |
| if (typeof raw !== "string") { | |
| response.result = 400 | |
| response.response = null | |
| response.extra = { error: "Invalid username type" } | |
| break | |
| } | |
| const normalized = raw.trim().replace(/^@/, "").toLowerCase() | |
| if (!normalized) { | |
| response.result = 400 | |
| response.response = null | |
| response.extra = { error: "No username specified" } | |
| break | |
| } | |
| // Telegram usernames: 5–32 chars, letters/numbers/underscore | |
| if (!/^[a-z0-9_]{5,32}$/.test(normalized)) { | |
| response.result = 400 | |
| response.response = null | |
| response.extra = { error: "Invalid Telegram username format" } | |
| break | |
| } | |
| response.response = await GCR.getAccountByTelegramUsername(normalized) | |
| break | |
| } |
🤖 Prompt for AI Agents
In src/libs/network/manageGCRRoutines.ts around lines 86 to 97, normalize and
validate the Telegram username before calling GCR: accept strings with or
without a leading "@", trim whitespace, convert to lowercase, and ensure the
type and format are valid (non-empty alphanumeric/underscore per Telegram
rules); if invalid, set response.result = 400, response.response = null and
response.extra = { error: "<message>" } to match the handler pattern used on
lines 76–79; on success call GCR.getAccountByTelegramUsername() with the
normalized username (no "@") so DB lookups are consistent.
- Change brittle node_modules import to proper SDK export path - Update from node_modules/@kynesyslabs/demosdk/build/types/abstraction - Update to @kynesyslabs/demosdk/abstraction - Requires SDK v2.4.9+ with TelegramAttestationPayload exports - Resolves CodeRabbit security concern about import path brittleness 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
…tructure initialization **Root Cause**: Partial socialAccounts objects causing undefined property access **Impact**: undefined <= 0 evaluates to false, allowing negative point deductions **Changes Made**: 1. **Fix getUserPointsInternal** (lines 114-119): - Ensure individual social account properties are always initialized - Change from object fallback to property-level null coalescing - Prevents undefined socialAccount properties 2. **Fix addPointsToGCR** (lines 193-198): - Add complete breakdown structure initialization before assignment - Prevents "Cannot set property of undefined" crashes - Ensures consistent data structure across all accounts 3. **Add defensive checks** (lines 577, 657, 821): - Twitter deduction: Extract currentTwitter with null coalescing - GitHub deduction: Extract currentGithub with null coalescing - Telegram deduction: Extract currentTelegram with null coalescing - Prevents undefined <= 0 logic errors **Security Impact**: Fixes data integrity issues preventing account corruption from negative points 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
|
This PR modifies Files that will be reverted:
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (4)
src/libs/abstraction/index.ts (4)
7-8: Stop importing types from node_modules internalsImporting
GenesisBlockvia a deep path is brittle. Use the SDK’s public API (request a re-export if missing) or rely on a local runtime-narrowed type instead.
32-80: Bot authorization: robustness + caching
- Dynamic import and re-scan per call; cache the authorized set once.
- Current non-zero check can misread BigInt/hex/large values.
- Avoid coupling to exact genesis shape; guard defensively.
Apply this diff (and remove the
toIntegerimport above):+const AUTHORIZED_BOTS = new Set<string>() +let authInit = false async function checkBotAuthorization(botAddress: string): Promise<boolean> { try { - // Import Chain class and GenesisBlock type to access genesis block - const chainModule = (await import("@/libs/blockchain/chain")).default + if (!authInit) { + const chainModule = (await import("@/libs/blockchain/chain")).default - // Get the genesis block (block number 0) - const genesisBlock = - (await chainModule.getGenesisBlock()) as GenesisBlock + const genesisBlock = await chainModule.getGenesisBlock() if (!genesisBlock || !genesisBlock.content) { console.error("Genesis block not found or has no content") return false } - // REVIEW: Now properly typed - accessing genesis data from extra.genesisData - if (!genesisBlock.content.extra?.genesisData) { + if (!genesisBlock.content.extra?.genesisData) { console.error("Genesis block missing extra.genesisData") return false } - // Get balances from properly typed genesis data - const balances = genesisBlock.content.extra.genesisData.balances + const balances = genesisBlock.content.extra.genesisData.balances if (!balances || !Array.isArray(balances)) { console.error("Genesis block balances not found or invalid format") return false } - const normalizedBotAddress = botAddress.toLowerCase() - // Check if bot address exists in balances array - for (const [address, balance] of balances) { - if ( - typeof address === "string" && - address.toLowerCase() === normalizedBotAddress - ) { - // Bot found in genesis with non-zero balance - authorized - return balance !== "0" && toInteger(balance) !== 0 - } - } - // Bot address not found in genesis balances - unauthorized - return false + for (const entry of balances) { + if (!Array.isArray(entry) || entry.length < 2) continue + const [address, bal] = entry + if (typeof address !== "string") continue + const nonZero = + typeof bal === "bigint" ? bal !== 0n : + typeof bal === "number" ? bal !== 0 : + typeof bal === "string" ? bal.trim() !== "" && bal !== "0" && bal !== "0x0" : + Boolean(bal) + if (nonZero) AUTHORIZED_BOTS.add(address.toLowerCase()) + } + authInit = true + } + return AUTHORIZED_BOTS.has(botAddress.toLowerCase()) } catch (error) { console.error(`Bot authorization check failed: ${error}`) return false } }
156-167: Use canonical JSON + algorithm allowlist + key format checks
- JSON.stringify is non-canonical; use a deterministic canonical string.
- Allow only expected algorithms (e.g., ED25519).
- Normalize hex (strip 0x, lower-case) and sanity-check lengths before verify.
Apply this diff:
- // Prepare the message that was signed (stringify the payload for consistent hashing) - const messageToVerify = JSON.stringify(attestationPayload) + // Prepare canonical message for signature verification + const messageToVerify = canonicalStringify(attestationPayload) @@ - const botSignatureValid = await ucrypto.verify({ - algorithm: signature.type, + const algo = signature.type + if (!ALLOWED_TELEGRAM_SIG_ALGOS.has(algo)) { + return { success: false, message: `Unsupported signature algorithm: ${algo}` } + } + const botPk = normalizeHex(botPublicKeyHex) + if (botPk.length !== 64) { + return { success: false, message: "Invalid bot public key length" } + } + const sigHex = normalizeHex(signature.data) + const botSignatureValid = await ucrypto.verify({ + algorithm: algo, message: new TextEncoder().encode(messageToVerify), - publicKey: hexToUint8Array(botAddress), // Bot's public key - signature: hexToUint8Array(signature.data), // Bot signature + publicKey: hexToUint8Array(botPk), + signature: hexToUint8Array(sigHex), })Add these helpers/constants (outside this hunk):
const ALLOWED_TELEGRAM_SIG_ALGOS = new Set(["ED25519"]) function normalizeHex(h: string): string { const s = (h || "").toLowerCase() return s.startsWith("0x") ? s.slice(2) : s } // Deterministic, key‑sorted JSON function canonicalStringify(obj: unknown): string { if (obj === null || typeof obj !== "object") return JSON.stringify(obj) if (Array.isArray(obj)) return `[${obj.map(canonicalStringify).join(",")}]` const entries = Object.entries(obj as Record<string, unknown>) .filter(([, v]) => v !== undefined) // drop undefined like JSON.stringify .sort(([a], [b]) => a.localeCompare(b)) return `{${entries .map(([k, v]) => `${JSON.stringify(k)}:${canonicalStringify(v)}`) .join(",")}}` }
184-193: Attestation freshness / replay-resistanceNo nonce/issued_at/expiry validation; attestations can be replayed indefinitely. Enforce TTL and one-time nonce per telegram_id.
Example (add after signature checks):
const nowMs = Date.now() const issuedAtMs = Date.parse((attestationPayload as any).issued_at || "") const maxSkewMs = 2 * 60 * 1000 const maxTtlMs = 10 * 60 * 1000 if (!Number.isFinite(issuedAtMs) || issuedAtMs > nowMs + maxSkewMs || nowMs - issuedAtMs > maxTtlMs) { return { success: false, message: "Attestation expired or not yet valid" } } // TODO: persist and reject reused nonce per telegram_id (e.g., Redis/DB)
🧹 Nitpick comments (4)
src/libs/abstraction/index.ts (4)
15-31: Duplicate JSDoc blocksTwo nearly identical docblocks back-to-back. Remove the duplicate to reduce noise.
175-182: Normalize before auth checkPass a normalized bot public key/addr into
checkBotAuthorizationto avoid case/0x mismatches.Apply this diff:
- const botAuthorized = await checkBotAuthorization(botAddress) + const botAuthorized = await checkBotAuthorization(normalizeHex(botPublicKeyHex))
248-252: Type guard for proofAvoid the blind cast; validate
payload.proofis a string before parsing.Apply this diff:
- const { message, type, signature } = await instance.readData( - payload.proof as string, - ) + if (typeof payload.proof !== "string") { + return { success: false, message: "Invalid proof type" } + } + const { message, type, signature } = await instance.readData(payload.proof)
141-167: Name clarity: address vs public key
bot_addressis treated as a public key. Rename locals tobotPublicKeyHexto avoid confusion.Apply this diff:
- const { public_key: userPublicKey, bot_address: botAddress } = - attestationPayload + const { public_key: userPublicKey, bot_address: botPublicKeyHex } = + attestationPayload…and update downstream references accordingly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
package.json(1 hunks)src/libs/abstraction/index.ts(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
|
This PR modifies Files that will be reverted:
|
+ update checkBotAuthorization to use new lookup + verify user signature on telegram identity submission + remove getAccountByTelegramUsername endpoint and handler + update isFirstConnection to reduce redundant web2 queries code + upgrade sdk
|
This PR modifies Files that will be reverted:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/libs/abstraction/index.ts (1)
36-42: Efficient genesis check, but should handle edge casesThe genesis identity check is now efficiently using the shared state Set. However, the function should validate the input.
Add input validation:
async function checkBotAuthorization(botAddress: string): Promise<boolean> { + if (!botAddress || typeof botAddress !== 'string') { + return false + } if (getSharedState.genesisIdentities.has(botAddress)) { return true } return false }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
.gitignore(2 hunks)package.json(1 hunks)run(1 hunks)src/features/incentive/PointSystem.ts(6 hunks)src/index.ts(2 hunks)src/libs/abstraction/index.ts(3 hunks)src/libs/blockchain/gcr/gcr.ts(3 hunks)src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts(15 hunks)src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts(1 hunks)src/libs/blockchain/routines/loadGenesisIdentities.ts(1 hunks)src/libs/network/manageGCRRoutines.ts(1 hunks)src/utilities/sharedState.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- run
- package.json
- .gitignore
- src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts
- src/libs/network/manageGCRRoutines.ts
- src/libs/blockchain/gcr/gcr.ts
🧰 Additional context used
🧬 Code graph analysis (5)
src/index.ts (1)
src/libs/blockchain/routines/loadGenesisIdentities.ts (1)
loadGenesisIdentities(6-18)
src/libs/abstraction/index.ts (2)
src/utilities/sharedState.ts (1)
getSharedState(262-264)src/utilities/logger.ts (1)
error(125-132)
src/libs/blockchain/routines/loadGenesisIdentities.ts (1)
src/utilities/sharedState.ts (1)
getSharedState(262-264)
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (4)
src/libs/abstraction/index.ts (1)
verifyWeb2Proof(173-259)src/utilities/logger.ts (1)
error(125-132)src/libs/crypto/hashing.ts (1)
Hashing(15-26)src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts (1)
IncentiveManager(9-135)
src/features/incentive/PointSystem.ts (1)
src/libs/blockchain/gcr/gcr_routines/ensureGCRForUser.ts (1)
ensureGCRForUser(8-33)
🔇 Additional comments (12)
src/utilities/sharedState.ts (1)
113-113: LGTM!The addition of
genesisIdentitiesas a Set is appropriate for efficient membership checks during bot authorization.src/index.ts (1)
273-274: LGTM!Loading genesis identities immediately after finding the genesis block ensures the data is available before any identity verification operations.
src/features/incentive/PointSystem.ts (3)
201-207: LGTM!Good defensive programming - initializing the breakdown structure ensures safe property access in subsequent operations.
839-841: LGTM!The explicit extraction of
currentTelegramwith nullish coalescing properly handles undefined values, preventing negative point balances.
722-804: Consider adding idempotency and race condition protectionThe Telegram points awarding flow correctly verifies ownership. However, concurrent requests could bypass the "already awarded" check.
Consider using database transactions or optimistic locking to prevent race conditions:
async awardTelegramPoints( userId: string, telegramUserId: string, referralCode?: string, ): Promise<RPCResponse> { try { + const db = await Datasource.getInstance() + return await db.getDataSource().transaction(async manager => { + const gcrRepo = manager.getRepository(GCRMain) // Get user's account data from GCR to verify Telegram ownership - const account = await ensureGCRForUser(userId) + const account = await gcrRepo.findOne({ + where: { pubkey: userId }, + lock: { mode: "pessimistic_write" } + }) // ... rest of the logic + }) } catch (error) {src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (3)
702-714: Excellent refactor: Template-based query eliminates code duplicationThe generalized query template for web2 contexts is clean and maintainable, correctly handling Twitter, GitHub, Telegram, and Discord with a single code path.
1-1: Type safety concern withanycasts throughout the fileMultiple handlers use
editOperation: anywhich removes compile-time type checking. Consider using a discriminated union for GCREdit subtypes to preserve type safety.
207-243: Good: Telegram uses proper verification, but other contexts still vulnerableThe Telegram branch correctly uses
verifyWeb2Proof, but Twitter/GitHub fall back to the insecuresha256(proof) === proofHashcheck which is client-controlled.Unify all contexts to use
verifyWeb2Proof:let proofOk = false - - if (context === "telegram") { - // Telegram uses dual signature validation (user + bot signatures) - // The proof is a TelegramSignedAttestation object, not a URL - try { - // Import verifyWeb2Proof which handles telegram verification - const { verifyWeb2Proof } = await import("@/libs/abstraction") - - const verificationResult = await verifyWeb2Proof( - { - context: "telegram", - username: data.username, - userId: data.userId, - proof: data.proof, - }, - accountGCR.pubkey, // sender's ed25519 address - ) - - proofOk = verificationResult.success - - if (!proofOk) { - log.error( - `Telegram verification failed: ${verificationResult.message}`, - ) - return { - success: false, - message: verificationResult.message, - } - } - - log.info( - `Telegram identity verified: ${data.username} (${data.userId})`, - ) - } catch (error) { - log.error(`Telegram proof verification failed: ${error}`) - proofOk = false - } - } else { - // Standard SHA256 proof validation for other platforms - proofOk = Hashing.sha256(data.proof) === data.proofHash - } + try { + const { verifyWeb2Proof } = await import("@/libs/abstraction") + const verificationResult = await verifyWeb2Proof( + { + context, + username: data.username, + userId: data.userId, + proof: data.proof, + }, + accountGCR.pubkey, + ) + proofOk = verificationResult.success + if (!proofOk) { + log.error(`${context} verification failed: ${verificationResult.message}`) + return { + success: false, + message: verificationResult.message, + } + } + log.info(`${context} identity verified: ${data.username} (${data.userId})`) + } catch (error) { + log.error(`${context} proof verification failed: ${error}`) + return { + success: false, + message: `${context} verification failed`, + } + }src/libs/abstraction/index.ts (4)
8-8: Fix brittle import path from node_modules internalsImporting directly from
node_modules/.../build/typeswill break when packages are updated or in different package managers.Apply this fix:
-import type { GenesisBlock } from "node_modules/@kynesyslabs/demosdk/build/types/blockchain/blocks" // TODO Properly import from types +import type { GenesisBlock } from "@kynesyslabs/demosdk/blockchain"If the type isn't exported from the public API, request it from the SDK maintainers or define a local type until it's available.
128-128: JSON.stringify is not deterministic for signature verificationUsing
JSON.stringifyfor signature verification can fail intermittently due to non-deterministic key ordering across runtimes.Implement canonical JSON serialization:
- const messageToVerify = JSON.stringify(attestationPayload) + const messageToVerify = canonicalStringify(attestationPayload) + + function canonicalStringify(obj: unknown): string { + if (obj === null || typeof obj !== "object") return JSON.stringify(obj) + if (Array.isArray(obj)) return `[${obj.map(canonicalStringify).join(",")}]` + const entries = Object.entries(obj as Record<string, unknown>) + .sort(([a], [b]) => a.localeCompare(b)) + return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalStringify(v)}`).join(",")}}` + }
146-153: Missing replay attack protectionThe attestation lacks nonce/timestamp validation, allowing attestations to be replayed indefinitely.
Add timestamp validation and nonce tracking:
+ // Check attestation freshness (must be within 5 minutes) + if (attestationPayload.timestamp) { + const attestTime = new Date(attestationPayload.timestamp).getTime() + const now = Date.now() + if (Math.abs(now - attestTime) > 5 * 60 * 1000) { + return { + success: false, + message: "Attestation expired or clock skew detected" + } + } + } + // Check bot authorization - bot must have balance in genesis block const botAuthorized = await checkBotAuthorization(botAddress)Consider implementing a nonce store to prevent replay of valid attestations.
113-125: Critical: User signature verification uses wrong messageThe user signature is being verified against the challenge string only, not the full attestation payload. This allows replay attacks where the same challenge signature can be reused in different attestations.
The user should sign the entire attestation payload to prevent tampering:
- // INFO: Verify user signature + // INFO: Verify user signature over the full attestation payload + const userMessage = JSON.stringify({ + telegram_user_id: attestationPayload.telegram_user_id, + username: attestationPayload.username, + challenge: attestationPayload.challenge, + public_key: attestationPayload.public_key, + bot_address: attestationPayload.bot_address + }) const userSignatureValid = await ucrypto.verify({ algorithm: "ed25519", - message: new TextEncoder().encode(attestationPayload.challenge), + message: new TextEncoder().encode(userMessage), publicKey: hexToUint8Array(attestationPayload.public_key), signature: hexToUint8Array(attestationPayload.signature), })
| if (balance[1] >= MIN_BALANCE) { | ||
| identities.add(balance[0]) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
String comparison bug: balance check fails for numeric values
The balance comparison uses string comparison (balance[1] >= MIN_BALANCE), which produces incorrect results when balance[1] is a number. For example, 100 >= "1000000000000" returns false in JavaScript, excluding valid genesis identities.
Apply this fix to handle both numeric and string balance types:
- if (balance[1] >= MIN_BALANCE) {
+ const balanceValue = typeof balance[1] === 'string' ? balance[1] : String(balance[1])
+ const minBalanceValue = MIN_BALANCE
+ if (BigInt(balanceValue) >= BigInt(minBalanceValue)) {🏁 Script executed:
#!/bin/bash
# Verify the genesis.json structure to understand balance format
if [ -f data/genesis.json ]; then
head -20 data/genesis.json | jq '.balances[0:3]' 2>/dev/null || head -20 data/genesis.json
echo "---"
# Check if balances are numbers or strings
jq -r '.balances | map(.[1] | type) | unique | .[]' data/genesis.json 2>/dev/null || echo "Could not determine balance types"
fiLength of output: 564
Fix balance comparison to use BigInt
genesis.json balances are strings (e.g. "1000000000000000000"); if (balance[1] >= MIN_BALANCE) can do lexicographic/unsafe numeric comparison and exclude valid identities — compare as BigInt instead.
File: src/libs/blockchain/routines/loadGenesisIdentities.ts (around lines 11–13)
- if (balance[1] >= MIN_BALANCE) {
+ const balanceValue = typeof balance[1] === 'string' ? balance[1] : String(balance[1])
+ const minBalanceValue = MIN_BALANCE
+ if (BigInt(balanceValue) >= BigInt(minBalanceValue)) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (balance[1] >= MIN_BALANCE) { | |
| identities.add(balance[0]) | |
| } | |
| const balanceValue = typeof balance[1] === 'string' ? balance[1] : String(balance[1]) | |
| const minBalanceValue = MIN_BALANCE | |
| if (BigInt(balanceValue) >= BigInt(minBalanceValue)) { | |
| identities.add(balance[0]) | |
| } |
🤖 Prompt for AI Agents
In src/libs/blockchain/routines/loadGenesisIdentities.ts around lines 11 to 13,
the current comparison uses if (balance[1] >= MIN_BALANCE) which can perform
lexicographic/unsafe numeric comparison because balances in genesis.json are
strings; convert the balance string to a BigInt and compare against a BigInt
MIN_BALANCE (e.g. use BigInt(balance[1]) >= MIN_BALANCE or ensure MIN_BALANCE is
BigInt via BigInt(MIN_BALANCE)) so the numeric comparison is correct.
|
This PR modifies Files that will be reverted:
|
|
This PR modifies Files that will be reverted:
|
|
This PR modifies Files that will be reverted:
|
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (1)
205-246: CRITICAL: Insecure proof fallback bypasses verification for Twitter/GitHub/Discord.The sha256 fallback (lines 243-246) allows client-controlled
proofandproofHashvalues, bypassing identity verification for non-Telegram contexts. This was flagged in past reviews but remains unfixed.All contexts (twitter, github, discord, telegram) should use
verifyWeb2Proofconsistently, as shown insrc/libs/abstraction/index.ts(lines 172-258).Apply this diff to unify verification across all contexts:
- let proofOk = false - - if (context === "telegram") { - // Telegram uses dual signature validation (user + bot signatures) - // The proof is a TelegramSignedAttestation object, not a URL - try { - // Import verifyWeb2Proof which handles telegram verification - const { verifyWeb2Proof } = await import("@/libs/abstraction") - - const verificationResult = await verifyWeb2Proof( - { - context: "telegram", - username: data.username, - userId: data.userId, - proof: data.proof, - }, - accountGCR.pubkey, // sender's ed25519 address - ) - - proofOk = verificationResult.success - - if (!proofOk) { - log.error( - `Telegram verification failed: ${verificationResult.message}`, - ) - return { - success: false, - message: verificationResult.message, - } - } - - log.info( - `Telegram identity verified: ${data.username} (${data.userId})`, - ) - } catch (error) { - log.error(`Telegram proof verification failed: ${error}`) - proofOk = false - } - } else { - // Standard SHA256 proof validation for other platforms - proofOk = Hashing.sha256(data.proof) === data.proofHash - } + // Unified verification for all Web2 contexts using verifyWeb2Proof + let proofOk = false + try { + const { verifyWeb2Proof } = await import("@/libs/abstraction") + + const verificationResult = await verifyWeb2Proof( + { + context, // twitter, github, telegram, or discord + username: data.username, + userId: data.userId, + proof: data.proof, + }, + accountGCR.pubkey, + ) + + proofOk = verificationResult.success + + if (!proofOk) { + log.error( + `${context} verification failed: ${verificationResult.message}`, + ) + return { + success: false, + message: verificationResult.message, + } + } + + log.info( + `${context} identity verified: ${data.username} (${data.userId})`, + ) + } catch (error) { + log.error(`${context} proof verification failed: ${error}`) + proofOk = false + }Also update the error message on lines 248-258 to remove the Telegram-specific branch:
if (!proofOk) { return { success: false, - message: - context === "telegram" - ? "Telegram attestation validation failed" - : "Sha256 proof mismatch: Expected " + - data.proofHash + - " but got " + - Hashing.sha256(data.proof), + message: `${context} attestation validation failed`, } }
🧹 Nitpick comments (1)
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (1)
713-792: Remove commented-out code.This large block of dead code should be deleted entirely rather than commented out. Version control preserves the history if needed.
Apply this diff to remove the commented-out code:
- // if (type === "twitter") { - // /** - // * Check if this Twitter userId exists anywhere - // */ - // const result = await gcrMainRepository - // .createQueryBuilder("gcr") - // .where( - // "EXISTS (SELECT 1 FROM jsonb_array_elements(gcr.identities->'web2'->'twitter') as twitter_id WHERE twitter_id->>'userId' = :userId)", - // { - // userId: data.userId, - // }, - // ) - // .andWhere("gcr.pubkey != :currentAccount", { currentAccount }) - // .getOne() - - // /** - // * Return true if no account has this userId - // */ - // return !result - // } else if (type === "github") { - // /** - // * Check if this GitHub userId exists anywhere - // */ - // const result = await gcrMainRepository - // .createQueryBuilder("gcr") - // .where( - // "EXISTS (SELECT 1 FROM jsonb_array_elements(gcr.identities->'web2'->'github') as github_id WHERE github_id->>'userId' = :userId)", - // { - // userId: data.userId, - // }, - // ) - // .andWhere("gcr.pubkey != :currentAccount", { currentAccount }) - // .getOne() - - // /** - // * Return true if no account has this userId - // */ - // return !result - // } else if (type === "discord") { - // /** - // * Check if this Discord userId exists anywhere - // */ - // const result = await gcrMainRepository - // .createQueryBuilder("gcr") - // .where( - // "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(gcr.identities->'web2'->'discord', '[]'::jsonb)) AS discord_id WHERE discord_id->>'userId' = :userId)", - // { userId: data.userId }, - // ) - // .andWhere("gcr.pubkey != :currentAccount", { currentAccount }) - // .getOne() - - // /** - // * Return true if no account has this userId - // */ - // return !result - // } else { /** * For web3 wallets, check if this address exists in any account for this chain/subchain */ const addressToCheck = data.chain === "evm" ? data.address.toLowerCase() : data.address const result = await gcrMainRepository .createQueryBuilder("gcr") .where( "EXISTS (SELECT 1 FROM jsonb_array_elements(gcr.identities->'xm'->:chain->:subchain) as xm_id WHERE xm_id->>'address' = :address)", { chain: data.chain, subchain: data.subchain, address: addressToCheck, }, ) .andWhere("gcr.pubkey != :currentAccount", { currentAccount }) .getOne() /** * Return true if this is the first connection */ return !result - // }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
src/features/incentive/PointSystem.ts(7 hunks)src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts(15 hunks)src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts(1 hunks)src/libs/identity/tools/twitter.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/features/incentive/PointSystem.ts (1)
src/libs/blockchain/gcr/gcr_routines/ensureGCRForUser.ts (1)
ensureGCRForUser(8-33)
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (3)
src/libs/abstraction/index.ts (1)
verifyWeb2Proof(173-259)src/libs/crypto/hashing.ts (1)
Hashing(15-26)src/libs/blockchain/gcr/gcr_routines/IncentiveManager.ts (1)
IncentiveManager(9-135)
🔇 Additional comments (11)
src/libs/identity/tools/twitter.ts (1)
580-584: LGTM! Directory initialization is correctly placed.The directory creation logic is appropriately placed in the singleton initialization, uses recursive creation, and prevents write failures in methods like
getTimelineandgetFollowers.src/features/incentive/PointSystem.ts (7)
16-16: LGTM! Telegram point value is consistent with GitHub.The
LINK_TELEGRAM: 1value aligns with the existingLINK_GITHUB: 1point value, maintaining consistency across social platform linkages.
123-131: LGTM! Safe defaults prevent undefined access.Initializing
telegramwith nullish coalescing ensures safe default values and consistency with other social accounts (twitter, github, discord).
160-166: LGTM! Defensive initialization prevents structure issues.Ensuring the breakdown structure exists before assignment guards against missing keys and aligns with the REVIEW comment's intent.
548-550: LGTM! Negative-points bug is fixed.The safe default (
?? 0) prevents the undefined ≤ 0 bug flagged in past reviews, correctly short-circuiting deduction when no Twitter points exist.
606-656: LGTM! GitHub deduction mirrors the Twitter fix.The refactored logic uses a safe default for
currentGithub, preventing negative points and maintaining consistency with the Twitter deduction flow.
658-740: LGTM! Telegram award flow follows established patterns.Ownership verification, idempotency checks, and point awarding are consistent with
awardGithubPoints. The theoretical race condition noted in past reviews (two parallel requests passing the "already awarded" check before save) remains unmitigated but is acceptable for the current implementation.
755-756: LGTM! Telegram deduction uses safe default.Consistent with the Twitter and GitHub fixes, the safe default prevents undefined ≤ 0 evaluation and negative points.
src/libs/blockchain/gcr/gcr_routines/GCRIdentityRoutines.ts (3)
297-310: LGTM! Telegram incentive hook is correctly integrated.The Telegram linking flow mirrors the Twitter and GitHub patterns, with proper first-connection checks and incentive manager invocation.
355-390: LGTM! Telegram unlinking follows established patterns.Tracking
removedIdentityfor Telegram and invokingIncentiveManager.telegramUnlinkedis consistent with the GitHub unlinking flow.
699-711: LGTM! Template-based query is cleaner than per-context branches.The unified template approach using dynamic type insertion is more maintainable than the commented-out per-context branches and correctly addresses the past review's concern about Telegram support.
|
This PR modifies Files that will be reverted:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/libs/blockchain/gcr/gcr.ts (1)
643-674: Document scaffolding for phased rollout.The commented-out
getAccountByTelegramUsernamemethod appears to be scaffolding for a future phase of the Telegram integration. Add a TODO or FIXME comment explaining the rollout phase and timeline for when this will be enabled.Apply this diff to add documentation:
+// TODO: Phase 2/3 - Telegram Identity Integration +// This method will be enabled in a future phase once Telegram bot verification +// and dual-signature validation are fully deployed and tested. +// See: [link to implementation plan or phase documentation] // static async getAccountByTelegramUsername(username: string) {Alternatively, if this code is not planned for near-term use, consider removing it entirely and reintroducing it when needed to reduce maintenance burden.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/libs/blockchain/gcr/gcr.ts(3 hunks)src/libs/network/manageGCRRoutines.ts(1 hunks)src/utilities/sharedState.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/libs/network/manageGCRRoutines.ts
- src/utilities/sharedState.ts
🔇 Additional comments (1)
src/libs/blockchain/gcr/gcr.ts (1)
617-626: LGTM!The
eslint-disable-next-line prefer-constis correctly applied sincechainis reassigned when it equals"eth"(line 625). The disable is justified and appropriate.
| static async getAccountByTwitterUsername(username: string) { | ||
| const db = await Datasource.getInstance() | ||
| const gcrMainRepository = db.getDataSource().getRepository(GCRMain) | ||
|
|
||
| // INFO: Find all accounts that have the twitter identity with the given username using a jsonb query | ||
| const accounts = await gcrMainRepository | ||
| .createQueryBuilder("gcr") | ||
| .where( | ||
| "EXISTS (SELECT 1 FROM jsonb_array_elements(gcr.identities->'web2'->'twitter') as twitter_id WHERE twitter_id->>'username' = :username)", | ||
| { username }, | ||
| ) | ||
| .getMany() | ||
|
|
||
| // If no accounts found, return null | ||
| if (accounts.length === 0) { | ||
| return null | ||
| } | ||
|
|
||
| // If only one account found, return it | ||
| if (accounts.length === 1) { | ||
| return accounts[0] | ||
| } | ||
|
|
||
| // If multiple accounts found, find the one that was awarded points | ||
| // (Twitter points > 0 means the account was awarded points) | ||
| const accountWithPoints = accounts.find( | ||
| account => account.points?.breakdown?.socialAccounts?.twitter > 0, | ||
| ) | ||
|
|
||
| // Return the account with points if found, otherwise return the first account | ||
| return accountWithPoints || accounts[0] | ||
| } |
There was a problem hiding this comment.
Clarify tie-breaking logic and add error handling.
The method has two concerns:
-
Tie-breaking ambiguity: If multiple accounts have
twitter > 0,.find()returns the first match without guaranteed ordering, leading to non-deterministic results. Consider using.sort()before.find()to establish a consistent tie-breaking rule (e.g., by registration date, total points, or explicit priority). -
Missing error handling: Database errors will propagate unhandled. Wrap the query in a try-catch block to handle potential database or query failures gracefully.
Apply this diff to add error handling:
static async getAccountByTwitterUsername(username: string) {
+ try {
const db = await Datasource.getInstance()
const gcrMainRepository = db.getDataSource().getRepository(GCRMain)
// INFO: Find all accounts that have the twitter identity with the given username using a jsonb query
const accounts = await gcrMainRepository
.createQueryBuilder("gcr")
.where(
"EXISTS (SELECT 1 FROM jsonb_array_elements(gcr.identities->'web2'->'twitter') as twitter_id WHERE twitter_id->>'username' = :username)",
{ username },
)
.getMany()
// If no accounts found, return null
if (accounts.length === 0) {
return null
}
// If only one account found, return it
if (accounts.length === 1) {
return accounts[0]
}
// If multiple accounts found, find the one that was awarded points
// (Twitter points > 0 means the account was awarded points)
const accountWithPoints = accounts.find(
account => account.points?.breakdown?.socialAccounts?.twitter > 0,
)
// Return the account with points if found, otherwise return the first account
return accountWithPoints || accounts[0]
+ } catch (error) {
+ log.error("Error fetching account by Twitter username:", error)
+ return null
+ }
}For the tie-breaking logic, consider sorting accounts before applying .find():
// If multiple accounts found, find the one that was awarded points
// (Twitter points > 0 means the account was awarded points)
- const accountWithPoints = accounts.find(
+ const accountWithPoints = accounts
+ .sort((a, b) =>
+ (b.points?.breakdown?.socialAccounts?.twitter || 0) -
+ (a.points?.breakdown?.socialAccounts?.twitter || 0)
+ )
+ .find(
account => account.points?.breakdown?.socialAccounts?.twitter > 0,
)🤖 Prompt for AI Agents
In src/libs/blockchain/gcr/gcr.ts around lines 539 to 570, the method lacks
error handling and uses a non-deterministic tie-break when multiple accounts
match; wrap the DB query and subsequent logic in a try-catch that logs the error
and returns null (or rethrows if preferred), and replace the current .find()
tie-break with a deterministic sort (for example, sort by total points
descending then by registration/createdAt ascending or another explicit
priority) before selecting the preferred account so results are reproducible.
User description
I'll let CodeRabbit review and comment this but in short:
📁 Key Files Modified
🎯 Implementation Highlights
PR Type
Enhancement
Description
Complete Telegram identity integration with bot verification system
Telegram bot verification replacing manual verification approach
GCR identity linking with dual signature validation
Points system integration for Telegram account linking
Diagram Walkthrough
File Walkthrough
7 files
Add Telegram points system integrationImplement Telegram dual signature verificationAdd Telegram username account lookupIntegrate Telegram identity processingAdd Telegram linking incentive methodsAdd Telegram username RPC endpointAdd Telegram to social accounts structure2 files
Minor formatting and structure updatesFix quote consistency formatting7 files
Add aptos examples to ignore listAllow UPPER_CASE variable naming conventionAdd Claude memory preservation workflowAdd Claude PR warning workflowAdd Serena memory preservation workflowAdd Serena merge warning workflowAdd Serena project configuration3 files
Add consolidated project context documentationAdd complete Telegram system documentationUpdate implementation plan documentation1 files
Bump DemosSDK to version 2.4.7Summary by CodeRabbit
New Features
Bug Fixes
Dependency Updates
Chores
Documentation