Skip to content

Use SDK protocol autodetection and refresh before exposing new wallet runtimes #1228

Open

Description

@llbartekll

Status (2026-11-04). Narrowed after #1232 (merged 2026-10-27, ba3a09547):

  • Done in feat(wallet): show a nonblocking protocol update advisory #1232: the testnet pin is gone (SwiftDashSDKHost.platformVersion(for:) returns 0 for every network); a proved epoch query runs in the background after runtime publication and on foreground return (WalletProtocolCompatibilityController), with stale-runtime results discarded; the "update available" advisory is shown when verified > maximum_supported_version.
  • Still open, the only remaining scope of this issue: proposed change 2 — call refreshProtocolVersion() for a newly created active-network runtime before it is exposed to version-sensitive writes (fee preflight, asset-lock funding). feat(wallet): show a nonblocking protocol update advisory #1232 explicitly accepted the cold-start window where auto mode runs at the network floor (13 on testnet) until the first verified response, so a Core → Shielded prepared in that window can still build ShieldFromAssetLock V0 and be rejected after the Core lock exists. Today the only explicit refresh in the app is ContestedUsernameFee.
  • Proposed changes 1 and 6 are done; 3, 4, 5 and 7 apply only insofar as they support change 2. The acceptance criteria and validation below are kept for reference.

Goal

Use the protocol-version autodetection and explicit refresh already available in SwiftDashSDK. Remove the app's hardcoded testnet protocol pin and refresh the SDK before exposing a newly created runtime to version-sensitive wallet operations.

Scope: dashwallet-ios only. No Platform SDK / rust-dashcore changes are required by this issue. A complete “update the app” flow for unsupported protocols is deferred.

Why

Core → Shielded failed on testnet when the app constructed ShieldFromAssetLockTransitionV0 using protocol 13 while the network required V1 under protocol 14. The Core asset lock had already been created before the Platform operation failed.

PR #1222 changes the testnet pin from 13 to 14 as an immediate workaround. An explicit pin disables protocol-version learning and makes refreshProtocolVersion() a no-op. It therefore requires another app change when the network next moves to a protocol requiring new transition formats.

Simply replacing 14 with 0 is insufficient: an unpinned SDK starts at its per-network floor and learns newer supported versions from verified responses. A first write can otherwise run before any such read.

Verified baseline

TestFlight 9.1.4 (11) was built from:

In that app revision, SwiftDashSDKHost.platformVersion(for:) returns 14 for testnet and 0 for mainnet/devnet/regtest. Its comment explicitly notes that the app does not call refreshProtocolVersion().

The older local develop checkout still pins testnet to 13. Implement against the target branch's actual state; do not assume the TF QA ref is identical to develop.

Existing SDK capability

SwiftDashSDK already exposes SDK.refreshProtocolVersion():

  • invokes a proven getEpochsInfo read;
  • learns a newer supported protocol version after proof/signature verification;
  • shares the learned version with clones of the same underlying SDK, including the wallet manager;
  • does not refresh a pinned SDK.

The relevant Rust implementation is Sdk::refresh_protocol_version.

Important limitations to preserve explicitly

This is a best-effort API, not a reliable compatibility handshake:

  • A failed network/proof fetch is logged by Rust; the method can still return the previously stored version as success.
  • A newer version unknown to the linked SDK is logged and is not adopted; the stored older version remains.
  • A returned version alone does not prove that a fresh check succeeded or that the network does not require a newer version.

Do not label a returned number as “network compatibility confirmed”, infer “update required” from a timeout, or claim that this issue solves unknown-protocol handling. No new SDK status/error contract is part of this scope.

