Skip to content

Conversation

@CharlVS
Copy link
Collaborator

@CharlVS CharlVS commented Oct 31, 2025

Overview

Refactored SDK withdrawals to enforce a secure preview-then-execute workflow, ensuring transactions are signed only once and users always review what they're signing.

Problem

Previously, when a user previewed a withdrawal and then executed it, the transaction was being signed twice:

  1. Preview Phase: Transaction was signed and created
  2. Execute Phase: A NEW transaction was signed and created (discarding the preview)

This applied to both:

  • Task-based API (non-Tendermint assets): Called init() twice
  • Legacy API (Tendermint assets): Called withdraw() RPC twice

Solution

Enforced a secure two-step withdrawal workflow:

  1. Preview - Signs the transaction and shows details to user
  2. Execute - Broadcasts the already-signed transaction

Changes Made

1. WithdrawalManager (Task-based API)

  • Added executeWithdrawal(WithdrawalPreview preview, String assetId) method

    • Broadcasts the pre-signed transaction from the preview
    • Avoids calling init() again, preventing duplicate signing
    • This is the ONLY recommended way to execute withdrawals
  • Deprecated withdraw(WithdrawParameters) method

    • Still works but not recommended
    • Signs and broadcasts immediately without user review
    • Clearly documented as deprecated with migration path

2. LegacyWithdrawalManager (Tendermint Assets)

  • Added executeWithdrawal(WithdrawalPreview preview, String assetId) method

    • Broadcasts the txHex from the preview
    • Avoids calling withdraw() RPC again, preventing duplicate signing
  • Deprecated withdraw(WithdrawParameters) method

    • Matches the same pattern as the main WithdrawalManager

3. Example Implementation

  • Updated example app to use executeWithdrawal() when a preview is available
  • Demonstrates the recommended pattern for preview-then-execute workflow

Usage Pattern

Old (Deprecated) - Signs Transaction Twice ❌

// Preview
final preview = await manager.previewWithdrawal(params);

// Execute - SIGNS AGAIN!
await for (final progress in manager.withdraw(params)) {
  // Handle progress
}

New (Recommended) - Signs Transaction Once ✅

// 1. Preview - Signs and shows to user
final preview = await manager.previewWithdrawal(params);

// 2. Show preview to user, get confirmation...

// 3. Execute - Broadcasts the already-signed transaction
await for (final progress in manager.executeWithdrawal(preview, assetId)) {
  // Handle progress
}

Benefits

  1. Security: User ALWAYS sees and confirms the EXACT transaction before broadcast
  2. No Duplicate Signing: Transaction is signed only once, saving resources
  3. Hardware Wallet Friendly: Only one hardware wallet interaction per withdrawal
  4. Better UX: Users can review fees, amounts, and addresses before confirming
  5. Consistency: Impossible for parameters to change between preview and execution
  6. Clear API: Single, unambiguous way to perform withdrawals

Breaking Changes

  • withdraw() is now deprecated (still works, but not recommended)
  • New recommended flow: previewWithdrawal()executeWithdrawal()

Migration Guide

Before:

await for (final progress in manager.withdraw(params)) {
  // Handle progress
}

After:

// 1. Preview
final preview = await manager.previewWithdrawal(params);

// 2. Show preview UI...

// 3. Execute after user confirmation
await for (final progress in manager.executeWithdrawal(preview, assetId)) {
  // Handle progress
}

Testing

  • ✅ No linter errors
  • ✅ Example app updated to demonstrate new workflow
  • ✅ Both task-based and legacy implementations follow same pattern

Files Modified

  • packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart
  • packages/komodo_defi_sdk/lib/src/withdrawals/legacy_withdrawal_manager.dart
  • packages/komodo_defi_sdk/example/lib/screens/withdrawal_page.dart

Note

Introduces executeWithdrawal to broadcast pre-signed previews, deprecates withdraw, and updates the example UI to use the preview-then-execute workflow.

  • SDK (core):
    • New executeWithdrawal(WithdrawalPreview, String) in lib/src/withdrawals/withdrawal_manager.dart to broadcast pre-signed transactions; ensures asset activation; delegates Tendermint to legacy; streams progress.
    • Deprecated withdraw(WithdrawParameters) with migration docs to preview→execute.
  • SDK (legacy/Tendermint):
    • New executeWithdrawal(WithdrawalPreview, String) in lib/src/withdrawals/legacy_withdrawal_manager.dart to send raw tx from preview; streams progress.
    • Deprecated withdraw(WithdrawParameters) with guidance to use preview→execute.
  • Example app:
    • Updates example/lib/screens/withdrawal_page.dart to call executeWithdrawal when a WithdrawalPreview exists; minor UI/formatting tweaks in preview dialog, fee priority handling, and validation UI.

Written by Cursor Bugbot for commit b2be41b. This will update automatically on new commits. Configure here.

…nt duplicate signing

- Add executeWithdrawal() method to broadcast pre-signed transactions
- Deprecate withdraw() method in favor of two-step preview-execute flow
- Update example app to use new executeWithdrawal() method
- Prevents transactions from being signed multiple times
- Ensures users always review transaction details before broadcast

Breaking Changes:
- withdraw() is now deprecated (still functional but not recommended)
- Recommended flow: previewWithdrawal() → executeWithdrawal()
Copilot AI review requested due to automatic review settings October 31, 2025 14:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/avoid-multi-signing-withdraws

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

❤️ Share

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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a new two-step withdrawal workflow to ensure users review transaction details before broadcasting. The key improvement is separating transaction preview from execution, allowing users to confirm fees and amounts before the transaction is broadcast.

  • Adds a new executeWithdrawal() method that broadcasts pre-signed transactions from previews
  • Deprecates the direct withdraw() method in favor of the preview-then-execute workflow
  • Updates the example app to use the new two-step workflow

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
withdrawal_manager.dart Adds executeWithdrawal() method and deprecates withdraw() with updated documentation explaining the recommended two-step workflow
legacy_withdrawal_manager.dart Implements executeWithdrawal() for Tendermint assets and deprecates withdraw()
withdrawal_page.dart Updates example to conditionally use the new executeWithdrawal() method when a preview exists, along with code formatting improvements

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

Comment on lines +220 to +223
// Execute the previewed withdrawal (transaction already signed)
final progressStream = _preview != null
? _sdk.withdrawals.executeWithdrawal(_preview!, params.asset)
: _sdk.withdrawals.withdraw(params);
Copy link

Copilot AI Oct 31, 2025

Choose a reason for hiding this comment

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

The fallback to withdraw() when _preview is null creates an inconsistent code path. Since _executeWithdrawal() is only called after the preview dialog (which requires _preview to be set), the null check and fallback are unnecessary. Consider either asserting _preview is non-null or restructuring the logic to ensure preview always exists before execution.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@cursoragent apply changes based on this feedback

Copy link

@cursor cursor bot Nov 2, 2025

Choose a reason for hiding this comment

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

Perfect! I've applied the feedback by:

  1. Added an assertion that _preview must be non-null before execution
  2. Removed the fallback to withdraw() method
  3. Simplified the code to directly call executeWithdrawal() with the preview

This enforces the preview-then-execute workflow more strictly and removes the inconsistent code path. Since _executeWithdrawal() is only called from the preview dialog's confirm button (line 209), and the dialog is only shown after _preview is set (line 166), the assertion will help catch any logic errors while making the code clearer.


Open in Cursor Open in Web

Learn more about Cursor Agents

@github-actions
Copy link
Contributor

github-actions bot commented Oct 31, 2025

Visit the preview URL for this PR (updated for commit b2be41b):

https://kdf-sdk--pr276-fix-avoid-multi-sign-itktpglj.web.app

(expires Sun, 09 Nov 2025 14:21:26 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 9c1b6e6c010cf0b965c455ba7a69c4aedafa8a1d

@github-actions
Copy link
Contributor

github-actions bot commented Oct 31, 2025

Visit the preview URL for this PR (updated for commit b2be41b):

https://komodo-playground--pr276-fix-avoid-multi-sign-ehuqsjr3.web.app

(expires Sun, 09 Nov 2025 14:22:31 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 2bfedd77fdea45b25ba7c784416e81f177aa5c47

@cursor
Copy link

cursor bot commented Nov 2, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

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