New WDK Starter RN App - #38
Conversation
- expo-router file-based routing under src/app - all 18 screens with working navigation - v2 light theme + dark-ready theme system - reusable component library - domain/data/session seams for WDK (mock repository) - auto-lock, edge-to-edge config
- RepositoriesProvider DI boundary (mock now, WDK-ready) - QueryClient + typed query hooks + centralized keys - loading/error/retry states on all data screens - screens no longer import concrete repositories
- add WdkAppProvider at root with minimal Bitcoin wdkConfigs - import generated worklet bundle - log useWdkApp lifecycle (INITIALIZING -> NO_WALLET confirmed) - no screen/behaviour change; still on mock repository
- Replace mock data with live WDK hooks (balances, account, send) - Wire wallet create/import/unlock via useWalletManager - Add session/lock handling: WdkSessionGate + AutoLockOnBackground - Fix locked-vs-no-wallet detection (lock() reports NO_WALLET, not LOCKED; disambiguate via persisted wallets list) Verified on device: create → real balance/address → lock → unlock.
…which is encrypted first with the help of wdk-utils and then we are storing it to secure storage
… icon on wallet and receive screen
…ve acccounts get listed in accounts screen
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
| "CloudKit" | ||
| ], | ||
| "com.apple.developer.icloud-container-identifiers": [ | ||
| "iCloud.io.tether.wdkshowcase" |
There was a problem hiding this comment.
CloudKit container here is iCloud.io.tether.wdkshowcase, but docs / .env.example use iCloud.io.tether.wdkstarterreactnative. Please align these.
| * | ||
| * ⚠️ Arbitrum and Polygon below point at REAL MAINNET — genuine funds and | ||
| * real Pimlico gas-sponsorship costs apply there. Only Ethereum (Sepolia) | ||
| * is a safe-to-experiment-freely testnet in this configuration. |
There was a problem hiding this comment.
Testnets and mainnets are easy to confuse here: ethereum is Sepolia in .env/explorers/indexer but labeled “Ethereum” in the UI; BTC uses network: 'bitcoin' (mainnet bc1… addresses) with a testnet Blockbook default and testnet explorers; Arb/Polygon are real mainnet. Please make network identity explicit end-to-end (config + labels + provider + explorers) so forks can tell testnet from real funds.
| package: '@tetherto/wdk-wallet-btc' | ||
| }, | ||
| spark: { | ||
| package: '@tetherto/wdk-wallet-spark' |
There was a problem hiding this comment.
Spark is in the worklet but not in runtime config/UI. Docs say Bitcoin needs it — wdk-wallet-btc doesn’t, and the bundle builds without it. Drop it or document why it stays.
jonathunne
left a comment
There was a problem hiding this comment.
Overall looks really fantastic! Aside from the comments I left in-line, I wanted to flag a few places where it looks like we can trim out some dead code. These were picked up by. a clanker, so verify carefully!
- The docs already admit
src/data/is obsolete (an earlier mock/WDK seam from before screens talked to WDK directly). It’s still in the tree? src/data/**— no screen undersrc/app/imports@/data;RepositoriesProvideris never mounted.AccountsHydrator— not mounted, and it callshydrate()on a store that no longer has it (accounts were deliberately made in-memory-only after the iOS Keychain-survives-uninstall bug).src/state/session.ts— unused; real session routing isuseWdkSession/WdkSessionGate.ARCHITECTURE.mdstill lists it with the live stores - please fix that when deleting.TokenIcon— exported but unused; every screen usesAssetIcon. Two icon components is one too many for a starter surface.
… Receive/Accounts/Activity fixes
… builds
buildEvmConfig() read its provider/bundler/paymaster values through an
interpolated key:
process.env[`EXPO_PUBLIC_EVM_${prefix}_PROVIDER`]
babel-preset-expo inlines a process.env read at bundle time only when the
key is a compile-time constant, so none of the nine EVM values were
present in the production bundle. Ethereum, Arbitrum and Polygon all
shipped with provider: '' — no balances and no sends on any EVM network.
Bitcoin was unaffected because it reads a literal key.
The bug was invisible in development: the dev server supplies
EXPO_PUBLIC_* at runtime, so the interpolated lookup resolves there and
only an installed release build shows the failure.
Each variable is now read by its literal name at the call site and passed
into buildEvmConfig, which keeps the shared shaping/warning logic. Note
bracket vs. dot notation was never the issue — process.env['LITERAL'] is
inlined fine (as in cloud-backup/CloudBackupContext.tsx); only
interpolation breaks it.
Verified by exporting a production bundle and grepping its Hermes string
table: all nine values are now present. A comment on buildEvmConfig warns
against refactoring the call sites back into a loop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sets ios.bundleIdentifier and android.package to io.tether.wdk.starter.react.native (was io.tether.wdkstarterreactnative). Nothing in src/ reads the bundle id, so this is a two-line functional change plus a documentation sweep. The Info.plist URL scheme entry updates itself: @expo/prebuild-config's setScheme always registers ios.bundleIdentifier as a scheme, so the next prebuild picks it up. The `scheme` value (wdkstarterreactnative) and the Xcode/Gradle project names are unchanged — they derive from name/slug, not the id. The CloudKit container stays iCloud.io.tether.wdkshowcase, which deliberately does not mirror the bundle id (Apple permits this). Docs and .env.example previously claimed the container was iCloud.io.tether.wdkstarterreactnative, which never matched app.json's entitlements — corrected to the real value, since a release provisioning profile must carry that exact container or signing fails. Store identifiers are immutable once created, so this lands before any App Store Connect or Play Console record exists. The Android OAuth client for Google Sign-In must be registered against the new package name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g plugin Three pieces of build configuration, none of which existed before. eas.json — a single `production` build profile with credentialsSource: "local" (signing material comes from credentials.json, supplied to CI as a secret) and appVersionSource: "remote" with autoIncrement, so build numbers and versionCode come from the EAS server-side counter rather than the template values that prebuild would reset. Android builds an app bundle for armeabi-v7a and arm64-v8a only; gradle.properties otherwise builds all four ABIs. The submit profile targets TestFlight and the Play `internal` track — never a production listing. Its iOS fields are placeholders until the App Store Connect records exist. app.config.js — a thin overlay on app.json rather than a rewrite. Expo passes the app.json-derived config in, so this only sources `slug` from EAS_PROJECT_SLUG and extra.eas.projectId from EAS_PROJECT_ID, with committed fallbacks so local prebuild works with no environment set. Both are required at *build* time, not just submit, because the remote version counter is a server call. modules/build/withCiBuildHardening.js — android/ is gitignored and regenerated by prebuild, so release-build fixes have to be config plugins. Disables Android Lint for release builds (lintVitalRelease exhausts the template's 512m Metaspace cap and fails with OutOfMemoryError; this app is especially exposed because expo-build-properties enables both ProGuard and resource shrinking) and raises the Gradle daemon heap from the template's -Xmx2048m, which is not enough for an R8 release build of this dependency tree. Verified: prebuild emits the lint block and the raised jvmargs, and `expo config` resolves the overlay both with and without the env overrides. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports the pipeline already proven in tetherto/city-wallets-wl-app-mobile,
collapsed from its two-app matrix to this repo's single app: local EAS
builds on GitHub Actions runners, then `eas submit` to TestFlight and the
Play `internal` track. workflow_dispatch only, with buildTarget
(stores|android|ios) and pushTag inputs.
Three composite actions:
setup-eas — Node 22 plus a pinned eas-cli. Forces npm >= 11 because
npm 10 silently drops packages from git-dependency trees
and this project has three git deps (docs/ENVIRONMENT.md).
select-xcode — pins Xcode 26.3 on the macOS runner.
write-dotenv — materialises the EXPO_PUBLIC_* config to .env before the
build, since babel-preset-expo inlines those at bundle
time and eas build --local bundles in a copy of the tree.
Notable differences from the city-wallets original, all deliberate:
- No link-bare-addons step. react-native-bare-kit self-links via its
podspec prepare_command and a gradle `link` task; the upstream script
only exists to work around that repo's yarn workspaces.
- npm rather than yarn/corepack.
- Artifact location comes from `eas build --output` instead of
`find $GITHUB_WORKSPACE -name '*.aab'`, which would also match the
gradle intermediate at android/app/build/outputs/bundle/release/.
- .env values are written single-quoted with $ escaped, and the step fails
on a value it cannot encode. Expo parses .env with node:util.parseEnv and
then interpolates, so an unquoted # or an unescaped $word silently
truncates the value — verified against Expo's own parser. Empty secrets
are warned about and omitted rather than inlined as "".
- The 20 EXPO_PUBLIC_* secrets are mapped at job level, not on the `uses:`
step: a composite action's inner steps do not reliably see step-level env.
- concurrency queues instead of cancelling; a cancelled run can leave a
half-finished store submission behind.
Also adds .github/actionlint.yaml declaring the self-hosted runner label so
`actionlint` runs clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/RELEASE.md covers how the pipeline is wired, the full secret inventory (including which values are base64 and the one that is raw JSON), the credentials.json shape — which exists only as a secret and so is not otherwise recorded anywhere — and the one-time setup still outstanding before the workflow can go green. Called out explicitly because each has a misleading failure mode: - The distribution provisioning profile must carry the iCloud entitlement with container iCloud.io.tether.wdkshowcase, which deliberately differs from the bundle id. - The release keystore's SHA-1 must be registered against the new package name, or Google Sign-In fails with DEVELOPER_ERROR (10) in release builds only. - The Play Developer API cannot create an app's first release, so one AAB must be uploaded by hand. Doing that with a locally built artifact consumes versionCode 1 and then collides with the EAS remote counter — the documented sequence avoids that by using the artifact from a CI run whose submit step failed. - The two *_NETWORK_LABEL variables are intentionally blank on mainnet, so a warning about them is not necessarily a missing secret. Includes the credential-free bundle test for confirming EXPO_PUBLIC_* values actually reach the bundle, and a troubleshooting table for the failure modes seen while building this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…workflow ci: add Build and Publish workflow (TestFlight + Play internal), and fix EVM env inlining
| 6. [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) — a running list of | ||
| real bugs we hit and how they were fixed, so you don't rediscover them. | ||
|
|
||
| ## What's actually built right now |
There was a problem hiding this comment.
Can we change the wording here? The README shouldn't describe what "this PR" covers.
| manages its underlying provider connections, not something fixable from | ||
| this app's code. See `docs/TROUBLESHOOTING.md`'s last entry. | ||
|
|
||
| ## Fork & run checklist |
There was a problem hiding this comment.
Would prefer one "getting started" checklist instead of two tiers to keep things simple.
Summary
Completes the onboarding flow end to end against a real WDK wallet —
not mocked — from Welcome through wallet creation/import, app password
setup, and cloud backup. Every screen was built by extracting the exact
markup/CSS from the design prototype rather than approximating it.
What's included
WDK integration (Steps 1–5)
runtime initialized)
docs/WDK_INTEGRATION.mdfor the full step-by-step and the versionpins that matter (
wdk-react-native-coremust stay on1.0.0-beta.10— see that doc for why)
Onboarding screens (prototype-matched)
word grid)
Security
@tetherto/wdk-utils, hardware-backedvault key) — see
docs/SECURITY.mdfor the stated tradeoff vs. aone-way verifier
fixes for three platform-specific bugs (iOS's
inactivetransitionstate, wallet-creation's transient locked-looking state, Google
Sign-In's native-Activity false-backgrounding signal)
Cloud backup
platform (not platform-gated)
including the iOS Google OAuth client this required beyond the
reference implementation) documented in
docs/CLOUD_BACKUP.mdResponsive design system
anchored to the prototype's own canvas size, not fixed pixel values
Documentation
README.md— replaces a stale template README that didn't describethis project
docs/ENVIRONMENT.md,docs/ARCHITECTURE.md,docs/PROJECT_STRUCTURE.md,docs/WDK_INTEGRATION.md,docs/SECURITY.md,docs/CLOUD_BACKUP.md,docs/TROUBLESHOOTING.mdCONTRIBUTING.md,.env.exampleTesting done
wallet home
phrase
uninstall — self-healing fix verified)
Known limitations (not in scope for this PR)
yet —
downloadBackup()exists and is readythe same prototype-matching pass as onboarding
mnemonic validation)
How to test this PR
See
README.md→ Quick Start, anddocs/ENVIRONMENT.mdfor requiredtoolchain versions (npm ≥ 11 and JDK 17 specifically — mismatches here
cause confusing native crashes, not clean errors). Cloud backup requires
your own Apple/Google developer setup — see
docs/CLOUD_BACKUP.md.