diff --git a/.github/actions/generate-github-token/action.yml b/.github/actions/generate-github-token/action.yml
new file mode 100644
index 000000000..43536397d
--- /dev/null
+++ b/.github/actions/generate-github-token/action.yml
@@ -0,0 +1,56 @@
+name: "Generate GitHub App Token"
+description: "Generates a GitHub App token for accessing repositories in the selfxyz organization"
+
+inputs:
+ app-id:
+ description: "The GitHub App ID"
+ required: true
+ private-key:
+ description: "The GitHub App private key"
+ required: true
+ configure-netrc:
+ description: "If true, writes a ~/.netrc entry for github.com using the generated token (useful for CocoaPods / git HTTPS fetches)"
+ required: false
+ default: "false"
+ netrc-machine:
+ description: "The machine hostname to write into ~/.netrc (default: github.com)"
+ required: false
+ default: "github.com"
+ owner:
+ description: "The owner (organization) of the repositories"
+ required: false
+ default: "selfxyz"
+ repositories:
+ description: "Comma-separated list of repository names to grant access to"
+ required: false
+ default: "NFCPassportReader,android-passport-nfc-reader,react-native-passport-reader,mobile-sdk-native"
+
+outputs:
+ token:
+ description: "The generated GitHub App installation token"
+ value: ${{ steps.app-token.outputs.token }}
+
+runs:
+ using: "composite"
+ steps:
+ - name: Generate GitHub App Token
+ uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
+ id: app-token
+ with:
+ app-id: ${{ inputs.app-id }}
+ private-key: ${{ inputs.private-key }}
+ owner: ${{ inputs.owner }}
+ repositories: ${{ inputs.repositories }}
+ - name: Configure Git auth via ~/.netrc (optional)
+ if: ${{ inputs.configure-netrc == 'true' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ TOKEN="${{ steps.app-token.outputs.token }}"
+ MACHINE="${{ inputs.netrc-machine }}"
+
+ # Mask the token in logs defensively (it shouldn't print, but this protects against future edits).
+ echo "::add-mask::${TOKEN}"
+
+ printf "machine %s\n login x-access-token\n password %s\n" "${MACHINE}" "${TOKEN}" > "${HOME}/.netrc"
+ chmod 600 "${HOME}/.netrc"
diff --git a/.github/workflows/mobile-bundle-analysis.yml b/.github/workflows/mobile-bundle-analysis.yml
index 198e572e0..d53fcfc1e 100644
--- a/.github/workflows/mobile-bundle-analysis.yml
+++ b/.github/workflows/mobile-bundle-analysis.yml
@@ -5,6 +5,7 @@ env:
JAVA_VERSION: 17
WORKSPACE: ${{ github.workspace }}
APP_PATH: ${{ github.workspace }}/app
+ NODE_ENV: "production"
on:
pull_request:
@@ -57,6 +58,14 @@ jobs:
path: |
~/.gradle/caches
~/.gradle/wrapper
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
+ configure-netrc: "true"
- name: Install Mobile Dependencies
uses: ./.github/actions/mobile-setup
with:
@@ -65,7 +74,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }}
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Build dependencies
shell: bash
run: yarn workspace @selfxyz/common build
@@ -113,6 +122,14 @@ jobs:
with:
path: app/ios/Pods
lockfile: app/ios/Podfile.lock
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
+ configure-netrc: "true"
- name: Install Mobile Dependencies
uses: ./.github/actions/mobile-setup
with:
@@ -121,7 +138,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }}
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Build dependencies
shell: bash
run: yarn workspace @selfxyz/common build
diff --git a/.github/workflows/mobile-ci.yml b/.github/workflows/mobile-ci.yml
index b936a9b5b..924542201 100644
--- a/.github/workflows/mobile-ci.yml
+++ b/.github/workflows/mobile-ci.yml
@@ -35,7 +35,7 @@ concurrency:
jobs:
build-deps:
- runs-on: macos-latest-large
+ runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@@ -90,12 +90,9 @@ jobs:
- name: Check App Types
run: yarn types
working-directory: ./app
- - name: Check license headers
- run: node scripts/check-license-headers.mjs --check
- working-directory: ./
test:
- runs-on: macos-latest-large
+ runs-on: ubuntu-latest
needs: build-deps
timeout-minutes: 60
steps:
@@ -190,6 +187,8 @@ jobs:
env:
# Increase Node.js memory to prevent hermes-parser WASM memory errors
NODE_OPTIONS: --max-old-space-size=4096
+ # Override production NODE_ENV for tests - React's production build doesn't include testing utilities
+ NODE_ENV: test
run: |
# Final verification from app directory perspective
echo "Final verification before running tests (from app directory)..."
@@ -268,6 +267,7 @@ jobs:
- name: Cache Ruby gems
uses: ./.github/actions/cache-bundler
with:
+ # TODO(jcortejoso): Confirm the path of the bundle cache
path: app/ios/vendor/bundle
lock-file: app/Gemfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
@@ -315,6 +315,14 @@ jobs:
bundle config set --local path 'vendor/bundle'
bundle install --jobs 4 --retry 3
working-directory: ./app
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
+ configure-netrc: "true"
- name: Install iOS Dependencies
uses: nick-fields/retry@v3
with:
@@ -325,7 +333,7 @@ jobs:
cd app/ios
bundle exec bash scripts/pod-install-with-cache-fix.sh
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Resolve iOS workspace
run: |
WORKSPACE_OPEN="ios/OpenPassport.xcworkspace"
@@ -470,12 +478,19 @@ jobs:
run: |
echo "Cache miss for built dependencies. Building now..."
yarn workspace @selfxyz/mobile-app run build:deps
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Setup Android private modules
run: |
cd ${{ env.APP_PATH }}
PLATFORM=android node scripts/setup-private-modules.cjs
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Android (with AAPT2 symlink fix)
run: yarn android:ci
diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml
index bd63c4e15..65609a404 100644
--- a/.github/workflows/mobile-deploy.yml
+++ b/.github/workflows/mobile-deploy.yml
@@ -31,6 +31,7 @@ name: Mobile Deploy
env:
# Build environment versions
RUBY_VERSION: 3.2
+ NODE_ENV: "production"
JAVA_VERSION: 17
ANDROID_API_LEVEL: 35
ANDROID_NDK_VERSION: 27.0.12077973
@@ -385,6 +386,7 @@ jobs:
id: gems-cache
uses: ./.github/actions/cache-bundler
with:
+ # TODO(jcortejoso): Confirm the path of the bundle cache
path: ${{ env.APP_PATH }}/ios/vendor/bundle
lock-file: app/Gemfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
@@ -428,6 +430,14 @@ jobs:
fi
echo "β
Lock files exist"
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
+ configure-netrc: "true"
- name: Install Mobile Dependencies (main repo)
if: inputs.platform != 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
@@ -438,7 +448,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }}
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install Mobile Dependencies (forked PRs - no secrets)
if: inputs.platform != 'android' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
@@ -691,7 +701,7 @@ jobs:
IOS_TESTFLIGHT_GROUPS: ${{ secrets.IOS_TESTFLIGHT_GROUPS }}
NODE_OPTIONS: "--max-old-space-size=8192"
SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }}
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
TURNKEY_AUTH_PROXY_CONFIG_ID: ${{ secrets.TURNKEY_AUTH_PROXY_CONFIG_ID }}
TURNKEY_GOOGLE_CLIENT_ID: ${{ secrets.TURNKEY_GOOGLE_CLIENT_ID }}
@@ -1046,6 +1056,14 @@ jobs:
echo "β
Lock files exist"
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
+
- name: Install Mobile Dependencies (main repo)
if: inputs.platform != 'ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
uses: ./.github/actions/mobile-setup
@@ -1055,7 +1073,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }}
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
PLATFORM: ${{ inputs.platform }}
- name: Install Mobile Dependencies (forked PRs - no secrets)
@@ -1112,7 +1130,7 @@ jobs:
cd ${{ env.APP_PATH }}
PLATFORM=android node scripts/setup-private-modules.cjs
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Dependencies (Android)
diff --git a/.github/workflows/mobile-e2e.yml b/.github/workflows/mobile-e2e.yml
index da1f946ae..3ecb7b2b3 100644
--- a/.github/workflows/mobile-e2e.yml
+++ b/.github/workflows/mobile-e2e.yml
@@ -70,6 +70,14 @@ jobs:
- name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
+ configure-netrc: "true"
- name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3
@@ -79,7 +87,7 @@ jobs:
retry_wait_seconds: 5
command: yarn install --immutable --silent
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3
@@ -138,7 +146,7 @@ jobs:
cd app
PLATFORM=android node scripts/setup-private-modules.cjs
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Android APK
run: |
@@ -149,6 +157,8 @@ jobs:
- name: Clean up Gradle build artifacts
uses: ./.github/actions/cleanup-gradle-artifacts
- name: Verify APK and android-passport-nfc-reader integration
+ env:
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
run: |
echo "π Verifying build artifacts..."
APK_PATH="app/android/app/build/outputs/apk/debug/app-debug.apk"
@@ -160,8 +170,8 @@ jobs:
echo "π± APK size: $APK_SIZE bytes"
# Verify private modules were properly integrated (skip for forks)
- if [ -z "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]; then
- echo "π No PAT available β skipping private module verification"
+ if [ -z "${SELFXYZ_APP_TOKEN:-}" ]; then
+ echo "π No SELFXYZ_APP_TOKEN available β skipping private module verification"
else
# Verify android-passport-nfc-reader
if [ -d "app/android/android-passport-nfc-reader" ]; then
@@ -263,6 +273,14 @@ jobs:
- name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
+ configure-netrc: "true"
- name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3
@@ -272,7 +290,7 @@ jobs:
retry_wait_seconds: 5
command: yarn install --immutable --silent
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3
@@ -360,7 +378,7 @@ jobs:
echo "π¦ Installing pods via centralized scriptβ¦"
BUNDLE_GEMFILE=../Gemfile bundle exec bash scripts/pod-install-with-cache-fix.sh
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Setup iOS Simulator
run: |
echo "Setting up iOS Simulator..."
diff --git a/.github/workflows/mobile-sdk-demo-e2e.yml b/.github/workflows/mobile-sdk-demo-e2e.yml
index 50f617b21..be554e3df 100644
--- a/.github/workflows/mobile-sdk-demo-e2e.yml
+++ b/.github/workflows/mobile-sdk-demo-e2e.yml
@@ -59,6 +59,9 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- run: corepack enable
- run: corepack prepare yarn@4.6.0 --activate
+ - name: Compute .yarnrc.yml hash
+ id: yarnrc-hash
+ uses: ./.github/actions/yarnrc-hash
- name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn
with:
@@ -66,10 +69,17 @@ jobs:
.yarn/cache
.yarn/install-state.gz
.yarn/unplugged
- cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }}
+ cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
- name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3
@@ -79,7 +89,7 @@ jobs:
retry_wait_seconds: 5
command: yarn install --immutable --silent
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3
@@ -220,6 +230,9 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- run: corepack enable
- run: corepack prepare yarn@4.6.0 --activate
+ - name: Compute .yarnrc.yml hash
+ id: yarnrc-hash
+ uses: ./.github/actions/yarnrc-hash
- name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn
with:
@@ -227,10 +240,17 @@ jobs:
.yarn/cache
.yarn/install-state.gz
.yarn/unplugged
- cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }}
+ cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
- name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
+ - name: Generate token for self repositories
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
+ uses: ./.github/actions/generate-github-token
+ id: github-token
+ with:
+ app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
+ private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3
@@ -240,7 +260,7 @@ jobs:
retry_wait_seconds: 5
command: yarn install --immutable --silent
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3
@@ -316,15 +336,15 @@ jobs:
max_attempts: 3
retry_wait_seconds: 10
command: |
- if [ -n "${SELFXYZ_INTERNAL_REPO_PAT}" ]; then
- echo "π Using SELFXYZ_INTERNAL_REPO_PAT for private pod access"
- echo "::add-mask::${SELFXYZ_INTERNAL_REPO_PAT}"
+ if [ -n "${SELFXYZ_APP_TOKEN}" ]; then
+ echo "π Using GitHub App token for private pod access"
+ echo "::add-mask::${SELFXYZ_APP_TOKEN}"
fi
cd packages/mobile-sdk-demo/ios
echo "π¦ Installing pods via cache-fix scriptβ¦"
bash scripts/pod-install-with-cache-fix.sh
env:
- SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
+ SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
GIT_TERMINAL_PROMPT: 0
- name: Setup iOS Simulator
run: |
diff --git a/.github/workflows/qrcode-sdk-ci.yml b/.github/workflows/qrcode-sdk-ci.yml
index 5849e7d49..8b4852cc7 100644
--- a/.github/workflows/qrcode-sdk-ci.yml
+++ b/.github/workflows/qrcode-sdk-ci.yml
@@ -76,6 +76,7 @@ jobs:
with:
path: |
common/dist
+ sdk/sdk-common/dist
sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
@@ -128,6 +129,7 @@ jobs:
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
@@ -195,6 +197,7 @@ jobs:
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
@@ -203,6 +206,7 @@ jobs:
run: |
echo "Verifying build artifacts..."
ls -la common/dist/
+ ls -la sdk/sdk-common/dist/
ls -la sdk/qrcode/dist/
echo "β
Build artifacts verified"
@@ -255,13 +259,11 @@ jobs:
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
- - name: Build SDK common dependency
- run: yarn workspace @selfxyz/sdk-common build
-
- name: Run tests
run: yarn workspace @selfxyz/qrcode test
diff --git a/.gitignore b/.gitignore
index 2809ef0c7..35d5c7123 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,8 @@ packages/mobile-sdk-alpha/docs/docstrings-report.json
# Private Android modules (cloned at build time)
app/android/android-passport-nfc-reader/
+
+# Foundry
+contracts/out/
+contracts/cache_forge/
+contracts/broadcast/
diff --git a/.gitmodules b/.gitmodules
index 445c09f1c..154693b6f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,10 @@
+[submodule "contracts/lib/openzeppelin-foundry-upgrades"]
+ path = contracts/lib/openzeppelin-foundry-upgrades
+ url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
+[submodule "contracts/lib/forge-std"]
+ path = contracts/lib/forge-std
+ url = https://github.com/foundry-rs/forge-std
+
[submodule "packages/mobile-sdk-alpha/mobile-sdk-native"]
path = packages/mobile-sdk-alpha/mobile-sdk-native
url = git@github.com:selfxyz/mobile-sdk-native.git
diff --git a/app/.eslintrc.cjs b/app/.eslintrc.cjs
index 596ae00c7..584d3d4a0 100644
--- a/app/.eslintrc.cjs
+++ b/app/.eslintrc.cjs
@@ -245,6 +245,8 @@ module.exports = {
],
// Allow any types in tests for mocking
'@typescript-eslint/no-explicit-any': 'off',
+ // Allow test skipping without warnings
+ 'jest/no-disabled-tests': 'off',
},
},
{
diff --git a/app/Gemfile.lock b/app/Gemfile.lock
index 55b364b0d..ba8b445a4 100644
--- a/app/Gemfile.lock
+++ b/app/Gemfile.lock
@@ -22,7 +22,7 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
- aws-partitions (1.1190.0)
+ aws-partitions (1.1194.0)
aws-sdk-core (3.239.2)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
@@ -86,7 +86,7 @@ GEM
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
- concurrent-ruby (1.3.5)
+ concurrent-ruby (1.3.6)
connection_pool (3.0.2)
declarative (0.0.20)
digest-crc (0.7.0)
@@ -222,14 +222,14 @@ GEM
i18n (1.14.7)
concurrent-ruby (~> 1.0)
jmespath (1.6.2)
- json (2.17.1)
+ json (2.18.0)
jwt (2.10.2)
base64
logger (1.7.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
- minitest (5.26.2)
+ minitest (5.27.0)
molinillo (0.8.0)
multi_json (1.18.0)
multipart-post (2.4.1)
@@ -241,7 +241,7 @@ GEM
nokogiri (1.18.10)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
- optparse (0.8.0)
+ optparse (0.8.1)
os (1.1.4)
plist (3.7.2)
public_suffix (4.0.7)
diff --git a/app/babel.config.cjs b/app/babel.config.cjs
index 44824a90e..f0861d392 100644
--- a/app/babel.config.cjs
+++ b/app/babel.config.cjs
@@ -26,4 +26,9 @@ module.exports = {
},
],
],
+ env: {
+ production: {
+ plugins: ['transform-remove-console'],
+ },
+ },
};
diff --git a/app/ios/OpenPassport/Info.plist b/app/ios/OpenPassport/Info.plist
index 8c50f71e9..6625f9f05 100644
--- a/app/ios/OpenPassport/Info.plist
+++ b/app/ios/OpenPassport/Info.plist
@@ -42,8 +42,15 @@
LSApplicationQueriesSchemes
- whatsapp
+ argent
+ cbwallet
+ coinbase
+ metamask
+ rainbow
sms
+ trust
+ wc
+ whatsapp
LSRequiresIPhoneOS
diff --git a/app/ios/Podfile b/app/ios/Podfile
index 8aa6f7023..c44bb5f41 100755
--- a/app/ios/Podfile
+++ b/app/ios/Podfile
@@ -33,7 +33,7 @@ def using_https_git_auth?
auth_data.include?("Logged in to github.com account") &&
auth_data.include?("Git operations protocol: https")
rescue => e
- puts "gh auth status failed, assuming no HTTPS auth -- will try SSH"
+ # Avoid printing auth-related details in CI logs.
false
end
end
@@ -51,18 +51,16 @@ target "Self" do
# External fork - use public NFCPassportReader repository (placeholder)
# TODO: Replace with actual public NFCPassportReader repository URL
nfc_repo_url = "https://github.com/PLACEHOLDER/NFCPassportReader.git"
- puts "π¦ Using public NFCPassportReader for external fork (#{ENV["GITHUB_REPOSITORY"]})"
- elsif ENV["GITHUB_ACTIONS"] == "true" && ENV["SELFXYZ_INTERNAL_REPO_PAT"]
- # Running in selfxyz GitHub Actions with PAT available - use private repo with token
- nfc_repo_url = "https://#{ENV["SELFXYZ_INTERNAL_REPO_PAT"]}@github.com/selfxyz/NFCPassportReader.git"
- puts "π¦ Using private NFCPassportReader with PAT (selfxyz GitHub Actions)"
+ elsif ENV["GITHUB_ACTIONS"] == "true"
+ # CI: NEVER embed credentials in URLs. Rely on workflow-provided auth via:
+ # - ~/.netrc or a Git credential helper, and token masking in logs.
+ nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git"
elsif using_https_git_auth?
# Local development with HTTPS GitHub auth via gh - use HTTPS to private repo
nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git"
else
# Local development in selfxyz repo - use SSH to private repo
nfc_repo_url = "git@github.com:selfxyz/NFCPassportReader.git"
- puts "π¦ Using SSH for private NFCPassportReader (local selfxyz development)"
end
pod "NFCPassportReader", git: nfc_repo_url, commit: "9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b"
diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock
index 0ec54a0a5..fb2ce8b18 100644
--- a/app/ios/Podfile.lock
+++ b/app/ios/Podfile.lock
@@ -2644,6 +2644,6 @@ SPEC CHECKSUMS:
SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb
Yoga: 1259c7a8cbaccf7b4c3ddf8ee36ca11be9dee407
-PODFILE CHECKSUM: b5f11f935be22fce84c5395aaa203b50427a79aa
+PODFILE CHECKSUM: 0aa47f53692543349c43673cda7380fa23049eba
COCOAPODS: 1.16.2
diff --git a/app/jest.config.cjs b/app/jest.config.cjs
index 554f36eca..a7d19eb81 100644
--- a/app/jest.config.cjs
+++ b/app/jest.config.cjs
@@ -16,7 +16,7 @@ module.exports = {
'node',
],
transformIgnorePatterns: [
- 'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags)/)',
+ 'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags|react-native-blur-effect)/)',
],
setupFiles: ['/jest.setup.js'],
testMatch: [
diff --git a/app/package.json b/app/package.json
index c973651a3..041d09f59 100644
--- a/app/package.json
+++ b/app/package.json
@@ -58,7 +58,7 @@
"sync-versions": "bundle exec fastlane ios sync_version && bundle exec fastlane android sync_version",
"tag:release": "node scripts/tag.cjs release",
"tag:remove": "node scripts/tag.cjs remove",
- "test": "yarn build:deps && yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
+ "test": "yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
"test:build": "yarn build:deps && yarn types && node ./scripts/bundle-analyze-ci.cjs ios && yarn test",
"test:ci": "yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
"test:coverage": "yarn jest:run --coverage --passWithNoTests",
@@ -105,6 +105,7 @@
"@segment/analytics-react-native": "^2.21.2",
"@segment/sovran-react-native": "^1.1.3",
"@selfxyz/common": "workspace:^",
+ "@selfxyz/euclid": "^0.6.0",
"@selfxyz/mobile-sdk-alpha": "workspace:^",
"@sentry/react": "^9.32.0",
"@sentry/react-native": "7.0.1",
@@ -209,6 +210,7 @@
"@typescript-eslint/parser": "^8.39.0",
"@vitejs/plugin-react-swc": "^3.10.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",
"eslint": "^8.57.0",
diff --git a/app/scripts/bundle-analyze-ci.cjs b/app/scripts/bundle-analyze-ci.cjs
index 914cf6afd..f4f7dbe72 100755
--- a/app/scripts/bundle-analyze-ci.cjs
+++ b/app/scripts/bundle-analyze-ci.cjs
@@ -17,8 +17,8 @@ if (!platform || !['android', 'ios'].includes(platform)) {
// Bundle size thresholds in MB - easy to update!
const BUNDLE_THRESHOLDS_MB = {
// TODO: fix temporary bundle bump
- ios: 44,
- android: 44,
+ ios: 45,
+ android: 45,
};
function formatBytes(bytes) {
diff --git a/app/scripts/mobile-ci-build-android.sh b/app/scripts/mobile-ci-build-android.sh
index 5da46c08f..501d48782 100755
--- a/app/scripts/mobile-ci-build-android.sh
+++ b/app/scripts/mobile-ci-build-android.sh
@@ -109,7 +109,13 @@ clone_private_module() {
local dir_name=$(basename "$target_dir")
# Use different clone methods based on environment
- if is_ci && [[ -n "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]]; then
+ if is_ci && [[ -n "${SELFXYZ_APP_TOKEN:-}" ]]; then
+ # CI environment with GitHub App installation token
+ git clone "https://x-access-token:${SELFXYZ_APP_TOKEN}@github.com/selfxyz/${repo_name}.git" "$dir_name" || {
+ log "ERROR: Failed to clone $repo_name with GitHub App token"
+ exit 1
+ }
+ elif is_ci && [[ -n "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]]; then
# CI environment with PAT (fallback if action didn't run)
git clone "https://${SELFXYZ_INTERNAL_REPO_PAT}@github.com/selfxyz/${repo_name}.git" "$dir_name" || {
log "ERROR: Failed to clone $repo_name with PAT"
@@ -119,14 +125,14 @@ clone_private_module() {
# Local development with SSH
git clone "git@github.com:selfxyz/${repo_name}.git" "$dir_name" || {
log "ERROR: Failed to clone $repo_name with SSH"
- log "Please ensure you have SSH access to the repository or set SELFXYZ_INTERNAL_REPO_PAT"
+ log "Please ensure you have SSH access to the repository or set SELFXYZ_APP_TOKEN/SELFXYZ_INTERNAL_REPO_PAT"
exit 1
}
else
log "ERROR: No authentication method available for cloning $repo_name"
log "Please either:"
log " - Set up SSH access (for local development)"
- log " - Set SELFXYZ_INTERNAL_REPO_PAT environment variable (for CI)"
+ log " - Set SELFXYZ_APP_TOKEN or SELFXYZ_INTERNAL_REPO_PAT environment variable (for CI)"
exit 1
fi
@@ -194,14 +200,15 @@ log "β
Package files backed up successfully"
# Install SDK from tarball in app with timeout
log "Installing SDK as real files..."
if is_ci; then
- # Temporarily unset PAT to skip private modules during SDK installation
- env -u SELFXYZ_INTERNAL_REPO_PAT timeout 180 yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" || {
+ # Temporarily unset both auth tokens to skip private modules during SDK installation
+ # Both tokens must be unset to prevent setup-private-modules.cjs from attempting clones
+ env -u SELFXYZ_INTERNAL_REPO_PAT -u SELFXYZ_APP_TOKEN timeout 180 yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" || {
log "SDK installation timed out after 3 minutes"
exit 1
}
else
- # Temporarily unset PAT to skip private modules during SDK installation
- env -u SELFXYZ_INTERNAL_REPO_PAT yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH"
+ # Temporarily unset both auth tokens to skip private modules during SDK installation
+ env -u SELFXYZ_INTERNAL_REPO_PAT -u SELFXYZ_APP_TOKEN yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH"
fi
# Verify installation (check for AAR file in both local and hoisted locations)
diff --git a/app/scripts/setup-private-modules.cjs b/app/scripts/setup-private-modules.cjs
index 73db64b4e..39ded4be9 100644
--- a/app/scripts/setup-private-modules.cjs
+++ b/app/scripts/setup-private-modules.cjs
@@ -29,8 +29,9 @@ const PRIVATE_MODULES = [
// Environment detection
// CI is set by GitHub Actions, CircleCI, etc. Check for truthy value
-const isCI = !!process.env.CI || process.env.GITHUB_ACTIONS === 'true';
+const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
const repoToken = process.env.SELFXYZ_INTERNAL_REPO_PAT;
+const appToken = process.env.SELFXYZ_APP_TOKEN; // GitHub App installation token
const isDryRun = process.env.DRY_RUN === 'true';
// Platform detection for Android-specific modules
@@ -150,13 +151,17 @@ function clonePrivateRepo(repoName, localPath) {
let cloneUrl;
- if (isCI && repoToken) {
+ if (isCI && appToken) {
+ // CI environment with GitHub App installation token
+ log('CI detected: Using SELFXYZ_APP_TOKEN for clone', 'info');
+ cloneUrl = `https://x-access-token:${appToken}@github.com/${GITHUB_ORG}/${repoName}.git`;
+ } else if (isCI && repoToken) {
// CI environment with Personal Access Token
log('CI detected: Using SELFXYZ_INTERNAL_REPO_PAT for clone', 'info');
cloneUrl = `https://${repoToken}@github.com/${GITHUB_ORG}/${repoName}.git`;
} else if (isCI) {
log(
- 'CI environment detected but SELFXYZ_INTERNAL_REPO_PAT not available - skipping private module setup',
+ 'CI environment detected but no token available - skipping private module setup',
'info',
);
log(
@@ -173,7 +178,7 @@ function clonePrivateRepo(repoName, localPath) {
}
// Security: Use quiet mode for credentialed URLs to prevent token exposure
- const isCredentialedUrl = isCI && repoToken;
+ const isCredentialedUrl = isCI && (appToken || repoToken);
const quietFlag = isCredentialedUrl ? '--quiet' : '';
const targetDir = path.basename(localPath);
const cloneCommand = `git clone --branch ${BRANCH} --single-branch --depth 1 ${quietFlag} "${cloneUrl}" "${targetDir}"`;
@@ -190,7 +195,7 @@ function clonePrivateRepo(repoName, localPath) {
} catch (error) {
if (isCI) {
log(
- 'Clone failed in CI environment. Check SELFXYZ_INTERNAL_REPO_PAT permissions.',
+ 'Clone failed in CI environment. Check SELFXYZ_APP_TOKEN or SELFXYZ_INTERNAL_REPO_PAT permissions.',
'error',
);
} else {
@@ -231,7 +236,7 @@ function setupPrivateModule(module) {
}
// Security: Remove credential-embedded remote URL after clone
- if (isCI && repoToken && !isDryRun) {
+ if (isCI && (appToken || repoToken) && !isDryRun) {
scrubGitRemoteUrl(localPath, repoName);
}
@@ -275,6 +280,11 @@ function setupAndroidPassportReader() {
`Setup complete: ${successCount}/${PRIVATE_MODULES.length} modules cloned`,
'warning',
);
+ } else {
+ log(
+ 'No private modules were cloned - this is expected for forked PRs',
+ 'info',
+ );
}
}
diff --git a/app/src/components/WebViewFooter.tsx b/app/src/components/WebViewFooter.tsx
index 297086498..7b3e72dae 100644
--- a/app/src/components/WebViewFooter.tsx
+++ b/app/src/components/WebViewFooter.tsx
@@ -6,11 +6,7 @@ import React from 'react';
import { ArrowLeft, ArrowRight, RotateCcw } from '@tamagui/lucide-icons';
import { Button, XStack, YStack } from '@selfxyz/mobile-sdk-alpha/components';
-import {
- black,
- slate50,
- slate400,
-} from '@selfxyz/mobile-sdk-alpha/constants/colors';
+import { black, slate400 } from '@selfxyz/mobile-sdk-alpha/constants/colors';
import { buttonTap } from '@/integrations/haptics';
@@ -23,8 +19,7 @@ export interface WebViewFooterProps {
onOpenInBrowser: () => void;
}
-const iconSize = 22;
-const buttonSize = 36;
+const iconSize = 24;
export const WebViewFooter: React.FC = ({
canGoBack,
@@ -42,19 +37,13 @@ export const WebViewFooter: React.FC = ({
) => (