-
Notifications
You must be signed in to change notification settings - Fork 14
Prod Release Sprint 151 — KYC verdict rendering · Solana/Tron/Base withdrawals · profile fixes (2026-07-16) #2407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a49ed5f
0fd9c23
e9f98c4
2e7e116
60a7f98
78a690a
a635ad2
1b6292e
d7a6508
05242ce
50d9202
8ebeec4
559d252
e6533d9
a9868e9
421359c
7fadb40
0d116a9
93e4ee2
a704ce5
3d0662f
ac1907a
2f24981
d12f2ee
b6b54d5
cb9b388
b44e826
6b8477d
4175a99
2f599a5
cbb51df
d70dfe2
40cbbe3
900aac9
1b9d123
6491d54
8c7ceef
2e4f795
5651fb0
35b608c
ec75c13
11e4e59
27bf5c9
1824561
e5f1949
b824a75
47068bd
7ae547b
11ccaa6
4dfe3d9
bff8147
43fdd7c
68b6ece
db3100f
9e7013f
204745a
0154a1a
b2950ed
41427f0
3ef0a57
65ad2c4
f7c7d23
937cf95
f26ff67
b1ec895
14a826d
30b37e1
3af5b20
ab6d894
3b71b5c
9c1e232
f84118b
8201e5d
8e7d53c
361c7ba
273d847
9de4033
9affb9a
20653a7
5379c14
b0fd006
d5cb82e
920f52d
0decad8
a063022
ae2a38e
42c3059
8146974
9875a13
ad5f3e6
347d912
723608f
c3d8163
9c30162
9339369
2d14825
bdd7629
f69cd79
ea918e7
0bda73d
ea3e711
f28976c
7f2e492
310bf94
3588f95
862d65a
0e8af8b
aa9f85c
7202b22
8b19e47
7decc35
9e5f98a
45a3250
06cc144
b768338
0b5a408
f5ffa21
d463cbb
9d826e2
8276a5c
c6264f4
2b0bec4
da943d6
32454bb
9970167
5fae58b
bdf38bb
49971f0
c68f7c2
9d80667
7842a37
24d2931
78f17b1
481c48b
a2e8650
895ea21
9642943
4d84133
dd1d19a
983fe6c
1d17ba9
0b5ad18
05d5ed1
51f5679
8e1f493
d05399e
8491d25
f70d7b2
17ae881
6973b69
a918cf8
021a71c
d080e39
cf6ec71
c6a8538
eb88b07
5cda85e
852e63c
70e8a22
1d399a7
664903d
7553dde
f3d8090
71ff447
b44c52b
7d9f354
cbaffc3
8fa2c5a
b87b0c6
696cf50
d805702
ea9c21e
d904b60
016b34b
a441dcc
bf96bdb
aa44a14
77e5a81
913c4bd
9ea0065
972f354
c8a8657
9a6c4e6
558b1cf
96a23ca
090dbc1
23d43bd
bc23beb
814ea00
7e0bc5b
e1b4d00
7de82b9
bbadbf1
9510882
83ed62a
f5e2156
ea63b6c
0ed73e9
a973865
5c7ec1d
914c22f
914ff46
74d03c4
89e0621
bd5af5f
6355cc6
a162edf
20dfbf0
13d05cc
ae09e7d
d532b6a
ce7ac76
5609a35
baf2dc7
2b8bacf
c55b448
a91e1a7
d5fd316
3f5b96b
a9cb2d9
2637d66
c0c1ffc
ad903ab
9223e5c
5d5d66c
0bab866
74d2371
c0bee71
776995c
d79de8b
6d166d9
b8e9bc4
1851669
59e3624
679905f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # CodeRabbit review scope. Generated native scaffolding (Xcode/Gradle | ||
| # projects, plists, resources) isn't worth AI review and pushes large | ||
| # mobile PRs over the 150-file review limit. | ||
| reviews: | ||
| path_filters: | ||
| - '!ios/**' | ||
| - '!android/**' | ||
| - '!patches/**' | ||
| - '!**/*.svg' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| name: Android Release (Play) | ||
|
|
||
| # Builds a SIGNED Android App Bundle and uploads it to Google Play. | ||
| # Removes the "release only builds on one laptop" gap: the upload keystore lives | ||
| # as CI secrets, the build is reproducible, and the AAB lands on a Play track. | ||
| # | ||
| # Trigger: push a tag `vX.Y.Z`, or run manually (workflow_dispatch) and pick a track. | ||
| # Prereq: fix/native-build-reliability must be merged (card-comparison static-export | ||
| # fix) or `native:release` will fail to build. | ||
| # | ||
| # Required repo secrets (see docs/NATIVE-RELEASE.md §Ops): | ||
| # ANDROID_KEYSTORE_BASE64 base64 of the upload keystore | ||
| # ANDROID_KEYSTORE_PASSWORD store password | ||
| # ANDROID_KEY_ALIAS key alias (e.g. peanut) | ||
| # ANDROID_KEY_PASSWORD key password | ||
| # PLAY_SERVICE_ACCOUNT_JSON Google Play Developer API service-account JSON | ||
| # SUBMODULE_TOKEN read access to the src/content submodule | ||
| # Plus the production NEXT_PUBLIC_* the static export bakes in (see the env step). | ||
|
|
||
| on: | ||
| push: | ||
| tags: ['v*'] | ||
| workflow_dispatch: | ||
| inputs: | ||
| track: | ||
| description: 'Play track' | ||
| required: true | ||
| default: 'internal' | ||
| type: choice | ||
| options: [internal, alpha, beta, production] | ||
| versionName: | ||
| description: 'versionName override (optional; defaults to package.json)' | ||
| required: false | ||
| type: string | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: android-release-${{ github.ref }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| release: | ||
| runs-on: ubuntu-latest | ||
| # Protect with required reviewers in repo Settings → Environments → Production. | ||
| environment: Production | ||
| steps: | ||
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | ||
| with: | ||
| submodules: recursive | ||
| token: ${{ secrets.SUBMODULE_TOKEN }} | ||
|
|
||
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6 | ||
|
|
||
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | ||
| with: | ||
| node-version: '22' # @sentry/profiling-node has no Node 25 binary; project targets 22 | ||
| cache: 'pnpm' | ||
|
|
||
| - uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5 | ||
| with: | ||
| distribution: 'temurin' | ||
| java-version: '21' # Capacitor 8's capacitor-android compiles at source 21 | ||
|
|
||
| - name: Install dependencies | ||
| run: pnpm install | ||
|
|
||
| - name: Decode upload keystore | ||
| env: | ||
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | ||
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | ||
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | ||
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | ||
| run: | | ||
| echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/peanut-release.keystore | ||
| cat > android/keystore.properties <<EOF | ||
| storeFile=../peanut-release.keystore | ||
| storePassword=${ANDROID_KEYSTORE_PASSWORD} | ||
| keyAlias=${ANDROID_KEY_ALIAS} | ||
| keyPassword=${ANDROID_KEY_PASSWORD} | ||
| EOF | ||
|
|
||
| - name: Production web env | ||
| run: | | ||
| # NEXT_PUBLIC_* values are baked into the static export at build time. | ||
| # rpId MUST be peanut.me (passkeys + assetlinks/AASA). | ||
| # Keep this block identical to ios-release.yml. CAPACITOR_BUILD / | ||
| # IS_NATIVE_BUILD / GIT_COMMIT_HASH are auto-baked by next.config.native.js. | ||
| cat > .env.production.local <<EOF | ||
| NEXT_PUBLIC_BASE_URL=https://peanut.me | ||
| NEXT_PUBLIC_PEANUT_API_URL=${{ vars.NEXT_PUBLIC_PEANUT_API_URL }} | ||
| NEXT_PUBLIC_NATIVE_RP_ID=peanut.me | ||
| NEXT_PUBLIC_ZERO_DEV_BUNDLER_URL=${{ secrets.NEXT_PUBLIC_ZERO_DEV_BUNDLER_URL }} | ||
| NEXT_PUBLIC_ZERO_DEV_PAYMASTER_URL=${{ secrets.NEXT_PUBLIC_ZERO_DEV_PAYMASTER_URL }} | ||
| NEXT_PUBLIC_ZERO_DEV_PASSKEY_PROJECT_ID=${{ secrets.NEXT_PUBLIC_ZERO_DEV_PASSKEY_PROJECT_ID }} | ||
| NEXT_PUBLIC_ZERO_DEV_RECOVERY_BUNDLER_URL=${{ secrets.NEXT_PUBLIC_ZERO_DEV_RECOVERY_BUNDLER_URL }} | ||
| NEXT_PUBLIC_JUSTANAME_ENS_DOMAIN=peanut.me | ||
| NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }} | ||
| NEXT_PUBLIC_POSTHOG_KEY=${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }} | ||
| NEXT_PUBLIC_POSTHOG_HOST=${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} | ||
| NEXT_PUBLIC_GA_KEY=${{ vars.NEXT_PUBLIC_GA_KEY }} | ||
| NEXT_PUBLIC_ONESIGNAL_APP_ID=${{ secrets.NEXT_PUBLIC_ONESIGNAL_APP_ID }} | ||
| NEXT_PUBLIC_SAFARI_WEB_ID=${{ vars.NEXT_PUBLIC_SAFARI_WEB_ID }} | ||
| NEXT_PUBLIC_ONESIGNAL_WEBHOOK=${{ vars.NEXT_PUBLIC_ONESIGNAL_WEBHOOK }} | ||
| EOF | ||
|
|
||
| - name: Decode google-services.json | ||
| # FCM credentials for native push. When the secret is unset the file | ||
| # is skipped and build.gradle simply omits the google-services plugin | ||
| # (push disabled, build still succeeds). | ||
| env: | ||
| ANDROID_GOOGLE_SERVICES_JSON: ${{ secrets.ANDROID_GOOGLE_SERVICES_JSON }} | ||
| run: | | ||
| if [ -n "$ANDROID_GOOGLE_SERVICES_JSON" ]; then | ||
| echo "$ANDROID_GOOGLE_SERVICES_JSON" | base64 -d > android/app/google-services.json | ||
| echo "✅ google-services.json written" | ||
| else | ||
| echo "⚠️ ANDROID_GOOGLE_SERVICES_JSON unset — native push disabled for this build" | ||
| fi | ||
|
|
||
| - name: Build signed AAB | ||
| run: | | ||
| # versionName: manual dispatch input wins; else the tag name minus | ||
| # its leading 'v' (v1.0.10 -> 1.0.10); else build.gradle's | ||
| # package.json fallback. Keeps the Play versionName in lockstep | ||
| # with the release tag without a manual package.json bump. | ||
| VERSION_NAME="${{ github.event.inputs.versionName }}" | ||
| if [ -z "$VERSION_NAME" ] && [ "$GITHUB_REF_TYPE" = "tag" ]; then | ||
| VERSION_NAME="${GITHUB_REF_NAME#v}" | ||
| fi | ||
| export ANDROID_VERSION_NAME="$VERSION_NAME" | ||
| # Monotonic (run_number always increases). +10000 clears legacy | ||
| # codes already on Play from earlier manual/local uploads (small | ||
| # console codes plus git-commit-count builds up to ~8600). | ||
| export ANDROID_VERSION_CODE=$((10000 + GITHUB_RUN_NUMBER)) | ||
| pnpm native:release | ||
|
|
||
| - name: Upload to Google Play | ||
| uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1 | ||
| with: | ||
| serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} | ||
| packageName: me.peanut.wallet | ||
| releaseFiles: android/app/build/outputs/bundle/release/app-release.aab | ||
| tracks: ${{ github.event.inputs.track || 'internal' }} | ||
| status: completed | ||
| # First releases have no reviewed base, so Play can't auto-submit | ||
| # for review; commit the edit and review from the Console instead. | ||
| changesNotSentForReview: true | ||
| # Staged production rollout: set status: inProgress + userFraction: 0.1, | ||
| # then promote in Play Console once crash/error rates look clean. | ||
|
|
||
| - name: Upload AAB as build artifact | ||
| if: always() | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | ||
| with: | ||
| name: app-release-aab | ||
| path: android/app/build/outputs/bundle/release/app-release.aab | ||
| if-no-files-found: warn |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| name: Deploy OTA Update — iOS only (Capgo) | ||
|
|
||
| # iOS-only OTA for the mobile-release branch. Uploads the JS bundle to a | ||
| # dedicated iOS channel so Android (which lives on the `production`/`staging` | ||
| # channels) is never affected. Devices must be subscribed to this channel to | ||
| # receive it (self-assign enabled below), so this is a testing/preview lane — | ||
| # not a push to all production iOS users. | ||
|
|
||
| on: | ||
| push: | ||
| branches: [feat/mobile-release] | ||
| workflow_dispatch: | ||
| inputs: | ||
| channel: | ||
| description: 'iOS Capgo channel to deploy to' | ||
| required: true | ||
| default: 'ios-mobile-release' | ||
| type: string | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: capgo-deploy-ios-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| deploy: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: | ||
| submodules: true | ||
| token: ${{ secrets.SUBMODULE_TOKEN }} | ||
|
|
||
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6 | ||
|
|
||
| - uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: '20' | ||
| cache: 'pnpm' | ||
|
|
||
| - name: Install dependencies | ||
| run: pnpm install | ||
|
|
||
| - name: Build native static export | ||
| run: node scripts/native-build.js | ||
|
|
||
| - name: Verify build output | ||
| run: | | ||
| test -d out && test -f out/index.html || (echo "ERROR: out/ directory missing or incomplete" && exit 1) | ||
| echo "Bundle ready. File count: $(find out -type f | wc -l)" | ||
|
|
||
| - name: Resolve channel | ||
| id: channel | ||
| run: echo "name=${{ github.event.inputs.channel || 'ios-mobile-release' }}" >> "$GITHUB_OUTPUT" | ||
|
Comment on lines
+54
to
+56
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win Do not interpolate workflow inputs or refs into shell code. A dispatch channel containing shell syntax reaches Line 81 in a step holding Also applies to: 71-85, 87-92 🧰 Tools🪛 zizmor (1.26.1)[error] 56-56: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| # Best-effort: create the channel and lock it to iOS. Idempotent and | ||
| # non-fatal — if the channel already exists / flags differ, the upload | ||
| # below still runs and the channel's platform can be set once in the | ||
| # Capgo dashboard (iOS on, Android off, self-assign on). | ||
| - name: Ensure iOS-only channel exists | ||
| continue-on-error: true | ||
| run: | | ||
| npx @capgo/cli@latest channel add ${{ steps.channel.outputs.name }} \ | ||
| --apikey ${{ secrets.CAPGO_API_KEY }} || true | ||
| npx @capgo/cli@latest channel set ${{ steps.channel.outputs.name }} \ | ||
| --apikey ${{ secrets.CAPGO_API_KEY }} \ | ||
| --ios --no-android --self-assign | ||
|
|
||
| - name: Upload iOS bundle to Capgo | ||
| # Pass the commit message via env, never inline — a multi-line message | ||
| # (or one containing quotes) injected into the run script breaks the | ||
| # --comment quoting and spills into positional args. Use its first line. | ||
| env: | ||
| CAPGO_API_KEY: ${{ secrets.CAPGO_API_KEY }} | ||
| COMMIT_MSG: ${{ github.event.head_commit.message }} | ||
| run: | | ||
| COMMENT="iOS OTA ${GITHUB_SHA:0:7} — $(printf '%s' "${COMMIT_MSG:-manual deploy}" | head -n1)" | ||
| npx @capgo/cli@latest bundle upload \ | ||
| --channel "${{ steps.channel.outputs.name }}" \ | ||
| --apikey "$CAPGO_API_KEY" \ | ||
| --path ./out \ | ||
| --auto-min-update-version \ | ||
| --comment "$COMMENT" | ||
|
|
||
| - name: Deployment summary | ||
| run: | | ||
| echo "## iOS-only OTA Deployment" >> $GITHUB_STEP_SUMMARY | ||
| echo "- **Channel:** ${{ steps.channel.outputs.name }} (iOS only)" >> $GITHUB_STEP_SUMMARY | ||
| echo "- **Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY | ||
| echo "- **Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Restrict the channel and fail closed on platform configuration.
The input accepts channels such as
production, while this step can disable Android on that channel and then continue even if enforcement fails. Use a fixed/allowlisted iOS channel and requirechannel setto succeed.Also applies to: 58-69
🤖 Prompt for AI Agents