Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 67 additions & 62 deletions .github/workflows/push-eas-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,53 @@ jobs:
artifact-name: node-modules-eas-update-base
artifact-retention-days: 1

prepare:
name: Load OTA build config
needs: [validate-pr]
runs-on: ubuntu-latest
outputs:
github_environment: ${{ steps.config.outputs.github_environment }}
secrets_json: ${{ steps.config.outputs.secrets_json }}
build_name: ${{ steps.config.outputs.build_name }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ needs.validate-pr.outputs.commit_sha }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'

- run: yarn install --immutable

- name: Map channel to build name and load config
id: config
run: |
case "${{ inputs.channel }}" in
exp) BUILD_NAME="main-exp" ;;
rc) BUILD_NAME="main-rc" ;;
production) BUILD_NAME="main-prod" ;;
*)
echo "::error::Unknown channel: ${{ inputs.channel }}"
exit 1
;;
esac
echo "build_name=$BUILD_NAME" >> "$GITHUB_OUTPUT"

node -e "
const yaml = require('js-yaml');
const fs = require('fs');
const config = yaml.load(fs.readFileSync('builds.yml', 'utf8'));
const build = config.builds['${BUILD_NAME}'];
if (!build) { console.error('Build not found: ${BUILD_NAME}'); process.exit(1); }
fs.appendFileSync(process.env.GITHUB_OUTPUT, 'github_environment=' + build.github_environment + '\n');
fs.appendFileSync(process.env.GITHUB_OUTPUT, 'secrets_json=' + JSON.stringify(build.secrets || {}) + '\n');
"

echo "✅ Loaded config for $BUILD_NAME"

fingerprint-comparison:
name: Compare Expo Fingerprints
needs:
Expand Down Expand Up @@ -287,80 +334,25 @@ jobs:
push-update:
name: Push EAS Update
runs-on: ubuntu-latest
environment: ${{ inputs.channel == 'exp' && 'build-exp' || inputs.channel == 'rc' && 'build-rc' || 'build-production' }}
environment: ${{ needs.prepare.outputs.github_environment }}
needs:
- fingerprint-comparison
- approval
- validate-pr
- setup-dependencies
- prepare
if: >
needs.fingerprint-comparison.outputs.fingerprints_equal == 'true' &&
needs.approval.result == 'success'
env:
# OTA-specific vars only — build config + secrets come from builds.yml via steps below
TARGET_COMMIT_HASH: ${{ needs.validate-pr.outputs.commit_sha }}
ARTIFACT_NAME: ${{ needs.setup-dependencies.outputs.artifact-name }}
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_PROJECT_ID: ${{ secrets.EXPO_PROJECT_ID }}
EXPO_CHANNEL: ${{ vars.EXPO_CHANNEL }}
UPDATE_MESSAGE: ${{ inputs.message }}
TARGET_CHANNEL: ${{ inputs.channel }}
OTA_PUSH_PLATFORM: ${{ inputs.platform }}
GIT_BRANCH: ${{ github.ref_name }}
RAMP_DEV_BUILD: ${{ secrets.RAMP_DEV_BUILD || 'false' }}
RAMP_INTERNAL_BUILD: ${{ secrets.RAMP_INTERNAL_BUILD || 'false' }}
RAMPS_ENVIRONMENT: ${{ secrets.RAMPS_ENVIRONMENT || 'production' }}
MM_MUSD_CONVERSION_FLOW_ENABLED: 'false'
MM_NETWORK_UI_REDESIGN_ENABLED: 'false'
MM_NOTIFICATIONS_UI_ENABLED: 'true'
MM_PERMISSIONS_SETTINGS_V1_ENABLED: 'false'
MM_PERPS_BLOCKED_REGIONS: 'US,CA-ON,GB,BE'
MM_PERPS_ENABLED: 'true'
MM_PERPS_HIP3_ALLOWLIST_MARKETS: ''
MM_PERPS_HIP3_BLOCKLIST_MARKETS: ''
MM_PERPS_HIP3_ENABLED: 'true'
MM_SECURITY_ALERTS_API_ENABLED: 'true'
BRIDGE_USE_DEV_APIS: 'false'
SEEDLESS_ONBOARDING_ENABLED: 'true'
FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN: ${{ secrets.FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN }}
FEATURES_ANNOUNCEMENTS_SPACE_ID: ${{ secrets.FEATURES_ANNOUNCEMENTS_SPACE_ID }}
SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY }}
SEGMENT_PROXY_URL: ${{ secrets.SEGMENT_PROXY_URL }}
SEGMENT_DELETE_API_SOURCE_ID: ${{ secrets.SEGMENT_DELETE_API_SOURCE_ID }}
SEGMENT_REGULATIONS_ENDPOINT: ${{ secrets.SEGMENT_REGULATIONS_ENDPOINT }}
MM_SENTRY_DSN: ${{ secrets.MM_SENTRY_DSN }}
MM_SENTRY_AUTH_TOKEN: ${{ secrets.MM_SENTRY_AUTH_TOKEN }}
IOS_GOOGLE_CLIENT_ID: ${{ secrets.IOS_GOOGLE_CLIENT_ID }}
IOS_GOOGLE_REDIRECT_URI: ${{ secrets.IOS_GOOGLE_REDIRECT_URI }}
ANDROID_APPLE_CLIENT_ID: ${{ secrets.ANDROID_APPLE_CLIENT_ID }}
ANDROID_GOOGLE_CLIENT_ID: ${{ secrets.ANDROID_GOOGLE_CLIENT_ID }}
ANDROID_GOOGLE_SERVER_CLIENT_ID: ${{ secrets.ANDROID_GOOGLE_SERVER_CLIENT_ID }}
GOOGLE_SERVICES_B64_IOS: ${{ secrets.GOOGLE_SERVICES_B64_IOS }}
GOOGLE_SERVICES_B64_ANDROID: ${{ secrets.GOOGLE_SERVICES_B64_ANDROID }}
MM_INFURA_PROJECT_ID: ${{ secrets.MM_INFURA_PROJECT_ID }}
MM_BRANCH_KEY_LIVE: ${{ secrets.MM_BRANCH_KEY_LIVE }}
MM_BRANCH_KEY_TEST: ${{ secrets.MM_BRANCH_KEY_TEST }}
MM_CARD_BAANX_API_CLIENT_KEY: ${{ secrets.MM_CARD_BAANX_API_CLIENT_KEY }}
WALLET_CONNECT_PROJECT_ID: ${{ secrets.WALLET_CONNECT_PROJECT_ID }}
MM_FOX_CODE: ${{ secrets.MM_FOX_CODE }}
FCM_CONFIG_API_KEY: ${{ secrets.FCM_CONFIG_API_KEY }}
FCM_CONFIG_AUTH_DOMAIN: ${{ secrets.FCM_CONFIG_AUTH_DOMAIN }}
FCM_CONFIG_STORAGE_BUCKET: ${{ secrets.FCM_CONFIG_STORAGE_BUCKET }}
FCM_CONFIG_PROJECT_ID: ${{ secrets.FCM_CONFIG_PROJECT_ID }}
FCM_CONFIG_MESSAGING_SENDER_ID: ${{ secrets.FCM_CONFIG_MESSAGING_SENDER_ID }}
FCM_CONFIG_APP_ID: ${{ secrets.FCM_CONFIG_APP_ID }}
FCM_CONFIG_MEASUREMENT_ID: ${{ secrets.FCM_CONFIG_MEASUREMENT_ID }}
QUICKNODE_MAINNET_URL: ${{ secrets.QUICKNODE_MAINNET_URL }}
QUICKNODE_ARBITRUM_URL: ${{ secrets.QUICKNODE_ARBITRUM_URL }}
QUICKNODE_AVALANCHE_URL: ${{ secrets.QUICKNODE_AVALANCHE_URL }}
QUICKNODE_BASE_URL: ${{ secrets.QUICKNODE_BASE_URL }}
QUICKNODE_LINEA_MAINNET_URL: ${{ secrets.QUICKNODE_LINEA_MAINNET_URL }}
QUICKNODE_MONAD_URL: ${{ secrets.QUICKNODE_MONAD_URL }}
QUICKNODE_HYPEREVM_URL: ${{ secrets.QUICKNODE_HYPEREVM_URL }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MM_PERPS_ENABLED dropped from OTA builds without builds.yml entry

Medium Severity

The old workflow explicitly set MM_PERPS_ENABLED: 'true' as a job-level env var, but this PR removes it without adding a corresponding entry to builds.yml's _public_envs. The app code in selectPerpsEnabledFlag uses process.env.MM_PERPS_ENABLED === 'true' as a fallback when the remote feature flag is unavailable or invalid. With the env var now unset, the fallback evaluates to false, silently disabling perps in OTA builds whenever the remote flag service is unreachable or the flag isn't configured. Other perps-related vars like MM_PERPS_HIP3_ENABLED and MM_PERPS_BLOCKED_REGIONS are present in _public_envs, suggesting this omission is unintentional.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c030552. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If MM_PERPS_ENABLED isn't in builds.yml we don't have it to begin with so this is fine

QUICKNODE_OPTIMISM_URL: ${{ secrets.QUICKNODE_OPTIMISM_URL }}
QUICKNODE_POLYGON_URL: ${{ secrets.QUICKNODE_POLYGON_URL }}
QUICKNODE_BSC_URL: ${{ secrets.QUICKNODE_BSC_URL }}
QUICKNODE_SEI_URL: ${{ secrets.QUICKNODE_SEI_URL }}
MM_CHARTING_LIBRARY_URL: ${{ secrets.MM_CHARTING_LIBRARY_URL }}
MM_BRAZE_API_KEY_IOS: ${{ secrets.MM_BRAZE_API_KEY_IOS }}
MM_BRAZE_API_KEY_ANDROID: ${{ secrets.MM_BRAZE_API_KEY_ANDROID }}
MM_BRAZE_SDK_ENDPOINT: ${{ secrets.MM_BRAZE_SDK_ENDPOINT }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down Expand Up @@ -402,6 +394,19 @@ jobs:
echo "📦 node_modules size: $(du -sh node_modules | cut -f1)"
echo "✅ Artifacts verified"

- name: Apply build config from builds.yml
run: |
BUILD_NAME="${{ needs.prepare.outputs.build_name }}"
echo "📦 Applying build config for: $BUILD_NAME"
eval "$(node scripts/apply-build-config.js "$BUILD_NAME" --export)"
node scripts/apply-build-config.js "$BUILD_NAME" --export-github-env >> "$GITHUB_ENV"

- name: Set secrets from builds.yml mapping
env:
CONFIG_SECRETS: ${{ needs.prepare.outputs.secrets_json }}
ALL_SECRETS: ${{ toJSON(secrets) }}
run: node scripts/set-secrets-from-config.js

- name: Determine signing secret name
shell: bash
env:
Expand Down Expand Up @@ -452,6 +457,7 @@ jobs:
run: |
TARGET_RUNTIME_VERSION=$(node -p "require('./package.json').version")
echo "🔧 Configuration:"
echo " Build: ${{ needs.prepare.outputs.build_name }}"
echo " Channel: ${EXPO_CHANNEL:-<not set>}"
echo " Channel (vars.EXPO_CHANNEL): ${{ vars.EXPO_CHANNEL }}"
echo " Message: ${UPDATE_MESSAGE:-<not set>}"
Expand All @@ -460,7 +466,6 @@ jobs:
- name: Build & Push EAS Update via build.sh
env:
SKIP_TRANSFORM_LINT: 'true'
# Increase Node heap to avoid OOM during Expo export in CI
NODE_OPTIONS: '--max_old_space_size=8192'
run: |
echo "📦 Configuring EXPO key..."
Expand Down
1 change: 1 addition & 0 deletions builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ _secrets: &secrets # Infrastructure
MM_BRAZE_SDK_ENDPOINT: 'MM_BRAZE_SDK_ENDPOINT'
# Expo
EXPO_PROJECT_ID: 'EXPO_PROJECT_ID'
EXPO_TOKEN: 'EXPO_TOKEN'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

BSC and SEI QuickNode secrets missing from builds.yml

High Severity

QUICKNODE_BSC_URL and QUICKNODE_SEI_URL were removed from the OTA workflow's hardcoded env block but never added to the _secrets anchor in builds.yml. The PR changelog claims all three secrets (QUICKNODE_BSC_URL, QUICKNODE_SEI_URL, EXPO_TOKEN) were added, but only EXPO_TOKEN actually appears in the diff. These variables are actively used in production code (app/util/networks/customNetworks.tsx) for BSC and SEI network failover URLs, so OTA updates will silently lose failover capability for those chains.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2fd32c0. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've confirmed with @Prithpal-Sooriya that we don't need QUICKNODE_BSC_URL and QUICKNODE_SEI_URL


# Signing config (AWS Secrets Manager) - omit for dev/simulator builds
# android_keystore_path: filename in android/keystores/ (build.gradle expects fixed paths)
Expand Down
147 changes: 61 additions & 86 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -676,82 +676,54 @@ generateAndroidBinary() {
createEnvFile() {
echo "📝 Creating .env file from environment variables..."

# List of environment variable names to export
local ENV_VARS=(
"MM_MUSD_CONVERSION_FLOW_ENABLED"
"MM_NETWORK_UI_REDESIGN_ENABLED"
"MM_NOTIFICATIONS_UI_ENABLED"
"MM_PERMISSIONS_SETTINGS_V1_ENABLED"
"MM_PERPS_BLOCKED_REGIONS"
"MM_PERPS_ENABLED"
"MM_PERPS_HIP3_ALLOWLIST_MARKETS"
"MM_PERPS_HIP3_BLOCKLIST_MARKETS"
"MM_PERPS_HIP3_ENABLED"
"MM_SECURITY_ALERTS_API_ENABLED"
"BRIDGE_USE_DEV_APIS"
"SEEDLESS_ONBOARDING_ENABLED"
"RAMP_INTERNAL_BUILD"
"FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN"
"FEATURES_ANNOUNCEMENTS_SPACE_ID"
"SEGMENT_WRITE_KEY"
"SEGMENT_PROXY_URL"
"SEGMENT_DELETE_API_SOURCE_ID"
"SEGMENT_REGULATIONS_ENDPOINT"
"MM_SENTRY_DSN"
"MM_SENTRY_AUTH_TOKEN"
"IOS_GOOGLE_CLIENT_ID"
"IOS_GOOGLE_REDIRECT_URI"
"ANDROID_APPLE_CLIENT_ID"
"ANDROID_GOOGLE_CLIENT_ID"
"ANDROID_GOOGLE_SERVER_CLIENT_ID"
"MM_INFURA_PROJECT_ID"
"MM_BRANCH_KEY_LIVE"
"MM_BRANCH_KEY_TEST"
"MM_CARD_BAANX_API_CLIENT_KEY"
"WALLET_CONNECT_PROJECT_ID"
"MM_FOX_CODE"
"FCM_CONFIG_API_KEY"
"FCM_CONFIG_AUTH_DOMAIN"
"FCM_CONFIG_STORAGE_BUCKET"
"FCM_CONFIG_PROJECT_ID"
"FCM_CONFIG_MESSAGING_SENDER_ID"
"FCM_CONFIG_APP_ID"
"FCM_CONFIG_MEASUREMENT_ID"
"QUICKNODE_MAINNET_URL"
"QUICKNODE_ARBITRUM_URL"
"QUICKNODE_AVALANCHE_URL"
"QUICKNODE_BASE_URL"
"QUICKNODE_LINEA_MAINNET_URL"
"QUICKNODE_MONAD_URL"
"QUICKNODE_OPTIMISM_URL"
"QUICKNODE_POLYGON_URL"
"QUICKNODE_HYPEREVM_URL"
"MM_CHARTING_LIBRARY_URL"
"MM_BRAZE_API_KEY_IOS"
"MM_BRAZE_API_KEY_ANDROID"
"MM_BRAZE_SDK_ENDPOINT"
)
# Derive build name from current METAMASK_BUILD_TYPE + METAMASK_ENVIRONMENT
local build_type="${METAMASK_BUILD_TYPE:-main}"
local environment="${METAMASK_ENVIRONMENT:-production}"
local normalized_env="$environment"
case "$environment" in
production) normalized_env="prod" ;;
esac
local build_name="${build_type}-${normalized_env}"

# Read env + secret keys from builds.yml (single source of truth).
# Any env var set in the shell (from builds.yml, workflow, or feature flags)
# that matches a key in builds.yml will be written to .env.
local builds_yml_keys
builds_yml_keys=$(node -e "
const yaml = require('js-yaml');
const fs = require('fs');
const config = yaml.load(fs.readFileSync('${__DIRNAME__}/../builds.yml', 'utf8'));
const build = config.builds['${build_name}'];
if (!build) { console.error('Build not found: ${build_name}'); process.exit(1); }
const keys = new Set([
...Object.keys(build.env || {}),
...Object.keys(build.secrets || {}),
]);
keys.forEach(k => console.log(k));
")

if [ $? -ne 0 ]; then
echo "❌ Failed to read builds.yml for build '${build_name}'"
return 1
fi

# Create .env file and export to GITHUB_ENV
> .env
local exported_count=0
for var in "${ENV_VARS[@]}"; do
# Check if variable is set (defined), not just non-empty
# This allows explicitly empty strings to be written to .env
while IFS= read -r var; do
if [ -n "${!var+x}" ]; then
value="${!var}"
echo "${var}=${value}" >> .env

# Export to GITHUB_ENV if running in GitHub Actions

if [ -n "${GITHUB_ENV:-}" ]; then
echo "${var}=${value}" >> "$GITHUB_ENV"
fi

exported_count=$((exported_count + 1))
fi
done
done <<< "$builds_yml_keys"

echo "📄 .env file created with ${exported_count} variables"
echo "📄 .env file created with ${exported_count} variables (from build: ${build_name})"
}

buildExpoUpdate() {
Expand Down Expand Up @@ -1002,40 +974,43 @@ checkParameters "$@"
printTitle

# ─────────────────────────────────────────────────────────────────────────────
# Load build configuration. Gated by BUILDS_ENABLED_WITH_GH_ACTIONS_TEMPORARY:
# Load build configuration from builds.yml (all platforms including expo-update).
# Gated by BUILDS_ENABLED_WITH_GH_ACTIONS_TEMPORARY:
# true = GHA (set by workflow) and local (set in .js.env) → use builds.yml
# false = Bitrise (unset) → skip builds.yml, use legacy remap only
# Local: .js.env is applied after loadBuildConfig so it overrides (see below).
# ─────────────────────────────────────────────────────────────────────────────

# Non-GHA: source .js.env early so BUILDS_ENABLED_WITH_GH_ACTIONS_TEMPORARY is set for the gate (local can opt in)
if [ -z "${GITHUB_ACTIONS:-}" ] && [ -e "$JS_ENV_FILE" ]; then
source "$JS_ENV_FILE"
fi

BUILD_TYPE_FOR_CONFIG=$(echo "$METAMASK_BUILD_TYPE" | tr '[:upper:]' '[:lower:]')
if [ "${BUILDS_ENABLED_WITH_GH_ACTIONS_TEMPORARY:-false}" = "true" ]; then
# builds.yml path: GHA or local with flag.
if ! loadBuildConfig "$BUILD_TYPE_FOR_CONFIG" "$METAMASK_ENVIRONMENT"; then
echo "❌ Build configuration failed. Exiting."
exit 1
fi
else
echo "⚠️ BUILDS_ENABLED_WITH_GH_ACTIONS_TEMPORARY is not true; skipping builds.yml, using legacy remap / .js.env"
echo ""
fi

# Local builds: .js.env overrides builds.yml (takes precedence)
if [ -z "${GITHUB_ACTIONS:-}" ] && [ -e "$JS_ENV_FILE" ]; then
source "$JS_ENV_FILE"
fi

# Native-build-specific flags and legacy Bitrise remap (not needed for expo-update)
if [ "$PLATFORM" != "expo-update" ]; then
# Set flags for main builds
if [ "$METAMASK_BUILD_TYPE" == "main" ]; then
export GENERATE_BUNDLE=true # Used only for Android
export PRE_RELEASE=true # Used mostly for iOS, for Android only deletes old APK and installs new one
fi

# Non-GHA: source .js.env early so BUILDS_ENABLED_WITH_GH_ACTIONS_TEMPORARY is set for the gate (local can opt in)
if [ -z "${GITHUB_ACTIONS:-}" ] && [ -e "$JS_ENV_FILE" ]; then
source "$JS_ENV_FILE"
fi

BUILD_TYPE_FOR_CONFIG=$(echo "$METAMASK_BUILD_TYPE" | tr '[:upper:]' '[:lower:]')
if [ "${BUILDS_ENABLED_WITH_GH_ACTIONS_TEMPORARY:-false}" = "true" ]; then
# builds.yml path: GHA or local with flag.
if ! loadBuildConfig "$BUILD_TYPE_FOR_CONFIG" "$METAMASK_ENVIRONMENT"; then
echo "❌ Build configuration failed. Exiting."
exit 1
fi
else
echo "⚠️ BUILDS_ENABLED_WITH_GH_ACTIONS_TEMPORARY is not true; skipping builds.yml, using legacy remap / .js.env"
echo ""
fi

# Local builds: .js.env overrides builds.yml (takes precedence)
if [ -z "${GITHUB_ACTIONS:-}" ] && [ -e "$JS_ENV_FILE" ]; then
source "$JS_ENV_FILE"
fi

# Bitrise (or other non-GHA CI): legacy env remapping (secrets use per-env names, e.g. SEGMENT_WRITE_KEY_PROD)
if [ -z "${GITHUB_ACTIONS:-}" ]; then
if [ "$METAMASK_BUILD_TYPE" == "main" ]; then
Expand Down
Loading