Skip to content

Release to Staging v2.9.16 - 2026-02-12#1741

Merged
transphorm merged 14 commits intostagingfrom
release/staging-2026-02-12
Feb 13, 2026
Merged

Release to Staging v2.9.16 - 2026-02-12#1741
transphorm merged 14 commits intostagingfrom
release/staging-2026-02-12

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Feb 12, 2026

🚀 Weekly Release to Staging

Release Date: February 12, 2026
Release Branch: release/staging-2026-02-12

This automated PR promotes a snapshot of dev to staging for testing.

What's Included

All commits merged to dev up to the branch creation time.

Note: This PR uses a dedicated release branch, so new commits to dev will NOT automatically appear here.

Review Checklist

  • All CI checks pass
  • Code review completed
  • QA team notified
  • Ready to merge to staging environment

Next Steps

After merging, the staging environment will be updated. A production release PR will be created on Sunday.


This PR was automatically created by the Release Calendar workflow on February 12, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced identity card display with multiple states: empty, expired, pending, and unregistered
    • Improved KYC verification workflow with better status tracking
    • Enhanced document management interface with clearer display of document types
  • Bug Fixes

    • Improved document expiration handling and validation
    • Better permission management for identity verification
  • Chores

    • Updated app version to 2.9.16
    • Increased build configurations and bundle thresholds
    • Updated dependencies and development tools

github-actions bot and others added 13 commits February 9, 2026 15:53
* chore: bump mobile app version to 2.9.15

Update build numbers and deployment timestamps after successful deployment.

* bump android version code

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Justin Hernandez <justin.hernandez@self.xyz>
…1724)

* remove microphone permission

* bump build to 142
* udpate fastlane

* audit gems
* fix: update celo-sepolia in upgrade script to correct chain ID

* feat: add celo-sepolia deployed contracts to registry.json

* fix: upgrade script now reads version from contract directly

* fix: add library linking for kyc related contracts in upgrade script

* feat: IdentityVerificationHub v2.13.0 deployed on Celo Sepolia

- Implementation: 0x244c93516Abd58E1952452d3D8C4Ce7D454776B8
- Code-only upgrade (no new initializer), adds KYC support
- Tx: 0xf24d1c2cd4fd0055237f406a9850ce6e24f538ed09e57ff09755ed142fcc903c

* chore: yarn prettier

