Skip to content

feat(documents): agree to document action fees before broadcasting - #1122

Draft
Claudius-Maginificent wants to merge 14 commits into
fix/protocol14-signing-key-selectionfrom
feat/document-action-fees
Draft

Claudius-Maginificent wants to merge 14 commits into
fix/protocol14-signing-key-selectionfrom
feat/document-action-fees

Conversation

@Claudius-Maginificent

@Claudius-Maginificent Claudius-Maginificent commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

TL;DR: On networks running protocol 14, a contract can charge a fee for document actions. The app shows that fee, asks the user to agree to it, and sends exactly the action that was confirmed. When the network's fee multiplier rises past the agreed tolerance, the app updates the fee from a proved epoch, so the next try shows the new amount.

Stacked atop #1117.

User story

As a user acting on a document whose contract charges a fee, I want to see that fee and agree to it before anything is sent, so that I am never charged more than I accepted (DOC-010).

Scenario

Base flow

  1. Connect to a protocol 14 network. Open a document action, for example deleting a document of a contract whose delete charges an action fee priced by the fee multiplier.
  2. Look at the estimated-fee panel, then send the action and confirm.
  3. Repeat after network fees have risen past the agreed tolerance, or after the contract changed its fee.

Actual

The app did not show contract action fees and sent no agreement to them, so the network refused every action of a contract that charges one.

Expected

  • The estimated-fee panel shows the contract fee next to the network fee, split between the contract owner and its moderators.
  • Sending opens a confirmation naming the fee, the most that can be charged ("up to N DASH"), and the tolerated increase (20%). A fixed fee says it is fixed.
  • Confirming sends exactly the action the app built when the dialog opened, with that fee agreement, even if the form behind the dialog changed meanwhile.
  • If network fees rose past the tolerance, the network refuses the action. The app reads the epoch's multiplier from a proved response, and trying again shows the new amount to confirm.
  • If the contract changed its fee after confirmation, nothing is sent, and the user is told to remove the contract on the Contracts screen and load it again to see and confirm its current fee.

Detailed discussion

  • Quote (src/model/fee_estimation.rs):
    • document_action_fee_quote builds a DocumentActionFeeQuote from the document type's declared actionFees. The quote holds the owner and moderator parts at the multiplier the user saw, the total, and the most it can become (max_total).
    • Its agreement carries the declared amounts and, for a multiplier-priced fee, the multiplier the user saw plus ACTION_FEE_MULTIPLIER_INCREASE_TOLERANCE_PERCENT (20%).
    • A tiny multiplier-priced fee is never called fixed, even when rounding hides its tolerance.
  • Agreement (src/model/fee_estimation.rs, src/backend_task/document.rs):
    • The app never agrees to a contract fee on the user's behalf.
    • checked_action_fee_agreement refuses an action whose agreement is missing, or names a fee other than the one the document type declares, before anything is signed (TaskError::ActionFeeAgreement).
    • The backend adds the agreement per transition, after the signing options, from the contract the user was shown.
  • Confirmation (src/ui/contracts_documents/document_action_screen.rs): the action is built when the confirmation opens and stored with it (PendingActionFeeConfirmation), so confirming sends that exact action.
  • Risen multiplier: a DocumentActionFeeMultiplierNotTolerated refusal triggers a proved epoch refresh through chore(deps)!: update Platform to v5.1-dev 063589bf and follow the network's protocol version #1114's refresh_current_epoch.
    • If the refresh succeeds, the refusal is reported as is, and trying again quotes the current fee.
    • If it fails, the user is told the contract fee could not be updated (DocumentActionFeeMultiplierNotRefreshed), rather than that it was.
    • The fee multiplier is never taken from an unproven source, because agreements are signed against it.
  • Errors (src/backend_task/error.rs):
    • DocumentActionFeeNotAgreed and DocumentActionFeeChanged (the network's refusals) show the same text as the app's own ActionFeeAgreementError::NotConfirmed and Outdated. Either way the app's copy of the contract may be out of date, and loading a contract that is already stored keeps the stored copy, so each tells the user to remove the contract on the Contracts screen and load it again.
    • DocumentActionFeeMultiplierRose names the known and current multipliers.
    • DocumentActionFeeMultiplierNotRefreshed covers a multiplier that could not be refreshed.
    • Each keeps the SDK error as its source. Moderation and gas-sponsorship refusals come from chore(deps)!: update Platform to v5.1-dev 063589bf and follow the network's protocol version #1114 and are not repeated here.
  • Docs: user story DOC-010.
  • Scope: skipping an optional token cost is in its own PR, feat/document-optional-token-cost.

Testing

  • New unit tests:
    • model::fee_estimation: quoting fixed and multiplier-priced fees (a_fixed_fee_is_charged_as_declared, a_multiplier_priced_fee_scales_and_names_the_tolerance, and others); refusing a missing or mismatched agreement (a_charged_fee_without_an_agreement_is_refused, an_agreement_to_another_fee_is_outdated).
    • ui::contracts_documents::document_action_screen::tests:
      • confirming_a_contract_fee_sends_the_action_it_was_shown_for;
      • the confirmation names the ceiling and tolerance, or promises no increase for a fixed fee;
      • a tiny multiplier-priced fee is not called fixed;
      • moderator-only and split fees are named;
      • every_action_maps_to_its_document_transition.
    • backend_task::document: a_fee_refusal_stands_once_the_epoch_is_refreshed and a_failed_epoch_refresh_says_the_fee_was_not_updated.
    • backend_task::error: the refusal mappings in protocol_v14_consensus_errors_map_to_their_variants, and fee_agreement_refusals_read_the_same_locally_and_from_the_network.
  • Library tests for the touched modules: 224 passed.
  • cargo clippy --all-features --lib --tests -- -D warnings and cargo fmt --all pass.
  • No live network or GUI flows were run.

🤖 Co-authored by Claudius the Magnificent AI Agent

🤖 Generated with Claude Code

https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q

lklimek and others added 4 commits October 24, 2026 20:09
On protocol 14 a document type can charge an action fee. A fee quote
holds the declared owner and moderator parts at the fee multiplier the
user saw and the most it can become; its agreement tolerates a 20%
multiplier rise for a multiplier-priced fee. The backend never agrees on
the user's behalf: an action whose agreement is missing or names another
fee is refused before signing.

A refusal for a risen multiplier refreshes the multiplier from a proved
epoch, so trying again quotes the current fee; if that refresh fails the
user is told the fee could not be updated. A protocol 13 guard checks
that a fee agreement is refused locally there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Map a missing or mismatched fee agreement, a changed contract fee and a
multiplier risen past the tolerance to dedicated TaskError variants,
plus one for a multiplier that could not be refreshed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
… action

The estimated-fee panel shows the contract fee split between owner and
moderators. Sending asks for confirmation naming the fee, the ceiling and
the tolerance (or that the fee is fixed); confirming sends the action
built when the dialog opened. An optional token cost can be skipped, the
gas then being paid in credits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
@coderabbitai

coderabbitai Bot commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@thepastaclaw

thepastaclaw commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Review not started yet because this PR is a draft.

  • Request normal review — click when the PR is ready for review.
  • Request priority review — click to move this review to the front of the queue.

Commit 5f34e34. Normal review starts when eligible; priority review starts as soon as a slot is available.

lklimek and others added 3 commits October 25, 2026 10:55
The optional token cost (the "Pay the token cost" option, DOC-011) moves
to its own branch, so every document action builds its token payment as
before and this branch only adds the contract action fee agreement.

The network's refusals of a missing or changed fee agreement now show
the same text as the app's own check, which tells the user to reload the
contract first, since either way the local copy may be out of date. The
fee estimation imports sit in the file's top import block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
…ment action fee branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Loading a contract that is already stored keeps the stored copy, so the
fee agreement refusals and DOC-010 now tell the user to remove the
contract on the Contracts screen and load it again, an action the app
offers, instead of an unnamed reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
lklimek and others added 2 commits October 26, 2026 11:21
Conflicts: platform_info.rs doc comment (kept base wording);
protocol_13_transitions.rs deleted upstream, deletion kept. This PR's
test there (a_fee_agreement_is_refused_on_protocol_13) exercised the
SDK's protocol-13 encoding guard, not DET logic, so it is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
@Claudius-Maginificent

Copy link
Copy Markdown
Collaborator Author

Next steps: the updated #1117 head is merged into this branch locally and verified (fmt, clippy, area tests); it will be pushed right after #1117. Once #1117 merges, this PR gets retargeted to v1.0-dev and goes through the normal review gate.

🤖 Co-authored by Claudius the Magnificent AI Agent

lklimek and others added 2 commits October 26, 2026 20:34
…tion-fees

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
…t-action-fees

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
@Claudius-Maginificent Claudius-Maginificent added priority: low Low priority — nice to have priority: medium Medium priority and removed priority: low Low priority — nice to have priority: medium Medium priority labels Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: low Low priority — nice to have

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants