Skip to content

feat!: managed identity top-up from asset lock (iOS + shared FFI)#4093

Merged
QuantumExplorer merged 6 commits into
v4.1-devfrom
feat/ios-managed-identity-topup
Jul 14, 2026
Merged

feat!: managed identity top-up from asset lock (iOS + shared FFI)#4093
QuantumExplorer merged 6 commits into
v4.1-devfrom
feat/ios-managed-identity-topup

Conversation

@shumkov

@shumkov shumkov commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

Closes #4092. Converges identity top-up from a Core asset lock onto a single managed path shared by iOS and Android, and wires it on iOS (previously unimplemented — the example-app executeIdentityTopUp was a notImplemented stub).

There were three fragmented top-up paths at different layers, none shared (see #4092). The correct "from asset lock" abstraction is the managed orchestrator IdentityWallet::top_up_identity_with_funding, which is already on v4.1-dev and does the full lifecycle (build/resolve the lock, IS→CL fallback, retries, persist balance). The only missing pieces were the thin FFI exports over it and the iOS wiring.

What was done?

Shared FFI (rs-platform-wallet-ffi) — two managed exports, both thin delegates to the upstream orchestrator (no reimplementation), giving top-up the same register+resume symmetry registration already has:

  • platform_wallet_top_up_identity_with_funding_signer (FromWalletBalance) — build a new lock from wallet balance. Extracted from feat(sdk): add Kotlin SDK and KotlinExampleApp (Android port of SwiftExampleApp) #3999.
  • platform_wallet_topup_identity_with_existing_asset_lock_signer (FromExistingAssetLock) — consume an already-tracked lock; the crash-recovery path for a top-up interrupted between Core confirmation and Platform submission. Extracted from the DIP-15 branch.

The IdentityTopUp transition is signed entirely by the asset lock's Core-side key (no identity-key signer), so both take only a MnemonicResolver core signer — key material never crosses FFI as plaintext.

Sub-floor funds guard — an amount between dust and Platform's processing-start minimum (50000 duffs) builds a real lock Core accepts but Platform rejects, stranding funds. Enforced in both the export (MIN_TOP_UP_DUFFS) and the iOS UI gate (defense in depth).

iOS (swift-sdk)ManagedPlatformWallet.topUpIdentityWithFunding + resumeTopUpWithAssetLock; example-app executeIdentityTopUp (now implemented) + executeIdentityTopUpResume, with catalog/menu entries. Resume reverses display-order txid → wire order and classifies the opaque "already consumed" rejection.

Retired the raw-key primitive — deleted dash_sdk_identity_topup_with_instant_lock(+_and_wait) and its Swift identityTopUp/topUpIdentity wrappers (zero app callers; the managed path supersedes it and avoids a raw 32-byte private key crossing FFI into non-zeroizable Swift Data).

Follow-ups

How Has This Been Tested?

  • cargo check + cargo clippy clean on platform-wallet-ffi and rs-sdk-ffi.
  • 8/8 new Rust guard unit tests pass — null-pointer guards for both exports + sub-floor (0 and MIN-1) rejection for the funding export.
  • ./build_ios.sh --target sim regenerates the cbindgen header (new symbols present, retired symbols gone); SwiftExampleApp builds against it.
  • Multi-agent code review (Rust FFI memory-safety + Swift correctness) returned clean.
  • Not yet run: the funded happy-path, IS→CL fallback, and resume-recovery require a live Core+Platform network (asset locks, IS/CL) — there is no hermetic funded-path harness in the repo (the registration twin has none either), so these are testnet UAT via the SwiftExampleApp. The existing two-step address top-up route is unchanged and still works.

Breaking Changes

Removes the FFI export dash_sdk_identity_topup_with_instant_lock (and ..._and_wait) from rs-sdk-ffi, and the public Swift SDK methods SDK.topUpIdentity(...) / identityTopUp(...). Any external consumer linking those symbols must migrate to the managed platform_wallet_top_up_identity_with_funding_signer / ManagedPlatformWallet.topUpIdentityWithFunding. No in-repo callers.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added identity top-ups funded through a new Core asset lock, with a minimum amount of 50,500 duffs.
    • Added recovery for interrupted top-ups using a previously tracked asset lock.
    • Added Swift SDK methods and example-app flows for initiating and resuming top-ups, including confirmation and clearer validation errors.
  • Changes

    • Replaced legacy instant-lock identity top-up APIs with address-based top-up APIs.
    • Updated identity top-up forms to accept funding amounts, account indexes, and asset-lock transaction details.

shumkov and others added 3 commits July 11, 2026 20:14
Add two managed FFI exports for topping up an existing identity from a
Core asset lock, both delegating to the upstream orchestrator
IdentityWallet::top_up_identity_with_funding (funding resolution, IS->CL
fallback, balance persist -- no reimplementation):

- platform_wallet_top_up_identity_with_funding_signer
  (AssetLockFunding::FromWalletBalance) builds and broadcasts a new lock
  from the wallet's balance.
- platform_wallet_topup_identity_with_existing_asset_lock_signer
  (AssetLockFunding::FromExistingAssetLock) consumes an already-tracked
  lock -- the crash-recovery path for a top-up interrupted between Core
  confirmation and Platform submission.

The IdentityTopUp transition is signed entirely by the asset lock's
Core-side key (no identity-key signer), so both take only a
MnemonicResolver core signer.

Guard the sub-floor funds trap: an amount between dust and Platform's
processing-start minimum (50000 duffs) builds a real lock Core accepts
but Platform rejects, stranding the funds. The funding export rejects
amount_duffs < MIN_TOP_UP_DUFFS before any broadcast.

Add null-pointer + sub-floor guard unit tests for both exports, and the
design spec under docs/ios-managed-topup/.

Refs #4092

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dash_sdk_identity_topup_with_instant_lock (+ _and_wait) required the
caller to build an InstantSend proof and pass a raw 32-byte asset-lock
private key across FFI into a non-zeroizable Swift Data. The managed
platform_wallet_top_up_identity_with_funding_signer path supersedes it
(key material stays behind the Keychain resolver, never crossing FFI as
plaintext) and the app has zero callers.

Delete the rs-sdk-ffi exports (topup.rs and its re-exports) and the Swift
identityTopUp / topUpIdentity wrappers. The rs-sdk TopUpIdentity trait is
kept -- the managed orchestrator uses it.

Refs #4092

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ManagedPlatformWallet.topUpIdentityWithFunding and
resumeTopUpWithAssetLock, mirroring the registration funding/resume pair,
over the new managed FFI exports. Implement the example-app
executeIdentityTopUp handler (previously a notImplemented stub) and a new
executeIdentityTopUpResume recovery handler, with catalog + menu entries.

The amount is Core-side duffs; the UI gates on the same 50000-duff floor
the FFI enforces so a sub-floor amount never reaches the network. The
resume handler reverses the display-order txid to wire order (matching
OutPointFFI, as CreateIdentityView.parseOutPointHex does) and classifies
the opaque "already consumed" rejection into a friendly message.

Refs #4092

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.1.0 milestone Jul 11, 2026
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@shumkov, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ef1a2619-8b23-450a-9c00-2ffeb5ed8c9e

📥 Commits

Reviewing files that changed from the base of the PR and between e489cdf and f449142.

📒 Files selected for processing (1)
  • packages/swift-sdk/SwiftExampleApp/TEST_PLAN.md
📝 Walkthrough

Walkthrough

Adds managed identity top-up and resume flows through Rust FFI, Swift SDK APIs, and the example app, while removing obsolete instant-lock top-up APIs.

Changes

Managed identity top-up

Layer / File(s) Summary
Managed FFI funding and recovery
packages/rs-platform-wallet-ffi/src/identity_top_up.rs, packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs
Adds signer-based FFI entrypoints for creating or consuming asset locks, validates pointers, enforces the 50,500-duff minimum, and returns updated balances with guard tests.
Swift managed APIs and transition contracts
packages/swift-sdk/Sources/SwiftDashSDK/Models/StateTransitionDefinitions.swift, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift
Adds funded and resume top-up APIs with outpoint and identity validation, FFI marshalling, signer lifetime handling, and balance results.
Example app top-up and resume flows
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionCategoryView.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift
Adds the resume transition, confirmation dialog, managed execution paths, balance persistence, and consumed-lock error translation.
API retirement and module wiring
packages/rs-sdk-ffi/src/identity/*, packages/swift-sdk/Sources/SwiftDashSDK/FFI/StateTransitionExtensions.swift
Removes obsolete instant-lock top-up exports and Swift wrappers, and renames SDK module wiring for top-up-from-addresses APIs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TransitionDetailView
  participant ManagedPlatformWallet
  participant PlatformWalletFFI
  participant IdentityWallet
  participant PersistentIdentity
  TransitionDetailView->>ManagedPlatformWallet: submit funded or resume top-up
  ManagedPlatformWallet->>PlatformWalletFFI: call signer-based top-up export
  PlatformWalletFFI->>IdentityWallet: create or consume asset lock
  IdentityWallet-->>PlatformWalletFFI: return updated balance
  PlatformWalletFFI-->>ManagedPlatformWallet: return FFI result and balance
  ManagedPlatformWallet-->>TransitionDetailView: return balance
  TransitionDetailView->>PersistentIdentity: persist updated balance
Loading

Suggested reviewers: llbartekll, zocolini, lklimek, quantumexplorer

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds the managed top-up and resume FFI exports, wires iOS and the example app, removes the raw IS-only path, and adds tests while preserving the address route.
Out of Scope Changes check ✅ Passed The changes stay focused on identity top-up convergence and the related SDK/UI wiring, with no unrelated feature work apparent.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: managed identity top-up from asset lock with shared FFI and iOS support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ios-managed-identity-topup

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

❤️ Share

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

@thepastaclaw

thepastaclaw commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit f449142)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/rs-platform-wallet-ffi/src/identity_top_up.rs (1)

144-154: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Funds-safety floor is duplicated across the FFI boundary. MIN_TOP_UP_DUFFS (Rust) and minTopUpDuffs (Swift) both hardcode 50_000 independently; the Swift copy already documents that it's meant to mirror the Rust value, so a future change to either side risks silent drift.

  • packages/rs-platform-wallet-ffi/src/identity_top_up.rs#L144-L154: keep as the source of truth; optionally expose it via a small FFI getter (e.g. platform_wallet_min_top_up_duffs()) so Swift can read it instead of re-declaring the literal.
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift#L614-L619: if the getter above is added, replace the hardcoded 50_000 with a value fetched from the FFI at startup/first use.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet-ffi/src/identity_top_up.rs` around lines 144 -
154, Keep MIN_TOP_UP_DUFFS in identity_top_up.rs as the sole source of truth and
expose it through a small FFI getter such as platform_wallet_min_top_up_duffs().
In packages/rs-platform-wallet-ffi/src/identity_top_up.rs:144-154, add the
getter using MIN_TOP_UP_DUFFS; in
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift:614-619,
replace the hardcoded minTopUpDuffs value with the getter result at startup or
first use.
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift (1)

614-650: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Amount floor is only enforced reactively, not gated in the UI.

Per the PR's design doc, the sub-floor amount should disable submit + show a "minimum…" hint proactively; here it's only caught by a guard after the user taps Execute. Funds are already safe (the guard runs before any FFI/broadcast call), so this is a UX-completeness gap versus the documented intent rather than a correctness bug.

Consider surfacing Self.minTopUpDuffs in isButtonEnabled/as inline helper text for the amount field so the constraint is visible before submission, mirroring CreateIdentityView.currentMinFundingDuffs.

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

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift`
around lines 614 - 650, The minTopUpDuffs constraint is enforced only after
submission instead of proactively in the UI. Update isButtonEnabled and the
amount-field presentation in TransitionDetailView to disable Execute and show
inline minimum-amount guidance when the parsed amount is below
Self.minTopUpDuffs, mirroring CreateIdentityView.currentMinFundingDuffs while
preserving executeIdentityTopUp’s validation guard.
🤖 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
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift`:
- Around line 671-723: Add a confirmation gate to the generic transition
execution flow before invoking executeIdentityTopUpResume and
wallet.resumeTopUpWithAssetLock. Present the selected identity and asset-lock
outpoint (txid and vout), require explicit user confirmation, and only then
perform the resume; canceling must leave the transition untouched.

---

Nitpick comments:
In `@packages/rs-platform-wallet-ffi/src/identity_top_up.rs`:
- Around line 144-154: Keep MIN_TOP_UP_DUFFS in identity_top_up.rs as the sole
source of truth and expose it through a small FFI getter such as
platform_wallet_min_top_up_duffs(). In
packages/rs-platform-wallet-ffi/src/identity_top_up.rs:144-154, add the getter
using MIN_TOP_UP_DUFFS; in
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift:614-619,
replace the hardcoded minTopUpDuffs value with the getter result at startup or
first use.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift`:
- Around line 614-650: The minTopUpDuffs constraint is enforced only after
submission instead of proactively in the UI. Update isButtonEnabled and the
amount-field presentation in TransitionDetailView to disable Execute and show
inline minimum-amount guidance when the parsed amount is below
Self.minTopUpDuffs, mirroring CreateIdentityView.currentMinFundingDuffs while
preserving executeIdentityTopUp’s validation guard.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ee2df99-41b4-418d-b42e-714cf968c8db

📥 Commits

Reviewing files that changed from the base of the PR and between f7d7c8d and 4e74559.

📒 Files selected for processing (10)
  • docs/ios-managed-topup/SPEC.md
  • packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs
  • packages/rs-platform-wallet-ffi/src/identity_top_up.rs
  • packages/rs-sdk-ffi/src/identity/mod.rs
  • packages/rs-sdk-ffi/src/identity/topup.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/FFI/StateTransitionExtensions.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Models/StateTransitionDefinitions.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionCategoryView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift
💤 Files with no reviewable changes (3)
  • packages/rs-sdk-ffi/src/identity/topup.rs
  • packages/rs-sdk-ffi/src/identity/mod.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/FFI/StateTransitionExtensions.swift

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Two thin FFI exports for managed identity top-up (new-lock and resume-from-existing-lock) are wired correctly into iOS, but the sub-floor amount guard (MIN_TOP_UP_DUFFS = 50_000) is 500 duffs short of the actual consensus minimum at the currently active protocol version, letting a real Core asset lock be broadcast and then permanently stranded by Platform. The resume-from-tracked-lock flow also skips the confirmation step the PR's own spec document mandates before irreversibly crediting an identity. A minor defensive-coding inconsistency (missing zero-sentinel on one of two new sibling FFI exports) was independently flagged by both Claude lanes.

Source (experiment sonnet-primary-opus-quarter-sample-20260710, cohort sonnet_primary, bucket 3): reviewers codex/general=gpt-5.6-sol(completed_parseable_output_with_nonzero_wrapper_exit); sonnet5/general=claude-sonnet-5(completed); codex/ffi-engineer=gpt-5.6-sol(completed); sonnet5/ffi-engineer=claude-sonnet-5(completed); verifier=verifier-sonnet5-4093-1783776297=claude-sonnet-5; orchestrator=openai/gpt-5.6-sol reasoning=high (orchestration-only, not a reviewer/verifier).

🔴 2 blocking | 🟡 1 suggestion(s)

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

In `packages/rs-platform-wallet-ffi/src/identity_top_up.rs`:
- [BLOCKING] packages/rs-platform-wallet-ffi/src/identity_top_up.rs:154: MIN_TOP_UP_DUFFS is 500 duffs below the actual consensus floor at the active protocol version, allowing broadcast locks that Platform will strand
  `MIN_TOP_UP_DUFFS = 50_000` only accounts for `required_asset_lock_duff_balance_for_processing_start_for_identity_top_up` (the v0 fee calculation). But `IdentityTopUpTransition::calculate_min_required_fee` dispatches on `platform_version.dpp.state_transitions.identities.calculate_min_required_fee_on_identity_top_up_transition`, and the currently active/latest protocol versions (v11, v12 — both wired to `STATE_TRANSITION_VERSIONS_V3`) set this to `1`, selecting `calculate_min_required_fee_v1`. That path adds `identity_topup_base_cost` (500,000 credits = 500 duffs at `CREDITS_PER_DUFF = 1000`) on top of the asset-lock floor: `base_cost.saturating_add(asset_lock_base_cost)` = 50,500,000 credits = 50,500 duffs. This is enforced on-chain in `rs-drive-abci/.../identity_top_up/transform_into_action/v0/mod.rs:59` via `required_balance = self.calculate_min_required_fee(...)`, which the asset-lock proof value is checked against.

  Amounts from 50,000–50,499 duffs pass this FFI guard and the identical Swift mirror at `TransitionDetailView.swift:619`, build and broadcast a real Core transaction spending real UTXOs, and are then rejected by Platform for insufficient asset-lock balance — permanently stranding the funds in a lock that can never complete the top-up. This is exactly the funds-safety scenario the guard's own doc comment (lines 145-153) says it exists to prevent. Notably, the PR's own `docs/ios-managed-topup/SPEC.md:216-217` cites the same `v3.rs:21` file for the 50,000 floor but misses that the same file's `calculate_min_required_fee_on_identity_top_up_transition: 1` (line 31) switches the active calculation to include the base cost.
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/identity_top_up.rs:201-209: out_new_balance has no zero-sentinel before fallible work, unlike its sibling added in the same PR
  `platform_wallet_top_up_identity_with_funding_signer` never writes a sentinel to `*out_new_balance` before the fallible `amount_duffs` guard, `block_on_worker` call, or `unwrap_result_or_return!`. If any of those return early, `out_new_balance` is left at whatever the caller's stack held. Its direct sibling added in this same PR, `platform_wallet_topup_identity_with_existing_asset_lock_signer` (`identity_registration_funded_with_signer.rs:282-283`), explicitly writes `*out_new_balance = 0;` right after the null checks with the comment "FFI-safe sentinel before any fallible work" — showing the pattern is known and applied elsewhere in this PR, just not here. This is a public `extern "C"` export whose own safety doc only requires `out_new_balance` to be "writable", not pre-zeroed, so a caller that reads it without checking the return code first sees defined `0` from one export and garbage from the other. Currently masked because the only Swift caller local-initializes `newBalance = 0` and never reads it after a thrown error.

In `packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift`:
- [BLOCKING] packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TransitionDetailView.swift:688-702: Resume-from-tracked-lock executes immediately with no confirmation, contradicting the PR's own spec
  `executeIdentityTopUpResume` parses the outpoint and calls `wallet.resumeTopUpWithAssetLock` immediately with no confirmation step. A tracked asset lock isn't bound to a specific identity — any self-owned identity can consume it — and this screen lets the user pick an arbitrary identity via `selectedIdentityId` and fire the resume in one action. `docs/ios-managed-topup/SPEC.md:174-183` (added in this same PR) explicitly calls this out: \"Add a confirmation step ('top up identity X with lock Y?') before invoking — resume directs a tracked lock at whatever identity is selected... a stray lock could still land on the wrong self-owned identity, which is not undoable.\" No confirmation UI exists anywhere in `TransitionDetailView.swift` or `TransitionCategoryView.swift` for this flow — the implementation doesn't follow its own design doc on a funds-safety point.

Comment thread packages/rs-platform-wallet-ffi/src/identity_top_up.rs Outdated
Comment thread packages/rs-platform-wallet-ffi/src/identity_top_up.rs
- MIN_TOP_UP_DUFFS 50000 -> 50500. The active v1 fee calc
  (IdentityTopUpTransition::calculate_min_required_fee_v1) adds
  identity_topup_base_cost (500000 credits = 500 duffs) on top of the
  50000-duff asset-lock floor, so the real consensus minimum is 50500
  duffs (enforced on-chain by tx_out_credit_value < required_balance).
  Amounts 50000-50499 previously passed the guard, built and broadcast a
  real Core lock, and were then stranded by Platform -- the exact
  funds-safety trap the guard exists to prevent. Bumped in the FFI const,
  the Swift UI gate, the catalog help, and the spec.

- Add the `*out_new_balance = 0` sentinel before fallible work in the
  funding export, matching its FromExistingAssetLock sibling, so an
  early-return leaves a defined value.

- Gate the example-app resume flow behind a confirmation dialog
  summarizing the target identity + outpoint. A tracked asset lock is not
  bound to an identity; resume directs it at whatever identity is
  selected, and a stray lock landing on the wrong self-owned identity is
  not undoable.

Addresses PR #4093 review (thepastaclaw, CodeRabbit).
Refs #4092

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

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Prior Reconciliation: All 3 prior findings are FIXED and independently verified against source at head 24328aacc0a80019386fff760ad3d81200a15e68. MIN_TOP_UP_DUFFS is now 50_500 duffs, which is mathematically correct against the actual consensus formula (identity_topup_base_cost 500_000 credits + required_asset_lock_duff_balance_for_processing_start_for_identity_top_up 50_000 duffs × CREDITS_PER_DUFF 1000 = 50,500,000 credits = 50,500 duffs), mirrored correctly in the Swift minTopUpDuffs constant, UI help text, and SPEC.md. The resume-from-tracked-lock flow now gates on a .confirmationDialog before firing. out_new_balance gets a zero sentinel before fallible work in the funding-signer export, matching its sibling. Carried-Forward Prior Findings: None. New Findings In Latest Delta: None. Additional Cumulative Findings: None — only the 4 expected files changed since the prior review, and all four agent lanes converge with no disagreement.

Source: (experiment sonnet-primary-opus-quarter-sample-20260710, cohort sonnet_primary, bucket 3, trigger new_push, prior head 4e74559): reviewers codex/general=gpt-5.6-sol(completed); sonnet5/general=claude-sonnet-5(completed); codex/ffi-engineer=gpt-5.6-sol(completed); sonnet5/ffi-engineer=claude-sonnet-5(completed); verifier=verifier-sonnet5-4093-1783778586=claude-sonnet-5; orchestrator=openai/gpt-5.6-sol reasoning=high (orchestration-only, not a reviewer/verifier).

shumkov and others added 2 commits July 14, 2026 12:19
Remove docs/ios-managed-topup/SPEC.md — it was the research/review design
doc, not intended to ship in the repo. Net effect on the PR: the spec is
no longer added (add + remove cancels in the base...head diff).

Refs #4092

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ID-13 ("Top up identity, builder path") was marked retired/notImplemented
stub. This feature un-retires it: the builder path now runs a managed
Core-funded top-up (executeIdentityTopUp -> topUpIdentityWithFunding ->
platform_wallet_top_up_identity_with_funding_signer), building a new Core
asset lock from wallet balance -- distinct from ID-05/ID-06, which spend
already-funded Platform addresses. Flip it to 🧪 (builder-reachable),
Tier=Common, and note the 50,500-duff floor and the removed raw-key path.

Add ID-16 for the crash-recovery resume flow (executeIdentityTopUpResume
-> resumeTopUpWithAssetLock ->
platform_wallet_topup_identity_with_existing_asset_lock_signer), incl. the
confirmation dialog and the clean-failure behavior for
untracked/consumed/foreign outpoints. Extend the §6 Identity span to
ID-01..16.

Funded e2e for both is not yet run (testnet UAT pending) -- noted in the
rows, not claimed as verified.

Refs #4092

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

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.42%. Comparing base (f7d7c8d) to head (f449142).
⚠️ Report is 18 commits behind head on v4.1-dev.

Additional details and impacted files
@@             Coverage Diff              @@
##           v4.1-dev    #4093      +/-   ##
============================================
+ Coverage     87.40%   87.42%   +0.02%     
============================================
  Files          2639     2643       +4     
  Lines        333066   333632     +566     
============================================
+ Hits         291103   291684     +581     
+ Misses        41963    41948      -15     
Components Coverage Δ
dpp 88.44% <ø> (ø)
drive 86.14% <ø> (ø)
drive-abci 89.51% <ø> (+0.06%) ⬆️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.88% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 49.55% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@QuantumExplorer QuantumExplorer merged commit 97477d1 into v4.1-dev Jul 14, 2026
20 checks passed
@QuantumExplorer QuantumExplorer deleted the feat/ios-managed-identity-topup branch July 14, 2026 18:14
bezibalazs added a commit that referenced this pull request Jul 14, 2026
…nd-example-app

Conflicts: Cargo.lock (theirs + re-resolve) and identity_top_up.rs (base
#4093's rework — MIN_TOP_UP_DUFFS floor, out_new_balance sentinel, guard
tests — supersedes the branch's older zero-guard).

Breaking-FFI adaptations for the Android JNI/Kotlin side:
- #4093/#4126: shielded identity-create/transfer/unshield/withdraw gained
  a mnemonic_resolver_handle param — threaded through the JNI exports,
  FundingNative externs, and PlatformWalletManager; the
  transfer/unshield/withdraw trio now runs under teardownGate.op (they
  borrow the manager's resolver, so the round-46 ungated rationale no
  longer holds).
- #4127 typed provider keys: the pool-entry write trampoline forwards
  exactly public_key_len meaningful bytes (blob self-describing by
  length: 33 ECDSA / 48 BLS / 32 EdDSA); the load path rebuilds the
  48-byte slot + key_type_tag from the stored blob length. No Kotlin
  bridge/descriptor change needed.
- AccountSpecFFI lost derived_platform_node_keys(+count) (moved into the
  typed core-address rows) — the round-42 null-init is dropped.
- #4126 Orchard viewing keys: the three new vtable slots are None —
  binds keep resolving the seed via the mnemonic resolver (the
  documented fallback); the seedless-bind persistence port is tracked as
  a follow-up.

Verified: cargo fmt, JNI check (shielded), workspace clippy
--all-features -D warnings, platform-wallet-ffi lib tests (170), gradle
:sdk+:app tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Wire iOS managed identity top-up from asset lock + converge the 3 top-up paths (v4.1-dev)

3 participants