* Feat/new gcp verifier (#1719)

* feat: new gcp jwt verifier

* lint: contracts

---------

Co-authored-by: Nesopie <87437291+Nesopie@users.noreply.github.com>
* feat: celo mainnet deployments

* fix: upgrade script now reads version from contract directly

* fix: add library linking for kyc related contracts in upgrade script

* feat: IdentityVerificationHub v2.13.0 deployed on Celo

- Implementation: 0x0D911083b2F2236D79EF20bb58AAf6009a1220B5
- Changelog: Upgrade

* feat: update with new gcpJwtVerifier contract

* chore: yarn prettier

* Feat/new gcp verifier (#1719)

* feat: new gcp jwt verifier

* lint: contracts

---------

Co-authored-by: Nesopie <87437291+Nesopie@users.noreply.github.com>
* Sumsub: Update keychain and types

* sumsub: ProvingMachine changes - WIP

* fix: remove duplicate identifier

* update proving machine

* Refactor && Continue onchain registration if user left the app

* fix register flow

* Add hooks to KycSuccessScreen

* Integrate KycVerifiedScreen (#1686)

* Integrate KycVerifiedScreen & Fix race conditions

* yarn lint

* lint

* lint

* add mock kyc

* fix disclose flow

* yarn lint

* Feat/add kyc home screen card design (#1708)

* feat: add new designs to the kycIdCard

* refactor: Update KycIdCard design to match IdCard styling

* feat: update document cards + dev document

* feat: update empty id card for new design

* feat: update pending document card design

* feat: update expired doc + unregistered doc cards from new design

* fix: unregisted id card button links to continue registration screen

* fix: logo design on document cards

* feat: add 6 different backgrounds for ids

deterministically shows 1 of 6 backgrounds for each document | fix: fixed document designs not displaying correctly.

* chore: trigger CI rebuild

* feat: Integrate PendingIdCard to Homescreen

* fix KycIdCard.tsx

---------

Co-authored-by: seshanthS <seshanth@protonmail.com>

* lint

* fix tests

* fix: cleanup only on unmount

* coderabbit comments

* fix: cleanup unused code

* fix: edge case for German Passports with D<< nationality code

* fix tests

* review comments

* review comments

* lint

* Hide duplicated cards in Homescreen

* remove console.log

* fix patch

* remove unused vars

* agent updates

* agent feedback

* abstract colors and formatting

* agent feedback

* Regenerate Sumsub patch-package patch

* fix: handle malformed kyc payload in card background selector

* re-add for clean up

---------

Co-authored-by: Evi Nova <66773372+Tranquil-Flow@users.noreply.github.com>
Co-authored-by: Evi Nova <tranquil_flow@protonmail.com>
Co-authored-by: Justin Hernandez <justin.hernandez@self.xyz>
* agent points feedback

* agent feeddback
* refactor(app): share homescreen card dimensions

* formatting

* fix logic
* Show badge for inactive documents

* fix

* refactor to use the new flag

* add inactive check to ProveScreen

* lint

* fix for proving button not working

* use new qrHashlogic

* increase bundle size threshold to 46MB

* remove commented out line

* add kyc related changes

---------

Co-authored-by: seshanthS <seshanth@protonmail.com>
* agent feedback

* fix settings screen color
* attempt to make test less flaky

* fix flaky test again

* lint
* enable sumsub

* bugbot feedback
@transphorm
Copy link
Member

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces comprehensive KYC (Know Your Customer) verification support with new document types, WebSocket-based verification tracking, card component variants for different document states, and smart contract deployments. The KYC feature flag is removed, making this functionality always available. Version bumps: app 2.9.15→2.9.16, Android 140→142, iOS 212→213.

Changes

Cohort / File(s) Summary
Version & Dependency Updates
app/Gemfile, app/android/app/build.gradle, app/ios/OpenPassport/Info.plist, app/ios/Self.xcodeproj/project.pbxproj, app/package.json, app/version.json
Bumped fastlane to 2.232.0, added bundler-audit. Updated app version to 2.9.16 across iOS and Android with build number increments. Updated bundle size threshold from 45 to 46 MB in CI script.
Android Manifest Permissions
app/android/app/src/main/AndroidManifest.xml
Removed FOREGROUND_SERVICE_MICROPHONE permission via manifest merge rule following Sumsub SDK integration.
Card Components
app/src/components/homescreen/EmptyIdCard.tsx, app/src/components/homescreen/ExpiredIdCard.tsx, app/src/components/homescreen/UnregisteredIdCard.tsx, app/src/components/homescreen/PendingIdCard.tsx, app/src/components/homescreen/KycIdCard.tsx
Introduced 5 new document-state-specific card components with responsive layout via useCardDimensions hook. Each handles distinct document lifecycle states (empty, expired, unregistered, pending, KYC-verified).
Card Infrastructure
app/src/components/homescreen/IdCard.tsx, app/src/components/homescreen/cardStyles.ts, app/src/components/homescreen/cardSecurityBadge.ts, app/src/hooks/useCardDimensions.ts
Completely reworked IdCard with gradient backgrounds, country flags, and dynamic dimensions. Added card styling utilities and security level determination (HI-SECURITY/LOW-SECURITY based on NFC data).
Document Utilities
app/src/utils/cardBackgroundSelector.ts, app/src/utils/documents.ts, app/src/utils/documentAttributes.ts, app/src/utils/documentUtils.ts
New deterministic background color selector using hashing, inactive document detection, extended document attributes extraction for KYC data, and document type naming with KYC support.
KYC Verification Hooks
app/src/hooks/useSumsubLauncher.ts, app/src/hooks/useSumsubWebSocket.ts, app/src/hooks/usePendingKycRecovery.ts
Extended Sumsub launcher to provide userId to onSuccess callback. Added WebSocket hook managing real-time verification via Sumsub TEE. Added recovery hook for re-engaging pending verifications on app startup.
KYC Store & Navigation
app/src/stores/pendingKycStore.ts, app/src/navigation/types.ts, app/src/navigation/index.tsx
New Zustand store tracking pending KYC verifications with 48-hour timeout. Extended KYCVerified route to accept optional documentId param. Integrated pending recovery hook into navigation root.
Sumsub Integration
app/src/integrations/sumsub/sumsubService.ts, app/src/integrations/sumsub/types.ts
Removed phoneNumber parameter from fetchAccessToken. Added SumsubApplicantInfoSerialized interface for signature/applicant data serialization.
Home & Proof Screens
app/src/screens/home/HomeScreen.tsx, app/src/screens/home/PointsInfoScreen.tsx, app/src/screens/kyc/KYCVerifiedScreen.tsx, app/src/screens/kyc/KycSuccessScreen.tsx, app/src/screens/verification/ProveScreen.tsx
Refactored HomeScreen to render pending KYC cards and new card state variants. Added inactive document detection in ProveScreen with modal prompt and routing. Integrated WebSocket for KYC completion in KycSuccessScreen. Added callback lifecycle management to PointsInfoScreen.
Feature Flag Removal
app/src/stores/settingStore.ts, app/src/screens/documents/.../AadhaarUploadErrorScreen.tsx, app/src/screens/documents/.../DocumentCameraTroubleScreen.tsx, app/src/screens/documents/.../DocumentNFCTroubleScreen.tsx, app/src/screens/documents/.../RegistrationFallbackMRZScreen.tsx, app/src/screens/documents/.../RegistrationFallbackNFCScreen.tsx, app/src/screens/documents/selection/IDPickerScreen.tsx, app/src/screens/documents/selection/LogoConfirmationScreen.tsx
Removed kycEnabled feature flag from store and all conditional rendering throughout onboarding flows, making KYC entry points always visible. Updated error handling and navigation.
Document Management
app/src/screens/documents/management/ManageDocumentsScreen.tsx, app/src/screens/documents/scanning/DocumentCameraTroubleScreen.tsx, app/src/providers/passportDataProvider.tsx, app/src/proving/validateDocument.ts
Updated document display names to handle KYC type mapping (Driver's License, Passport, etc.). Extended provider to store hasExpirationDate and idType metadata. Unified CSCA key retrieval for both aadhaar and kyc categories.
Dev Tools
app/src/navigation/devTools.tsx, app/src/screens/dev/DevSettingsScreen.tsx, app/src/screens/dev/SumsubTestScreen.tsx, app/src/screens/dev/hooks/useDangerZoneActions.ts, app/src/screens/dev/sections/DangerZoneSection.tsx, app/src/screens/dev/sections/DebugShortcutsSection.tsx, app/src/screens/dev/sections/DevTogglesSection.tsx
Removed SumsubTestScreen and its routes. Removed KYC toggle from DevTogglesSection. Added danger zone handlers for clearing pending KYC verifications and removing expiration flags.
Common Library - Type System
common/src/utils/types.ts, common/src/utils/index.ts
Introduced KycData type extending BaseIDDocument. Extended IDDocument union to include KycData. Added PendingKycStatus and PendingKycVerification tracking types. Added AttestationIdHex.kyc. Updated type guards (isAadhaarDocument, isMRZDocument, new isKycDocument).
Common Library - KYC Utilities
common/src/utils/kyc/api.ts, common/src/utils/kyc/generateInputs.ts, common/src/utils/kyc/utils.ts, common/index.ts
Reorganized KYC exports. Added KYC commitment and nullifier generation. Introduced data-driven KYC input generators (generateKycRegisterInput, generateKycDiscloseInputFromData). Extended DUMMY_INPUT fixtures for OFAC scenarios.
Common Library - Passport & Proving
common/src/utils/passports/passport.ts, common/src/utils/passports/validate.ts, common/src/utils/proving.ts, common/src/utils/circuits/circuitsName.ts, common/src/utils/circuits/registerInputs.ts
Extended content hashing and nullifier generation for KYC documents. Updated signature validation to support KYC attestation. Added register_kyc and disclose_kyc circuit mappings. Introduced KYC-specific disclosure and registration input generation with SMT/OFAC tree support.
Aadhaar Mock Data
common/src/utils/aadhaar/mockData.ts
Modified QR hash computation to exclude timestamp region (bytes 9–25) before hashing, improving hash stability.
Mobile SDK - Document Processing
packages/mobile-sdk-alpha/src/documents/utils.ts, packages/mobile-sdk-alpha/src/documents/validation.ts, packages/mobile-sdk-alpha/src/types/ui.ts
Extended document storage to compute and persist hasExpirationDate flag. Added KYC document attribute extraction via deserialization. Updated DocumentMetadata interface with hasExpirationDate field.
Mobile SDK - Proving
packages/mobile-sdk-alpha/src/proving/provingMachine.ts, packages/mobile-sdk-alpha/src/stores/protocolStore.ts, packages/mobile-sdk-alpha/src/flows/onboarding/confirm-identification.tsx
Added KYC branches in circuit selection and data fetching. Implemented full fetchers for KYC protocol data (deployed circuits, public keys, identity tree, OFAC trees). Unified aadhaar/kyc handling in alternative CSCA lookup.
Mobile SDK - UI Components
packages/mobile-sdk-alpha/src/components/buttons/HeldPrimaryButtonProveScreen.tsx, packages/mobile-sdk-alpha/src/constants/colors.ts, packages/mobile-sdk-alpha/src/constants/index.ts, packages/mobile-sdk-alpha/src/flows/onboarding/id-selection-screen.tsx
Added hasCheckedForInactiveDocument tracking to button state machine. Extended color palette (amber200, amber700, gray400, red600, warmCream, yellow50). Removed showKyc prop from ID selection, making KYC always visible.
Contracts & Deployments
contracts/contracts/IdentityVerificationHubImplV2.sol, contracts/deployments/registry.json, contracts/ignition/deployments/chain-42220/deployed_addresses.json, contracts/ignition/modules/registry/deployKycRegistry.ts, contracts/ignition/modules/registry/updateRegistries.ts, contracts/tasks/upgrade/upgrade.ts
Bumped IdentityVerificationHub to version 2.13.0. Added IdentityRegistryKyc contract deployment on Celo and Celo-Sepolia. Updated registry with new contract addresses and KYC circuit verifiers (register_kyc, vc_and_disclose_kyc). Refactored upgrade task for reinitializer version tracking and multi-library linking.
Tests
app/tests/src/integrations/nfc/nfcScanner.test.ts, app/tests/src/navigation.test.tsx, app/tests/src/screens/home/PointsInfoScreen.test.tsx, app/tests/src/screens/kyc/KYCVerifiedScreen.test.tsx, app/tests/src/screens/kyc/KycSuccessScreen.test.tsx, app/tests/src/utils/cardBackgroundSelector.test.ts, app/tests/src/utils/documents.test.ts, app/tests/src/utils/points/api.test.ts, packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts, packages/mobile-sdk-alpha/tests/documents/utils.test.ts, packages/mobile-sdk-alpha/tests/proving/provingMachine.documentProcessor.test.ts
Refactored NFC scanner tests to use direct parseScanResponse import. Added comprehensive PointsInfoScreen test suite. Updated KYC screens to test event emission and WebSocket integration. Added card background selector determinism tests. Extended document storage tests for hasExpirationDate. Added AbortSignal to network error patterns.
Dependencies & Patches
patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch
Enabled Fisherman device intelligence by uncommenting the gradle dependency. Simplified documentation around VideoIdent (remains disabled).

Sequence Diagrams

sequenceDiagram
    participant App as Mobile App
    participant Store as Pending KYC Store
    participant WebSocket as Sumsub WebSocket
    participant Navigation as Navigation
    participant Success as KycSuccessScreen

    App->>Store: 1. usePendingKycRecovery on startup
    App->>Store: 2. Fetch & clean expired verifications
    Store-->>App: Return non-expired pending list
    
    App->>WebSocket: 3. Subscribe to userId for each pending
    Note over WebSocket: Maintains active socket connections
    
    WebSocket->>WebSocket: 4. Monitor verification events
    
    alt Verification Completes
        WebSocket->>Store: 5. updateVerificationStatus(userId, 'processing')
        WebSocket->>Store: 6. Deserialize applicant info → create KYC data
        WebSocket->>Store: 7. Store KYC document with documentId
        WebSocket->>Navigation: 8. Navigate to KYCVerified(documentId)
        Navigation->>Success: 9. Route to KycSuccessScreen
        Success->>WebSocket: 10. Subscribe again for status updates
    else Verification Fails
        WebSocket->>Store: 5. updateVerificationStatus(userId, 'failed', error)
        WebSocket->>Navigation: 6. Navigate to KycFailure
    else Timeout (48h)
        App->>Store: 5. removeExpiredVerifications()
        Store->>Navigation: 6. Clear from pending list
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • SELF-1938 sumsub integration #1661: Implements coordinated KYC support with matching useSumsubWebSocket and usePendingKycRecovery hooks, pending KYC store, and homescreen card components.
  • Upgrade/update celo sepolia hub kyc #1725: Directly related contract deployment changes—bumps IdentityVerificationHub to 2.13.0 and adds Celo-Sepolia deployment entries matching this PR's contract updates.
  • Show badge for inactive documents #1487: Implements the inactive-document flow with matching changes to IdCard props, hasExpirationDate metadata, isDocumentInactive utility, and ProveScreen inactive detection logic.

Suggested reviewers

  • remicolin

Poem

🚀 KYC comes alive, documents now diversify,
Cards for every state—pending, expired, verified with style,
WebSockets dance with Sumsub, tracking verifications real-time,
From feature flags to always-on, the journey feels sublime! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (85 files):

⚔️ .github/workflows/circuits.yml (content)
⚔️ .github/workflows/contracts.yml (content)
⚔️ .github/workflows/core-sdk-ci.yml (content)
⚔️ .github/workflows/qrcode-sdk-ci.yml (content)
⚔️ app/Gemfile (content)
⚔️ app/Gemfile.lock (content)
⚔️ app/android/app/build.gradle (content)
⚔️ app/android/app/src/main/AndroidManifest.xml (content)
⚔️ app/ios/OpenPassport/Info.plist (content)
⚔️ app/ios/Self.xcodeproj/project.pbxproj (content)
⚔️ app/package.json (content)
⚔️ app/scripts/bundle-analyze-ci.cjs (content)
⚔️ app/src/components/homescreen/IdCard.tsx (content)
⚔️ app/src/components/proof-request/BottomVerifyBar.tsx (content)
⚔️ app/src/hooks/useSumsubLauncher.ts (content)
⚔️ app/src/integrations/sumsub/sumsubService.ts (content)
⚔️ app/src/integrations/sumsub/types.ts (content)
⚔️ app/src/navigation/devTools.tsx (content)
⚔️ app/src/navigation/index.tsx (content)
⚔️ app/src/navigation/types.ts (content)
⚔️ app/src/providers/passportDataProvider.tsx (content)
⚔️ app/src/proving/validateDocument.ts (content)
⚔️ app/src/screens/account/settings/SettingsScreen.tsx (content)
⚔️ app/src/screens/app/LoadingScreen.tsx (content)
⚔️ app/src/screens/dev/DevSettingsScreen.tsx (content)
⚔️ app/src/screens/dev/hooks/useDangerZoneActions.ts (content)
⚔️ app/src/screens/dev/sections/DangerZoneSection.tsx (content)
⚔️ app/src/screens/dev/sections/DebugShortcutsSection.tsx (content)
⚔️ app/src/screens/dev/sections/DevTogglesSection.tsx (content)
⚔️ app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx (content)
⚔️ app/src/screens/documents/management/ManageDocumentsScreen.tsx (content)
⚔️ app/src/screens/documents/scanning/DocumentCameraTroubleScreen.tsx (content)
⚔️ app/src/screens/documents/scanning/DocumentNFCTroubleScreen.tsx (content)
⚔️ app/src/screens/documents/scanning/RegistrationFallbackMRZScreen.tsx (content)
⚔️ app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx (content)
⚔️ app/src/screens/documents/selection/IDPickerScreen.tsx (content)
⚔️ app/src/screens/documents/selection/LogoConfirmationScreen.tsx (content)
⚔️ app/src/screens/home/HomeScreen.tsx (content)
⚔️ app/src/screens/home/PointsInfoScreen.tsx (content)
⚔️ app/src/screens/kyc/KYCVerifiedScreen.tsx (content)
⚔️ app/src/screens/kyc/KycSuccessScreen.tsx (content)
⚔️ app/src/screens/verification/DocumentSelectorForProvingScreen.tsx (content)
⚔️ app/src/screens/verification/ProveScreen.tsx (content)
⚔️ app/src/screens/verification/ProvingScreenRouter.tsx (content)
⚔️ app/src/stores/settingStore.ts (content)
⚔️ app/src/utils/devUtils.ts (content)
⚔️ app/src/utils/documentAttributes.ts (content)
⚔️ app/src/utils/documentUtils.ts (content)
⚔️ app/tests/src/integrations/nfc/nfcScanner.test.ts (content)
⚔️ app/tests/src/navigation.test.tsx (content)
⚔️ app/tests/src/screens/kyc/KYCVerifiedScreen.test.tsx (content)
⚔️ app/tests/src/screens/kyc/KycSuccessScreen.test.tsx (content)
⚔️ app/tests/src/utils/points/api.test.ts (content)
⚔️ app/version.json (content)
⚔️ common/index.ts (content)
⚔️ common/src/utils/aadhaar/mockData.ts (content)
⚔️ common/src/utils/circuits/circuitsName.ts (content)
⚔️ common/src/utils/circuits/registerInputs.ts (content)
⚔️ common/src/utils/index.ts (content)
⚔️ common/src/utils/kyc/api.ts (content)
⚔️ common/src/utils/kyc/generateInputs.ts (content)
⚔️ common/src/utils/passports/passport.ts (content)
⚔️ common/src/utils/passports/validate.ts (content)
⚔️ common/src/utils/proving.ts (content)
⚔️ common/src/utils/types.ts (content)
⚔️ contracts/contracts/IdentityVerificationHubImplV2.sol (content)
⚔️ contracts/deployments/registry.json (content)
⚔️ contracts/ignition/deployments/chain-42220/deployed_addresses.json (content)
⚔️ contracts/ignition/modules/registry/deployKycRegistry.ts (content)
⚔️ contracts/ignition/modules/registry/updateRegistries.ts (content)
⚔️ contracts/tasks/upgrade/upgrade.ts (content)
⚔️ packages/mobile-sdk-alpha/src/components/buttons/HeldPrimaryButtonProveScreen.tsx (content)
⚔️ packages/mobile-sdk-alpha/src/constants/colors.ts (content)
⚔️ packages/mobile-sdk-alpha/src/constants/index.ts (content)
⚔️ packages/mobile-sdk-alpha/src/documents/utils.ts (content)
⚔️ packages/mobile-sdk-alpha/src/documents/validation.ts (content)
⚔️ packages/mobile-sdk-alpha/src/flows/onboarding/confirm-identification.tsx (content)
⚔️ packages/mobile-sdk-alpha/src/flows/onboarding/id-selection-screen.tsx (content)
⚔️ packages/mobile-sdk-alpha/src/proving/provingMachine.ts (content)
⚔️ packages/mobile-sdk-alpha/src/stores/protocolStore.ts (content)
⚔️ packages/mobile-sdk-alpha/src/types/ui.ts (content)
⚔️ packages/mobile-sdk-alpha/tests/data/country-data-sync.integration.test.ts (content)
⚔️ packages/mobile-sdk-alpha/tests/documents/utils.test.ts (content)
⚔️ packages/mobile-sdk-alpha/tests/proving/provingMachine.documentProcessor.test.ts (content)
⚔️ patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch (content)

These conflicts must be resolved before merging into staging.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly identifies this as a release to staging with the specific version (v2.9.16) and date (2026-02-12), which matches the main objective of the changeset.
Description check ✅ Passed The PR description follows the required template structure with sections for What's Included, Review Checklist, and Next Steps. It provides clear context about this being an automated weekly release, though the Tested and How to QA sections from the template are not explicitly included.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch release/staging-2026-02-12

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
packages/mobile-sdk-alpha/tests/proving/provingMachine.documentProcessor.test.ts (1)

152-158: ⚠️ Potential issue | 🟠 Major

Update buildProtocolState type to include REGISTER_KYC and add it to all test fixtures.

The production DeployedCircuits type in common/src/utils/types.ts includes REGISTER_KYC: string[], but the test helper's type definition at lines 152–158 omits it. This creates a type mismatch: only one fixture (line 654–661) includes REGISTER_KYC: [], while all others skip it. Since provingMachine.ts returns REGISTER_KYC as a valid circuit name for KYC documents, all fixtures should include this field to match the actual production shape.

Update the type definition and add REGISTER_KYC: [] to every deployedCircuits fixture in this file:

Type definition fix
   deployedCircuits: {
     REGISTER: string[];
     REGISTER_ID: string[];
     REGISTER_AADHAAR: string[];
+    REGISTER_KYC: string[];
     DSC: string[];
     DSC_ID: string[];
   };
app/src/proving/validateDocument.ts (1)

197-213: ⚠️ Potential issue | 🟠 Major

Add explicit guard to skip KYC documents or throw error in getAlternativeCSCA.

KYC documents can reach getAlternativeCSCA and will silently return an empty map (since kyc.public_keys is always null), causing isUserRegisteredWithAlternativeCSCA to perpetually return isRegistered: false. This masks incomplete KYC support—other code paths like RecoverWithPhraseScreen explicitly throw "KYC is not supported yet," but validateDocument silently degrades. Either:

  • Add an early skip/guard in checkAndUpdateRegistrationStates to exclude kyc documents
  • Throw an error in getAlternativeCSCA when kyc is passed (consistent with recovery screens)
  • Implement kyc.fetch_public_keys and populate public keys properly

Also update the JSDoc on lines 198–200 to mention KYC alongside Aadhaar, or remove it if KYC is not actually supported here.

packages/mobile-sdk-alpha/src/flows/onboarding/confirm-identification.tsx (1)

126-128: ⚠️ Potential issue | 🟡 Minor

User-facing description doesn't mention KYC documents.

Now that KYC documents can reach this confirmation screen, the copy still only references "passport, biometric ID or Aadhaar card." Users verifying via KYC will see text that doesn't describe their document type, which is confusing and could undermine trust in the flow.

Consider updating the description to be document-category-aware (e.g., accept the category as a parameter and adjust the wording), or at minimum add "KYC identity document" to the list.

contracts/tasks/upgrade/upgrade.ts (1)

106-115: ⚠️ Potential issue | 🟠 Major

Potential chainId/txServiceUrl mismatch for celo-sepolia.

celo-sepolia on Line 109 shares the celo mainnet Safe Transaction Service URL, but its chainId (Line 68) is 11142220 — different from celo mainnet's 42220. When SafeApiKit is initialized (Line 118) with chainId: 11142220n and txServiceUrl pointing to the celo mainnet service, the service may reject requests or return mismatched Safe data. Ensure the Safe Transaction Service at this URL actually supports chainId 11142220.

app/src/screens/documents/selection/LogoConfirmationScreen.tsx (1)

62-103: ⚠️ Potential issue | 🟡 Minor

Consider guarding against a missing accessToken.userId on the success path.

If fetchAccessToken() returns a token but userId is undefined/null (e.g., backend issue), line 94 would navigate to KycSuccess with an undefined userId, potentially causing downstream issues on that screen.

🛡️ Suggested defensive check
          // Verification succeeded - navigate to KycSuccessScreen
+         if (!accessToken.userId) {
+           console.error('Sumsub verification succeeded but userId is missing');
+           navigation.navigate('KycFailure', {
+             countryCode,
+             canRetry: true,
+           });
+           return;
+         }
          navigation.navigate('KycSuccess', { userId: accessToken.userId });
packages/mobile-sdk-alpha/src/proving/provingMachine.ts (1)

76-78: ⚠️ Potential issue | 🟡 Minor

getMappingKey silently returns 'DSC_ID' for KYC documents in the DSC path.

If circuitType === 'dsc' and documentCategory === 'kyc', this falls through to 'DSC_ID'. While _generateCircuitInputs correctly throws for this case (lines 124–126), resolveWebSocketUrl is called before that in initTeeConnection, which would resolve a bogus WebSocket URL. Consider throwing here explicitly, consistent with the guard in _generateCircuitInputs.

Proposed fix
   // circuitType === 'dsc'
+  if (documentCategory === 'aadhaar' || documentCategory === 'kyc') {
+    throw new Error(`DSC circuit type is not supported for ${documentCategory} documents`);
+  }
   return documentCategory === 'passport' ? 'DSC' : 'DSC_ID';
app/src/screens/kyc/KYCVerifiedScreen.tsx (1)

100-103: ⚠️ Potential issue | 🟡 Minor

Typo: "proof" should be "prove".

Line 102: "you can use to proof your identity" → "you can use to prove your identity".

✏️ Fix
         <Description style={styles.description}>
-          Next Self will generate a zk proof specifically for this device that
-          you can use to proof your identity.
+          Next Self will generate a zk proof specifically for this device that
+          you can use to prove your identity.
         </Description>
packages/mobile-sdk-alpha/src/components/buttons/HeldPrimaryButtonProveScreen.tsx (1)

17-25: ⚠️ Potential issue | 🟠 Major

Breaking change to public API: HeldPrimaryButtonProveScreenProps requires new prop.

The new required prop hasCheckedForInactiveDocument: boolean is a breaking change since HeldPrimaryButtonProveScreen is exported in the mobile-sdk-alpha public API. The only identified consumer (BottomVerifyBar) has been updated, but external consumers of the package will need to update their code. Document this breaking change in the release notes or CHANGELOG if not already done.

common/src/utils/passports/passport.ts (1)

47-72: ⚠️ Potential issue | 🟠 Major

Normalize fullName for OFAC matching per compliance rules.

Line 61 concatenates parsedApplicantInfo.fullName directly without applying required normalization. Per coding guidelines for this file path, names must be normalized using case-folding, Unicode NFKC normalization, and diacritics removal for OFAC matching. Additionally, the learnings indicate OFAC hash commitments should use the Poseidon hash function with 'ofac-v1' domain separator and per-issuer salt rather than SHA256.

The type widening to IDDocument is backward-compatible and poses no issues at call sites.

common/src/utils/circuits/registerInputs.ts (1)

105-112: ⚠️ Potential issue | 🔴 Critical

🚨 Secret and PII logged to console in generateTEEInputsAadhaarRegister.

This console.log dumps the raw secret (a cryptographic secret/private key) and the full aadhaarData object (which contains PII — name, DOB, Aadhaar number, etc.) to the console. While this appears to be pre-existing code not introduced in this PR, it's a critical security violation that should be addressed.

As per coding guidelines: "NEVER log sensitive data including PII (names, DOB, passport numbers, addresses), credentials, tokens, API keys, private keys, or session identifiers."

Proposed fix
-  console.log(
-    'publicKeys-aadhaar',
-    publicKeys,
-    'secret-aadhaar',
-    secret,
-    'aadhaarData-aadhaar',
-    aadhaarData
-  );
+  console.log('generateTEEInputsAadhaarRegister: publicKeys count:', publicKeys.length);
app/src/providers/passportDataProvider.tsx (1)

696-697: ⚠️ Potential issue | 🟠 Major

Type mismatch: IPassportContext.setData accepts PassportData but storePassportData implementation accepts IDDocument.

The interface declares setData: (data: PassportData) => Promise<void> (line 691), while the assigned implementation storePassportData accepts the broader IDDocument type (which is AadhaarData | KycData | PassportData). This breaks the type contract and prevents callers from passing KycData or AadhaarData through the context, even though the runtime function handles them.

Update the interface to match the implementation:

Proposed fix
-  setData: (data: PassportData) => Promise<void>;
+  setData: (data: IDDocument) => Promise<void>;
🤖 Fix all issues with AI agents
In `@app/src/components/homescreen/IdCard.tsx`:
- Around line 274-283: The getCountryDemonym function is incorrectly treating
any input containing '<' as German; remove the special-case check that inspects
the original code (code.includes('<')) and its D<< fallback. Instead use the
already-sanitized upperCode (from uppercasing, stripping '<', and trimming) to
look up COUNTRY_DEMONYMS and return that value or upperCode as the fallback;
keep the early guards (if !code / if !upperCode) intact and reference
getCountryDemonym and COUNTRY_DEMONYMS when making the change.
- Around line 307-317: The switch in navigateToDocumentOnboarding (inside
IdCard.tsx) omits the 'kyc' case so tapping the inactive-warning "Continue"
silently no-ops for KYC documents; add a case 'kyc' that routes to the same
onboarding flow as passport/id_card (e.g.,
navigation.navigate('DocumentOnboarding')), mirroring the fix applied in
ProveScreen, so KYC documents correctly navigate to the onboarding screen.

In `@app/src/components/homescreen/KycIdCard.tsx`:
- Around line 148-153: The getTruncatedId function is prepending "0x" to KYC
document IDs which is misleading for non-hex IDs; update getTruncatedId to stop
adding the "0x" prefix and return either a truncated plain ID (e.g.,
`${idNumber.slice(0,4)}..${idNumber.slice(-3)}`) when length > 10 or the raw
idNumber when shorter, ensuring both branches use the same no-prefix format and
preserving the existing idNumber/null checks.

In `@app/src/components/homescreen/UnregisteredIdCard.tsx`:
- Around line 131-150: The YStack used as the interactive "Complete
Registration" control in UnregisteredIdCard.tsx is missing an accessibility role
so screen readers won't announce it as a button; update the YStack that has
onPress={onRegisterPress} to include the proper accessibility prop (e.g.,
accessibilityRole="button" or role="button") and any relevant
accessibilityLabel/accessible props as needed so VoiceOver/TalkBack treat it as
a button; ensure you apply this change specifically to the YStack containing the
"Complete Registration" Text.

In `@app/src/hooks/usePendingKycRecovery.ts`:
- Around line 57-106: The effect can stall when processingWithDocument exists
but navigationRef.isReady() is false because nothing triggers the effect to
re-run; update usePendingKycRecovery so that when processingWithDocument is
found and navigationRef.isReady() is false you register a one-time listener or
short-lived polling tied to navigation readiness (or navigation container ref's
ready callback) that re-attempts the navigation logic for that specific userId,
and only mark the userId in hasAttemptedRecoveryRef after successful navigation;
specifically modify the useEffect handling of processingWithDocument to
subscribe to navigation readiness (or setInterval with clear) and on readiness
call navigationRef.navigate('KYCVerified', { documentId:
processingWithDocument.documentId }) then add processingWithDocument.userId to
hasAttemptedRecoveryRef and clean up the listener/poll to avoid memory
leaks—keep existing behavior for pending items using subscribe and ensure
cleanup in the effect return.

In `@app/src/screens/dev/DevSettingsScreen.tsx`:
- Around line 57-82: The handler handleRemoveExpirationDateFlagPress should wrap
the async work in a try/catch and provide user feedback: call
loadDocumentCatalogDirectlyFromKeychain() and find the selectedDocument inside a
try block, if no selectedDocument show an Alert explaining no document is
selected, otherwise delete selectedDocument.hasExpirationDate, await
saveDocumentCatalogDirectlyToKeychain(catalog), then show a success Alert; in
the catch show an Alert with the error message and log the error so
saveDocumentCatalogDirectlyToKeychain/loadDocumentCatalogDirectlyFromKeychain
failures aren’t silently swallowed.

In `@app/src/screens/home/HomeScreen.tsx`:
- Around line 314-320: Guard the expiration check so we only run it for
documents that can expire: read the document metadata
(metadata.hasExpirationDate) and only compute attributes.expiryDateSlice and
call checkDocumentExpiration when that flag is truthy; then return
<ExpiredIdCard key={metadata.id} /> only if metadata.hasExpirationDate is true
and checkDocumentExpiration(...) returns true. This mirrors the mobile SDK
pattern in isDocumentValidForProving() and avoids unnecessary processing for
KYC/non-expiring docs.

In `@app/src/screens/home/PointsInfoScreen.tsx`:
- Around line 99-123: The cleanup effect is re-running because `callbacks` may
be a new object each render; fix by stabilizing that reference: memoize the
lookup with useMemo(() => getModalCallbacks(callbackId), [callbackId]) and use
that memoized `callbacks` in `handleNextPress` and the useEffect so the effect
only cleans up on real `callbackId` changes/unmount, or alternatively keep
`callbackId` in the effect deps and store the latest callbacks in a ref (e.g.,
`callbacksRef.current = getModalCallbacks(callbackId)`) then read from
`callbacksRef` inside `handleNextPress` and the cleanup while removing
`callbacks` from the dependency array; update references to `callbacks` in
`handleNextPress`, the cleanup, and the unregister call
(`unregisterModalCallbacks`) accordingly.

In `@app/src/screens/kyc/KYCVerifiedScreen.tsx`:
- Around line 84-86: The catch block in KYCVerifiedScreen currently logs the raw
error object in the catch of the registration trigger; replace that direct
logging with the sanitized message from the existing sanitizeErrorMessage
utility (the same approach used in useSumsubLauncher.ts) so sensitive
SDK/document data isn't emitted. Locate the catch in the KYCVerifiedScreen
component (the block that currently does console.error('[KYCVerifiedScreen]
Failed to trigger registration:', err)) and call sanitizeErrorMessage(err) and
log its result (including the same context prefix) instead of logging err
directly.
- Around line 39-87: handleGenerateProof can be invoked multiple times because
there's no loading guard; add a local state flag (e.g., isLoading) and wire it
to the Generate Proof button's disabled/loading prop, early-return if isLoading
at the start of handleGenerateProof, set isLoading = true right after
buttonTap() and clear it in a finally block so setSelectedDocument,
loadSelectedDocument, removePendingVerification and
selfClient.emit(SdkEvents.DOCUMENT_OWNERSHIP_CONFIRMED, ...) run only once per
tap; ensure any error path also clears isLoading.

In `@app/src/screens/verification/ProveScreen.tsx`:
- Around line 123-136: The switch in navigateToDocumentOnboarding ignores
documentCategory === 'kyc', causing no navigation; add a 'kyc' case to the
switch that calls navigate(...) to the correct onboarding screen (e.g.,
navigate('DocumentOnboarding') or navigate('KycOnboarding') depending on your
routing), so documentMetadata.documentCategory === 'kyc' will route the user
when the modal "Continue" triggers navigateToDocumentOnboarding; update the
switch inside navigateToDocumentOnboarding accordingly and keep the dependency
array [navigate] unchanged.

In `@common/src/utils/circuits/registerInputs.ts`:
- Around line 274-284: In mapDisclosuresToKycFields, the
['issuing_state','ADDRESS'] pair is wrong: update the mapping array (the mapping
variable inside mapDisclosuresToKycFields) so that 'issuing_state' is not mapped
to 'ADDRESS' — either remove the ['issuing_state', ...] entry entirely or map it
to an appropriate field such as 'COUNTRY' (or add a new 'ISSUING_COUNTRY' KYC
field and its length in KYC_FIELD_LENGTHS and map to that) and ensure you adjust
KYC_FIELD_LENGTHS if you introduce 'ISSUING_COUNTRY'; do not leave issuing_state
mapped to ADDRESS.

In `@common/src/utils/passports/validate.ts`:
- Around line 290-291: The function generateKycCommitment currently lacks an
explicit return type and only returns a string inside the
isKycDocument(passportData) branch, causing an inferred string | undefined;
update generateKycCommitment to have an explicit return type of string and
handle the non-KYC case explicitly (e.g., throw a clear error or return a
defined default) so it never returns undefined, ensuring assignment to
commitment: string is safe; additionally remove the unused import
generateKycNullifier from the top of the file.

In `@contracts/deployments/registry.json`:
- Around line 155-182: The JSON contains duplicate "2.13.0" keys causing the
celo-sepolia deployment to be lost; locate the "2.13.0" entries in the registry
JSON and merge their "deployments" objects so a single "2.13.0" entry contains
both "celo-sepolia" and "celo" under the same "deployments" map (preserve each
deployment's impl, deployedAt, deployedBy, gitCommit, initializerVersion,
initializerFunction, changelog and gitTag fields).

In `@contracts/tasks/upgrade/upgrade.ts`:
- Around line 709-725: The upgrade currently skips parameterized initializers
(the block using proxyContract.interface.getFunction(initializerName) only
encodes when initFragment.inputs.length === 0), leaving initData as "0x" and
never calling required initializers; fix by either (A) supporting parameterized
initializers: read initializer arguments from the target version metadata (e.g.,
enhance getVersionInfo(contractId, newVersion) to return a field like
initializerArgs or initializerParams and then call
iface.encodeFunctionData(initializerName, initializerArgs) when
initFragment.inputs.length > 0), or (B) fail loudly: if
initFragment.inputs.length > 0 and no initializer args are present, throw or log
an error and abort the upgrade (instead of silently setting initData), making
use of symbols initializerName, initFragment, proxyContract.interface,
getVersionInfo, noNewInitializer, and initData to locate and implement the
change.
🟡 Minor comments (14)
packages/mobile-sdk-alpha/src/constants/colors.ts-9-9 (1)

9-9: ⚠️ Potential issue | 🟡 Minor

Breaking change: amber500 value updated from #F2E3C8 to #F59E0B.

This is a publicly exported constant from the SDK. Any consumer relying on the previous amber500 value will see a visual change (cream → actual amber). The old value now lives in warmCream (line 69), which is good, but downstream consumers won't automatically pick that up.

Please ensure this change is documented in release notes or a migration guide so SDK consumers can adjust. Based on learnings: "Ensure no breaking changes to public API or document them properly."

app/src/utils/devUtils.ts-135-137 (1)

135-137: ⚠️ Potential issue | 🟡 Minor

PII logged in console output.

Lines 135–137 log firstName and lastName to the console. Even though this is dev-only test data today, the pattern sets a precedent and the log could surface real-looking names in crash reporters or log aggregators on debug builds.

Redact or omit the names:

Proposed fix
-      console.log(
-        `Created ${doc.type} for ${doc.firstName} ${doc.lastName} (${doc.country})`,
-      );
+      console.log(
+        `Created ${doc.type} for ${doc.country}`,
+      );

As per coding guidelines: "NEVER log sensitive data including PII (names, DOB, passport numbers, addresses)."

contracts/deployments/registry.json-290-304 (1)

290-304: ⚠️ Potential issue | 🟡 Minor

Missing metadata for IdentityRegistryKyc v1.0.0 deployment.

gitTag (line 295) and deployedBy (line 300) are empty strings. This reduces traceability for the initial KYC registry deployment. Consider populating these before merging to staging.

packages/mobile-sdk-alpha/src/documents/utils.ts-244-244 (1)

244-244: ⚠️ Potential issue | 🟡 Minor

data field will be empty string for KYC documents.

With the new 'kyc' category flowing through storeDocumentWithDeduplication, this line casts to AadhaarData and accesses .qrData, which won't exist on a KYC document. The || '' fallback prevents a crash, but if downstream code relies on metadata.data being meaningful, KYC documents will silently have an empty string. Worth confirming this is acceptable or if KYC docs should populate a more representative value.

packages/mobile-sdk-alpha/src/flows/onboarding/confirm-identification.tsx-83-86 (1)

83-86: ⚠️ Potential issue | 🟡 Minor

Update the description text to include KYC documents.

Line 127's description mentions "passport, biometric ID or Aadhaar card" but KYC is now a valid document type flowing through this screen. The user-facing text should be updated to reflect all supported document categories.

The concern about signatureAlgorithm and curveOrExponent being undefined for KYC documents is not an issue—these fields are explicitly optional in the event payload type definition, and the listener correctly handles undefined values. This is intentional: KYC documents don't have CSCA signature metadata.

common/src/utils/kyc/api.ts-90-94 (1)

90-94: ⚠️ Potential issue | 🟡 Minor

No validation on signature format before destructuring.

deserializeSignature splits on , and destructures exactly 3 elements. If the base64-decoded string doesn't contain exactly 3 comma-separated BigInt-parseable values:

  • Fewer than 3: BigInt(undefined) throws an unhelpful TypeError
  • More than 3: silently ignores extra values

For a cryptographic primitive, a defensive check with a clear error message would be appropriate:

🛡️ Proposed defensive check
 export function deserializeSignature(signature: string): { R: Point<bigint>; s: bigint } {
-  const [Rx, Ry, s] = Buffer.from(signature, 'base64').toString('utf-8').split(',').map(BigInt);
+  const parts = Buffer.from(signature, 'base64').toString('utf-8').split(',');
+  if (parts.length !== 3) {
+    throw new Error(`Invalid signature format: expected 3 components, got ${parts.length}`);
+  }
+  const [Rx, Ry, s] = parts.map(BigInt);
   return { R: [Rx, Ry] as Point<bigint>, s };
 }
app/src/components/homescreen/cardSecurityBadge.ts-11-38 (1)

11-38: ⚠️ Potential issue | 🟡 Minor

Remove misleading docstring claim about KYC documents — they are excluded from this function and handled separately.

The getSecurityLevel() function's parameter type is PassportData | AadhaarData, which explicitly excludes KycData. In the only call site (IdCard.tsx), KYC documents are filtered out with an early return (lines 351–357) before getSecurityLevel() is called on line 393.

The docstring incorrectly claims "KYC documents -> STANDARD (always)" but this code path never executes. The 'STANDARD' value is defined in the SecurityLevel type but never returned. Update the JSDoc to document only the actual supported document types (Aadhaar and MRZ), or refactor to include KYC handling in the function if it should return 'STANDARD' for those documents.

app/src/components/homescreen/EmptyIdCard.tsx-84-104 (1)

84-104: ⚠️ Potential issue | 🟡 Minor

Duplicate text: both header title and subtitle display "NO IDENTITY FOUND".

Both the title (line 93) and the subtitle (line 102) render the identical string "NO IDENTITY FOUND". Comparing with sibling components — ExpiredIdCard uses "EXPIRED ID" / "TIME TO REGISTER A VALID COPY" and PendingIdCard uses "IDENTITY UNDER REVIEW" / "NO IDENTITY FOUND" — this looks like a copy-paste oversight. The subtitle likely needs different text (e.g., "REGISTER A NEW DOCUMENT" or similar per the Figma spec).

common/src/utils/kyc/generateInputs.ts-283-283 (1)

283-283: ⚠️ Potential issue | 🟡 Minor

Precision loss when generating mock secret key from subOrder.

Number(subOrder - 2n) truncates the BabyJubJub suborder (~2^253) to Number.MAX_SAFE_INTEGER (~2^53), so the random key space is vastly reduced. This is in a mock/test generator, so it's not a production security issue, but worth noting for correctness in test scenarios.

app/src/screens/dev/DevSettingsScreen.tsx-60-60 (1)

60-60: ⚠️ Potential issue | 🟡 Minor

Minor typo in alert message.

The message ends with ?." — there's an extra period after the question mark.

-      'Are you sure you want to remove the expiration date flag for the current (selected) document?.',
+      'Are you sure you want to remove the expiration date flag for the current (selected) document?',
common/src/utils/passports/passport.ts-58-63 (1)

58-63: ⚠️ Potential issue | 🟡 Minor

Content hash: field concatenation without delimiters risks collisions.

The stable fields are concatenated without any separator: ${fullName}${dob}${country}${idType}. Different field-value combinations can produce the same concatenated string (e.g., fullName="AB" + country="CD" vs fullName="ABC" + country="D"). Use a delimiter consistent with the other branches.

Proposed fix
-    const stableFields = `${parsedApplicantInfo.fullName}${parsedApplicantInfo.dob}${parsedApplicantInfo.country}${parsedApplicantInfo.idType}`;
+    const stableFields = `${parsedApplicantInfo.fullName}|${parsedApplicantInfo.dob}|${parsedApplicantInfo.country}|${parsedApplicantInfo.idType}`;
common/src/utils/kyc/utils.ts-14-23 (1)

14-23: ⚠️ Potential issue | 🟡 Minor

Add explicit return type annotation for clarity.

The function lacks a return type annotation and implicitly returns string | undefined. Add an explicit return type string | undefined to clarify the contract. Additionally, line 19 contains a redundant .map(Number) conversion since Array.from(...charCodeAt(0)) already produces number[].

app/src/screens/verification/ProveScreen.tsx-320-320 (1)

320-320: ⚠️ Potential issue | 🟡 Minor

Debug log leaks selectedApp object to console.

console.log('useEffect selectedApp', selectedApp) will serialize the entire selectedApp object, which may include userId, endpoint, sessionId, and other fields. Even if not strict PII, this is noisy in production and could expose session identifiers. Consider removing or gating behind a debug flag.

As per coding guidelines: "Do not include sensitive data in logs - avoid logging PII, credentials, and tokens."

app/src/screens/home/HomeScreen.tsx-292-299 (1)

292-299: ⚠️ Potential issue | 🟡 Minor

Bare return; inside .map() produces undefined — use return null; instead.

Static analysis (Biome) flagged this: a .map() callback should always return a value. The bare return; at line 298 returns undefined, which React handles gracefully but violates the linter rule and harms readability. Use return null; for consistency with the pattern at line 288.

Proposed fix
           if (
             !documentData.metadata.isRegistered &&
             activePendingVerifications.some(
               doc => doc.documentId === documentData.metadata.id,
             )
           ) {
-            return;
+            return null;
           }
🧹 Nitpick comments (25)
app/Gemfile (1)

16-16: Nice addition of bundler-audit — solid move for catching vulnerable gem versions in CI. The require: false is appropriate for a CLI-only tool.

RuboCop flags that bundler-audit should appear before nokogiri alphabetically. Low priority, but easy to fix if you want a clean lint run.

Optional: fix gem ordering
 group :development do
+  gem "bundler-audit", "~> 0.9", require: false
   gem "dotenv"
   gem "nokogiri", "~> 1.18", platform: :ruby
-  gem "bundler-audit", "~> 0.9", require: false
 end
app/src/utils/devUtils.ts (2)

83-94: clearAllDocuments is callable without a dev-mode guard.

The function is async function (not exported), so today it's only reachable via the guarded generateTestDocuments. However, it unconditionally deletes every document in the catalog—a destructive operation. Adding a lightweight IS_DEV_MODE check inside it would provide defense-in-depth against accidental future misuse.

Suggested safeguard
 async function clearAllDocuments(): Promise<void> {
+  if (!IS_DEV_MODE) {
+    return;
+  }
   const catalog = await loadDocumentCatalogDirectlyFromKeychain();

128-129: Setting mock = false on mock documents is intentional but risky.

Overriding mockDoc.mock = false makes generated test docs indistinguishable from real ones. If a tester forgets to clear them, downstream verification flows, analytics, or on-chain interactions could treat them as genuine. Consider adding a comment or a separate field (e.g., devGenerated: true) that the UI can key off for background rendering without losing traceability.

common/src/utils/proving.ts (1)

76-84: Consider a lookup map instead of deepening ternary chains.

The ternary chain for mapping circuitName → type is now 4 levels deep and will keep growing as new document types are added. A simple map would be more maintainable, self-documenting, and would let you throw on unexpected circuit names rather than silently falling back to 'disclose_id'.

♻️ Optional refactor
- const type =
-   circuitName === 'vc_and_disclose'
-     ? 'disclose'
-     : circuitName === 'vc_and_disclose_aadhaar'
-       ? 'disclose_aadhaar'
-       : circuitName === 'vc_and_disclose_kyc'
-         ? 'disclose_kyc'
-         : 'disclose_id';
+ const discloseTypeMap: Record<string, DiscloseProofType> = {
+   vc_and_disclose: 'disclose',
+   vc_and_disclose_aadhaar: 'disclose_aadhaar',
+   vc_and_disclose_kyc: 'disclose_kyc',
+   vc_and_disclose_id: 'disclose_id',
+ };
+ const type = discloseTypeMap[circuitName];
+ if (!type) {
+   throw new Error(`Unknown disclose circuit name: ${circuitName}`);
+ }

Same pattern could be applied to the register branch (lines 100-105). This would also catch mismatches at runtime instead of silently defaulting.

contracts/tasks/upgrade/upgrade.ts (2)

406-425: Libraries are redeployed on every upgrade run — consider reuse for cost savings.

Each invocation deploys all 7 libraries fresh, even if identical bytecode is already on-chain. For testnet this is fine, but on Celo mainnet this could burn non-trivial gas. Consider optionally caching library addresses (e.g., in the registry) and skipping redeployment when bytecode matches.

That said, for an operator-facing script with infrequent execution, this is pragmatic.


882-906: Dead code: the isOwnableContract block on Lines 887–906 is unreachable.

The same isOwnableContract condition is already handled at Lines 821–847, which returns early. The code here can never execute. There's also a duplicate "Step 15" comment (Lines 753 and 883).

Remove the unreachable block
-    // ========================================================================
-    // Step 15: Handle Safe proposal or owner direct execution
-    // ========================================================================
-
-    // Skip Safe proposal if this is an Ownable contract (first upgrade)
-    if (isOwnableContract) {
-      log.step("Skipping Safe proposal - owner must execute directly");
-      log.box([
-        "OWNER DIRECT EXECUTION REQUIRED",
-        "═".repeat(70),
-        "",
-        "Transaction Data:",
-        `  To: ${proxyAddress}`,
-        `  Data: ${upgradeData}`,
-        "",
-        "Execute this transaction from the owner account:",
-        `  ${currentOwner}`,
-        "",
-        "After upgrade completes, grant SECURITY_ROLE to Safe:",
-        `  ${governance.securityMultisig || "Not configured"}`,
-        "",
-        "Future upgrades can use Safe proposals.",
-      ]);
-      return;
-    }
-
-    log.step("Checking if you're a multisig signer...");
+    log.step("Checking if you're a multisig signer...");
app/src/utils/documentUtils.ts (1)

9-24: Naming collision with getDocumentTypeName in documentAttributes.ts.

There's another getDocumentTypeName function in app/src/utils/documentAttributes.ts (lines 330-341) that maps short codes ('p''Passport', 'i''ID'). Both are potentially exported from @/utils. Having two functions with the same name but different input semantics (category string vs. short code) is a maintenance hazard — callers can easily import the wrong one.

Consider renaming one to disambiguate, e.g. getDocumentCategoryDisplayName vs getDocumentTypeCodeDisplayName.

#!/bin/bash
# Check how both getDocumentTypeName functions are exported and imported
rg -n --type=ts 'getDocumentTypeName' -g '!node_modules'
app/src/screens/verification/DocumentSelectorForProvingScreen.tsx (1)

272-272: Note: Two identically-named getDocumentTypeName functions exist in the codebase.

app/src/utils/documentUtils.ts exports getDocumentTypeName(category, idType?) while app/src/utils/documentAttributes.ts exports getDocumentTypeName(documentType). The import here correctly references documentUtils.ts, but the name collision across modules could trip up future contributors.

packages/mobile-sdk-alpha/src/stores/protocolStore.ts (3)

562-583: KYC fetch_deployed_circuits and fetch_circuits_dns_mapping lack timeout protection.

These fetchers use bare fetch() without fetchWithTimeout(), unlike the passport and id_card counterparts. On degraded infrastructure, these calls can hang indefinitely. The same issue exists for aadhaar's fetchers, but since this is new code, it's worth hardening from the start.

♻️ Suggested fix for fetch_deployed_circuits (same pattern for fetch_circuits_dns_mapping)
     fetch_deployed_circuits: async (environment: 'prod' | 'stg') => {
       const url = `${environment === 'prod' ? API_URL : API_URL_STAGING}/deployed-circuits`;
-      const response = await fetch(url);
+      const response = await fetchWithTimeout(url);
       if (!response.ok) {
         throw new Error(`HTTP error fetching ${url}! status: ${response.status}`);
       }

584-586: fetch_public_keys is a no-op stub — consider adding a comment explaining why.

Setting public_keys to null without fetching is presumably intentional (KYC verification doesn't use traditional CSCA/DSC public keys), but a brief comment would prevent future contributors from treating this as unfinished work. The stale comment on line 541 (//add empty functions for kyc) also no longer reflects reality.


602-641: KYC OFAC tree fetcher also uses bare fetch() inside fetchTree — same timeout concern.

The nested fetchTree helper at lines 608-623 calls fetch() directly. For consistency and resilience, consider using fetchWithTimeout() here as well, matching the pattern in fetch_identity_tree just above.

app/src/hooks/useSumsubWebSocket.ts (1)

79-81: No connection timeout or reconnection control on the WebSocket.

io(SUMSUB_TEE_URL, { transports: ['websocket', 'polling'] }) uses socket.io defaults: auto-reconnect with exponential backoff, no hard connection timeout. On mobile, if the TEE server is unreachable, this will retry indefinitely, draining battery and resources.

Consider adding timeout and reconnectionAttempts to bound the connection lifecycle:

Suggested socket options
      const socket = io(SUMSUB_TEE_URL, {
        transports: ['websocket', 'polling'],
+       timeout: 15000,
+       reconnectionAttempts: 5,
      });
app/tests/src/screens/kyc/KYCVerifiedScreen.test.tsx (1)

173-177: Test "should use the documentId from route params" doesn't actually assert documentId usage.

This test only verifies the component renders truthy — identical to the "should render the screen without errors" test on line 115. Consider asserting something that actually depends on documentId (e.g., verifying it's passed to a downstream call or appears in rendered output) to make this test meaningful.

app/src/screens/documents/management/ManageDocumentsScreen.tsx (1)

219-222: KYC data deserialized twice per document in list rendering.

For KYC documents, deserializeApplicantInfo(data) is called once in getDisplayNamegetKYCDisplayName (line 149) and again in extractCountryFromData (line 220), both invoked per document in the .map() at line 308. The operation is lightweight (base64 decode + slicing), so this isn't a performance blocker, but extracting the deserialized result once and passing it through would be cleaner.

packages/mobile-sdk-alpha/src/proving/provingMachine.ts (1)

907-914: Duplicated circuitName resolution logic between _reconnectTeeWebSocket and initTeeConnection.

Lines 907–913 and 1348–1354 contain identical ternary chains for determining circuitName based on documentCategory. This is a maintenance risk — if a new document category is added, both must be updated in lockstep.

Consider extracting a shared helper, e.g. getDiscloseCircuitName(documentCategory).

app/src/stores/pendingKycStore.ts (1)

14-14: Open TODO: VERIFICATION_TIMEOUT_MS value needs finalization.

The comment TODO seshanth suggests this 48-hour timeout is a placeholder. Per learnings, this is UX-only (server-side is authoritative), but the value should still be intentional before shipping to staging.

Would you like me to open a tracking issue for this TODO?

common/src/utils/kyc/generateInputs.ts (1)

248-271: generateKycRegisterInput is well-structured.

Deserializes applicant info and signature, constructs the register input with proper field mapping. The async keyword is present despite no await — the function returns a plain value, so this is harmless but technically unnecessary.

app/tests/src/utils/documents.test.ts (1)

23-131: Consider adding a test case for kyc document category.

Given the broader KYC changes in this PR (new isKycDocument type guard, KYC-specific circuit paths, etc.), it would be valuable to verify isDocumentInactive behavior for documentCategory: 'kyc' as well.

app/src/hooks/usePendingKycRecovery.ts (1)

106-106: unsubscribeAll in dependency array but unused in effect body.

unsubscribeAll appears in the dependency array but is never called inside this effect. If its reference identity ever changes, it will cause an unnecessary re-execution. Consider removing it from the deps array (with an eslint-disable comment if needed) or moving it to the cleanup-only effect on line 50.

Suggested fix
-  }, [pendingVerifications, subscribe, unsubscribeAll]);
+  }, [pendingVerifications, subscribe]);
app/src/components/homescreen/KycIdCard.tsx (1)

107-111: hidden prop is accepted but unused.

The hidden prop is destructured as _hidden and never referenced. If the card should support a hidden/collapsed state (like other card variants), the rendering logic should honor it. If not needed, remove it from the interface to keep the API surface clean.

common/src/utils/kyc/utils.ts (1)

25-42: generateKycNullifier is the canonical implementation — ensure generateNullifier in passport.ts delegates here.

As noted in the passport.ts review, the KYC branch in generateNullifier (passport.ts:211-227) duplicates this exact logic. This file should be the single source of truth for KYC nullifier generation.

common/src/utils/passports/passport.ts (1)

211-227: Extract the KYC nullifier logic to use the shared utility generateKycNullifier.

The deserialization → serialization → slicing → Poseidon hashing logic in the isKycDocument branch (lines 211–227) is identical to generateKycNullifier in common/src/utils/kyc/utils.ts. Changes to either copy must be synchronized to avoid divergence.

Suggested refactor
  if (isKycDocument(passportData)) {
-   const applicantInfo = deserializeApplicantInfo(passportData.serializedApplicantInfo);
-   const serializedData = serializeKycData(applicantInfo);
-   const msgPadded = Array.from(serializedData, (x) => x.charCodeAt(0));
-   const dataPadded = msgPadded.map((x) => Number(x));
-   const idNumber = dataPadded.slice(
-     KYC_ID_NUMBER_INDEX,
-     KYC_ID_NUMBER_INDEX + KYC_ID_NUMBER_LENGTH
-   );
-   const nullifierInputs = [
-     ...'sumsub'.split('').map((x) => x.charCodeAt(0)),
-     ...idNumber,
-     ...dataPadded.slice(KYC_ID_TYPE_INDEX, KYC_ID_TYPE_INDEX + KYC_ID_TYPE_LENGTH),
-   ];
-   const nullifier = packBytesAndPoseidon(nullifierInputs);
-   return nullifier;
+   return generateKycNullifier(passportData);
  }
packages/mobile-sdk-alpha/src/documents/validation.ts (1)

109-189: Identical getKycAttributes implementation exists in both app/src/utils/documentAttributes.ts and packages/mobile-sdk-alpha/src/documents/validation.ts.

Both functions deserialize KYC data, format name/date fields, and return the same DocumentAttributes structure using identical logic. This creates a maintenance risk—any future changes to deserialization, date parsing, or field mappings must be synchronized across both files.

Consider extracting the shared logic to a common utilities module (either within an existing shared package or a new one) to establish a single source of truth.

app/src/providers/passportDataProvider.tsx (1)

898-911: Silent catch when deserializing KYC idType — consider logging a warning.

The empty catch block at line 907 swallows deserialization errors completely. While this is intentionally defensive for metadata enrichment, a console.warn would help diagnose issues with malformed KYC data during development.

Proposed fix
          } catch {
-           return {};
+           console.warn('Failed to deserialize KYC applicantInfo for idType extraction');
+           return {};
          }
app/src/components/homescreen/IdCard.tsx (1)

61-267: Consider extracting COUNTRY_DEMONYMS to a shared utility.

This 200+ line static mapping bloats the component file. Moving it to a utility (e.g., @/utils/countryDemonyms.ts) would improve readability and make it reusable across components.

Comment on lines +274 to +283
const getCountryDemonym = (code: string): string => {
if (!code) return '';
const upperCode = code.toUpperCase().replace(/</g, '').trim();
if (!upperCode) return '';
// Fallback for any remaining special codes with <
if (code.includes('<')) {
return COUNTRY_DEMONYMS['D<<'] || 'GERMAN';
}
return COUNTRY_DEMONYMS[upperCode] || upperCode;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

getCountryDemonym incorrectly treats any code containing < as German.

Line 279 checks code.includes('<') after stripping < from upperCode on line 276. Since code (the original, unstripped value) is checked, any MRZ nationality code with padding (e.g., 'GBR<<<', 'F<<') would incorrectly return 'GERMAN'. The special D<< handling at lines 366-369 (where nationality is normalized before calling this function) makes the fallback here redundant and harmful.

Proposed fix
 const getCountryDemonym = (code: string): string => {
   if (!code) return '';
   const upperCode = code.toUpperCase().replace(/</g, '').trim();
   if (!upperCode) return '';
-  // Fallback for any remaining special codes with <
-  if (code.includes('<')) {
-    return COUNTRY_DEMONYMS['D<<'] || 'GERMAN';
-  }
   return COUNTRY_DEMONYMS[upperCode] || upperCode;
 };
🤖 Prompt for AI Agents
In `@app/src/components/homescreen/IdCard.tsx` around lines 274 - 283, The
getCountryDemonym function is incorrectly treating any input containing '<' as
German; remove the special-case check that inspects the original code
(code.includes('<')) and its D<< fallback. Instead use the already-sanitized
upperCode (from uppercasing, stripping '<', and trimming) to look up
COUNTRY_DEMONYMS and return that value or upperCode as the fallback; keep the
early guards (if !code / if !upperCode) intact and reference getCountryDemonym
and COUNTRY_DEMONYMS when making the change.

Comment on lines +307 to +317
const navigateToDocumentOnboarding = useCallback(() => {
switch (idDocument?.documentCategory) {
case 'passport':
case 'id_card':
navigation.navigate('DocumentOnboarding');
break;
case 'aadhaar':
navigation.navigate('AadhaarUpload', { countryCode: 'IND' });
break;
}
}, [idDocument?.documentCategory, navigation]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing 'kyc' case in navigateToDocumentOnboarding — same issue as ProveScreen.

The switch handles passport, id_card, and aadhaar but not kyc. If a KYC document becomes inactive and the user taps the inactive warning, the modal's "Continue" callback calls navigateToDocumentOnboarding which silently no-ops for KYC.

🤖 Prompt for AI Agents
In `@app/src/components/homescreen/IdCard.tsx` around lines 307 - 317, The switch
in navigateToDocumentOnboarding (inside IdCard.tsx) omits the 'kyc' case so
tapping the inactive-warning "Continue" silently no-ops for KYC documents; add a
case 'kyc' that routes to the same onboarding flow as passport/id_card (e.g.,
navigation.navigate('DocumentOnboarding')), mirroring the fix applied in
ProveScreen, so KYC documents correctly navigate to the onboarding screen.

Comment on lines +148 to +153
const getTruncatedId = (): string => {
if (idNumber && idNumber.length > 10) {
return `0x${idNumber.slice(0, 4)}..${idNumber.slice(-3)}`;
}
return idNumber ? `0x${idNumber}` : '';
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

"0x" prefix on KYC document ID numbers is misleading.

getTruncatedId prepends "0x" to the document ID (e.g., a driver's license number). The 0x prefix conventionally denotes hexadecimal or blockchain addresses, which doesn't apply to government-issued document numbers. This will display something like 0xDL12..678 for a driver's license — confusing for users.

Proposed fix — remove 0x prefix or use a contextual prefix
   const getTruncatedId = (): string => {
     if (idNumber && idNumber.length > 10) {
-      return `0x${idNumber.slice(0, 4)}..${idNumber.slice(-3)}`;
+      return `${idNumber.slice(0, 4)}..${idNumber.slice(-3)}`;
     }
-    return idNumber ? `0x${idNumber}` : '';
+    return idNumber || '';
   };
📝 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.

Suggested change
const getTruncatedId = (): string => {
if (idNumber && idNumber.length > 10) {
return `0x${idNumber.slice(0, 4)}..${idNumber.slice(-3)}`;
}
return idNumber ? `0x${idNumber}` : '';
};
const getTruncatedId = (): string => {
if (idNumber && idNumber.length > 10) {
return `${idNumber.slice(0, 4)}..${idNumber.slice(-3)}`;
}
return idNumber || '';
};
🤖 Prompt for AI Agents
In `@app/src/components/homescreen/KycIdCard.tsx` around lines 148 - 153, The
getTruncatedId function is prepending "0x" to KYC document IDs which is
misleading for non-hex IDs; update getTruncatedId to stop adding the "0x" prefix
and return either a truncated plain ID (e.g.,
`${idNumber.slice(0,4)}..${idNumber.slice(-3)}`) when length > 10 or the raw
idNumber when shorter, ensuring both branches use the same no-prefix format and
preserving the existing idNumber/null checks.

Comment on lines +123 to +136
const navigateToDocumentOnboarding = useCallback(
(documentMetadata: DocumentMetadata) => {
switch (documentMetadata.documentCategory) {
case 'passport':
case 'id_card':
navigate('DocumentOnboarding');
break;
case 'aadhaar':
navigate('AadhaarUpload', { countryCode: 'IND' });
break;
}
},
[navigate],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing 'kyc' case in navigateToDocumentOnboarding.

The switch handles passport, id_card, and aadhaar but silently does nothing for kyc documents. If a KYC document is detected as inactive and the user taps "Continue" in the modal (line 154), the callback fires navigateToDocumentOnboarding which falls through the switch with no action — the user is stuck.

Proposed fix
   const navigateToDocumentOnboarding = useCallback(
     (documentMetadata: DocumentMetadata) => {
       switch (documentMetadata.documentCategory) {
         case 'passport':
         case 'id_card':
           navigate('DocumentOnboarding');
           break;
         case 'aadhaar':
           navigate('AadhaarUpload', { countryCode: 'IND' });
           break;
+        case 'kyc':
+          // TODO: navigate to appropriate KYC re-verification flow
+          navigate('Home' as never);
+          break;
       }
     },
     [navigate],
   );
🤖 Prompt for AI Agents
In `@app/src/screens/verification/ProveScreen.tsx` around lines 123 - 136, The
switch in navigateToDocumentOnboarding ignores documentCategory === 'kyc',
causing no navigation; add a 'kyc' case to the switch that calls navigate(...)
to the correct onboarding screen (e.g., navigate('DocumentOnboarding') or
navigate('KycOnboarding') depending on your routing), so
documentMetadata.documentCategory === 'kyc' will route the user when the modal
"Continue" triggers navigateToDocumentOnboarding; update the switch inside
navigateToDocumentOnboarding accordingly and keep the dependency array
[navigate] unchanged.

Comment on lines +274 to +284
const mapDisclosuresToKycFields = (config: SelfAppDisclosureConfig): KycField[] => {
const mapping: [keyof SelfAppDisclosureConfig, KycField][] = [
['issuing_state', 'ADDRESS'],
['nationality', 'COUNTRY'],
['name', 'FULL_NAME'],
['passport_number', 'ID_NUMBER'],
['date_of_birth', 'DOB'],
['gender', 'GENDER'],
['expiry_date', 'EXPIRY_DATE'],
];
return mapping.filter(([key]) => config[key]).map(([_, field]) => field);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find KycField enum/type definition
echo "=== Searching for KycField definition ==="
rg -n "enum\s+KycField|type\s+KycField|interface\s+KycField|KycField\s*=" --type=ts | head -20

echo ""
echo "=== Checking imports in registerInputs.ts ==="
head -40 common/src/utils/circuits/registerInputs.ts

echo ""
echo "=== Searching for ADDRESS field usage ==="
rg -n "ADDRESS" --type=ts -B2 -A2 | head -40

Repository: selfxyz/self

Length of output: 3805


🏁 Script executed:

#!/bin/bash
# Find SelfAppDisclosureConfig definition
echo "=== Searching for SelfAppDisclosureConfig ==="
rg -n "SelfAppDisclosureConfig" --type=ts -B5 -A10

echo ""
echo "=== Check context around line 276 in registerInputs.ts ==="
sed -n '260,290p' common/src/utils/circuits/registerInputs.ts

Repository: selfxyz/self

Length of output: 25803


🏁 Script executed:

#!/bin/bash
# Find KYC_FIELD_LENGTHS and all available KycFields
echo "=== KYC_FIELD_LENGTHS definition ==="
rg -n "KYC_FIELD_LENGTHS" --type=ts -A20 -B5

echo ""
echo "=== All KycField references ==="
rg -n "KYC_[A-Z_]*(?:INDEX|LENGTH|FIELD)" --type=ts | head -30

Repository: selfxyz/self

Length of output: 6473


🏁 Script executed:

#!/bin/bash
# Check how KYC fields are actually used in generateKycDiscloseInputFromData and generateKycRegisterInput
echo "=== generateKycDiscloseInputFromData usage ==="
rg -n "generateKycDiscloseInputFromData" --type=ts -B2 -A10

echo ""
echo "=== Check kyc/generateInputs.ts ==="
rg -n "issuing" --type=ts -i

Repository: selfxyz/self

Length of output: 12727


🏁 Script executed:

#!/bin/bash
# Check what data is actually in issuing_state from passport DG1
echo "=== issuing_state in dg1.ts ==="
sed -n '15,50p' common/src/utils/passports/dg1.ts

echo ""
echo "=== Check test examples of issuing_state ==="
rg "issuing_state|issuingCountry|issuingState" --type=ts -B1 -A1 | grep -E "(FRA|USA|UTO)" | head -10

Repository: selfxyz/self

Length of output: 3218


The mapping 'issuing_state' → 'ADDRESS' is semantically and typologically incorrect.

issuing_state from passport DG1 is a 3-letter country code (e.g., "FRA", "UTO") representing the document's issuing country. The ADDRESS field in KYC expects a 100-character physical address string. This mismatch will disclose the wrong data—the issuing country will be incorrectly populated as a residential address field, violating both the semantic intent and type constraints.

The codebase has no dedicated ISSUING_COUNTRY field in KYC_FIELD_LENGTHS. Either map issuing_state to an appropriate field or remove it from KYC disclosure if no suitable equivalent exists.

🤖 Prompt for AI Agents
In `@common/src/utils/circuits/registerInputs.ts` around lines 274 - 284, In
mapDisclosuresToKycFields, the ['issuing_state','ADDRESS'] pair is wrong: update
the mapping array (the mapping variable inside mapDisclosuresToKycFields) so
that 'issuing_state' is not mapped to 'ADDRESS' — either remove the
['issuing_state', ...] entry entirely or map it to an appropriate field such as
'COUNTRY' (or add a new 'ISSUING_COUNTRY' KYC field and its length in
KYC_FIELD_LENGTHS and map to that) and ensure you adjust KYC_FIELD_LENGTHS if
you introduce 'ISSUING_COUNTRY'; do not leave issuing_state mapped to ADDRESS.

Comment on lines +290 to +291
if (isKycDocument(documentData)) {
commitment = generateKycCommitment(documentData, secret);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the kyc/utils file and examine generateKycCommitment implementation
fd -t f "utils" --path "*/kyc/*" | head -20

Repository: selfxyz/self

Length of output: 289


🏁 Script executed:

# Search for generateKycCommitment definition and its return type
rg "generateKycCommitment" -A 15 -B 2 --type ts --type tsx

Repository: selfxyz/self

Length of output: 83


🏁 Script executed:

# Get the actual validate.ts file content around lines 280-320
fd "validate.ts" | xargs -I {} sed -n '280,320p' {}

Repository: selfxyz/self

Length of output: 1403


🏁 Script executed:

# Check for generateKycNullifier usage in validate.ts
rg "generateKycNullifier" -B 2 -A 2

Repository: selfxyz/self

Length of output: 761


🏁 Script executed:

# Get the full generateKycCommitment implementation in kyc/utils.ts
rg "export const generateKycCommitment" -A 20

Repository: selfxyz/self

Length of output: 1714


🏁 Script executed:

# Check the full context of isUserRegistered function
fd "validate.ts" | xargs -I {} sed -n '270,320p' {}

Repository: selfxyz/self

Length of output: 1664


🏁 Script executed:

# Search for all uses of generateKycNullifier in validate.ts
rg "generateKycNullifier" --context 5

Repository: selfxyz/self

Length of output: 1616


🏁 Script executed:

# Get the type signatures from the kyc/utils file
fd "kyc.*utils" -e ts | xargs -I {} grep -A 5 "generateKycCommitment\|generateKycNullifier" {}

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Check if generateKycCommitment has an explicit return type
rg "export const generateKycCommitment" -A 1 | head -5

Repository: selfxyz/self

Length of output: 237


🏁 Script executed:

# Check complete kyc/utils file to see all function signatures with types
fd "kyc.*utils" -e ts | xargs -I {} grep -B 2 "export const generateKyc" {}

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Verify the context: isKycDocument is checked BEFORE calling generateKycCommitment
sed -n '285,295p' common/src/utils/passports/validate.ts

Repository: selfxyz/self

Length of output: 495


🏁 Script executed:

# Check the full signature of generateKycCommitment - does it have explicit return type?
rg "export const generateKycCommitment.*=>|^}" -A 10 common/src/utils/kyc/utils.ts

Repository: selfxyz/self

Length of output: 1156


🏁 Script executed:

# Verify that commitment is always assigned before line 312 - check all branches
sed -n '279,315p' common/src/utils/passports/validate.ts

Repository: selfxyz/self

Length of output: 1318


🏁 Script executed:

# Look for any TypeScript tsconfig to understand type strictness
fd "tsconfig" -e json | xargs cat | head -50

Repository: selfxyz/self

Length of output: 1439


generateKycCommitment lacks explicit return type and implicitly returns undefined, creating a type mismatch.

The function has no explicit return type annotation. It returns commitment.toString() only inside the if (isKycDocument(passportData)) guard, implicitly returning undefined otherwise. This gives it an inferred return type of string | undefined. However, at line 290, it's assigned to commitment: string, which is a type safety violation. While the nested isKycDocument check should protect against undefined in practice, the implicit return violates the declared type contract and creates a maintenance risk if the function is called from elsewhere.

Also remove the unused import: generateKycNullifier is imported on line 32 but never used in this file.

Suggested fixes:

  1. Add explicit return type: generateKycCommitment = (...): string => { ... } and handle the non-KYC case explicitly (either throw or return a default).
  2. Remove unused import generateKycNullifier.
🤖 Prompt for AI Agents
In `@common/src/utils/passports/validate.ts` around lines 290 - 291, The function
generateKycCommitment currently lacks an explicit return type and only returns a
string inside the isKycDocument(passportData) branch, causing an inferred string
| undefined; update generateKycCommitment to have an explicit return type of
string and handle the non-KYC case explicitly (e.g., throw a clear error or
return a defined default) so it never returns undefined, ensuring assignment to
commitment: string is safe; additionally remove the unused import
generateKycNullifier from the top of the file.

Comment on lines +155 to 182
"2.13.0": {
"initializerVersion": 12,
"initializerFunction": "",
"changelog": "Upgrade to v2.13.0",
"gitTag": "identityverificationhub-v2.13.0",
"deployments": {
"celo-sepolia": {
"impl": "0x244c93516Abd58E1952452d3D8C4Ce7D454776B8",
"deployedAt": "2026-02-02T14:47:21.882Z",
"deployedBy": "0x82D8DaC3a386dec55a0a44DffBd3113e8A7D139B",
"gitCommit": "33bca485"
}
}
},
"2.13.0": {
"initializerVersion": 12,
"initializerFunction": "",
"changelog": "Upgrade to v2.13.0",
"gitTag": "identityverificationhub-v2.13.0",
"deployments": {
"celo": {
"impl": "0x0D911083b2F2236D79EF20bb58AAf6009a1220B5",
"deployedAt": "2026-02-09T11:26:30.941Z",
"deployedBy": "0xC1C860804EFdA544fe79194d1a37e60b846CEdeb",
"gitCommit": "88ae00b1"
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Duplicate "2.13.0" key — celo-sepolia deployment data will be silently lost.

JSON does not permit duplicate keys. The second "2.13.0" object (lines 169–182, celo deployment) overwrites the first (lines 155–168, celo-sepolia deployment). This means the celo-sepolia deployment record for v2.13.0 will be dropped at parse time.

Merge both network deployments under a single "2.13.0" key:

🐛 Proposed fix
-      "2.13.0": {
-        "initializerVersion": 12,
-        "initializerFunction": "",
-        "changelog": "Upgrade to v2.13.0",
-        "gitTag": "identityverificationhub-v2.13.0",
-        "deployments": {
-          "celo-sepolia": {
-            "impl": "0x244c93516Abd58E1952452d3D8C4Ce7D454776B8",
-            "deployedAt": "2026-02-02T14:47:21.882Z",
-            "deployedBy": "0x82D8DaC3a386dec55a0a44DffBd3113e8A7D139B",
-            "gitCommit": "33bca485"
-          }
-        }
-      },
-      "2.13.0": {
-        "initializerVersion": 12,
-        "initializerFunction": "",
-        "changelog": "Upgrade to v2.13.0",
-        "gitTag": "identityverificationhub-v2.13.0",
-        "deployments": {
-          "celo": {
-            "impl": "0x0D911083b2F2236D79EF20bb58AAf6009a1220B5",
-            "deployedAt": "2026-02-09T11:26:30.941Z",
-            "deployedBy": "0xC1C860804EFdA544fe79194d1a37e60b846CEdeb",
-            "gitCommit": "88ae00b1"
-          }
-        }
-      }
+      "2.13.0": {
+        "initializerVersion": 12,
+        "initializerFunction": "",
+        "changelog": "Upgrade to v2.13.0",
+        "gitTag": "identityverificationhub-v2.13.0",
+        "deployments": {
+          "celo-sepolia": {
+            "impl": "0x244c93516Abd58E1952452d3D8C4Ce7D454776B8",
+            "deployedAt": "2026-02-02T14:47:21.882Z",
+            "deployedBy": "0x82D8DaC3a386dec55a0a44DffBd3113e8A7D139B",
+            "gitCommit": "33bca485"
+          },
+          "celo": {
+            "impl": "0x0D911083b2F2236D79EF20bb58AAf6009a1220B5",
+            "deployedAt": "2026-02-09T11:26:30.941Z",
+            "deployedBy": "0xC1C860804EFdA544fe79194d1a37e60b846CEdeb",
+            "gitCommit": "88ae00b1"
+          }
+        }
+      }
🧰 Tools
🪛 Biome (2.3.14)

[error] 155-155: The key 2.13.0 was already declared.

This is where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
In `@contracts/deployments/registry.json` around lines 155 - 182, The JSON
contains duplicate "2.13.0" keys causing the celo-sepolia deployment to be lost;
locate the "2.13.0" entries in the registry JSON and merge their "deployments"
objects so a single "2.13.0" entry contains both "celo-sepolia" and "celo" under
the same "deployments" map (preserve each deployment's impl, deployedAt,
deployedBy, gitCommit, initializerVersion, initializerFunction, changelog and
gitTag fields).

Comment on lines +709 to 725
if (!noNewInitializer) {
const targetVersionInfoForInit = getVersionInfo(contractId, newVersion);
const initializerName = targetVersionInfoForInit?.initializerFunction || `initializeV${newInitializerVersion}`;

try {
const iface = proxyContract.interface;
const initFragment = iface.getFunction(initializerName);
if (initFragment && initFragment.inputs.length === 0) {
initData = iface.encodeFunctionData(initializerName, []);
log.detail("Initializer", initializerName);
}
} catch {
log.detail("Initializer", "None");
}
} catch {
log.detail("Initializer", "None");
} else {
log.detail("Initializer", "None (code-only upgrade)");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.ts" -path "*/contracts/tasks/upgrade/*" | head -5

Repository: selfxyz/self

Length of output: 237


🏁 Script executed:

wc -l contracts/tasks/upgrade/upgrade.ts 2>/dev/null || echo "File not found, searching for upgrade files..."
fd -e ts -p "*upgrade*" contracts/

Repository: selfxyz/self

Length of output: 401


🏁 Script executed:

# Check if versioned initializer functions exist in contracts
rg -n 'function\s+initializeV\d+' --type=sol 2>/dev/null | head -20

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Also check TypeScript for versioned initializers
rg -n 'initializeV\d+' contracts/tasks/upgrade/upgrade.ts | head -20

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

sed -n '625,640p' contracts/tasks/upgrade/upgrade.ts

Repository: selfxyz/self

Length of output: 540


🏁 Script executed:

sed -n '705,730p' contracts/tasks/upgrade/upgrade.ts

Repository: selfxyz/self

Length of output: 1074


🏁 Script executed:

# Search more broadly for initializer functions - any pattern
rg -n 'initializer' contracts/tasks/upgrade/upgrade.ts | head -30

Repository: selfxyz/self

Length of output: 2041


🏁 Script executed:

# Check if there are any initialize functions in contracts (not versioned)
rg -n 'function\s+initialize\s*\(' --type=sol | head -20

Repository: selfxyz/self

Length of output: 83


🏁 Script executed:

# Search for any .sol files first
find . -name "*.sol" -type f 2>/dev/null | head -10

Repository: selfxyz/self

Length of output: 585


🏁 Script executed:

# Look for initialize function definitions (not just the word initialize)
rg -n 'function.*initialize' contracts/ --type ts | head -20

Repository: selfxyz/self

Length of output: 330


🏁 Script executed:

# Check how getVersionInfo works and what it returns
rg -n 'function getVersionInfo\|const getVersionInfo' contracts/tasks/upgrade/upgrade.ts -A 10

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Check what happens when targetVersionInfoForInit is undefined
rg -n 'getVersionInfo\(' contracts/tasks/upgrade/upgrade.ts -B 2 -A 2

Repository: selfxyz/self

Length of output: 615


🏁 Script executed:

# Now search for initialize functions in .sol files
find contracts/contracts -name "*.sol" -type f -exec grep -l "function.*initialize" {} \; 2>/dev/null

Repository: selfxyz/self

Length of output: 957


🏁 Script executed:

# Check a specific contract to see what initializer it has
head -100 contracts/contracts/IdentityVerificationHubImplV1.sol | grep -n "function.*initialize\|reinitializer"

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Look for reinitializer decorator pattern (which is what's being used)
rg -n 'reinitializer' contracts/contracts/ --glob="*.sol" | head -20

Repository: selfxyz/self

Length of output: 2646


🏁 Script executed:

# Check the actual initialization logic in contracts
rg -n '@.*initializer|function.*initialize' contracts/contracts/ --glob="*.sol" -A 3 | head -40

Repository: selfxyz/self

Length of output: 3682


🏁 Script executed:

# Check the getVersionInfo implementation
rg -n 'function getVersionInfo\|const getVersionInfo\|export.*getVersionInfo' contracts/tasks/upgrade/ -A 15 | head -50

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Also verify: when does getVersionInfo return undefined?
rg -n 'getVersionInfo' contracts/tasks/upgrade/upgrade.ts -B 1 -A 1 | head -30

Repository: selfxyz/self

Length of output: 779


🏁 Script executed:

# Check if the fallback initializeV is ever actually used
rg -n 'initializeV' contracts/tasks/upgrade/upgrade.ts

Repository: selfxyz/self

Length of output: 177


🏁 Script executed:

# Verify the actual contract structure - do we have functions with parameters?
rg -n 'function initialize\(' contracts/contracts/ --glob="*.sol" -A 2 | head -30

Repository: selfxyz/self

Length of output: 2540


🏁 Script executed:

# Check what getVersionInfo is imported from and how it works
rg -n 'getVersionInfo' contracts/tasks/upgrade/utils.ts -B 2 -A 15

Repository: selfxyz/self

Length of output: 759


🏁 Script executed:

# Understand the registry structure and when getVersionInfo returns undefined
rg -n 'export.*getVersionInfo\|function.*getVersionInfo' contracts/tasks/upgrade/ -A 10

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Check the test cases to see how initializer encoding is tested
fd -e ts -path "*/test*" -path "*upgrade*" | head -5

Repository: selfxyz/self

Length of output: 224


🏁 Script executed:

# Look at the actual initialization call site - is initData used?
sed -n '700,740p' contracts/tasks/upgrade/upgrade.ts

Repository: selfxyz/self

Length of output: 1668


🏁 Script executed:

# Check if there are any tests for the initializer encoding logic
find contracts -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "initializeV\|encodeFunctionData" 2>/dev/null | head -5

Repository: selfxyz/self

Length of output: 248


🏁 Script executed:

# Look at how initialization is actually done - do we pass parameters?
rg -n 'upgradeToAndCall\|initialize.*address' contracts/tasks/upgrade/upgrade.ts -B 2 -A 2 | head -30

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Check what happens with contracts that have initialize(address)
rg -n 'initialize\(' contracts/contracts/registry/IdentityRegistryImplV1.sol -B 2 -A 5

Repository: selfxyz/self

Length of output: 340


🏁 Script executed:

# Verify if the code ever handles parameterized initializers anywhere
rg -n 'inputs.length\|\.inputs\|parameters' contracts/tasks/upgrade/upgrade.ts | head -20

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Check how the initializer is supposed to get its parameters (if any)
rg -n 'encodeFunctionData.*initialize' contracts/tasks/upgrade/upgrade.ts -B 5 -A 5

Repository: selfxyz/self

Length of output: 487


🏁 Script executed:

# Verify: do ANY real contracts have parameterized initialize?
rg -n 'function initialize\(' contracts/contracts/ --glob="*.sol" | grep -v 'reinitializer\|initializer)'

Repository: selfxyz/self

Length of output: 1056


🏁 Script executed:

# What happens if initialize has parameters - can we pass them somewhere?
sed -n '630,650p' contracts/tasks/upgrade/upgrade.ts

Repository: selfxyz/self

Length of output: 902


🏁 Script executed:

# Check the full context of how initialization data gets passed
sed -n '706,745p' contracts/tasks/upgrade/upgrade.ts

Repository: selfxyz/self

Length of output: 1519


🏁 Script executed:

# Check which contracts are actually deployed and upgraded
rg -n 'contractId.*IdentityRegistry\|contractId.*IdentityVerification' contracts/tasks/upgrade/upgrade.ts | head -10

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# See what contracts have NO-parameter initialize functions
rg -n 'function initialize\(\)\s*external' contracts/contracts/ --glob="*.sol"

Repository: selfxyz/self

Length of output: 476


🏁 Script executed:

# Check if parameterized initializers cause an issue in the upgrade flow
rg -n 'initializerVersion\|newInitializerVersion' contracts/tasks/upgrade/upgrade.ts | head -20

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

# Verify: when getVersionInfo returns null, does fallback initializeVN actually work?
sed -n '709,730p' contracts/tasks/upgrade/upgrade.ts | cat -n

Repository: selfxyz/self

Length of output: 1076


🏁 Script executed:

# Critical check: do parameterized initializers get called anywhere?
rg -n 'initialize.*address.*external.*initializer' contracts/contracts/ --glob="*.sol" -B 2 -A 5 | head -30

Repository: selfxyz/self

Length of output: 2728


🏁 Script executed:

# Are there ANY tests that verify initializer is called with parameters?
rg -n 'encodeFunctionData.*initialize' contracts/test/ --glob="*.ts" -B 3 -A 3

Repository: selfxyz/self

Length of output: 10461


🏁 Script executed:

# What's the actual behavior when initialize has parameters?
sed -n '714,722p' contracts/tasks/upgrade/upgrade.ts

Repository: selfxyz/self

Length of output: 430


Parameterized initializers are silently skipped during upgrades.

The encoding logic on lines 716–721 only encodes initializers that have zero parameters: if (initFragment && initFragment.inputs.length === 0). Contracts like IdentityRegistryKycImplV1 have parameterized initializers—e.g., initialize(address hubAddress, address pcr0ManagerAddress)—which do not match this condition. When upgrading such contracts, the code silently skips encoding the initializer, leaving initData as "0x" and never calling the initializer on the upgraded contract. No error is logged; the upgrade completes with an uninitialized contract state.

Either:

  1. Support parameterized initializers by storing and retrieving init parameters from the registry, or
  2. Fail loudly if the initializer has required parameters rather than silently skipping it.
🤖 Prompt for AI Agents
In `@contracts/tasks/upgrade/upgrade.ts` around lines 709 - 725, The upgrade
currently skips parameterized initializers (the block using
proxyContract.interface.getFunction(initializerName) only encodes when
initFragment.inputs.length === 0), leaving initData as "0x" and never calling
required initializers; fix by either (A) supporting parameterized initializers:
read initializer arguments from the target version metadata (e.g., enhance
getVersionInfo(contractId, newVersion) to return a field like initializerArgs or
initializerParams and then call iface.encodeFunctionData(initializerName,
initializerArgs) when initFragment.inputs.length > 0), or (B) fail loudly: if
initFragment.inputs.length > 0 and no initializer args are present, throw or log
an error and abort the upgrade (instead of silently setting initData), making
use of symbols initializerName, initFragment, proxyContract.interface,
getVersionInfo, noNewInitializer, and initData to locate and implement the
change.

@transphorm transphorm merged commit c823901 into staging Feb 13, 2026
51 of 52 checks passed
@transphorm transphorm deleted the release/staging-2026-02-12 branch February 13, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants