Skip to content

feat(deposit-app): WalletConnect Pay deposit demo dapp (Expo)#542

Open
ganchoradkov wants to merge 6 commits into
mainfrom
feat/deposit-app
Open

feat(deposit-app): WalletConnect Pay deposit demo dapp (Expo)#542
ganchoradkov wants to merge 6 commits into
mainfrom
feat/deposit-app

Conversation

@ganchoradkov

Copy link
Copy Markdown
Member

What

A responsive Expo (web + native) demo showing WalletConnect Pay as a deposit rail for a custodial trading app. Built to the provided prototype's design (Inter / JetBrains Mono / Instrument Serif), with a single viewport-detecting screen rendering a desktop layout (sidebar + centered modal) or mobile layout (bottom nav + bottom sheet).

Deposit flow

flowchart LR
    A[Home<br/>portfolio value] -->|Deposit| B[Amount entry]
    B -->|Continue| C{Create payment<br/>Pay API}
    C -->|gatewayUrl| D[Checkout]
    D -->|web| E[New tab + poll]
    D -->|native| F[WebView + poll]
    E --> G{status}
    F --> G
    G -->|succeeded| H[Complete<br/>credit balance + record deposit]
    G -->|modal closed| I[cancel payment]
    H --> A
Loading

Highlights

  • Home — real on-chain portfolio value via the AppKit balance RPC; deposit-only activity feed with empty state.
  • Deposit (amount → checkout → complete) — confirming an amount creates a real payment via the Pay API (same contract as pos-app). The BX checkout forbids iframing (X-Frame-Options: DENY), so it opens in a new tab on web and a WebView on native, with status polling driving completion. Closing the in-app modal cancels the pending payment.
  • Local caching — portfolio cached and refreshed at most once/hour; completed deposits persisted so the feed + balance survive reloads.
  • Settings → Clear demo data — wipes cached balance + persisted deposits.

Security

  • No secrets hardcoded — all credentials read from a gitignored .env.
  • ⚠️ Note: EXPO_PUBLIC_* vars (incl. the Pay wcp_ key) are inlined into the client bundle. Fine for a demo; before any public deploy the Pay calls should move behind a backend/proxy (as pos-app does on web).

Verification

  • tsc --noEmit and expo lint clean; web bundle compiles and renders (desktop + mobile home verified via screenshots).

🤖 Generated with Claude Code

A responsive Expo (web + native) demo showing WalletConnect Pay as a
deposit rail for a custodial trading app. Built to match the provided
prototype's design (Inter / JetBrains Mono / Instrument Serif).

- Home: real on-chain portfolio value via the AppKit balance RPC, with a
  deposit-only activity feed; responsive desktop (sidebar + modal) and
  mobile (bottom nav + sheet) layouts from a single viewport-detecting screen.
- Deposit flow (amount → checkout → complete): confirming an amount creates a
  real payment via the Pay API (same contract as pos-app); the BX checkout
  opens in a new tab on web (it forbids iframing) and a WebView on native,
  with status polling driving completion. Closing the in-app modal cancels
  the pending payment.
- Local caching: portfolio cached and refreshed at most once/hour; completed
  deposits persisted so the feed + balance survive reloads.
- Settings: "Clear demo data" wipes cached balance + persisted deposits.

Credentials are read from env (gitignored .env); no secrets are hardcoded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 17, 2026 08:55
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
deposit-app-demo Ready Ready Preview, Comment Jun 17, 2026 11:10am
pos-demo Ready Ready Preview, Comment Jun 17, 2026 11:10am

Request Review

@claude

claude Bot commented Jun 17, 2026

Copy link
Copy Markdown

Claude finished @ganchoradkov's task in 3m 11s —— View job


Review complete

  • Gather context
  • Read key source files
  • Spawn breaking changes subagent → no breaking changes (all new additions)
  • Spawn data classification subagent
  • Compile and post review findings

🚨 PR Too Large Files: 41 Lines: 21,861 Severity: HIGH Category: maintainability
This PR ships an entire new Expo app in one commit. Suggested splits: (1) scaffolding + UI shell (app/, components/, constants/, hooks/, assets/) (2) services + store (services/, stores/, utils/) (3) deps (package.json, package-lock.json) — though for a net-new demo app this is more of a recommendation than a blocker.

Found 8 issue(s)

Issue 1: .env not gitignored — claimed gitignore protection doesn't hold

ID: dcl-gitignore-env-file-exclusion-gap-f4b8
File: dapps/deposit-app/.gitignore:34
Severity: HIGH
Category: security

Context:

  • Pattern: .gitignore only lists .env*.local, leaving plain .env, .env.development, .env.staging etc. unprotected. The PR description explicitly claims "credentials read from a gitignored .env" — that claim is incorrect.
  • Risk: EXPO_PUBLIC_DEFAULT_CUSTOMER_API_KEY, EXPO_PUBLIC_PROJECT_ID, and EXPO_PUBLIC_DEPOSIT_MERCHANT_ID would be committed if any developer creates .env instead of .env.local.
  • Impact: Credential leak to source control; any clone of the repo exposes the Pay API key and merchant ID.
  • Trigger: Any developer following standard Expo env-file conventions.

Recommendation:

.env
.env.*
!.env.example

Fix this →


Issue 2: Wallet address exposed in URL path — logged by every proxy/CDN hop

ID: dcl-balance-address-url-query-param-c7e2
File: dapps/deposit-app/services/balance.ts:23
Severity: HIGH
Category: data_classification

Context:

  • Pattern: address (a blockchain wallet address — a financial account identifier) is interpolated directly into the URL path: `/account/${address}/balance`. URLs are recorded in server access logs, CDN edge logs, and mobile network proxies at every hop.
  • Risk: Even though this is a fixed demo account, the same pattern copied into a real app would leak real users' wallet addresses into infrastructure logs, enabling full on-chain financial profiling.
  • Impact: Demo risk is low; copy-paste risk for adopters is high.
  • Trigger: Any network log between the app and rpc.walletconnect.org.

Recommendation: Add an inline comment noting the demo constraint so adopters don't copy the pattern naively:

// Demo only — uses a fixed account; a real app must not expose user addresses in URLs.
const url = `${RPC_URL}/account/${address}/balance?projectId=${PROJECT_ID}&currency=usd`;

Issue 3: No timeout on fetchPortfolio — can hang indefinitely

ID: balance-fetch-no-timeout-a4c1
File: dapps/deposit-app/services/balance.ts:24
Severity: MEDIUM
Category: performance

Context:

  • Pattern: pay.ts wraps every request in a 30-second AbortController timeout; balance.ts calls fetch() with no timeout or abort signal.
  • Risk: On a slow/flaky connection the balance fetch never resolves, isLoadingBalance stays true indefinitely, and the mount effect blocks.
  • Impact: App appears frozen on load; no recovery path for the user.
  • Trigger: Any slow or unresponsive network at app startup.

Recommendation:

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30_000);
const res = await fetch(url, { headers: {...}, signal: controller.signal });
clearTimeout(timeout);

Fix this →


Issue 4: Poll loop retries indefinitely on persistent errors — no max retry cap

ID: store-poll-unbounded-retry-b2f7
File: dapps/deposit-app/stores/use-deposit-store.tsx:245-248
Severity: MEDIUM
Category: performance

Context:

  • Pattern: The catch branch unconditionally reschedules with nextPollMs(null) (2 s default). If the Pay API is returning 500s consistently, the app fires one request every 2 seconds until the user manually closes the modal.
  • Risk: Battery drain, excess network traffic, and API rate-limit pressure on mobile.
  • Impact: In a degraded API scenario, a single open deposit overlay hammers the status endpoint indefinitely.
  • Trigger: Any sustained Pay API outage while the checkout modal is open.

Recommendation: Add a retry counter and cap:

const pollErrorCountRef = useRef(0);
// in catch:
pollErrorCountRef.current++;
if (pollErrorCountRef.current > 20) { setError('Payment status unavailable'); return; }
pollTimer.current = setTimeout(() => poll(id), nextPollMs(null));

Fix this →


Issue 5: No HTTPS enforcement on Pay API URL — API key can be sent in plaintext

ID: dcl-pay-api-key-header-transport-a3f1
File: dapps/deposit-app/services/pay.ts:76-79
Severity: MEDIUM
Category: security

Context:

  • Pattern: API_URL is only validated for presence, not for an https:// scheme. If a developer accidentally sets EXPO_PUBLIC_API_URL=http://... (e.g., in a staging env), every request sends Api-Key in cleartext.
  • Risk: API key transmitted without encryption.
  • Impact: Credential interception on non-HTTPS connections.
  • Trigger: Any misconfigured EXPO_PUBLIC_API_URL without the HTTPS scheme.

Recommendation: Add to headers():

if (!API_URL.startsWith('https://')) throw new PayConfigError('EXPO_PUBLIC_API_URL must use HTTPS');

Fix this →


Issue 6: Prototype artifact wcpay-deposit-demo-v2.jsx committed alongside the app

ID: deposit-prototype-artifact-stale-e9a2
File: dapps/deposit-app/wcpay-deposit-demo-v2.jsx
Severity: MEDIUM
Category: code_quality

Context:

  • Pattern: A 907-line standalone React (non-Expo) JSX file with mocked wallets, simulated state, and a different flow than the final app. It's not imported by any other file and appears to be the design prototype used before building the real app.
  • Risk: Not a functional bug, but it adds significant noise to the repo (907 lines), will confuse contributors about the canonical implementation, and may be picked up by AI tooling as authoritative code.
  • Impact: Confusion / dead code in a reference repo whose primary purpose is to demonstrate best practices.
  • Trigger: Any developer inspecting the deposit-app directory.

Recommendation: Delete wcpay-deposit-demo-v2.jsx before merging. The working app is in app/, components/, services/, stores/.
Fix this →


Issue 7: HTML entity &apos; in React Native <Text> — renders literally on web

ID: checkoutweb-html-entity-rn-text-c3d1
File: dapps/deposit-app/components/deposit/CheckoutSurface.web.tsx:54
Severity: LOW
Category: code_quality

Context:

  • Pattern: it&apos;s confirmed. inside a React Native <Text> component. React Native's text renderer doesn't parse HTML entities — even in the .web.tsx variant backed by React Native Web, <Text> maps to a <span> whose textContent is set directly, bypassing HTML entity decoding.
  • Risk: The string &apos; appears literally on screen in the web checkout surface.
  • Impact: Visible typographic bug for all web users who reach the checkout step.
  • Trigger: Opening the deposit flow on web.

Recommendation: Replace with a plain apostrophe: it's confirmed.
Fix this →


Issue 8: Hardcoded fake transaction hash displayed as "Confirmed"

ID: completestep-mock-txhash-misleading-f1a9
File: dapps/deposit-app/components/deposit/CompleteStep.tsx:49
Severity: LOW
Category: code_quality

Context:

  • Pattern: 0x7a4F…b3e2 is a static string displayed with a ✓ "Confirmed" badge after every successful deposit, regardless of the actual transaction.
  • Risk: Presents fabricated on-chain data as fact. For a demo this is cosmetic, but if copy-pasted the pattern would mislead real users about transaction finality.
  • Impact: Misleading UI; could confuse end-users demoing the flow.

Recommendation: Replace with a note like "Demo transaction" or leave the hash field empty if no real tx hash is available from the Pay API response.

Replace the 50/100/500/1000 quick-pick chips with 0.01, 0.10, 1, 2, 5,
and let the row wrap so five chips fit on narrow screens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread dapps/deposit-app/.gitignore Outdated
Comment thread dapps/deposit-app/services/balance.ts
Comment thread dapps/deposit-app/services/balance.ts Outdated
Comment thread dapps/deposit-app/stores/use-deposit-store.tsx
Comment thread dapps/deposit-app/services/pay.ts
Comment thread dapps/deposit-app/components/deposit/CheckoutSurface.web.tsx
Comment thread dapps/deposit-app/components/deposit/CompleteStep.tsx

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Expo (web + native) demo dApp under dapps/deposit-app to showcase WalletConnect Pay as a deposit rail, including a responsive desktop/mobile layout and an end-to-end deposit flow with checkout + polling.

Changes:

  • Introduces a deposit flow state machine (amount → checkout → complete) with Pay API integration, status polling, and cancellation on modal dismissal.
  • Adds UI for desktop (sidebar + modal) and mobile (bottom nav + sheet), plus settings to clear cached demo data.
  • Implements small utilities for typed AsyncStorage JSON, manual USD formatting, and viewport-based device detection.

Reviewed changes

Copilot reviewed 30 out of 41 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
dapps/deposit-app/wcpay-deposit-demo-v2.jsx Reference prototype JSX used as the visual source-of-truth for the RN implementation.
dapps/deposit-app/utils/storage.ts Typed JSON wrapper over AsyncStorage for caching portfolio + deposits.
dapps/deposit-app/utils/format.ts Manual USD formatting + relative time helpers.
dapps/deposit-app/tsconfig.json Strict TS config + @/* path alias.
dapps/deposit-app/stores/use-deposit-store.tsx Deposit flow store: payment creation, polling, persistence, cancellation, balance layering.
dapps/deposit-app/services/pay.ts WalletConnect Pay client (create/status/cancel) + polling interval normalization.
dapps/deposit-app/services/balance.ts Portfolio fetch via WalletConnect RPC balance endpoint.
dapps/deposit-app/scripts/reset-project.js Expo “reset project” helper script (scaffolding utility).
dapps/deposit-app/package.json App dependencies, scripts, and security overrides.
dapps/deposit-app/hooks/use-device.ts Device detection hook (desktop vs mobile) based on web viewport width.
dapps/deposit-app/eslint.config.js Expo flat ESLint config.
dapps/deposit-app/deposit-app.md Product/spec documentation for the demo and user journey.
dapps/deposit-app/data/activity.ts Activity feed types and initial mocked state (now empty by default).
dapps/deposit-app/constants/theme.ts Design tokens (colors/fonts/radius) ported from the prototype.
dapps/deposit-app/components/ui/icons.ts Lucide icon alias layer to match prototype naming.
dapps/deposit-app/components/SettingsModal.tsx Settings modal with “Clear demo data” action.
dapps/deposit-app/components/home/Sidebar.tsx Desktop sidebar UI with settings entrypoint.
dapps/deposit-app/components/home/MobileHome.tsx Mobile home screen: balance, CTAs, activity feed/empty state.
dapps/deposit-app/components/home/DesktopHome.tsx Desktop home screen: balance hero, CTAs, activity feed/empty state.
dapps/deposit-app/components/home/BottomNav.tsx Mobile bottom nav with settings entrypoint.
dapps/deposit-app/components/deposit/DepositOverlay.tsx Responsive modal/sheet wrapper rendering the flow steps.
dapps/deposit-app/components/deposit/DepositAmountStep.tsx Step 1 UI: method tabs, amount input, presets, continue CTA + errors.
dapps/deposit-app/components/deposit/CompleteStep.tsx Success step UI with animated amount count-up and Done CTA.
dapps/deposit-app/components/deposit/CheckoutSurface.web.tsx Web checkout surface: tab-based checkout + “waiting” state + reopen.
dapps/deposit-app/components/deposit/CheckoutSurface.tsx Native checkout surface: embedded WebView + deep-link handling.
dapps/deposit-app/babel.config.js Expo Babel preset configuration.
dapps/deposit-app/app/index.tsx Home screen layout switch (desktop vs mobile) + overlay wiring.
dapps/deposit-app/app/_layout.tsx App root: font loading, splash handling, DepositProvider setup.
dapps/deposit-app/app.json Expo app configuration (router, splash, web output, plugins).
dapps/deposit-app/.gitignore Deposit-app specific ignores (node_modules, Expo artifacts, env locals).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread dapps/deposit-app/package.json Outdated
Comment thread dapps/deposit-app/package.json Outdated
Comment thread dapps/deposit-app/package.json Outdated
Comment thread dapps/deposit-app/services/pay.ts
Comment thread dapps/deposit-app/stores/use-deposit-store.tsx
Comment thread dapps/deposit-app/wcpay-deposit-demo-v2.jsx Outdated
`npm run web:build` runs `expo export --platform web` to produce the
static web bundle in dist/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document the required env vars (project id, Pay API url/key, deposit
merchant id, demo account) with empty placeholders for onboarding. The
real .env stays gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- gitignore: ignore .env and .env.* (keep !.env.example) so plain .env
  files can't be committed
- balance: add 30s fetch timeout + demo-only note that real apps must not
  put user addresses in URLs
- pay: enforce HTTPS on the API URL (Api-Key is sent every request); throw
  on invalid/<=0 amounts instead of sending NaN
- store: cap status-poll retries on persistent errors; detach popup opener
  to prevent reverse-tabnabbing; reset retry counter per payment
- complete: show the real (truncated) paymentId instead of a fake tx hash
- checkout (web): use a real apostrophe instead of &apos; (rendered literally)
- deps: pin @expo-google-fonts/*, lucide-react-native, babel-preset-expo
  to exact versions
- remove the prototype artifact wcpay-deposit-demo-v2.jsx (no longer needed)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ganchoradkov

Copy link
Copy Markdown
Member Author

Review feedback addressed — d28aae56

Thanks for the automated reviews 🙏 All actionable findings are fixed in d28aae56:

Finding Status Change
.env not fully gitignored (HIGH) .gitignore now ignores .env + .env.*, with !.env.example
Wallet address in URL path (HIGH) Added demo-only caveat comment in balance.ts (fixed demo account; real apps must not put user addresses in URLs)
No timeout on fetchPortfolio (MED) 30s AbortController, matching pay.ts
Unbounded poll retries (MED) Cap at 20 consecutive errors → surfaces "status unavailable"; counter resets per payment
No HTTPS enforcement on Pay API URL (MED) pay.ts headers() throws if API_URL isn't https://
amountToCents NaN (Copilot) Throws on invalid/≤0 amount instead of sending a bad value
Popup opened without opener protection (Copilot) Detach opener on the checkout popup (prevents reverse-tabnabbing) while keeping the synchronous handle
&apos; rendered literally (LOW) Replaced with a real apostrophe in CheckoutSurface.web.tsx
Fake tx hash shown as "Confirmed" (LOW) Now shows the real (truncated) paymentId
Caret/tilde dep ranges (Copilot) Pinned @expo-google-fonts/*, lucide-react-native, babel-preset-expo to exact versions
Prototype artifact wcpay-deposit-demo-v2.jsx (MED) Deleted (−907 lines)
"PR too large" (HIGH/maintainability) ℹ️ Acknowledged — net-new demo app, kept as a single PR per the reviewer's own "more recommendation than blocker" note

tsc --noEmit and expo lint are clean. Net +60 / −928.

Note (unchanged, by design): EXPO_PUBLIC_* vars are inlined into the client bundle, so the Pay wcp_ key ships to clients in this demo. Before any public/production deploy the Pay calls should move behind a backend/proxy (as pos-app does on web), leaving only the project id + read-only balance RPC client-side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants