Skip to content

perf: use native WebCrypto implementation - #36645

Merged
tommasini merged 4 commits into
mainfrom
perf/native-webcrypto-subtle
Sep 25, 2026
Merged

tommasini merged 4 commits into
mainfrom
perf/native-webcrypto-subtle

Conversation

@tommasini

@tommasini tommasini commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

Background

This PR expands the WebCrypto API exposed at global.crypto.subtle from a digest-only shim to the complete native Subtle instance provided by react-native-quick-crypto@1.1.5.

The digest-only behavior was intentional when it was introduced in #23769: MetaMask Mobile was using react-native-quick-crypto@0.7.15, whose broader SubtleCrypto implementation was still partial, while digest had the parity needed to accelerate SHA-256 verification of the Snaps registry.

The dependency was subsequently upgraded from patched 0.7.15 to 1.1.5 in #30716. Version 1.1.5 has native implementations for the standard SubtleCrypto operation families documented in the implementation coverage matrix, including encryption/decryption, key generation/import/export, derivation, signing/verification, and key wrapping.

What is being replaced

Before this change, the effective runtime shape was:

global.crypto.subtle = { digest: quickCryptoSubtle.digest }

Although the old code spread global.crypto.subtle and crypto.subtle, neither supplied a broader implementation in the React Native runtime:

  • react-native-get-random-values initializes global.crypto with getRandomValues, but does not provide subtle.
  • Metro resolves require('crypto') to react-native-crypto in metro.config.js; that JavaScript polyfill does not export subtle.
  • The explicit digest override was therefore the only effective SubtleCrypto method.

After this change:

global.crypto.subtle = quickCryptoSubtle

This exposes the actual native-backed Subtle instance. Assigning the instance directly matters because its methods are class prototype methods and are not copied by object spread.

The newly exposed operation families are:

  • encrypt / decrypt
  • deriveBits / deriveKey
  • generateKey, importKey, and exportKey
  • sign / verify
  • wrapKey / unwrapKey
  • digest (unchanged native implementation)
  • Additional quick-crypto extensions such as encapsulation/decapsulation and getPublicKey

Known global WebCrypto consumers

The repository has two distinct consumer groups:

  • First-party wallet encryption, PBKDF2, deeplink verification, and Card PKCE import react-native-quick-crypto directly. Those paths already used the package singleton and are not changed by this shim.
  • Global WebCrypto consumers are affected by this PR:
    • @metamask/snaps-registry uses globalThis.crypto.subtle.digest for its SHA-256 registry-verification fast path. The same helper is bundled into app/core/InpageBridgeWeb3.js.
    • multiformats and jsontokens use global subtle.digest where available.
    • node-forge feature-detects global SubtleCrypto and may now select WebCrypto-backed RSA paths that were unavailable with the digest-only object.
    • @solana-mobile/mobile-wallet-adapter-protocol uses global SubtleCrypto for key generation/import, derivation, signing, encryption, and decryption. These branches can now execute natively instead of failing or using a fallback.

The runtime matrix below validates the global surface and representative native primitives. It does not claim end-to-end coverage of every third-party feature flow; those dependency branches are the main residual integration risk.

Scope and compatibility

  • This changes only global.crypto.subtle. The existing top-level global.crypto merge, randomUUID, and getRandomValues wiring are unchanged.
  • The existing Snaps registry digest path remains on the same native implementation.
  • Code that imports react-native-quick-crypto directly is unaffected; it already had access to the complete package API.
  • This does not change wallet vault formats, keychain storage, or the Encryptor implementation. Wallet unlock tests are therefore general regressions, not direct proof of this shim.
  • The primary compatibility risk is native binary/JavaScript version skew. The runtime test must use a development build containing quick-crypto and quick-base64 versions matching this branch, on both iOS and Android.

Validation

subtle-coverage-evidence.pdf

Package-level algorithm and error-path coverage remains owned by react-native-quick-crypto's native iOS and Android SubtleCrypto suites. App-level validation for this PR was executed through global.crypto.subtle inside Hermes, rather than through a direct package import.

The same runtime matrix passed on both branch-matched native builds:

  • iOS: iPhone 15 Pro simulator
  • Android: API 36 arm64 emulator
  • Confirmed the global object is the quick-crypto Subtle instance.
  • Confirmed all 12 standard methods are callable: decrypt, deriveBits, deriveKey, digest, encrypt, exportKey, generateKey, importKey, sign, unwrapKey, verify, and wrapKey.
  • SHA-256 known vector: SHA-256("abc") returned ba7816bf...15ad on both platforms.
  • AES-256-GCM: generated a native key and successfully encrypted/decrypted MetaMask native WebCrypto on both platforms.
  • PBKDF2-HMAC-SHA-256 known vector: password password, salt salt, one iteration returned 120fb6cf...be17b on both platforms.
  • ECDSA P-256/SHA-256: generated a native key pair, produced a 64-byte signature, verified the original message, and rejected a modified message on both platforms.
  • Both native builds completed successfully with QuickCrypto 1.1.5 and quick-base64 3.0.0 linked into the app.

Supporting validation:

  • All 10 CI unit-test shards passed.
  • 126 focused crypto-related Jest tests passed, but these mostly exercise direct quick-crypto imports or unrelated wallet encryption paths; they are supporting regressions and are not treated as proof of global runtime wiring.
  • ESLint and Prettier pass for shim.js (four unrelated existing deep-import warnings remain).
  • Full TypeScript validation reports existing errors in unrelated files; shim.js introduces none.

The remaining failed component-view CI shard exhausted its 12 GB Node heap after its tests passed. It does not exercise shim.js or native crypto and is unrelated to this change.

Changelog

CHANGELOG entry: null

Related issues

Refs: #23769, #30716

Manual testing steps

Feature: Native WebCrypto global shim

  Scenario Outline: global SubtleCrypto operations run through Hermes
    Given a <platform> development build containing the branch native dependencies
    And the app is connected to the branch Metro bundle

    When the WebCrypto runtime matrix is evaluated through global.crypto.subtle
    Then SHA-256 should match its known digest vector
    And AES-GCM should decrypt the generated ciphertext to the original plaintext
    And PBKDF2 should match its known derived-bits vector
    And an ECDSA signature should verify for the original message
    And the signature should not verify for a modified message

    Examples:
      | platform |
      | iOS      |
      | Android  |

  Scenario Outline: existing Snaps registry verification remains compatible
    Given MetaMask Mobile is running on <platform>
    And Snaps are enabled

    When the app fetches and verifies the Snaps registry
    Then registry verification should complete without a WebCrypto error
    And the available Snaps should be displayed

    Examples:
      | platform |
      | iOS      |
      | Android  |

Screenshots/Recordings

Before

N/A — no UI change.

After

N/A — no UI change.

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
  • I've tested with a power user scenario
    • Use these power-user SRPs to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Widens global SubtleCrypto for third-party code paths that may now run native crypto operations; digest-only callers should behave the same, but native binary/JS version skew remains the main integration risk.

Overview
global.crypto.subtle now exposes the full native Subtle instance from react-native-quick-crypto instead of a digest-only object built from spreads and a single digest override.

In shim.js, the polyfill assigns subtle: quickCryptoSubtle directly so prototype methods (encrypt/decrypt, key import/export, sign/verify, PBKDF2, etc.) are available to code that uses global WebCrypto—e.g. Snaps registry SHA-256, Solana mobile wallet adapter, and libraries that feature-detect subtle. digest stays on the same native path; only the global surface widens from { digest } to the complete implementation.

Reviewed by Cursor Bugbot for commit f4b4592. Bugbot is set up for automated code reviews on this repo. Configure here.

Co-authored-by: Cursor <cursoragent@cursor.com>
@tommasini tommasini self-assigned this Sep 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@github-actions github-actions Bot added size-XS pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. labels Sep 22, 2026
@metamask-ci metamask-ci Bot added the team-mobile-platform Mobile Platform team label Sep 22, 2026
@github-project-automation github-project-automation Bot moved this to Needs dev review in PR review queue Sep 22, 2026
@tommasini tommasini removed the pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. label Sep 22, 2026
@tommasini
tommasini marked this pull request as ready for review September 22, 2026 15:52

@andrepimenta andrepimenta left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have a few questions before merging, will discuss in a call

@github-project-automation github-project-automation Bot moved this from Needs dev review to Needs more work from the author in PR review queue Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Smart E2E Test Selection

Selected E2E tags: ALL

Selected Performance tags: ALL

AI Confidence: 90

E2E reasoning

Expand to read

The change in shim.js replaces a partial crypto.subtle shim (that only overrode digest from quickCryptoSubtle while merging in global.crypto.subtle and crypto.subtle for all other methods) with the complete quickCryptoSubtle object. This means ALL SubtleCrypto methods (encrypt, decrypt, sign, verify, digest, generateKey, importKey, exportKey, deriveBits, deriveKey, wrapKey, unwrapKey, etc.) now come exclusively from react-native-quick-crypto instead of the native/Node.js implementation.

shim.js is a critical global polyfill that runs at app startup and sets global.crypto.subtle. This is used by:

  • Authentication and vault encryption/decryption (Encryptor, Vault, SecureKeychain)
  • Wallet creation and SRP import (key derivation)
  • Transaction signing and confirmations
  • WalletConnect and SDK connections
  • Seedless onboarding (OAuth/TOPRF flows)
  • Virtually every cryptographic operation in the app

Since this change affects the foundational cryptographic API used across the entire application, any flow that relies on SubtleCrypto could be impacted. The blast radius is the entire app. Running ALL E2E smoke tags is the appropriate conservative approach to ensure no cryptographic operations are broken across any user flow.

Performance reasoning

Expand to read

The change to shim.js replaces the partial crypto.subtle shim with the full quickCryptoSubtle implementation. Since crypto.subtle is used in cryptographic operations that underpin authentication (login/unlock), wallet creation, and onboarding flows, all performance scenarios that involve these flows could be affected. Specifically: @PerformanceLogin (unlock uses crypto), @PerformanceOnboarding (wallet creation uses key derivation), @PerformanceLaunch (startup path includes shim execution). Running all performance tags is the safe approach given the foundational nature of this change.

View run

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Performance Test Results

ℹ️ Performance test results are currently non-blocking and will not block this PR.

❌ 7 tests failed · 23 tests · 1 device

📱 Devices tested (1)

Android: Google Pixel 8 Pro (v14.0)

❌ Failed Tests (7)

🔬 App profiling vs main is included under each failed scenario that has a prior baseline.

@metamask-mobile-platform

Cold Start: Measure ColdStart To Login Screen

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 35991814370 · Baseline (last green on main) run 35962851073 @ e01b8ad

Summary: ⚠️ 5 metrics over +10%: CPU avg (+2.33 (+27.8%)), CPU max (+3.16 (+14.7%)), Memory avg (+72.1 (+12.3%)), Slow frames (+24.15 (+465.3%)), Issues (+1 (+50%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 8.39% 10.72% +2.33 (+27.8%) ⚠️
CPU max 21.56% 24.72% +3.16 (+14.7%) ⚠️
Memory avg 586.18 MB 658.28 MB +72.1 (+12.3%) ⚠️
Memory max 754.29 MB 787.35 MB +33.06 (+4.4%)
Slow frames 5.19% 29.34% +24.15 (+465.3%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 3 +1 (+50%) ⚠️
Critical issues 1 1 0 (0%)
App size 397.48 MB 397.58 MB +0.1 (+0%)

Measure Warm Start: Login To Wallet Screen

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 35991814370 · Baseline (last green on main) run 35962851073 @ e01b8ad

Summary: ⚠️ 2 metrics over +10%: Slow frames (+34.85 (+376.4%)), Critical issues (+1 (+100%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 8.03% 8.54% +0.51 (+6.4%)
CPU max 22.55% 23.41% +0.86 (+3.8%)
Memory avg 643.23 MB 572.3 MB -70.93 (-11%)
Memory max 777.44 MB 707.9 MB -69.54 (-8.9%)
Slow frames 9.26% 44.11% +34.85 (+376.4%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 2 0 (0%)
Critical issues 1 2 +1 (+100%) ⚠️
App size 397.48 MB 397.58 MB +0.1 (+0%)

Measure Warm Start: Warm Start to Login Screen

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 35991814370 · Baseline (last green on main) run 35635949154 @ 7bdb9cb

Summary: ⚠️ 3 metrics over +10%: CPU avg (+2.7 (+39.2%)), Memory avg (+97.06 (+17.3%)), Slow frames (+6.88 (+55%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 6.88% 9.58% +2.7 (+39.2%) ⚠️
CPU max 21.63% 23.25% +1.62 (+7.5%)
Memory avg 560.41 MB 657.47 MB +97.06 (+17.3%) ⚠️
Memory max 712.95 MB 782.86 MB +69.91 (+9.8%)
Slow frames 12.5% 19.38% +6.88 (+55%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 2 0 (0%)
Critical issues 1 1 0 (0%)
App size 396.81 MB 397.58 MB +0.77 (+0.2%)

@mm-perps-engineering-team

Perps add funds

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 35991814370 · Baseline (last green on main) run 34935384411 @ 4e14632

Summary: ✅ No metrics over the +10% baseline margin.

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 9.95% 9.1% -0.85 (-8.5%)
CPU max 22.93% 24.56% +1.63 (+7.1%)
Memory avg 702.75 MB 689.09 MB -13.66 (-1.9%)
Memory max 877.25 MB 836.73 MB -40.52 (-4.6%)
Slow frames 15.97% 10.62% -5.35 (-33.5%)
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 1 -1 (-50%)
Critical issues 1 1 0 (0%)
App size 394.88 MB 397.58 MB +2.7 (+0.7%)

Perps open position and close it

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 35991814370 · Baseline (last green on main) run 34966774120 @ acda9d8

Summary: ⚠️ 1 metric over +10%: Slow frames (+2.03 (+22.1%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 8.17% 7.83% -0.34 (-4.2%)
CPU max 25.69% 21.51% -4.18 (-16.3%)
Memory avg 826.35 MB 817.72 MB -8.63 (-1%)
Memory max 1069.37 MB 1010.74 MB -58.63 (-5.5%)
Slow frames 9.2% 11.23% +2.03 (+22.1%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 2 0 (0%)
Critical issues 2 1 -1 (-50%)
App size 394.9 MB 397.58 MB +2.68 (+0.7%)

@team-predict

Predict Deposit - Complete Flow Performance

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 35991814370 · Baseline (last run on main (scenario also failing)) run 35962851073 @ e01b8ad

⚠️ No green baseline on main — comparing against the latest usable profiling.

Summary: ⚠️ 3 metrics over +10%: CPU avg (+2.44 (+32%)), Memory max (+92.3 (+12.3%)), Slow frames (+2.02 (+15.4%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 7.63% 10.07% +2.44 (+32%) ⚠️
CPU max 21.97% 23.63% +1.66 (+7.6%)
Memory avg 668.2 MB 730.22 MB +62.02 (+9.3%)
Memory max 748.46 MB 840.76 MB +92.3 (+12.3%) ⚠️
Slow frames 13.1% 15.12% +2.02 (+15.4%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 2 0 (0%)
Critical issues 1 1 0 (0%)
App size 397.48 MB 397.58 MB +0.1 (+0%)

Predict Market Details - Complete Flow Performance

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 35991814370 · Baseline (last green on main) run 35962851073 @ e01b8ad

Summary: ✅ No metrics over the +10% baseline margin.

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 9.88% 7.89% -1.99 (-20.1%)
CPU max 21.66% 21.33% -0.33 (-1.5%)
Memory avg 676.97 MB 692.5 MB +15.53 (+2.3%)
Memory max 889.79 MB 782.12 MB -107.67 (-12.1%)
Slow frames 46.38% 10.34% -36.04 (-77.7%)
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 3 1 -2 (-66.7%)
Critical issues 2 1 -1 (-50%)
App size 397.48 MB 397.58 MB +0.1 (+0%)
✅ Passed Tests (16)
Test Platform Device Duration Team Recording
Aggregated Balance Loading Time, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 8.48s @assets-dev-team 📹 Watch
Asset View, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 1.74s @assets-dev-team 📹 Watch
Cross-chain swap flow - ETH to SOL - 50+ accounts, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 5.31s @swap-bridge-dev-team 📹 Watch
Swap flow - ETH to LINK, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 1.80s @swap-bridge-dev-team 📹 Watch
Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3 Android Google Pixel 8 Pro (v14.0) 4.19s @Accounts-team 📹 Watch
Predict Available Balance - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 0.00s @team-predict 📹 Watch
Rewards tab time-to-content (onboarding or dashboard) Android Google Pixel 8 Pro (v14.0) 0.90s @performance-team 📹 Watch
Measure Cold Start To Onboarding Screen Android Google Pixel 8 Pro (v14.0) 0.38s @metamask-mobile-platform 📹 Watch
Fresh SRP wallet creation performance Android Google Pixel 8 Pro (v14.0) 12.65s @metamask-onboarding-team 📹 Watch
Onboarding Import SRP with +50 accounts, SRP 3 Android Google Pixel 8 Pro (v14.0) 4.50s @metamask-onboarding-team 📹 Watch
Money Home after fresh wallet creation with empty balance Android Google Pixel 8 Pro (v14.0) 2.58s @mm-earn-team 📹 Watch
Money Home after importing SRP with funded balance Android Google Pixel 8 Pro (v14.0) 3.08s @mm-earn-team 📹 Watch
Account creation after fresh install Android Google Pixel 8 Pro (v14.0) 1.83s @metamask-onboarding-team 📹 Watch
Seedless Onboarding: Google Login New User Android Google Pixel 8 Pro (v14.0) 3.57s @metamask-onboarding-team 📹 Watch
Seedless Onboarding: Apple Login New User Android Google Pixel 8 Pro (v14.0) 3.80s @metamask-onboarding-team 📹 Watch
Seedless Onboarding: Telegram Login New User Android Google Pixel 8 Pro (v14.0) 2.85s @metamask-onboarding-team 📹 Watch

Branch: perf/native-webcrypto-subtle · Build: E2E · Commit: bb9c8d7 · View full run

@tommasini
tommasini added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 942c7ce Sep 25, 2026
125 checks passed
@tommasini
tommasini deleted the perf/native-webcrypto-subtle branch September 25, 2026 10:54
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 25, 2026
@metamask-ci metamask-ci Bot added the release-8.15.0 Issue or pull request that will be included in release 8.15.0 label Sep 25, 2026

This branch was successfully deployed

1 active deployment
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-8.15.0 Issue or pull request that will be included in release 8.15.0 risk:high AI analysis: high risk size-XS team-mobile-platform Mobile Platform team

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants