Skip to content

feat: add send to platform address mode#10

Open
thepastaclaw wants to merge 10 commits intoPastaPastaPasta:mainfrom
thepastaclaw:feat/send-to-platform-address
Open

feat: add send to platform address mode#10
thepastaclaw wants to merge 10 commits intoPastaPastaPasta:mainfrom
thepastaclaw:feat/send-to-platform-address

Conversation

@thepastaclaw
Copy link
Contributor

@thepastaclaw thepastaclaw commented Feb 18, 2026

Adds a "Send to Platform Address" mode per thephez request.

Flow: user enters recipient bech32m address → deposits L1 DASH → bridge creates asset lock → sdk.addresses.fundFromAssetLock() → done.

Changes:

  • New send_to_address mode, enter_recipient_address/sending_to_address steps in types.ts
  • Mode/step logic + state helpers in state.ts
  • "Send to Platform Address" button + bech32m input step with validation in components.ts
  • sendToPlatformAddress() using sdk.addresses.fundFromAssetLock() in identity.ts
  • Full flow wiring in main.ts

Bug fix (bonus): recheckDeposit() always ran the identity registration path regardless of mode — fixed to branch on state.mode.

⚠️ Runtime risk: PlatformAddressSigner is created empty (we have the bech32m address, not the private key). If the SDK requires signer keys for all output addresses, this throws after the asset lock broadcasts. TODO comment at that spot — needs live testing.

Validation

  • Build: npm run build completes without errors
  • Type checking: TypeScript compilation passes — new types (BridgeMode additions, BridgeStep additions, new state fields) are consistent across types.ts, state.ts, components.ts, main.ts, and identity.ts
  • Lint: No new lint warnings introduced
  • Manual UI verification:
    • "Send to Platform Address" button appears on the init page alongside existing modes
    • Selecting the mode transitions to the enter_recipient_address step with bech32m input field
    • Address validation rejects invalid input and shows appropriate error messages (correct network prefix: tdash1 for testnet, dash1 for mainnet)
    • Continue button is disabled until a valid address is entered
    • Back button returns to init step
    • Deposit step shows truncated recipient address in headline
    • Processing step shows "Sending to platform address" messaging
    • Completion step shows recipient address and transaction ID
    • Key backup download generates mode-specific filename (dash-send-to-address-*-recovery.json)
  • recheckDeposit() fix verified: Mode branching logic correctly routes fund_address and send_to_address modes to their respective handlers instead of always falling through to identity registration
  • Fund Platform Address mode (also added in this PR): Similar flow verified — private key input with show/hide toggle, bech32m address derivation display, correct wiring to fundPlatformAddress()
  • State isolation: clearModeSensitiveFields() properly clears cross-mode state when switching between modes (e.g., recipientPlatformAddress cleared when switching to fund_address mode and vice versa)

Summary by CodeRabbit

  • New Features

    • Added "Fund Platform Address" and "Send to Platform Address" flows to fund or send from an asset-lock using a platform private key or recipient platform address.
    • UI now supports entering platform/recipient addresses, toggling key visibility, and includes platform data in key backup/download with mode-specific filenames.
  • UX Improvements

    • Bech32m address and private-key validation with blur/paste handling, clearer processing/complete steps, progress indicators, and improved validation/error messaging.

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds two bridge modes (fund_address, send_to_address) with end-to-end platform flows: UI steps for entering platform/recipient addresses and validation, new state setters, EvoSDK platform integrations, new platform APIs (fundPlatformAddress, sendToPlatformAddress), and orchestration in main to drive asset-lock → platform funding/sending.

Changes

Cohort / File(s) Summary
Platform Layer
src/platform/identity.ts, src/platform/index.ts
New exports fundPlatformAddress and sendToPlatformAddress: construct AssetLockProof from AssetLockProofData, build PlatformAddressSigner/outputs, call EvoSDK fundFromAssetLock, validate results and return structured success objects; re-exported via platform index.
Main Orchestration
src/main.ts
Imported PrivateKey, PlatformAddress, PlatformAddressSigner from evo-sdk; wired new UI setters and platform APIs; added startFundAddress and startSendToAddress flows; reworked deposit recheck to branch by mode and invoke topUpIdentity / fundPlatformAddress / sendToPlatformAddress accordingly; added validation, logging and key-visibility/continue controls.
Types
src/types.ts
Extended BridgeMode with fund_address and send_to_address; added BridgeStep entries (enter_platform_address, funding_address, enter_recipient_address, sending_to_address); added optional state fields platformAddressPrivateKeyWif, platformAddress, recipientPlatformAddress.
UI Components
src/ui/components.ts
Added renderEnterPlatformAddressStep and renderEnterRecipientAddressStep; integrated new init buttons; updated deposit/processing/complete renderers to be mode-aware (headlines, backup/download filenames, validation and derived address display).
UI State & Exports
src/ui/state.ts, src/ui/index.ts
Added state helpers clearModeSensitiveFields, enhanced setMode to initialize fund_address & send_to_address; added setters setPlatformAddress, setFundAddressComplete, setRecipientPlatformAddress, setSendToAddressComplete; updated step descriptions/progress and processing detection.
Misc
.gitignore
Added worktrees/ ignore pattern.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as UI Layer
    participant State as State Management
    participant Main as Main Orchestration
    participant Platform as Platform Functions
    participant SDK as EvoSDK

    User->>UI: Start "Fund Platform Address" mode
    UI->>State: setMode('fund_address')
    State-->>UI: step = enter_platform_address

    User->>UI: Submit platform private key
    UI->>State: setPlatformAddress(keyWif, derivedAddress)

    User->>UI: Confirm fund
    UI->>Main: startFundAddress(assetLockProofData, keyWif)
    Main->>Platform: fundPlatformAddress(keyWif, assetLockProofData, ...)
    Platform->>SDK: connect() / buildAssetLockProof()
    Platform->>SDK: fundFromAssetLock(proof, PlatformAddressSigner, outputs)
    SDK-->>Platform: result
    Platform-->>Main: result
    Main->>State: setFundAddressComplete()
    State-->>UI: step = complete
    UI->>User: Show funded platform address / backup
Loading
sequenceDiagram
    actor User
    participant UI as UI Layer
    participant State as State Management
    participant Main as Main Orchestration
    participant Platform as Platform Functions
    participant SDK as EvoSDK

    User->>UI: Start "Send to Address" mode
    UI->>State: setMode('send_to_address')
    State-->>UI: step = enter_recipient_address

    User->>UI: Submit recipient bech32m address
    UI->>State: setRecipientPlatformAddress(address)

    User->>UI: Confirm send
    UI->>Main: startSendToAddress(assetLockProofData, recipient)
    Main->>Platform: sendToPlatformAddress(recipient, assetLockProofData, ...)
    Platform->>SDK: connect() / buildAssetLockProof()
    Platform->>SDK: fundFromAssetLock(proof, recipientOutput)
    SDK-->>Platform: txResult
    Platform-->>Main: result
    Main->>State: setSendToAddressComplete()
    State-->>UI: step = complete
    UI->>User: Show recipient address confirmation / backup
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I nibble keys beneath the moon,

I hop and fund a distant tune,
I send a carrot down the chain,
Signed and backed — no hop in vain,
Little paws, big network boon.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main addition: a new 'send to platform address' mode, which is a primary feature across multiple files and the core workflow addition.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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

🧹 Nitpick comments (1)
src/ui/state.ts (1)

81-103: Clear other mode-specific fields when switching modes.

fund_address clears its own fields but keeps recipientPlatformAddress, and send_to_address keeps platformAddressPrivateKeyWif/platformAddress. Clearing both directions avoids stale sensitive data and accidental reuse.

Suggested cleanup
   } else if (mode === 'fund_address') {
     // Fund platform address mode: user enters platform address private key
     return {
       ...state,
       step: 'enter_platform_address',
       mode,
       mnemonic: undefined,
       identityKeys: [],
       isOneTimeKey: true,
       platformAddressPrivateKeyWif: undefined,
       platformAddress: undefined,
+      recipientPlatformAddress: undefined,
     };
   } else if (mode === 'send_to_address') {
     // Send to platform address mode: user enters recipient bech32m address
     return {
       ...state,
       step: 'enter_recipient_address',
       mode,
       mnemonic: undefined,
       identityKeys: [],
       isOneTimeKey: true,
       recipientPlatformAddress: undefined,
+      platformAddressPrivateKeyWif: undefined,
+      platformAddress: undefined,
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/state.ts` around lines 81 - 103, When switching modes in the UI state
reducer for mode values 'fund_address' and 'send_to_address', ensure the
opposite mode's sensitive fields are cleared: in the branch handling mode ===
'fund_address' also set recipientPlatformAddress to undefined, and in the branch
handling mode === 'send_to_address' also set platformAddressPrivateKeyWif and
platformAddress to undefined; update the return objects in the reducer (the
branches that set step: 'enter_platform_address' and step:
'enter_recipient_address') to include these additional undefined fields so stale
sensitive data cannot persist.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.ts`:
- Around line 1236-1238: The current fund-address flow incorrectly sets the step
to 'topping_up' (via updateState(setStep(state, 'topping_up'))), which is
reserved for identity top-ups; change this to use the 'funding_address' step so
progress/labels and recheck logic align: update the call in startFundAddress
(and any other fund-address entry points) to call updateState(setStep(state,
'funding_address')) and ensure any state checks or UI labels that expect
'topping_up' for identity flows remain unchanged.
- Around line 323-338: The recipient address check around
continueSendToAddressBtn currently only checks prefix/length via
validatePlatformAddress; update validatePlatformAddress (and the click handler
logic before calling startSendToAddress) to perform full bech32m checksum
validation either by using a bech32m library (e.g., `@scure/bech32`) to decode and
verify the checksum for the expected hrp or by attempting to construct a
PlatformAddress and catching any validation errors, and only call
startSendToAddress when that validation passes; reference
validatePlatformAddress, continueSendToAddressBtn,
state.recipientPlatformAddress, state.network, startSendToAddress and ensure you
prevent calling fundFromAssetLock with an invalid address.

In `@src/ui/components.ts`:
- Around line 379-405: The code injects user-controlled values into
keyGroup.innerHTML and addressDisplay.innerHTML (notably
state.platformAddressPrivateKeyWif and state.platformAddress), which can lead to
XSS; fix by replacing the innerHTML interpolations with safe DOM assignments
(create the input element and set its .value or use element.textContent for the
<code> and data-copy attributes) or call the existing escapeHtml helper before
inserting into innerHTML; update references in the platform-address handling
logic (look for keyGroup.innerHTML, toggle-key-visibility-btn,
platform-address-key-input, and addressDisplay.innerHTML) and apply the same
pattern to the other occurrences mentioned (lines around 521-527 and 871-875).

---

Nitpick comments:
In `@src/ui/state.ts`:
- Around line 81-103: When switching modes in the UI state reducer for mode
values 'fund_address' and 'send_to_address', ensure the opposite mode's
sensitive fields are cleared: in the branch handling mode === 'fund_address'
also set recipientPlatformAddress to undefined, and in the branch handling mode
=== 'send_to_address' also set platformAddressPrivateKeyWif and platformAddress
to undefined; update the return objects in the reducer (the branches that set
step: 'enter_platform_address' and step: 'enter_recipient_address') to include
these additional undefined fields so stale sensitive data cannot persist.

Copy link
Contributor

@thephez thephez left a comment

Choose a reason for hiding this comment

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

  1. The UI for the new screens do not match the style of all other screens.

This PR's added screen
Image

Existing screens
Image

  1. Validation is performed against the wrong bech32m HRP. (see DIP-18 dashpay/dips#171)

It may also be necessary to upgrade to the latest evo sdk. That should be a standalone PR as it may require other modifications.

thepastaclaw and others added 3 commits February 19, 2026 09:47
Add new 'fund_address' mode to the bridge that lets users send L1 DASH
directly to a Platform address (bech32m) using an asset lock.

Flow:
1. User enters their platform address private key (WIF)
2. Bridge derives and displays the bech32m platform address for confirmation
3. Same L1 deposit → asset lock flow as topup mode
4. Calls sdk.addresses.fundFromAssetLock() instead of sdk.identities.topUp()

New files:
- src/platform/address.ts — fundPlatformAddress() implementation
- src/ui/fund-address.ts — enter_platform_address step UI

Updated:
- src/types.ts — added fund_address mode + enter_platform_address step + state fields
- src/ui/state.ts — setMode(), setPlatformAddressKey(), setFundAddressComplete()
- src/ui/components.ts — mode button + fund-address step rendering
- src/main.ts — startFundAddress(), event handlers, step routing
Add new bridge mode that lets users send L1 DASH to any platform
address (bech32m) without requiring the recipient's private key.
Uses sdk.addresses.fundFromAssetLock() with PlatformAddressOutput
and an empty PlatformAddressSigner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
recheckDeposit() now branches on state.mode for the final operation
(topup/fund_address/send_to_address/create) instead of always
registering an identity. Also escapeHtml() all user-supplied
recipientPlatformAddress values rendered via innerHTML.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@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: 1

🧹 Nitpick comments (4)
src/main.ts (2)

1234-1235: setInstantLockReceived hardcodes step: 'registering_identity', causing a brief incorrect step flash in fund_address and send_to_address modes.

setInstantLockReceived always sets step: 'registering_identity', which is immediately overwritten on the next line. While the flash is short (one render frame), a user on slow hardware could see "Creating identity..." in an address-funding flow. The cleanest fix is to pass the intended next step, or skip setInstantLockReceived for non-create modes and just store instantLockBytes/assetLockProof directly. Alternatively, the step-setting can be omitted from setInstantLockReceived for these paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1234 - 1235, setInstantLockReceived currently
forces step: 'registering_identity', causing a UI flash in non-create flows;
update the call site around updateState(setInstantLockReceived(...)) to either
(a) pass the intended next step into setInstantLockReceived so it doesn't
overwrite the current flow step, or (b) skip calling setInstantLockReceived in
fund_address/send_to_address modes and instead directly update
instantLockBytes/assetLockProof in state, or (c) change setInstantLockReceived
to not set step at all and only store instantLockBytes/assetLockProof; use the
function name setInstantLockReceived and the surrounding updateState call to
locate and apply the change.

1157-1359: Steps 1–6 are duplicated verbatim across startFundAddress, startSendToAddress, startTopUp, and startBridge.

All four functions share identical logic for key generation, deposit polling, transaction build/sign/broadcast, and InstantSend-lock wait. Only step 7 (the platform operation) differs. Extract a shared helper:

async function buildAndBroadcastAssetLock(
  existingKeyPair?: KeyPair
): Promise<{ assetLockProof: string; assetLockKeyPair: KeyPair; signedTxBytes: Uint8Array }> {
  // shared steps 1-6
}

This would reduce ~200 lines of near-identical code to a single reusable function, making future maintenance changes (e.g. timeout adjustment, fee changes) apply in one place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1157 - 1359, The four functions startFundAddress,
startSendToAddress, startTopUp and startBridge duplicate steps 1–6 (key
generation, deposit wait, tx build/sign/broadcast, InstantSend wait); extract
those shared steps into a new helper function
buildAndBroadcastAssetLock(existingKeyPair?: KeyPair): Promise<{ assetLockProof:
any; assetLockKeyPair: KeyPair; signedTxBytes: Uint8Array; signedTxHex: string;
txid: string }>, move all logic from generating_keys through receiving the
InstantSend lock into that helper, preserve calls to updateState inside the
helper (setStep, setOneTimeKeyPair, setUtxoDetected, setTransactionSigned,
setTransactionBroadcast, setInstantLockReceived) and return the assetLockProof,
assetLockKeyPair and serialized tx/txid; then update each of the four callers
(startFundAddress, startSendToAddress, startTopUp, startBridge) to call
buildAndBroadcastAssetLock(...) and perform only step 7 (the platform-specific
operation) using the returned assetLockProof and assetLockKeyPair/private WIF,
keeping existing error handling in each caller.
src/ui/components.ts (1)

982-993: Stale comment: "For top-up mode" is now incorrect for the shared else-branch.

The else block at line 983 now handles fund_address and send_to_address as well as topup. Both the leading comment and the note string at line 990 ('One-time key for top-up. Use this WIF...') are misleading for the new modes.

-  } else {
-    // For top-up mode: include target identity and one-time key
+  } else {
+    // For top-up/fund_address/send_to_address modes: include one-time key for recovery
     backup.targetIdentityId = state.targetIdentityId;
     backup.assetLockKey = state.assetLockKeyPair
       ? {
           wif: privateKeyToWif(state.assetLockKeyPair.privateKey, network),
           publicKeyHex: bytesToHex(state.assetLockKeyPair.publicKey),
-          note: 'One-time key for top-up. Use this WIF to recover funds if top-up fails.',
+          note: 'One-time key. Use this WIF to recover funds if the operation fails.',
         }
       : null;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/components.ts` around lines 982 - 993, The leading comment and the
assetLockKey.note are stale because the else-branch now serves multiple modes
(fund_address, send_to_address, topup); update the comment above the else-branch
and the note string assigned in backup.assetLockKey to be mode-agnostic (e.g.,
"Include target identity and one-time key when applicable" and change the note
to something like "One-time key for this operation. Use this WIF to recover
funds if the operation fails."). Ensure you update the text near
backup.targetIdentityId, backup.assetLockKey, and the creation of the note that
uses privateKeyToWif(state.assetLockKeyPair.privateKey, network) so messaging
correctly reflects all supported modes.
src/ui/state.ts (1)

93-103: setMode('send_to_address') leaves stale platformAddressPrivateKeyWif / platformAddress from a prior fund_address session.

If a user starts fund_address, enters a key (which populates platformAddressPrivateKeyWif and platformAddress), navigates back, then selects send_to_address, both fields persist in state. They are not consumed by the send_to_address flow, but they bloat the key-backup JSON and could cause confusion. Apply symmetric cleanup:

  } else if (mode === 'send_to_address') {
    return {
      ...state,
      step: 'enter_recipient_address',
      mode,
      mnemonic: undefined,
      identityKeys: [],
      isOneTimeKey: true,
      recipientPlatformAddress: undefined,
+     platformAddressPrivateKeyWif: undefined,
+     platformAddress: undefined,
    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/state.ts` around lines 93 - 103, When switching to mode ===
'send_to_address' the state update leaves platformAddressPrivateKeyWif and
platformAddress from a prior 'fund_address' session intact; update the branch
that returns the new state (in the state reducer / setMode handler where mode
=== 'send_to_address') to explicitly set platformAddressPrivateKeyWif: undefined
and platformAddress: undefined (in addition to clearing mnemonic, identityKeys,
recipientPlatformAddress, etc.) so stale keys are removed from the state and
exported key backups.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/identity.ts`:
- Around line 356-371: The call to sdk.addresses.fundFromAssetLock inside the
withRetry wrapper is logged but its result is not validated, so the function
always returns { success: true }; update the handling to check the returned
result (e.g. ensure result is non-null and result.success === true or handle
result.error) and return/throw accordingly, mirroring the pattern used in
topUpIdentity/registerIdentity; do the same validation for sendToPlatformAddress
(the sdk call there) and only return success when the SDK result indicates
success, otherwise propagate or log the error and return a failure object.

---

Duplicate comments:
In `@src/main.ts`:
- Around line 1032-1036: The current validatePlatformAddress only checks
prefix/length and allows invalid bech32m addresses; replace that naive check
with real bech32m checksum validation by either calling a bech32m decode library
or attempting to construct a PlatformAddressOutput (or similar platform address
class used elsewhere) inside a try/catch and returning true only if
decoding/constructor succeeds; update validatePlatformAddress to catch and
return false on errors and ensure callers (e.g., prior to fundFromAssetLock) use
this stronger validation to prevent broadcasting invalid asset locks.
- Around line 1236-1237: The step name used in startFundAddress is wrong—it's
calling updateState(setStep(state, 'topping_up')) but should use the same step
key as recheckDeposit ('funding_address'); update the call inside
startFundAddress to updateState(setStep(state, 'funding_address')) (referencing
startFundAddress, recheckDeposit, setStep, updateState and state) so the UI
shows the correct funding address headline/subtitle.

In `@src/ui/components.ts`:
- Around line 521-526: The Fund address branch sets headline.innerHTML using
truncatedAddr without escaping, which is inconsistent with the send_to_address
branch that uses escapeHtml(recipientPlatformAddress); update the fund_address
branch to wrap truncatedAddr with escapeHtml before interpolation (replace usage
in the template string for headline.innerHTML) so the output is escaped like in
the other branch, keeping the same truncatedAddr logic.
- Around line 379-407: Interpolate user-controlled values safely by replacing
direct innerHTML interpolation in keyGroup.innerHTML and
addressDisplay.innerHTML: use the existing escapeHtml utility to escape
state.platformAddressPrivateKeyWif before inserting it into the input value and
escape state.platformAddress before placing it into the <code> content and the
data-copy attribute; alternatively build DOM nodes and set
textContent/element.value and dataset.copy rather than concatenating strings so
symbols like state.platformAddressPrivateKeyWif and state.platformAddress cannot
inject HTML/attributes.
- Around line 869-875: In renderCompleteStep the isFundAddress branch injects
state.platformAddress directly into addressInfo.innerHTML (addressInfo.innerHTML
= `...${state.platformAddress}...`), which allows unescaped HTML; update this to
safely escape the value (e.g., use escapeHtml(state.platformAddress) in the
template) or avoid innerHTML and set the code element's textContent/innerText
after creating it to prevent XSS while preserving the existing structure.

---

Nitpick comments:
In `@src/main.ts`:
- Around line 1234-1235: setInstantLockReceived currently forces step:
'registering_identity', causing a UI flash in non-create flows; update the call
site around updateState(setInstantLockReceived(...)) to either (a) pass the
intended next step into setInstantLockReceived so it doesn't overwrite the
current flow step, or (b) skip calling setInstantLockReceived in
fund_address/send_to_address modes and instead directly update
instantLockBytes/assetLockProof in state, or (c) change setInstantLockReceived
to not set step at all and only store instantLockBytes/assetLockProof; use the
function name setInstantLockReceived and the surrounding updateState call to
locate and apply the change.
- Around line 1157-1359: The four functions startFundAddress,
startSendToAddress, startTopUp and startBridge duplicate steps 1–6 (key
generation, deposit wait, tx build/sign/broadcast, InstantSend wait); extract
those shared steps into a new helper function
buildAndBroadcastAssetLock(existingKeyPair?: KeyPair): Promise<{ assetLockProof:
any; assetLockKeyPair: KeyPair; signedTxBytes: Uint8Array; signedTxHex: string;
txid: string }>, move all logic from generating_keys through receiving the
InstantSend lock into that helper, preserve calls to updateState inside the
helper (setStep, setOneTimeKeyPair, setUtxoDetected, setTransactionSigned,
setTransactionBroadcast, setInstantLockReceived) and return the assetLockProof,
assetLockKeyPair and serialized tx/txid; then update each of the four callers
(startFundAddress, startSendToAddress, startTopUp, startBridge) to call
buildAndBroadcastAssetLock(...) and perform only step 7 (the platform-specific
operation) using the returned assetLockProof and assetLockKeyPair/private WIF,
keeping existing error handling in each caller.

In `@src/ui/components.ts`:
- Around line 982-993: The leading comment and the assetLockKey.note are stale
because the else-branch now serves multiple modes (fund_address,
send_to_address, topup); update the comment above the else-branch and the note
string assigned in backup.assetLockKey to be mode-agnostic (e.g., "Include
target identity and one-time key when applicable" and change the note to
something like "One-time key for this operation. Use this WIF to recover funds
if the operation fails."). Ensure you update the text near
backup.targetIdentityId, backup.assetLockKey, and the creation of the note that
uses privateKeyToWif(state.assetLockKeyPair.privateKey, network) so messaging
correctly reflects all supported modes.

In `@src/ui/state.ts`:
- Around line 93-103: When switching to mode === 'send_to_address' the state
update leaves platformAddressPrivateKeyWif and platformAddress from a prior
'fund_address' session intact; update the branch that returns the new state (in
the state reducer / setMode handler where mode === 'send_to_address') to
explicitly set platformAddressPrivateKeyWif: undefined and platformAddress:
undefined (in addition to clearing mnemonic, identityKeys,
recipientPlatformAddress, etc.) so stale keys are removed from the state and
exported key backups.

@thepastaclaw
Copy link
Contributor Author

thepastaclaw commented Feb 19, 2026

@thephez I pushed a review-fix pass for your feedback + CodeRabbit comments.

Included in latest commits:

What changed:

  • Switched recipient HRP handling/messages to DIP-18 format (tevo1 testnet / evo1 mainnet).
  • Upgraded recipient address validation to real bech32m parsing/canonical network check (not just prefix+length).
  • Corrected fund-address flow step from topping_up -> funding_address.
  • Made new fund/send entry screens reuse existing enter-identity styling structure for consistency.
  • Removed unsafe user-controlled interpolation in those new screens; moved to safe DOM assignment and escaped remaining platform-address interpolations.
  • Cleared opposite mode-sensitive fields in setMode so stale values don’t persist across mode switches.
  • Added validation of fundFromAssetLock results in both fund/send platform-address paths before returning success.

npm run test -- --run still reports no test files in this repo.

If you see anything else off in the new screens, I’ll patch it immediately.

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.ts`:
- Around line 255-283: The invalid-key catch currently clears the input and then
triggers updateState, which re-renders and hides the validation message; instead
preserve the typed WIF in state and ensure the message is set after the render.
In the catch block use updateState(setPlatformAddress(state, privateKeyWif, ''))
(keep the entered privateKeyWif but empty address) and then set the
platform-address-validation-msg textContent and remove the 'hidden' class (or
set an error flag in state and render it). Refer to validatePlatformKey,
platformAddressKeyInput, updateState, and setPlatformAddress to implement this
change so the input value remains and the error message is visible.

In `@src/platform/identity.ts`:
- Around line 405-468: The call to sdk.addresses.fundFromAssetLock() is invalid
for evo-sdk v3; replace it with the proper v3 identity methods: use
sdk.identityCreate(...) when creating a new identity or sdk.identityTopUp(...)
when funding an existing identity. Remove PlatformAddressSigner and
PlatformAddressOutput usage in sendToPlatformAddress, convert
assetLockPrivateKeyWif -> assetLockProofPrivateKey (use PrivateKey.fromWIF(...)
value as assetLockProofPrivateKey) and pass assetLockProof and
assetLockProofPrivateKey to the chosen sdk.identityCreate or sdk.identityTopUp
call; then adapt the response handling currently around result to check the
identityCreate/identityTopUp return shape and throw on errors as before.

In `@src/ui/state.ts`:
- Around line 83-105: When switching into the 'fund_address' or
'send_to_address' branches in the state-reset logic, explicitly clear any stale
top-up identity fields so they don't leak into fund/send flows; update the
returned state objects in those branches (the blocks that return
{...clearedState, step: 'enter_platform_address', mode, ...} and
{...clearedState, step: 'enter_recipient_address', mode, ...}) to include
targetIdentityId: undefined (and optionally identityId: undefined) alongside the
other cleared fields.

Copy link
Contributor

@thephez thephez left a comment

Choose a reason for hiding this comment

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

@thepastaclaw the HRP in DIP-18 is not evo/tevo

@thepastaclaw
Copy link
Contributor Author

Addressed review feedback from @thephez and CodeRabbit:

HRP fix (DIP-18):

  • evo1/tevo1dash1/tdash1 in all UI text and validation messages (83cdaa4)

UI styling alignment:

  • Removed <h2> headlines from new screens to match existing renderEnterIdentityStep pattern
  • Rewrote both renderEnterPlatformAddressStep and renderEnterRecipientAddressStep to use innerHTML template pattern matching the existing identity step
  • Password toggle still works for the WIF input (f269b5d)

XSS hardening:

  • Escaped user-controlled values in innerHTML interpolations (top-up headline, platform address displays)
  • Used safe DOM assignment (.value, .textContent) where possible (f269b5d)

State management:

  • Mode switching now clears cross-mode fields (recipientPlatformAddress, platformAddressPrivateKeyWif, platformAddress, targetIdentityId, identityId) to prevent stale data leaking between modes (5fa1bbd)

UX improvements:

  • Invalid WIF input now preserves the typed value (so users can see what they entered wrong) instead of clearing the field (627816e)
  • Fund/send flows avoid the setInstantLockReceived step flash ("Creating identity..." briefly appearing in address-funding flows) by setting instantLockBytes/assetLockProof directly (627816e)
  • Updated stale comment and note text for one-time key backup to be mode-agnostic (f269b5d)

Note: Pre-existing TypeScript errors in dpns.ts and identity.ts remain — these are SDK type mismatches from the evo-sdk upgrade, not introduced by this PR.

@thephez
Copy link
Contributor

thephez commented Feb 20, 2026

I created #14 and #15 which I think address the issue better

Copy link

@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.

🧹 Nitpick comments (2)
src/ui/state.ts (1)

204-232: setFundAddressComplete and setSendToAddressComplete are identical.

Both functions return { ...state, step: 'complete' }. They can be collapsed into the existing setStep(state, 'complete') call pattern used elsewhere (e.g., setTopUpComplete differs only in also setting identityId, but these two are truly identical). This removes two exported symbols for the same operation.

♻️ Proposed consolidation

In callers (src/main.ts):

-    updateState(setFundAddressComplete(state));
+    updateState(setStep(state, 'complete'));

-    updateState(setSendToAddressComplete(state));
+    updateState(setStep(state, 'complete'));

Then remove both functions and their exports from src/ui/state.ts and src/ui/index.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/state.ts` around lines 204 - 232, Remove the duplicated functions
setFundAddressComplete and setSendToAddressComplete and replace their usage with
the existing setStep(state, 'complete') pattern; update all callers that import
or call setFundAddressComplete/setSendToAddressComplete to call setStep(state,
'complete') instead, and remove the now-unused exports for those two symbols
from the module that re-exports UI state helpers.
src/main.ts (1)

1163-1373: Steps 1–6 are copy-pasted across three start-flow functions.

startFundAddress, startSendToAddress, and startTopUp each independently implement the identical 6-step sequence (key generation → deposit detection → tx build → sign → broadcast → ISLock wait). Only step 7 (the platform operation) differs. Any future bug fix or behavioral change (e.g., timeout values, retry logic) must be applied in three places.

♻️ Suggested extraction
/**
 * Shared steps 1-6: generates one-time keypair, waits for deposit,
 * builds/signs/broadcasts asset lock tx, and waits for ISLock.
 * Returns asset lock proof + private key WIF, or null on timeout.
 */
async function buildAssetLock(): Promise<{
  assetLockProof: string;
  assetLockPrivateKeyWif: string;
  assetLockKeyPair: KeyPair;
} | null> {
  const network = getNetwork(state.network);
  updateState(setStep(state, 'generating_keys'));

  const assetLockKeyPair = generateKeyPair();
  const depositAddress = publicKeyToAddress(assetLockKeyPair.publicKey, network);
  const stateWithKeys = setOneTimeKeyPair(state, assetLockKeyPair, depositAddress);
  updateState(stateWithKeys);
  downloadKeyBackup(stateWithKeys);

  updateState(setStep(stateWithKeys, 'detecting_deposit'));
  const depositResult = await insightClient.waitForUtxo(depositAddress, 300000, 120000, 3000);
  if (!depositResult.utxo) {
    updateState(setDepositTimedOut(state, true, depositResult.totalAmount));
    return null;
  }
  // ... build, sign, broadcast, wait ISLock ...
  return { assetLockProof, assetLockPrivateKeyWif, assetLockKeyPair };
}

// Then in each flow:
async function startFundAddress() {
  const result = await buildAssetLock();
  if (!result) return;
  updateState(setStep(state, 'funding_address'));
  await fundPlatformAddress(state.platformAddressPrivateKeyWif!, result.assetLockProof, result.assetLockPrivateKeyWif, state.network);
  updateState(setStep(state, 'complete'));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1163 - 1373, The three flows startFundAddress,
startSendToAddress and startTopUp duplicate steps 1–6 (key generation → deposit
detection → tx build → sign → broadcast → ISLock wait); extract that common
logic into a single helper (e.g., buildAssetLock) that accepts/uses state,
insightClient and dapiClient, performs the shared sequence, updates state
appropriately (setStep, setOneTimeKeyPair, downloadKeyBackup, setUtxoDetected,
setTransactionSigned, setTransactionBroadcast, etc.), and returns {
assetLockProof, assetLockPrivateKeyWif, assetLockKeyPair } or null on timeout;
then refactor startFundAddress, startSendToAddress, startTopUp to call
buildAssetLock, bail out on null, and only perform their unique step 7
(fundPlatformAddress/sendToPlatformAddress/topUpPlatformAddress) and final state
transitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main.ts`:
- Around line 274-282: The validation error message is currently being set on
the DOM and then immediately wiped by updateState (which re-renders); to fix it
either persist the validation message in state or apply the DOM changes after
the re-render: move the DOM mutation that sets platform-address-validation-msg
(textContent and classList.remove('hidden')) to run after calling
updateState(setPlatformAddress(state, privateKeyWif, '')) or instead updateState
with a field carrying the validation message so the renderer shows it; locate
the catch block around updateState and setPlatformAddress and change the order
or store the message in state so the message remains visible.

In `@src/ui/state.ts`:
- Around line 83-105: clearModeSensitiveFields currently conditionally preserves
platformAddressPrivateKeyWif/platformAddress while setMode's 'fund_address'
branch immediately clears them, so remove the redundant conditional
preservation: update clearModeSensitiveFields (and any other callers) to
unconditionally clear platformAddressPrivateKeyWif and platformAddress, or
alternatively remove the override in setMode; reference clearModeSensitiveFields
and setMode ('fund_address' branch) when making the change so the behavior is
consistent and no stale platform address fields can leak across modes.

---

Nitpick comments:
In `@src/main.ts`:
- Around line 1163-1373: The three flows startFundAddress, startSendToAddress
and startTopUp duplicate steps 1–6 (key generation → deposit detection → tx
build → sign → broadcast → ISLock wait); extract that common logic into a single
helper (e.g., buildAssetLock) that accepts/uses state, insightClient and
dapiClient, performs the shared sequence, updates state appropriately (setStep,
setOneTimeKeyPair, downloadKeyBackup, setUtxoDetected, setTransactionSigned,
setTransactionBroadcast, etc.), and returns { assetLockProof,
assetLockPrivateKeyWif, assetLockKeyPair } or null on timeout; then refactor
startFundAddress, startSendToAddress, startTopUp to call buildAssetLock, bail
out on null, and only perform their unique step 7
(fundPlatformAddress/sendToPlatformAddress/topUpPlatformAddress) and final state
transitions.

In `@src/ui/state.ts`:
- Around line 204-232: Remove the duplicated functions setFundAddressComplete
and setSendToAddressComplete and replace their usage with the existing
setStep(state, 'complete') pattern; update all callers that import or call
setFundAddressComplete/setSendToAddressComplete to call setStep(state,
'complete') instead, and remove the now-unused exports for those two symbols
from the module that re-exports UI state helpers.

Merge main into feat/send-to-platform-address, resolving:
- Import conflicts in identity.ts (combined both branch imports)
- package-lock.json (regenerated from main's package.json)
- Updated fundPlatformAddress/sendToPlatformAddress to use typed
  AssetLockProofData instead of hex strings, matching the SDK upgrade
  pattern from registerIdentity
- Migrated to EvoSDK.mainnetTrusted()/testnetTrusted() constructors
Copy link

@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: 1

🧹 Nitpick comments (4)
src/ui/state.ts (2)

380-391: setInstantLockReceived hardcodes 'registering_identity' — footgun for new modes.

The new fund_address/send_to_address flows correctly work around this by using a direct state spread in main.ts instead of calling setInstantLockReceived. However, the hardcoded step creates a silent footgun for future developers. Consider accepting the target step as a parameter, or documenting the constraint in the JSDoc:

♻️ Proposed fix
 export function setInstantLockReceived(
   state: BridgeState,
   instantLockBytes: Uint8Array,
-  assetLockProof: AssetLockProofData
+  assetLockProof: AssetLockProofData,
+  nextStep: BridgeStep = 'registering_identity'
 ): BridgeState {
   return {
     ...state,
-    step: 'registering_identity',
+    step: nextStep,
     instantLockBytes,
     assetLockProof,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/state.ts` around lines 380 - 391, The function setInstantLockReceived
currently hardcodes step: 'registering_identity', which is fragile for flows
like fund_address/send_to_address; modify setInstantLockReceived to accept an
additional parameter (e.g., targetStep: BridgeState["step"] or step: string) and
set that as the step instead of the literal, or alternatively add a clear JSDoc
on setInstantLockReceived stating it always sets 'registering_identity' and
should not be used by other flows (update callers like main.ts accordingly);
reference the setInstantLockReceived function and ensure all call sites are
updated to pass the desired step (or validated) to avoid the silent footgun.

205-233: setFundAddressComplete and setSendToAddressComplete are identical.

Both functions set step: 'complete' with no other changes. The distinct names are fine for semantic clarity, but if additional completion logic ever diverges (e.g., clearing sensitive key material on completion), having two separate stubs makes that easier. No action required, but consider at minimum sharing the body:

♻️ Proposed simplification
 export function setFundAddressComplete(state: BridgeState): BridgeState {
-  return {
-    ...state,
-    step: 'complete',
-  };
+  return setSendToAddressComplete(state);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/state.ts` around lines 205 - 233, The two functions
setFundAddressComplete and setSendToAddressComplete are identical (both return
{...state, step: 'complete'}); consolidate to avoid duplication by extracting a
shared helper (e.g., setComplete(state: BridgeState): BridgeState) and have both
functions call that helper, or implement one as a thin wrapper that calls the
other (e.g., setSendToAddressComplete calls setFundAddressComplete), ensuring
references to setFundAddressComplete, setSendToAddressComplete, and the new
helper are updated accordingly.
src/main.ts (1)

1163-1267: startFundAddress and startSendToAddress duplicate 90% of startTopUp.

Steps 1–6 (key generation, deposit detection, transaction build/sign/broadcast, ISLock wait, proof construction) are byte-for-byte identical across all three functions. Only step 7 differs. Consider extracting the shared path into a private helper that accepts a callback for the platform operation:

♻️ Proposed refactor sketch
+type AssetLockCallback = (
+  assetLockProof: AssetLockProofData,
+  assetLockPrivateKeyWif: string
+) => Promise<void>;
+
+async function runAssetLockFlow(platformStep: BridgeStep, onPlatformOp: AssetLockCallback) {
+  const network = getNetwork(state.network);
+  updateState(setStep(state, 'generating_keys'));
+  const assetLockKeyPair = generateKeyPair();
+  const depositAddress = publicKeyToAddress(assetLockKeyPair.publicKey, network);
+  const stateWithKeys = setOneTimeKeyPair(state, assetLockKeyPair, depositAddress);
+  updateState(stateWithKeys);
+  downloadKeyBackup(stateWithKeys);
+  updateState(setStep(stateWithKeys, 'detecting_deposit'));
+  // ... shared steps 2-6 ...
+  updateState(setStep(state, platformStep));
+  const assetLockPrivateKeyWif = privateKeyToWif(assetLockKeyPair.privateKey, network);
+  await onPlatformOp(assetLockProof, assetLockPrivateKeyWif);
+}
+
+async function startFundAddress() {
+  try {
+    await runAssetLockFlow('funding_address', async (proof, wif) => {
+      await fundPlatformAddress(state.platformAddressPrivateKeyWif!, proof, wif, state.network);
+      updateState(setFundAddressComplete(state));
+    });
+  } catch (error) {
+    updateState(setError(state, error instanceof Error ? error : new Error(String(error))));
+  }
+}

Also applies to: 1269-1373

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1163 - 1267, The three functions startFundAddress,
startSendToAddress, and startTopUp duplicate steps 1–6 (key generation, deposit
detection, tx build/sign/broadcast, ISLock wait, proof construction); extract
that shared sequence into a single helper (e.g., performAssetLockFlow or
runAssetLockFlow) which returns the common results (assetLockKeyPair,
depositAddress, utxo, signedTxHex/Bytes, txid, islockBytes, assetLockProof) and
accept a callback or an options object for the final step (step 7) so each
original function calls the helper and then performs its specific platform
action (fundPlatformAddress, sendToAddress, topUp) with the returned proof/keys;
update startFundAddress/startSendToAddress/startTopUp to use the helper and
remove duplicated code while preserving existing state update calls (setStep,
updateState, setTransactionSigned, setTransactionBroadcast, etc.) and error
handling.
src/platform/identity.ts (1)

402-559: Significant code duplication between fundPlatformAddress and sendToPlatformAddress.

The SDK init, connect, proof construction, and result-validation blocks are byte-for-byte identical across both functions (and share the same pattern as topUpIdentity). Extract a shared private helper:

♻️ Suggested extraction
+async function initSdkAndBuildProof(
+  network: 'testnet' | 'mainnet',
+  assetLockProofData: AssetLockProofData,
+  assetLockPrivateKeyWif: string,
+  retryOptions?: RetryOptions
+): Promise<{ sdk: ReturnType<typeof EvoSDK.testnetTrusted>; assetLockProof: AssetLockProof; assetLockPrivateKey: PrivateKey }> {
+  const sdk = network === 'mainnet' ? EvoSDK.mainnetTrusted() : EvoSDK.testnetTrusted();
+  console.log(`Connecting to ${network}...`);
+  await withRetry(() => sdk.connect(), retryOptions);
+  console.log('Connected to Platform');
+  const assetLockProof = AssetLockProof.createInstantAssetLockProof(
+    assetLockProofData.instantLockBytes,
+    assetLockProofData.transactionBytes,
+    assetLockProofData.outputIndex
+  );
+  const assetLockPrivateKey = PrivateKey.fromWIF(assetLockPrivateKeyWif);
+  return { sdk, assetLockProof, assetLockPrivateKey };
+}
+
+function validateFundFromAssetLockResult(result: unknown, context: string): void {
+  if (result == null) throw new Error(`${context}: fundFromAssetLock returned no result`);
+  if (typeof result === 'object') {
+    const r = result as { success?: unknown; error?: unknown; message?: unknown };
+    if (r.success === false || r.error !== undefined || r.message !== undefined) {
+      throw new Error(`${context}: ${String(r.error ?? r.message ?? 'unknown error')}`);
+    }
+  }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/identity.ts` around lines 402 - 559, Both fundPlatformAddress
and sendToPlatformAddress duplicate SDK init/connect, AssetLockProof creation,
assetLockPrivateKey building, signer setup, fundFromAssetLock call and result
validation (same pattern used in topUpIdentity); extract this into a single
helper (e.g. fundFromAssetLockHelper) that accepts (network, assetLockProofData,
assetLockPrivateKeyWif, outputs, signer, retryOptions) and returns the validated
result or throws the same errors. Replace the duplicated blocks inside
fundPlatformAddress and sendToPlatformAddress with calls to
fundFromAssetLockHelper, preserving the original behavior (construct
PlatformAddressOutput or recipient address before calling, keep returning
success + address fields) and reuse the helper from topUpIdentity if applicable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.ts`:
- Around line 304-312: The click handler for continueFundAddressBtn silently
no-ops when state.platformAddressPrivateKeyWif or state.platformAddress is
falsy; add an else branch to provide user feedback consistent with other gates
(e.g., continueTopUpBtn). Update the event listener on continueFundAddressBtn
(the block that currently calls startFundAddress()) to call the existing
showValidationError(...) with a clear message when either
state.platformAddressPrivateKeyWif or state.platformAddress is missing (or
alternatively set a dedicated validation element like
`#platform-address-validation-msg`), so users see a validation error instead of
nothing happening.

---

Nitpick comments:
In `@src/main.ts`:
- Around line 1163-1267: The three functions startFundAddress,
startSendToAddress, and startTopUp duplicate steps 1–6 (key generation, deposit
detection, tx build/sign/broadcast, ISLock wait, proof construction); extract
that shared sequence into a single helper (e.g., performAssetLockFlow or
runAssetLockFlow) which returns the common results (assetLockKeyPair,
depositAddress, utxo, signedTxHex/Bytes, txid, islockBytes, assetLockProof) and
accept a callback or an options object for the final step (step 7) so each
original function calls the helper and then performs its specific platform
action (fundPlatformAddress, sendToAddress, topUp) with the returned proof/keys;
update startFundAddress/startSendToAddress/startTopUp to use the helper and
remove duplicated code while preserving existing state update calls (setStep,
updateState, setTransactionSigned, setTransactionBroadcast, etc.) and error
handling.

In `@src/platform/identity.ts`:
- Around line 402-559: Both fundPlatformAddress and sendToPlatformAddress
duplicate SDK init/connect, AssetLockProof creation, assetLockPrivateKey
building, signer setup, fundFromAssetLock call and result validation (same
pattern used in topUpIdentity); extract this into a single helper (e.g.
fundFromAssetLockHelper) that accepts (network, assetLockProofData,
assetLockPrivateKeyWif, outputs, signer, retryOptions) and returns the validated
result or throws the same errors. Replace the duplicated blocks inside
fundPlatformAddress and sendToPlatformAddress with calls to
fundFromAssetLockHelper, preserving the original behavior (construct
PlatformAddressOutput or recipient address before calling, keep returning
success + address fields) and reuse the helper from topUpIdentity if applicable.

In `@src/ui/state.ts`:
- Around line 380-391: The function setInstantLockReceived currently hardcodes
step: 'registering_identity', which is fragile for flows like
fund_address/send_to_address; modify setInstantLockReceived to accept an
additional parameter (e.g., targetStep: BridgeState["step"] or step: string) and
set that as the step instead of the literal, or alternatively add a clear JSDoc
on setInstantLockReceived stating it always sets 'registering_identity' and
should not be used by other flows (update callers like main.ts accordingly);
reference the setInstantLockReceived function and ensure all call sites are
updated to pass the desired step (or validated) to avoid the silent footgun.
- Around line 205-233: The two functions setFundAddressComplete and
setSendToAddressComplete are identical (both return {...state, step:
'complete'}); consolidate to avoid duplication by extracting a shared helper
(e.g., setComplete(state: BridgeState): BridgeState) and have both functions
call that helper, or implement one as a thin wrapper that calls the other (e.g.,
setSendToAddressComplete calls setFundAddressComplete), ensuring references to
setFundAddressComplete, setSendToAddressComplete, and the new helper are updated
accordingly.

Comment on lines +304 to +312
// Continue fund address button
const continueFundAddressBtn = container.querySelector('#continue-fund-address-btn');
if (continueFundAddressBtn) {
continueFundAddressBtn.addEventListener('click', () => {
if (state.platformAddressPrivateKeyWif && state.platformAddress) {
startFundAddress();
}
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

continue-fund-address-btn shows no feedback when prerequisites are not met.

When either state.platformAddressPrivateKeyWif or state.platformAddress is falsy (e.g., user clicks before entering/validating a key), the handler silently no-ops. Every other validation gate in this file shows a message:

  • continueTopUpBtn calls showValidationError(...) (line 250)
  • continueSendToAddressBtn sets the #recipient-address-validation-msg element (line 332)

Add an else branch here for consistency:

🐛 Proposed fix
   continueFundAddressBtn.addEventListener('click', () => {
     if (state.platformAddressPrivateKeyWif && state.platformAddress) {
       startFundAddress();
+    } else {
+      const msg = document.getElementById('platform-address-validation-msg');
+      if (msg) {
+        msg.textContent = 'Please enter and validate a platform address private key first.';
+        msg.classList.remove('hidden');
+      }
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 304 - 312, The click handler for
continueFundAddressBtn silently no-ops when state.platformAddressPrivateKeyWif
or state.platformAddress is falsy; add an else branch to provide user feedback
consistent with other gates (e.g., continueTopUpBtn). Update the event listener
on continueFundAddressBtn (the block that currently calls startFundAddress()) to
call the existing showValidationError(...) with a clear message when either
state.platformAddressPrivateKeyWif or state.platformAddress is missing (or
alternatively set a dedicated validation element like
`#platform-address-validation-msg`), so users see a validation error instead of
nothing happening.

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