Add mobile vault screen for deposit, withdraw, and escrow management - #1
Open
BigManly4 wants to merge 45 commits into
Open
Add mobile vault screen for deposit, withdraw, and escrow management#1BigManly4 wants to merge 45 commits into
BigManly4 wants to merge 45 commits into
Conversation
Port the vault sub-account flow from the Next.js wallet (frontend/wallet/app/vault/page.tsx and frontend/wallet/lib/vault.ts) to Expo/React Native, following the conventions already established by the bulk-payout and swap mobile ports. - frontend/mobile/lib/vault.ts: typed vault domain logic ported from the wallet lib (amount/delay parsing and formatting, address validation, withdrawal status/countdown helpers). Chain submission and reads are behind injectable VaultSubmit/VaultFetch parameters, matching the executeBulkPayout(rows, submitBatch) pattern already used in lib/bulkPayout.ts, since mobile has no passkey/session signing infrastructure ported yet. - frontend/mobile/lib/escrow.ts: claimable-balance escrow helpers (createEscrow, claimEscrow, reclaimEscrow, buildEscrowClaimants). The wallet's version is a one-line re-export of sdk/src/claimableBalance.ts; that re-export does not typecheck from frontend/mobile because the mobile TS program only resolves @stellar/stellar-sdk from frontend/mobile/node_modules and there is no hoisted root node_modules, so this file mirrors that implementation locally using the @stellar/stellar-sdk dependency mobile already has. - frontend/mobile/app/vault.tsx: React Native screen covering vault creation/attach, balance display, deposit, and the full withdrawal lifecycle (queue with delay, live countdown, execute once ready, cancel while pending). Styled to match the existing dark-theme mobile screens. Signing/submission and vault reads go through stub functions declared at the top of the screen (stubSubmitVaultTx, stubFetchVaultDetails) so the flow is exercisable today and can be swapped for real Soroban RPC calls once mobile signing lands. Verified: npm run typecheck is clean, and npx expo export --platform web bundles /vault as a static route with no errors. Not verified: an interactive simulator/device render.
…racle656#504) * i18n: add Spanish translation for marketing site (Miracle656#307) * fix(website): wire /es to the Spanish message bundle The site uses the App Router, where the `i18n` key in next.config.js is ignored (it is Pages-Router-only), so `/es` 404'd and the new message bundles were never read by any component. - Drop the no-op `i18n` config key - Add `lib/i18n.ts` — typed message bundles keyed by locale, with `en.json` as the source-of-truth shape - Extract the landing page into `components/LandingPage.tsx`, driven by messages instead of hardcoded copy; `WhyVeil` takes the same messages - Add `app/es/page.tsx` (and per-locale metadata + hreflang alternates); `/` stays English - Add an EN/ES switcher to the navbar - Split `howItWorks.title` into title1/title2 so the gold highlight keeps working, and add accessible names for the comparison table - Correct Spanish copy: "Cómo Nos Compararmos" -> "Cómo Nos Comparamos", plus several awkward renderings of biometric/seedless/finality --------- Co-authored-by: Sakariyah Abdulhazeem <150973162+zeemscript@users.noreply.github.com> Co-authored-by: Miracle656 <Miracle656@users.noreply.github.com>
…racle656#414) Bumps [@stryker-mutator/typescript-checker](https://github.com/stryker-mutator/stryker-js/tree/HEAD/packages/typescript-checker) from 8.7.1 to 9.6.1. - [Release notes](https://github.com/stryker-mutator/stryker-js/releases) - [Changelog](https://github.com/stryker-mutator/stryker-js/blob/master/packages/typescript-checker/CHANGELOG.md) - [Commits](https://github.com/stryker-mutator/stryker-js/commits/v9.6.1/packages/typescript-checker) --- updated-dependencies: - dependency-name: "@stryker-mutator/typescript-checker" dependency-version: 9.6.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#418) Bumps [@stellar/stellar-sdk](https://github.com/stellar/js-stellar-sdk) from 15.1.0 to 16.1.0. - [Release notes](https://github.com/stellar/js-stellar-sdk/releases) - [Changelog](https://github.com/stellar/js-stellar-sdk/blob/main/CHANGELOG.md) - [Commits](stellar/js-stellar-sdk@v15.1.0...v16.1.0) --- updated-dependencies: - dependency-name: "@stellar/stellar-sdk" dependency-version: 16.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…iracle656#422) Bumps [next](https://github.com/vercel/next.js) from 14.2.35 to 16.2.12. - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](vercel/next.js@v14.2.35...v16.2.12) --- updated-dependencies: - dependency-name: next dependency-version: 16.2.9 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [jest-environment-jsdom](https://github.com/jestjs/jest/tree/HEAD/packages/jest-environment-jsdom) from 29.7.0 to 30.4.1. - [Release notes](https://github.com/jestjs/jest/releases) - [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/jestjs/jest/commits/v30.4.1/packages/jest-environment-jsdom) --- updated-dependencies: - dependency-name: jest-environment-jsdom dependency-version: 30.4.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The suite has been red on main since at least 2026-07-23, tainting every
sdk/ and frontend/wallet PR. Six root causes:
- No NEXT_PUBLIC_FACTORY_CONTRACT_ID in the workflow, so
computeWalletAddress('') threw "invalid version byte. expected 16, got
undefined" and every wallet-creation test failed. Uses the same public
testnet address ci.yml already sets.
- The Soroban stub matched on a URL glob that never fired: the RPC URL has
no path, so the browser normalises it to a trailing slash. Requests
escaped to the real testnet and failed with txNoAccount. Matching is now
by hostname.
- Every hardcoded XDR blob in the stubs was undecodable ("attempt to read
outside the boundary of the buffer"). They are now built with the XDR
types, so simulate/send/getTransaction actually stub out.
- The Horizon account stub omitted data_attr, which checkMemoRequired
dereferences on every payment; and classic G... payments post to Horizon
/transactions, which nothing stubbed at all.
- onboarding.spec.ts had a local addVirtualAuthenticator that attached the
CDP session to a throwaway page, so the page under test had no
authenticator and hung on "Waiting for biometric...". It now uses the
shared _authenticator helper.
- Tests asserted UI that never existed: a one-step send form (it is
Review -> Confirm & sign) and a /dashboard redirect from the recover
button (it opens /recover).
Also: the send form's labels were not associated with their inputs, so
getByLabel found nothing — wired up with htmlFor/id, which the a11y audit
wants anyway. E2E now runs against a production build; next dev compiled
routes on first request and regularly blew the test timeout.
17/17 passing on two consecutive runs, down from 9.6m to ~3m.
The 'exposes no plaintext to the server' case searched the serialized envelope for 'USD'. That string is base64, drawn from a 64-symbol alphabet, so a 3-letter token appears in a ~700-character encoding by chance often enough to fail CI at random -- as it did on PR Miracle656#537, whose diff touches no SDK code. Search the decoded ciphertext bytes instead. Same guarantee, and a chance collision now needs three specific bytes out of 256 rather than out of 64.
Adds a /settings/backup screen that seals the wallet's non-secret state into an AES-256-GCM envelope under a user passphrase and hands the file to the system share sheet. The SDK's backup module builds its envelope on crypto.subtle, which Hermes does not implement, so lib/backup.ts ports the primitives to audited pure-JS crypto (@noble) while keeping the wire format identical: version 1, AES-256-GCM over a PBKDF2-SHA256 key, base64 fields. A test decrypts our output with Web Crypto to hold that guarantee, so a file exported on mobile restores in the web wallet and vice versa. assertNoSecretMaterial is carried over unchanged and runs before every encryption, so private key material cannot reach a backup file. Also adds the Jest harness the mobile app was missing (jest-expo, with @noble opted into the transform allow-list since it ships ESM only).
* fix(ci): re-enable invisible wallet tests * fix(ci): align arbitrary dev dependency Pin arbitrary to the 1.3.x line required by soroban-sdk 22 so the contracts workspace resolves again. Assisted-by: GPT-5 Codex via Codex --------- Co-authored-by: kpassito <260419977+kpassito@users.noreply.github.com> Co-authored-by: Ebube <iupacnumen2020@gmail.com>
The mobile shell rendered everything in the system font. Load the four brand typefaces at startup with useFonts and hold render behind the splash screen so the UI never flashes a system font. - theme/typography.ts: font assets + role→family map + role text styles, mirroring the web wallet (Lora 600 italic headings, Anton accent caps at 0.08em, Inter body, Inconsolata addresses/hashes). - app/_layout.tsx: preventAutoHideAsync + useFonts(fontAssets); render held until fonts resolve, then hideAsync. - app/fonts.tsx: a /fonts specimen screen rendering all four roles. Closes Miracle656#432 Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
Ports the web wallet's useTheme hook and ThemeToggle button to React Native, persisting the choice under the same veil_theme storage key. The web version applies its theme by setting data-theme on the document root and letting CSS variables restyle everything. React Native has neither a document nor cascading variables, so the palette becomes data: lib/theme.ts defines a ThemeColors record per theme and screens build their StyleSheets from it. The dark values are the ones the app already shipped with, so dark mode looks exactly as it did and only light mode is new. The active theme lives in module state read through useSyncExternalStore rather than a context provider, which keeps useTheme() callable anywhere with nothing to wrap the tree in — the same ergonomics as the web hook — while still driving every subscriber from one source of truth. Restyles the existing screens (home, swap, bulk payout) and the root layout, so the toggle changes the whole app rather than one component. The Stack's contentStyle carries the background so a route that is still loading never flashes the opposite theme. A tap applies immediately and persists in the background: a failed write costs the preference on next launch rather than the interaction now. Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
* feat(mobile): load brand fonts at startup via expo-font The mobile shell rendered everything in the system font. Load the four brand typefaces at startup with useFonts and hold render behind the splash screen so the UI never flashes a system font. - theme/typography.ts: font assets + role→family map + role text styles, mirroring the web wallet (Lora 600 italic headings, Anton accent caps at 0.08em, Inter body, Inconsolata addresses/hashes). - app/_layout.tsx: preventAutoHideAsync + useFonts(fontAssets); render held until fonts resolve, then hideAsync. - app/fonts.tsx: a /fonts specimen screen rendering all four roles. Closes Miracle656#432 * feat(mobile): add shared UI primitives Build the reusable components every screen composes from, as native ports of the web wallet's core CSS classes so feature screens don't re-implement layout chrome. - theme/colors.ts: native palette mirroring globals.css :root vars. - components/ui/Screen.tsx (.wallet-shell shell + safe area), Card.tsx (.card/.card-md), Button.tsx (.btn-gold/.btn-ghost gold variants), AddressChip.tsx (.address-chip, Inconsolata + tap-to-copy via expo-clipboard), Highlight.tsx (.hl gold underlay). - app/_layout.tsx: wrap in SafeAreaProvider (required by Screen). - app/ui.tsx: /ui gallery rendering every primitive for side-by-side check. Closes Miracle656#433 --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
* feat(mobile): load brand fonts at startup via expo-font The mobile shell rendered everything in the system font. Load the four brand typefaces at startup with useFonts and hold render behind the splash screen so the UI never flashes a system font. - theme/typography.ts: font assets + role→family map + role text styles, mirroring the web wallet (Lora 600 italic headings, Anton accent caps at 0.08em, Inter body, Inconsolata addresses/hashes). - app/_layout.tsx: preventAutoHideAsync + useFonts(fontAssets); render held until fonts resolve, then hideAsync. - app/fonts.tsx: a /fonts specimen screen rendering all four roles. Closes Miracle656#432 * feat(mobile): add shared UI primitives Build the reusable components every screen composes from, as native ports of the web wallet's core CSS classes so feature screens don't re-implement layout chrome. - theme/colors.ts: native palette mirroring globals.css :root vars. - components/ui/Screen.tsx (.wallet-shell shell + safe area), Card.tsx (.card/.card-md), Button.tsx (.btn-gold/.btn-ghost gold variants), AddressChip.tsx (.address-chip, Inconsolata + tap-to-copy via expo-clipboard), Highlight.tsx (.hl gold underlay). - app/_layout.tsx: wrap in SafeAreaProvider (required by Screen). - app/ui.tsx: /ui gallery rendering every primitive for side-by-side check. Closes Miracle656#433 * feat(mobile): build the swap UI surface Port the layout of the web wallet's swap page to mobile: in/out token selectors (tap to open a token sheet), the amount input, a direction-flip control, and an empty-quote state. Quoting (backlog Miracle656#45) and execution (backlog Miracle656#46) fill in the receive amount and quote area later, so those render an empty state for now. Composes the shared UI primitives (Screen, Card, Button) and the typography/colour tokens. Closes Miracle656#472 --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
* feat(mobile): load brand fonts at startup via expo-font The mobile shell rendered everything in the system font. Load the four brand typefaces at startup with useFonts and hold render behind the splash screen so the UI never flashes a system font. - theme/typography.ts: font assets + role→family map + role text styles, mirroring the web wallet (Lora 600 italic headings, Anton accent caps at 0.08em, Inter body, Inconsolata addresses/hashes). - app/_layout.tsx: preventAutoHideAsync + useFonts(fontAssets); render held until fonts resolve, then hideAsync. - app/fonts.tsx: a /fonts specimen screen rendering all four roles. Closes Miracle656#432 * feat(mobile): add shared UI primitives Build the reusable components every screen composes from, as native ports of the web wallet's core CSS classes so feature screens don't re-implement layout chrome. - theme/colors.ts: native palette mirroring globals.css :root vars. - components/ui/Screen.tsx (.wallet-shell shell + safe area), Card.tsx (.card/.card-md), Button.tsx (.btn-gold/.btn-ghost gold variants), AddressChip.tsx (.address-chip, Inconsolata + tap-to-copy via expo-clipboard), Highlight.tsx (.hl gold underlay). - app/_layout.tsx: wrap in SafeAreaProvider (required by Screen). - app/ui.tsx: /ui gallery rendering every primitive for side-by-side check. Closes Miracle656#433 * feat(mobile): add the settings shell Establish the settings screen structure, ported from the web wallet's settings page: configuration grouped into Security / Preferences / General sections of navigable rows, plus a wallet-address card. Selecting a row opens that section's detail (in-page navigation mirroring the web page's section state); later settings issues fill each with real controls. Composes the shared UI primitives (Screen, Card, AddressChip). Closes Miracle656#488 --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…6#525) * feat(mobile): encrypted wallet backup export Adds a /settings/backup screen that seals the wallet's non-secret state into an AES-256-GCM envelope under a user passphrase and hands the file to the system share sheet. The SDK's backup module builds its envelope on crypto.subtle, which Hermes does not implement, so lib/backup.ts ports the primitives to audited pure-JS crypto (@noble) while keeping the wire format identical: version 1, AES-256-GCM over a PBKDF2-SHA256 key, base64 fields. A test decrypts our output with Web Crypto to hold that guarantee, so a file exported on mobile restores in the web wallet and vice versa. assertNoSecretMaterial is carried over unchanged and runs before every encryption, so private key material cannot reach a backup file. Also adds the Jest harness the mobile app was missing (jest-expo, with @noble opted into the transform allow-list since it ships ESM only). * feat(mobile): restore wallet from an encrypted backup file Completes the backup story: the /settings/backup screen gains a Restore panel that picks a backup file, decrypts it, and writes the wallet state back to device storage. Ports decryptBackup, deserializeBackup, restoreBackup, and bindNewSigner from sdk/src/backup.ts to the React Native crypto path, keeping the SDK's error contract intact. Every failure that could hand the caller a half-restored wallet raises BackupTamperError rather than returning partial data: a failed GCM tag, plaintext that is not JSON, and plaintext that is JSON but missing the fields a wallet needs. Nothing is written to storage until decryption succeeds, so a wrong passphrase or a damaged file leaves the device untouched. The screen tells the user exactly that instead of inviting a retry. Signer enrolment is injected through restoreFromFile's enrollSigner option rather than assumed. The app has no passkey registration flow yet, and the web wallet takes the same shape when WebAuthn is unavailable. Adds 28 tests covering the round trip, each individually authenticated envelope field, malformed envelopes, and bindNewSigner. --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
Add an AsyncStorage-backed useContacts hook and a contacts screen that renders the saved address book. This is the base persistence layer that add/edit and the contact picker build on top of. Co-authored-by: AdaBliss <adabliss@users.noreply.github.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
* feat(mobile): add persisted contacts list Add an AsyncStorage-backed useContacts hook and a contacts screen that renders the saved address book. This is the base persistence layer that add/edit and the contact picker build on top of. * feat(mobile): add create, edit, and delete for contacts Extend the contacts screen with an add/edit form and a delete action. Address validation and persistence are handled by useContacts, so changes survive an app relaunch. --------- Co-authored-by: AdaBliss <adabliss@users.noreply.github.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
* feat(mobile): add persisted contacts list Add an AsyncStorage-backed useContacts hook and a contacts screen that renders the saved address book. This is the base persistence layer that add/edit and the contact picker build on top of. * feat(mobile): add create, edit, and delete for contacts Extend the contacts screen with an add/edit form and a delete action. Address validation and persistence are handled by useContacts, so changes survive an app relaunch. * feat(mobile): add reusable ContactPicker component Provide a searchable bottom-sheet picker that resolves a saved contact to its Stellar address, so send and bulk-payout flows can offer contact selection without duplicating the list/search UI. --------- Co-authored-by: AdaBliss <adabliss@users.noreply.github.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
Port the web wallet's receive page to native: show the wallet address as copyable text and a scannable QR, with a share action. The QR encodes a destination-only SEP-7 pay URI (byte-compatible with the web wallet's buildSep7PayUri) so a payer's wallet opens pre-filled. - app/(tabs)/receive.tsx: QR (react-native-qrcode-svg), copy (expo-clipboard), share via the native share sheet. Self-contained styling (the shared UI primitives aren't in main yet), representative address pending wallet wiring. - deps: react-native-qrcode-svg, react-native-svg, expo-clipboard. Closes Miracle656#469 Co-authored-by: chizzy0011 <chisomnteh@gmail.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…tbox (Miracle656#542) Loss of connectivity currently surfaces as silently failing network calls with no explanation and no way to recover the work in flight. Add a NetInfo-backed connectivity provider that wraps the app, a dedicated offline route ported from the wallet's offline page, and a durable outbox so actions taken while offline are queued instead of dropped. - lib/outbox.ts: AsyncStorage-persisted FIFO queue with pluggable per-type handlers, serial flushing, bounded retries, and re-entrancy protection so a concurrent flush cannot double-send. - lib/connectivity.tsx: ConnectivityProvider + useConnectivity(). Treats an undetermined NetInfo state as online so the offline screen only appears on a positive "no connection" signal, and flushes the outbox on the offline to online transition. - app/offline.tsx: offline screen with the wallet's copy, a dependency-free no-signal glyph, a queued-action summary, and a retry that re-probes NetInfo. - app/_layout.tsx: pushes /offline when connectivity drops and pops it when it returns, preserving the underlying route. Adds a jest-expo test harness with unit coverage for the outbox. Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…6#543) Port the browser wallet's WalletConnect v2 integration to React Native so the mobile wallet can pair with external dApps and sign for web apps from the phone. - lib/polyfills.ts: the shims WalletConnect and the Stellar SDK need on Hermes. react-native-compat has to be evaluated first, so the import order is fixed here and this module is imported before anything else in lib/walletConnect.ts. - lib/walletConnectHelpers.ts: pure URI, session and request parsing kept free of native imports so the rules are unit-testable. Pasted or scanned deep links that wrap the pairing URI in a query parameter are unwrapped. - lib/walletConnect.ts: client lifecycle, pairing, session approve/reject/ disconnect and the passkey signing pipeline. Sessions persist to AsyncStorage, hashing goes through expo-crypto, and the passkey assertion comes from an injected signer so this module stays independent of any one passkey implementation. Incoming requests are held in a subscribable queue rather than dispatched as one-shot events, so an approval UI that mounts later still sees them. - lib/walletStore.ts: wallet address and fee-payer secret in the OS keychain via expo-secure-store rather than web storage. - lib/network.ts: network config reading EXPO_PUBLIC_* variables. - hooks/useWalletConnect.ts, components/ConnectDAppModal.tsx: scan with expo-camera or paste a URI, then approve or reject the session proposal. - app/index.tsx: Connect dApp entry point and the list of connected dApps. Adds a jest-expo test harness with unit coverage for the parsing helpers. Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…656#545) Port the web pools page to native: list liquidity pools and manage positions. Each pool shows APR, TVL and fee; tapping one opens a sheet with Add / Remove tabs. - Add: enter the first asset amount, the second is derived proportionally from the pool reserves; submit is gated on a positive amount. - Remove: enter a percentage of your position; the amounts you receive are computed from your pool ownership; gated on holding a position. Self-contained (shared UI primitives aren't in main yet); representative pool data pending AMM/wallet wiring. Closes Miracle656#476 Co-authored-by: chizzy0011 <chisomnteh@gmail.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…656#544) * feat(mobile): WalletConnect pairing and connect dApp modal Port the browser wallet's WalletConnect v2 integration to React Native so the mobile wallet can pair with external dApps and sign for web apps from the phone. - lib/polyfills.ts: the shims WalletConnect and the Stellar SDK need on Hermes. react-native-compat has to be evaluated first, so the import order is fixed here and this module is imported before anything else in lib/walletConnect.ts. - lib/walletConnectHelpers.ts: pure URI, session and request parsing kept free of native imports so the rules are unit-testable. Pasted or scanned deep links that wrap the pairing URI in a query parameter are unwrapped. - lib/walletConnect.ts: client lifecycle, pairing, session approve/reject/ disconnect and the passkey signing pipeline. Sessions persist to AsyncStorage, hashing goes through expo-crypto, and the passkey assertion comes from an injected signer so this module stays independent of any one passkey implementation. Incoming requests are held in a subscribable queue rather than dispatched as one-shot events, so an approval UI that mounts later still sees them. - lib/walletStore.ts: wallet address and fee-payer secret in the OS keychain via expo-secure-store rather than web storage. - lib/network.ts: network config reading EXPO_PUBLIC_* variables. - hooks/useWalletConnect.ts, components/ConnectDAppModal.tsx: scan with expo-camera or paste a URI, then approve or reject the session proposal. - app/index.tsx: Connect dApp entry point and the list of connected dApps. Adds a jest-expo test harness with unit coverage for the parsing helpers. * feat(mobile): dApp approval modal signing via device passkey Nothing a connected dApp asks for should be signed without the user seeing it. Add the approval surface that gates every incoming WalletConnect sign request. - components/WalletConnectApprovalModal.tsx: mounted once at the root, watches the pending-request queue and presents requests one at a time in arrival order. Payments show amount and destination, contract calls show the contract address and function name, and anything undecodable is labelled an unknown contract interaction to review carefully rather than presented as safe. Rejecting answers the dApp with USER_REJECTED so it fails cleanly instead of hanging. - lib/passkey.ts: device passkey signer built on react-native-passkeys, the React Native equivalent of the browser's navigator.credentials.get(). The Soroban authorization-entry hash is the WebAuthn challenge, so the assertion covers exactly the payload the wallet contract verifies, with no separate decorative biometric prompt. A dismissed sheet is a rejection, not an error. - lib/webauthn.ts: base64url and hex codecs plus DER to raw 64-byte r||s conversion, ported from sdk/src/utils.ts. Keeps the low-S normalisation, which matters because Soroban's secp256r1_verify rejects the malleable high-S form authenticators sometimes return. Unit coverage for the encoding and signature conversion, including the sign-byte, short-component and high-S cases. --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
* chore(mobile): add ESLint and Prettier tooling * fix(mobile): use ESLint flat config so `expo lint` runs The branch pinned eslint ^9 but shipped a legacy .eslintrc.js, which ESLint 9 no longer reads: ESLint couldn't find an eslint.config.(js|mjs|cjs) file. Replace it with eslint.config.js extending eslint-config-expo/flat, and sync the lockfile with the new devDependencies. `npx expo lint` now exits 0. --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
* feat(mobile): navigation shell (Miracle656#434) Lay down the full route tree so every web page has a place to live before its screen is built. Establishes the tab bar for primary destinations and stack routes for the rest, letting later issues drop content into existing routes instead of also having to wire navigation each time. - Root app/_layout.tsx wraps the Stack in SafeAreaProvider - app/index.tsx redirects to /(tabs)/dashboard - (tabs)/ group: dashboard, send, receive, settings with dark tab bar - Stack stubs for every web wallet page: swap, multisig, vault, earn, pools, buy, withdraw, contacts, recover, lock, agent, token/[id] - Shared components/ScreenScaffold.tsx + components/TabIcon.tsx - metro.config.js wires tsconfig path aliases for SDK 57 runtime - tsconfig.json registers @/components/* mapping every web route has a corresponding mobile route reachable via navigation (NavRow entries on dashboard + settings tabs prove it). * fix(mobile): resolve PR Miracle656#507 typecheck blockers - frontend/mobile/app/swap.tsx: take upstream/main's real Soroswap quoting screen (replaces local ComingSoonBadge stub; the conflict from main is now resolved). - frontend/mobile/tsconfig.json: drop the deprecated baseUrl line (TS5101 was aborting the compile before anything else ran). - frontend/mobile/components/TabIcon.tsx: widen color to ColorValue (fixes TS2322 at app/(tabs)/_layout.tsx:38, 45, 52, 59). Verified: npm run typecheck exits 0 with these exact changes. * chore(mobile): drop unrelated package-lock libc-field churn --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
recover.tsx tripped react/no-unescaped-entities on the apostrophe in "What you'll need"; buy.tsx imported colors and Text without using either. `npx expo lint` is now clean, which it needs to be for the config added in Miracle656#506 to mean anything.
@walletconnect/keyvaluestorage peers on @react-native-async-storage/async-storage 1.x, but only the root 3.1.1 was locked, so npm 10 (what CI installs) rejected the tree: npm error Missing: @react-native-async-storage/async-storage@1.24.0 from lock file npm 11 resolves this without complaint, which is why it went unnoticed locally. Regenerated with npm 10; `npm ci` now succeeds.
…e656#508) * feat(mobile): register veil:// scheme and universal/app links Register the custom scheme and platform universal/app links so payment requests and other external entry points open the right screen from both a cold start and a warm resume. - app.config.ts replaces app.json so the deep-linking surface is derived from the same constants the runtime resolver uses. Declares the veil:// and web+stellar: schemes, iOS associated domains, an autoVerify Android intent filter, and the bundle identifier / package name both need. - lib/deepLinks.ts resolves an inbound URL to an in-app route. Inbound links are untrusted, so it matches a fixed allowlist of routes and copies only the query parameters each route declares; foreign hosts, unknown schemes, and over-long URLs fall back to the home route rather than navigating. Query parsing is hand-rolled because React Native's URLSearchParams accessors throw. - app/+native-intent.ts wires it into expo-router, which calls it for both launch paths, and can never throw during launch. - Placeholder /pay, /send, /receive, and /create-wallet routes give the links somewhere to land; /pay forwards a request into the send form and preserves the raw SEP-7 URI for the handler in backlog Miracle656#38. - apple-app-site-association and assetlinks.json are served by the wallet web app, with next.config.js pinning their Content-Type. Both carry placeholders documented in the mobile README. * ci(mobile): use npm ci without the npm install fallback `npm ci || npm install` would silently paper over lockfile drift, which is the one thing the job is there to catch. Verified `npm ci` resolves cleanly against frontend/mobile/package-lock.json. --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
… config (Miracle656#514) Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
* feat(wallet): add CAP-46 NFT gallery route and metadata detection (Miracle656#349) * feat(mobile): add QuickActions dashboard row component (Miracle656#460) --------- Co-authored-by: flourishbar <you@example.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…Miracle656#518) Adds lib/sep7.ts (ported from frontend/wallet/lib/sep7.ts) to parse both web+stellar:pay?... URIs and this app's own veil://pay?... deep link scheme, and wires a Linking listener into the root layout that routes a matched link to /send with the destination/amount/asset/memo pre-filled. Registers "web+stellar" as an additional URL scheme in app.json so the OS opens such links in this app. A minimal send.tsx placeholder renders whatever got pre-filled; the interactive recipient/amount form and submit flow are separate follow-up issues. Co-authored-by: Vincent ibochi <290086463+ibochivincent-lang@users.noreply.github.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…56#452) (Miracle656#520) Port the web wallet's landing/hero experience to mobile as an onboarding flow. First-time users see the branded intro before creating a wallet; returning users skip straight past it. - Add (onboarding)/welcome.tsx with logo, tagline, feature highlights, and Get Started / Recover actions - Add (onboarding)/_layout.tsx for the onboarding route group - Update app/index.tsx to check AsyncStorage for wallet state and route first-time users to welcome, returning users to dashboard - Add @react-native-async-storage/async-storage dependency Refs: Miracle656#452, backlog item 24 Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
Adds a mobile buy screen that lets users bring fiat into the wallet through a SEP-24 anchor. lib/sep24.ts ports the deposit-side protocol logic from frontend/wallet/lib/sep24.ts (anchor TOML discovery, SEP-10 challenge validation/signing, interactive deposit request, status polling) as pure, injectable-signer functions with no browser-only APIs. app/buy.tsx drives the flow: enter anchor domain/amount/asset, kick off the interactive deposit, launch the returned URL with expo-web-browser's openAuthSessionAsync, and poll for the resulting transaction status once the user returns to the app. Adds expo-web-browser as a dependency (installed via `expo install` for SDK-compatible versioning). Co-authored-by: BigManly4 <294554482+BigManly4@users.noreply.github.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…weep (Miracle656#523) Add frontend/mobile/app/withdraw.tsx implementing the fiat off-ramp flow: asset/amount entry, anchor discovery, SEP-24 interactive withdrawal request, launching the anchor's interactive URL via expo-web-browser, and status polling until the anchor reports the transaction complete. Add frontend/mobile/lib/sep24.ts, ported from frontend/wallet/lib/sep24.ts with just the pieces the withdraw (and future deposit) flow needs: TOML discovery, SEP-10 JWT exchange, interactive withdraw/deposit requests, and transaction status polling. The wallet version signs the SEP-10 challenge with a browser WebAuthn passkey; the mobile port takes an injectable signChallenge callback instead, following the same pattern as executeBulkPayout's submitBatch parameter. Add frontend/mobile/lib/sweepContractBalance.ts, ported from frontend/wallet/lib/sweepContractBalance.ts. The RPC-only balance check (getContractBalance) is ported as-is; building/signing/submitting the transfer transaction is delegated to an injectable signAndSubmit callback since the wallet's Soroban-auth signing depends on crypto.subtle and WebAuthn, neither available on React Native and no mobile signing infra exists yet. The withdraw screen wires both stubbed signers as inline no-ops for now, matching the existing bulk-payout screen's convention of passing a stub submit function. Add expo-web-browser as a dependency (via npx expo install to match the installed Expo SDK). Co-authored-by: BigManly4 <294554482+BigManly4@users.noreply.github.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…56#526) The web wallet picks its network from NEXT_PUBLIC_NETWORK at build time. That does not carry to mobile: a build is a store submission, and a tester moving between testnet and mainnet cannot wait on one. Adds lib/network.ts, which resolves the active network from a persisted AsyncStorage override, then EXPO_PUBLIC_NETWORK, then testnet. Reads stay synchronous — hydration runs once on first import and notifies subscribers when it lands, so nothing has to await before calling getNetwork(). Switching re-points the RPC URL, factory contract, Horizon URL, and network passphrase together, and lib/soroswap.ts now builds its client per call instead of pinning the network at module load, so quotes follow the switch. Consumers holding chain-derived state subscribe through subscribeToNetwork or the useNetwork hook and refetch, since a switch invalidates everything read from the previous chain. The in-memory network is updated only after the write succeeds, so a failed write cannot leave the app pointed somewhere it will forget on next launch. Mainnet ships without a default RPC or factory, and /settings/network says so rather than letting the failure surface later inside a request. Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…ent (Miracle656#528) * feat(mobile): port activity feed store and create ActivityFeed component - Create frontend/mobile/lib/activityFeed.ts with hydrate/append lifecycle ported from frontend/wallet - Wire to Wraith GET /transfers/:address endpoint with 15s polling - Create ActivityFeed component with filter pills, skeleton loading, swap display - Update mobile dashboard to integrate the feed Closes Miracle656#461 * fix(activity-feed): robust amount/timestamp parsing, swap detection, env docs * fix(activity-feed): address PR review — move feed to dashboard tab, use wallet address from store, add fetch timeout * Merge origin/main into activity-feed-mobile - Restore QuickActions on the dashboard. This branch predates Miracle656#517 and its dashboard rewrite dropped the component and its import, which would have removed the quick-actions row. - fetchTransfers swallowed every failure and returned [], so an unreachable indexer, an HTTP error and a genuinely empty wallet all rendered as "No transactions yet." It now throws; useInitActivityFeed already catches into its error state, which was being returned but never used. - ActivityFeed takes an error prop and renders a distinct message, and the dashboard passes it through. tsc clean; jest 11 suites / 198 tests; expo lint clean. * chore(mobile): resync package-lock after the merge The lockfile auto-merged into a state npm 10 rejected: npm error `npm ci` can only install packages when your package.json and package-lock.json ... are in sync npm error Missing: @jest/transform@29.7.0 from lock file Regenerated with npm 10 so it matches what CI installs. --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…le656#431, Miracle656#455) (Miracle656#530) * feat(mobile): add brand theme constants and first-run tutorial - Add constants/theme.ts with brand colors, surfaces, spacing, radii - Add constants/typography.ts with font families and role-based styles - Add components/OnboardingTutorial.tsx with 3-step dismissible coach marks - Export __e2eSkipTutorial flag for e2e test bypass Closes Miracle656#431 Closes Miracle656#455 * Merge origin/main into fix/issue-431-455-brand-theme-and-tutorial Miracle656#431 is already satisfied on main by theme/colors.ts and theme/typography.ts, so drop the duplicate constants/ copies and keep the tutorial: - constants/theme.ts duplicated a palette main already has (theme/colors.ts is a superset — same gold/nearBlack/offWhite/lilac/teal/warmGrey/navy plus the surface, border and gold-tint tokens). constants/typography.ts additionally declared web CSS font stacks ('Lora, Georgia, serif'), which React Native cannot resolve; theme/typography.ts uses the loaded family names. - Add theme/spacing.ts for the spacing/radii scale, which main genuinely lacked, so the tutorial has somewhere to import layout tokens from. Make the tutorial meet Miracle656#455's acceptance, which it did not as written: - Nothing mounted it and nothing persisted dismissal — the component only had an onComplete prop with a comment saying the parent would store it. Added FirstRunTutorial, a self-gating wrapper that reads/writes veil_seen_tutorial in AsyncStorage, and mounted it on the dashboard. - Added testID="tutorial-overlay" and testID="tutorial-skip-button", which is what an external driver like Maestro can actually use; the exported __e2eSkipTutorial module flag is only reachable from in-process tests. - Fixed a rules-of-hooks violation: the __e2eSkipTutorial early return sat above four useCallback calls, so hook order changed with the flag. Moved it below every hook. tsc clean; jest 11 suites / 198 tests; expo lint clean. --------- Co-authored-by: MrG139 <mrg139@users.noreply.github.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…6#540) Stop bad payments before they happen: validate the send recipient (on-chain G/M/C address via StrKey, or a well-formed SEP-2 federated name*domain) and let users pick a saved contact instead of typing. - lib/address.ts: isValidDestination (ports the SDK's rule + federated format). - components/ContactPicker.tsx: bottom-sheet list of saved contacts. - app/(tabs)/send.tsx: recipient field that shows an error and blocks submit on an invalid address; 'Choose contact' fills the recipient. Closes Miracle656#467 Co-authored-by: chizzy0011 <chisomnteh@gmail.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
Add the reusable transaction surfaces used across the app: a preview card shown before signing, and a detail sheet opened from a history feed. Porting them as native bottom sheets keeps transaction presentation consistent. - components/TxPreviewCard.tsx: presentational confirm-step preview. - components/TxDetailSheet.tsx: @gorhom BottomSheetModal over a TxRecord (type mirrors the web wallet), opened via a ref. - app/_layout.tsx: wrap in GestureHandlerRootView + BottomSheetModalProvider (required by the sheet). - app/(tabs)/activity.tsx: demo — renders the preview and a feed whose items open the detail sheet on tap. - dep: @gorhom/bottom-sheet. Closes Miracle656#470 Co-authored-by: chizzy0011 <chisomnteh@gmail.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
) Creates QrScanner component that replaces the web BarcodeDetector approach with expo-camera's CameraView + onBarcodeScanned for native mobile QR scanning. Includes camera permission handling, viewfinder overlay, and manual address entry fallback. Closes Miracle656#468 Co-authored-by: iredis <iredis@users.noreply.github.com> Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
) Adds /agent, the mobile client for the Claude-powered assistant in packages/agent. It speaks the same protocol as the web wallet's agent page and reuses its storage keys, so a profile set up in the browser carries over rather than the user being onboarded twice. The transport is split out into lib/agentSocket.ts because a phone's socket drops constantly -- backgrounding the app is enough. It reconnects with jittered exponential backoff instead of the web page's fixed 2s retry, queues messages composed while offline and flushes them on reconnect, and tracks in-flight requests: the agent server keeps no outbox, so a reply interrupted by a drop never arrives and the screen now says so instead of leaving the thinking indicator up forever. The socket constructor, timers, and backoff jitter are all injectable, which is what makes the reconnect paths testable without a server. Agent markdown is rendered as nested Text runs rather than the web page's dangerouslySetInnerHTML, so model output can never be interpreted as markup. Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…Miracle656#551) * feat(mobile): security settings for lock timeout and biometric unlock Adds /settings/security, where the user tunes the app-lock policy: how long the app may sit idle before locking (5 / 15 / 30 minutes, or never) and whether unlocking must present a biometric factor. The timeout uses the same veil_idle_lock_minutes key as the web wallet, so the choice carries between clients. lib/appLock.ts holds the policy and the idle watcher the lock screen (backlog Miracle656#28) consumes. Changing the timeout takes effect immediately rather than at the next lock: watchers subscribe to the settings store and reschedule, including firing straight away when a shortened timeout leaves the deadline already in the past. Two things differ from the web wallet's lib/idle-lock.ts. Activity is reported explicitly through noteActivity(), because React Native has no global mouse/keyboard event stream to listen to. And backgrounding is not treated as activity the way visibilitychange is on the web -- JS timers are frozen or killed while the app is away, so the watcher records when it left the foreground and locks on return if the idle period already elapsed. Without that, a phone could sit in a pocket for an hour and come back unlocked. The biometric requirement is stored as a preference rather than gated on a capability check: whether the device has usable biometrics can change between now and the unlock, so the lock screen resolves that when it prompts and falls back to the passkey rather than locking the user out of their funds. * Merge origin/main into feat/mobile-security-settings Two gaps that stopped the settings taking effect: - hydrateLockSettings() was never called, so the app started on the defaults every launch and a saved timeout only applied once the user re-picked it. Called from app/_layout.tsx alongside hydrateNetwork(). - /settings/security was unreachable. The settings tab rendered its rows as Pressables that only set local state, so the "Security & lock" row went nowhere. Rows now take an optional href and navigate when present. tsc clean; jest 15 suites / 266 tests; expo lint clean. --------- Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
…e656#552) Adds /multisig, which turns the read-only multisig view into a working coordination tool: an owner raises a transfer, owners approve it, and the approval that reaches the threshold executes it. There is no separate Execute action because the contract has no execute entry point -- contracts/multisig-wallet performs the transfer inside the sign_transaction invocation that reaches the threshold. The screen names that approval for what it is ("Approve and execute") and warns that it sends the funds immediately, rather than offering a button the contract cannot back. Three fixes carried over from the web port: - propose_transaction takes caller as its first argument and calls caller.require_auth(). The web version omits it, so every proposal it builds is rejected. It is passed here. - The owner is the source account rather than a separate fee payer, so prepareTransaction resolves require_auth against the source account and no second signature is needed. This also drops the fallback that minted a throwaway keypair from Friendbot. - Amounts convert through integer string arithmetic instead of parseFloat(x) * 10_000_000, which misrounds ordinary values such as 0.7 at stroop precision. Approval preconditions -- owner membership, duplicate approvals, and whether the wallet can actually cover a transfer that is about to execute -- are checked locally, so a rejection is immediate and specific instead of arriving as a contract panic after a fee. Deployment stays on the desktop wizard; the contract address is read from the veil_multisig_contract key the web wallet already uses. Co-authored-by: ezedike-evan <120946193+ezedike-evan@users.noreply.github.com>
# Conflicts: # frontend/mobile/app/vault.tsx
BigManly4
force-pushed
the
mobile/vault-screen-477
branch
from
August 3, 2026 15:37
d7720b3 to
a50486e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports the vault sub-account feature from the Next.js web wallet to the Expo mobile app, following the conventions already established by the bulk-payout and swap mobile ports in this repo.
Recreates the work from Miracle656#521, whose branch was deleted before merge.
What was ported
frontend/mobile/lib/vault.ts— typed vault domain logic ported fromfrontend/wallet/lib/vault.ts: XLM/stroops conversion, address validation, delay parsing, and the delay/countdown/status formatting helpers used to render the withdrawal queue. Deploy, deposit, queue withdrawal, cancel withdrawal, and execute withdrawal are all present as typed functions with the same shapes (VaultDetails, VaultWithdrawal, VaultConfig) as the wallet lib.frontend/mobile/lib/escrow.ts— claimable-balance escrow helpers (createEscrow, claimEscrow, reclaimEscrow, buildEscrowClaimants, buildClaimLink). Mirrors the sdk implementation locally instead of reaching outside the mobile package, since mobile already depends on@stellar/stellar-sdkdirectly.frontend/mobile/app/vault.tsx— a React Native screen covering: create/attach a vault, view balance (total, available, queued, delay), a deposit form, and the full withdrawal lifecycle (queue with a delay, live countdown to unlock, execute once ready, cancel while pending). Styled to match the existing dark-theme mobile screens.How signing is stubbed, and why
Mobile does not yet have the passkey/session-based signing infrastructure the web wallet uses. Chain submission and reads are behind injectable async parameters —
VaultSubmitandVaultFetchinlib/vault.ts— the same style asexecuteBulkPayout(rows, submitBatch)in the existinglib/bulkPayout.ts. The screen supplies stub implementations today so the full deposit/withdraw/queue/execute/cancel flow is exercisable end to end in the UI, and a real Soroban RPC-backed implementation can be swapped in later without touching the screen or the typed domain logic.Also references (does not auto-close, different repo): Miracle656#477
closes #2