feat: add send to platform address mode#10
feat: add send to platform address mode#10thepastaclaw wants to merge 10 commits intoPastaPastaPasta:mainfrom
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/ui/state.ts (1)
81-103: Clear other mode-specific fields when switching modes.
fund_addressclears its own fields but keepsrecipientPlatformAddress, andsend_to_addresskeepsplatformAddressPrivateKeyWif/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.
thephez
left a comment
There was a problem hiding this comment.
- The UI for the new screens do not match the style of all other screens.
- 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.
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>
28cca40 to
9adf0b3
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/main.ts (2)
1234-1235:setInstantLockReceivedhardcodesstep: 'registering_identity', causing a brief incorrect step flash infund_addressandsend_to_addressmodes.
setInstantLockReceivedalways setsstep: '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 skipsetInstantLockReceivedfor non-create modes and just storeinstantLockBytes/assetLockProofdirectly. Alternatively, the step-setting can be omitted fromsetInstantLockReceivedfor 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 acrossstartFundAddress,startSendToAddress,startTopUp, andstartBridge.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
elseblock at line 983 now handlesfund_addressandsend_to_addressas well astopup. Both the leading comment and thenotestring 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 staleplatformAddressPrivateKeyWif/platformAddressfrom a priorfund_addresssession.If a user starts
fund_address, enters a key (which populatesplatformAddressPrivateKeyWifandplatformAddress), navigates back, then selectssend_to_address, both fields persist in state. They are not consumed by thesend_to_addressflow, 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.
|
@thephez I pushed a review-fix pass for your feedback + CodeRabbit comments. Included in latest commits:
What changed:
If you see anything else off in the new screens, I’ll patch it immediately. |
There was a problem hiding this comment.
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.
thephez
left a comment
There was a problem hiding this comment.
@thepastaclaw the HRP in DIP-18 is not evo/tevo
The platform address prefix was incorrectly using evo1/tevo1 in UI text and validation messages. DIP-18 specifies the HRP should be 'dash' for mainnet and 'tdash' for testnet.
|
Addressed review feedback from @thephez and CodeRabbit: HRP fix (DIP-18):
UI styling alignment:
XSS hardening:
State management:
UX improvements:
Note: Pre-existing TypeScript errors in |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/ui/state.ts (1)
204-232:setFundAddressCompleteandsetSendToAddressCompleteare identical.Both functions return
{ ...state, step: 'complete' }. They can be collapsed into the existingsetStep(state, 'complete')call pattern used elsewhere (e.g.,setTopUpCompletediffers only in also settingidentityId, 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.tsandsrc/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, andstartTopUpeach 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
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/ui/state.ts (2)
380-391:setInstantLockReceivedhardcodes'registering_identity'— footgun for new modes.The new
fund_address/send_to_addressflows correctly work around this by using a direct state spread inmain.tsinstead of callingsetInstantLockReceived. 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:setFundAddressCompleteandsetSendToAddressCompleteare 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:startFundAddressandstartSendToAddressduplicate 90% ofstartTopUp.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 betweenfundPlatformAddressandsendToPlatformAddress.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.
| // Continue fund address button | ||
| const continueFundAddressBtn = container.querySelector('#continue-fund-address-btn'); | ||
| if (continueFundAddressBtn) { | ||
| continueFundAddressBtn.addEventListener('click', () => { | ||
| if (state.platformAddressPrivateKeyWif && state.platformAddress) { | ||
| startFundAddress(); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
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:
continueTopUpBtncallsshowValidationError(...)(line 250)continueSendToAddressBtnsets the#recipient-address-validation-msgelement (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.


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:
send_to_addressmode,enter_recipient_address/sending_to_addresssteps intypes.tsstate.tscomponents.tssendToPlatformAddress()usingsdk.addresses.fundFromAssetLock()inidentity.tsmain.tsBug fix (bonus):
recheckDeposit()always ran the identity registration path regardless of mode — fixed to branch onstate.mode.PlatformAddressSigneris 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
npm run buildcompletes without errorsBridgeModeadditions,BridgeStepadditions, new state fields) are consistent acrosstypes.ts,state.ts,components.ts,main.ts, andidentity.tsenter_recipient_addressstep with bech32m input fieldtdash1for testnet,dash1for mainnet)dash-send-to-address-*-recovery.json)recheckDeposit()fix verified: Mode branching logic correctly routesfund_addressandsend_to_addressmodes to their respective handlers instead of always falling through to identity registrationfundPlatformAddress()clearModeSensitiveFields()properly clears cross-mode state when switching between modes (e.g.,recipientPlatformAddresscleared when switching tofund_addressmode and vice versa)Summary by CodeRabbit
New Features
UX Improvements