mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -05:00
Merge pull request #1669 from selfxyz/release/staging-2026-01-29
chore: fix circuits tests in staging
This commit is contained in:
47
.github/actions/cache-core-sdk-build/action.yml
vendored
Normal file
47
.github/actions/cache-core-sdk-build/action.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Cache Core SDK Build
|
||||
description: Cache core SDK build artifacts (common, sdk/core)
|
||||
|
||||
inputs:
|
||||
mode:
|
||||
description: "save or restore"
|
||||
required: true
|
||||
cache-version:
|
||||
description: Cache version string
|
||||
required: false
|
||||
default: v1
|
||||
fail-on-cache-miss:
|
||||
description: Fail if cache not found (restore mode only)
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: Whether cache was hit
|
||||
value: ${{ steps.restore.outputs.cache-hit }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: restore
|
||||
if: inputs.mode == 'restore'
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/core/dist
|
||||
node_modules
|
||||
sdk/core/node_modules
|
||||
common/node_modules
|
||||
key: core-sdk-build-${{ inputs.cache-version }}-${{ github.sha }}
|
||||
fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }}
|
||||
- id: save
|
||||
if: inputs.mode == 'save'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/core/dist
|
||||
node_modules
|
||||
sdk/core/node_modules
|
||||
common/node_modules
|
||||
key: core-sdk-build-${{ inputs.cache-version }}-${{ github.sha }}
|
||||
47
.github/actions/cache-mobile-sdk-build/action.yml
vendored
Normal file
47
.github/actions/cache-mobile-sdk-build/action.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Cache Mobile SDK Build
|
||||
description: Cache mobile SDK build artifacts (common, mobile-sdk-alpha)
|
||||
|
||||
inputs:
|
||||
mode:
|
||||
description: "save or restore"
|
||||
required: true
|
||||
cache-version:
|
||||
description: Cache version string
|
||||
required: false
|
||||
default: v1
|
||||
fail-on-cache-miss:
|
||||
description: Fail if cache not found (restore mode only)
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: Whether cache was hit
|
||||
value: ${{ steps.restore.outputs.cache-hit }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: restore
|
||||
if: inputs.mode == 'restore'
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
packages/mobile-sdk-alpha/dist
|
||||
node_modules
|
||||
packages/mobile-sdk-alpha/node_modules
|
||||
common/node_modules
|
||||
key: mobile-sdk-alpha-build-${{ inputs.cache-version }}-${{ github.sha }}
|
||||
fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }}
|
||||
- id: save
|
||||
if: inputs.mode == 'save'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
packages/mobile-sdk-alpha/dist
|
||||
node_modules
|
||||
packages/mobile-sdk-alpha/node_modules
|
||||
common/node_modules
|
||||
key: mobile-sdk-alpha-build-${{ inputs.cache-version }}-${{ github.sha }}
|
||||
43
.github/actions/cache-sdk-build/action.yml
vendored
Normal file
43
.github/actions/cache-sdk-build/action.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Cache SDK Build
|
||||
description: Cache SDK build artifacts (common, sdk-common, qrcode)
|
||||
|
||||
inputs:
|
||||
mode:
|
||||
description: "save or restore"
|
||||
required: true
|
||||
cache-version:
|
||||
description: Cache version string
|
||||
required: false
|
||||
default: v1
|
||||
fail-on-cache-miss:
|
||||
description: Fail if cache not found (restore mode only)
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: Whether cache was hit
|
||||
value: ${{ steps.restore.outputs.cache-hit }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: restore
|
||||
if: inputs.mode == 'restore'
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ inputs.cache-version }}-${{ github.sha }}
|
||||
fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }}
|
||||
- id: save
|
||||
if: inputs.mode == 'save'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ inputs.cache-version }}-${{ github.sha }}
|
||||
1
.github/workflows/circuits-build.yml
vendored
1
.github/workflows/circuits-build.yml
vendored
@@ -33,6 +33,7 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ["128ram"]
|
||||
timeout-minutes: 720 # 12 hours
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
65
.github/workflows/core-sdk-ci.yml
vendored
65
.github/workflows/core-sdk-ci.yml
vendored
@@ -56,15 +56,10 @@ jobs:
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/core build
|
||||
- name: Cache build artifacts
|
||||
uses: actions/cache/save@v4
|
||||
uses: ./.github/actions/cache-core-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/core/dist
|
||||
node_modules
|
||||
sdk/core/node_modules
|
||||
common/node_modules
|
||||
key: core-sdk-build-${{ github.sha }}
|
||||
mode: save
|
||||
cache-version: v1
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -80,19 +75,19 @@ jobs:
|
||||
corepack prepare yarn@4.12.0 --activate
|
||||
- name: Restore build artifacts
|
||||
id: build-cache
|
||||
uses: actions/cache/restore@v4
|
||||
uses: ./.github/actions/cache-core-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/core/dist
|
||||
node_modules
|
||||
sdk/core/node_modules
|
||||
common/node_modules
|
||||
key: core-sdk-build-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: v1
|
||||
fail-on-cache-miss: false
|
||||
- name: Install Dependencies
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/core build
|
||||
- name: Run linter
|
||||
run: yarn workspace @selfxyz/core lint
|
||||
|
||||
@@ -110,19 +105,19 @@ jobs:
|
||||
corepack prepare yarn@4.12.0 --activate
|
||||
- name: Restore build artifacts
|
||||
id: build-cache
|
||||
uses: actions/cache/restore@v4
|
||||
uses: ./.github/actions/cache-core-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/core/dist
|
||||
node_modules
|
||||
sdk/core/node_modules
|
||||
common/node_modules
|
||||
key: core-sdk-build-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: v1
|
||||
fail-on-cache-miss: false
|
||||
- name: Install Dependencies
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/core build
|
||||
- name: Type checking
|
||||
run: yarn workspace @selfxyz/core types
|
||||
|
||||
@@ -140,18 +135,18 @@ jobs:
|
||||
corepack prepare yarn@4.12.0 --activate
|
||||
- name: Restore build artifacts
|
||||
id: build-cache
|
||||
uses: actions/cache/restore@v4
|
||||
uses: ./.github/actions/cache-core-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/core/dist
|
||||
node_modules
|
||||
sdk/core/node_modules
|
||||
common/node_modules
|
||||
key: core-sdk-build-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: v1
|
||||
fail-on-cache-miss: false
|
||||
- name: Install Dependencies
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/core build
|
||||
- name: Run tests
|
||||
run: yarn workspace @selfxyz/core test
|
||||
|
||||
87
.github/workflows/mobile-sdk-ci.yml
vendored
87
.github/workflows/mobile-sdk-ci.yml
vendored
@@ -21,15 +21,10 @@ jobs:
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/mobile-sdk-alpha build
|
||||
- name: Cache build artifacts
|
||||
uses: actions/cache/save@v4
|
||||
uses: ./.github/actions/cache-mobile-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
packages/mobile-sdk-alpha/dist
|
||||
node_modules
|
||||
packages/mobile-sdk-alpha/node_modules
|
||||
common/node_modules
|
||||
key: mobile-sdk-alpha-build-${{ github.sha }}
|
||||
mode: save
|
||||
cache-version: v1
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -39,16 +34,17 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore build artifacts
|
||||
uses: actions/cache/restore@v4
|
||||
id: restore-build
|
||||
uses: ./.github/actions/cache-mobile-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
packages/mobile-sdk-alpha/dist
|
||||
node_modules
|
||||
packages/mobile-sdk-alpha/node_modules
|
||||
common/node_modules
|
||||
key: mobile-sdk-alpha-build-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: v1
|
||||
fail-on-cache-miss: false
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.restore-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/mobile-sdk-alpha build
|
||||
- name: Run linter
|
||||
run: yarn workspace @selfxyz/mobile-sdk-alpha lint
|
||||
|
||||
@@ -60,16 +56,17 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore build artifacts
|
||||
uses: actions/cache/restore@v4
|
||||
id: restore-build
|
||||
uses: ./.github/actions/cache-mobile-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
packages/mobile-sdk-alpha/dist
|
||||
node_modules
|
||||
packages/mobile-sdk-alpha/node_modules
|
||||
common/node_modules
|
||||
key: mobile-sdk-alpha-build-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: v1
|
||||
fail-on-cache-miss: false
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.restore-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/mobile-sdk-alpha build
|
||||
- name: Check Prettier formatting
|
||||
run: yarn workspace @selfxyz/mobile-sdk-alpha prettier --check .
|
||||
|
||||
@@ -81,16 +78,17 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore build artifacts
|
||||
uses: actions/cache/restore@v4
|
||||
id: restore-build
|
||||
uses: ./.github/actions/cache-mobile-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
packages/mobile-sdk-alpha/dist
|
||||
node_modules
|
||||
packages/mobile-sdk-alpha/node_modules
|
||||
common/node_modules
|
||||
key: mobile-sdk-alpha-build-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: v1
|
||||
fail-on-cache-miss: false
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.restore-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/mobile-sdk-alpha build
|
||||
- name: Type checking
|
||||
run: yarn workspace @selfxyz/mobile-sdk-alpha types
|
||||
|
||||
@@ -102,15 +100,16 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore build artifacts
|
||||
uses: actions/cache/restore@v4
|
||||
id: restore-build
|
||||
uses: ./.github/actions/cache-mobile-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
packages/mobile-sdk-alpha/dist
|
||||
node_modules
|
||||
packages/mobile-sdk-alpha/node_modules
|
||||
common/node_modules
|
||||
key: mobile-sdk-alpha-build-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: v1
|
||||
fail-on-cache-miss: false
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.restore-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/mobile-sdk-alpha build
|
||||
- name: Run tests
|
||||
run: yarn workspace @selfxyz/mobile-sdk-alpha test
|
||||
|
||||
99
.github/workflows/qrcode-sdk-ci.yml
vendored
99
.github/workflows/qrcode-sdk-ci.yml
vendored
@@ -80,16 +80,14 @@ jobs:
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v4
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
path: |
|
||||
.yarn/cache
|
||||
node_modules
|
||||
sdk/qrcode/node_modules
|
||||
common/node_modules
|
||||
key: ${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-
|
||||
cache-version: ${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}
|
||||
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
@@ -102,13 +100,10 @@ jobs:
|
||||
yarn workspace @selfxyz/qrcode build
|
||||
|
||||
- name: Cache build artifacts
|
||||
uses: actions/cache/save@v4
|
||||
uses: ./.github/actions/cache-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
|
||||
mode: save
|
||||
cache-version: ${{ env.GH_SDK_CACHE_VERSION }}
|
||||
|
||||
# Quality checks job
|
||||
quality-checks:
|
||||
@@ -141,29 +136,32 @@ jobs:
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v4
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
path: |
|
||||
.yarn/cache
|
||||
node_modules
|
||||
sdk/qrcode/node_modules
|
||||
common/node_modules
|
||||
key: ${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-
|
||||
cache-version: ${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}
|
||||
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
- name: Restore build artifacts
|
||||
uses: actions/cache/restore@v4
|
||||
id: restore-build
|
||||
uses: ./.github/actions/cache-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: ${{ env.GH_SDK_CACHE_VERSION }}
|
||||
fail-on-cache-miss: false
|
||||
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.restore-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/sdk-common build
|
||||
yarn workspace @selfxyz/qrcode build
|
||||
|
||||
- name: Run linter
|
||||
run: yarn workspace @selfxyz/qrcode lint:imports:check
|
||||
@@ -210,29 +208,32 @@ jobs:
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v4
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
path: |
|
||||
.yarn/cache
|
||||
node_modules
|
||||
sdk/qrcode/node_modules
|
||||
common/node_modules
|
||||
key: ${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-
|
||||
cache-version: ${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}
|
||||
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
- name: Restore build artifacts
|
||||
uses: actions/cache/restore@v4
|
||||
id: restore-build
|
||||
uses: ./.github/actions/cache-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: ${{ env.GH_SDK_CACHE_VERSION }}
|
||||
fail-on-cache-miss: false
|
||||
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.restore-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/sdk-common build
|
||||
yarn workspace @selfxyz/qrcode build
|
||||
|
||||
- name: Verify build artifacts
|
||||
run: |
|
||||
@@ -273,29 +274,41 @@ jobs:
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v4
|
||||
uses: ./.github/actions/cache-yarn
|
||||
with:
|
||||
path: |
|
||||
.yarn/cache
|
||||
node_modules
|
||||
sdk/qrcode/node_modules
|
||||
common/node_modules
|
||||
key: ${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-
|
||||
cache-version: ${{ env.GH_YARN_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}
|
||||
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
- name: Restore build artifacts
|
||||
uses: actions/cache/restore@v4
|
||||
id: restore-build
|
||||
uses: ./.github/actions/cache-sdk-build
|
||||
with:
|
||||
path: |
|
||||
common/dist
|
||||
sdk/sdk-common/dist
|
||||
sdk/qrcode/dist
|
||||
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
|
||||
fail-on-cache-miss: true
|
||||
mode: restore
|
||||
cache-version: ${{ env.GH_SDK_CACHE_VERSION }}
|
||||
fail-on-cache-miss: false
|
||||
|
||||
- name: Build dependencies (fallback on cache miss)
|
||||
if: steps.restore-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
yarn workspace @selfxyz/common build
|
||||
yarn workspace @selfxyz/sdk-common build
|
||||
yarn workspace @selfxyz/qrcode build
|
||||
|
||||
- name: Check for nested require() in tests
|
||||
run: |
|
||||
# Check SDK tests for nested require patterns that cause OOM
|
||||
if grep -rE "require\(['\"]react(-native)?['\"])" sdk/qrcode/src/ sdk/qrcode/tests/ 2>/dev/null; then
|
||||
echo "❌ Found nested require() patterns that cause OOM in CI"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ No nested require() patterns found"
|
||||
|
||||
- name: Run tests
|
||||
run: yarn workspace @selfxyz/qrcode test
|
||||
|
||||
@@ -11,9 +11,9 @@ import Foundation
|
||||
import React
|
||||
#if !E2E_TESTING
|
||||
import NFCPassportReader
|
||||
import Mixpanel
|
||||
#endif
|
||||
import Security
|
||||
import Mixpanel
|
||||
import Sentry
|
||||
|
||||
#if !E2E_TESTING
|
||||
|
||||
@@ -53,8 +53,6 @@ end
|
||||
target "Self" do
|
||||
# Native module exclusion for E2E testing is handled in react-native.config.cjs
|
||||
config = use_native_modules!
|
||||
|
||||
use_frameworks!
|
||||
# Skip NFCPassportReader for e2e testing to avoid build issues
|
||||
unless ENV["E2E_TESTING"] == "1"
|
||||
# Check if we're running in a selfxyz repo or an external fork
|
||||
@@ -79,10 +77,13 @@ target "Self" do
|
||||
pod "NFCPassportReader", git: nfc_repo_url, commit: "9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b"
|
||||
end
|
||||
|
||||
# Explicitly declare Mixpanel to ensure it's available even in E2E builds
|
||||
# (NFCPassportReader also includes Mixpanel, but is skipped during E2E testing)
|
||||
pod "Mixpanel-swift", :modular_headers => true
|
||||
|
||||
pod "QKMRZScanner"
|
||||
pod "lottie-ios"
|
||||
pod "SwiftQRScanner", :git => "https://github.com/vinodiOS/SwiftQRScanner"
|
||||
pod "Mixpanel-swift", "~> 5.0.0"
|
||||
# RNReactNativeHapticFeedback is handled by autolinking
|
||||
|
||||
use_react_native!(
|
||||
|
||||
@@ -1558,7 +1558,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-netinfo (11.4.1):
|
||||
- React-Core
|
||||
- react-native-nfc-manager (3.16.3):
|
||||
- react-native-nfc-manager (3.17.2):
|
||||
- React-Core
|
||||
- react-native-passkey (3.3.1):
|
||||
- DoubleConversion
|
||||
@@ -1984,7 +1984,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNDeviceInfo (14.1.1):
|
||||
- RNDeviceInfo (15.0.1):
|
||||
- React-Core
|
||||
- RNFBApp (19.3.0):
|
||||
- Firebase/CoreOnly (= 10.24.0)
|
||||
@@ -2042,7 +2042,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNLocalize (3.6.0):
|
||||
- RNLocalize (3.6.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2129,7 +2129,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNSentry (7.0.1):
|
||||
- RNSentry (7.0.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2152,7 +2152,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- Sentry/HybridSDK (= 8.53.2)
|
||||
- Yoga
|
||||
- RNSVG (15.15.0):
|
||||
- RNSVG (15.12.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2172,9 +2172,9 @@ PODS:
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- RNSVG/common (= 15.15.0)
|
||||
- RNSVG/common (= 15.12.1)
|
||||
- Yoga
|
||||
- RNSVG/common (15.15.0):
|
||||
- RNSVG/common (15.12.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2223,7 +2223,7 @@ DEPENDENCIES:
|
||||
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
|
||||
- lottie-ios
|
||||
- lottie-react-native (from `../node_modules/lottie-react-native`)
|
||||
- Mixpanel-swift (~> 5.0.0)
|
||||
- Mixpanel-swift
|
||||
- "NFCPassportReader (from `git@github.com:selfxyz/NFCPassportReader.git`, commit `9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b`)"
|
||||
- QKMRZScanner
|
||||
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||
@@ -2626,7 +2626,7 @@ SPEC CHECKSUMS:
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-mobilesdk-module: 4770cb45fdd19dc4eed04615f0fcdab013b3dfe2
|
||||
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
|
||||
react-native-nfc-manager: 66a00e5ddab9704efebe19d605b1b8afb0bb1bd7
|
||||
react-native-nfc-manager: c8891e460b4943b695d63f7f4effc6345bbefc83
|
||||
react-native-passkey: 8853c3c635164864da68a6dbbcec7148506c3bcf
|
||||
react-native-safe-area-context: a7aad44fe544b55e2369a3086e16a01be60ce398
|
||||
react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed
|
||||
@@ -2660,18 +2660,18 @@ SPEC CHECKSUMS:
|
||||
ReactCommon: b2eb96a61b826ff327a773a74357b302cf6da678
|
||||
RNCAsyncStorage: 0003b916f1a69fe2d20b7910e0d08da3d32c7bd6
|
||||
RNCClipboard: a4827e134e4774e97fa86f7f986694dd89320f13
|
||||
RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c
|
||||
RNDeviceInfo: 36d7f232bfe7c9b5c494cb7793230424ed32c388
|
||||
RNFBApp: 4097f75673f8b42a7cd1ba17e6ea85a94b45e4d1
|
||||
RNFBMessaging: 92325b0d5619ac90ef023a23cfd16fd3b91d0a88
|
||||
RNFBRemoteConfig: a569bacaa410acfcaba769370e53a787f80fd13b
|
||||
RNGestureHandler: a63b531307e5b2e6ea21d053a1a7ad4cf9695c57
|
||||
RNInAppBrowser: 6d3eb68d471b9834335c664704719b8be1bfdb20
|
||||
RNKeychain: 471ceef8c13f15a5534c3cd2674dbbd9d0680e52
|
||||
RNLocalize: 4f5e4a46d2bccd04ccb96721e438dcb9de17c2e0
|
||||
RNLocalize: 2760999d1e2fc95fb7b7e5247631feb3c08156dc
|
||||
RNReactNativeHapticFeedback: e526ac4a7ca9fb23c7843ea4fd7d823166054c73
|
||||
RNScreens: 806e1449a8ec63c2a4e4cf8a63cc80203ccda9b8
|
||||
RNSentry: 6ad982be2c8e32dab912afb4132b6a0d88484ea0
|
||||
RNSVG: 39476f26bbbe72ffe6194c6fc8f6acd588087957
|
||||
RNSentry: f79dd124cc49088445c16d23955860dd0d1db6f3
|
||||
RNSVG: 0c1fc3e7b147949dc15644845e9124947ac8c9bb
|
||||
segment-analytics-react-native: 0eae155b0e9fa560fa6b17d78941df64537c35b7
|
||||
Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
@@ -2681,6 +2681,6 @@ SPEC CHECKSUMS:
|
||||
TwilioVideo: 9f51085d4e4fb3aff8e168b8215b31cb0f486a2f
|
||||
Yoga: 1259c7a8cbaccf7b4c3ddf8ee36ca11be9dee407
|
||||
|
||||
PODFILE CHECKSUM: f03c12b5d96fb6e22afe20fba517840fef44e76f
|
||||
PODFILE CHECKSUM: 8cfd84595c3e826f512f5c545d232a27f1850ff3
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
|
||||
|
||||
import Foundation
|
||||
import Mixpanel
|
||||
|
||||
#if !E2E_TESTING
|
||||
import Mixpanel
|
||||
import NFCPassportReader
|
||||
|
||||
public class SelfAnalytics: Analytics {
|
||||
|
||||
@@ -72,6 +72,9 @@ const config = {
|
||||
new RegExp(
|
||||
'packages/mobile-sdk-alpha/node_modules/react-native-svg(/|$)',
|
||||
),
|
||||
new RegExp(
|
||||
'packages/mobile-sdk-alpha/node_modules/react-native-webview(/|$)',
|
||||
),
|
||||
new RegExp('packages/mobile-sdk-demo/node_modules/react(/|$)'),
|
||||
new RegExp('packages/mobile-sdk-demo/node_modules/react-dom(/|$)'),
|
||||
new RegExp('packages/mobile-sdk-demo/node_modules/react-native(/|$)'),
|
||||
|
||||
@@ -85,13 +85,13 @@
|
||||
"react-native-webview": "13.16.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.3",
|
||||
"@ethersproject/shims": "^5.7.0",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@ethersproject/shims": "^5.8.0",
|
||||
"@noble/hashes": "^1.5.0",
|
||||
"@openpassport/zk-kit-imt": "^0.0.5",
|
||||
"@openpassport/zk-kit-lean-imt": "^0.0.6",
|
||||
"@openpassport/zk-kit-smt": "^0.0.1",
|
||||
"@peculiar/x509": "^1.13.0",
|
||||
"@peculiar/x509": "^1.14.3",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@react-native-clipboard/clipboard": "1.16.3",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
@@ -108,7 +108,7 @@
|
||||
"@selfxyz/euclid": "^0.6.1",
|
||||
"@selfxyz/mobile-sdk-alpha": "workspace:^",
|
||||
"@sentry/react": "^9.32.0",
|
||||
"@sentry/react-native": "7.0.1",
|
||||
"@sentry/react-native": "7.0.0",
|
||||
"@stablelib/cbor": "^2.0.1",
|
||||
"@sumsub/react-native-mobilesdk-module": "1.40.2",
|
||||
"@tamagui/animations-css": "1.126.14",
|
||||
@@ -122,7 +122,7 @@
|
||||
"@turnkey/react-native-wallet-kit": "1.1.5",
|
||||
"@walletconnect/react-native-compat": "^2.23.0",
|
||||
"@xstate/react": "^5.0.3",
|
||||
"asn1js": "^3.0.6",
|
||||
"asn1js": "^3.0.7",
|
||||
"axios": "^1.13.2",
|
||||
"buffer": "^6.0.3",
|
||||
"country-emoji": "^1.5.6",
|
||||
@@ -136,8 +136,8 @@
|
||||
"js-sha512": "^0.9.0",
|
||||
"lottie-react": "^2.4.1",
|
||||
"lottie-react-native": "7.2.2",
|
||||
"node-forge": "^1.3.1",
|
||||
"pkijs": "^3.2.5",
|
||||
"node-forge": "^1.3.3",
|
||||
"pkijs": "^3.3.3",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -147,7 +147,7 @@
|
||||
"react-native-blur-effect": "^1.1.3",
|
||||
"react-native-check-version": "^1.3.0",
|
||||
"react-native-cloud-storage": "^2.2.2",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-device-info": "^15.0.1",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-native-edge-to-edge": "^1.7.0",
|
||||
"react-native-gesture-handler": "2.19.0",
|
||||
@@ -156,18 +156,18 @@
|
||||
"react-native-inappbrowser-reborn": "^3.7.0",
|
||||
"react-native-keychain": "^10.0.0",
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-localize": "^3.5.2",
|
||||
"react-native-logs": "^5.3.0",
|
||||
"react-native-nfc-manager": "3.16.3",
|
||||
"react-native-passkey": "^3.3.1",
|
||||
"react-native-localize": "^3.6.1",
|
||||
"react-native-logs": "^5.5.0",
|
||||
"react-native-nfc-manager": "3.17.2",
|
||||
"react-native-passkey": "^3.3.2",
|
||||
"react-native-passport-reader": "1.0.3",
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"react-native-safe-area-context": "^5.6.2",
|
||||
"react-native-screens": "4.15.3",
|
||||
"react-native-sqlite-storage": "^6.0.1",
|
||||
"react-native-svg": "15.15.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-svg-web": "1.0.9",
|
||||
"react-native-url-polyfill": "^3.0.0",
|
||||
"react-native-web": "^0.19.0",
|
||||
"react-native-web": "^0.21.2",
|
||||
"react-native-webview": "^13.16.0",
|
||||
"react-qr-barcode-scanner": "^2.1.8",
|
||||
"socket.io-client": "^4.8.3",
|
||||
@@ -177,14 +177,14 @@
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/plugin-syntax-flow": "^7.27.1",
|
||||
"@babel/plugin-transform-classes": "^7.27.1",
|
||||
"@babel/core": "^7.28.6",
|
||||
"@babel/plugin-syntax-flow": "^7.28.6",
|
||||
"@babel/plugin-transform-classes": "^7.28.6",
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.27.1",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/plugin-transform-private-methods": "^7.28.6",
|
||||
"@babel/preset-env": "^7.28.6",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@react-native-community/cli": "^16.0.3",
|
||||
"@react-native/babel-preset": "0.76.9",
|
||||
"@react-native/eslint-config": "0.76.9",
|
||||
@@ -210,11 +210,11 @@
|
||||
"@types/react-test-renderer": "^18",
|
||||
"@typescript-eslint/eslint-plugin": "^8.39.0",
|
||||
"@typescript-eslint/parser": "^8.39.0",
|
||||
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||
"@vitejs/plugin-react-swc": "^4.2.2",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"constants-browserify": "^1.0.0",
|
||||
"dompurify": "^3.2.6",
|
||||
"dompurify": "^3.3.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-typescript": "^3.7.0",
|
||||
@@ -229,14 +229,15 @@
|
||||
"jest": "^30.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^3.5.3",
|
||||
"react-native-svg-transformer": "^1.5.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-native-svg-transformer": "^1.5.2",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"rollup-plugin-visualizer": "^6.0.3",
|
||||
"rollup-plugin-visualizer": "^6.0.5",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ts-morph": "^22.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-svgr": "^4.5.0"
|
||||
},
|
||||
"packageManager": "yarn@4.12.0",
|
||||
|
||||
27
app/src/hooks/useErrorInjection.ts
Normal file
27
app/src/hooks/useErrorInjection.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import type { InjectedErrorType } from '@/stores/errorInjectionStore';
|
||||
import { useErrorInjectionStore } from '@/stores/errorInjectionStore';
|
||||
import { IS_DEV_MODE } from '@/utils/devUtils';
|
||||
|
||||
/**
|
||||
* Hook for checking if a specific error should be injected
|
||||
* Only active in dev mode
|
||||
*/
|
||||
export function useErrorInjection() {
|
||||
const injectedErrors = useErrorInjectionStore(state => state.injectedErrors);
|
||||
|
||||
const shouldInjectError = useCallback(
|
||||
(errorType: InjectedErrorType): boolean => {
|
||||
if (!IS_DEV_MODE) return false;
|
||||
return injectedErrors.includes(errorType);
|
||||
},
|
||||
[injectedErrors],
|
||||
);
|
||||
|
||||
return { shouldInjectError };
|
||||
}
|
||||
127
app/src/hooks/useSumsubLauncher.ts
Normal file
127
app/src/hooks/useSumsubLauncher.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import { sanitizeErrorMessage } from '@selfxyz/mobile-sdk-alpha';
|
||||
|
||||
import { fetchAccessToken, launchSumsub } from '@/integrations/sumsub';
|
||||
import type { SumsubResult } from '@/integrations/sumsub/types';
|
||||
import type { RootStackParamList } from '@/navigation';
|
||||
|
||||
export type FallbackErrorSource =
|
||||
| 'mrz_scan_failed'
|
||||
| 'nfc_scan_failed'
|
||||
| 'sumsub_initialization'
|
||||
| 'sumsub_verification';
|
||||
|
||||
export interface UseSumsubLauncherOptions {
|
||||
/**
|
||||
* Country code for the user's document
|
||||
*/
|
||||
countryCode: string;
|
||||
/**
|
||||
* Error source to track where the Sumsub launch was initiated from
|
||||
*/
|
||||
errorSource: FallbackErrorSource;
|
||||
/**
|
||||
* Optional callback to handle successful verification
|
||||
*/
|
||||
onSuccess?: (result: SumsubResult) => void | Promise<void>;
|
||||
/**
|
||||
* Optional callback to handle user cancellation
|
||||
*/
|
||||
onCancel?: () => void | Promise<void>;
|
||||
/**
|
||||
* Optional callback to handle verification failure
|
||||
*/
|
||||
onError?: (error: unknown, result?: SumsubResult) => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook for launching Sumsub verification with consistent error handling.
|
||||
*
|
||||
* Abstracts the common pattern of:
|
||||
* 1. Fetching access token
|
||||
* 2. Launching Sumsub SDK
|
||||
* 3. Handling errors by navigating to fallback screen
|
||||
* 4. Managing loading state
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { launchSumsubVerification, isLoading } = useSumsubLauncher({
|
||||
* countryCode: 'US',
|
||||
* errorSource: 'nfc_scan_failed',
|
||||
* });
|
||||
*
|
||||
* <Button onPress={launchSumsubVerification} disabled={isLoading}>
|
||||
* {isLoading ? 'Loading...' : 'Try Alternative Verification'}
|
||||
* </Button>
|
||||
* ```
|
||||
*/
|
||||
export const useSumsubLauncher = (options: UseSumsubLauncherOptions) => {
|
||||
const { countryCode, errorSource, onSuccess, onCancel, onError } = options;
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const launchSumsubVerification = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const accessToken = await fetchAccessToken();
|
||||
const result = await launchSumsub({ accessToken: accessToken.token });
|
||||
|
||||
// Handle user cancellation
|
||||
if (!result.success && result.status === 'Interrupted') {
|
||||
await onCancel?.();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle verification failure
|
||||
if (!result.success) {
|
||||
const error = result.errorMsg || result.errorType || 'Unknown error';
|
||||
const safeError = sanitizeErrorMessage(error);
|
||||
console.error('Sumsub verification failed:', safeError);
|
||||
|
||||
// Call custom error handler if provided, otherwise navigate to fallback screen
|
||||
if (onError) {
|
||||
await onError(safeError, result);
|
||||
} else {
|
||||
navigation.navigate('RegistrationFallback', {
|
||||
errorSource,
|
||||
countryCode,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle success
|
||||
await onSuccess?.(result);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
const safeError = sanitizeErrorMessage(errorMessage);
|
||||
console.error('Error launching alternative verification:', safeError);
|
||||
|
||||
// Call custom error handler if provided, otherwise navigate to fallback screen
|
||||
if (onError) {
|
||||
await onError(safeError);
|
||||
} else {
|
||||
navigation.navigate('RegistrationFallback', {
|
||||
errorSource,
|
||||
countryCode,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [navigation, countryCode, errorSource, onSuccess, onCancel, onError]);
|
||||
|
||||
return {
|
||||
launchSumsubVerification,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
@@ -19,6 +19,7 @@ import DocumentCameraTroubleScreen from '@/screens/documents/scanning/DocumentCa
|
||||
import DocumentNFCMethodSelectionScreen from '@/screens/documents/scanning/DocumentNFCMethodSelectionScreen';
|
||||
import DocumentNFCScanScreen from '@/screens/documents/scanning/DocumentNFCScanScreen';
|
||||
import DocumentNFCTroubleScreen from '@/screens/documents/scanning/DocumentNFCTroubleScreen';
|
||||
import RegistrationFallbackScreen from '@/screens/documents/scanning/RegistrationFallbackScreen';
|
||||
import ConfirmBelongingScreen from '@/screens/documents/selection/ConfirmBelongingScreen';
|
||||
import CountryPickerScreen from '@/screens/documents/selection/CountryPickerScreen';
|
||||
import DocumentOnboardingScreen from '@/screens/documents/selection/DocumentOnboardingScreen';
|
||||
@@ -155,6 +156,17 @@ const documentsScreens = {
|
||||
errorType: 'general',
|
||||
},
|
||||
},
|
||||
RegistrationFallback: {
|
||||
screen: RegistrationFallbackScreen,
|
||||
options: {
|
||||
title: 'REGISTRATION',
|
||||
headerShown: false,
|
||||
} as NativeStackNavigationOptions,
|
||||
initialParams: {
|
||||
errorSource: 'sumsub_initialization',
|
||||
countryCode: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default documentsScreens;
|
||||
|
||||
@@ -79,6 +79,7 @@ export type RootStackParamList = Omit<
|
||||
| 'Home'
|
||||
| 'IDPicker'
|
||||
| 'IdDetails'
|
||||
| 'RegistrationFallback'
|
||||
| 'Loading'
|
||||
| 'Modal'
|
||||
| 'MockDataDeepLink'
|
||||
@@ -127,6 +128,16 @@ export type RootStackParamList = Omit<
|
||||
errorType: string;
|
||||
};
|
||||
|
||||
// Registration Fallback screens
|
||||
RegistrationFallback: {
|
||||
errorSource:
|
||||
| 'mrz_scan_failed'
|
||||
| 'nfc_scan_failed'
|
||||
| 'sumsub_initialization'
|
||||
| 'sumsub_verification';
|
||||
countryCode: string;
|
||||
};
|
||||
|
||||
// Account/Recovery screens
|
||||
AccountRecovery:
|
||||
| {
|
||||
@@ -190,6 +201,7 @@ export type RootStackParamList = Omit<
|
||||
|
||||
// Onboarding screens
|
||||
Disclaimer: undefined;
|
||||
KycSuccess: undefined;
|
||||
|
||||
// Dev screens
|
||||
CreateMock: undefined;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';
|
||||
|
||||
import KycSuccessScreen from '@/screens/kyc/KycSuccessScreen';
|
||||
import AccountVerifiedSuccessScreen from '@/screens/onboarding/AccountVerifiedSuccessScreen';
|
||||
import DisclaimerScreen from '@/screens/onboarding/DisclaimerScreen';
|
||||
import SaveRecoveryPhraseScreen from '@/screens/onboarding/SaveRecoveryPhraseScreen';
|
||||
@@ -30,6 +31,13 @@ const onboardingScreens = {
|
||||
animation: 'slide_from_bottom',
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
KycSuccess: {
|
||||
screen: KycSuccessScreen,
|
||||
options: {
|
||||
headerShown: false,
|
||||
animation: 'slide_from_bottom',
|
||||
} as NativeStackNavigationOptions,
|
||||
},
|
||||
};
|
||||
|
||||
export default onboardingScreens;
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
type LogLevel,
|
||||
type NFCScanContext,
|
||||
reactNativeScannerAdapter,
|
||||
sanitizeErrorMessage,
|
||||
SdkEvents,
|
||||
SelfClientProvider as SDKSelfClientProvider,
|
||||
type TrackEventParams,
|
||||
useMRZStore,
|
||||
webNFCScannerShim,
|
||||
type WsConn,
|
||||
} from '@selfxyz/mobile-sdk-alpha';
|
||||
@@ -33,7 +35,12 @@ import {
|
||||
setPassportKeychainErrorCallback,
|
||||
} from '@/providers/passportDataProvider';
|
||||
import { trackEvent, trackNfcEvent } from '@/services/analytics';
|
||||
import {
|
||||
type InjectedErrorType,
|
||||
useErrorInjectionStore,
|
||||
} from '@/stores/errorInjectionStore';
|
||||
import { useSettingStore } from '@/stores/settingStore';
|
||||
import { IS_DEV_MODE } from '@/utils/devUtils';
|
||||
import {
|
||||
registerModalCallbacks,
|
||||
unregisterModalCallbacks,
|
||||
@@ -68,7 +75,20 @@ function navigateIfReady<RouteName extends keyof RootStackParamList>(
|
||||
}
|
||||
|
||||
export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
const config = useMemo(() => ({}), []);
|
||||
const config = useMemo(
|
||||
() => ({
|
||||
devConfig: IS_DEV_MODE
|
||||
? {
|
||||
shouldTrigger: (errorType: string) => {
|
||||
return useErrorInjectionStore
|
||||
.getState()
|
||||
.shouldTrigger(errorType as InjectedErrorType);
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const adapters: Adapters = useMemo(
|
||||
() => ({
|
||||
scanner:
|
||||
@@ -167,6 +187,9 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
const appListeners = useMemo(() => {
|
||||
const { map, addListener } = createListenersMap();
|
||||
|
||||
// Track current countryCode for error navigation
|
||||
let currentCountryCode = '';
|
||||
|
||||
addListener(SdkEvents.PROVING_PASSPORT_DATA_NOT_FOUND, () => {
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('DocumentDataNotFound');
|
||||
@@ -261,7 +284,10 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
});
|
||||
|
||||
addListener(SdkEvents.DOCUMENT_MRZ_READ_FAILURE, () => {
|
||||
navigateIfReady('DocumentCameraTrouble');
|
||||
navigateIfReady('RegistrationFallback', {
|
||||
errorSource: 'mrz_scan_failed',
|
||||
countryCode: currentCountryCode,
|
||||
});
|
||||
});
|
||||
|
||||
addListener(SdkEvents.PROVING_AADHAAR_UPLOAD_SUCCESS, () => {
|
||||
@@ -280,6 +306,9 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
countryCode: string;
|
||||
documentTypes: string[];
|
||||
}) => {
|
||||
currentCountryCode = countryCode;
|
||||
// Store country code early so it's available for Sumsub fallback flows
|
||||
useMRZStore.getState().update({ countryCode });
|
||||
navigateIfReady('IDPicker', { countryCode, documentTypes });
|
||||
},
|
||||
);
|
||||
@@ -300,14 +329,73 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
}
|
||||
break;
|
||||
case 'kyc':
|
||||
fetchAccessToken()
|
||||
.then(accessToken => {
|
||||
launchSumsub({ accessToken: accessToken.token });
|
||||
})
|
||||
// TODO: show sumsub error screen
|
||||
.catch(error => {
|
||||
console.error('Error launching Sumsub:', error);
|
||||
});
|
||||
(async () => {
|
||||
try {
|
||||
// Dev-only: Check for injected initialization error
|
||||
if (
|
||||
useErrorInjectionStore
|
||||
.getState()
|
||||
.shouldTrigger('sumsub_initialization')
|
||||
) {
|
||||
console.log('[DEV] Injecting Sumsub initialization error');
|
||||
throw new Error(
|
||||
'Injected Sumsub initialization error for testing',
|
||||
);
|
||||
}
|
||||
|
||||
const accessToken = await fetchAccessToken();
|
||||
const result = await launchSumsub({
|
||||
accessToken: accessToken.token,
|
||||
});
|
||||
|
||||
// User cancelled - return silently
|
||||
if (!result.success && result.status === 'Interrupted') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dev-only: Check for injected verification error
|
||||
const shouldInjectVerificationError = useErrorInjectionStore
|
||||
.getState()
|
||||
.shouldTrigger('sumsub_verification');
|
||||
|
||||
// Actual error from provider
|
||||
if (!result.success || shouldInjectVerificationError) {
|
||||
if (shouldInjectVerificationError) {
|
||||
console.log('[DEV] Injecting Sumsub verification error');
|
||||
} else {
|
||||
const safeError = sanitizeErrorMessage(
|
||||
result.errorMsg || result.errorType || 'unknown_error',
|
||||
);
|
||||
console.error('KYC provider failed:', safeError);
|
||||
}
|
||||
// Guard navigation call after async operations
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('RegistrationFallback', {
|
||||
errorSource: 'sumsub_verification',
|
||||
countryCode,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Success case: navigate to KYC success screen
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('KycSuccess');
|
||||
}
|
||||
} catch (error) {
|
||||
const safeInitError = sanitizeErrorMessage(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
console.error('Error in KYC flow:', safeInitError);
|
||||
// Guard navigation call after async operations
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('RegistrationFallback', {
|
||||
errorSource: 'sumsub_initialization',
|
||||
countryCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
break;
|
||||
default:
|
||||
if (countryCode) {
|
||||
|
||||
@@ -107,8 +107,8 @@ const LoadingScreen: React.FC<LoadingScreenProps> = ({ route }) => {
|
||||
} else {
|
||||
await init(selfClient, 'dsc', true);
|
||||
}
|
||||
} catch {
|
||||
console.error('Error loading selected document:');
|
||||
} catch (error) {
|
||||
console.error('Error loading selected document:', error);
|
||||
await init(selfClient, 'dsc', true);
|
||||
} finally {
|
||||
setIsInitializing(false);
|
||||
|
||||
@@ -44,6 +44,12 @@ import {
|
||||
subscribeToTopics,
|
||||
unsubscribeFromTopics,
|
||||
} from '@/services/notifications/notificationService';
|
||||
import type { InjectedErrorType } from '@/stores/errorInjectionStore';
|
||||
import {
|
||||
ERROR_GROUPS,
|
||||
ERROR_LABELS,
|
||||
useErrorInjectionStore,
|
||||
} from '@/stores/errorInjectionStore';
|
||||
import { usePointEventStore } from '@/stores/pointEventStore';
|
||||
import { useSettingStore } from '@/stores/settingStore';
|
||||
import { IS_DEV_MODE } from '@/utils/devUtils';
|
||||
@@ -390,6 +396,152 @@ const LogLevelSelector = ({
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorInjectionSelector = () => {
|
||||
const injectedErrors = useErrorInjectionStore(state => state.injectedErrors);
|
||||
const setInjectedErrors = useErrorInjectionStore(
|
||||
state => state.setInjectedErrors,
|
||||
);
|
||||
const clearAllErrors = useErrorInjectionStore(state => state.clearAllErrors);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// Single error selection - replace instead of toggle
|
||||
const selectError = (errorType: InjectedErrorType) => {
|
||||
// If clicking the same error, clear it; otherwise set the new one
|
||||
if (injectedErrors.length === 1 && injectedErrors[0] === errorType) {
|
||||
clearAllErrors();
|
||||
} else {
|
||||
setInjectedErrors([errorType]);
|
||||
}
|
||||
// Close the sheet after selection
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const currentError = injectedErrors.length > 0 ? injectedErrors[0] : null;
|
||||
const currentErrorLabel = currentError ? ERROR_LABELS[currentError] : null;
|
||||
|
||||
return (
|
||||
<YStack gap="$2">
|
||||
<Button
|
||||
style={{ backgroundColor: 'white' }}
|
||||
borderColor={slate200}
|
||||
borderRadius="$2"
|
||||
height="$5"
|
||||
padding={0}
|
||||
onPress={() => setOpen(true)}
|
||||
>
|
||||
<XStack
|
||||
width="100%"
|
||||
justifyContent="space-between"
|
||||
paddingVertical="$3"
|
||||
paddingLeft="$4"
|
||||
paddingRight="$1.5"
|
||||
>
|
||||
<Text fontSize="$5" color={slate500} fontFamily={dinot}>
|
||||
{currentErrorLabel || 'Select onboarding error to test'}
|
||||
</Text>
|
||||
<ChevronDown color={slate500} strokeWidth={2.5} />
|
||||
</XStack>
|
||||
</Button>
|
||||
|
||||
{currentError && (
|
||||
<Button
|
||||
backgroundColor={red500}
|
||||
borderRadius="$2"
|
||||
height="$5"
|
||||
onPress={clearAllErrors}
|
||||
pressStyle={{
|
||||
opacity: 0.8,
|
||||
scale: 0.98,
|
||||
}}
|
||||
>
|
||||
<Text color={white} fontSize="$5" fontFamily={dinot}>
|
||||
Clear
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Sheet
|
||||
modal
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
snapPoints={[85]}
|
||||
animation="medium"
|
||||
dismissOnSnapToBottom
|
||||
>
|
||||
<Sheet.Overlay />
|
||||
<Sheet.Frame
|
||||
backgroundColor={white}
|
||||
borderTopLeftRadius="$9"
|
||||
borderTopRightRadius="$9"
|
||||
>
|
||||
<YStack padding="$4">
|
||||
<XStack
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
marginBottom="$4"
|
||||
>
|
||||
<Text fontSize="$8" fontFamily={dinot}>
|
||||
Onboarding Error Testing
|
||||
</Text>
|
||||
<Button
|
||||
onPress={() => setOpen(false)}
|
||||
padding="$2"
|
||||
backgroundColor="transparent"
|
||||
>
|
||||
<ChevronDown
|
||||
color={slate500}
|
||||
strokeWidth={2.5}
|
||||
style={{ transform: [{ rotate: '180deg' }] }}
|
||||
/>
|
||||
</Button>
|
||||
</XStack>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 100 }}
|
||||
>
|
||||
{Object.entries(ERROR_GROUPS).map(([groupName, errors]) => (
|
||||
<YStack key={groupName} marginBottom="$4">
|
||||
<Text
|
||||
fontSize="$6"
|
||||
fontFamily={dinot}
|
||||
fontWeight="600"
|
||||
color={slate800}
|
||||
marginBottom="$2"
|
||||
>
|
||||
{groupName}
|
||||
</Text>
|
||||
{errors.map((errorType: InjectedErrorType) => (
|
||||
<TouchableOpacity
|
||||
key={errorType}
|
||||
onPress={() => selectError(errorType)}
|
||||
>
|
||||
<XStack
|
||||
paddingVertical="$3"
|
||||
paddingHorizontal="$2"
|
||||
borderBottomWidth={1}
|
||||
borderBottomColor={slate200}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Text fontSize="$5" color={slate600} fontFamily={dinot}>
|
||||
{ERROR_LABELS[errorType]}
|
||||
</Text>
|
||||
{currentError === errorType && (
|
||||
<Check color={slate600} size={20} />
|
||||
)}
|
||||
</XStack>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</YStack>
|
||||
))}
|
||||
</ScrollView>
|
||||
</YStack>
|
||||
</Sheet.Frame>
|
||||
</Sheet>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
const DevSettingsScreen: React.FC<DevSettingsScreenProps> = ({}) => {
|
||||
const { clearDocumentCatalogForMigrationTesting } = usePassport();
|
||||
const clearPointEvents = usePointEventStore(state => state.clearEvents);
|
||||
@@ -779,6 +931,16 @@ const DevSettingsScreen: React.FC<DevSettingsScreenProps> = ({}) => {
|
||||
/>
|
||||
</ParameterSection>
|
||||
|
||||
{IS_DEV_MODE && (
|
||||
<ParameterSection
|
||||
icon={<BugIcon />}
|
||||
title="Onboarding Error Testing"
|
||||
description="Test onboarding error flows"
|
||||
>
|
||||
<ErrorInjectionSelector />
|
||||
</ParameterSection>
|
||||
)}
|
||||
|
||||
{Platform.OS === 'android' && (
|
||||
<ParameterSection
|
||||
icon={<BugIcon />}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { YStack } from 'tamagui';
|
||||
|
||||
import { Caption } from '@selfxyz/mobile-sdk-alpha/components';
|
||||
import { slate500 } from '@selfxyz/mobile-sdk-alpha/constants/colors';
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { Caption, SecondaryButton } from '@selfxyz/mobile-sdk-alpha/components';
|
||||
import { slate500, slate700 } from '@selfxyz/mobile-sdk-alpha/constants/colors';
|
||||
|
||||
import Activity from '@/assets/icons/activity.svg';
|
||||
import PassportCameraBulb from '@/assets/icons/passport_camera_bulb.svg';
|
||||
@@ -15,6 +17,7 @@ import Star from '@/assets/icons/star.svg';
|
||||
import type { TipProps } from '@/components/Tips';
|
||||
import Tips from '@/components/Tips';
|
||||
import useHapticNavigation from '@/hooks/useHapticNavigation';
|
||||
import { useSumsubLauncher } from '@/hooks/useSumsubLauncher';
|
||||
import SimpleScrolledTitleLayout from '@/layouts/SimpleScrolledTitleLayout';
|
||||
import { flush as flushAnalytics } from '@/services/analytics';
|
||||
|
||||
@@ -48,6 +51,13 @@ const tips: TipProps[] = [
|
||||
|
||||
const DocumentCameraTroubleScreen: React.FC = () => {
|
||||
const go = useHapticNavigation('DocumentCamera', { action: 'cancel' });
|
||||
const selfClient = useSelfClient();
|
||||
const { useMRZStore } = selfClient;
|
||||
const { countryCode } = useMRZStore();
|
||||
const { launchSumsubVerification, isLoading } = useSumsubLauncher({
|
||||
countryCode,
|
||||
errorSource: 'sumsub_initialization',
|
||||
});
|
||||
|
||||
// error screen, flush analytics
|
||||
useEffect(() => {
|
||||
@@ -64,10 +74,28 @@ const DocumentCameraTroubleScreen: React.FC = () => {
|
||||
</Caption>
|
||||
}
|
||||
footer={
|
||||
<Caption size="large" style={{ color: slate500 }}>
|
||||
Following these steps should help your phone's camera capture the ID
|
||||
page quickly and clearly!
|
||||
</Caption>
|
||||
<YStack gap="$3">
|
||||
<Caption size="large" style={{ color: slate500 }}>
|
||||
Following these steps should help your phone's camera capture the ID
|
||||
page quickly and clearly!
|
||||
</Caption>
|
||||
|
||||
<Caption
|
||||
size="large"
|
||||
style={{ color: slate500, marginTop: 12, marginBottom: 8 }}
|
||||
>
|
||||
Or try an alternative verification method:
|
||||
</Caption>
|
||||
|
||||
<SecondaryButton
|
||||
onPress={launchSumsubVerification}
|
||||
disabled={isLoading}
|
||||
textColor={slate700}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
{isLoading ? 'Loading...' : 'Try Alternative Verification'}
|
||||
</SecondaryButton>
|
||||
</YStack>
|
||||
}
|
||||
>
|
||||
<Tips items={tips} />
|
||||
|
||||
@@ -54,6 +54,7 @@ import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts';
|
||||
import passportVerifyAnimation from '@/assets/animations/passport_verify.json';
|
||||
import NFC_IMAGE from '@/assets/images/nfc.png';
|
||||
import { logNFCEvent } from '@/config/sentry';
|
||||
import { useErrorInjection } from '@/hooks/useErrorInjection';
|
||||
import { useFeedbackAutoHide } from '@/hooks/useFeedbackAutoHide';
|
||||
import useHapticNavigation from '@/hooks/useHapticNavigation';
|
||||
import {
|
||||
@@ -106,8 +107,9 @@ const DocumentNFCScanScreen: React.FC = () => {
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const route = useRoute<DocumentNFCScanRoute>();
|
||||
const { showModal } = useFeedback();
|
||||
useFeedback();
|
||||
useFeedbackAutoHide();
|
||||
const { shouldInjectError } = useErrorInjection();
|
||||
const {
|
||||
passportNumber,
|
||||
dateOfBirth,
|
||||
@@ -189,18 +191,12 @@ const DocumentNFCScanScreen: React.FC = () => {
|
||||
},
|
||||
{ message: sanitizeErrorMessage(message) },
|
||||
);
|
||||
showModal({
|
||||
titleText: 'NFC Scan Error',
|
||||
bodyText: message,
|
||||
buttonText: SUPPORT_FORM_BUTTON_TEXT,
|
||||
secondaryButtonText: 'Help',
|
||||
preventDismiss: false,
|
||||
onButtonPress: openSupportForm,
|
||||
onSecondaryButtonPress: goToNFCTrouble,
|
||||
onModalDismiss: () => {},
|
||||
navigation.navigate('RegistrationFallback', {
|
||||
errorSource: 'nfc_scan_failed',
|
||||
countryCode,
|
||||
});
|
||||
},
|
||||
[baseContext, showModal, goToNFCTrouble],
|
||||
[baseContext, navigation, countryCode],
|
||||
);
|
||||
|
||||
const checkNfcSupport = useCallback(async () => {
|
||||
@@ -324,6 +320,18 @@ const DocumentNFCScanScreen: React.FC = () => {
|
||||
}, 30000);
|
||||
|
||||
try {
|
||||
// Dev-only: Check for injected timeout error
|
||||
if (shouldInjectError('nfc_timeout')) {
|
||||
console.log('[DEV] Injecting NFC timeout error');
|
||||
throw new Error('Injected timeout error for testing');
|
||||
}
|
||||
|
||||
// Dev-only: Check for injected module unavailable error
|
||||
if (shouldInjectError('nfc_module_unavailable')) {
|
||||
console.log('[DEV] Injecting NFC module unavailable error');
|
||||
throw new Error('NFC scanning is currently unavailable');
|
||||
}
|
||||
|
||||
const {
|
||||
canNumber,
|
||||
useCan,
|
||||
@@ -376,6 +384,12 @@ const DocumentNFCScanScreen: React.FC = () => {
|
||||
);
|
||||
let passportData: PassportData | null = null;
|
||||
try {
|
||||
// Dev-only: Check for injected parse failure error
|
||||
if (shouldInjectError('nfc_parse_failure')) {
|
||||
console.log('[DEV] Injecting NFC parse failure error');
|
||||
throw new Error('Failed to parse NFC response');
|
||||
}
|
||||
|
||||
passportData = parseScanResponse(scanResponse);
|
||||
} catch (e: unknown) {
|
||||
console.error('Parsing NFC Response Unsuccessful');
|
||||
@@ -452,6 +466,7 @@ const DocumentNFCScanScreen: React.FC = () => {
|
||||
navigation,
|
||||
openErrorModal,
|
||||
trackEvent,
|
||||
shouldInjectError,
|
||||
]);
|
||||
|
||||
const navigateToHome = useHapticNavigation('Home', {
|
||||
|
||||
@@ -7,13 +7,15 @@ import { View } from 'react-native';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
import { YStack } from 'tamagui';
|
||||
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { Caption, SecondaryButton } from '@selfxyz/mobile-sdk-alpha/components';
|
||||
import { slate500 } from '@selfxyz/mobile-sdk-alpha/constants/colors';
|
||||
import { slate500, slate700 } from '@selfxyz/mobile-sdk-alpha/constants/colors';
|
||||
|
||||
import type { TipProps } from '@/components/Tips';
|
||||
import Tips from '@/components/Tips';
|
||||
import { useFeedbackAutoHide } from '@/hooks/useFeedbackAutoHide';
|
||||
import useHapticNavigation from '@/hooks/useHapticNavigation';
|
||||
import { useSumsubLauncher } from '@/hooks/useSumsubLauncher';
|
||||
import SimpleScrolledTitleLayout from '@/layouts/SimpleScrolledTitleLayout';
|
||||
import { flushAllAnalytics } from '@/services/analytics';
|
||||
import { openSupportForm, SUPPORT_FORM_BUTTON_TEXT } from '@/services/support';
|
||||
@@ -50,6 +52,13 @@ const DocumentNFCTroubleScreen: React.FC = () => {
|
||||
const goToNFCMethodSelection = useHapticNavigation(
|
||||
'DocumentNFCMethodSelection',
|
||||
);
|
||||
const selfClient = useSelfClient();
|
||||
const { useMRZStore } = selfClient;
|
||||
const { countryCode } = useMRZStore();
|
||||
const { launchSumsubVerification, isLoading } = useSumsubLauncher({
|
||||
countryCode,
|
||||
errorSource: 'sumsub_initialization',
|
||||
});
|
||||
useFeedbackAutoHide();
|
||||
|
||||
// error screen, flush analytics
|
||||
@@ -71,9 +80,24 @@ const DocumentNFCTroubleScreen: React.FC = () => {
|
||||
secondaryButtonText="Open NFC Options"
|
||||
onSecondaryButtonPress={goToNFCMethodSelection}
|
||||
footer={
|
||||
<SecondaryButton onPress={openSupportForm} style={{ marginBottom: 0 }}>
|
||||
{SUPPORT_FORM_BUTTON_TEXT}
|
||||
</SecondaryButton>
|
||||
<YStack gap="$3">
|
||||
<SecondaryButton
|
||||
onPress={openSupportForm}
|
||||
textColor={slate700}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
{SUPPORT_FORM_BUTTON_TEXT}
|
||||
</SecondaryButton>
|
||||
|
||||
<SecondaryButton
|
||||
onPress={launchSumsubVerification}
|
||||
disabled={isLoading}
|
||||
textColor={slate700}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
{isLoading ? 'Loading...' : 'Try Alternative Verification'}
|
||||
</SecondaryButton>
|
||||
</YStack>
|
||||
}
|
||||
>
|
||||
<YStack
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Button, XStack, YStack } from 'tamagui';
|
||||
import type { RouteProp } from '@react-navigation/native';
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { HelpCircle, X } from '@tamagui/lucide-icons';
|
||||
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import {
|
||||
BodyText,
|
||||
PrimaryButton,
|
||||
SecondaryButton,
|
||||
} from '@selfxyz/mobile-sdk-alpha/components';
|
||||
import {
|
||||
black,
|
||||
cyan300,
|
||||
slate100,
|
||||
slate200,
|
||||
slate300,
|
||||
slate500,
|
||||
white,
|
||||
} from '@selfxyz/mobile-sdk-alpha/constants/colors';
|
||||
import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts';
|
||||
import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks';
|
||||
|
||||
import WarningIcon from '@/assets/images/warning.svg';
|
||||
import { useSumsubLauncher } from '@/hooks/useSumsubLauncher';
|
||||
import { buttonTap } from '@/integrations/haptics';
|
||||
import type { RootStackParamList } from '@/navigation';
|
||||
import { extraYPadding } from '@/utils/styleUtils';
|
||||
|
||||
type FallbackErrorSource =
|
||||
| 'mrz_scan_failed'
|
||||
| 'nfc_scan_failed'
|
||||
| 'sumsub_initialization'
|
||||
| 'sumsub_verification';
|
||||
|
||||
type RegistrationFallbackRouteParams = {
|
||||
errorSource: FallbackErrorSource;
|
||||
countryCode: string;
|
||||
};
|
||||
|
||||
type RegistrationFallbackRoute = RouteProp<
|
||||
Record<string, RegistrationFallbackRouteParams>,
|
||||
string
|
||||
>;
|
||||
|
||||
const getHeaderTitle = (errorSource: FallbackErrorSource): string => {
|
||||
switch (errorSource) {
|
||||
case 'mrz_scan_failed':
|
||||
return 'MRZ SCAN';
|
||||
case 'nfc_scan_failed':
|
||||
return 'NFC SCAN';
|
||||
default:
|
||||
return 'REGISTRATION';
|
||||
}
|
||||
};
|
||||
|
||||
const getCurrentStep = (errorSource: FallbackErrorSource): number => {
|
||||
switch (errorSource) {
|
||||
case 'mrz_scan_failed':
|
||||
return 1; // Step 1: MRZ scanning
|
||||
case 'nfc_scan_failed':
|
||||
return 2; // Step 2: NFC reading
|
||||
case 'sumsub_initialization':
|
||||
case 'sumsub_verification':
|
||||
return 3; // Step 3: Proving/verification
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
const getRetryButtonText = (errorSource: FallbackErrorSource): string => {
|
||||
switch (errorSource) {
|
||||
case 'mrz_scan_failed':
|
||||
return 'Try scanning again';
|
||||
case 'nfc_scan_failed':
|
||||
return 'Try reading again';
|
||||
default:
|
||||
return 'Try again';
|
||||
}
|
||||
};
|
||||
|
||||
const getErrorMessages = (
|
||||
errorSource: FallbackErrorSource,
|
||||
): { title: string; description: string; canRetryOriginal: boolean } => {
|
||||
switch (errorSource) {
|
||||
case 'mrz_scan_failed':
|
||||
return {
|
||||
title: 'There was a problem scanning your document',
|
||||
description: 'Make sure the document is clearly visible and try again',
|
||||
canRetryOriginal: true,
|
||||
};
|
||||
case 'nfc_scan_failed':
|
||||
return {
|
||||
title: 'There was a problem reading the chip',
|
||||
description: 'Make sure NFC is enabled and try again',
|
||||
canRetryOriginal: true,
|
||||
};
|
||||
case 'sumsub_initialization':
|
||||
return {
|
||||
title: 'Connection Error',
|
||||
description:
|
||||
'Unable to connect to verification service. Please check your internet connection and try again.',
|
||||
canRetryOriginal: false,
|
||||
};
|
||||
case 'sumsub_verification':
|
||||
return {
|
||||
title: 'Verification Error',
|
||||
description:
|
||||
'Something went wrong during the verification process. Please try again.',
|
||||
canRetryOriginal: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const RegistrationFallbackScreen: React.FC = () => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const paddingBottom = useSafeBottomPadding(extraYPadding + 35);
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const route = useRoute<RegistrationFallbackRoute>();
|
||||
const selfClient = useSelfClient();
|
||||
const { trackEvent, useMRZStore } = selfClient;
|
||||
const storeCountryCode = useMRZStore(state => state.countryCode);
|
||||
|
||||
const errorSource = route.params?.errorSource || 'sumsub_initialization';
|
||||
// Use country code from route params, or fall back to MRZ store
|
||||
const countryCode = route.params?.countryCode || storeCountryCode || '';
|
||||
|
||||
const headerTitle = getHeaderTitle(errorSource);
|
||||
const retryButtonText = getRetryButtonText(errorSource);
|
||||
const currentStep = getCurrentStep(errorSource);
|
||||
const { title, description, canRetryOriginal } =
|
||||
getErrorMessages(errorSource);
|
||||
|
||||
const { launchSumsubVerification, isLoading: isRetrying } = useSumsubLauncher(
|
||||
{
|
||||
countryCode,
|
||||
errorSource,
|
||||
onCancel: () => {
|
||||
navigation.goBack();
|
||||
},
|
||||
onError: (_error, _result) => {
|
||||
// Stay on this screen - user can try again
|
||||
// Error is already logged in the hook
|
||||
},
|
||||
onSuccess: () => {
|
||||
// Success - provider handles its own success UI
|
||||
// The screen will be navigated away by the provider's flow
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
buttonTap();
|
||||
navigation.goBack();
|
||||
}, [navigation]);
|
||||
|
||||
const handleTryAlternative = useCallback(async () => {
|
||||
trackEvent('REGISTRATION_FALLBACK_TRY_ALTERNATIVE', { errorSource });
|
||||
await launchSumsubVerification();
|
||||
}, [errorSource, launchSumsubVerification, trackEvent]);
|
||||
|
||||
const handleRetryOriginal = useCallback(() => {
|
||||
trackEvent('REGISTRATION_FALLBACK_RETRY_ORIGINAL', { errorSource });
|
||||
|
||||
// Navigate back to the appropriate screen based on error source
|
||||
if (errorSource === 'mrz_scan_failed') {
|
||||
navigation.navigate('DocumentCamera');
|
||||
} else if (errorSource === 'nfc_scan_failed') {
|
||||
navigation.navigate('DocumentNFCScan', {});
|
||||
} else if (errorSource === 'sumsub_initialization') {
|
||||
// Go back to ID Picker
|
||||
navigation.goBack();
|
||||
}
|
||||
// TODO: Handle 'sumsub_verification' case - currently falls through without action
|
||||
// which could leave users stuck when tapping "Try again" after Sumsub verification failure.
|
||||
// Consider: calling launchSumsubVerification() or navigating to appropriate retry screen.
|
||||
// Need to determine the correct retry behavior for failed Sumsub verifications.
|
||||
}, [errorSource, navigation, trackEvent]);
|
||||
|
||||
return (
|
||||
<YStack flex={1} backgroundColor={slate100}>
|
||||
{/* Header */}
|
||||
<YStack backgroundColor={slate100}>
|
||||
<XStack
|
||||
backgroundColor={slate100}
|
||||
padding={20}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
paddingTop={Math.max(insets.top, 15) + extraYPadding}
|
||||
paddingBottom={10}
|
||||
>
|
||||
<Button
|
||||
unstyled
|
||||
onPress={handleClose}
|
||||
padding={8}
|
||||
borderRadius={20}
|
||||
hitSlop={10}
|
||||
>
|
||||
<X size={24} color={black} />
|
||||
</Button>
|
||||
|
||||
<BodyText
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: black,
|
||||
fontWeight: '600',
|
||||
fontFamily: dinot,
|
||||
}}
|
||||
>
|
||||
{headerTitle}
|
||||
</BodyText>
|
||||
|
||||
<Button
|
||||
unstyled
|
||||
padding={8}
|
||||
borderRadius={20}
|
||||
hitSlop={10}
|
||||
width={32}
|
||||
height={32}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
disabled
|
||||
>
|
||||
<HelpCircle size={20} color={black} opacity={0} />
|
||||
</Button>
|
||||
</XStack>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<YStack paddingHorizontal={40} paddingBottom={10}>
|
||||
<XStack gap={3} height={6}>
|
||||
{[1, 2, 3, 4].map(step => (
|
||||
<YStack
|
||||
key={step}
|
||||
flex={1}
|
||||
backgroundColor={step === currentStep ? cyan300 : slate300}
|
||||
borderRadius={10}
|
||||
/>
|
||||
))}
|
||||
</XStack>
|
||||
</YStack>
|
||||
</YStack>
|
||||
|
||||
{/* Warning Icon */}
|
||||
<YStack flex={1} paddingHorizontal={20} paddingTop={20}>
|
||||
<YStack
|
||||
flex={1}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
paddingVertical={20}
|
||||
>
|
||||
<WarningIcon width={120} height={120} />
|
||||
</YStack>
|
||||
</YStack>
|
||||
|
||||
{/* Error Message */}
|
||||
<YStack
|
||||
paddingHorizontal={20}
|
||||
paddingTop={20}
|
||||
alignItems="center"
|
||||
paddingVertical={25}
|
||||
>
|
||||
<BodyText style={{ fontSize: 19, textAlign: 'center', color: black }}>
|
||||
{title}
|
||||
</BodyText>
|
||||
<BodyText
|
||||
style={{
|
||||
marginTop: 6,
|
||||
fontSize: 17,
|
||||
textAlign: 'center',
|
||||
color: slate500,
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</BodyText>
|
||||
</YStack>
|
||||
|
||||
{/* Top Button - Retry */}
|
||||
{canRetryOriginal && (
|
||||
<YStack paddingHorizontal={25} paddingBottom={20}>
|
||||
<PrimaryButton onPress={handleRetryOriginal} disabled={isRetrying}>
|
||||
{retryButtonText}
|
||||
</PrimaryButton>
|
||||
</YStack>
|
||||
)}
|
||||
|
||||
{/* Bottom Section with Grey Line Separator */}
|
||||
<YStack
|
||||
paddingHorizontal={25}
|
||||
backgroundColor={white}
|
||||
paddingBottom={paddingBottom}
|
||||
paddingTop={25}
|
||||
gap="$3"
|
||||
borderTopWidth={1}
|
||||
borderTopColor={slate200}
|
||||
>
|
||||
<SecondaryButton onPress={handleTryAlternative} disabled={isRetrying}>
|
||||
{isRetrying ? 'Loading...' : 'Try a different method'}
|
||||
</SecondaryButton>
|
||||
|
||||
{/* Footer Text */}
|
||||
<BodyText
|
||||
style={{
|
||||
fontSize: 15,
|
||||
textAlign: 'center',
|
||||
color: slate500,
|
||||
fontStyle: 'italic',
|
||||
marginTop: 8,
|
||||
}}
|
||||
>
|
||||
Registering with alternative methods may take longer to verify your
|
||||
document.
|
||||
</BodyText>
|
||||
</YStack>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegistrationFallbackScreen;
|
||||
124
app/src/screens/kyc/KycSuccessScreen.tsx
Normal file
124
app/src/screens/kyc/KycSuccessScreen.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { YStack } from 'tamagui';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import { DelayedLottieView } from '@selfxyz/mobile-sdk-alpha';
|
||||
import loadingAnimation from '@selfxyz/mobile-sdk-alpha/animations/loading/misc.json';
|
||||
import {
|
||||
AbstractButton,
|
||||
Description,
|
||||
Title,
|
||||
} from '@selfxyz/mobile-sdk-alpha/components';
|
||||
import { black, white } from '@selfxyz/mobile-sdk-alpha/constants/colors';
|
||||
|
||||
import { buttonTap } from '@/integrations/haptics';
|
||||
import type { RootStackParamList } from '@/navigation';
|
||||
import { requestNotificationPermission } from '@/services/notifications/notificationService';
|
||||
|
||||
const KycSuccessScreen: React.FC = () => {
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const handleReceiveUpdates = async () => {
|
||||
buttonTap();
|
||||
await requestNotificationPermission();
|
||||
// Navigate to Home regardless of permission result
|
||||
navigation.navigate('Home', {});
|
||||
};
|
||||
|
||||
const handleCheckLater = () => {
|
||||
buttonTap();
|
||||
navigation.navigate('Home', {});
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
|
||||
<View style={styles.centerSection}>
|
||||
<View style={styles.animationContainer}>
|
||||
<DelayedLottieView
|
||||
autoPlay
|
||||
loop={true}
|
||||
source={loadingAnimation}
|
||||
style={styles.animation}
|
||||
cacheComposition={true}
|
||||
renderMode="HARDWARE"
|
||||
/>
|
||||
</View>
|
||||
<YStack
|
||||
paddingHorizontal={24}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gap={12}
|
||||
>
|
||||
<Title style={styles.title}>Your ID is being verified</Title>
|
||||
<Description style={styles.description}>
|
||||
Turn on push notifications to receive an update on your
|
||||
verification. It's also safe the close the app and come back later.
|
||||
</Description>
|
||||
</YStack>
|
||||
</View>
|
||||
<YStack gap={12} paddingHorizontal={20} paddingBottom={24}>
|
||||
<AbstractButton
|
||||
bgColor={white}
|
||||
color={black}
|
||||
onPress={handleReceiveUpdates}
|
||||
>
|
||||
Receive live updates
|
||||
</AbstractButton>
|
||||
<AbstractButton
|
||||
bgColor="transparent"
|
||||
color={white}
|
||||
borderColor="rgba(255, 255, 255, 0.3)"
|
||||
borderWidth={1}
|
||||
onPress={handleCheckLater}
|
||||
>
|
||||
I will check back later
|
||||
</AbstractButton>
|
||||
</YStack>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: black,
|
||||
},
|
||||
centerSection: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
animationContainer: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 32,
|
||||
},
|
||||
animation: {
|
||||
width: 160,
|
||||
height: 160,
|
||||
},
|
||||
title: {
|
||||
color: white,
|
||||
textAlign: 'center',
|
||||
fontSize: 28,
|
||||
letterSpacing: 1,
|
||||
},
|
||||
description: {
|
||||
color: white,
|
||||
textAlign: 'center',
|
||||
fontSize: 18,
|
||||
},
|
||||
});
|
||||
|
||||
export default KycSuccessScreen;
|
||||
103
app/src/stores/errorInjectionStore.ts
Normal file
103
app/src/stores/errorInjectionStore.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
import { IS_DEV_MODE } from '@/utils/devUtils';
|
||||
|
||||
export type InjectedErrorType =
|
||||
| 'mrz_invalid_format'
|
||||
| 'mrz_unknown_error'
|
||||
| 'nfc_timeout'
|
||||
| 'nfc_module_unavailable'
|
||||
| 'nfc_parse_failure'
|
||||
| 'api_network_error'
|
||||
| 'api_timeout'
|
||||
| 'sumsub_initialization'
|
||||
| 'sumsub_verification';
|
||||
|
||||
export const ERROR_GROUPS = {
|
||||
MRZ: ['mrz_invalid_format', 'mrz_unknown_error'] as InjectedErrorType[],
|
||||
NFC: [
|
||||
'nfc_timeout',
|
||||
'nfc_module_unavailable',
|
||||
'nfc_parse_failure',
|
||||
] as InjectedErrorType[],
|
||||
API: ['api_network_error', 'api_timeout'] as InjectedErrorType[],
|
||||
Sumsub: [
|
||||
'sumsub_initialization',
|
||||
'sumsub_verification',
|
||||
] as InjectedErrorType[],
|
||||
};
|
||||
|
||||
export const ERROR_LABELS: Record<InjectedErrorType, string> = {
|
||||
mrz_invalid_format: 'MRZ: Invalid format',
|
||||
mrz_unknown_error: 'MRZ: Unknown error',
|
||||
nfc_timeout: 'NFC: Timeout',
|
||||
nfc_module_unavailable: 'NFC: Module unavailable',
|
||||
nfc_parse_failure: 'NFC: Parse failure',
|
||||
api_network_error: 'API: Network error',
|
||||
api_timeout: 'API: Timeout',
|
||||
sumsub_initialization: 'Sumsub: Initialization',
|
||||
sumsub_verification: 'Sumsub: Verification',
|
||||
};
|
||||
|
||||
interface ErrorInjectionState {
|
||||
injectedErrors: InjectedErrorType[];
|
||||
// Actions
|
||||
setInjectedErrors: (errors: InjectedErrorType[]) => void;
|
||||
toggleError: (error: InjectedErrorType) => void;
|
||||
clearError: (error: InjectedErrorType) => void;
|
||||
clearAllErrors: () => void;
|
||||
shouldTrigger: (error: InjectedErrorType) => boolean;
|
||||
}
|
||||
|
||||
export const useErrorInjectionStore = create<ErrorInjectionState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
injectedErrors: [],
|
||||
|
||||
setInjectedErrors: (errors: InjectedErrorType[]) => {
|
||||
if (!IS_DEV_MODE) return;
|
||||
set({ injectedErrors: errors });
|
||||
},
|
||||
|
||||
toggleError: (error: InjectedErrorType) => {
|
||||
if (!IS_DEV_MODE) return;
|
||||
set(state => {
|
||||
const hasError = state.injectedErrors.includes(error);
|
||||
return {
|
||||
injectedErrors: hasError
|
||||
? state.injectedErrors.filter(e => e !== error)
|
||||
: [...state.injectedErrors, error],
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
clearError: (error: InjectedErrorType) => {
|
||||
if (!IS_DEV_MODE) return;
|
||||
set(state => ({
|
||||
injectedErrors: state.injectedErrors.filter(e => e !== error),
|
||||
}));
|
||||
},
|
||||
|
||||
clearAllErrors: () => {
|
||||
if (!IS_DEV_MODE) return;
|
||||
set({ injectedErrors: [] });
|
||||
},
|
||||
|
||||
shouldTrigger: (error: InjectedErrorType) => {
|
||||
if (!IS_DEV_MODE) return false;
|
||||
const state = get();
|
||||
return state.injectedErrors.includes(error);
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'error-injection-storage',
|
||||
storage: createJSONStorage(() => AsyncStorage),
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -85,6 +85,7 @@ describe('navigation', () => {
|
||||
'Home',
|
||||
'IDPicker',
|
||||
'IdDetails',
|
||||
'KycSuccess',
|
||||
'Loading',
|
||||
'ManageDocuments',
|
||||
'MockDataDeepLink',
|
||||
@@ -101,6 +102,7 @@ describe('navigation', () => {
|
||||
'QRCodeViewFinder',
|
||||
'RecoverWithPhrase',
|
||||
'Referral',
|
||||
'RegistrationFallback',
|
||||
'SaveRecoveryPhrase',
|
||||
'Settings',
|
||||
'ShowRecoveryPhrase',
|
||||
|
||||
154
app/tests/src/screens/kyc/KycSuccessScreen.test.tsx
Normal file
154
app/tests/src/screens/kyc/KycSuccessScreen.test.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import React from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { render } from '@testing-library/react-native';
|
||||
|
||||
import ErrorBoundary from '@/components/ErrorBoundary';
|
||||
import KycSuccessScreen from '@/screens/kyc/KycSuccessScreen';
|
||||
import * as notificationService from '@/services/notifications/notificationService';
|
||||
|
||||
// Note: While jest.setup.js provides comprehensive React Native mocking,
|
||||
// react-test-renderer requires component-based mocks (functions) rather than
|
||||
// string-based mocks for proper rendering. This minimal mock provides the
|
||||
// specific components needed for this test without using requireActual to
|
||||
// avoid memory issues (see .cursor/rules/test-memory-optimization.mdc).
|
||||
jest.mock('react-native', () => ({
|
||||
__esModule: true,
|
||||
Platform: { OS: 'ios', select: jest.fn() },
|
||||
StyleSheet: {
|
||||
create: (styles: any) => styles,
|
||||
flatten: (style: any) => style,
|
||||
},
|
||||
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
||||
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
|
||||
}));
|
||||
|
||||
jest.mock('react-native-edge-to-edge', () => ({
|
||||
SystemBars: () => null,
|
||||
}));
|
||||
|
||||
jest.mock('react-native-safe-area-context', () => ({
|
||||
useSafeAreaInsets: jest.fn(() => ({ top: 0, bottom: 0 })),
|
||||
}));
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
useNavigation: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock Tamagui components
|
||||
jest.mock('tamagui', () => ({
|
||||
__esModule: true,
|
||||
YStack: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
||||
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
||||
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
|
||||
}));
|
||||
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
|
||||
DelayedLottieView: () => null,
|
||||
}));
|
||||
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/constants/colors', () => ({
|
||||
black: '#000000',
|
||||
white: '#FFFFFF',
|
||||
}));
|
||||
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/components', () => ({
|
||||
AbstractButton: ({ children, onPress }: any) => (
|
||||
<button onClick={onPress} data-testid="abstract-button" type="button">
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
PrimaryButton: ({ children, onPress }: any) => (
|
||||
<button onClick={onPress} data-testid="primary-button" type="button">
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
SecondaryButton: ({ children, onPress }: any) => (
|
||||
<button onClick={onPress} data-testid="secondary-button" type="button">
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
Title: ({ children }: any) => <div data-testid="title">{children}</div>,
|
||||
Description: ({ children }: any) => (
|
||||
<div data-testid="description">{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/integrations/haptics', () => ({
|
||||
buttonTap: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/services/notifications/notificationService', () => ({
|
||||
requestNotificationPermission: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/config/sentry', () => ({
|
||||
captureException: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/services/analytics', () => ({
|
||||
flushAllAnalytics: jest.fn(),
|
||||
trackNfcEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUseNavigation = useNavigation as jest.MockedFunction<
|
||||
typeof useNavigation
|
||||
>;
|
||||
|
||||
describe('KycSuccessScreen', () => {
|
||||
const mockNavigate = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockUseNavigation.mockReturnValue({
|
||||
navigate: mockNavigate,
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('should render the screen without errors', () => {
|
||||
const { root } = render(<KycSuccessScreen />);
|
||||
expect(root).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have navigation available', () => {
|
||||
render(<KycSuccessScreen />);
|
||||
expect(mockUseNavigation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have notification service available', () => {
|
||||
render(<KycSuccessScreen />);
|
||||
expect(notificationService.requestNotificationPermission).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders fallback on render error', () => {
|
||||
// Mock console.error to suppress error boundary error logs during test
|
||||
const consoleErrorSpy = jest
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
// Create a component that throws an error during render
|
||||
const ThrowError = () => {
|
||||
throw new Error('Test render error');
|
||||
};
|
||||
|
||||
// Render the error-throwing component wrapped in ErrorBoundary
|
||||
const { root } = render(
|
||||
<ErrorBoundary>
|
||||
<ThrowError />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
|
||||
// Verify the error boundary fallback UI is displayed
|
||||
// Use a more flexible matcher since the text is nested in mocked components
|
||||
expect(root.findByType('span').props.children).toBe(
|
||||
'Something went wrong. Please restart the app.',
|
||||
);
|
||||
|
||||
// Restore console.error
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -23,8 +23,8 @@
|
||||
"test-custom-hasher": "yarn test-base 'tests/other_circuits/custom_hasher.test.ts' --exit",
|
||||
"test-disclose": "yarn test-base 'tests/disclose/vc_and_disclose.test.ts' --exit",
|
||||
"test-disclose-aadhaar": "yarn test-base 'tests/disclose/vc_and_disclose_aadhaar.test.ts' --exit",
|
||||
"test-disclose-kyc": "yarn test-base 'tests/disclose/vc_and_disclose_kyc.test.ts' --exit",
|
||||
"test-disclose-id": "yarn test-base 'tests/disclose/vc_and_disclose_id.test.ts' --exit",
|
||||
"test-disclose-kyc": "yarn test-base 'tests/disclose/vc_and_disclose_kyc.test.ts' --exit",
|
||||
"test-dsc": "yarn test-base --max-old-space-size=51200 'tests/dsc/dsc.test.ts' --exit",
|
||||
"test-ecdsa": "yarn test-base 'tests/utils/ecdsa.test.ts' --exit",
|
||||
"test-gcp-jwt-verifier": "yarn test-base 'tests/gcp_jwt_verifier/gcp_jwt_verifier.test.ts' --exit",
|
||||
@@ -35,8 +35,8 @@
|
||||
"test-qr-extractor": "yarn test-base 'tests/other_circuits/qrdata_extractor.test.ts' --exit",
|
||||
"test-register": "yarn test-base --max-old-space-size=40960 'tests/register/register.test.ts' --exit",
|
||||
"test-register-aadhaar": "yarn test-base 'tests/register/register_aadhaar.test.ts' --exit",
|
||||
"test-register-kyc": "yarn test-base 'tests/register/register_kyc.test.ts' --exit",
|
||||
"test-register-id": "yarn test-base --max-old-space-size=40960 'tests/register_id/register_id.test.ts' --exit",
|
||||
"test-register-kyc": "yarn test-base 'tests/register/register_kyc.test.ts' --exit",
|
||||
"test-rsa": "yarn test-base 'tests/utils/rsaPkcs1v1_5.test.ts' --exit",
|
||||
"test-rsa-pss": "yarn test-base 'tests/utils/rsapss.test.ts' --exit"
|
||||
},
|
||||
@@ -77,6 +77,7 @@
|
||||
"snarkjs": "^0.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.6",
|
||||
"@types/chai": "4.3.11",
|
||||
"@types/chai-as-promised": "^7.1.6",
|
||||
"@types/circomlibjs": "^0.1.6",
|
||||
@@ -85,6 +86,8 @@
|
||||
"@types/node-forge": "^1.3.5",
|
||||
"@yarnpkg/sdks": "^3.2.0",
|
||||
"chai": "^4.4.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"mocha": "^10.7.3",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-mocha": "^10.0.0",
|
||||
|
||||
@@ -15,9 +15,9 @@ OUTPUT_DIR="build/${CIRCUIT_TYPE}"
|
||||
# Define circuits and their configurations
|
||||
# format: name:poweroftau:build_flag
|
||||
CIRCUITS=(
|
||||
# "vc_and_disclose:20:true"
|
||||
# "vc_and_disclose_id:20:true"
|
||||
# "vc_and_disclose_aadhaar:20:true"
|
||||
"vc_and_disclose:20:true"
|
||||
"vc_and_disclose_id:20:true"
|
||||
"vc_and_disclose_aadhaar:20:true"
|
||||
"vc_and_disclose_kyc:17:true"
|
||||
)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('OFAC - Name and DOB match', async function () {
|
||||
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
|
||||
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('OFAC - Name and DOB match', async function () {
|
||||
const dummy_kyc_input = serializeKycData(NON_OFAC_DUMMY_INPUT);
|
||||
const ofacInputs = generateCircuitInputsOfac(NON_OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('OFAC - Name and DOB match', async function () {
|
||||
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
|
||||
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
smt_leaf_key: BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString(),
|
||||
};
|
||||
@@ -79,7 +79,7 @@ describe('OFAC - Name and DOB match', async function () {
|
||||
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
|
||||
ofacInputs.smt_siblings[0] = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
};
|
||||
|
||||
@@ -92,7 +92,7 @@ describe('OFAC - Name and DOB match', async function () {
|
||||
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
|
||||
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
smt_root: BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString(),
|
||||
};
|
||||
@@ -129,7 +129,7 @@ describe('OFAC - Name and YOB match', async function () {
|
||||
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
|
||||
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('OFAC - Name and YOB match', async function () {
|
||||
const dummy_kyc_input = serializeKycData(NON_OFAC_DUMMY_INPUT);
|
||||
const ofacInputs = generateCircuitInputsOfac(NON_OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
};
|
||||
|
||||
@@ -155,7 +155,7 @@ describe('OFAC - Name and YOB match', async function () {
|
||||
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
|
||||
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
smt_leaf_key: BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString(),
|
||||
};
|
||||
@@ -170,7 +170,7 @@ describe('OFAC - Name and YOB match', async function () {
|
||||
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
|
||||
ofacInputs.smt_siblings[0] = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
};
|
||||
|
||||
@@ -183,7 +183,7 @@ describe('OFAC - Name and YOB match', async function () {
|
||||
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
|
||||
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
|
||||
const inputs = {
|
||||
kyc_data: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
|
||||
...ofacInputs,
|
||||
smt_root: BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString(),
|
||||
};
|
||||
|
||||
@@ -685,12 +685,12 @@
|
||||
"@openpassport/zk-kit-imt": "^0.0.5",
|
||||
"@openpassport/zk-kit-lean-imt": "^0.0.6",
|
||||
"@openpassport/zk-kit-smt": "^0.0.1",
|
||||
"@peculiar/x509": "^1.12.3",
|
||||
"@peculiar/x509": "^1.14.3",
|
||||
"@stablelib/cbor": "^2.0.1",
|
||||
"@zk-kit/baby-jubjub": "^1.0.3",
|
||||
"@zk-kit/eddsa-poseidon": "^1.1.0",
|
||||
"asn1.js": "^5.4.1",
|
||||
"asn1js": "^3.0.5",
|
||||
"asn1js": "^3.0.7",
|
||||
"axios": "^1.7.2",
|
||||
"buffer": "^6.0.3",
|
||||
"country-emoji": "^1.5.6",
|
||||
@@ -706,7 +706,7 @@
|
||||
"jsrsasign": "^11.1.0",
|
||||
"node-forge": "github:remicolin/forge#17a11a632dd0e50343b3b8393245a2696f78afbb",
|
||||
"path": "^0.12.7",
|
||||
"pkijs": "^3.2.4",
|
||||
"pkijs": "^3.3.3",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
"snarkjs": "^0.7.5",
|
||||
"typescript-parser": "^2.6.1",
|
||||
@@ -727,7 +727,7 @@
|
||||
"eslint-plugin-sort-exports": "^0.9.1",
|
||||
"prettier": "^3.5.3",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"packageManager": "yarn@4.12.0",
|
||||
|
||||
@@ -313,8 +313,8 @@ contract IdentityVerificationHubImplV2 is ImplRoot {
|
||||
);
|
||||
} else if (attestationId == AttestationId.KYC) {
|
||||
IIdentityRegistryKycV1($._registries[attestationId]).registerCommitment(
|
||||
registerCircuitProof.pubSignals[CircuitConstantsV2.SELFRICA_NULLIFIER_INDEX],
|
||||
registerCircuitProof.pubSignals[CircuitConstantsV2.SELFRICA_COMMITMENT_INDEX]
|
||||
registerCircuitProof.pubSignals[CircuitConstantsV2.KYC_NULLIFIER_INDEX],
|
||||
registerCircuitProof.pubSignals[CircuitConstantsV2.KYC_COMMITMENT_INDEX]
|
||||
);
|
||||
} else {
|
||||
revert InvalidAttestationId();
|
||||
@@ -876,7 +876,7 @@ contract IdentityVerificationHubImplV2 is ImplRoot {
|
||||
* @notice Performs current date validation with format-aware parsing
|
||||
* @dev Handles three date formats:
|
||||
* - E_PASSPORT/EU_ID_CARD: 6 ASCII chars (YYMMDD)
|
||||
* - SELFRICA_ID_CARD: 8 ASCII digits (YYYYMMDD)
|
||||
* - KYC: 8 ASCII digits (YYYYMMDD)
|
||||
* - AADHAAR: 3 numeric signals (year, month, day)
|
||||
* @param attestationId The attestation type to determine date format
|
||||
* @param vcAndDiscloseProof The proof containing date information
|
||||
@@ -900,7 +900,7 @@ contract IdentityVerificationHubImplV2 is ImplRoot {
|
||||
}
|
||||
currentTimestamp = Formatter.proofDateToUnixTimestamp(dateNum);
|
||||
} else if (attestationId == AttestationId.KYC) {
|
||||
// SELFRICA: 8 ASCII digits (YYYYMMDD)
|
||||
// KYC: 8 ASCII digits (YYYYMMDD)
|
||||
uint256[3] memory dateNum; // [year, month, day]
|
||||
unchecked {
|
||||
for (uint256 i; i < 4; ++i)
|
||||
@@ -1016,7 +1016,7 @@ contract IdentityVerificationHubImplV2 is ImplRoot {
|
||||
/**
|
||||
* @notice Creates verification output based on attestation type.
|
||||
* @dev Formats proof data into the appropriate output structure for the attestation type.
|
||||
* @param attestationId The attestation identifier (passport, EU ID card, Aadhaar, or Selfrica).
|
||||
* @param attestationId The attestation identifier (passport, EU ID card, Aadhaar, or KYC).
|
||||
* @param vcAndDiscloseProof The VC and Disclose proof data.
|
||||
* @param indices The circuit-specific indices for extracting proof values.
|
||||
* @param userIdentifier The user identifier to include in the output.
|
||||
|
||||
@@ -8,7 +8,7 @@ pragma solidity 0.8.28;
|
||||
* - E_PASSPORT (1): Electronic passports with NFC chip
|
||||
* - EU_ID_CARD (2): EU biometric ID cards with NFC chip
|
||||
* - AADHAAR (3): Indian Aadhaar identity documents
|
||||
* - SELFRICA_ID_CARD (4): African identity documents via Selfrica/SmileID
|
||||
* - KYC (4): African identity documents via SumSub
|
||||
*/
|
||||
library AttestationId {
|
||||
/// @notice Identifier for an E-PASSPORT attestation (electronic passports with NFC chip).
|
||||
@@ -20,6 +20,6 @@ library AttestationId {
|
||||
/// @notice Identifier for an AADHAAR attestation (Indian Aadhaar identity documents).
|
||||
bytes32 constant AADHAAR = bytes32(uint256(3));
|
||||
|
||||
/// @notice Identifier for a SELFRICA_ID_CARD attestation (African identity documents via Selfrica/SmileID).
|
||||
/// @notice Identifier for a KYC attestation (African identity documents via SumSub).
|
||||
bytes32 constant KYC = bytes32(uint256(4));
|
||||
}
|
||||
|
||||
@@ -55,15 +55,15 @@ library CircuitConstantsV2 {
|
||||
uint256 constant AADHAAR_TIMESTAMP_INDEX = 3;
|
||||
|
||||
// ---------------------------
|
||||
// Selfrica Circuit Constants
|
||||
// KYC Circuit Constants
|
||||
// ---------------------------
|
||||
/**
|
||||
* @notice Index to access the pubkey commitment in the Selfrica circuit public signals.
|
||||
* @notice Index to access the pubkey commitment in the KYC circuit public signals.
|
||||
*/
|
||||
uint256 constant SELFRICA_NULLIFIER_INDEX = 0;
|
||||
uint256 constant SELFRICA_COMMITMENT_INDEX = 1;
|
||||
uint256 constant SELFRICA_PUBKEY_COMMITMENT_INDEX = 2;
|
||||
uint256 constant SELFRICA_ATTESTATION_ID_INDEX = 3;
|
||||
uint256 constant KYC_NULLIFIER_INDEX = 0;
|
||||
uint256 constant KYC_COMMITMENT_INDEX = 1;
|
||||
uint256 constant KYC_PUBKEY_COMMITMENT_INDEX = 2;
|
||||
uint256 constant KYC_ATTESTATION_ID_INDEX = 3;
|
||||
|
||||
// -------------------------------------
|
||||
// VC and Disclose Circuit Constants
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
/**
|
||||
* @title IIdentityRegistrySelfricaV1
|
||||
* @notice Interface for the Identity Registry Selfrica v1.
|
||||
* @title IIdentityRegistryKycV1
|
||||
* @notice Interface for the Identity Registry KYC v1.
|
||||
* @dev This interface exposes only the external functions accessible by regular callers,
|
||||
* i.e. functions that are not owner-restricted.
|
||||
*/
|
||||
|
||||
@@ -64,7 +64,7 @@ interface IAadhaarRegisterCircuitVerifier {
|
||||
) external view returns (bool isValid);
|
||||
}
|
||||
|
||||
interface ISelfricaRegisterCircuitVerifier {
|
||||
interface IKycRegisterCircuitVerifier {
|
||||
/**
|
||||
* @notice Verifies a given register circuit proof.
|
||||
* @dev This function checks the validity of the provided proof parameters.
|
||||
|
||||
@@ -50,7 +50,7 @@ interface IVcAndDiscloseAadhaarCircuitVerifier {
|
||||
) external view returns (bool);
|
||||
}
|
||||
|
||||
interface IVcAndDiscloseSelfricaCircuitVerifier {
|
||||
interface IVcAndDiscloseKycCircuitVerifier {
|
||||
/**
|
||||
* @notice Verifies a given VC and Disclose zero-knowledge proof.
|
||||
* @dev This function checks the validity of the provided proof parameters.
|
||||
|
||||
@@ -38,8 +38,8 @@ library CustomVerifier {
|
||||
SelfStructs.AadhaarOutput memory aadhaarOutput = abi.decode(proofOutput, (SelfStructs.AadhaarOutput));
|
||||
return CustomVerifier.verifyAadhaar(verificationConfig, aadhaarOutput);
|
||||
} else if (attestationId == AttestationId.KYC) {
|
||||
SelfStructs.KycOutput memory selfricaOutput = abi.decode(proofOutput, (SelfStructs.KycOutput));
|
||||
return CustomVerifier.verifySelfrica(verificationConfig, selfricaOutput);
|
||||
SelfStructs.KycOutput memory kycOutput = abi.decode(proofOutput, (SelfStructs.KycOutput));
|
||||
return CustomVerifier.verifyKyc(verificationConfig, kycOutput);
|
||||
} else {
|
||||
revert InvalidAttestationId();
|
||||
}
|
||||
@@ -298,20 +298,20 @@ library CustomVerifier {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Verifies a Selfrica output.
|
||||
* @notice Verifies a KYC output.
|
||||
* @param verificationConfig The verification configuration.
|
||||
* @param selfricaOutput The Selfrica output from the circuit.
|
||||
* @param kycOutput The KYC output from the circuit.
|
||||
* @return genericDiscloseOutput The generic disclose output.
|
||||
*/
|
||||
function verifySelfrica(
|
||||
function verifyKyc(
|
||||
SelfStructs.VerificationConfigV2 memory verificationConfig,
|
||||
SelfStructs.KycOutput memory selfricaOutput
|
||||
SelfStructs.KycOutput memory kycOutput
|
||||
) internal pure returns (SelfStructs.GenericDiscloseOutputV2 memory) {
|
||||
if (verificationConfig.ofacEnabled[1] || verificationConfig.ofacEnabled[2]) {
|
||||
if (
|
||||
!CircuitAttributeHandlerV2.compareOfac(
|
||||
AttestationId.KYC,
|
||||
selfricaOutput.revealedDataPacked,
|
||||
kycOutput.revealedDataPacked,
|
||||
false,
|
||||
verificationConfig.ofacEnabled[1],
|
||||
verificationConfig.ofacEnabled[2]
|
||||
@@ -323,9 +323,7 @@ library CustomVerifier {
|
||||
|
||||
if (verificationConfig.forbiddenCountriesEnabled) {
|
||||
for (uint256 i = 0; i < 4; i++) {
|
||||
if (
|
||||
selfricaOutput.forbiddenCountriesListPacked[i] != verificationConfig.forbiddenCountriesListPacked[i]
|
||||
) {
|
||||
if (kycOutput.forbiddenCountriesListPacked[i] != verificationConfig.forbiddenCountriesListPacked[i]) {
|
||||
revert InvalidForbiddenCountries();
|
||||
}
|
||||
}
|
||||
@@ -335,7 +333,7 @@ library CustomVerifier {
|
||||
if (
|
||||
!CircuitAttributeHandlerV2.compareOlderThanNumeric(
|
||||
AttestationId.KYC,
|
||||
selfricaOutput.revealedDataPacked,
|
||||
kycOutput.revealedDataPacked,
|
||||
verificationConfig.olderThan
|
||||
)
|
||||
) {
|
||||
@@ -345,27 +343,27 @@ library CustomVerifier {
|
||||
|
||||
SelfStructs.GenericDiscloseOutputV2 memory genericDiscloseOutput = SelfStructs.GenericDiscloseOutputV2({
|
||||
attestationId: AttestationId.KYC,
|
||||
userIdentifier: selfricaOutput.userIdentifier,
|
||||
nullifier: selfricaOutput.nullifier,
|
||||
forbiddenCountriesListPacked: selfricaOutput.forbiddenCountriesListPacked,
|
||||
userIdentifier: kycOutput.userIdentifier,
|
||||
nullifier: kycOutput.nullifier,
|
||||
forbiddenCountriesListPacked: kycOutput.forbiddenCountriesListPacked,
|
||||
issuingState: "UNAVAILABLE",
|
||||
name: CircuitAttributeHandlerV2.getName(AttestationId.KYC, selfricaOutput.revealedDataPacked),
|
||||
idNumber: CircuitAttributeHandlerV2.getDocumentNumber(AttestationId.KYC, selfricaOutput.revealedDataPacked),
|
||||
nationality: CircuitAttributeHandlerV2.getNationality(AttestationId.KYC, selfricaOutput.revealedDataPacked),
|
||||
name: CircuitAttributeHandlerV2.getName(AttestationId.KYC, kycOutput.revealedDataPacked),
|
||||
idNumber: CircuitAttributeHandlerV2.getDocumentNumber(AttestationId.KYC, kycOutput.revealedDataPacked),
|
||||
nationality: CircuitAttributeHandlerV2.getNationality(AttestationId.KYC, kycOutput.revealedDataPacked),
|
||||
dateOfBirth: CircuitAttributeHandlerV2.getDateOfBirthFullYear(
|
||||
AttestationId.KYC,
|
||||
selfricaOutput.revealedDataPacked
|
||||
kycOutput.revealedDataPacked
|
||||
),
|
||||
gender: CircuitAttributeHandlerV2.getGender(AttestationId.KYC, selfricaOutput.revealedDataPacked),
|
||||
gender: CircuitAttributeHandlerV2.getGender(AttestationId.KYC, kycOutput.revealedDataPacked),
|
||||
expiryDate: CircuitAttributeHandlerV2.getExpiryDateFullYear(
|
||||
AttestationId.KYC,
|
||||
selfricaOutput.revealedDataPacked
|
||||
kycOutput.revealedDataPacked
|
||||
),
|
||||
olderThan: verificationConfig.olderThan,
|
||||
ofac: [
|
||||
false,
|
||||
CircuitAttributeHandlerV2.getNameAndDobOfac(AttestationId.KYC, selfricaOutput.revealedDataPacked),
|
||||
CircuitAttributeHandlerV2.getNameAndYobOfac(AttestationId.KYC, selfricaOutput.revealedDataPacked)
|
||||
CircuitAttributeHandlerV2.getNameAndDobOfac(AttestationId.KYC, kycOutput.revealedDataPacked),
|
||||
CircuitAttributeHandlerV2.getNameAndYobOfac(AttestationId.KYC, kycOutput.revealedDataPacked)
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {AttestationId} from "../constants/AttestationId.sol";
|
||||
import {GenericProofStruct} from "../interfaces/IRegisterCircuitVerifier.sol";
|
||||
import {IVcAndDiscloseCircuitVerifier} from "../interfaces/IVcAndDiscloseCircuitVerifier.sol";
|
||||
import {IVcAndDiscloseAadhaarCircuitVerifier} from "../interfaces/IVcAndDiscloseCircuitVerifier.sol";
|
||||
import {IVcAndDiscloseSelfricaCircuitVerifier} from "../interfaces/IVcAndDiscloseCircuitVerifier.sol";
|
||||
import {IVcAndDiscloseKycCircuitVerifier} from "../interfaces/IVcAndDiscloseCircuitVerifier.sol";
|
||||
|
||||
/**
|
||||
* @title ProofVerifierLib
|
||||
@@ -25,7 +25,7 @@ library ProofVerifierLib {
|
||||
* @dev Handles different attestation types with different public signal counts:
|
||||
* - E_PASSPORT and EU_ID_CARD: 21 public signals
|
||||
* - AADHAAR: 19 public signals
|
||||
* - SELFRICA_ID_CARD: 28 public signals
|
||||
* - KYC: 29 public signals
|
||||
* @param attestationId The type of attestation being verified
|
||||
* @param verifierAddress The address of the verifier contract
|
||||
* @param vcAndDiscloseProof The proof data including public signals
|
||||
@@ -73,7 +73,7 @@ library ProofVerifierLib {
|
||||
}
|
||||
|
||||
if (
|
||||
!IVcAndDiscloseSelfricaCircuitVerifier(verifierAddress).verifyProof(
|
||||
!IVcAndDiscloseKycCircuitVerifier(verifierAddress).verifyProof(
|
||||
vcAndDiscloseProof.a,
|
||||
vcAndDiscloseProof.b,
|
||||
vcAndDiscloseProof.c,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {CircuitConstantsV2} from "../constants/CircuitConstantsV2.sol";
|
||||
import {GenericProofStruct} from "../interfaces/IRegisterCircuitVerifier.sol";
|
||||
import {IRegisterCircuitVerifier} from "../interfaces/IRegisterCircuitVerifier.sol";
|
||||
import {IAadhaarRegisterCircuitVerifier} from "../interfaces/IRegisterCircuitVerifier.sol";
|
||||
import {ISelfricaRegisterCircuitVerifier} from "../interfaces/IRegisterCircuitVerifier.sol";
|
||||
import {IKycRegisterCircuitVerifier} from "../interfaces/IRegisterCircuitVerifier.sol";
|
||||
import {IIdentityRegistryV1} from "../interfaces/IIdentityRegistryV1.sol";
|
||||
import {IIdentityRegistryIdCardV1} from "../interfaces/IIdentityRegistryIdCardV1.sol";
|
||||
import {IIdentityRegistryAadhaarV1} from "../interfaces/IIdentityRegistryAadhaarV1.sol";
|
||||
@@ -100,7 +100,7 @@ library RegisterProofVerifierLib {
|
||||
} else if (attestationId == AttestationId.KYC) {
|
||||
if (
|
||||
!IIdentityRegistryKycV1(registryAddress).checkPubkeyCommitment(
|
||||
registerCircuitProof.pubSignals[CircuitConstantsV2.SELFRICA_PUBKEY_COMMITMENT_INDEX]
|
||||
registerCircuitProof.pubSignals[CircuitConstantsV2.KYC_PUBKEY_COMMITMENT_INDEX]
|
||||
)
|
||||
) {
|
||||
revert InvalidPubkeyCommitment();
|
||||
@@ -158,7 +158,7 @@ library RegisterProofVerifierLib {
|
||||
registerCircuitProof.pubSignals[3]
|
||||
];
|
||||
if (
|
||||
!ISelfricaRegisterCircuitVerifier(verifier).verifyProof(
|
||||
!IKycRegisterCircuitVerifier(verifier).verifyProof(
|
||||
registerCircuitProof.a,
|
||||
registerCircuitProof.b,
|
||||
registerCircuitProof.c,
|
||||
|
||||
@@ -32,11 +32,11 @@ import {Formatter} from "../libraries/Formatter.sol";
|
||||
*/
|
||||
|
||||
/**
|
||||
* @title IdentityRegistrySelfricaStorageV1
|
||||
* @dev Abstract contract for storage layout of IdentityRegistrySelfricaImplV1.
|
||||
* @title IdentityRegistryKycStorageV1
|
||||
* @dev Abstract contract for storage layout of IdentityRegistryKycImplV1.
|
||||
* Inherits from ImplRoot to provide upgradeable functionality.
|
||||
*/
|
||||
abstract contract IdentityRegistrySelfricaStorageV1 is ImplRoot {
|
||||
abstract contract IdentityRegistryKycStorageV1 is ImplRoot {
|
||||
// =============================================
|
||||
// Storage Variables
|
||||
// =============================================
|
||||
@@ -56,7 +56,7 @@ abstract contract IdentityRegistrySelfricaStorageV1 is ImplRoot {
|
||||
/// @notice Mapping from nullifier to a boolean indicating registration.
|
||||
mapping(uint256 => bool) internal _nullifiers;
|
||||
|
||||
/// @notice Pubkey commitments registered for Selfrica.
|
||||
/// @notice Pubkey commitments registered for KYC.
|
||||
mapping(uint256 => bool) internal _isRegisteredPubkeyCommitment;
|
||||
|
||||
/// @notice Current name and date of birth OFAC root.
|
||||
@@ -110,11 +110,11 @@ interface IPCR0Manager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @title IdentityRegistrySelfricaImplV1
|
||||
* @title IdentityRegistryKycImplV1
|
||||
* @notice Provides functions to register and manage identity commitments using a Merkle tree structure.
|
||||
* @dev Inherits from IdentityRegistrySelfricaStorageV1 and implements IIdentityRegistrySelfricaV1.
|
||||
* @dev Inherits from IdentityRegistryKycStorageV1 and implements IIdentityRegistryKycV1.
|
||||
*/
|
||||
contract IdentityRegistrySelfricaImplV1 is IdentityRegistrySelfricaStorageV1, IIdentityRegistryKycV1 {
|
||||
contract IdentityRegistryKycImplV1 is IdentityRegistryKycStorageV1, IIdentityRegistryKycV1 {
|
||||
using InternalLeanIMT for LeanIMTData;
|
||||
|
||||
// ====================================================
|
||||
@@ -38,7 +38,7 @@ function getHubImplV2InitializeData() {
|
||||
* - Verification configs via setVerificationConfigV2()
|
||||
*
|
||||
* Post-deployment configuration steps:
|
||||
* 1. Set registry addresses for each attestation type (E_PASSPORT, EU_ID_CARD, AADHAAR, SELFRICA_ID_CARD)
|
||||
* 1. Set registry addresses for each attestation type (E_PASSPORT, EU_ID_CARD, AADHAAR, KYC)
|
||||
* 2. Configure circuit verifiers for different signature types
|
||||
* 3. Set up verification configurations using setVerificationConfigV2()
|
||||
* 4. Transfer ownership to the appropriate address if needed
|
||||
|
||||
@@ -16,9 +16,8 @@ import RegisterVerifierArtifactLocal from "../../artifacts/contracts/verifiers/l
|
||||
import RegisterIdVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/register_id/Verifier_register_id_sha256_sha256_sha256_rsa_65537_4096_staging.sol/Verifier_register_id_sha256_sha256_sha256_rsa_65537_4096_staging.json";
|
||||
import RegisterAadhaarVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/register/Verifier_register_aadhaar_staging.sol/Verifier_register_aadhaar_staging.json";
|
||||
import DscVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/dsc/Verifier_dsc_sha256_rsa_65537_4096_staging.sol/Verifier_dsc_sha256_rsa_65537_4096_staging.json";
|
||||
import RegisterSelfricaVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/register/Verifier_register_kyc_staging.sol/Verifier_register_kyc_staging.json";
|
||||
// import GCPJWTVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/gcp_jwt_verifier/Verifier_gcp_jwt_verifier_staging.sol/Verifier_gcp_jwt_verifier_staging.json";
|
||||
import VcAndDiscloseSelfricaVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/disclose/Verifier_vc_and_disclose_kyc_staging.sol/Verifier_vc_and_disclose_kyc_staging.json";
|
||||
import RegisterKycVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/register/Verifier_register_kyc_staging.sol/Verifier_register_kyc_staging.json";
|
||||
import VcAndDiscloseKycVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/disclose/Verifier_vc_and_disclose_kyc_staging.sol/Verifier_vc_and_disclose_kyc_staging.json";
|
||||
|
||||
export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
let identityVerificationHubV2: any;
|
||||
@@ -29,16 +28,16 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
let identityRegistryIdImpl: any;
|
||||
let identityRegistryAadhaarImpl: any;
|
||||
let identityRegistryAadhaarProxy: any;
|
||||
let identityRegistrySelfricaImpl: any;
|
||||
let identityRegistrySelfricaProxy: any;
|
||||
let identityRegistryKycImpl: any;
|
||||
let identityRegistryKycProxy: any;
|
||||
let vcAndDiscloseVerifier: any;
|
||||
let vcAndDiscloseIdVerifier: any;
|
||||
let vcAndDiscloseAadhaarVerifier: any;
|
||||
let vcAndDiscloseSelfricaVerifier: any;
|
||||
let vcAndDiscloseKycVerifier: any;
|
||||
let registerVerifier: any;
|
||||
let registerIdVerifier: any;
|
||||
let registerAadhaarVerifier: any;
|
||||
let registerSelfricaVerifier: any;
|
||||
let registerKycVerifier: any;
|
||||
let dscVerifier: any;
|
||||
let testSelfVerificationRoot: any;
|
||||
let owner: HardhatEthersSigner;
|
||||
@@ -92,16 +91,16 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
await vcAndDiscloseAadhaarVerifier.waitForDeployment();
|
||||
}
|
||||
|
||||
let vcAndDiscloseSelfricaVerifierArtifact;
|
||||
// Deploy VC and Disclose Selfrica verifier
|
||||
let vcAndDiscloseKycVerifierArtifact;
|
||||
// Deploy VC and Disclose KYC verifier
|
||||
{
|
||||
vcAndDiscloseSelfricaVerifierArtifact = VcAndDiscloseSelfricaVerifierArtifactLocal;
|
||||
const vcAndDiscloseSelfricaVerifierFactory = await ethers.getContractFactory(
|
||||
vcAndDiscloseSelfricaVerifierArtifact.abi,
|
||||
vcAndDiscloseSelfricaVerifierArtifact.bytecode,
|
||||
vcAndDiscloseKycVerifierArtifact = VcAndDiscloseKycVerifierArtifactLocal;
|
||||
const vcAndDiscloseKycVerifierFactory = await ethers.getContractFactory(
|
||||
vcAndDiscloseKycVerifierArtifact.abi,
|
||||
vcAndDiscloseKycVerifierArtifact.bytecode,
|
||||
);
|
||||
vcAndDiscloseSelfricaVerifier = await vcAndDiscloseSelfricaVerifierFactory.connect(owner).deploy();
|
||||
await vcAndDiscloseSelfricaVerifier.waitForDeployment();
|
||||
vcAndDiscloseKycVerifier = await vcAndDiscloseKycVerifierFactory.connect(owner).deploy();
|
||||
await vcAndDiscloseKycVerifier.waitForDeployment();
|
||||
}
|
||||
|
||||
// Deploy register verifier
|
||||
@@ -140,16 +139,16 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
await registerAadhaarVerifier.waitForDeployment();
|
||||
}
|
||||
|
||||
// Deploy register selfrica verifier
|
||||
let registerSelfricaVerifierArtifact, registerSelfricaVerifierFactory;
|
||||
// Deploy register kyc verifier
|
||||
let registerKycVerifierArtifact, registerKycVerifierFactory;
|
||||
{
|
||||
registerSelfricaVerifierArtifact = RegisterSelfricaVerifierArtifactLocal;
|
||||
registerSelfricaVerifierFactory = await ethers.getContractFactory(
|
||||
registerSelfricaVerifierArtifact.abi,
|
||||
registerSelfricaVerifierArtifact.bytecode,
|
||||
registerKycVerifierArtifact = RegisterKycVerifierArtifactLocal;
|
||||
registerKycVerifierFactory = await ethers.getContractFactory(
|
||||
registerKycVerifierArtifact.abi,
|
||||
registerKycVerifierArtifact.bytecode,
|
||||
);
|
||||
registerSelfricaVerifier = await registerSelfricaVerifierFactory.connect(owner).deploy();
|
||||
await registerSelfricaVerifier.waitForDeployment();
|
||||
registerKycVerifier = await registerKycVerifierFactory.connect(owner).deploy();
|
||||
await registerKycVerifier.waitForDeployment();
|
||||
}
|
||||
|
||||
// Deploy dsc verifier
|
||||
@@ -257,16 +256,16 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
await identityRegistryAadhaarImpl.waitForDeployment();
|
||||
}
|
||||
|
||||
// Deploy IdentityRegistrySelfricaImplV1 for Selfrica
|
||||
let IdentityRegistrySelfricaImplFactory;
|
||||
// Deploy IdentityRegistryKycImplV1 for KYC
|
||||
let IdentityRegistryKycImplFactory;
|
||||
{
|
||||
IdentityRegistrySelfricaImplFactory = await ethers.getContractFactory("IdentityRegistrySelfricaImplV1", {
|
||||
IdentityRegistryKycImplFactory = await ethers.getContractFactory("IdentityRegistryKycImplV1", {
|
||||
libraries: {
|
||||
PoseidonT3: poseidonT3.target,
|
||||
},
|
||||
});
|
||||
identityRegistrySelfricaImpl = await IdentityRegistrySelfricaImplFactory.connect(owner).deploy();
|
||||
await identityRegistrySelfricaImpl.waitForDeployment();
|
||||
identityRegistryKycImpl = await IdentityRegistryKycImplFactory.connect(owner).deploy();
|
||||
await identityRegistryKycImpl.waitForDeployment();
|
||||
}
|
||||
|
||||
// Deploy IdentityVerificationHubImplV2
|
||||
@@ -324,18 +323,18 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
await identityRegistryAadhaarProxy.waitForDeployment();
|
||||
}
|
||||
|
||||
// Deploy Selfrica registry with temporary hub address and local PCR0Manager
|
||||
let registrySelfricaInitData, registrySelfricaProxyFactory;
|
||||
// Deploy Kyc registry with temporary hub address and local PCR0Manager
|
||||
let registryKycInitData, registryKycProxyFactory;
|
||||
{
|
||||
registrySelfricaInitData = identityRegistrySelfricaImpl.interface.encodeFunctionData("initialize", [
|
||||
registryKycInitData = identityRegistryKycImpl.interface.encodeFunctionData("initialize", [
|
||||
temporaryHubAddress,
|
||||
pcr0Manager.target,
|
||||
]);
|
||||
registrySelfricaProxyFactory = await ethers.getContractFactory("IdentityRegistry");
|
||||
identityRegistrySelfricaProxy = await registrySelfricaProxyFactory
|
||||
registryKycProxyFactory = await ethers.getContractFactory("IdentityRegistry");
|
||||
identityRegistryKycProxy = await registryKycProxyFactory
|
||||
.connect(owner)
|
||||
.deploy(identityRegistrySelfricaImpl.target, registrySelfricaInitData);
|
||||
await identityRegistrySelfricaProxy.waitForDeployment();
|
||||
.deploy(identityRegistryKycImpl.target, registryKycInitData);
|
||||
await identityRegistryKycProxy.waitForDeployment();
|
||||
}
|
||||
|
||||
// Deploy hub V2 with simple initialization (V2 has different initialization)
|
||||
@@ -374,17 +373,14 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
await updateAadhaarHubTx.wait();
|
||||
}
|
||||
|
||||
let registrySelfricaContract, updateSelfricaHubTx;
|
||||
let registryKycContract, updateKycHubTx;
|
||||
{
|
||||
registrySelfricaContract = await ethers.getContractAt(
|
||||
"IdentityRegistrySelfricaImplV1",
|
||||
identityRegistrySelfricaProxy.target,
|
||||
);
|
||||
updateSelfricaHubTx = await registrySelfricaContract.updateHub(identityVerificationHubV2.target);
|
||||
await updateSelfricaHubTx.wait();
|
||||
registryKycContract = await ethers.getContractAt("IdentityRegistryKycImplV1", identityRegistryKycProxy.target);
|
||||
updateKycHubTx = await registryKycContract.updateHub(identityVerificationHubV2.target);
|
||||
await updateKycHubTx.wait();
|
||||
|
||||
// Configure GCP JWT verifier for Selfrica
|
||||
await registrySelfricaContract.updateGCPJWTVerifier(gcpJwtVerifier.target);
|
||||
// Configure GCP JWT verifier for Kyc
|
||||
await registryKycContract.updateGCPJWTVerifier(gcpJwtVerifier.target);
|
||||
}
|
||||
|
||||
let hubContract;
|
||||
@@ -412,8 +408,8 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
nameAndYob_smt,
|
||||
nameDobAadhar_smt,
|
||||
nameYobAadhar_smt,
|
||||
nameAndDob_selfrica_smt,
|
||||
nameAndYob_selfrica_smt,
|
||||
nameAndDob_kyc_smt,
|
||||
nameAndYob_kyc_smt,
|
||||
} = getSMTs();
|
||||
|
||||
// Update passport roots
|
||||
@@ -429,27 +425,27 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
await registryAadhaarContract.updateNameAndDobOfacRoot(nameDobAadhar_smt.root, { from: owner });
|
||||
await registryAadhaarContract.updateNameAndYobOfacRoot(nameYobAadhar_smt.root, { from: owner });
|
||||
|
||||
// Update Selfrica roots
|
||||
await registrySelfricaContract.updateNameAndDobOfacRoot(nameAndDob_selfrica_smt.root, { from: owner });
|
||||
await registrySelfricaContract.updateNameAndYobOfacRoot(nameAndYob_selfrica_smt.root, { from: owner });
|
||||
// Update Kyc roots
|
||||
await registryKycContract.updateNameAndDobOfacRoot(nameAndDob_kyc_smt.root, { from: owner });
|
||||
await registryKycContract.updateNameAndYobOfacRoot(nameAndYob_kyc_smt.root, { from: owner });
|
||||
|
||||
// Register verifiers with the hub
|
||||
const E_PASSPORT = ethers.hexlify(ethers.zeroPadValue(ethers.toBeHex(1), 32));
|
||||
const EU_ID_CARD = ethers.hexlify(ethers.zeroPadValue(ethers.toBeHex(2), 32));
|
||||
const AADHAAR = ethers.hexlify(ethers.zeroPadValue(ethers.toBeHex(3), 32));
|
||||
const SELFRICA = ethers.hexlify(ethers.zeroPadValue(ethers.toBeHex(4), 32));
|
||||
const Kyc = ethers.hexlify(ethers.zeroPadValue(ethers.toBeHex(4), 32));
|
||||
|
||||
// Update registries in the hub
|
||||
await hubContract.updateRegistry(E_PASSPORT, identityRegistryProxy.target);
|
||||
await hubContract.updateRegistry(EU_ID_CARD, identityRegistryIdProxy.target);
|
||||
await hubContract.updateRegistry(AADHAAR, identityRegistryAadhaarProxy.target);
|
||||
await hubContract.updateRegistry(SELFRICA, identityRegistrySelfricaProxy.target);
|
||||
await hubContract.updateRegistry(Kyc, identityRegistryKycProxy.target);
|
||||
|
||||
// Update VC and Disclose verifiers
|
||||
await hubContract.updateVcAndDiscloseCircuit(E_PASSPORT, vcAndDiscloseVerifier.target);
|
||||
await hubContract.updateVcAndDiscloseCircuit(EU_ID_CARD, vcAndDiscloseIdVerifier.target);
|
||||
await hubContract.updateVcAndDiscloseCircuit(AADHAAR, vcAndDiscloseAadhaarVerifier.target);
|
||||
await hubContract.updateVcAndDiscloseCircuit(SELFRICA, vcAndDiscloseSelfricaVerifier.target);
|
||||
await hubContract.updateVcAndDiscloseCircuit(Kyc, vcAndDiscloseKycVerifier.target);
|
||||
|
||||
// Update register verifiers
|
||||
await hubContract.updateRegisterCircuitVerifier(
|
||||
@@ -463,7 +459,7 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
registerIdVerifier.target,
|
||||
);
|
||||
await hubContract.updateRegisterCircuitVerifier(AADHAAR, 0, registerAadhaarVerifier.target);
|
||||
await hubContract.updateRegisterCircuitVerifier(SELFRICA, 0, registerSelfricaVerifier.target);
|
||||
await hubContract.updateRegisterCircuitVerifier(Kyc, 0, registerKycVerifier.target);
|
||||
|
||||
// Update DSC verifiers
|
||||
await hubContract.updateDscVerifier(E_PASSPORT, DscVerifierId.dsc_sha256_rsa_65537_4096, dscVerifier.target);
|
||||
@@ -487,12 +483,12 @@ export async function deploySystemFixturesV2(): Promise<DeployedActorsV2> {
|
||||
registryId: registryIdContract,
|
||||
registryAadhaarImpl: identityRegistryAadhaarImpl,
|
||||
registryAadhaar: registryAadhaarContract,
|
||||
registrySelfrica: registrySelfricaContract,
|
||||
registrySelfricaImpl: identityRegistrySelfricaImpl,
|
||||
registryKyc: registryKycContract,
|
||||
registryKycImpl: identityRegistryKycImpl,
|
||||
vcAndDisclose: vcAndDiscloseVerifier,
|
||||
vcAndDiscloseId: vcAndDiscloseIdVerifier,
|
||||
vcAndDiscloseAadhaar: vcAndDiscloseAadhaarVerifier,
|
||||
vcAndDiscloseSelfrica: vcAndDiscloseSelfricaVerifier,
|
||||
vcAndDiscloseKyc: vcAndDiscloseKycVerifier,
|
||||
aadhaarPubkey: aadhaarPubkeyCommitment,
|
||||
register: registerVerifier,
|
||||
registerId: RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
|
||||
|
||||
@@ -86,8 +86,8 @@ const vcAndDiscloseCircuitsAadhaar: CircuitArtifacts = {
|
||||
},
|
||||
};
|
||||
|
||||
const vcAndDiscloseCircuitsSelfrica: CircuitArtifacts = {
|
||||
vc_and_disclose_selfrica: {
|
||||
const vcAndDiscloseCircuitsKyc: CircuitArtifacts = {
|
||||
vc_and_disclose_kyc: {
|
||||
wasm: "../circuits/build/disclose/vc_and_disclose_kyc/vc_and_disclose_kyc_js/vc_and_disclose_kyc.wasm",
|
||||
zkey: "../circuits/build/disclose/vc_and_disclose_kyc/vc_and_disclose_kyc_final.zkey",
|
||||
vkey: "../circuits/build/disclose/vc_and_disclose_kyc/vc_and_disclose_kyc_vkey.json",
|
||||
@@ -206,7 +206,7 @@ export async function generateRegisterAadhaarProof(
|
||||
return fixedProof;
|
||||
}
|
||||
|
||||
export async function generateRegisterSelfricaProof(
|
||||
export async function generateRegisterKycProof(
|
||||
secret: string,
|
||||
//return type of prepareAadhaarTestData
|
||||
inputs: Awaited<ReturnType<typeof generateMockKycRegisterInput>>,
|
||||
@@ -529,11 +529,11 @@ export async function generateVcAndDiscloseAadhaarProof(
|
||||
return fixedProof;
|
||||
}
|
||||
|
||||
export async function generateVcAndDiscloseSelfricaProof(
|
||||
export async function generateVcAndDiscloseKycProof(
|
||||
inputs: ReturnType<typeof generateKycDiscloseInput>,
|
||||
): Promise<GenericProofStructStruct> {
|
||||
const circuitName = "vc_and_disclose_selfrica";
|
||||
const circuitArtifacts = vcAndDiscloseCircuitsSelfrica;
|
||||
const circuitName = "vc_and_disclose_kyc";
|
||||
const circuitArtifacts = vcAndDiscloseCircuitsKyc;
|
||||
const artifactKey = circuitName;
|
||||
|
||||
const vcAndDiscloseProof = await groth16.fullProve(
|
||||
@@ -545,7 +545,7 @@ export async function generateVcAndDiscloseSelfricaProof(
|
||||
const vKey = JSON.parse(fs.readFileSync(circuitArtifacts[artifactKey].vkey, "utf8"));
|
||||
const isValid = await groth16.verify(vKey, vcAndDiscloseProof.publicSignals, vcAndDiscloseProof.proof);
|
||||
if (!isValid) {
|
||||
throw new Error("Generated VC and Disclose Selfrica proof verification failed");
|
||||
throw new Error("Generated VC and Disclose KYC proof verification failed");
|
||||
}
|
||||
|
||||
const rawCallData = await groth16.exportSolidityCallData(vcAndDiscloseProof.proof, vcAndDiscloseProof.publicSignals);
|
||||
@@ -589,12 +589,8 @@ export function getSMTs() {
|
||||
) as typeof SMT;
|
||||
const nameAndDob_id_smt = importSMTFromJsonFile("../circuits/tests/consts/ofac/nameAndDobSMT_ID.json") as typeof SMT;
|
||||
const nameAndYob_id_smt = importSMTFromJsonFile("../circuits/tests/consts/ofac/nameAndYobSMT_ID.json") as typeof SMT;
|
||||
const nameAndDob_selfrica_smt = importSMTFromJsonFile(
|
||||
"../circuits/tests/consts/ofac/nameAndDobSelfricaSMT.json",
|
||||
) as typeof SMT;
|
||||
const nameAndYob_selfrica_smt = importSMTFromJsonFile(
|
||||
"../circuits/tests/consts/ofac/nameAndYobSelfricaSMT.json",
|
||||
) as typeof SMT;
|
||||
const nameAndDob_kyc_smt = importSMTFromJsonFile("../circuits/tests/consts/ofac/nameAndDobKycSMT.json") as typeof SMT;
|
||||
const nameAndYob_kyc_smt = importSMTFromJsonFile("../circuits/tests/consts/ofac/nameAndYobKycSMT.json") as typeof SMT;
|
||||
|
||||
return {
|
||||
passportNo_smt,
|
||||
@@ -604,8 +600,8 @@ export function getSMTs() {
|
||||
nameAndYob_id_smt,
|
||||
nameDobAadhar_smt,
|
||||
nameYobAadhar_smt,
|
||||
nameAndDob_selfrica_smt,
|
||||
nameAndYob_selfrica_smt,
|
||||
nameAndDob_kyc_smt,
|
||||
nameAndYob_kyc_smt,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,11 @@ import {
|
||||
IdentityRegistry,
|
||||
IdentityRegistryImplV1,
|
||||
IdentityRegistryIdCardImplV1,
|
||||
IdentityRegistrySelfricaImplV1,
|
||||
TestSelfVerificationRoot,
|
||||
Verifier_vc_and_disclose_staging as LocalVerifier,
|
||||
Verifier_vc_and_disclose_id_staging as LocalIdCardVerifier,
|
||||
Verifier_vc_and_disclose_aadhaar_staging as LocalAadhaarVerifier,
|
||||
Verifier_vc_and_disclose_selfrica_staging as LocalSelfricaVerifier,
|
||||
Verifier_vc_and_disclose_kyc_staging as LocalKycVerifier,
|
||||
Verifier_vc_and_disclose as ProdVerifier,
|
||||
Verifier_vc_and_disclose_id as ProdIdCardVerifier,
|
||||
Verifier_register_sha256_sha256_sha256_rsa_65537_4096 as ProdRegisterVerifier,
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
Verifier_dsc_sha256_rsa_65537_4096 as ProdDscVerifier,
|
||||
Verifier_dsc_sha256_rsa_65537_4096_staging as LocalDscVerifier,
|
||||
IIdentityVerificationHubV1,
|
||||
IVcAndDiscloseSelfricaCircuitVerifier,
|
||||
IVcAndDiscloseKycCircuitVerifier,
|
||||
IVcAndDiscloseAadhaarCircuitVerifier,
|
||||
IIdentityVerificationHubV2,
|
||||
IIdentityRegistryIdCardV1,
|
||||
@@ -35,6 +34,7 @@ import {
|
||||
IVcAndDiscloseCircuitVerifier,
|
||||
IdentityRegistryAadhaarImplV1,
|
||||
PCR0Manager,
|
||||
IdentityRegistryKycImplV1,
|
||||
} from "../../typechain-types";
|
||||
|
||||
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common";
|
||||
@@ -82,11 +82,11 @@ export interface DeployedActorsV2 {
|
||||
registryId: IdentityRegistryIdCardImplV1;
|
||||
registryAadhaarImpl: IdentityRegistryAadhaarImplV1;
|
||||
registryAadhaar: IdentityRegistryAadhaarImplV1;
|
||||
registrySelfrica: IdentityRegistrySelfricaImplV1;
|
||||
registrySelfricaImpl: IdentityRegistrySelfricaImplV1;
|
||||
registryKyc: IdentityRegistryKycImplV1;
|
||||
registryKycImpl: IdentityRegistryKycImplV1;
|
||||
vcAndDisclose: VcAndDiscloseVerifier;
|
||||
vcAndDiscloseAadhaar: LocalAadhaarVerifier;
|
||||
vcAndDiscloseSelfrica: LocalSelfricaVerifier;
|
||||
vcAndDiscloseKyc: LocalKycVerifier;
|
||||
aadhaarPubkey: bigint;
|
||||
vcAndDiscloseId: VcAndDiscloseIdVerifier;
|
||||
register: RegisterVerifier;
|
||||
|
||||
@@ -14,15 +14,15 @@ import { generateKycDiscloseInput } from "@selfxyz/common";
|
||||
import { getSMTs } from "../utils/generateProof";
|
||||
import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts/forbiddenCountries";
|
||||
import { BigNumberish } from "ethers";
|
||||
import { generateVcAndDiscloseSelfricaProof } from "../utils/generateProof";
|
||||
import { generateVcAndDiscloseKycProof } from "../utils/generateProof";
|
||||
import { KYC_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
|
||||
import { poseidon2 } from "poseidon-lite";
|
||||
|
||||
// Selfrica circuit indices - matches CircuitConstantsV2.getDiscloseIndices(SELFRICA_ID_CARD)
|
||||
// KYC circuit indices - matches CircuitConstantsV2.getDiscloseIndices(KYC_ID_CARD)
|
||||
// See CircuitConstantsV2.sol for full layout documentation
|
||||
const SELFRICA_CURRENT_DATE_INDEX = 21;
|
||||
const KYC_CURRENT_DATE_INDEX = 21;
|
||||
|
||||
describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
describe("Self Verification Flow V2 - KYC", () => {
|
||||
let deployedActors: DeployedActorsV2;
|
||||
let snapshotId: string;
|
||||
let nullifier: any;
|
||||
@@ -51,8 +51,8 @@ describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
const userData = "test-user-data-for-verification";
|
||||
|
||||
userIdentifierHash = BigInt(calculateUserIdentifierHash(destChainId, user1Address.slice(2), userData).toString());
|
||||
nameAndDob_smt = getSMTs().nameAndDob_selfrica_smt;
|
||||
nameAndYob_smt = getSMTs().nameAndYob_selfrica_smt;
|
||||
nameAndDob_smt = getSMTs().nameAndDob_kyc_smt;
|
||||
nameAndYob_smt = getSMTs().nameAndYob_kyc_smt;
|
||||
|
||||
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
|
||||
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
|
||||
@@ -78,7 +78,7 @@ describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
nullifier = packBytesAndPoseidon(nullifier);
|
||||
const commitment = poseidon2([BigInt(testInputs.secret), packBytesAndPoseidon(dataPadded)]);
|
||||
|
||||
await deployedActors.registrySelfrica.devAddIdentityCommitment(nullifier, commitment);
|
||||
await deployedActors.registryKyc.devAddIdentityCommitment(nullifier, commitment);
|
||||
|
||||
forbiddenCountriesList = [] as Country3LetterCode[];
|
||||
forbiddenCountriesListPacked = getPackedForbiddenCountries(forbiddenCountriesList);
|
||||
@@ -97,7 +97,7 @@ describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
};
|
||||
|
||||
await deployedActors.testSelfVerificationRoot.setVerificationConfig(verificationConfigV2);
|
||||
baseVcAndDiscloseProof = await generateVcAndDiscloseSelfricaProof(testInputs);
|
||||
baseVcAndDiscloseProof = await generateVcAndDiscloseKycProof(testInputs);
|
||||
snapshotId = await ethers.provider.send("evm_snapshot", []);
|
||||
});
|
||||
|
||||
@@ -302,7 +302,7 @@ describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
const attestationId = ethers.zeroPadValue(ethers.toBeHex(BigInt(KYC_ATTESTATION_ID)), 32);
|
||||
|
||||
const clonedPubSignal = structuredClone(baseVcAndDiscloseProof.pubSignals);
|
||||
// scopeIndex for Selfrica is 16
|
||||
// scopeIndex for KYC is 16
|
||||
clonedPubSignal[16] = 1n;
|
||||
|
||||
const encodedProof = ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
@@ -348,7 +348,7 @@ describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
const attestationId = ethers.zeroPadValue(ethers.toBeHex(BigInt(KYC_ATTESTATION_ID)), 32);
|
||||
|
||||
const clonedPubSignal = structuredClone(baseVcAndDiscloseProof.pubSignals);
|
||||
// userIdentifierIndex for Selfrica is 20
|
||||
// userIdentifierIndex for KYC is 20
|
||||
clonedPubSignal[20] = 1n;
|
||||
|
||||
const encodedProof = ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
@@ -395,8 +395,8 @@ describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
|
||||
const clonedPubSignal = structuredClone(baseVcAndDiscloseProof.pubSignals);
|
||||
// Modify current date at the correct index using BigInt for safe arithmetic
|
||||
const currentDateValue = BigInt(clonedPubSignal[SELFRICA_CURRENT_DATE_INDEX]);
|
||||
clonedPubSignal[SELFRICA_CURRENT_DATE_INDEX] = (currentDateValue + 2n).toString();
|
||||
const currentDateValue = BigInt(clonedPubSignal[KYC_CURRENT_DATE_INDEX]);
|
||||
clonedPubSignal[KYC_CURRENT_DATE_INDEX] = (currentDateValue + 2n).toString();
|
||||
|
||||
const encodedProof = ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
["tuple(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[] pubSignals)"],
|
||||
@@ -443,8 +443,8 @@ describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
|
||||
const clonedPubSignal = structuredClone(baseVcAndDiscloseProof.pubSignals);
|
||||
// Modify current date at the correct index using BigInt for safe arithmetic
|
||||
const currentDateValue = BigInt(clonedPubSignal[SELFRICA_CURRENT_DATE_INDEX]);
|
||||
clonedPubSignal[SELFRICA_CURRENT_DATE_INDEX] = (currentDateValue - 1n).toString();
|
||||
const currentDateValue = BigInt(clonedPubSignal[KYC_CURRENT_DATE_INDEX]);
|
||||
clonedPubSignal[KYC_CURRENT_DATE_INDEX] = (currentDateValue - 1n).toString();
|
||||
|
||||
const encodedProof = ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
["tuple(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[] pubSignals)"],
|
||||
@@ -733,7 +733,7 @@ describe("Self Verification Flow V2 - Selfrica", () => {
|
||||
KYC_ATTESTATION_ID,
|
||||
);
|
||||
|
||||
const newProof = await generateVcAndDiscloseSelfricaProof(inputs);
|
||||
const newProof = await generateVcAndDiscloseKycProof(inputs);
|
||||
const attestationId = ethers.zeroPadValue(ethers.toBeHex(BigInt(KYC_ATTESTATION_ID)), 32);
|
||||
const encodedProof = ethers.AbiCoder.defaultAbiCoder().encode(
|
||||
["tuple(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[] pubSignals)"],
|
||||
|
||||
@@ -3,7 +3,7 @@ import { deploySystemFixturesV2 } from "../utils/deploymentV2";
|
||||
import { DeployedActorsV2 } from "../utils/types";
|
||||
import { KYC_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
|
||||
import { generateMockKycRegisterInput } from "@selfxyz/common/utils/kyc/generateInputs";
|
||||
import { generateRegisterSelfricaProof } from "../utils/generateProof";
|
||||
import { generateRegisterKycProof } from "../utils/generateProof";
|
||||
import { expect } from "chai";
|
||||
|
||||
function getCurrentDateDigitsYYMMDDHHMMSS(hoursOffset: number = 0): bigint[] {
|
||||
@@ -46,7 +46,7 @@ function packUint256ToHexFields(value: bigint): [bigint, bigint, bigint] {
|
||||
return [p0, p1, p2];
|
||||
}
|
||||
|
||||
describe("Selfrica Registration test", function () {
|
||||
describe("KYC Registration test", function () {
|
||||
this.timeout(0);
|
||||
|
||||
let deployedActors: DeployedActorsV2;
|
||||
@@ -60,10 +60,10 @@ describe("Selfrica Registration test", function () {
|
||||
attestationIdBytes32 = ethers.zeroPadValue(ethers.toBeHex(BigInt(KYC_ATTESTATION_ID)), 32);
|
||||
|
||||
// Set the owner as the TEE for all tests
|
||||
await deployedActors.registrySelfrica.updateTEE(await deployedActors.owner.getAddress());
|
||||
await deployedActors.registryKyc.updateTEE(await deployedActors.owner.getAddress());
|
||||
|
||||
// Set the GCP root CA pubkey hash
|
||||
await deployedActors.registrySelfrica.updateGCPRootCAPubkeyHash(GCP_ROOT_CA_PUBKEY_HASH);
|
||||
await deployedActors.registryKyc.updateGCPRootCAPubkeyHash(GCP_ROOT_CA_PUBKEY_HASH);
|
||||
|
||||
console.log("🎉 System deployment and initial setup completed!");
|
||||
});
|
||||
@@ -77,7 +77,7 @@ describe("Selfrica Registration test", function () {
|
||||
});
|
||||
|
||||
describe("Identity Commitment", () => {
|
||||
let selfricaData: any;
|
||||
let kycData: any;
|
||||
let registerProof: any;
|
||||
let registerSecret: string;
|
||||
let mockVerifier: any;
|
||||
@@ -87,14 +87,14 @@ describe("Selfrica Registration test", function () {
|
||||
|
||||
before(async () => {
|
||||
registerSecret = "12345";
|
||||
selfricaData = await generateMockKycRegisterInput(undefined, true, registerSecret);
|
||||
registerProof = await generateRegisterSelfricaProof(registerSecret, selfricaData);
|
||||
kycData = await generateMockKycRegisterInput(undefined, true, registerSecret);
|
||||
registerProof = await generateRegisterKycProof(registerSecret, kycData);
|
||||
|
||||
// Deploy and set mock GCP JWT verifier
|
||||
const MockVerifierFactory = await ethers.getContractFactory("MockGCPJWTVerifier");
|
||||
mockVerifier = await MockVerifierFactory.deploy();
|
||||
await mockVerifier.waitForDeployment();
|
||||
await deployedActors.registrySelfrica.updateGCPJWTVerifier(mockVerifier.target);
|
||||
await deployedActors.registryKyc.updateGCPJWTVerifier(mockVerifier.target);
|
||||
|
||||
// Get the pubkey commitment from the register proof and pack as hex
|
||||
const pubkeyCommitment = registerProof.pubSignals[registerProof.pubSignals.length - 2];
|
||||
@@ -145,19 +145,14 @@ describe("Selfrica Registration test", function () {
|
||||
});
|
||||
|
||||
it("should successfully register an identity commitment", async () => {
|
||||
await deployedActors.registrySelfrica.registerPubkeyCommitment(
|
||||
mockProof.a,
|
||||
mockProof.b,
|
||||
mockProof.c,
|
||||
mockPubSignals,
|
||||
);
|
||||
await deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals);
|
||||
|
||||
await expect(deployedActors.hub.registerCommitment(attestationIdBytes32, 0n, registerProof)).to.emit(
|
||||
deployedActors.registrySelfrica,
|
||||
deployedActors.registryKyc,
|
||||
"CommitmentRegistered",
|
||||
);
|
||||
|
||||
const isRegistered = await deployedActors.registrySelfrica.nullifiers(registerProof.pubSignals[0]);
|
||||
const isRegistered = await deployedActors.registryKyc.nullifiers(registerProof.pubSignals[0]);
|
||||
expect(isRegistered).to.be.true;
|
||||
});
|
||||
|
||||
@@ -168,12 +163,7 @@ describe("Selfrica Registration test", function () {
|
||||
});
|
||||
|
||||
it("should not register an identity commitment if the proof is invalid", async () => {
|
||||
await deployedActors.registrySelfrica.registerPubkeyCommitment(
|
||||
mockProof.a,
|
||||
mockProof.b,
|
||||
mockProof.c,
|
||||
mockPubSignals,
|
||||
);
|
||||
await deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals);
|
||||
|
||||
const invalidRegisterProof = structuredClone(registerProof);
|
||||
invalidRegisterProof.pubSignals[1] = 0n;
|
||||
@@ -229,21 +219,21 @@ describe("Selfrica Registration test", function () {
|
||||
};
|
||||
|
||||
it("should have correct GCP root CA pubkey hash", async () => {
|
||||
const contractHash = await deployedActors.registrySelfrica.gcpRootCAPubkeyHash();
|
||||
const contractHash = await deployedActors.registryKyc.gcpRootCAPubkeyHash();
|
||||
expect(contractHash).to.equal(GCP_ROOT_CA_PUBKEY_HASH);
|
||||
});
|
||||
|
||||
it("should allow owner to update GCP root CA pubkey hash", async () => {
|
||||
const newHash = 12345n;
|
||||
await deployedActors.registrySelfrica.updateGCPRootCAPubkeyHash(newHash);
|
||||
const contractHash = await deployedActors.registrySelfrica.gcpRootCAPubkeyHash();
|
||||
await deployedActors.registryKyc.updateGCPRootCAPubkeyHash(newHash);
|
||||
const contractHash = await deployedActors.registryKyc.gcpRootCAPubkeyHash();
|
||||
expect(contractHash).to.equal(newHash);
|
||||
});
|
||||
|
||||
it("should not allow non-owner to update GCP root CA pubkey hash", async () => {
|
||||
await expect(
|
||||
deployedActors.registrySelfrica.connect(deployedActors.user1).updateGCPRootCAPubkeyHash(12345n),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "AccessControlUnauthorizedAccount");
|
||||
deployedActors.registryKyc.connect(deployedActors.user1).updateGCPRootCAPubkeyHash(12345n),
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "AccessControlUnauthorizedAccount");
|
||||
});
|
||||
|
||||
it("should fail with INVALID_IMAGE when image hash not in PCR0Manager", async () => {
|
||||
@@ -259,21 +249,21 @@ describe("Selfrica Registration test", function () {
|
||||
];
|
||||
|
||||
await expect(
|
||||
deployedActors.registrySelfrica.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "INVALID_IMAGE");
|
||||
deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_IMAGE");
|
||||
});
|
||||
|
||||
it("should not allow non-owner to update GCP JWT verifier", async () => {
|
||||
await expect(
|
||||
deployedActors.registrySelfrica
|
||||
deployedActors.registryKyc
|
||||
.connect(deployedActors.user1)
|
||||
.updateGCPJWTVerifier(ethers.Wallet.createRandom().address),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "AccessControlUnauthorizedAccount");
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "AccessControlUnauthorizedAccount");
|
||||
});
|
||||
|
||||
it("should allow owner to update GCP JWT verifier", async () => {
|
||||
const newVerifier = ethers.Wallet.createRandom().address;
|
||||
await deployedActors.registrySelfrica.updateGCPJWTVerifier(newVerifier);
|
||||
await deployedActors.registryKyc.updateGCPJWTVerifier(newVerifier);
|
||||
});
|
||||
|
||||
describe("TEE Access Control", () => {
|
||||
@@ -290,28 +280,28 @@ describe("Selfrica Registration test", function () {
|
||||
];
|
||||
|
||||
await expect(
|
||||
deployedActors.registrySelfrica
|
||||
deployedActors.registryKyc
|
||||
.connect(deployedActors.user1)
|
||||
.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "ONLY_TEE_CAN_ACCESS");
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "ONLY_TEE_CAN_ACCESS");
|
||||
});
|
||||
|
||||
it("should not allow non-owner to update TEE", async () => {
|
||||
await expect(
|
||||
deployedActors.registrySelfrica.connect(deployedActors.user1).updateTEE(ethers.Wallet.createRandom().address),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "AccessControlUnauthorizedAccount");
|
||||
deployedActors.registryKyc.connect(deployedActors.user1).updateTEE(ethers.Wallet.createRandom().address),
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "AccessControlUnauthorizedAccount");
|
||||
});
|
||||
|
||||
it("should allow owner to update TEE", async () => {
|
||||
const newTee = ethers.Wallet.createRandom().address;
|
||||
await deployedActors.registrySelfrica.updateTEE(newTee);
|
||||
expect(await deployedActors.registrySelfrica.tee()).to.equal(newTee);
|
||||
await deployedActors.registryKyc.updateTEE(newTee);
|
||||
expect(await deployedActors.registryKyc.tee()).to.equal(newTee);
|
||||
});
|
||||
|
||||
it("should fail with TEE_NOT_SET when TEE address is zero", async () => {
|
||||
// Deploy minimal fresh registry to test uninitialized TEE state
|
||||
const freshImpl = await (
|
||||
await ethers.getContractFactory("IdentityRegistrySelfricaImplV1", {
|
||||
await ethers.getContractFactory("IdentityRegistryKycImplV1", {
|
||||
libraries: { PoseidonT3: deployedActors.poseidonT3.target },
|
||||
})
|
||||
).deploy();
|
||||
@@ -324,7 +314,7 @@ describe("Selfrica Registration test", function () {
|
||||
await ethers.getContractFactory("IdentityRegistry")
|
||||
).deploy(freshImpl.target, initData);
|
||||
|
||||
const freshRegistry = await ethers.getContractAt("IdentityRegistrySelfricaImplV1", freshProxy.target);
|
||||
const freshRegistry = await ethers.getContractAt("IdentityRegistryKycImplV1", freshProxy.target);
|
||||
await freshRegistry.updateGCPJWTVerifier(deployedActors.gcpJwtVerifier.target);
|
||||
|
||||
const mockPubSignals: bigint[] = [
|
||||
@@ -373,13 +363,13 @@ describe("Selfrica Registration test", function () {
|
||||
];
|
||||
|
||||
await expect(
|
||||
deployedActors.registrySelfrica.registerPubkeyCommitment(
|
||||
deployedActors.registryKyc.registerPubkeyCommitment(
|
||||
mockProof.a,
|
||||
mockProof.b,
|
||||
mockProof.c,
|
||||
mockPubSignalsPast,
|
||||
),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "INVALID_TIMESTAMP");
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_TIMESTAMP");
|
||||
|
||||
// Create a timestamp 2 hours in the future (more than 1 hour threshold)
|
||||
const nextHourDate = getCurrentDateDigitsYYMMDDHHMMSS(2);
|
||||
@@ -396,13 +386,13 @@ describe("Selfrica Registration test", function () {
|
||||
];
|
||||
|
||||
await expect(
|
||||
deployedActors.registrySelfrica.registerPubkeyCommitment(
|
||||
deployedActors.registryKyc.registerPubkeyCommitment(
|
||||
mockProof.a,
|
||||
mockProof.b,
|
||||
mockProof.c,
|
||||
mockPubSignalsFuture,
|
||||
),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "INVALID_TIMESTAMP");
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_TIMESTAMP");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -413,7 +403,7 @@ describe("Selfrica Registration test", function () {
|
||||
const MockVerifierFactory = await ethers.getContractFactory("MockGCPJWTVerifier");
|
||||
mockVerifier = await MockVerifierFactory.deploy();
|
||||
await mockVerifier.waitForDeployment();
|
||||
await deployedActors.registrySelfrica.updateGCPJWTVerifier(mockVerifier.target);
|
||||
await deployedActors.registryKyc.updateGCPJWTVerifier(mockVerifier.target);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -434,13 +424,8 @@ describe("Selfrica Registration test", function () {
|
||||
];
|
||||
|
||||
await expect(
|
||||
deployedActors.registrySelfrica.registerPubkeyCommitment(
|
||||
mockProof.a,
|
||||
mockProof.b,
|
||||
mockProof.c,
|
||||
mockPubSignals,
|
||||
),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "INVALID_PROOF");
|
||||
deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_PROOF");
|
||||
});
|
||||
|
||||
it("should fail with INVALID_ROOT_CA when root CA hash does not match", async () => {
|
||||
@@ -456,13 +441,8 @@ describe("Selfrica Registration test", function () {
|
||||
];
|
||||
|
||||
await expect(
|
||||
deployedActors.registrySelfrica.registerPubkeyCommitment(
|
||||
mockProof.a,
|
||||
mockProof.b,
|
||||
mockProof.c,
|
||||
mockPubSignals,
|
||||
),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "INVALID_ROOT_CA");
|
||||
deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_ROOT_CA");
|
||||
});
|
||||
|
||||
it("should fail with INVALID_IMAGE when image hash not in PCR0Manager", async () => {
|
||||
@@ -478,13 +458,8 @@ describe("Selfrica Registration test", function () {
|
||||
];
|
||||
|
||||
await expect(
|
||||
deployedActors.registrySelfrica.registerPubkeyCommitment(
|
||||
mockProof.a,
|
||||
mockProof.b,
|
||||
mockProof.c,
|
||||
mockPubSignals,
|
||||
),
|
||||
).to.be.revertedWithCustomError(deployedActors.registrySelfrica, "INVALID_IMAGE");
|
||||
deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
||||
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_IMAGE");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
11
package.json
11
package.json
@@ -37,16 +37,17 @@
|
||||
"types": "yarn workspaces foreach --topological-dev --parallel --exclude @selfxyz/contracts --exclude @selfxyz/common --exclude @selfxyz/mobile-app -i --all run types"
|
||||
},
|
||||
"resolutions": {
|
||||
"@babel/core": "^7.28.4",
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@babel/core": "^7.28.6",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@noble/curves": "1.9.7",
|
||||
"@noble/hashes": "1.8.0",
|
||||
"ethereum-cryptography": "3.2.0",
|
||||
"@swc/core": "1.7.36",
|
||||
"@tamagui/animations-react-native": "1.126.14",
|
||||
"@tamagui/toast": "1.126.14",
|
||||
"@types/node": "^22.18.3",
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"ethereum-cryptography": "3.2.0",
|
||||
"punycode": "npm:punycode.js@^2.3.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -56,7 +57,7 @@
|
||||
"react-native-webview": "13.16.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.3",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"js-sha1": "^0.7.0",
|
||||
"react": "^18.3.1",
|
||||
"react-native": "0.76.9",
|
||||
@@ -71,7 +72,7 @@
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.2"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"engines": {
|
||||
|
||||
@@ -149,14 +149,14 @@
|
||||
"watch": "pkill -f 'tsup.*--watch' 2>/dev/null || true && tsup && yarn postbuild && tsup --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.3",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@selfxyz/common": "workspace:^",
|
||||
"@selfxyz/euclid": "^0.6.1",
|
||||
"@xstate/react": "^5.0.5",
|
||||
"node-forge": "^1.3.1",
|
||||
"react-native-nfc-manager": "^3.17.1",
|
||||
"node-forge": "^1.3.3",
|
||||
"react-native-nfc-manager": "^3.17.2",
|
||||
"react-native-svg-circle-country-flags": "^0.2.2",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"uuid": "^11.1.0",
|
||||
"xstate": "^5.20.2",
|
||||
"zustand": "^4.5.2"
|
||||
@@ -183,11 +183,14 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-native": "0.76.9",
|
||||
"react-native-blur-effect": "^1.1.3",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-localize": "^3.5.2",
|
||||
"react-native-web": "^0.21.1",
|
||||
"react-native-localize": "^3.6.1",
|
||||
"react-native-svg": "15.15.1",
|
||||
"react-native-web": "^0.21.2",
|
||||
"react-native-webview": "13.16.0",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -224,5 +224,7 @@ export function createSelfClient({
|
||||
useSelfAppStore,
|
||||
useProtocolStore,
|
||||
useMRZStore,
|
||||
// Expose config for internal SDK use
|
||||
config: cfg,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import type { Config } from '../types/public';
|
||||
|
||||
export const defaultConfig: Required<Config> = {
|
||||
export const defaultConfig: Omit<Required<Config>, 'devConfig'> & Pick<Config, 'devConfig'> = {
|
||||
timeouts: { scanMs: 60000 },
|
||||
// in future this can be used to enable/disable experimental features
|
||||
features: {},
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
|
||||
import type { Config } from '../types/public';
|
||||
|
||||
export function mergeConfig(base: Required<Config>, override: Config): Required<Config> {
|
||||
type BaseConfig = Omit<Required<Config>, 'devConfig'> & Pick<Config, 'devConfig'>;
|
||||
|
||||
export function mergeConfig(base: BaseConfig, override: Config): BaseConfig {
|
||||
return {
|
||||
...base,
|
||||
...override,
|
||||
timeouts: { ...base.timeouts, ...(override.timeouts ?? {}) },
|
||||
features: { ...base.features, ...(override.features ?? {}) },
|
||||
devConfig: override.devConfig ?? base.devConfig,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ import { checkScannedInfo, formatDateToYYMMDD } from '../../processing/mrz';
|
||||
import { SdkEvents } from '../../types/events';
|
||||
import type { MRZInfo } from '../../types/public';
|
||||
|
||||
// Dev-only error injection - uses injected devConfig from SDK context
|
||||
// No cross-package requires needed
|
||||
|
||||
export type { MRZScannerViewProps } from '../../components/MRZScannerView';
|
||||
export { MRZScannerView } from '../../components/MRZScannerView';
|
||||
|
||||
@@ -28,12 +31,25 @@ const calculateScanDurationSeconds = (scanStartTimeRef: RefObject<number>) => {
|
||||
|
||||
export function useReadMRZ(scanStartTimeRef: RefObject<number>) {
|
||||
const selfClient = useSelfClient();
|
||||
const shouldTrigger = selfClient.config?.devConfig?.shouldTrigger;
|
||||
|
||||
return {
|
||||
onPassportRead: useCallback(
|
||||
(error: Error | null, result?: MRZInfo) => {
|
||||
const scanDurationSeconds = calculateScanDurationSeconds(scanStartTimeRef);
|
||||
|
||||
// Dev-only: Check for injected unknown error
|
||||
if (shouldTrigger?.('mrz_unknown_error')) {
|
||||
console.log('[DEV] Injecting MRZ unknown error');
|
||||
selfClient.trackEvent(PassportEvents.CAMERA_SCAN_FAILED, {
|
||||
reason: 'unknown_error',
|
||||
error: 'Injected error for testing',
|
||||
duration_seconds: parseFloat(scanDurationSeconds),
|
||||
});
|
||||
selfClient.emit(SdkEvents.DOCUMENT_MRZ_READ_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -63,7 +79,16 @@ export function useReadMRZ(scanStartTimeRef: RefObject<number>) {
|
||||
const formattedDateOfBirth = Platform.OS === 'ios' ? formatDateToYYMMDD(dateOfBirth) : dateOfBirth;
|
||||
const formattedDateOfExpiry = Platform.OS === 'ios' ? formatDateToYYMMDD(dateOfExpiry) : dateOfExpiry;
|
||||
|
||||
if (!checkScannedInfo(documentNumber, formattedDateOfBirth, formattedDateOfExpiry)) {
|
||||
// Dev-only: Check for injected invalid format error
|
||||
const shouldInjectInvalidFormat = shouldTrigger?.('mrz_invalid_format') || false;
|
||||
|
||||
if (
|
||||
shouldInjectInvalidFormat ||
|
||||
!checkScannedInfo(documentNumber, formattedDateOfBirth, formattedDateOfExpiry)
|
||||
) {
|
||||
if (shouldInjectInvalidFormat) {
|
||||
console.log('[DEV] Injecting MRZ invalid format error');
|
||||
}
|
||||
selfClient.trackEvent(PassportEvents.CAMERA_SCAN_FAILED, {
|
||||
reason: 'invalid_format',
|
||||
passportNumberLength: documentNumber.length,
|
||||
@@ -90,7 +115,7 @@ export function useReadMRZ(scanStartTimeRef: RefObject<number>) {
|
||||
|
||||
selfClient.emit(SdkEvents.DOCUMENT_MRZ_READ_SUCCESS);
|
||||
},
|
||||
[selfClient],
|
||||
[selfClient, shouldTrigger],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -138,5 +138,6 @@ export { parseNFCResponse, scanNFC } from './nfc';
|
||||
export { reactNativeScannerAdapter } from './adapters/react-native/nfc-scanner';
|
||||
export { sanitizeErrorMessage } from './utils/utils';
|
||||
export { useCountries } from './documents/useCountries';
|
||||
export { useMRZStore } from './stores/mrzStore';
|
||||
|
||||
export { webNFCScannerShim } from './adapters/web/shims';
|
||||
|
||||
@@ -35,6 +35,18 @@ export interface Config {
|
||||
* treated as `false` and the SDK will continue using legacy flows.
|
||||
*/
|
||||
features?: Record<string, boolean>;
|
||||
/**
|
||||
* Optional dev-mode configuration for error injection and testing. Should
|
||||
* only be provided in development builds. Production builds should omit this.
|
||||
*/
|
||||
devConfig?: {
|
||||
/**
|
||||
* Callback to check if a specific error type should be injected for testing.
|
||||
* @param errorType - The type of error to check (e.g., 'mrz_unknown_error')
|
||||
* @returns true if the error should be injected, false otherwise
|
||||
*/
|
||||
shouldTrigger?: (errorType: string) => boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -375,6 +387,9 @@ export interface SelfClient {
|
||||
useProtocolStore: ReturnType<typeof create<ProtocolState, []>>;
|
||||
/** Zustand store hook mirroring {@link MRZState}. */
|
||||
useMRZStore: ReturnType<typeof create<MRZState, []>>;
|
||||
|
||||
/** The merged configuration object passed to createSelfClient. */
|
||||
config: Config;
|
||||
}
|
||||
|
||||
/** Function returned by {@link SelfClient.on} to detach a listener. */
|
||||
|
||||
@@ -8,7 +8,7 @@ import { mergeConfig } from '../src/config/merge';
|
||||
import type { Config } from '../src/types/public';
|
||||
|
||||
describe('mergeConfig', () => {
|
||||
const baseConfig: Required<Config> = {
|
||||
const baseConfig: Omit<Required<Config>, 'devConfig'> & Pick<Config, 'devConfig'> = {
|
||||
timeouts: {
|
||||
scanMs: 30000,
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"types": "yarn prebuild && tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.3",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@faker-js/faker": "^10.0.0",
|
||||
"@noble/hashes": "^1.5.0",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
@@ -46,16 +46,16 @@
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-keychain": "^10.0.0",
|
||||
"react-native-localize": "^3.5.4",
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-localize": "^3.6.1",
|
||||
"react-native-safe-area-context": "^5.6.2",
|
||||
"react-native-svg": "15.15.1",
|
||||
"react-native-vector-icons": "^10.3.0",
|
||||
"react-native-webview": "13.16.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"util": "^0.12.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/core": "^7.28.6",
|
||||
"@react-native-community/cli": "^16.0.3",
|
||||
"@react-native/gradle-plugin": "0.76.9",
|
||||
"@react-native/metro-config": "0.76.9",
|
||||
@@ -83,8 +83,8 @@
|
||||
"metro-react-native-babel-preset": "0.76.9",
|
||||
"prettier": "^3.6.2",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-native-svg-transformer": "^1.5.1",
|
||||
"typescript": "^5.9.2",
|
||||
"react-native-svg-transformer": "^1.5.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
diff --git a/node_modules/@sumsub/react-native-mobilesdk-module/android/build.gradle b/node_modules/@sumsub/react-native-mobilesdk-module/android/build.gradle
|
||||
index 1234567..abcdefg 100644
|
||||
index 0000000..0000001 100644
|
||||
--- a/node_modules/@sumsub/react-native-mobilesdk-module/android/build.gradle
|
||||
+++ b/node_modules/@sumsub/react-native-mobilesdk-module/android/build.gradle
|
||||
@@ -69,9 +69,9 @@ dependencies {
|
||||
@@ -77,11 +77,11 @@ dependencies {
|
||||
implementation "com.sumsub.sns:idensic-mobile-sdk:1.40.2"
|
||||
|
||||
// remove comment to enable Device Intelligence
|
||||
@@ -12,3 +12,7 @@ index 1234567..abcdefg 100644
|
||||
- // implementation "com.sumsub.sns:idensic-mobile-sdk-videoident:1.40.2"
|
||||
+ implementation "com.sumsub.sns:idensic-mobile-sdk-videoident:1.40.2"
|
||||
// remove comment if you need EID support
|
||||
// implementation "com.sumsub.sns:idensic-mobile-sdk-eid:1.40.2"
|
||||
// remove comment if you need NFC support
|
||||
// implementation "com.sumsub.sns:idensic-mobile-sdk-nfc:1.40.2"
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
diff --git a/node_modules/ethereum-cryptography/utils.js b/node_modules/ethereum-cryptography/utils.js
|
||||
index cedfa36..b494c49 100644
|
||||
--- a/node_modules/ethereum-cryptography/utils.js
|
||||
+++ b/node_modules/ethereum-cryptography/utils.js
|
||||
@@ -8,11 +8,18 @@ exports.bytesToUtf8 = bytesToUtf8;
|
||||
exports.hexToBytes = hexToBytes;
|
||||
exports.equalsBytes = equalsBytes;
|
||||
exports.wrapHash = wrapHash;
|
||||
-const _assert_1 = __importDefault(require("@noble/hashes/_assert"));
|
||||
+const assertModule = __importDefault(require("@noble/hashes/_assert"));
|
||||
const utils_1 = require("@noble/hashes/utils");
|
||||
-const assertBool = _assert_1.default.bool;
|
||||
+const assertBool = (assertModule.default && assertModule.default.bool) ||
|
||||
+ assertModule.bool ||
|
||||
+ ((value) => {
|
||||
+ if (typeof value !== "boolean")
|
||||
+ throw new TypeError(`Expected boolean, not ${value}`);
|
||||
+ });
|
||||
exports.assertBool = assertBool;
|
||||
-const assertBytes = _assert_1.default.bytes;
|
||||
+const assertBytes = (assertModule.default && assertModule.default.bytes) ||
|
||||
+ assertModule.bytes ||
|
||||
+ assertModule.abytes;
|
||||
exports.assertBytes = assertBytes;
|
||||
var utils_2 = require("@noble/hashes/utils");
|
||||
Object.defineProperty(exports, "bytesToHex", { enumerable: true, get: function () { return utils_2.bytesToHex; } });
|
||||
diff --git a/node_modules/ethereum-cryptography/esm/utils.js b/node_modules/ethereum-cryptography/esm/utils.js
|
||||
index 8e771ea..b3eed9d 100644
|
||||
--- a/node_modules/ethereum-cryptography/esm/utils.js
|
||||
+++ b/node_modules/ethereum-cryptography/esm/utils.js
|
||||
@@ -1,7 +1,11 @@
|
||||
import assert from "@noble/hashes/_assert";
|
||||
import { hexToBytes as _hexToBytes } from "@noble/hashes/utils";
|
||||
-const assertBool = assert.bool;
|
||||
-const assertBytes = assert.bytes;
|
||||
+const assertBool = (assert?.bool) ||
|
||||
+ ((value) => {
|
||||
+ if (typeof value !== "boolean")
|
||||
+ throw new TypeError(`Expected boolean, not ${value}`);
|
||||
+ });
|
||||
+const assertBytes = assert.bytes || assert.abytes;
|
||||
export { assertBool, assertBytes };
|
||||
export { bytesToHex, bytesToHex as toHex, concatBytes, createView, utf8ToBytes } from "@noble/hashes/utils";
|
||||
// buf.toString('utf8') -> bytesToUtf8(buf)
|
||||
15
patches/react-native-gesture-handler+2.30.0.patch
Normal file
15
patches/react-native-gesture-handler+2.30.0.patch
Normal file
@@ -0,0 +1,15 @@
|
||||
diff --git a/node_modules/react-native-gesture-handler/android/build.gradle b/node_modules/react-native-gesture-handler/android/build.gradle
|
||||
--- a/node_modules/react-native-gesture-handler/android/build.gradle
|
||||
+++ b/node_modules/react-native-gesture-handler/android/build.gradle
|
||||
@@ -229,9 +229,10 @@
|
||||
}
|
||||
|
||||
def kotlin_version = safeExtGet('kotlinVersion', project.properties['RNGH_kotlinVersion'])
|
||||
+def reactNativeDependency = safeExtGet("reactNativeDependency", "com.facebook.react:react-android")
|
||||
|
||||
dependencies {
|
||||
- implementation 'com.facebook.react:react-native:+' // from node_modules
|
||||
+ implementation reactNativeDependency
|
||||
|
||||
|
||||
if (shouldUseCommonInterfaceFromReanimated()) {
|
||||
@@ -1,98 +0,0 @@
|
||||
diff --git a/node_modules/react-native-svg/android/.project b/node_modules/react-native-svg/android/.project
|
||||
new file mode 100644
|
||||
index 0000000..dd0da62
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native-svg/android/.project
|
||||
@@ -0,0 +1,28 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<projectDescription>
|
||||
+ <name>react-native-svg</name>
|
||||
+ <comment>Project OpenPassport-android-react-native-svg created by Buildship.</comment>
|
||||
+ <projects>
|
||||
+ </projects>
|
||||
+ <buildSpec>
|
||||
+ <buildCommand>
|
||||
+ <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
+ <arguments>
|
||||
+ </arguments>
|
||||
+ </buildCommand>
|
||||
+ </buildSpec>
|
||||
+ <natures>
|
||||
+ <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
+ </natures>
|
||||
+ <filteredResources>
|
||||
+ <filter>
|
||||
+ <id>1759738232589</id>
|
||||
+ <name></name>
|
||||
+ <type>30</type>
|
||||
+ <matcher>
|
||||
+ <id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
+ <arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
+ </matcher>
|
||||
+ </filter>
|
||||
+ </filteredResources>
|
||||
+</projectDescription>
|
||||
diff --git a/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp b/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp
|
||||
index 11718dd..fe993b3 100644
|
||||
--- a/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp
|
||||
+++ b/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp
|
||||
@@ -28,8 +28,8 @@ void RNSVGLayoutableShadowNode::setZeroDimensions() {
|
||||
// views in the layout inspector when Yoga attempts to interpret SVG
|
||||
// properties like width when viewBox scale is set.
|
||||
auto style = yogaNode_.style();
|
||||
- style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(0));
|
||||
- style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(0));
|
||||
+ style.setDimension(yoga::Dimension::Width, yoga::StyleLength::points(0));
|
||||
+ style.setDimension(yoga::Dimension::Height, yoga::StyleLength::points(0));
|
||||
yogaNode_.setStyle(style);
|
||||
}
|
||||
|
||||
diff --git a/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp.bak b/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp.bak
|
||||
new file mode 100644
|
||||
index 0000000..11718dd
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp.bak
|
||||
@@ -0,0 +1,43 @@
|
||||
+#include "RNSVGLayoutableShadowNode.h"
|
||||
+#include <react/renderer/core/LayoutContext.h>
|
||||
+
|
||||
+namespace facebook::react {
|
||||
+
|
||||
+RNSVGLayoutableShadowNode::RNSVGLayoutableShadowNode(
|
||||
+ const ShadowNodeFragment &fragment,
|
||||
+ const ShadowNodeFamily::Shared &family,
|
||||
+ ShadowNodeTraits traits)
|
||||
+ : YogaLayoutableShadowNode(fragment, family, traits) {
|
||||
+ if (std::strcmp(this->getComponentName(), "RNSVGGroup") != 0) {
|
||||
+ setZeroDimensions();
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+RNSVGLayoutableShadowNode::RNSVGLayoutableShadowNode(
|
||||
+ const ShadowNode &sourceShadowNode,
|
||||
+ const ShadowNodeFragment &fragment)
|
||||
+ : YogaLayoutableShadowNode(sourceShadowNode, fragment) {
|
||||
+ if (std::strcmp(this->getComponentName(), "RNSVGGroup") != 0) {
|
||||
+ setZeroDimensions();
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+void RNSVGLayoutableShadowNode::setZeroDimensions() {
|
||||
+ // SVG handles its layout manually on the native side and does not depend on
|
||||
+ // the Yoga layout. Setting the dimensions to 0 eliminates randomly positioned
|
||||
+ // views in the layout inspector when Yoga attempts to interpret SVG
|
||||
+ // properties like width when viewBox scale is set.
|
||||
+ auto style = yogaNode_.style();
|
||||
+ style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(0));
|
||||
+ style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(0));
|
||||
+ yogaNode_.setStyle(style);
|
||||
+}
|
||||
+
|
||||
+void RNSVGLayoutableShadowNode::layout(LayoutContext layoutContext) {
|
||||
+ auto affectedNodes = layoutContext.affectedNodes;
|
||||
+ layoutContext.affectedNodes = nullptr;
|
||||
+ YogaLayoutableShadowNode::layout(layoutContext);
|
||||
+ layoutContext.affectedNodes = affectedNodes;
|
||||
+}
|
||||
+
|
||||
+} // namespace facebook::react
|
||||
@@ -88,30 +88,45 @@ if (!isExecutableAvailableOnPath('patch-package')) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Workspaces with isolated node_modules due to nmHoistingLimits: workspaces
|
||||
// Most packages are in workspace node_modules, not root
|
||||
const workspaceRoots = [
|
||||
{ name: 'app', path: path.join(repositoryRootPath, 'app') },
|
||||
{ name: 'contracts', path: path.join(repositoryRootPath, 'contracts') }
|
||||
];
|
||||
|
||||
// Run patch-package with better error handling
|
||||
try {
|
||||
const rootPatchRun = spawnSync('patch-package', ['--patch-dir', 'patches'], {
|
||||
cwd: repositoryRootPath,
|
||||
shell: true,
|
||||
stdio: isCI ? 'pipe' : 'inherit',
|
||||
timeout: 30000
|
||||
});
|
||||
if (rootPatchRun.status === 0) {
|
||||
if (!isCI) console.log('✓ Patches applied to root workspace');
|
||||
} else {
|
||||
const errorOutput = rootPatchRun.stderr?.toString() || rootPatchRun.stdout?.toString() || '';
|
||||
console.error(`patch-package failed for root workspace (exit code ${rootPatchRun.status})`);
|
||||
if (errorOutput) console.error(errorOutput);
|
||||
if (!isCI) process.exit(1);
|
||||
let anyPatchApplied = false;
|
||||
let anyPatchFailed = false;
|
||||
|
||||
// Try root node_modules first (some packages may be hoisted here)
|
||||
const rootNodeModules = path.join(repositoryRootPath, 'node_modules');
|
||||
if (fs.existsSync(rootNodeModules)) {
|
||||
const rootPatchRun = spawnSync('patch-package', ['--patch-dir', 'patches'], {
|
||||
cwd: repositoryRootPath,
|
||||
shell: true,
|
||||
stdio: 'pipe', // Always capture output to check for real errors vs missing packages
|
||||
timeout: 30000
|
||||
});
|
||||
const output = rootPatchRun.stdout?.toString() || '';
|
||||
const stderrOutput = rootPatchRun.stderr?.toString() || '';
|
||||
const hasRealError = (output.includes('**ERROR**') && !output.includes('which is not present at')) ||
|
||||
(stderrOutput.length > 0 && rootPatchRun.status !== 0);
|
||||
|
||||
if (rootPatchRun.status === 0) {
|
||||
if (!isCI) console.log('✓ Patches applied to root workspace');
|
||||
anyPatchApplied = true;
|
||||
} else if (hasRealError) {
|
||||
console.error(`patch-package failed for root workspace`);
|
||||
console.error(output);
|
||||
if (stderrOutput) console.error(stderrOutput);
|
||||
anyPatchFailed = true;
|
||||
}
|
||||
// If packages are just missing (not hoisted to root), that's expected - continue to workspace patches
|
||||
}
|
||||
|
||||
// Also patch app/node_modules if it exists
|
||||
// Workspaces with isolated node_modules due to limited hoisting
|
||||
const workspaceRoots = [
|
||||
{ name: 'app', path: path.join(repositoryRootPath, 'app') },
|
||||
{ name: 'contracts', path: path.join(repositoryRootPath, 'contracts') }
|
||||
];
|
||||
|
||||
// Apply patches to workspace node_modules (where most packages are with nmHoistingLimits)
|
||||
for (const workspace of workspaceRoots) {
|
||||
const workspaceNodeModules = path.join(workspace.path, 'node_modules');
|
||||
if (!fs.existsSync(workspaceNodeModules)) continue;
|
||||
@@ -119,19 +134,41 @@ try {
|
||||
const workspacePatchRun = spawnSync('patch-package', ['--patch-dir', '../patches'], {
|
||||
cwd: workspace.path,
|
||||
shell: true,
|
||||
stdio: isCI ? 'pipe' : 'inherit',
|
||||
stdio: 'pipe',
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
const output = workspacePatchRun.stdout?.toString() || '';
|
||||
const stderrOutput = workspacePatchRun.stderr?.toString() || '';
|
||||
const hasRealError = (output.includes('**ERROR**') && !output.includes('which is not present at')) ||
|
||||
(stderrOutput.length > 0 && workspacePatchRun.status !== 0);
|
||||
|
||||
if (workspacePatchRun.status === 0) {
|
||||
if (!isCI) console.log(`✓ Patches applied to ${workspace.name} workspace`);
|
||||
} else {
|
||||
const errorOutput = workspacePatchRun.stderr?.toString() || workspacePatchRun.stdout?.toString() || '';
|
||||
console.error(`patch-package failed for ${workspace.name} workspace (exit code ${workspacePatchRun.status})`);
|
||||
if (errorOutput) console.error(errorOutput);
|
||||
if (!isCI) process.exit(1);
|
||||
anyPatchApplied = true;
|
||||
} else if (hasRealError) {
|
||||
console.error(`patch-package failed for ${workspace.name} workspace`);
|
||||
console.error(output);
|
||||
if (stderrOutput) console.error(stderrOutput);
|
||||
anyPatchFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyPatchFailed && !isCI) {
|
||||
console.error('Some patches failed to apply. Check if patch versions match installed package versions.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (anyPatchFailed && isCI) {
|
||||
console.warn('⚠️ CI Warning: Some patches failed to apply. Review patch compatibility.');
|
||||
}
|
||||
|
||||
if (anyPatchApplied) {
|
||||
if (!isCI) console.log('✓ patch-package completed');
|
||||
else console.log('patch-package completed');
|
||||
} else {
|
||||
if (!isCI) console.log('patch-package: no patches applied (packages may be in different locations)');
|
||||
else console.log('patch-package: no patches applied');
|
||||
}
|
||||
} catch (error) {
|
||||
if (isCI) {
|
||||
console.log('patch-package: error during execution (CI mode):', error.message);
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"js-sha1": "^0.7.0",
|
||||
"js-sha256": "^0.11.0",
|
||||
"js-sha512": "^0.9.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-forge": "^1.3.3",
|
||||
"poseidon-lite": "^0.3.0",
|
||||
"snarkjs": "^0.7.4",
|
||||
"uuid": "^11.1.0"
|
||||
@@ -72,7 +72,7 @@
|
||||
"ts-node": "^10.9.2",
|
||||
"tsup": "^8.5.0",
|
||||
"typechain": "^8.3.2",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript": "^5.9.3",
|
||||
"webpack": "^5.95.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"dependencies": {
|
||||
"angularx-qrcode": "^20.0.0",
|
||||
"lottie-web": "^5.12.2",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -68,7 +68,7 @@
|
||||
"prettier": "^3.5.3",
|
||||
"rxjs": "^7.8.0",
|
||||
"tslib": "^2.6.0",
|
||||
"typescript": "~5.9.0",
|
||||
"typescript": "~5.9.3",
|
||||
"zone.js": "^0.15.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -72,11 +72,11 @@
|
||||
"js-sha256": "^0.11.0",
|
||||
"js-sha512": "^0.9.0",
|
||||
"lottie-react": "^2.4.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-forge": "^1.3.3",
|
||||
"poseidon-lite": "^0.3.0",
|
||||
"qrcode.react": "^4.1.0",
|
||||
"react-spinners": "^0.14.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -104,7 +104,7 @@
|
||||
"ts-mocha": "^10.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript": "^5.9.3",
|
||||
"webpack": "^5.95.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -32,6 +32,6 @@
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.2"
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user