Proposed iOS changes

  1. Configure testnet with platformVersion: 0, as mainnet already is. Preserve existing devnet/regtest support restrictions; do not expand the network scope.
  2. For each newly created active-network SDK runtime, call the existing refreshProtocolVersion() after SDK creation and before making the runtime available to version-sensitive operations, fee preflight, or new asset-lock funding. The intended ordering is SDK creation → refresh attempt completes → runtime becomes usable.
  3. Cover cold startup, network switching, and runtime recreation through a shared lifecycle path. Ensure a stale asynchronous result from the previous network/runtime cannot publish readiness for the new one.
  4. Run the blocking FFI refresh off the main thread, using the existing SDK initialization execution model or an appropriate equivalent. Avoid freezing navigation or the launch UI.
  5. Use the existing SDK request timeout/retry policy; do not add an unbounded launch retry loop. Preserve usable offline/error behavior. Handle errors actually surfaced by the Swift API through the existing lifecycle error handling, and document that internal best-effort fetch failures are not distinguishable through this return value.
  6. Add concise diagnostics: network, autodetect configuration, refresh attempt duration, returned SDK protocol version, and any surfaced error. Do not log secrets or describe the returned version as independently confirmed current network state.
  7. Update stale pin-related comments and relevant tests/documentation. Keep the linked framework provenance explicit; autodetection cannot supply support for protocol versions absent from that framework.

Refreshing on foreground resume is a useful subsequent lifecycle improvement, but does not need to expand this first issue beyond startup/network-switch/runtime-creation behavior.

Acceptance criteria

  • Testnet and mainnet SDKs are unpinned.
  • Each newly initialized active-network runtime attempts explicit refresh before it can be used for version-sensitive writes.
  • A supported version advertised in verified metadata is used by the same SDK/wallet-manager instance that performs the operation.
  • The application remains responsive while refresh runs, with no indefinite startup waiting added by the app.
  • Changing networks while initialization is in progress cannot apply the old runtime's completion to the new network.
  • Failed/offline refresh is not presented as successful network compatibility verification or as evidence that an app update is required.
  • The implementation does not promise to prevent all writes after an internally swallowed refresh failure; that requires a stronger SDK contract and is deferred explicitly.

Validation

Focused automated coverage

  • SDK configuration uses autodetect on testnet and mainnet.
  • A controllable/delayed refresh verifies that the active runtime is not exposed to spending before the refresh attempt returns.
  • Surfaced errors and network switches do not leave stale initialization tasks publishing the wrong runtime.
  • Exercise existing lifecycle tests and a clean dashpay arm64 simulator build using the target branch's supported test setup.

Live testnet smoke test

  • Record app SHA, Platform/framework provenance, and framework checksum.
  • Cold-start with autodetect and observe the returned protocol version after refresh. At the time of diagnosis the testnet version was 14; validate the network state at test time rather than making 14 a permanent app constant.
  • Switch mainnet → testnet and back; verify separate SDK instances refresh for the correct network.
  • Start offline / with an unreachable endpoint, then restore connectivity and recreate or restart the runtime. Record actual behavior, including the possibility of an unchanged returned version after an internally logged failure.
  • After Core wallet synchronization is ready, execute one dedicated testnet Core → Shielded smoke test and verify Platform success. Do not mix this with the incomplete-Core-sync spending bug in Core spends allowed during Keychain recovery sync can select already-spent inputs #1227.

Preliminary probe already performed (not full app validation)

On 2026-10-20, a standalone FFI probe ran inside an iOS simulator using a local framework reporting 5.0.0-rc.1:

Configuration Explicit refresh result Observation
Testnet, platform_version = 0 14 approximately 1 second; trusted provider quorum cache populated
Testnet, platform_version = 13 13 immediate no-op; quorum cache remained empty

No wallet was loaded, no mnemonic accessed, and no transaction was created or broadcast. This verifies the existing refresh mechanism on that local binary only; it is not an end-to-end test of TF 9.1.4(11), startup ordering, offline behavior, or shielding.

Probe framework simulator archive SHA-256: b30588436a8ca6968302d6c382aaf6bb6c2c8ea67026a6eef7bd35ec1eadcceb.

Out of scope / related work

No application code has been changed as part of filing this issue.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions