Skip to content

DIP: App Connect, wallet sign-in and transition signing for Platform applications - #181

Open
PastaPastaPasta wants to merge 10 commits into
dashpay:masterfrom
PastaPastaPasta:dip-pasta-yappr
Open

PastaPastaPasta wants to merge 10 commits into
dashpay:masterfrom
PastaPastaPasta:dip-pasta-yappr

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Mar 11, 2026 •

Copy link
Copy Markdown
Member

This draft described dash-key:/dash-st:. In that protocol the wallet hands the app one login secret, the app derives and registers its own keys, and a first login takes two QR scans. That design was replaced in September, and the wallets now implement its successor without a spec in this repo. This PR rewrites the draft as that successor: App Connect.

The wallet derives and registers every key itself. Each key is bounded to the app's contracts and carries a credit budget and an expiry, all enforced by Platform. The wallet returns the private keys encrypted in a loginKeyResponse document of the App Connect system contract (H8F9mP1BM55TE1ShsxPZHzhyinaMdY9bMmP85mkDhcJJ, protocol version 14).

What the DIP covers:

  • URIs: dashpay://connect and dashpay://sign (extending DIP-15's dashpay: scheme). dash-key: v2 is a transitional carrier, never on mainnet.
  • Request v2 body: TLVs; up to 4 authentication bindings (single contract, document type, contract group); up to 8 encryption bindings.
  • App identity: the app is its contract id. There's no manifest, because anyone could publish one claiming to be a well-known app.
  • Wallet rules:
    • owner consistency;
    • security levels: any binding may ask for CRITICAL (still bounded, with a warning), never MASTER or unbounded;
    • limits;
    • session keys at DIP-13 6', with the leaf hash256(appEphPub ‖ u8(bindingIndex));
    • encryption keys at 7': one current key per contract and purpose, on any contract that opts in at contract level, with a rotation' level so a user can replace a key (where the contract's requirement allows it);
    • identity updates, publishing, the return link, the approval sheet and disconnect.
  • Response: the envelope (ECDH, HKDF, AES-256-GCM, with AAD binding the request and the publishing identity) and the payload.
  • App side: polling, verifying every key on chain, and identity confirmation.
  • sign: accepts every transition type signed by an identity key, describes it, and signs it with an unbound key.
  • Rest: security considerations, compatibility (wallets may ship a subset first and refuse the rest as unsupported), and test vectors (dip-pasta-app-connect/vectors.json, plus the derivation vectors pinned in rust-dashcore).

The derivation paths themselves, including the rotation level, are in #191.

The masternode reward shares draft was committed to this branch by mistake and is removed here. It now lives in DIP-26. The old file dip-pasta-yappr.md is replaced by dip-pasta-app-connect.md.

Implementations:

  • app side: platform-auth src/app-connect/ (vendored in Yappr);
  • wallets: dashwallet-ios #1202/#1204/#1210 and dash-wallet #1604, which implement the transitional carrier, encryption keys on the app's own contract only, and the encryption path without the rotation level.

🤖 Generated with Claude Code

…ansition Signing

Defines the dash-key: and dash-st: URI protocols for wallet-based QR code login to Dash Platform applications. Covers ECDH key exchange, AES-256-GCM encryption, BIP32+HKDF key derivation, the loginKeyResponse contract schema, and first-time key registration flow.
@coderabbitai

coderabbitai Bot commented Mar 11, 2026 •

Copy link
Copy Markdown

Need the big picture first? Review this PR in Change Stack to see what changed before going file by file.

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
Walkthrough

Adds two new DIPs: dip-pasta-yappr.md describing dash-key and dash-st wallet–app URI protocols, cryptographic flows, encodings, and UX/validation; and dip-pasta-masternode-shares.md defining provider payload v4 with a unified payouts list, payment/validation rules, RPC changes, deployment guidance, tests, and security notes.

Changes

DIP: Wallet–App Protocols (dash-key / dash-st)

Layer / File(s) Summary
dash-key & dash-st specification
dip-pasta-yappr.md
New specification introducing two URI-based protocols for wallet–app interaction: URI formats and query parameters, payload encodings, deterministic login key derivation (BIP32 path + HKDF), ECDH shared-secret and AES-256-GCM encryption, application key derivation, Dash data contract (loginKeyResponse) and lifecycle, dash-st unsigned state-transition signing workflow, first-time login registration (IdentityUpdateTransition), validation rules, nonce management, UX wallet prompts, and security considerations.

Masternode Reward Shares

Layer / File(s) Summary
Header and Table of Contents
dip-pasta-masternode-shares.md
Document header, metadata, and table of contents for the Masternode Reward Shares DIP.
Intent and context
dip-pasta-masternode-shares.md
Defines intent to extend deterministic masternode lists to split owner rewards across multiple payout scripts and outlines motivation and related work.
Protocol changes and validation rules
dip-pasta-masternode-shares.md
Introduces provider transaction payload v4 with unified payouts list, payout entry/list structure, consensus ordering/rounding, ProRegTx/ProUpRegTx payload updates, list replacement semantics, payment construction (operator subtraction then basis-point splitting), and enumerated validation constraints.
RPC/JSON and filtering interoperability
dip-pasta-masternode-shares.md
Requires RPC/JSON inputs to accept payouts arrays, JSON outputs to expose payouts, and updates special transaction/bloom filtering to include all payout scripts.
Deployment, compatibility, and rationale
dip-pasta-masternode-shares.md
Activation and compatibility expectations, equivalence behavior for single-entry payouts, and rationale for unified list, max entries, basis-point choice, and operator reward handling.
Test requirements and security considerations
dip-pasta-masternode-shares.md
Enumerated valid/invalid test cases, runtime payment/reporting expectations, and security notes on key reuse, dust limits, and off-chain distribution removal.
Copyright and license
dip-pasta-masternode-shares.md
License and copyright attribution for the DIP.

Sequence Diagram(s)

sequenceDiagram
    participant App
    participant Wallet
    participant Node
    App->>Wallet: Open `dash-key:` URI (includes login request)
    Wallet->>Wallet: Derive deterministic login key (BIP32 + HKDF)
    Wallet->>App: Provide public key / Base58 payload (encrypted loginKeyResponse request)
    App->>Node: Query `loginKeyResponse` contract / poll
    Node-->>App: Return `loginKeyResponse` (encrypted payload)
    App->>Wallet: Forward encrypted loginKeyResponse
    Wallet->>Wallet: ECDH -> derive shared-secret, decrypt (AES-256-GCM)
    Wallet-->>App: Confirm login (signed response / auth keys)
Loading
sequenceDiagram
    participant App
    participant Wallet
    participant Node
    participant IdentityStore
    App->>Wallet: Open `dash-st:` URI with unsigned state transition
    Wallet->>Wallet: Validate wallet identity/keys, fetch signing keys
    Wallet->>IdentityStore: (optional) check identity/key metadata
    Wallet->>Node: (optional) request additional state or fees
    Wallet->>Wallet: Sign state transition per-identity/key selection
    Wallet-->>App: Return signed state transition (encoded)
    App->>Node: Submit signed state transition to network
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks: Passed 4

Check name Status Explanation
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.
Title check Passed The title clearly summarizes the primary change: wallet-based application sign-in and state-transition signing for Dash Platform applications.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Finishing Touches

🧪 Generate unit tests (beta)

  • Create PR with unit tests
  • 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
dip-pasta-yappr.md (2)

262-265: Consider clarifying concurrent request handling.

The keyIndex logic states that if no document exists, keyIndex is 0. If a wallet processes multiple key exchange requests for the same identity and contract concurrently (before the first document is created), both could use keyIndex 0, potentially creating a race condition. Consider adding guidance on how wallets should handle this scenario (e.g., serialize requests per identity+contract or retry on conflict).

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

In `@dip-pasta-yappr.md` around lines 262 - 265, Clarify concurrent-request
behavior for keyIndex/loginKeyResponse: explain that when no loginKeyResponse
exists wallets must handle races by serializing requests per identity+contract
or using a create-if-not-exists atomic operation and retry-on-conflict;
reference the keyIndex and loginKeyResponse documents and state a recommended
strategy (e.g., queue/serialize per identity+contract, use optimistic
concurrency/version checks on loginKeyResponse, or retry on conflict) so two
concurrent requests cannot both assume keyIndex = 0 and create duplicate keys.

391-401: Consider exponential backoff for revision conflict retries.

The document lifecycle specifies retrying up to 3 times with a fixed 500ms delay on revision conflicts. Consider recommending exponential backoff (e.g., 500ms, 1000ms, 2000ms) to reduce contention under high load while maintaining the same maximum retry count.

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

In `@dip-pasta-yappr.md` around lines 391 - 401, The retry logic for handling
revision conflicts when updating a loginKeyResponse document should use
exponential backoff instead of a fixed 500ms delay: update the retry loop that
currently retries up to 3 times with a fixed 500ms pause to instead wait 500ms,
then 1000ms, then 2000ms between attempts (keeping the same max retry count),
and continue to re-query the loginKeyResponse document on each retry and
preserve the existing update/create flow and revision bump behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dip-pasta-yappr.md`:
- Line 36: The Table of Contents link fragment "#supported-encodings" is broken
because the actual header is "## Encoding"; update either the TOC entry or the
heading so they match—e.g., change the TOC item "* [Supported
Encodings](`#supported-encodings`)" to "* [Encoding](`#encoding`)" or rename the
header "## Encoding" to "## Supported Encodings" so the fragment and header text
are consistent.

---

Nitpick comments:
In `@dip-pasta-yappr.md`:
- Around line 262-265: Clarify concurrent-request behavior for
keyIndex/loginKeyResponse: explain that when no loginKeyResponse exists wallets
must handle races by serializing requests per identity+contract or using a
create-if-not-exists atomic operation and retry-on-conflict; reference the
keyIndex and loginKeyResponse documents and state a recommended strategy (e.g.,
queue/serialize per identity+contract, use optimistic concurrency/version checks
on loginKeyResponse, or retry on conflict) so two concurrent requests cannot
both assume keyIndex = 0 and create duplicate keys.
- Around line 391-401: The retry logic for handling revision conflicts when
updating a loginKeyResponse document should use exponential backoff instead of a
fixed 500ms delay: update the retry loop that currently retries up to 3 times
with a fixed 500ms pause to instead wait 500ms, then 1000ms, then 2000ms between
attempts (keeping the same max retry count), and continue to re-query the
loginKeyResponse document on each retry and preserve the existing update/create
flow and revision bump behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6edfd7c4-83bf-45c9-b579-c785373b1631

📥 Commits

Reviewing files that changed from the base of the PR and between 40b74e9 and 9482c77.

📒 Files selected for processing (1)
  • dip-pasta-yappr.md

Comment thread dip-pasta-yappr.md Resolved

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9482c77c3f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread dip-pasta-yappr.md Resolved
Comment thread dip-pasta-yappr.md Resolved
Comment thread dip-pasta-yappr.md Resolved
Comment thread dip-pasta-yappr.md Outdated
| ---------- | ----- | ----------- |
| 9' | Feature purpose (per [DIP-0009](https://github.com/dashpay/dips/blob/master/dip-0009.md)) | |
| coin_type' | `5` (mainnet) or `1` (testnet/devnet/regtest) | Per [BIP-0044](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) |
| 21' | Key exchange feature index | |

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.

Why 21?

Comment thread dip-pasta-yappr.md Resolved
Comment thread dip-pasta-yappr.md Outdated
| --------- | -------- | ----------- |
| `n` | Yes | Network identifier (`m`, `t`, `d`, or `r`) |
| `v` | Yes | Protocol version; must be `1` |
| `id` | No | Base58-encoded identity identifier hint (32 bytes); wallet should pre-select this identity |

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.

Extraneous word?

Suggested change
| `id` | No | Base58-encoded identity identifier hint (32 bytes); wallet should pre-select this identity |
| `id` | No | Base58-encoded identity identifier (32 bytes); wallet should pre-select this identity |

Comment thread dip-pasta-yappr.md Resolved
Comment thread dip-pasta-yappr.md Resolved
- Fix broken ToC link: rename heading to 'Supported Encodings' to match ToC entry
- Make Binary Payload Layout a subsection of URI Format and reorder above Network Identifiers
- Simplify BIP32 path table by removing empty Description column
- Add hyperlink to Network Identifiers from dash-st component table
- Remove extraneous 'hint' from id parameter description
- Remove unnecessary 'Read Only' rows from key registration tables

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@dip-pasta-masternode-shares.md`:
- Around line 15-28: The Table of Contents entries use two spaces after numeric
list markers which violates markdownlint MD030; update each TOC line (e.g., "1. 
[Abstract](`#abstract`)", "4.  [Specification](`#specification`)" and the nested
items like "4.  1.  [Terminology](`#terminology`)") to use a single space after
the period (e.g., "1. [Abstract](`#abstract`)"), and apply the same single-space
fix to the remaining TOC entries indicated in the comment (including lines
29-32) so all numbered list markers conform to MD030.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4d7df077-48e0-48c6-b473-46f573c1b5eb

📥 Commits

Reviewing files that changed from the base of the PR and between 50756a7 and adbe162.

📒 Files selected for processing (1)
  • dip-pasta-masternode-shares.md

Comment thread dip-pasta-masternode-shares.md Outdated
Comment on lines +15 to +28
1. [Abstract](#abstract)
2. [Motivation](#motivation)
3. [Prior Work](#prior-work)
4. [Specification](#specification)
1. [Terminology](#terminology)
2. [Provider Transaction Version](#provider-transaction-version)
3. [Masternode Payout Share](#masternode-payout-share)
4. [Registering a Masternode](#registering-a-masternode)
5. [Updating Masternode Payout Shares](#updating-masternode-payout-shares)
6. [Masternode List State](#masternode-list-state)
7. [Masternode Reward Payments](#masternode-reward-payments)
8. [Validation Rules](#validation-rules)
9. [RPC and JSON Interfaces](#rpc-and-json-interfaces)
10. [Special Transaction Filtering](#special-transaction-filtering)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix list marker spacing to pass markdownlint.

The Table of Contents uses 2 spaces after numbered list markers, but markdownlint rule MD030 expects exactly 1 space. This will cause the GitHub Actions lint check to fail.

📝 Proposed fix for list marker spacing
-1.  [Abstract](`#abstract`)
-2.  [Motivation](`#motivation`)
-3.  [Prior Work](`#prior-work`)
-4.  [Specification](`#specification`)
-    1.  [Terminology](`#terminology`)
-    2.  [Provider Transaction Version](`#provider-transaction-version`)
-    3.  [Masternode Payout Share](`#masternode-payout-share`)
-    4.  [Registering a Masternode](`#registering-a-masternode`)
-    5.  [Updating Masternode Payout Shares](`#updating-masternode-payout-shares`)
-    6.  [Masternode List State](`#masternode-list-state`)
-    7.  [Masternode Reward Payments](`#masternode-reward-payments`)
-    8.  [Validation Rules](`#validation-rules`)
-    9.  [RPC and JSON Interfaces](`#rpc-and-json-interfaces`)
-    10. [Special Transaction Filtering](`#special-transaction-filtering`)
+1. [Abstract](`#abstract`)
+2. [Motivation](`#motivation`)
+3. [Prior Work](`#prior-work`)
+4. [Specification](`#specification`)
+    1. [Terminology](`#terminology`)
+    2. [Provider Transaction Version](`#provider-transaction-version`)
+    3. [Masternode Payout Share](`#masternode-payout-share`)
+    4. [Registering a Masternode](`#registering-a-masternode`)
+    5. [Updating Masternode Payout Shares](`#updating-masternode-payout-shares`)
+    6. [Masternode List State](`#masternode-list-state`)
+    7. [Masternode Reward Payments](`#masternode-reward-payments`)
+    8. [Validation Rules](`#validation-rules`)
+    9. [RPC and JSON Interfaces](`#rpc-and-json-interfaces`)
+    10. [Special Transaction Filtering](`#special-transaction-filtering`)

Apply the same 1-space correction to the remaining TOC entries (lines 29-32).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
1. [Abstract](#abstract)
2. [Motivation](#motivation)
3. [Prior Work](#prior-work)
4. [Specification](#specification)
1. [Terminology](#terminology)
2. [Provider Transaction Version](#provider-transaction-version)
3. [Masternode Payout Share](#masternode-payout-share)
4. [Registering a Masternode](#registering-a-masternode)
5. [Updating Masternode Payout Shares](#updating-masternode-payout-shares)
6. [Masternode List State](#masternode-list-state)
7. [Masternode Reward Payments](#masternode-reward-payments)
8. [Validation Rules](#validation-rules)
9. [RPC and JSON Interfaces](#rpc-and-json-interfaces)
10. [Special Transaction Filtering](#special-transaction-filtering)
1. [Abstract](`#abstract`)
2. [Motivation](`#motivation`)
3. [Prior Work](`#prior-work`)
4. [Specification](`#specification`)
1. [Terminology](`#terminology`)
2. [Provider Transaction Version](`#provider-transaction-version`)
3. [Masternode Payout Share](`#masternode-payout-share`)
4. [Registering a Masternode](`#registering-a-masternode`)
5. [Updating Masternode Payout Shares](`#updating-masternode-payout-shares`)
6. [Masternode List State](`#masternode-list-state`)
7. [Masternode Reward Payments](`#masternode-reward-payments`)
8. [Validation Rules](`#validation-rules`)
9. [RPC and JSON Interfaces](`#rpc-and-json-interfaces`)
10. [Special Transaction Filtering](`#special-transaction-filtering`)
🧰 Tools
🪛 GitHub Check: lint

[failure] 24-24: Spaces after list markers
dip-pasta-masternode-shares.md:24:5 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 23-23: Spaces after list markers
dip-pasta-masternode-shares.md:23:5 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 22-22: Spaces after list markers
dip-pasta-masternode-shares.md:22:5 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 21-21: Spaces after list markers
dip-pasta-masternode-shares.md:21:5 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 20-20: Spaces after list markers
dip-pasta-masternode-shares.md:20:5 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 19-19: Spaces after list markers
dip-pasta-masternode-shares.md:19:5 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 18-18: Spaces after list markers
dip-pasta-masternode-shares.md:18:1 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 17-17: Spaces after list markers
dip-pasta-masternode-shares.md:17:1 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 16-16: Spaces after list markers
dip-pasta-masternode-shares.md:16:1 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md


[failure] 15-15: Spaces after list markers
dip-pasta-masternode-shares.md:15:1 MD030/list-marker-space Spaces after list markers [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md030.md

🤖 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 `@dip-pasta-masternode-shares.md` around lines 15 - 28, The Table of Contents
entries use two spaces after numeric list markers which violates markdownlint
MD030; update each TOC line (e.g., "1.  [Abstract](`#abstract`)", "4. 
[Specification](`#specification`)" and the nested items like "4.  1. 
[Terminology](`#terminology`)") to use a single space after the period (e.g., "1.
[Abstract](`#abstract`)"), and apply the same single-space fix to the remaining
TOC entries indicated in the comment (including lines 29-32) so all numbered
list markers conform to MD030.

@thephez

thephez commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

@PastaPastaPasta those last 4 commits don't belong on this branch?

The dash-key:/dash-st: protocol this draft described was replaced in
September: the wallet now derives and registers every key itself
(bounded, budgeted, expiring) and returns them through the App Connect
system contract. Rewrite the draft as that protocol: dashpay://connect
and dashpay://sign, request v2, DIP-13 6'/7' derivation, per-contract
encryption keys, the response envelope, app verification and test
vectors.

Also drop the masternode reward shares draft, which was committed to
this branch by mistake and now lives in DIP-26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PastaPastaPasta PastaPastaPasta changed the title DIP: Dash Platform Application Key Exchange and State Transition Signing DIP: App Connect, wallet sign-in and transition signing for Platform applications Oct 28, 2026
- Encryption keys gain a final rotation' level on the DIP-13 7' path.
  Rotation is a wallet-side user action, possible only when a contract's
  requirement is 2 (Platform never frees a unique slot). Apps must check
  that a key is still current before encrypting to it.
- Define "current key", and let a wallet send a current key it holds at
  any path it derives on, so keys from earlier wallets keep working.
- CRITICAL is allowed on any binding kind; wallets warn before granting.
- Encryption bindings may name system contracts that opt in at contract
  level (DashPay does not: its requirement is on contactRequest only).
- Wallets may support a subset at first and must refuse the rest as
  unsupported, never grant less.
- Rewrite the TLV section as "Fields", with the request vector as an
  example; add derivation vectors for the new path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PastaPastaPasta added a commit to PastaPastaPasta/yappr that referenced this pull request Oct 28, 2026
The App Connect DIP (dashpay/dips#181, e9b904e) and the DIP-13 paths
(dashpay/dips#191) are now written, and the profile contradicted them in
several places. The profile now describes Yappr's side and points to the
DIP for the protocol; the rules it still restates match the DIP.

- Carriers: dashpay://connect is the protocol carrier and required on
  mainnet; dash-key: v2 is testnet/devnet only; dash-key: v1 and dash-st:
  are obsolete. Point to the DIP's dashpay://connect vector.
- Encryption bindings may name any contract with a requirement of 0 or 2
  (not DashPay), not only appId. Yappr naming its anchor is its choice.
- Encryption path gains the rotation level; add the current-key rules,
  the wallet-side rotation, the app's current-key check and the legacy
  8-level keys as current keys at another path.
- CRITICAL or HIGH on any binding kind; wallets today grant CRITICAL on
  contractGroup only. System contracts MUST NOT be appId.
- Return link and brand-map rules follow the DIP's SHOULD/MUST wording.
- Approval sheet: encryption-binding line, CRITICAL warning for any
  CRITICAL key, full app id one step away, label as the app's claim.
- sign: add the scope rule and the describe-or-show-fields rule; fix the
  status table.
- Wallets may ship a subset and refuse the rest as unsupported.

Also fix the "no rotation" statements in WALLET_INTEGRATION.md, DM_V5.md
and mobile/tools/README.md, and the ENC_BINDING note in
APP_CONNECT_CONTRACTS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PastaPastaPasta added a commit to PastaPastaPasta/yappr that referenced this pull request Oct 28, 2026
The App Connect DIP (dashpay/dips#181, e9b904e) and the DIP-13 paths
(dashpay/dips#191) are now written, and the profile contradicted them in
several places. The profile now describes Yappr's side and points to the
DIP for the protocol; the rules it still restates match the DIP.

- Carriers: dashpay://connect is the protocol carrier and required on
  mainnet; dash-key: v2 is testnet/devnet only; dash-key: v1 and dash-st:
  are obsolete. Point to the DIP's dashpay://connect vector.
- Encryption bindings may name any contract with a requirement of 0 or 2
  (not DashPay), not only appId. Yappr naming its anchor is its choice.
- Encryption path gains the rotation level; add the current-key rules,
  the wallet-side rotation, the app's current-key check and the legacy
  8-level keys as current keys at another path.
- CRITICAL or HIGH on any binding kind; wallets today grant CRITICAL on
  contractGroup only. System contracts MUST NOT be appId.
- Return link and brand-map rules follow the DIP's SHOULD/MUST wording.
- Approval sheet: encryption-binding line, CRITICAL warning for any
  CRITICAL key, full app id one step away, label as the app's claim.
- sign: add the scope rule and the describe-or-show-fields rule; fix the
  status table.
- Wallets may ship a subset and refuse the rest as unsupported.

Also fix the "no rotation" statements in WALLET_INTEGRATION.md, DM_V5.md
and mobile/tools/README.md, and the ENC_BINDING note in
APP_CONNECT_CONTRACTS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xdustinface pushed a commit to dashpay/rust-dashcore that referenced this pull request Oct 29, 2026
…ption paths (#1250)

The application encryption path is about to become permanent on mainnet.
DIP-13 (dashpay/dips#191, used by App Connect in dashpay/dips#181) ends it
with a hardened 31-bit rotation level so an identity can replace the
encryption key for a contract:
m/9'/coin'/5'/7'/0'/identityId'/contractId'/purpose'/rotation',
serialised as ser32(rotation + 2^31), with rotation 0' first.

`DerivationPath::application_encryption_path` now takes `rotation: u32` and
returns `Result`, failing with `Error::InvalidChildNumber` if the rotation
does not fit in 31 bits.

Migration: callers pass the rotation (0 for a first key) and handle the
`Result`. The old 8-level layout moved to
`application_encryption_path_without_rotation`, which wallets derive only to
recognise keys that pre-DIP wallets registered on testnet and devnet.

The pinned vectors now cover the rotation keys and keep the legacy pins
against the renamed function.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Under requirement 2 the key Platform records as current can be disabled
while an older key is still enabled. Rule 2 applies whenever no key is
current, so say that instead of "all of them disabled".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PastaPastaPasta added a commit to dashpay/kotlin-platform that referenced this pull request Oct 29, 2026
The application encryption path ends with a hardened 31-bit rotation level
(dashpay/dips#191, used by the App Connect DIP, dashpay/dips#181):

  m/9'/coin'/5'/7'/0'/identityId'/contractId'/purpose'/rotation'

with rotation 0' first. Without it the path becomes permanent with the first
mainnet key and a compromised or retired key could never be replaced.

platform-mobile: application_encryption_private_key_from_seed takes a
rotation (below 2^31, an error otherwise) and appends it as a hardened level.
The 8-level path without rotation stays available as
application_encryption_legacy_private_key_from_seed, only to recognise keys
that wallets registered on testnet/devnet before the rotation level existed.
Tests pin the DIP vectors for both paths (abandon x11 about, mainnet).

Bindings: Ferment output for dash-sdk-bindings and dash-sdk-android now has
the new signature and the legacy function (regenerated with cargo build of
dash-sdk-bindings; the Android copy carries the same two functions), plus a
SWIG rename for applicationEncryptionLegacyPrivateKeyFromSeed.

Kotlin: ApplicationKeyDerivation.encryption gains a required rotation
parameter, so callers must choose it; new keys use 0. The pre-rotation path
is the explicitly named legacyEncryptionWithoutRotation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PastaPastaPasta added a commit to dashpay/kotlin-platform that referenced this pull request Oct 29, 2026
The application encryption path ends with a hardened 31-bit rotation level
(dashpay/dips#191, used by the App Connect DIP, dashpay/dips#181):

  m/9'/coin'/5'/7'/0'/identityId'/contractId'/purpose'/rotation'

with rotation 0' first. Without it the path becomes permanent with the first
mainnet key and a compromised or retired key could never be replaced.

platform-mobile: application_encryption_private_key_from_seed takes a
rotation (below 2^31, an error otherwise) and appends it as a hardened level.
The 8-level path without rotation stays available as
application_encryption_legacy_private_key_from_seed, only to recognise keys
that wallets registered on testnet/devnet before the rotation level existed.
Tests pin the DIP vectors for both paths (abandon x11 about, mainnet).

Bindings: Ferment output for dash-sdk-bindings and dash-sdk-android now has
the new signature and the legacy function (regenerated with cargo build of
dash-sdk-bindings; the Android copy carries the same two functions), plus a
SWIG rename for applicationEncryptionLegacyPrivateKeyFromSeed.

Kotlin: ApplicationKeyDerivation.encryption gains a required rotation
parameter, so callers must choose it; new keys use 0. The pre-rotation path
is the explicitly named legacyEncryptionWithoutRotation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HashEngineering added a commit to dashpay/kotlin-platform that referenced this pull request Oct 29, 2026
The application encryption path ends with a hardened 31-bit rotation level
(dashpay/dips#191, used by the App Connect DIP, dashpay/dips#181):

  m/9'/coin'/5'/7'/0'/identityId'/contractId'/purpose'/rotation'

with rotation 0' first. Without it the path becomes permanent with the first
mainnet key and a compromised or retired key could never be replaced.

platform-mobile: application_encryption_private_key_from_seed takes a
rotation (below 2^31, an error otherwise) and appends it as a hardened level.
The 8-level path without rotation stays available as
application_encryption_legacy_private_key_from_seed, only to recognise keys
that wallets registered on testnet/devnet before the rotation level existed.
Tests pin the DIP vectors for both paths (abandon x11 about, mainnet).

Bindings: Ferment output for dash-sdk-bindings and dash-sdk-android now has
the new signature and the legacy function (regenerated with cargo build of
dash-sdk-bindings; the Android copy carries the same two functions), plus a
SWIG rename for applicationEncryptionLegacyPrivateKeyFromSeed.

Kotlin: ApplicationKeyDerivation.encryption gains a required rotation
parameter, so callers must choose it; new keys use 0. The pre-rotation path
is the explicitly named legacyEncryptionWithoutRotation.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: HashEngineering <hashengineeringsolutions@gmail.com>
HashEngineering pushed a commit to dashpay/dash-wallet that referenced this pull request Oct 29, 2026
* feat(connect): accept App Connect requests on dashpay://connect

The App Connect DIP (dashpay/dips#181) never allows the dash-key:
carrier on mainnet and moves requests to the dashpay:// scheme the
wallet already uses for invitations. The Android wallet only knew
dash-key:, so a dashpay://connect link from a scan or a tap did nothing.

Accept the same request v2 body on dashpay://connect?v=2&n=..&r=..[&cb=..]
and hand the decoded bytes to the existing AppConnectParser, so both
carriers are processed identically. r is decoded by a strict canonical
base64url decoder (java.util.Base64 tolerates padding and trailing bits),
with the DIP's size limits checked before decoding. Scheme and host are
compared case-insensitively, a repeated v/n/r/cb is invalid, and any
dashpay verb the wallet does not implement says the link needs a newer
wallet. dashpay://invite still goes to the invitation handler.

MainActivity gets a separate dashpay + host connect intent filter; a host
on the dash-key/dash-st filter would stop those host-less URIs matching.
dash-key:/dash-st: behaviour, mainnet gating and key derivation are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): report a dashpay:// link with userinfo or a port as invalid

dashpay://user@connect?... read 'user' as the verb and told the user to
update the wallet. Userinfo or a port makes a malformed link, so it is
now refused as an invalid connection link before the verb is looked at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
HashEngineering added a commit to dashpay/dash-wallet that referenced this pull request Nov 8, 2026
* feat(dashpay): contested username fee follows the Platform protocol version (MO-1067)

Platform protocol version 14 (v4.2) lowers the prefunded voting balance a
contested username pays from 0.2 to 0.1 DASH, so creating an identity with
a contested name drops from 0.25 to 0.15 DASH.

ContestedUsernameFees replaces the DASH_PAY_FEE_CONTESTED(_NAME) constants
and derives both fees from the protocol version learned via the new
PlatformRepo.refreshProtocolVersion() (dash-sdk 4.2.0-SNAPSHOT). The version
is refreshed when the username/invite screens open and right before
CreateIdentityService sizes the asset lock. Until it is known, the higher
pre-v14 fees apply: over-funding only leaves identity credits, while
under-funding would make the registration fail.

The "You need to have 0.25 DASH" requirement text was hardcoded in every
locale; it now takes the fee as an argument (and shows the non-contested
fee for non-contested names).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(connect): remove second QR registration path

* chore: update Platform SDK to 5.0.0-SNAPSHOT

* fix: refresh username affordability and amounts when protocol fees change

* fix: refresh invitation labels without delaying non-contested invites

* Use DIP-13 DashConnect session key derivation

* feat: configure wallet devnet flavor for Sakura

* fix(dashpay): dark mode text in the create username confirmation dialog

OffsetDialog parented from Theme.Design.Light.BottomSheetDialog with no
values-night override, so android:textColorPrimary inside every offset
bottom sheet stayed the light theme near-black. The sheet background
already follows the night palette via @color/background_secondary, which
left any view that relies on the theme for its colour dark-on-dark.

Most TextViews escaped this because their style sets
android:textColor="@color/content_primary", a colour resource that does
have a night variant. The confirmation message and the "I accept" label
in dialog_confirm_username_request carried no style at all, fell through
to the theme, and rendered black on the dark sheet.

Base.OffsetDialog now holds the parent and is overridden in values-night
with Theme.Design.BottomSheetDialog. The two Material themes carry
identical items and differ only in their AppCompat palette, so nothing
structural about the sheet changes. Keeping every <item> on OffsetDialog
means the two configurations cannot drift apart - duplicating the style
per configuration is what invites this bug back.

No layout in the app reads theme-attribute colours and none of the 36
offset sheet layouts hardcodes a light background, so there is no
white-on-white inverse of this bug to introduce. The theme fix also
covers the unstyled CheckBox tint in the same dialog and the same class
of bug in the other offset sheets.

The two TextViews additionally get style="@style/Body2", matching the
convention every sibling dialog already follows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(ci): point devnet distribution at the real Firebase app

The devnet app id matched no app in dash-wallet-firebase, so every QA
upload for that flavor failed the build and then 404'd:

  App Distribution halted because it had a problem uploading the APK.
  (Invalid request, status_code: 404)

The flavor builds as org.dash.wallet.devnet, whose app is
1:1039839682638:android:fdff563e24b69c5fea631f. Fixed in the workflow
too, which carried the same bad id and would have failed the same way.

Note that org.dash.dashpay.devnet is a second, older app with the same
"DashPay [devnet]" display name, left over from before the applicationId
changed. It is not the one we distribute to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: prevent crashes if google-service.json is missing

* revert: devnet flavor app id

* docs: update sakura-devnet.md

* chore: update dashj to 24.0.0-SNAPSHOT

* revert: use 22.0.5 dashj

* feat: add PV14 App Connect v2 wallet flow

* fix: prevent DashConnect only on mainnet

* fix: reuse wallet dialogs and select Sakura quorums

* fix: reconcile Connect cancellation and restore approval state

Revoke an attempted request before reporting it as declined, retain uncertain outcomes for retry, and freeze approved terms. Revoke confirmed auth keys without blocking on unrelated pending registrations and recognize irrecoverable key ID conflicts.

Use a restorable approval dialog owned by the connections fragment, with no denial on composition disposal. Cover approval/revocation errors, key-state policy, and saved-state recreation in focused regressions.

* feat: approve dash-st token purchases and handle operation links

Keep one-QR login and add a separate explicit token-purchase approval sheet. Validate and display identity, token denomination, quantity and price ceiling, then rebuild through the SDK rather than signing untrusted bytes.

Persist scoped submission receipts to prevent uncertain debit retries, retain approval through recreation, and route dash-key and dash-st through Android browsable links and all wallet scanners. Requires kotlin-platform purchase bridge commit 25c83e7.

Validation: 74 wallet tests passed with 3 existing flavor skips; 17 JNI/identity tests and 4 Rust parser tests passed. Devnet APK built and native library matched the updated AAR.

* fix: confirm Connect grants and restore reconnect actions

* fix(connect): preserve revocation after key limit and storage changes

* fix: address Connect grant recovery and signing review findings

* fix: require wallet unlock for Connect approvals

* fix: batch Connect revocation and retain recovery audits

* fix: permanently revoke expired Connect keys

* fix: separate request revocation from app disconnect confirmation

* Fix Connect authentication and cold-start lifecycle blockers

* Trigger PR synchronization

* fix: prevent DashConnect on 32-bit devices

* fix: reject unsupported Connect requests before identity initialization

* fix: refresh DashConnect status at expiry and on resume

* fix: preserve responses for independent app connections

* fix: reject unverified group grants and redact QR errors

* fix: reject disabled Connect networks before identity initialization

* fix: use dashj 24.0.0-SNAPSHOT

* fix: fail closed on grant storage and require explicit signing-key setup

* fix: invalidate Connect signing authorization when the wallet locks

* fix: reject legacy DashConnect login requests before granting access

* Restore Yappr group grants with verified scope and renewed consent

The owner-only bridge led to a blanket group rejection that broke Yappr's CRITICAL login grants. Consume the full proof-verified SDK snapshot, disclose current and future scope and administrators, persist approved scope, and recheck before registration and response publication. Refresh consent explicitly when configuration changes while retaining recovery and revocation controls. Pin SDK 9fdfe17, include regression tests, and document the rationale and Pixel 9 test evidence.

* Require renewed group-scope consent when recovering Connect grants

Decode the persisted approved scope and compare it with the current verified authorization scope across process restarts. Changed or missing prior metadata requires a visible unchecked acknowledgement; direct session approval enforces it before secrets and binds the plan to the saved record. Preserve frozen grants, revocation and error recovery, reset consent on refresh, and leave malformed metadata fail-closed. Cover prepared/pending/confirmed/active recovery and document test limits.

* Invalidate Connect signing authority when its UI leaves the foreground

Cancel sensitive jobs and invalidate captured authorization on pause, stop, destroy or screen-off. Require an interactive unlocked device, preserve uncertain-outcome recovery and require a fresh explicit action after return. Cover lifecycle wiring on API 28/34 and all sensitive operation types. Also correct the 2 DASH preset, keep QR names explicitly unverified, refresh state-change timestamps and export workflow support email only after external SDK builds.

* Refresh Connect timestamps when revocation confirmation changes

A disabled grant can move from request-only revocation to confirmed app-wide disconnect without changing its state string. Refresh the status timestamp on confirmation changes and preserve it for identical repeated settlements.

* Pin the published DashJ build and complete Connect status follow-ups

Use the exact timestamped DashJ 24 publication across direct and transitive dependencies and pin the corresponding SDK revision. Derive expiry status dates from the final auth-key expiry while retaining later state changes. Retain failed approval status and offer the established ac=failed callback only after explicit end-recovery and confirmed revocation; preserve scanner and uncertain-outcome safeguards.

* Keep unavailable Connect scope distinct from an empty grant list

Propagate wallet and identity scope failures. Keep records loading during pending identity initialization, report failures as unavailable, and restart observation on initialization or identity changes. Clear stale rows while reloading and retain Retry without initiating identity setup. Cover real repository/ViewModel cold start, failure, recovery, identity loss and valid empty results.

* Serialize Connect availability and connection-row updates

Keep state writes inside the same cancellable identity observation instead of forwarding buffered lists to a separate READY consumer. Check the collecting coroutine before updates and error handling so cancelled observers cannot overwrite newer refresh state. Reproduce emit-then-fail and cover initialization changes and late cancelled-observer failures.

* Reload missing Connect identity metadata on explicit Retry

Route Retry through the existing identity loader after failed or successful-but-empty initialization. Use the injected application scope so screen closure cannot strand the shared loader, coalesce pending retries, and preserve read-only automatic refresh. Cover actual storage rereads, cached missing identity recovery, startup/healthy guards, duplicate taps and ViewModel disposal.

* Settle cancelled identity loads without swallowing cancellation

Publish an ordinary retryable interrupted-load failure before rethrowing cancellation. Serialize metadata initialization owners so cancelled or queued attempts cannot overwrite a newer result, and check cancellation after storage reads. Cover owner cancellation, queue ordering, reload and real owner-to-ViewModel Retry recovery without key registration.

* Preserve selected grant expiry and fresh consent editing

* Reject Unicode formatting characters in Connect labels

* Normalize group consent and restore trusted quick sends

* Keep Connect recovery reachable without username metadata

* Restore saved grant selection before recovery expiry checks

* Reload partial identity metadata on explicit Connect Retry

* Avoid reading partial identity IDs during metadata reload

* Share Connections state ownership and verify pinned DashJ bytes

* Gate connection revoke toggle on wallet and record availability

* Distinguish confirmed no identity from unavailable Connect records

* Distinguish purchase preflight cancellation from native handoff

* Consume canonical purchase IDs and pin nonce-preserving SDK

* Run native purchase replay regressions in SDK CI

* Pin wallet Platform SDK to 5.0.1

* Pin final regenerated Platform SDK bindings

* Pin SDK CI publication fix

* Fix DashConnect confirmation recovery and simplify connection names

Poll exact response confirmation up to three times with backoff and redacted outcome diagnostics. Refresh persisted approval terms after failures, block stale retries when refresh fails, and retain group-consent and revocation checks.

Show the friendly app name alone on connection cards per MO-1072 testing feedback. Include regression coverage and build code 11090202. Verified with 260 DashConnect tests against the local 5.0.0 SDK and successful response confirmation on emulator-5556.

* feat(connect): accept App Connect requests on dashpay://connect (#1656)

* feat(connect): accept App Connect requests on dashpay://connect

The App Connect DIP (dashpay/dips#181) never allows the dash-key:
carrier on mainnet and moves requests to the dashpay:// scheme the
wallet already uses for invitations. The Android wallet only knew
dash-key:, so a dashpay://connect link from a scan or a tap did nothing.

Accept the same request v2 body on dashpay://connect?v=2&n=..&r=..[&cb=..]
and hand the decoded bytes to the existing AppConnectParser, so both
carriers are processed identically. r is decoded by a strict canonical
base64url decoder (java.util.Base64 tolerates padding and trailing bits),
with the DIP's size limits checked before decoding. Scheme and host are
compared case-insensitively, a repeated v/n/r/cb is invalid, and any
dashpay verb the wallet does not implement says the link needs a newer
wallet. dashpay://invite still goes to the invitation handler.

MainActivity gets a separate dashpay + host connect intent filter; a host
on the dash-key/dash-st filter would stop those host-less URIs matching.
dash-key:/dash-st: behaviour, mainnet gating and key derivation are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): report a dashpay:// link with userinfo or a port as invalid

dashpay://user@connect?... read 'user' as the verb and told the user to
update the wallet. Userinfo or a port makes a malformed link, so it is
now refused as an invalid connection link before the verb is looked at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Specify initial encryption rotation for Platform SDK 5.0.1

Pass rotation 0 explicitly to the updated application encryption derivation API. Verified merged wallet compilation and all 288 DashConnect tests against SDK 5.0.1. Pixel emulator dashpay://connect testing confirmed response recovery on the second read after backoff.

* Block key allocation while other scoped registrations are unresolved

Check every other grant in the wallet/identity/network scope before deriving or allocating new keys and saving a prepared record. Do not narrow the settlement check to the same app or connection; exclude only the request being recovered. Keep revocation targeting unchanged.

Add nine regression tests covering cross-app and cross-connection pending grants, scope and current-request exclusions, settled versus unresolved states, storage failure, and approval ordering before record persistence. All 297 testnet DashConnect tests pass against SDK 5.0.1.

* Pin Platform SDK ValueMap header-order fix

Use bb3eeae17cccfbd7d70ae1d077770e19e8b36ebc so CI declares ValueMap before generated header use regardless of definition order. The SDK fix passed reordered-header regression checks, a clean native C++ rebuild and seven JNI Value tests. Platform fork/revision remain unchanged.

* Consume published Platform SDK 5.0.1 instead of building it in wallet CI

Remove the native SDK bootstrap script, composite action and source revision pin, remove its calls from all three wallet workflows, and drop the isolated repository override. Resolve dash-sdk-java, dash-sdk-kotlin (dpp) and dash-sdk-android 5.0.1 from the published Maven artifacts.

Verified testnet compilation and all 297 DashConnect tests with SDK resolution restricted to Maven Central, valid workflow YAML, and required Connect/purchase JNI exports in both published Android ABIs.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PastaPastaPasta

Copy link
Copy Markdown
Member Author

Synced with the shipped implementation: Platform v5.0.3 app-connect-contract and dashwallet-ios develop (app-connect:v2 HKDF/AAD, 17-slot cap). The contract id, loginKeyResponse schema, index and field sizes match. dec23c7 updates the test-vector note: rust-dashcore now pins the rotation-level encryption keys, and the paths without that level are kept as legacy vectors. The three unresolved threads are on files no longer in this PR. Ready for maintainer review.


🤖 Posted autonomously by Claude on behalf of pasta.

HashEngineering added a commit to dashpay/kotlin-platform that referenced this pull request Nov 9, 2026
…on (#41)

* feat: support 4.2-dev

* feat: upgrade to v4.2-dev.8

* feat: expose the network's Platform protocol version

Add get_protocol_version_with_sdk and refresh_protocol_version_with_sdk
exports (fermented bindings regenerated) and DapiClient.getProtocolVersion()
/ refreshProtocolVersion() wrappers. refresh issues a proven getEpochsInfo
query and ratchets dash-sdk up to the version the network runs, so wallets
can pick fees that depend on the activated protocol version (e.g. the
contested username prefunded voting balance, 0.2 -> 0.1 DASH in v14).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: skip retired Android SDK tools package

* feat: upgrade to Platform 5.0 and support App Connect

Expose bounded V1 identity keys, DIP-13 application key derivation, remaining budgets, and explicit devnet configuration across Rust, JNI, and Kotlin.

Fix V1 native enum widths and index-only response confirmation using the submitted PV14 nonce-derived document ID. Add regression coverage and build against HashEngineering/platform ferment-5.0.

* feat: expose safe wallet token-purchase requests

Decode one exact unsigned direct-purchase batch, verify token identity and denomination, and rebuild approved values with a fresh nonce and an unbound CRITICAL wallet key. Reject unsupported operations and hidden fee or group changes.

Add native decoder and JNI boundary regression tests. Treat purchase proof results as affected-state snapshots rather than execution confirmation.

* fix: provision wallet signing keys and prove current application key index

* fix: propagate data contract query and conversion failures

* fix: require opt-in critical key migration and use DashJ 24

* fix: guard identity updates and wallet signing with caller authorization

* Expose complete proof-verified contract group scope for Android

Replace the owner-only bridge limitation that forced the wallet to block Yappr's group-bound CRITICAL grants. Verify all member pages at one block, expose admins and each member kind, and retain explicit errors instead of partial scope. Order Android native builds after SWIG generation to prevent stale JNI wrappers. Document the rationale and verified test coverage.

* Pin DashJ 24 to the published timestamped build

Use 24.0.0-20261024.220449-1 instead of the moving SNAPSHOT alias. Record the publicly resolved JAR checksum and snapshot retention caveat; native SDK code is unchanged.

* Preserve purchase operation nonces and expose canonical replay IDs

* Upgrade Platform SDK integration to 5.0.1

* Regenerate Android Ferment bindings for Platform 5.0.1

* Use unsigned local publication in SDK CI

* chore: set dash-sdk-bindings to 5.0.1

* feat: add the DIP-13 rotation level to application encryption keys (#42)

The application encryption path ends with a hardened 31-bit rotation level
(dashpay/dips#191, used by the App Connect DIP, dashpay/dips#181):

  m/9'/coin'/5'/7'/0'/identityId'/contractId'/purpose'/rotation'

with rotation 0' first. Without it the path becomes permanent with the first
mainnet key and a compromised or retired key could never be replaced.

platform-mobile: application_encryption_private_key_from_seed takes a
rotation (below 2^31, an error otherwise) and appends it as a hardened level.
The 8-level path without rotation stays available as
application_encryption_legacy_private_key_from_seed, only to recognise keys
that wallets registered on testnet/devnet before the rotation level existed.
Tests pin the DIP vectors for both paths (abandon x11 about, mainnet).

Bindings: Ferment output for dash-sdk-bindings and dash-sdk-android now has
the new signature and the legacy function (regenerated with cargo build of
dash-sdk-bindings; the Android copy carries the same two functions), plus a
SWIG rename for applicationEncryptionLegacyPrivateKeyFromSeed.

Kotlin: ApplicationKeyDerivation.encryption gains a required rotation
parameter, so callers must choose it; new keys use 0. The pre-rotation path
is the explicitly named legacyEncryptionWithoutRotation.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: HashEngineering <hashengineeringsolutions@gmail.com>

* fix: sync bindings lockfile with 5.0.1

* Forward declare ValueMap independently of generated header order

Declare platform_value_value_map_ValueMap before including Ferment's generated header. This fixes the CI compile failure when Value is emitted before ValueMap without changing ABI or Platform versions.

Add a native header-order regression. Verified the original and reordered generated headers, a clean C++ rebuild, and seven JNI ValueTest tests.

* chore: prepare 5.0.1 Maven Central release

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>
HashEngineering added a commit to dashpay/dash-wallet that referenced this pull request Nov 9, 2026
…#1670, #1674) (#1687)

* chore: remove CrowdNode integration (#1664)

* chore: remove CrowdNode integration (MO-1114)

Remove the CrowdNode integration module and every reference to it:
staking entry points (Explore menu item, Staking shortcut, StakingActivity),
CrowdNode validations and messaging, the leftover-balance check on sends
(LeftoverBalanceException, checkSendingConditions, MinimumBalanceDialog),
and CrowdNode transaction grouping in wallet history.

Upgrade handling for users who had CrowdNode:
- Room onOpen callback deletes cached crowdnode rows from tx_group_cache
  and tx_display_cache so history is rebuilt (no schema version bump)
- the saved Staking shortcut id (10) is ignored and refilled
- obsolete crowdnode Configuration keys, the crowdnode DataStore file and
  the pending CrowdNode.WORK job are removed on startup

ServiceName.CrowdNode is kept so existing transaction metadata still reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: rebuild history without waiting for sync; keep scan_qr translations

Clear both transaction caches when they hold CrowdNode rows instead of only
deleting those rows, so history is rebuilt from the wallet on load rather
than after sync completes. Move the scan_qr translations from the removed
CrowdNode module to common, and drop the now-unused button_show_qr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: use JUnit assertions in DatabaseMigrationTest

Kotlin assert is a no-op unless assertions are enabled at runtime, so the
migration and CrowdNode cache cleanup tests could pass without checking
anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: index-friendly CrowdNode cache probe; restore group details heading

Probe tx_group_cache by groupId, which the primary key indexes, so the
startup check no longer scans the whole cache for wallets without CrowdNode.

The CoinJoin group details sheet showed the explainer text as both heading
and body. Restore the "Why do I see all these transactions?" heading it had
before, moved from the removed CrowdNode module with its translations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: drop unused analytics from ExploreEntryViewModel

logEvent lost its last callers with the CrowdNode Staking entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: drop orphaned CrowdNode test fixtures and stale APY KDoc

TransactionWrapperHelperTest lost its only test with the CrowdNode
grouping; BlockchainStateProvider no longer provides Masternode APY.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): update DashConnect to v2 one-QR flow  (#1604)

* feat(dashpay): contested username fee follows the Platform protocol version (MO-1067)

Platform protocol version 14 (v4.2) lowers the prefunded voting balance a
contested username pays from 0.2 to 0.1 DASH, so creating an identity with
a contested name drops from 0.25 to 0.15 DASH.

ContestedUsernameFees replaces the DASH_PAY_FEE_CONTESTED(_NAME) constants
and derives both fees from the protocol version learned via the new
PlatformRepo.refreshProtocolVersion() (dash-sdk 4.2.0-SNAPSHOT). The version
is refreshed when the username/invite screens open and right before
CreateIdentityService sizes the asset lock. Until it is known, the higher
pre-v14 fees apply: over-funding only leaves identity credits, while
under-funding would make the registration fail.

The "You need to have 0.25 DASH" requirement text was hardcoded in every
locale; it now takes the fee as an argument (and shows the non-contested
fee for non-contested names).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(connect): remove second QR registration path

* chore: update Platform SDK to 5.0.0-SNAPSHOT

* fix: refresh username affordability and amounts when protocol fees change

* fix: refresh invitation labels without delaying non-contested invites

* Use DIP-13 DashConnect session key derivation

* feat: configure wallet devnet flavor for Sakura

* fix(dashpay): dark mode text in the create username confirmation dialog

OffsetDialog parented from Theme.Design.Light.BottomSheetDialog with no
values-night override, so android:textColorPrimary inside every offset
bottom sheet stayed the light theme near-black. The sheet background
already follows the night palette via @color/background_secondary, which
left any view that relies on the theme for its colour dark-on-dark.

Most TextViews escaped this because their style sets
android:textColor="@color/content_primary", a colour resource that does
have a night variant. The confirmation message and the "I accept" label
in dialog_confirm_username_request carried no style at all, fell through
to the theme, and rendered black on the dark sheet.

Base.OffsetDialog now holds the parent and is overridden in values-night
with Theme.Design.BottomSheetDialog. The two Material themes carry
identical items and differ only in their AppCompat palette, so nothing
structural about the sheet changes. Keeping every <item> on OffsetDialog
means the two configurations cannot drift apart - duplicating the style
per configuration is what invites this bug back.

No layout in the app reads theme-attribute colours and none of the 36
offset sheet layouts hardcodes a light background, so there is no
white-on-white inverse of this bug to introduce. The theme fix also
covers the unstyled CheckBox tint in the same dialog and the same class
of bug in the other offset sheets.

The two TextViews additionally get style="@style/Body2", matching the
convention every sibling dialog already follows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(ci): point devnet distribution at the real Firebase app

The devnet app id matched no app in dash-wallet-firebase, so every QA
upload for that flavor failed the build and then 404'd:

  App Distribution halted because it had a problem uploading the APK.
  (Invalid request, status_code: 404)

The flavor builds as org.dash.wallet.devnet, whose app is
1:1039839682638:android:fdff563e24b69c5fea631f. Fixed in the workflow
too, which carried the same bad id and would have failed the same way.

Note that org.dash.dashpay.devnet is a second, older app with the same
"DashPay [devnet]" display name, left over from before the applicationId
changed. It is not the one we distribute to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: prevent crashes if google-service.json is missing

* revert: devnet flavor app id

* docs: update sakura-devnet.md

* chore: update dashj to 24.0.0-SNAPSHOT

* revert: use 22.0.5 dashj

* feat: add PV14 App Connect v2 wallet flow

* fix: prevent DashConnect only on mainnet

* fix: reuse wallet dialogs and select Sakura quorums

* fix: reconcile Connect cancellation and restore approval state

Revoke an attempted request before reporting it as declined, retain uncertain outcomes for retry, and freeze approved terms. Revoke confirmed auth keys without blocking on unrelated pending registrations and recognize irrecoverable key ID conflicts.

Use a restorable approval dialog owned by the connections fragment, with no denial on composition disposal. Cover approval/revocation errors, key-state policy, and saved-state recreation in focused regressions.

* feat: approve dash-st token purchases and handle operation links

Keep one-QR login and add a separate explicit token-purchase approval sheet. Validate and display identity, token denomination, quantity and price ceiling, then rebuild through the SDK rather than signing untrusted bytes.

Persist scoped submission receipts to prevent uncertain debit retries, retain approval through recreation, and route dash-key and dash-st through Android browsable links and all wallet scanners. Requires kotlin-platform purchase bridge commit 25c83e7.

Validation: 74 wallet tests passed with 3 existing flavor skips; 17 JNI/identity tests and 4 Rust parser tests passed. Devnet APK built and native library matched the updated AAR.

* fix: confirm Connect grants and restore reconnect actions

* fix(connect): preserve revocation after key limit and storage changes

* fix: address Connect grant recovery and signing review findings

* fix: require wallet unlock for Connect approvals

* fix: batch Connect revocation and retain recovery audits

* fix: permanently revoke expired Connect keys

* fix: separate request revocation from app disconnect confirmation

* Fix Connect authentication and cold-start lifecycle blockers

* Trigger PR synchronization

* fix: prevent DashConnect on 32-bit devices

* fix: reject unsupported Connect requests before identity initialization

* fix: refresh DashConnect status at expiry and on resume

* fix: preserve responses for independent app connections

* fix: reject unverified group grants and redact QR errors

* fix: reject disabled Connect networks before identity initialization

* fix: use dashj 24.0.0-SNAPSHOT

* fix: fail closed on grant storage and require explicit signing-key setup

* fix: invalidate Connect signing authorization when the wallet locks

* fix: reject legacy DashConnect login requests before granting access

* Restore Yappr group grants with verified scope and renewed consent

The owner-only bridge led to a blanket group rejection that broke Yappr's CRITICAL login grants. Consume the full proof-verified SDK snapshot, disclose current and future scope and administrators, persist approved scope, and recheck before registration and response publication. Refresh consent explicitly when configuration changes while retaining recovery and revocation controls. Pin SDK 9fdfe17, include regression tests, and document the rationale and Pixel 9 test evidence.

* Require renewed group-scope consent when recovering Connect grants

Decode the persisted approved scope and compare it with the current verified authorization scope across process restarts. Changed or missing prior metadata requires a visible unchecked acknowledgement; direct session approval enforces it before secrets and binds the plan to the saved record. Preserve frozen grants, revocation and error recovery, reset consent on refresh, and leave malformed metadata fail-closed. Cover prepared/pending/confirmed/active recovery and document test limits.

* Invalidate Connect signing authority when its UI leaves the foreground

Cancel sensitive jobs and invalidate captured authorization on pause, stop, destroy or screen-off. Require an interactive unlocked device, preserve uncertain-outcome recovery and require a fresh explicit action after return. Cover lifecycle wiring on API 28/34 and all sensitive operation types. Also correct the 2 DASH preset, keep QR names explicitly unverified, refresh state-change timestamps and export workflow support email only after external SDK builds.

* Refresh Connect timestamps when revocation confirmation changes

A disabled grant can move from request-only revocation to confirmed app-wide disconnect without changing its state string. Refresh the status timestamp on confirmation changes and preserve it for identical repeated settlements.

* Pin the published DashJ build and complete Connect status follow-ups

Use the exact timestamped DashJ 24 publication across direct and transitive dependencies and pin the corresponding SDK revision. Derive expiry status dates from the final auth-key expiry while retaining later state changes. Retain failed approval status and offer the established ac=failed callback only after explicit end-recovery and confirmed revocation; preserve scanner and uncertain-outcome safeguards.

* Keep unavailable Connect scope distinct from an empty grant list

Propagate wallet and identity scope failures. Keep records loading during pending identity initialization, report failures as unavailable, and restart observation on initialization or identity changes. Clear stale rows while reloading and retain Retry without initiating identity setup. Cover real repository/ViewModel cold start, failure, recovery, identity loss and valid empty results.

* Serialize Connect availability and connection-row updates

Keep state writes inside the same cancellable identity observation instead of forwarding buffered lists to a separate READY consumer. Check the collecting coroutine before updates and error handling so cancelled observers cannot overwrite newer refresh state. Reproduce emit-then-fail and cover initialization changes and late cancelled-observer failures.

* Reload missing Connect identity metadata on explicit Retry

Route Retry through the existing identity loader after failed or successful-but-empty initialization. Use the injected application scope so screen closure cannot strand the shared loader, coalesce pending retries, and preserve read-only automatic refresh. Cover actual storage rereads, cached missing identity recovery, startup/healthy guards, duplicate taps and ViewModel disposal.

* Settle cancelled identity loads without swallowing cancellation

Publish an ordinary retryable interrupted-load failure before rethrowing cancellation. Serialize metadata initialization owners so cancelled or queued attempts cannot overwrite a newer result, and check cancellation after storage reads. Cover owner cancellation, queue ordering, reload and real owner-to-ViewModel Retry recovery without key registration.

* Preserve selected grant expiry and fresh consent editing

* Reject Unicode formatting characters in Connect labels

* Normalize group consent and restore trusted quick sends

* Keep Connect recovery reachable without username metadata

* Restore saved grant selection before recovery expiry checks

* Reload partial identity metadata on explicit Connect Retry

* Avoid reading partial identity IDs during metadata reload

* Share Connections state ownership and verify pinned DashJ bytes

* Gate connection revoke toggle on wallet and record availability

* Distinguish confirmed no identity from unavailable Connect records

* Distinguish purchase preflight cancellation from native handoff

* Consume canonical purchase IDs and pin nonce-preserving SDK

* Run native purchase replay regressions in SDK CI

* Pin wallet Platform SDK to 5.0.1

* Pin final regenerated Platform SDK bindings

* Pin SDK CI publication fix

* Fix DashConnect confirmation recovery and simplify connection names

Poll exact response confirmation up to three times with backoff and redacted outcome diagnostics. Refresh persisted approval terms after failures, block stale retries when refresh fails, and retain group-consent and revocation checks.

Show the friendly app name alone on connection cards per MO-1072 testing feedback. Include regression coverage and build code 11090202. Verified with 260 DashConnect tests against the local 5.0.0 SDK and successful response confirmation on emulator-5556.

* feat(connect): accept App Connect requests on dashpay://connect (#1656)

* feat(connect): accept App Connect requests on dashpay://connect

The App Connect DIP (dashpay/dips#181) never allows the dash-key:
carrier on mainnet and moves requests to the dashpay:// scheme the
wallet already uses for invitations. The Android wallet only knew
dash-key:, so a dashpay://connect link from a scan or a tap did nothing.

Accept the same request v2 body on dashpay://connect?v=2&n=..&r=..[&cb=..]
and hand the decoded bytes to the existing AppConnectParser, so both
carriers are processed identically. r is decoded by a strict canonical
base64url decoder (java.util.Base64 tolerates padding and trailing bits),
with the DIP's size limits checked before decoding. Scheme and host are
compared case-insensitively, a repeated v/n/r/cb is invalid, and any
dashpay verb the wallet does not implement says the link needs a newer
wallet. dashpay://invite still goes to the invitation handler.

MainActivity gets a separate dashpay + host connect intent filter; a host
on the dash-key/dash-st filter would stop those host-less URIs matching.
dash-key:/dash-st: behaviour, mainnet gating and key derivation are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): report a dashpay:// link with userinfo or a port as invalid

dashpay://user@connect?... read 'user' as the verb and told the user to
update the wallet. Userinfo or a port makes a malformed link, so it is
now refused as an invalid connection link before the verb is looked at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Specify initial encryption rotation for Platform SDK 5.0.1

Pass rotation 0 explicitly to the updated application encryption derivation API. Verified merged wallet compilation and all 288 DashConnect tests against SDK 5.0.1. Pixel emulator dashpay://connect testing confirmed response recovery on the second read after backoff.

* Block key allocation while other scoped registrations are unresolved

Check every other grant in the wallet/identity/network scope before deriving or allocating new keys and saving a prepared record. Do not narrow the settlement check to the same app or connection; exclude only the request being recovered. Keep revocation targeting unchanged.

Add nine regression tests covering cross-app and cross-connection pending grants, scope and current-request exclusions, settled versus unresolved states, storage failure, and approval ordering before record persistence. All 297 testnet DashConnect tests pass against SDK 5.0.1.

* Pin Platform SDK ValueMap header-order fix

Use bb3eeae17cccfbd7d70ae1d077770e19e8b36ebc so CI declares ValueMap before generated header use regardless of definition order. The SDK fix passed reordered-header regression checks, a clean native C++ rebuild and seven JNI Value tests. Platform fork/revision remain unchanged.

* Consume published Platform SDK 5.0.1 instead of building it in wallet CI

Remove the native SDK bootstrap script, composite action and source revision pin, remove its calls from all three wallet workflows, and drop the isolated repository override. Resolve dash-sdk-java, dash-sdk-kotlin (dpp) and dash-sdk-android 5.0.1 from the published Maven artifacts.

Verified testnet compilation and all 297 DashConnect tests with SDK resolution restricted to Maven Central, valid workflow YAML, and required Connect/purchase JNI exports in both published Android ABIs.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>

* feat(connect): redesign the approve sheet and Connections page (#1662)

* feat(connect): redesign the approve connection sheet

Follow the new DashPay Android designs for the App Connect approval:
- Reorder the sheet: app name and website, a first-connection warning,
  request details, identity funding limit and expiration, then
  collapsible "able to do" / "will NOT have access to" cards.
- Describe each grant in plain language instead of raw grant text, and
  approve every requested grant (the per-grant checkboxes are gone).
- Replace the dropdowns with funding limit (0.1/0.5/2/5 DASH) and
  expiration (30/90/365 days) picker sheets. The sheet opens on the
  smallest presets at or above the request, so it grants exactly what
  was asked until the user lowers it.
- Add info sheets for identity funding limits and spendable Dash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): keep approve buttons on screen and fix sheet text

Found while checking the redesigned sheet on a staging build:
- Pin Approve/Deny below a body that scrolls within the visible part of
  the expanded sheet, instead of a fixed 720dp cap that left a blank
  gap; the top spacer stays outside the scroll so cards no longer slide
  under the drag indicator.
- Hide the website row when no website was resolved rather than
  repeating the unverified app label beside the globe.
- Escape paragraph breaks in the new strings (raw newlines were folded
  into spaces) and avoid a possessive on the quoted app label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): scroll the approve sheet instead of dismissing it on drag down

The approve sheet's Compose content scrolled inside a View-based bottom
sheet that could not see that scroll, so dragging down to re-read the
terms dismissed the sheet, which denies the request. Connect the scroll
through nested-scroll interop so the sheet only collapses once the
content is back at the top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): tabbed Connections page with connection details sheet

Follow the DashPay Android Connections designs (Figma 40042:59884):
- Active / Disconnected / Expired tabs once a connection exists, and a
  + button in the top bar that opens the scanner like Scan QR. Pending
  connections stay under Active; local-only records under Disconnected.
- Add an EXPIRED status for connections whose keys all expired without
  being revoked; they stay enabled on Platform until disconnected.
- Rows show the app name and website; tapping one opens a details sheet
  (status, IDs, funding limit and expiry, what the app could and could
  not do) built from the latest recorded grant, with Disconnect for
  active and expired connections and Remove from list for local-only
  records. The old per-key text moves into a collapsed Key details card.
- Share the sheet cards between the approve and details sheets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): Reconnect button and disconnect date on the disconnected sheet

Reconnect opens the scanner, since a new login needs the app's QR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): warn about DashPay profile keys no connection holds

Connections now lists enabled keys bound to the DashPay profile that no local
grant record holds, e.g. after a wallet restore or from another wallet with the
same identity. Dismiss hides those keys on this device until another one
appears; Revoke disables them with a master-key identity update, re-checking
first that each key is still unlinked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): drop the Key details card from the connection sheet

It is not in the design, and key info is out of scope for the MVP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): address pre-review findings on the Connections redesign

- Describe every login that may still authorize access, not only the newest one.
- Show Expired only when no login is still registering or awaiting a confirmed revoke.
- Keep the details sheet open while the list reloads on resume.
- Cancel an earlier details load when the sheet is reopened or closed.
- Warn that removing an older connection does not revoke its Platform access.
- Catch a failed save of dismissed profile keys and show the warning again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): show each key's current Platform limits on the details sheet

A key's budget and expiry can change on Platform without changing the key, so
the sheet reads the identity and describes each recorded key as Platform holds
it now. A key no longer on the identity keeps its recorded terms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): expose collapsible card headers as buttons with expanded state

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(connect): cover shared DashPay profile key listing, dismissal and revocation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): confirm on Platform that a connection expired before listing it

Another device can extend a key's expiry on Platform, so the expiry saved at
approval is only a hint. When a connection's saved expiry has passed, the list
reads the identity once and keeps the connection Active if a recorded key is
still enabled with a later expiry, scheduling the next refresh from it. If
Platform cannot be read, the connection shows as pending, never Expired. Live
connections are still listed without a Platform read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): keep the details selection while connection records reload

A reload empties the list until it finishes, so a missing row proves nothing
yet; the sheet shows a spinner and closes only if a finished read no longer
has the connection. This also survives the dialog being recreated mid-reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): give info buttons a button role and a topic-specific label

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): always show Connections in Tools when Connect is supported

Connections now offers to create a username, so a wallet without one no
longer needs the entry hidden. Only the Platform and Connect flags decide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): offer to create a username on Connections

Without a username, Connections shows the design's intro (Figma 40042:60147)
with a Create username button that starts the same flow as Join DashPay.
A request opened from the main scanner, the camera or a link lands on this
screen instead of an error toast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): offer to buy credits when the identity cannot pay to approve

Approve reads the identity balance first. When it is empty, nothing is
submitted, the request stays open, and the sheet offers Buy credits, the
same top-up the wallet uses before contact requests and profile edits. An
unreadable balance or a recovering approval goes ahead as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): one funding limit per connection, split between its keys

Per MO-1117, the approve sheet sets one funding limit and one expiry for the
whole connection:
- The limit starts at the total the app requests (Yappr: 0.5 + 0.01 DASH),
  capped at 5 DASH, and the user may go above or below it.
- It is split between the keys: a DashPay profile key gets 0.002 DASH (half
  the limit under 0.004 DASH) and the app's own key gets the rest.
- One expiry for all keys: the request, capped at a year, 90 days if none.
- Set custom limit opens a keypad sheet (Figma 40103:38833) that refuses 0
  and anything above 5 DASH; Set date opens the system date picker.
- Consent checks only the wallet's maximums; a recovering approval keeps the
  limits it was prepared with.
- The approve and details sheets show the keys' budgets together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): show when a connection was made and the funding left on its keys

Per MO-1117 (no designs; the wallet's existing patterns):
- Details show a Connected date. A record keeps connectedAt from the first
  time it became active; older records use when they became active.
- Details show the funding left on each enabled key (the app's own key and
  the DashPay profile key), read from Platform; an unreadable one shows as
  unavailable.
- Disconnect asks for confirmation first, since it revokes the app's keys
  on Platform for good. Removing an older connection from the list does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): reword what the app's own key may do with tokens

"Act as you in Yappr until <date>, including buying and sending its tokens.
It can spend up to <limit> DASH of your identity balance on fees and token
purchases, but can send any amount of your Yappr tokens."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): close the pre-review findings on limits, expiry and consent

- Judge a connection expired by Platform time, and count any live key of the
  app the wallet has no record of (e.g. registered on another device).
- A recovering approval keeps an unlimited budget an earlier version saved.
- Connection terms leave out keys a renewal disabled; a key without an expiry
  leaves the connection without one.
- The details sheet is bound to its connection's wallet scope, and reads the
  username before the scope-checked terms.
- The Buy credits prompt shows only while the wallet is unlocked.
- The approve sheet shows what each contract-group key covers, including
  members added later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* revert(connect): drop the contract-group scope lines from the approve sheet

The disclosure needs a design before it goes on the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): keep a disconnected connection's terms when its encryption key survives

A disconnect disables the authentication keys and keeps encryption keys, so
leaving out disabled keys left only the encryption key and the sheet showed
"No limit". Disabled keys are left out only while an authentication key is
still enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat(connect): run DashConnect on mainnet, gated on Platform protocol 14 (#1670)

* feat(connect): run DashConnect on mainnet, gated on Platform protocol 14

DashConnect is built into the prod flavour (SUPPORTS_CONNECT = true) and no
longer refuses production builds or mainnet. Instead every entry point and
operation requires the network to run Platform protocol version 14, which
brings key funding limits and expiry, contract-group bounds, remaining-budget
queries and the AppConnect response contract.

- ConnectAvailability: build support plus the protocol version the wallet
  already records (PlatformRepo.refreshProtocolVersion, which only goes up).
- Tools shows Connections only once the network is known to be new enough.
- The Connections screen and every repository operation check it, and report
  DashConnect as unavailable below version 14.
- A QR's network selector is matched to the wallet's network, so a mainnet
  wallet accepts n=m (it was compared with devnet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): mainnet key derivation and a retryable protocol check

- Application keys are derived with the wallet's network, so a mainnet
  wallet uses the mainnet coin type (both were derived as testnet).
- Tools reads the protocol version again on every resume until Connections
  is available, so an offline start or a network upgrade is noticed.
- A protocol version that could not be read is not reported as too old: the
  Connections screen stays retryable and a request says to try again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): count only a Platform version the network reported

A failed refreshProtocolVersion() does not return 0: the SDK keeps its per-network
starting version (13 on mainnet and testnet) and returns that. Offline after a fresh
launch, the gate read 13 as the network's answer and called a protocol 14 network too
old, removing the Connections screen's Retry.

The version now counts only after a proven query succeeds. PlatformRepo.readProtocolVersion()
reads Platform time for the identity, whose verified response sets the SDK's version,
and returns null when the network does not answer. ConnectAvailability keeps the
highest version read this way; Tools and the repository ask through it. The session's
checks run after its proven identity read instead of after a refresh.

A wallet without a username still gets the username prompt: the Connections screen
reads the version once the identity is known.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(connect): pin the network flag of approval key derivation

Approval derives authentication and encryption keys with isMainnet true on mainnet
and false elsewhere; going back to a hard-coded value now fails this test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(connect): drop the stale prod note from the QR routing comments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): read the Platform version without a wallet identity

The proven query behind the version read asked for the wallet's own
identity, so a wallet without a username never learned the version and
Tools kept the Connections entry hidden, the way to create a username.
The query now asks for an identity no wallet owns: its proven absence
carries the same verified metadata, so any wallet can read the version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(tools): keep Connections shown while the username loads

The username coroutine copied the state before its suspending read and
wrote that copy back afterwards, so an availability update in between
was undone. With the version already known, a later Tools visit hid the
Connections entry for good. Every writer now goes through update {}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): answer with the version kept when probes overlap

The cache kept the highest version read, but each probe answered with
its own. An older probe that read 13 and finished after a newer one read
14 reported the network too old, which hid the Connections screen while
Tools showed the entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): check the Platform version before offering a username

The screen skipped the version check for a wallet without a username,
because the version used to be read with the wallet's identity. Now that
any wallet can read it, the screen checks it first, so a network too old
for DashConnect is not offered a username to use it with. A restored
process does not replay the link, so the screen cannot rely on the link
check. Requests check the version before waiting for the identity too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(connect): keep a confirmed version when an overlapping probe fails

A probe that failed answered "unknown" before looking at the version
kept, so after another probe had confirmed protocol 14 it still made the
caller reject an incoming link or mark the records unavailable. A failed
probe now answers with the kept version too, and is unknown only when no
probe has confirmed one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat: v11.9.2 with updated translations and core 24 support (#1674)

* feat(connect): translate DashConnect strings into DE, PL, IT, ES, PT-BR, RU and FR

Add strings_app_connect.xml for the seven locales and the three DashConnect
strings in strings-dashpay.xml and strings.xml. Translated locally, not
pulled from Transifex; strings_app_connect.xml is not in .tx/config yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: v11.9.2 release notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: v11.9.2 (11090205) and pin dashj 24.0.0 checksums

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: update dashj to 24.0.0

* chore: drop Gradle dependency verification for dashj-core

The checksums pinned a locally built 24.0.0 whose JAR and module metadata
differ in packaging from the Maven Central release, which failed CI. The
release is immutable, so the verification file and the non-changing
override it needed are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: verify dashj-core 24.0.0 against the Maven Central release

Restores gradle/verification-metadata.xml, scoped to dashj-core, with the
SHA-256 hashes of the Maven Central 24.0.0 JAR, POM, module metadata,
sources and javadoc. The version force pins coordinates, not bytes, and
Maven Local is searched first, so the hashes are what keeps a different
local publication out of the build.

docs/app-connect-v2.md: describe the release pin and its verification,
the dashpay://connect carrier, and drop the stale SDK-build CI note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(merge): keep the DashConnect audit through the registry-driven wipe

Reset Wallet clears every live config through BaseConfig.clearAllLiveInstances()
before deleting datastore files. It called clearAll() directly, bypassing
DashConnectConfig.onWalletWiped(), which keeps grants and unknown purchase
receipts for a same-seed restore, so the audit was lost before either
preservation step ran. The registry now applies each config's own wipe
policy; onWalletWiped() defaults to clearAll(), so every other config is
unchanged.

DashConnectConfigTest now runs the production order (registry clear, then
file deletion). DatabaseMigrationTest includes the 21-to-22 migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: drop WalletDataAdapter's unused seam-lock registry, fix registry KDoc

Both left over from removing lockOutputsPayingTo with CrowdNode (PastaClaw on #1687).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>
Co-authored-by: QA Integration <eric.britten@dash.org>
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