Skip to content

Integrate escrow app#261

Open
Kukks wants to merge 34 commits intomasterfrom
integrate-escrow-app
Open

Integrate escrow app#261
Kukks wants to merge 34 commits intomasterfrom
integrate-escrow-app

Conversation

@Kukks
Copy link
Contributor

@Kukks Kukks commented Dec 10, 2025

Summary by CodeRabbit

  • New Features

    • Introduced Escrow app with embedded iframe, wallet-backed signing/funding flows, and deep-link navigation
    • Added Escrow app icon and an Escrow entry in the Apps list
    • Added a new "Sign" option in Advanced settings
  • Documentation

    • Updated README and environment variable docs (including new escrow URL variable)
  • Tests

    • Added comprehensive RPC handler tests for escrow interactions

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

Adds an Escrow app: UI icon and screen, an iframe host with keep-alive and origin validation, an RPC message handler for wallet operations (signing, balance, funding, keep-alive), navigation/deeplink wiring, tests, and updated docs/type for a new environment variable.

Changes

Cohort / File(s) Summary
Documentation & Types
README.md, src/vite-env.d.ts
Reworked Environment Variables table and added VITE_ARK_ESCROW_URL declaration in Vite types.
Icon Component
src/icons/Escrow.tsx
New EscrowIcon component rendering a circular escrow image.
Navigation & Options
src/providers/navigation.tsx, src/providers/options.tsx, src/providers/wallet.tsx, src/screens/Apps/Index.tsx
Added AppEscrow page enum and tab mapping; imported/wired Escrow screen into navigation and Apps list; added Sign option near Logs; added deep-link case routing for appId: "escrow".
Escrow Screen & Iframe Host
src/screens/Apps/Escrow/Index.tsx, src/screens/Apps/Escrow/ArkadeIframeHost.tsx
New AppEscrow screen that resolves network-specific escrow URL, integrates wallet helpers (address, balance, signing, funding), and embeds ArkadeIframeHost; ArkadeIframeHost manages iframe lifecycle, origin validation, message dispatching, and keep-alive polling.
RPC Layer & Tests
src/screens/Apps/Escrow/RpcHandler.ts, src/screens/Apps/Escrow/RpcHandler.test.ts
Added typed message handler factory makeMessageHandler (methods: keep-alive, get-x-public-key, sign-login-challenge, get-ark-wallet-address, get-ark-wallet-balance, sign-transaction, fund-address) and a comprehensive test suite covering success and failure paths.

Sequence Diagram(s)

sequenceDiagram
    participant Iframe as Escrow Iframe
    participant Host as Host App (ArkadeIframeHost)
    participant Handler as RPC Handler (makeMessageHandler)
    participant Wallet as Wallet Service

    Iframe->>Host: postMessage(RPC request)
    Host->>Host: validate origin & source
    Host->>Handler: handle(InboundMessage)
    Handler->>Wallet: invoke method (e.g., sign / getBalance / fund)
    Wallet-->>Handler: result (signed PSBT / balance / txId)
    Handler-->>Host: OutboundMessage (response)
    Host-->>Iframe: postMessage(response)

    alt Keep-alive flow
        Host->>Iframe: postMessage(ARKADE_KEEP_ALIVE) every 2s until alive, then every 5s
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas needing careful review:
    • src/screens/Apps/Escrow/RpcHandler.ts — async dispatch, error wrapping, payload normalization, types.
    • src/screens/Apps/Escrow/ArkadeIframeHost.tsx — origin checks, message source validation, polling timing and cleanup.
    • src/screens/Apps/Escrow/Index.tsx — wallet integration for PSBT signing (main + checkpoints), hex handling, and navigation side-effects.
    • Tests: ensure RpcHandler.test.ts mocks align with real wallet APIs and edge cases.

Possibly related PRs

  • Feat/lendaswap #190 — modifies navigation/page rendering patterns for iframe-based apps; likely overlaps with src/providers/navigation.tsx changes.
  • Support app deep-link via URL hash #222 — changes deep-link handling in src/providers/wallet.tsx; related to the added escrow deep-link case.
  • update to ts-sdk 0.2.0 #121 — refactors wallet initialization/types in src/providers/wallet.tsx; may intersect with wallet integration and deep-link routing.

Suggested reviewers

  • louisinger
  • bordalix

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Integrate escrow app' clearly and directly describes the main objective of the pull request, which adds escrow app functionality throughout the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch integrate-escrow-app

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/screens/Apps/Index.tsx (1)

95-140: Clean up unused deepLinkInfo and consider network-based Escrow gating

  • deepLinkInfo from FlowContext is currently unused (isEscrowEnabled is hard-coded to true), which is triggering the static-analysis warning and adds noise.
  • Also, Escrow is rendered in the list on all networks, but AppEscrow only shows an iframe when BASE_URLS[aspInfo.network] is non-null; on unsupported networks, clicking Escrow leads to a header-only screen.

Consider either:

  • Dropping FlowContext / deepLinkInfo here and keeping Escrow always visible, or
  • Using a real condition (e.g., a feature flag or a network check mirroring BASE_URLS) to control isEscrowEnabled so the tile is hidden when the Escrow app is not actually available.

Example if you just want it always on and to silence the warning:

-import { FlowContext } from '../../providers/flow'
+// import { FlowContext } from '../../providers/flow'
...
-export default function Apps() {
-  const { deepLinkInfo } = useContext(FlowContext)
-  const isEscrowEnabled = true // deepLinkInfo?.appId === 'escrow'
+export default function Apps() {
+  const isEscrowEnabled = true
🧹 Nitpick comments (5)
src/providers/options.tsx (1)

53-57: Consider using a distinct icon for the Sign option.

The Sign option reuses LogsIcon, which is already used for the Logs option (lines 48-52). This may confuse users when navigating settings. Consider creating or using a dedicated icon (e.g., a pen/signature icon) for better visual distinction.

src/screens/Apps/Escrow/RpcHandler.ts (2)

208-211: Unreachable code after exhaustive switch.

Line 211 is unreachable because the switch (message.kind) covers both possible cases (ARKADE_KEEP_ALIVE and ARKADE_RPC_REQUEST), and the inner switch (method) is also exhaustive. TypeScript's control flow analysis should flag this. Consider removing the unreachable return or adding an explicit default case with never type assertion for compile-time exhaustiveness checking.

         case 'fund-address': {
           // ... existing code ...
         }
+        default: {
+          const _exhaustiveCheck: never = method
+          return { tag: 'failure', error: new Error(`Unknown method: ${_exhaustiveCheck}`) }
+        }
       }
     }
+    default: {
+      const _exhaustiveCheck: never = message
+      return { tag: 'failure', error: new Error('Unknown message kind') }
+    }
   }
-  return { tag: 'failure', error: new Error('Unknown message kind') }
 }

124-160: Consider consistent error handling for getter methods.

The get-x-public-key, get-ark-wallet-address, and get-ark-wallet-balance methods don't have try-catch wrappers, unlike sign-login-challenge, sign-transaction, and fund-address. If any of these getter functions throw, the error will bubble up as an unhandled promise rejection rather than returning a structured { tag: 'failure', error } result.

src/screens/Apps/Escrow/Index.tsx (1)

17-26: Centralize the crypto hash configuration and align Escrow availability with network support

  • hashes.sha256 = sha256 mutates global crypto-library configuration inside this screen. That’s easy to miss and couples the global signing behavior to whether this component module has been loaded.
  • Separately, escrowAppUrl is only set when BASE_URLS[aspInfo.network] is non-null, so on networks without an entry (e.g., where it’s null), AppEscrow renders just the header and no iframe.

Consider:

  • Moving the hashes.sha256 = sha256 assignment to a single, shared initialization point (e.g., wherever the wallet / signing stack is first configured) so that all flows using the identity share the same, explicit hash configuration.
  • Optionally tying the Escrow tile visibility (in Apps/Index.tsx) to the same BASE_URLS/network logic so users don’t navigate into a header-only Escrow page on unsupported networks.

Also applies to: 36-41, 72-81

src/screens/Apps/Escrow/ArkadeIframeHost.tsx (1)

10-27: Tighten the origin check to use equality instead of startsWith

The message handling pipeline (origin check + event.source === iframeRef.current?.contentWindow + shape validation) looks solid.

Given that allowedChildOrigins are already bare origins (as built in AppEscrow), you can simplify and harden the origin check:

-      if (!allowedChildOrigins.some((_) => _.startsWith(event.origin))) {
+      if (!allowedChildOrigins.includes(event.origin)) {
         console.error(`[wallet]: ignoring message from ${event.origin}`)
         return
       }

This keeps the intent obvious and avoids any accidental prefix matches if allowedChildOrigins ever started to include paths.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7703715 and ecf94b7.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • public/escrow.png is excluded by !**/*.png
📒 Files selected for processing (11)
  • README.md (1 hunks)
  • src/icons/Escrow.tsx (1 hunks)
  • src/providers/navigation.tsx (4 hunks)
  • src/providers/options.tsx (1 hunks)
  • src/providers/wallet.tsx (1 hunks)
  • src/screens/Apps/Escrow/ArkadeIframeHost.tsx (1 hunks)
  • src/screens/Apps/Escrow/Index.tsx (1 hunks)
  • src/screens/Apps/Escrow/RpcHandler.test.ts (1 hunks)
  • src/screens/Apps/Escrow/RpcHandler.ts (1 hunks)
  • src/screens/Apps/Index.tsx (3 hunks)
  • src/vite-env.d.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-30T18:33:29.839Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 114
File: src/lib/asp.ts:0-0
Timestamp: 2025-06-30T18:33:29.839Z
Learning: In src/lib/asp.ts, only the collaborativeExit function should accept both IWallet and ServiceWorkerWallet types. Other wallet functions (getBalance, getTxHistory, getReceivingAddresses, redeemNotes, sendOnChain, settleVtxos) should maintain their original IWallet-only signatures and not be updated for consistency.

Applied to files:

  • src/providers/wallet.tsx
  • src/screens/Apps/Escrow/RpcHandler.ts
🧬 Code graph analysis (6)
src/providers/options.tsx (1)
src/icons/Logs.tsx (1)
  • LogsIcon (1-10)
src/screens/Apps/Index.tsx (3)
src/providers/flow.tsx (1)
  • FlowContext (89-104)
src/App.tsx (1)
  • App (36-164)
src/icons/Escrow.tsx (1)
  • EscrowIcon (1-3)
src/screens/Apps/Escrow/RpcHandler.test.ts (1)
src/screens/Apps/Escrow/RpcHandler.ts (1)
  • makeMessageHandler (116-213)
src/screens/Apps/Escrow/Index.tsx (6)
src/providers/navigation.tsx (1)
  • NavigationContext (174-178)
src/providers/wallet.tsx (1)
  • WalletContext (42-56)
src/providers/flow.tsx (1)
  • FlowContext (89-104)
src/providers/asp.tsx (1)
  • AspContext (15-20)
src/screens/Apps/Escrow/RpcHandler.ts (1)
  • makeMessageHandler (116-213)
src/screens/Apps/Escrow/ArkadeIframeHost.tsx (1)
  • ArkadeIframeHost (10-83)
src/screens/Apps/Escrow/RpcHandler.ts (1)
src/test/setup.mjs (2)
  • address (208-208)
  • txid (96-96)
src/screens/Apps/Escrow/ArkadeIframeHost.tsx (1)
src/screens/Apps/Escrow/RpcHandler.ts (1)
  • MessageHandler (105-105)
🪛 GitHub Check: test
src/screens/Apps/Index.tsx

[warning] 96-96:
'deepLinkInfo' is assigned a value but never used


[warning] 96-96:
'deepLinkInfo' is assigned a value but never used

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test
  • GitHub Check: Cloudflare Pages: wallet-mutinynet
  • GitHub Check: Cloudflare Pages: wallet-bitcoin
🔇 Additional comments (6)
src/vite-env.d.ts (1)

9-9: LGTM!

The environment variable type declaration follows the established pattern and correctly marks the variable as optional.

README.md (1)

37-37: LGTM!

The new VITE_ARK_ESCROW_URL environment variable is properly documented.

src/screens/Apps/Escrow/RpcHandler.ts (1)

101-116: Clean API design with tagged union Result type.

The use of discriminated unions for message types and the tagged Result type for success/failure handling is a solid pattern that enables type-safe message processing.

src/icons/Escrow.tsx (1)

1-3: LGTM, but note the architectural inconsistency.

The /escrow.png asset exists in the public directory. However, this component differs from other icon components in the codebase—all others use inline SVGs (e.g., AddIcon, BackIcon, HomeIcon), while this uses an external PNG image. This approach may impact consistency and performance, as inline SVGs are bundled with the application while the PNG requires a separate network request.

src/providers/wallet.tsx (1)

131-146: Escrow deeplink routing is consistent with existing apps

The new 'escrow' case in the deep-link switch cleanly routes to Pages.AppEscrow and matches the pattern used for other apps (Boltz/Lenda*). No issues spotted.

src/providers/navigation.tsx (1)

27-76: Navigation wiring for AppEscrow looks good; just confirm enum renumbering is safe

  • Import, Pages.AppEscrow enum entry, pageTab mapping, and pageComponent switch case all match the existing pattern for other Apps-tab screens.
  • Since Pages.AppEscrow was inserted in the middle of the enum, the numeric values of following Pages members have shifted. This is harmless as long as you don’t serialize or share the raw numeric enum values outside this module (e.g., in storage or URLs).

If you do persist Pages somewhere, it’s worth double-checking that you’re storing something stable (like names/strings) rather than the numeric enum value.

Also applies to: 103-113

Comment on lines +150 to +166
it('handles fund-address', async () => {
;(props.fundAddress as any).mockResolvedValue(undefined)
const handler = makeMessageHandler(props)
const res = await handler({
kind: 'ARKADE_RPC_REQUEST',
id,
method: 'fund-address',
payload: { address: 'ark1qq', amount: 123 },
})
expect(props.fundAddress).toHaveBeenCalledWith('ark1qq', 123)
expect(res.tag).toBe('success')
if (res.tag === 'success') {
const out: any = res.result
expect(out.method).toBe('fund-address')
expect(out.payload).toEqual({})
}
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Align fund-address test with RpcHandler implementation (payload currently mismatched)

RpcHandler.ts returns a payload containing { txid } for method: 'fund-address', but the test here:

;(props.fundAddress as any).mockResolvedValue(undefined)
// ...
expect(out.payload).toEqual({})

expects an empty payload. These semantics don’t match and will either break the test or obscure regressions.

If you want to keep the { txid } contract (which is more useful to callers), adjust the test like this:

-  it('handles fund-address', async () => {
-    ;(props.fundAddress as any).mockResolvedValue(undefined)
+  it('handles fund-address', async () => {
+    ;(props.fundAddress as any).mockResolvedValue('txid-123')
...
-    expect(res.tag).toBe('success')
-    if (res.tag === 'success') {
-      const out: any = res.result
-      expect(out.method).toBe('fund-address')
-      expect(out.payload).toEqual({})
-    }
+    expect(res.tag).toBe('success')
+    if (res.tag === 'success') {
+      const out: any = res.result
+      expect(out.method).toBe('fund-address')
+      expect(out.payload).toEqual({ txid: 'txid-123' })
+    }

If instead you intend fund-address to be fire-and-forget with an empty payload, then RpcHandler.ts should be changed to return { payload: {} } for that method.

🤖 Prompt for AI Agents
In src/screens/Apps/Escrow/RpcHandler.test.ts around lines 150 to 166, the test
for 'fund-address' currently mocks props.fundAddress to resolve to undefined and
asserts an empty payload, but RpcHandler returns a payload { txid } for that
method; update the test to mock props.fundAddress to resolve to a txid string
(e.g. 'txid123') and assert that the response payload equals { txid: 'txid123' }
and that fundAddress was called with the expected args, so the test matches the
RpcHandler contract (alternatively, if you prefer fire-and-forget semantics
change RpcHandler to return { payload: {} } but keep tests consistent).

Comment on lines +76 to +79
type RpcFundAddressResponse = {
method: 'fund-address'
payload: {}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Type mismatch: RpcFundAddressResponse payload does not include txid.

The response type declares an empty payload object, but the implementation at lines 197-200 returns { txid }. This causes a type inconsistency.

 type RpcFundAddressResponse = {
   method: 'fund-address'
-  payload: {}
+  payload: {
+    txid: string
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type RpcFundAddressResponse = {
method: 'fund-address'
payload: {}
}
type RpcFundAddressResponse = {
method: 'fund-address'
payload: {
txid: string
}
}
🤖 Prompt for AI Agents
In src/screens/Apps/Escrow/RpcHandler.ts around lines 76 to 79, the
RpcFundAddressResponse type currently defines payload as an empty object but the
implementation returns { txid }; update the type so payload includes txid (e.g.
payload: { txid: string }) to match the returned object, and adjust any callers
or tests that consume RpcFundAddressResponse if they expect a different shape.

import { base64 } from '@scure/base'
import makeMessageHandler from './RpcHandler'

// Needed to sign the message, perhaps to be lifted up int App structure?
Copy link
Member

Choose a reason for hiding this comment

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

I think we fixed this? @bordalix

Copilot AI and others added 7 commits December 12, 2025 14:42
…s in restricted environments (#192)

* Initial plan

* Add JavaScript/JIT capability detection with informative error messages

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

* Format code with prettier

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

* rebuild service worker

* refactor how blocking errors are shown

* fix tests

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>
@pietro909 pietro909 force-pushed the integrate-escrow-app branch from ecf94b7 to 27b5b11 Compare December 12, 2025 13:43
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 12, 2025

Deploying wallet-mutinynet with  Cloudflare Pages  Cloudflare Pages

Latest commit: 27b5b11
Status: ✅  Deploy successful!
Preview URL: https://4f01356c.arkade-wallet.pages.dev
Branch Preview URL: https://integrate-escrow-app.arkade-wallet.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

Deploying wallet-bitcoin with  Cloudflare Pages  Cloudflare Pages

Latest commit: 27b5b11
Status: ✅  Deploy successful!
Preview URL: https://6212c1d8.wallet-bitcoin.pages.dev
Branch Preview URL: https://integrate-escrow-app.wallet-bitcoin.pages.dev

View logs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
README.md (1)

29-37: Remove stray periods after backticked env var names (docs rendering).
This was previously flagged; the periods are still present.

-| `VITE_UTXO_MAX_AMOUNT`.     | Overwrite the server's utxoMaxAmount                            | `VITE_UTXO_MAX_AMOUNT=-1`                         |
-| `VITE_UTXO_MIN_AMOUNT`.     | Overwrite the server's utxoMinAmount                            | `VITE_UTXO_MIN_AMOUNT=330`                        |
-| `VITE_VTXO_MAX_AMOUNT`.     | Overwrite the server's vtxoMaxAmount                            | `VITE_VTXO_MAX_AMOUNT=-1`                         |
-| `VITE_VTXO_MIN_AMOUNT`.     | Overwrite the server's vtxoMinAmount                            | `VITE_VTXO_MIN_AMOUNT=330`                        |
+| `VITE_UTXO_MAX_AMOUNT`      | Overwrite the server's utxoMaxAmount                            | `VITE_UTXO_MAX_AMOUNT=-1`                         |
+| `VITE_UTXO_MIN_AMOUNT`      | Overwrite the server's utxoMinAmount                            | `VITE_UTXO_MIN_AMOUNT=330`                        |
+| `VITE_VTXO_MAX_AMOUNT`      | Overwrite the server's vtxoMaxAmount                            | `VITE_VTXO_MAX_AMOUNT=-1`                         |
+| `VITE_VTXO_MIN_AMOUNT`      | Overwrite the server's vtxoMinAmount                            | `VITE_VTXO_MIN_AMOUNT=330`                        |
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecf94b7 and 27b5b11.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • public/escrow.png is excluded by !**/*.png
📒 Files selected for processing (11)
  • README.md (1 hunks)
  • src/icons/Escrow.tsx (1 hunks)
  • src/providers/navigation.tsx (4 hunks)
  • src/providers/options.tsx (1 hunks)
  • src/providers/wallet.tsx (1 hunks)
  • src/screens/Apps/Escrow/ArkadeIframeHost.tsx (1 hunks)
  • src/screens/Apps/Escrow/Index.tsx (1 hunks)
  • src/screens/Apps/Escrow/RpcHandler.test.ts (1 hunks)
  • src/screens/Apps/Escrow/RpcHandler.ts (1 hunks)
  • src/screens/Apps/Index.tsx (3 hunks)
  • src/vite-env.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/providers/navigation.tsx
  • src/screens/Apps/Escrow/RpcHandler.test.ts
  • src/vite-env.d.ts
  • src/icons/Escrow.tsx
  • src/screens/Apps/Escrow/RpcHandler.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-30T18:33:29.839Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 114
File: src/lib/asp.ts:0-0
Timestamp: 2025-06-30T18:33:29.839Z
Learning: In src/lib/asp.ts, only the collaborativeExit function should accept both IWallet and ServiceWorkerWallet types. Other wallet functions (getBalance, getTxHistory, getReceivingAddresses, redeemNotes, sendOnChain, settleVtxos) should maintain their original IWallet-only signatures and not be updated for consistency.

Applied to files:

  • src/providers/wallet.tsx
🧬 Code graph analysis (4)
src/providers/options.tsx (1)
src/icons/Logs.tsx (1)
  • LogsIcon (1-10)
src/screens/Apps/Escrow/Index.tsx (4)
src/providers/navigation.tsx (1)
  • NavigationContext (174-178)
src/providers/wallet.tsx (1)
  • WalletContext (42-56)
src/screens/Apps/Escrow/RpcHandler.ts (1)
  • makeMessageHandler (116-213)
src/screens/Apps/Escrow/ArkadeIframeHost.tsx (1)
  • ArkadeIframeHost (10-83)
src/screens/Apps/Escrow/ArkadeIframeHost.tsx (2)
src/screens/Apps/Escrow/RpcHandler.ts (1)
  • MessageHandler (105-105)
src/test/setup.mjs (1)
  • result (14-14)
src/screens/Apps/Index.tsx (3)
src/providers/flow.tsx (1)
  • FlowContext (89-104)
src/App.tsx (1)
  • App (36-164)
src/icons/Escrow.tsx (1)
  • EscrowIcon (1-3)
🪛 GitHub Check: test
src/screens/Apps/Index.tsx

[warning] 96-96:
'deepLinkInfo' is assigned a value but never used


[warning] 96-96:
'deepLinkInfo' is assigned a value but never used

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: Cloudflare Pages: wallet-mutinynet
🔇 Additional comments (3)
src/providers/options.tsx (1)

53-57: Ensure SettingsOptions.Sign is fully wired + consider a dedicated icon (not LogsIcon).
If SettingsOptions.Sign is new, confirm enum + settings routing/rendering exists; otherwise tapping this entry will dead-end. Also, reusing LogsIcon for signing is likely confusing—worth adding a SignIcon (or similar) for clarity.

src/providers/wallet.tsx (1)

131-146: Deep-link routing for appId: 'escrow' looks consistent.
Matches existing boltz/lendasat/lendaswap handling and keeps wallet fallback behavior intact.

src/screens/Apps/Escrow/Index.tsx (1)

2-5: Verify @noble/secp256k1 hash wiring + PSBT signing assumptions.
hashes.sha256 = sha256 and the PSBT signing flow (Transaction.fromPSBT(..., { allowUnknown: true }), plus signing checkpoints with [0]) are dependency/API-sensitive—worth confirming with the exact library versions in this repo that this is the intended/required setup and that checkpoint PSBTs always have the expected input structure.

Also applies to: 17-19, 56-76

Comment on lines +15 to +21
const poll = useCallback(() => {
if (iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({ kind: 'ARKADE_KEEP_ALIVE', timestamp: Date.now() }, childOrigin)
setTimeout(() => poll(), isAlive ? 5000 : 2000)
}
}, [isAlive, iframeRef, childOrigin])

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix timer/listener leaks and make origin validation strict (===).
As written, polling can continue after unmount, and the load listener can be registered multiple times. Also, origin checking via startsWith is brittle—prefer exact origin matching.

 export const ArkadeIframeHost: React.FC<Props> = ({ src, allowedChildOrigins, messageHandler }) => {
   const iframeRef = useRef<HTMLIFrameElement | null>(null)
   const childOrigin = useMemo(() => new URL(src).origin, [src])
   const [isAlive, setIsAlive] = useState(false)
+  const timeoutRef = useRef<number | null>(null)
+  const isAliveRef = useRef(false)
+
+  useEffect(() => {
+    isAliveRef.current = isAlive
+  }, [isAlive])
 
   const poll = useCallback(() => {
     if (iframeRef.current?.contentWindow) {
       iframeRef.current.contentWindow.postMessage({ kind: 'ARKADE_KEEP_ALIVE', timestamp: Date.now() }, childOrigin)
-      setTimeout(() => poll(), isAlive ? 5000 : 2000)
+      if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
+      timeoutRef.current = window.setTimeout(() => poll(), isAliveRef.current ? 5000 : 2000)
     }
-  }, [isAlive, iframeRef, childOrigin])
+  }, [iframeRef, childOrigin])
 
   useEffect(() => {
     const onMessage = async (event: MessageEvent) => {
-      if (!allowedChildOrigins.some((_) => _.startsWith(event.origin))) {
+      if (!allowedChildOrigins.some((allowed) => allowed === event.origin)) {
         console.error(`[wallet]: ignoring message from ${event.origin}`)
         return
       }
@@
     window.addEventListener('message', onMessage)
 
     return () => {
       window.removeEventListener('message', onMessage)
+      if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
     }
   }, [messageHandler, isAlive, allowedChildOrigins])
 
-  useEffect(() => {
-    iframeRef.current?.addEventListener('load', () => {
-      poll()
-    })
-  }, [poll])
+  const onLoad = useCallback(() => poll(), [poll])
@@
     <iframe
       ref={iframeRef}
       title='Ark Escrow'
       src={src}
+      onLoad={onLoad}
       style={{ width: '100%', height: `100%`, border: 0, display: 'flex' }}
       sandbox='allow-scripts allow-forms allow-same-origin'
       allow='clipboard-read; clipboard-write'
     />

Also applies to: 24-27, 67-71

🤖 Prompt for AI Agents
In src/screens/Apps/Escrow/ArkadeIframeHost.tsx around lines 15-21 (also apply
to 24-27 and 67-71): the poll timer and load event listener leak because
timeouts keep running after unmount and the load handler can be attached
multiple times; change origin checks from startsWith to strict ===, store the
timeout id and clear it on unmount/when polling stops, ensure poll is wrapped to
avoid re-registering (or use a ref to track mounted state) so poll stops when
unmounted, add the load listener only once (register in useEffect with an empty
dependency array or proper deps) and remove that listener in the effect cleanup,
and validate event.origin with === before processing postMessage.

Comment on lines +17 to +27
// Needed to sign the message, perhaps to be lifted up int App structure?
hashes.sha256 = sha256

const BASE_URLS: Record<Network, string | null> = {
bitcoin: import.meta.env.VITE_ARK_ESCROW_URL ?? 'https://escrow.arkade.sh/client/',
mutinynet: import.meta.env.VITE_ARK_ESCROW_URL ?? 'https://api.escrow.mutinynet.arkade.sh/client/',
signet: null,
regtest: import.meta.env.VITE_ARK_ESCROW_URL ?? 'http://localhost:3002/client',
testnet: null,
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid stale escrowAppUrl + narrow allowedOrigins to the active iframe origin.
Right now, switching to a network with BASE_URLS[network] === null won’t clear escrowAppUrl, so the iframe may remain mounted with the previous URL. Also, consider only allowing the selected escrow origin rather than all known origins.

   useEffect(() => {
-    if (!aspInfo.network || !svcWallet) return
-    const baseUrl = BASE_URLS[aspInfo.network as Network]
-    if (!baseUrl) return // No escrow app for this network
-    setEscrowAppUrl(baseUrl)
-  }, [aspInfo, svcWallet])
+    if (!aspInfo.network || !svcWallet) {
+      setEscrowAppUrl(null)
+      return
+    }
+    const baseUrl = BASE_URLS[aspInfo.network as Network]
+    // No escrow app for this network
+    setEscrowAppUrl(baseUrl ?? null)
+  }, [aspInfo.network, svcWallet])
@@
-  const allowedOrigins = Object.values(BASE_URLS)
-    .filter((_) => _ !== null)
-    .map((url) => new URL(url).origin)
+  const allowedOrigins = escrowAppUrl ? [new URL(escrowAppUrl).origin] : []
@@
-      {escrowAppUrl !== null && (
+      {escrowAppUrl !== null && (
         <ArkadeIframeHost src={escrowAppUrl} allowedChildOrigins={allowedOrigins} messageHandler={handlers} />
       )}

Also applies to: 36-42, 96-105

🤖 Prompt for AI Agents
In src/screens/Apps/Escrow/Index.tsx around lines 17-27 (also apply same changes
at 36-42 and 96-105): the component currently leaves escrowAppUrl set when
switching to a network whose BASE_URLS[network] is null and uses a broad
allowedOrigins list; update the logic so that when BASE_URLS[network] is null
you explicitly clear escrowAppUrl (e.g., set it to null/empty) to unmount the
iframe and prevent using a stale URL, and compute allowedOrigins dynamically
from the single active origin (only include the origin corresponding to the
selected network) instead of allowing all known origins so postMessage listeners
only accept messages from the mounted iframe origin.

Comment on lines +12 to 16
import EscrowIcon from '../../icons/Escrow'
import LendasatIcon from './Lendasat/LendasatIcon'
import LendaswapIcon from './Lendaswap/LendaswapIcon'
import { FlowContext } from '../../providers/flow'

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix unused deepLinkInfo + remove hardcoded enablement (or drop the import).
Right now deepLinkInfo is read but never used, and Escrow is always enabled. Either use the intended gating or remove the dead code. Also prefer omitting link instead of link=''.

 export default function Apps() {
-  const { deepLinkInfo } = useContext(FlowContext)
-  const isEscrowEnabled = true // deepLinkInfo?.appId === 'escrow'
+  const { deepLinkInfo } = useContext(FlowContext)
+  const isEscrowEnabled = deepLinkInfo?.appId === 'escrow'
@@
             {isEscrowEnabled ? (
               <App
                 name='Escrow'
                 icon={<EscrowIcon />}
                 desc='Escrow system on Ark'
-                link=''
+                link={undefined}
                 page={Pages.AppEscrow}
                 live
               />
             ) : null}

Also applies to: 95-140

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.

6 participants