Skip to content

fix(platform-wallet): report the exact send_payment fee (inputs − outputs)#4049

Open
QuantumExplorer wants to merge 5 commits into
v4.1-devfrom
feat/dashpay-payment-fee-and-faucet-client
Open

fix(platform-wallet): report the exact send_payment fee (inputs − outputs)#4049
QuantumExplorer wants to merge 5 commits into
v4.1-devfrom
feat/dashpay-payment-fee-and-faucet-client

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jul 9, 2026

Copy link
Copy Markdown
Member

Two commits used by dashwallet-ios (the DashPay contacts arc, dashwallet-ios #787), cherry-picked clean onto v4.1-dev:

  1. Fee threadingsend_payment returns the exact network fee of the broadcast transaction; platform_wallet_send_dashpay_payment gains a nullable out_fee_duffs out-param; Swift sendDashPayPayment returns (txid: Data, feeDuffs: UInt64). Lets wallet UIs show the real fee on the payment success screen instead of an estimate.

  2. Testnet faucet client promoted into the SDK packageTestnetFaucet + the cap.js proof-of-work solver move from SwiftExampleApp into Sources/SwiftDashSDK/Utils so dashwallet-ios can drive the faucet in-app without carrying a drifting copy (public API unchanged; the example app picks it up through its existing import). Includes the example-app call-site fix for the new sendDashPayPayment tuple return.

Verified: cargo check -p platform-wallet -p platform-wallet-ffi clean on this branch; both SwiftExampleApp and dashwallet-ios build against these commits (on the pre-rebase base); the fee display and the faucet request were exercised live on testnet (faucet txid 395eeb08…).

Not included (owned elsewhere): the tx-decode commit (PR #3981) and the RawKeySigner extraction (47a83cf6d0, Row #27's branch).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • DashPay payments now return the exact network fee alongside the transaction ID, making payment costs clearer after sending.
    • SDK integrations and example flows have been updated to handle and surface the fee value without changing the send experience.

@github-actions github-actions Bot added this to the v4.1.0 milestone Jul 9, 2026
@thepastaclaw

thepastaclaw commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit d45de7f)

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a broadcast transaction fee (duffs) return value to the DashPay payment send path, propagated from the Rust wallet's send_payment through the C FFI (out_fee_duffs) and into the Swift SDK's sendDashPayPayment, which now returns a tuple with txid and feeDuffs. The example app is updated to match. Additionally, TestnetFaucet and TestnetFaucetOutcome visibility is promoted to public.

Changes

DashPay fee propagation

Layer / File(s) Summary
Rust send_payment returns fee
packages/rs-platform-wallet/src/wallet/identity/network/payments.rs
send_payment now captures the fee from build_signed and returns (txid, entry, fee) instead of (txid, entry).
FFI exposes out_fee_duffs
packages/rs-platform-wallet-ffi/src/dashpay.rs
platform_wallet_send_dashpay_payment adds an optional out_fee_duffs: *mut u64 parameter and writes fee_duffs when non-null.
Swift ManagedPlatformWallet returns fee tuple
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift
sendDashPayPayment return type changes to (txid: Data, feeDuffs: UInt64), passing a feeDuffs output pointer into the FFI call.
Example app adapts to tuple return
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DashPay/SendDashPayPaymentSheet.swift
Destructures the awaited tuple into (txid, _), discarding the fee.

TestnetFaucet public API

Layer / File(s) Summary
Faucet visibility promotion
packages/swift-sdk/Sources/SwiftDashSDK/Utils/TestnetFaucet.swift
TestnetFaucetOutcome, TestnetFaucet, webURL, init(session:), and requestCoreDash(address:) are changed from internal to public, with updated header documentation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SendDashPayPaymentSheet
  participant ManagedPlatformWallet
  participant FFI
  participant RustWallet

  SendDashPayPaymentSheet->>ManagedPlatformWallet: sendDashPayPayment(...)
  ManagedPlatformWallet->>FFI: platform_wallet_send_dashpay_payment(out_txid, out_fee_duffs)
  FFI->>RustWallet: send_payment(...)
  RustWallet-->>FFI: (txid, entry, fee)
  FFI-->>ManagedPlatformWallet: writes out_txid, out_fee_duffs
  ManagedPlatformWallet-->>SendDashPayPaymentSheet: (txid, feeDuffs)
Loading

Possibly related PRs

  • dashpay/platform#3985: Both PRs modify the DashPay send_payment send-path in payments.rs, one adding fee_duffs to the return value and the other changing broadcast/retry reservation-releasing behavior.

Suggested reviewers: shumkov, llbartekll, ZocoLini, thepastaclaw

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: reporting the exact send_payment fee through the wallet payment path.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashpay-payment-fee-and-faucet-client

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
packages/rs-platform-wallet/src/wallet/identity/network/payments.rs (1)

539-542: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the # Returns doc comment to mention the fee.

The return type now includes u64 (network fee in duffs), but the doc comment still describes only the Txid and PaymentEntry. Callers reading the docs won't know about the third element.

📝 Proposed doc update
 /// # Returns
 ///
-/// The `Txid` of the broadcast transaction and the newly created
-/// [`PaymentEntry`] recording the outgoing payment.
+/// A tuple of the `Txid` of the broadcast transaction, the newly created
+/// [`PaymentEntry`] recording the outgoing payment, and the exact network
+/// fee in duffs (inputs − outputs) of the broadcast transaction.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet/src/wallet/identity/network/payments.rs` around
lines 539 - 542, The `# Returns` doc comment for the payment/broadcast function
is out of date and does not mention the `u64` network fee in duffs. Update the
return documentation near the function that returns the `Txid`, `PaymentEntry`,
and fee so it clearly describes all three tuple elements, including the fee
amount and its units.
packages/rs-platform-wallet-ffi/src/dashpay.rs (1)

538-547: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Document out_fee_duffs in # Safety
Add the nullable-out-pointer contract for out_fee_duffs: callers may pass NULL to ignore the fee, and a non-null pointer must reference valid u64 storage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet-ffi/src/dashpay.rs` around lines 538 - 547,
Update the safety contract for platform_wallet_send_dashpay_payment to
explicitly document out_fee_duffs as a nullable output pointer: callers may pass
NULL to ignore the fee, and any non-null value must point to valid writable u64
storage. Make sure the function’s # Safety section matches the existing
nullable-output conventions used by the other FFI parameters in dashpay.rs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/rs-platform-wallet-ffi/src/dashpay.rs`:
- Around line 538-547: Update the safety contract for
platform_wallet_send_dashpay_payment to explicitly document out_fee_duffs as a
nullable output pointer: callers may pass NULL to ignore the fee, and any
non-null value must point to valid writable u64 storage. Make sure the
function’s # Safety section matches the existing nullable-output conventions
used by the other FFI parameters in dashpay.rs.

In `@packages/rs-platform-wallet/src/wallet/identity/network/payments.rs`:
- Around line 539-542: The `# Returns` doc comment for the payment/broadcast
function is out of date and does not mention the `u64` network fee in duffs.
Update the return documentation near the function that returns the `Txid`,
`PaymentEntry`, and fee so it clearly describes all three tuple elements,
including the fee amount and its units.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7123145-bd8d-4051-b361-f70822ba32bb

📥 Commits

Reviewing files that changed from the base of the PR and between aae79a8 and 57003a9.

📒 Files selected for processing (5)
  • packages/rs-platform-wallet-ffi/src/dashpay.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/payments.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Utils/TestnetFaucet.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DashPay/SendDashPayPaymentSheet.swift

QuantumExplorer added a commit that referenced this pull request Jul 9, 2026
…fs contract

CodeRabbit review nits on #4049: send_payment's # Returns now covers
the third (fee) tuple element, and the FFI # Safety documents
out_fee_duffs as a nullable out-pointer written only on success.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Both CodeRabbit findings fixed in 9ca4736: send_payment's # Returns now documents the third (fee) tuple element, and the FFI # Safety documents out_fee_duffs as a nullable out-pointer (NULL to ignore; non-null must be valid writable u64 storage, written only on success — verified against the write site, which is only reached past unwrap_result_or_return!).

🤖 Addressed by Claude Code

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Source: reviewers: claude opus general (failed quota), codex gpt-5.5 general, claude opus security-auditor (failed quota), codex gpt-5.5 security-auditor, claude opus ffi-engineer (failed quota), codex gpt-5.5 ffi-engineer. Verifier: codex gpt-5.5 (Claude verifier failed quota).

Prior run at 57003a9 had no verified output, so there are no carried-forward findings. In the latest delta, the fee documentation/out-param contract is new and blocking because the returned value is still the builder's size-based fee, not the transaction's inputs-minus-outputs fee. Cumulatively, the promoted SDK faucet client has one in-scope hardening suggestion; the unrelated data-contract parser issue is real but outside PR #4049.

🔴 1 blocking | 🟡 1 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-platform-wallet/src/wallet/identity/network/payments.rs`:
- [BLOCKING] packages/rs-platform-wallet/src/wallet/identity/network/payments.rs:670-677: Return the actual transaction fee, not the builder's size fee
  `send_payment` now exposes this value through Rust, FFI, and Swift as the exact network fee of the broadcast transaction, but it forwards the second value from `TransactionBuilder::build_signed`. In the pinned `key-wallet` source, `build_signed` computes that value as `fee_rate.calculate_fee(encoded_size(tx))`; it does not compute `sum(selected inputs) - sum(tx outputs)`. These diverge when the builder omits a dust change output: the dust remainder is also paid to miners, while the returned value remains only the size-based fee. That makes the new public API under-report the fee in real sends and breaks the PR's stated contract.

In `packages/swift-sdk/Sources/SwiftDashSDK/Utils/TestnetFaucet.swift`:
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/Utils/TestnetFaucet.swift:264-266: Cap faucet proof-of-work by total expected work
  This PR promotes `TestnetFaucet.requestCoreDash` into the SDK as reusable public API, but the captcha bounds still allow a same-domain or compromised faucet endpoint to return `c=256,d=6`. That is roughly 4.3 billion expected SHA-256 attempts before the 30-second timeout cancels the solver, pinning device CPU for each user-initiated faucet attempt. Bound the aggregate challenge cost, not just each field independently.

Comment thread packages/rs-platform-wallet/src/wallet/identity/network/payments.rs Outdated
QuantumExplorer added a commit that referenced this pull request Jul 9, 2026
…e size fee

Review finding on #4049: build_signed's returned fee is size-based
(fee_rate x encoded_size) and under-reports whenever the builder drops
a dust change remainder (<= 546 duffs) to miners instead of emitting
the change output. send_payment now computes the fee it exposes as
sum(selected input values) - sum(output values), reading input values
from the account UTXO map (build reserves selected UTXOs but does not
remove them). A selected input missing from the map is a hard error,
never a fabricated fee.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Jul 9, 2026
Review hardening on #4049: the per-field bounds still admitted
c=256, d=6 (~4.3B expected SHA-256 attempts) from a compromised or
misbehaving faucet endpoint — enough to pin the device CPU until the
30 s timeout on every faucet tap. Bound the aggregate expected work
(c x 16^d) at 64M attempts (~10x the live faucet's soft challenge);
costlier challenges throw and route to the web-faucet fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Both findings from the changes-requested review are addressed:

  • [BLOCKING] size-based fee — this run reviewed the pre-fix delta; it was already fixed in a810120d67 (same finding as the earlier inline thread): send_payment computes the exposed fee as Σ(selected input values) − Σ(output values) from the account UTXO map, so a dropped dust change remainder is included. 29/29 payments-module tests pass.
  • [SUGGESTION] aggregate PoW cap — fixed in eafbfda4ab: the faucet client now bounds the aggregate expected work (c × 16^d ≤ 64M attempts, ~10× the live faucet's soft challenge) in addition to the per-field bounds, so a compromised/misbehaving endpoint can't pin the CPU until the 30 s timeout; costlier challenges throw and route to the web-faucet fallback.

🤖 Addressed by Claude Code

shumkov
shumkov previously approved these changes Jul 10, 2026
@shumkov shumkov requested a review from thepastaclaw July 10, 2026 06:30

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed current head eafbfda. Both prior findings are fixed: send_payment now reports inputs minus outputs, including dropped dust change, and the faucet caps aggregate expected proof-of-work at 64M attempts. Wallet and Swift CI pass. No additional issues found.

QuantumExplorer added a commit that referenced this pull request Jul 11, 2026
…fs contract

CodeRabbit review nits on #4049: send_payment's # Returns now covers
the third (fee) tuple element, and the FFI # Safety documents
out_fee_duffs as a nullable out-pointer written only on success.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Jul 11, 2026
…e size fee

Review finding on #4049: build_signed's returned fee is size-based
(fee_rate x encoded_size) and under-reports whenever the builder drops
a dust change remainder (<= 546 duffs) to miners instead of emitting
the change output. send_payment now computes the fee it exposes as
sum(selected input values) - sum(output values), reading input values
from the account UTXO map (build reserves selected UTXOs but does not
remove them). A selected input missing from the map is a hard error,
never a fabricated fee.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Jul 11, 2026
Review hardening on #4049: the per-field bounds still admitted
c=256, d=6 (~4.3B expected SHA-256 attempts) from a compromised or
misbehaving faucet endpoint — enough to pin the device CPU until the
30 s timeout on every faucet tap. Bound the aggregate expected work
(c x 16^d) at 64M attempts (~10x the live faucet's soft challenge);
costlier challenges throw and route to the web-faucet fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer QuantumExplorer force-pushed the feat/dashpay-payment-fee-and-faucet-client branch from eafbfda to 942b51d Compare July 11, 2026 22:21
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Rebased onto current v4.1-dev to clear the conflicts. The original fee-threading commit was dropped by the rebase as already upstream (its contents were merged into v4.1-dev independently); the four surviving commits still apply cleanly on top: the faucet-client promotion, the fee doc fixes, the actual-fee correctness fix (upstream's copy still returned the builder's size-based fee — the Σinputs−Σoutputs fix re-applied on top of it), and the PoW aggregate cap. cargo check clean and 29/29 payments-module tests pass on the new base.

🤖 Addressed by Claude Code

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Source: reviewers: Sol gpt-5.6-sol general, Sonnet claude-sonnet-5 general, Opus claude-opus-4-8 general. Verifier: Sonnet claude-sonnet-5. Orchestrator: openai/gpt-5.6-sol (orchestration-only, not reviewer evidence).

Prior reconciliation: both prior findings are FIXED at 942b51d. The payment path now computes the exact fee as inputs minus outputs, and the faucet caps aggregate expected PoW at 64,000,000 attempts. Carried-forward prior findings: none.

New findings in the latest cumulative head: add a successful send_payment regression test that exercises the dust/no-change case and asserts the exact returned fee. Also update dashpay.rs:599-601: the comment still says the fee comes straight from the builder, but this PR intentionally replaces the builder size-fee with an inputs-minus-outputs computation.

🟡 1 suggestion(s) | 💬 1 nitpick(s)

1 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-platform-wallet/src/wallet/identity/network/payments.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/identity/network/payments.rs:677-709: No test exercises a successful send_payment fee computation
  This block is the fix for the bug this PR addresses (returning the real inputs-minus-outputs fee instead of the builder's size-based estimate), but neither existing test reaches it successfully. `send_payment_runs_pending_contact_crypto_drain` (line 3259) pays a contact with no external account and fails before `build_signed`; `send_payment_passes_external_lookup_once_account_built` (line 3353) registers the external account but still asserts an `err` result at line 3419, so it also never reaches this code. Add a test with a funded test wallet that completes a real `send_payment` call, ideally hitting the dust-change-omission case called out in the comments at lines 677-681, and assert the returned fee equals `Σinputs − Σoutputs` and can exceed the builder's size-based fee. Without this, the exact behavior just fixed here can regress silently.

Comment thread packages/rs-platform-wallet/src/wallet/identity/network/payments.rs

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Source: reviewers: Sol gpt-5.6-sol general, Sonnet claude-sonnet-5 general. Verifier: Sonnet claude-sonnet-5. Orchestrator: openai/gpt-5.6-sol (orchestration-only, not reviewer evidence).

Prior reconciliation: both prior findings are FIXED. The new tests complete a real build+sign+broadcast path and assert Σinputs−Σoutputs for both dropped-dust and emitted-change cases; the FFI comment now correctly describes that computation instead of the builder estimate. Carried-forward prior findings: none.

New findings in the latest delta: none.

New cumulative finding: ManagedPlatformWallet.swift:2105, introduced earlier in this PR, still documents the returned fee as coming “straight from the builder.” That is stale because send_payment deliberately discards the builder’s size-based estimate and returns selected inputs minus outputs, including dropped dust. Full platform-wallet + platform-wallet-ffi lib suites pass (140 tests), the two targeted fee tests pass, and clippy is clean.

💬 1 nitpick(s)

1 additional finding(s) omitted (not in diff).

@QuantumExplorer

Copy link
Copy Markdown
Member Author

Nitpick fixed in 6210d2dManagedPlatformWallet.sendDashPayPayment's doc no longer says the fee comes "straight from the builder"; it now states the fee is computed Rust-side as Σ(selected input values) − Σ(output values), so folded sub-dust change is reflected rather than the builder's size estimate. This was the last of the three stale "straight from the builder" comments (Rust payments.rs, FFI dashpay.rs, and now the Swift wrapper). Comment-only change.

🤖 Addressed by Claude Code

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Source: reviewers: Sol gpt-5.6-sol general/security-auditor/ffi-engineer; Sonnet claude-sonnet-5 general/security-auditor/ffi-engineer. Verifier: Sonnet claude-sonnet-5. Orchestrator: openai/gpt-5.6-sol (orchestration-only, not reviewer evidence).

Prior reconciliation: prior-swift-builder-doc is STILL VALID. PR #4049's own fee-computation commit changes the returned value away from the builder estimate to selected inputs minus outputs, so it directly makes the unchanged public Swift sentence false.

Carried-forward prior findings: one documentation nitpick:

  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift:2105Public Swift API doc still attributes the exact fee to the builder. sendDashPayPayment says feeDuffs comes “straight from the builder,” but #4049 deliberately discards that size-based estimate and computes the fee as selected inputs minus transaction outputs, including dropped dust change. The Rust FFI comment was corrected in this PR; the public Swift comment should likewise read: (duffs) computed as inputs minus outputs, including any dropped dust change.

New findings in the latest delta: none. The delta is rustfmt-only reformatting of two fee tests, and cargo fmt -p platform-wallet -- --check passes.

Additional cumulative findings: none.

💬 1 nitpick

@QuantumExplorer

Copy link
Copy Markdown
Member Author

This carried-forward nitpick is already fixed on the branch tip — this review round ran against c43ecd29e2 (the rustfmt-only delta), one commit before the fix landed. At the current tip 6210d2d915, ManagedPlatformWallet.sendDashPayPayment's doc (line ~2106) reads "computed as Σ(selected input values) − Σ(output values) … not the builder's size-based estimate" — no "straight from the builder" text remains in the file. Re-review of the tip should show it clean.

🤖 Addressed by Claude Code

QuantumExplorer and others added 2 commits July 12, 2026 16:49
…fs contract

CodeRabbit review nits on #4049: send_payment's # Returns now covers
the third (fee) tuple element, and the FFI # Safety documents
out_fee_duffs as a nullable out-pointer written only on success.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e size fee

Review finding on #4049: build_signed's returned fee is size-based
(fee_rate x encoded_size) and under-reports whenever the builder drops
a dust change remainder (<= 546 duffs) to miners instead of emitting
the change output. send_payment now computes the fee it exposes as
sum(selected input values) - sum(output values), reading input values
from the account UTXO map (build reserves selected UTXOs but does not
remove them). A selected input missing from the map is a hard error,
never a fabricated fee.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Jul 12, 2026
Review hardening on #4049: the per-field bounds still admitted
c=256, d=6 (~4.3B expected SHA-256 attempts) from a compromised or
misbehaving faucet endpoint — enough to pin the device CPU until the
30 s timeout on every faucet tap. Bound the aggregate expected work
(c x 16^d) at 64M attempts (~10x the live faucet's soft challenge);
costlier challenges throw and route to the web-faucet fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer QuantumExplorer force-pushed the feat/dashpay-payment-fee-and-faucet-client branch from 6210d2d to 9505cbd Compare July 12, 2026 09:53
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Rebased onto current v4.1-dev again to clear the conflicts. The faucet-client promotion commit dropped as already upstream (it landed on v4.1-dev independently) — same collapse pattern as the earlier fee-threading commit. The six substantive commits survive intact: fee doc + out_fee_duffs contract, the actual-fee (Σin−Σout) correctness fix, the PoW aggregate cap, the two end-to-end fee tests, the rustfmt pass, and the Swift doc correction. cargo fmt --check clean and 31/31 payments-module tests pass on the new base.

🤖 Addressed by Claude Code

QuantumExplorer and others added 3 commits July 12, 2026 17:05
…op case

Two full end-to-end `send_payment` tests reach the fee block for the
first time (no existing test did): they fund BIP-44 account 0 with a
known UTXO, register the external contact account, sign with the seed
signer, and broadcast through an accepting stub broadcaster injected via
the `IdentityWallet<B>` broadcaster generic (production `identity()`
pins `SpvBroadcaster`, whose runtime is never started in tests). Build,
sign, and the Σin − Σout fee computation all run for real.

- dust case: change ≤ 546 is dropped, so the tx has a single output and
  the reported fee is the full V − A, folding the dropped dust into the
  fee (the bug this PR fixed).
- control: change > 546 is emitted, so Σin − Σout equals the plain
  size-based fee, proving the computation isn't blindly inflating sends.

Also reword the stale rs-platform-wallet-ffi/dashpay.rs comment that
claimed the fee comes "straight from the builder" — it is now computed
in send_payment as Σ(inputs) − Σ(outputs).

Co-Authored-By: Claude Opus <noreply@anthropic.com>
The Wallet-tests CI job runs cargo fmt --check; the hand-written test
fixtures in 6618881 weren't rustfmt-clean. No logic change.

Co-Authored-By: Claude Opus <noreply@anthropic.com>
…t from the builder')

sendDashPayPayment's doc still said the fee comes straight from the
builder; send_payment now returns Σinputs − Σoutputs (dropped dust
folded in). Comment-only.

Co-Authored-By: Claude Opus <noreply@anthropic.com>
@QuantumExplorer QuantumExplorer force-pushed the feat/dashpay-payment-fee-and-faucet-client branch from 9505cbd to d45de7f Compare July 12, 2026 10:08
@QuantumExplorer QuantumExplorer changed the title feat(platform-wallet): exact network fee from send_payment + shared testnet faucet client fix(platform-wallet): report the exact send_payment fee (inputs − outputs) Jul 12, 2026
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Narrowed to the fee-correctness fix only. Per reviewer discussion, dropped the faucet captcha PoW-cap commit — the TestnetFaucet client already merged separately (#4098), and its hardening doesn't belong bundled with the fee work. This PR is now purely: send_payment returns the exact fee (Σ selected inputs − Σ outputs, folding dropped sub-dust change) instead of the builder's size-based estimate, the FFI out_fee_duffs contract + doc, the Swift wrapper doc, and two end-to-end regression tests (dust-drop + emitted-change). No Utils/TestnetFaucet.swift change remains; 31/31 payments-module tests pass.

The captcha PoW cap is a real (minor) hardening on the now-upstream faucet — it'll come back separately alongside the faucet's placement review, not here.

🤖 Addressed by Claude Code

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Source: reviewers: Sol gpt-5.6-sol general; Sonnet claude-sonnet-5 general. Verifier: Sonnet claude-sonnet-5. Orchestrator: openai/gpt-5.6-sol (orchestration-only, not reviewer evidence).

Prior reconciliation: the immediately prior review at 9505cbdd had zero verified findings.

Carried-forward prior findings: none.

New findings in the latest delta: one suggestion. The force-push dropped the aggregate captcha proof-of-work cap added as review hardening for this PR; the remaining independent bounds admit roughly 4.3 billion expected SHA-256 attempts and can saturate device CPUs until the 30-second timeout.

Additional cumulative findings: none. The exact-fee implementation, FFI/Swift threading, documentation, and dust-change/control tests are consistent. cargo test -p platform-wallet --lib send_payment_reports, cargo fmt -p platform-wallet -- --check, and cargo clippy -p platform-wallet -p platform-wallet-ffi --all-targets all passed at this head.

🟡 1 suggestion(s)

1 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/Utils/TestnetFaucet.swift`:
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/Utils/TestnetFaucet.swift:266-267: Restore the aggregate captcha proof-of-work cap dropped in the latest push
  This exact cap existed at the immediately-prior-reviewed head of this PR (9505cbdd) and was added there specifically as 'Review hardening on #4049' (commit 942b51d0fc/eafbfda4ab). The latest force-push dropped it, and it is not merged anywhere else in the tree (confirmed absent from origin/v4.1-dev). The remaining per-field bounds alone still admit c=256, d=6 (~4.3B expected SHA-256 attempts), so any misbehaving/compromised same-domain faucet endpoint pins the device's cores across 256 parallel solver tasks until the 30-second timeout cancels them — an avoidable CPU/battery spike the file's own surrounding comments say should route to the web-faucet fallback instead. Re-add the aggregate bound so only the documented soft-challenge workload (~6.6M expected attempts) reaches the solver.

@QuantumExplorer

Copy link
Copy Markdown
Member Author

Re the dropped PoW cap: that was a deliberate scope decision by the repo owner, not force-push loss — this PR was narrowed to the send_payment fee fix only (see the narrowing comment above), and the faucet-client hardening was parked for a standalone change since TestnetFaucet merged separately in #4098. It's now up as #4100 (same hunk, cherry-picked onto current v4.1-dev), so upstream gets the protection without re-broadening this PR. No change here.

🤖 Addressed by Claude Code

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.

3 participants