Repository navigation
Consolidate every open iOS PR: 4.2.1 (2026.11.11), redesign and brand, config, subscription and test fixes - #1326
Open
takemiyamakoto wants to merge 105 commits into
Open
takemiyamakoto wants to merge 105 commits into
takemiyamakoto wants to merge 105 commits into
Conversation
Two Polkaswap screens broke on iPhone with the 4.2.0 redesign (#1305): - The token picker stretched its search field over most of the sheet and left the list a strip at the bottom. SearchTextField took its size from BackgroundedContentControl's empty contentView, a zero height, and #1305 replaced its fixed height with a 44 pt minimum, so the sheet's stack view stretched the field. The field now reports its own height and hugs it, and the list and its empty state share a container so the hidden empty view no longer fights the list's height. - Preview pushed the confirmation inside the new swap tab, where the tab bar and the raised Polkaswap button covered Confirm and the header sat under the status bar. The confirmation now hides the tab bar and keeps its header and button inside the safe area. Both tests fail on the previous code: the search field measured 653 pt in an iPhone-sized sheet, and a tap on Confirm reached a tab button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 4.2.0 a swap quote could take minutes to appear. Since #1305 every quote request carried its own ID and only the newest request's reply was shown, while every SORA block (about 6 s) cancelled the pending request, hid the shown quote and asked again. Any reply slower than a block was therefore thrown away, block after block, until one happened to be fast enough. Quotes also queued in the app-wide operation queue behind unrelated work, waited 0.8 s after each keystroke, and replaced block subscriptions kept refreshing because the subscription service cancels them as requests, which leaves them running on the node. - A reply is shown when it answers the question on screen (pair, amount, direction, market) and nothing newer is shown, so a refresh no longer invalidates the reply already on its way. Replies to an old amount and older replies are still dropped. - A block refresh keeps the shown quote, sends nothing while a reply is awaited, and a failed refresh keeps the quote instead of clearing the inputs behind an alert. - Quotes go straight to the SORA socket with a 10 s timeout; fees and pair checks use the swap screen's own operation queue. - Typing waits 0.3 s instead of 0.8 s. - Updates from replaced block subscriptions are ignored. In the simulator, against the production SORA nodes, the time from keystroke to quote fell from about 1.8 s to 0.7 s. The new presenter test fails on the previous code: the awaited reply is discarded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026.11.5 went to TestFlight and App Review on 4 November 2026, so it joins the consumed builds and becomes a supported in-place upgrade predecessor in the usability gate. The startup capture keeps accepting it under its own name, every release tool now pins 2026.11.6, and the build-number check's test treats 2026.11.5 as the latest consumed build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apple approved 4.2.0 with build 2026.11.5 (Pending Developer Release on 7 November 2026), which closes 4.2.0 to new uploads, so the Polkaswap fixes ship as 4.2.1 (2026.11.6). The Release target, the archive, identity, signed-artifact and build-number checks, the usability gate and fastlane now expect 4.2.1. The startup capture still expects 4.2.0 for every earlier build and 4.2.1 only for 2026.11.6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The self-test parsed the check's output with a literal "Release 4.2.0" and used 4.2.0 in its version-drift cases, so it stopped recognising a passing repository once the release moved to 4.2.1. It now reads the version and build from the check's output and uses them in every case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
remote-config/development/chains/v13/chains_dev.json listed Avail DA Mainnet (b91746b4...) twice, as the shared-features-utils f2ed4ccf list it was imported from already did. Debug and Fearless Dev builds read this list (F_DEV), and ChainSyncService.sanitizingRemoteChains refuses a list that repeats a chainId, so every sync failed and those builds kept only Bitcoin, Solana and Taira: no Polkaswap, no Substrate networks. Both copies were identical to the single production entry. Keep the one at the production position (after Viction, next to Avail Turing Testnet) and delete the other; no other entry changes. - Re-pin the file's ownedSha256 and log the correction in config/remote-config-imports.json. - scripts/verify-remote-config.py now fails on a versioned chain list that repeats a chainId; the previous audit passed this list. - Refresh the owned-registry table in docs/ChainRegistryVersions.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the owner's word, 4.2.0 (2026.11.5) was removed from review, its version record renumbered to 4.2.1 with build 2026.11.6, and submitted on 7 November 2026 at 10:06 UTC. It is Waiting for Review with a manual release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WebSocketEngine.unsubsribe(_:) never ended a subscription. It decoded the subscribe request's params as [[Data]] (storage keys are hex, not base64, so this usually threw before anything was sent) and otherwise sent state_unsubscribeStorage with the storage keys. A SORA mainnet node (4.8.13, 2026-11-07) answers that with `false` and keeps notifying; the completion also decoded the boolean answer as Data, so the local handler was never removed either. Now the handler is removed at once and the node gets the subscription ID it assigned, through the unsubscribe method that matches the subscribe method (chain_subscribeNewHeads -> chain_unsubscribeNewHeads, and so on; the node answers `false` to a mismatched method). A subscription still waiting for its ID is ended when the ID arrives and is not resent after a reconnect; a subscribe request that was never sent is dropped, while a guarded write is never withdrawn. Watchers registered without a subscribe request, such as extrinsic status streams, are removed locally. Tests: SSFUtilsTests drive a real engine against a node double that answers like the live node; the AuthorizedRPC contract suite covers the locked send path and the guarded case (38 cases). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PolkaswapRemoteSubscriptionService.unsubscribe() called cancelForIdentifier, which only withdraws requests that have not been answered. Every pair change on the swap screen therefore left another SORA block subscription running. It now calls the engine's unsubscribe, and keeps every subscription ID it creates (a second subscribe call no longer overwrites the first one's IDs). Tests run a real WebSocketEngine against a scripted SORA node: unsubscribe ends the subscription on the node, five pair changes leave one subscription, and releasing a SubstrateTransactionObserver removes its watchers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ain types The bundled fallback chain list (fearless/Modules/LiquidityPools/LiquidityPoolDetails/Resources/chains.json) repeated a block of three entries: Manta Pacific Mainnet (169), Enjin Relaychain (d8761d3c...) and Liberland (6bd89e05...). Drop the second block. The Manta Pacific and Enjin copies were identical. The dropped Liberland copy listed only LLD and no XCM; the kept copy lists LLD, LLM and XOR with the SORA route, as the production and development lists do. The order now matches the development list (76 -> 73 chains). ChainSyncService reads this file only when ChainSyncService.fetchLocalData is true (false today); sanitizingRemoteChains would refuse the whole list for these repeats. remote-config/production/chains/all_chains_types.json listed the retired SORA Kusama parachain (6d8d9f14...) twice: runtime_id 1 with numeric type ids at index 54, and the runtime_id 14 entry that shared-features-utils 8791e434 appended in January 2025 at index 98. Every reader of the file (ChainsTypesSyncService, SnapshotHotBootBuilder and the SSF ChainsTypesSyncService) folds it into a map keyed by chainId, so the last entry wins. Drop the index-54 entry and keep the one builds use; the map the app builds is unchanged (110 -> 109 entries, 109 chainIds). Re-pin the chain types file's ownedSha256 (ced6c59d... -> cbef0d2d...), log both edits in config/remote-config-imports.json and refresh the registry table in docs/ChainRegistryVersions.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and the bundled fallback scripts/verify-remote-config.py now also fails when: - the bundled fallback chains.json or types.json (fearless/Modules/LiquidityPools/LiquidityPoolDetails/Resources) repeats a chainId. ChainSyncService.sanitizingRemoteChains refuses a whole chain list that does; - a chain types file (remote-config/*/chains/all_chains_types.json) repeats a chainId. Its readers keep the last entry and drop the others silently; - config/remote-config-imports.json is missing or unreadable, a pin is malformed or repeated, a pinned file is missing (exact-case paths inside remote-config/ only), or a pinned file's SHA-256 differs from its ownedSha256. Nothing checked these pins before. The output gains a pinnedFiles count. Other documents (for example the XCM files) may still repeat a chainId. Twelve tests added (28 pass); the docs describe the checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
verify-native-source.py accepted SOURCE_PACKAGES_DIR from its callers but never read it, so a moved tag or an edited package checkout passed the check. It now compares every pinned checkout's HEAD with its revision and refuses local changes, using git --no-optional-locks so the check never writes. verify-pods.py and config/pods-source-pins.json record digests of the 11 pods compiled into the app or its tests and of the rswift and swiftformat executables that Xcode runs. The rswift digest equals the upstream 6.1.0 release binary. Recorded from the reviewed 4.2.1 install. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
createPublicKeySoft copied SR25519_KEYPAIR_SIZE (96) bytes out of a 32-byte stack buffer, reading stack memory into the key and always failing. Nothing calls it today; it now copies the 32-byte public key and a test checks it against the soft-derived key pair. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tonkeeper/ton-api-swift 0.1.7 has no licence file. Packages/TonAPI holds the same client generated here by Apple's swift-openapi-generator 0.3.1 (Apache-2.0) from the MIT-licensed opentonapi specification, with provenance in its README. Client.swift is byte-identical to ton-api-swift's; Types.swift is byte-identical after postprocess.py drops Action._type, as ton-api-swift did, so a new tonapi action kind cannot fail a history page. scripts/deps/regenerate-tonapi.sh rebuilds the pinned generator and fails if the committed sources differ from fresh output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner, 2026-11-07: fix the key-exposure audit findings, and "don't use any closed source libraries. make everything you need here in this repo, open source". Closed and non-open-source code: - Delete the closed Kotlin/Native sorawallet framework, loaded at every launch. Its only use, Polkaswap strategic-bonus APY, is now Swift (PolkaswapAPYWorker, PolkaswapPoolApyMapper) against pi.soramitsu.io; the framework's SubQuery endpoint answers 503. - Remove WalletConnect (reown-swift, WalletConnect Community License) with its prebuilt yttrium binary and five Reown-only packages. A one-time clean-up deletes only com.walletconnect.sdk generic passwords and WalletConnect defaults. Keep the Chainlink ABI code under its web3swift and MEWwalletKit licences. - Build against the in-repo TonAPI package instead of ton-api-swift. Key exposure: - The emergency recovery export becomes a password-encrypted file (scrypt plus XSalsa20-Poly1305, 8+ characters, verified before sharing) and never shows a secret; scripts/recovery/decrypt-emergency-material.py opens it. - Forget wallet (now PIN-gated) and logout erase the Keychain secrets of removed wallets that were backed up, after their rows are gone. Orphaned keys and never-backed-up wallets' keys stay recoverable through the encrypted export. - PIN lockout with backoff persisted in the Keychain, constant-time compare, biometric enrollment checks that trust an existing install's first unlock, a 5-minute monotonic background re-lock, and PIN or biometrics before every fund-moving or staking submission. - JSON exports need 8+ characters; secret files get complete protection, their own directories and removal after sharing; Inbox copies and old leftovers are removed. - Remote SVGs are filtered before SVGKit; seed and key share sheets exclude Copy; secrets hide while the screen is captured and screenshots warn; keyboard extensions are refused. - TON Connect never signs a ton_proof for a site its manifest does not come from, and gets a veto-only kill switch. - Xcode now runs the checkout and pod verification before any build tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026.11.6 is in App Review and counts as consumed; 2026.11.7 replaces it in review with the closed-source removal and the key-exposure fixes. Release tools, gates and their tests now pin 4.2.1 (2026.11.7), as cc8e3ec and 1092c5e did for 2026.11.6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…26.11.7) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Packages/FearlessWalletConnect speaks WalletConnect v2 in the wallet role on Apple's system frameworks only (Foundation, CryptoKit, Security, Combine). It replaces Reown's reown-swift, which is under the WalletConnect Community License and is not open source. - X25519 key agreement, HKDF-SHA256 session keys, SHA-256 topics, and ChaCha20-Poly1305 type 0 envelopes in padded base64 (type 1 is read, never opened). - The relay's EdDSA did:key sign-in JWT, valid for 24 hours. - The relay over URLSessionWebSocketTask: subscribe, batch subscribe, publish, unsubscribe, the batch message fetch, acknowledging each delivery, keep-alive pings and reconnecting with back-off. - Pairing, proposals, approval and refusal, settlement, requests checked against the session's namespaces, answers, ping, disconnection and expiry, with each Sign method's relay tag and lifetime. - Verify: ES256 attestations from the relay, or the trusted service's answer by message hash. - A strict JSON reader that keeps numbers exact and refuses duplicate keys. - Its record in the app group's user defaults and its keys in one keychain item, and the import of what WalletConnectSwiftV2 (Fearless up to 4.1.0) and Reown 1.8.0 (4.2.x) saved, left in place for a downgrade. Tests run on macOS with swift test: RFC 7748 and WalletConnect relay-auth vectors, the wallet role against an in-memory relay that follows the live relay's delivery rules, and both earlier storage layouts. Two checks use the WalletConnect SDK installed outside the repository, never as a dependency: scripts/walletconnect/reference-vectors.mjs for a differential cryptography check, and reference-dapp.mjs as a dApp on the live relay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner, 2026-11-08, asked "Bring WalletConnect back on the new 4.2.1 head with this client?": "Bring it back". e0dda28 removed WalletConnect because reown-swift is not open source. This restores the WalletConnect part of that commit on Packages/FearlessWalletConnect and keeps everything else in it: the key-exposure fixes, the removal of sorawallet and ton-api-swift, and the source and pod verification phase. - The WalletConnect service, screens, coordinators, models and tests come back as they were in 4.2.1 (2026.11.6), on FearlessWalletConnect instead of WalletConnectSign. Reown's socket engine, socket factory and crypto provider stay deleted. - Forgetting a wallet and logging out disconnect its WalletConnect sessions again, after the new secret erasure. - RetiredWalletConnectDataCleanup is removed. It deleted the com.walletconnect.sdk Keychain items and WalletConnect defaults that the client imports, so sessions paired in 3.0.5 to 4.2.1 (2026.11.6) carry over again. Devices that already ran 2026.11.7 lost them; their dApps pair again. - RLPBigInt.swift and SignTypedData.swift (MEWwalletKit, MIT) return to WalletConnect/eip_mew with that licence and a NOTICE entry; the regex helper the ABI move dropped is String+Match.swift there. - The release audits again require WalletConnect.projectId (FL_WALLET_CONNECT_PROJECT_ID) and check that the WalletConnect service uses the entitled app group. - No package is added: the resolved graph stays at 40 external packages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/walletconnect-protocol.md describes Packages/FearlessWalletConnect: its files, the wire format, its storage, the import of what earlier SDKs saved, and how to rerun its checks, including the real-storage upgrade check from a Reown 1.8.0 build. It notes that 4.2.1 build 2026.11.7 deleted those records once per install, so a device that ran that build has nothing to carry over and its dApps pair again. The binary provenance record says the wallet now speaks WalletConnect with its own client on Apple's system frameworks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WalletConnect pairing codes use the `wc:` scheme (EIP-1328), but the QR matcher accepted `ws:` instead, so a scanned pairing code was reported as an invalid QR code. `WalletQRService` now matches `wc:` codes itself, from the camera and from gallery images, and no longer reports a `ws:` node address as a pairing code (the shared decoder still does; its result is refused). A new connection is only useful for signing, so `connect(uri:)` opens it together with signing: while `WalletConnectSigningPolicy` is off, a scanned code shows a localized "not available in this version" message and nothing is paired. This change also adds, in every bundled localization, the strings the reworked WalletConnect request flow uses in the following changes. Tests cover pairing codes and node addresses from the camera, the gallery and the scanner, connecting with signing off and on, malformed codes, and the new message in every shipped localization. The scanner tests now use `wc:` codes. Ported onto the in-repo client (Packages/FearlessWalletConnect): the service pairs through the client, or through an injected pairer in tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 805b624e2cadaa59856d89ea719e56ba9230e7b6)
…ation Every request is now prepared before its confirmation opens, and the confirmation and the signer use the same prepared values: - The request must name an account the session approved for the request's chain. The signing key is that account's, not a match picked by address from all wallets. - EVM transactions get their nonce, gas limit and gas price before the confirmation opens. The confirmation shows the network first, then the recipient (the token recipient or spender for ERC-20 transfers and approvals), the amount and the most the network can charge, and exactly those values are signed; nothing is quoted again after Confirm. Chain ids are parsed as the decimal values the app stores, a transaction for another chain is declined, and so is a request without a recipient. - EIP-712 typed data must carry the request's chain id when its domain names one, and permits show their spender and amount. The legacy array format, which no domain binds to a network, is declined. - Substrate transactions must carry the chain's genesis hash, spec version and transaction version. The call is decoded and must encode back to exactly the dApp's bytes; transfers show their recipient and amount, the fee is quoted for that exact extrinsic, and the tip is signed as sent. - Substrate messages are signed inside the `<Bytes>…</Bytes>` envelope that polkadot-js uses. - Personal-sign and typed-data signatures use the 27/28 recovery id that JSON-RPC callers and `ecrecover` expect. Declined requests show a localized reason and get an error response, and the request sheet no longer prepares the request again on every balance update. `WalletConnectSigningPolicy.production` stays off. Tests cover account binding, the quoted and exactly signed transaction for a decimal chain id, chain-id, address and quantity checks, ERC-20 decoding, the EIP-712 specification's example digest, permits, the `<Bytes>` envelope and its signature, Substrate genesis, runtime and call checks against the bundled Polkadot runtime, the signed payload, a runtime change before signing, and the confirmation rows for every method. Ported onto the in-repo client: the new files import FearlessWalletConnect, and the project changes were applied to this line's project file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 59c74e0bf33f93f29593006dc1219cb4b39e2b08)
…sts behind the lock - Signing now waits for the PIN or biometrics, asked from the confirmation after the user taps Sign. Closing the PIN screen signs nothing and leaves the request open to sign or reject; without a PIN nothing is signed, and a PIN screen that goes away without an answer counts as not confirmed. - The PIN screen that locks the app after inactivity now records that the app is locked. While it is shown, WalletConnect requests and notices wait in their queue, and a request already on screen is hidden until the app is unlocked. - A request the SDK delivers again while it is queued or on screen is shown only once. Tests cover signing with and without the user's confirmation, the PIN screen from the confirmation with its success and cancel paths, the missing-PIN case, requests waiting behind the lock and appearing after it, one request at a time, a repeated request shown once, the request window hidden while locked, and the lock ending with its PIN screen. Applied unchanged on the 4.2.1 line: the lock marker sits after the head's unlock-PIN guard in FearlessApplication.dropSession(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit cc3e7ee3a6275c65e84f58f077de2781bcd66ebe)
…s scams The SDK reports what WalletConnect Verify knows about the app behind each proposal and request, but the service dropped it. It now reaches the screens: - The proposal shows a Verification row below the app: verified domain, domain not verified, or domain mismatch. - The request sheet shows the same status under the app's name; a domain mismatch replaces the generic warning with one saying the app may not be the website it claims to be. The confirmation lists it as its last row. - A proposal or request from an app Verify flags as a known scam is declined before anything opens, and the user is told which app was blocked and that nothing was connected or signed. This applies while signing is off too. The notice presenter now serves both the signing-unavailable and the blocked-app notices. The domain-mismatch status is shortened for the rows, with its explanation as a separate warning, in every bundled localization. Tests cover how every Verify result maps to what is shown, declined and never shown scam proposals, scam requests declined before any confirmation with signing on and off, verified requests opening with their status, the blocked-app notice, the confirmation row, and the texts in every shipped localization. Ported onto the in-repo client: the client's sessionProposal and sessionRequest events carry the VerifyContext its Verify client produced, and a scam-flagged proposal is refused through the client (or an injected rejecter in tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 1b9a737d14a75b715d8f3d722c7a682b8dd9dc18)
…submission PIN Owner's decision for 4.2.1 (2026.11.8), relayed from the key-exposure session: "we need some wallet connect support of course", choosing "Connect and sign": pair from wc: codes and links, sign EVM, Polkadot and TON requests, ask for the PIN or Face ID before each signature, show the real requesting site, and let a remote kill switch turn signing off. - WalletConnectSigningPolicy.production is on. The ported WC-SIGN checks stay in front of every key: the account the session approved, the network, the decoded call that must encode back to the dApp's bytes, the quoted fee, the <Bytes> envelope, and scam-flagged apps declined. - Every signature asks through SubmissionAuthorizer, the step every fund-moving and staking submission takes, with its PIN lockout and biometric rules. Without a PIN nothing is signed. - Remote vetoes, absent means enabled and only an explicit false pauses: evm_dapp_signing_enabled and polkadot_dapp_signing_enabled for WalletConnect, ton_dapp_signing_enabled for TON Connect transactions and ton_proof sign-ins, the Android key names; never dapp_enabled. A paused request is declined before anything opens and the user is told nothing was signed. The confirmation checks again before and after the PIN. - The requesting site is shown as WalletConnect Verify reports it: on a domain mismatch the website row names the site the request came from, and a site Verify could not check is labelled "Domain not verified" with a warning. - wc: links and fearless://wc?uri= links pair (Info.plist registers wc). A link that opens the app waits until WalletConnect starts after the unlock; a failed link gets a notice. A wc: link without a key only brings the wallet forward. - New strings in every bundled localization. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/walletconnect/reference-dapp-sign.mjs runs the WalletConnect SDK most dApps ship as a dApp on the live relay. It asks for personal_sign, eth_signTypedData_v4 and polkadot_signMessage, and checks every signature with code that is not Fearless's: ox recovers the EVM signer and @noble/curves verifies the Polkadot ed25519 signature over the <Bytes> envelope (and that it does not cover the bare message). The SDK and those libraries stay outside the repository. WalletConnectLiveSigningTests (skipped unless FEARLESS_WC_SIGN_LIVE=1) is the wallet: it pairs from the dApp's link, approves an EVM and a Polkadot account, and answers each request with the app's own request preparation and signer, as the confirmation does after the PIN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/walletconnect-protocol.md now says how the app pairs (wc: codes and links, fearless://wc?uri=), what it signs and checks before the confirmation, how the requesting site is shown, the PIN step, the per-network remote vetoes, and the live signing check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cribe The remaining subscription owners ended their subscriptions with cancelForIdentifier, which only withdraws a request that has not been answered. An established subscription therefore kept running on the node until the socket closed, and its handler stayed registered in the engine. They now call WebSocketEngine.unsubsribe, which #1320 made end the subscription on the node with its subscription ID and matching method: - StorageSubscriptionContainer and WebSocketProviderSource (storage, on release) - SpecVersionSubscription (runtime version; ChainRegistry unsubscribes when a chain is updated or removed) - StakingAccountSubscription, StakingAccountResolver and PoolStakingAccountSubscription (staking storage, on a stash change and on release) The three staking owners now end a subscription on the connection that opened it. ChainRegistry replaces a chain's connection when the chain's nodes change, and the same UInt16 identifier can belong to another owner's subscription on the new connection. They also end a subscription that a second subscribe replaces: StakingAccountUpdatingService and PoolStakingAccountUpdatingService subscribe again after the owners' init already has, so the first subscription's identifier was overwritten and it ran until the socket closed. The cross-chain dispatch watcher follows the author_submitAndWatchExtrinsic subscription opened by the submission through a watcher registered without a subscribe request. cancelForIdentifier found no request and left the watcher registered for good. It is now removed with unsubsribe; when the watcher stops before the node reported finality (its timeout), it first ends the watch with author_unwatchExtrinsic. One-shot request cancellation (JSONRPCWorkerDefault, JSONRPCOperation) is unchanged. Tests: SubscriptionOwnerUnsubscribeTests drive each owner against a real WebSocketEngine and ScriptedSubstrateNode, now extended to runtime version and extrinsic status subscriptions; each is ended only by its own unsubscribe method with the subscription ID, as SORA 4.8.13 answers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 10, 2026
Remote config integrity: verify pins, list each chain once in the bundled list and chain types
#1318
Closed
nvzhu
previously approved these changes
Nov 10, 2026
Pin the Yatagarasu brand kit at manifest e49da29b (kit v3, 2026-11-13). Every v2 file is byte-identical; the generator now also copies the Ignite launch clip and its timeline, the send and receive sounds, the haptic patterns timed to them, the solar geometry and the sunset sky for the balance card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.2.1 (2026.11.11) can crash right after unlock with "Index out of range" in ChainRegistry.webSocketDidChangeState, on the socket state queue (device crash report from TestFlight, iPhone 16 Pro Max, iOS 27.2.1). Since 4.2.0 the socket engine reports state changes on its own queue, so each new chain socket's first change arrives while the registry is still adding the chain list. The delegate read that list without the registry's lock and trapped when the update replaced it mid-read. 4.0.4 reported the change on the updating thread itself, which hid the missing lock. - ChainRegistry: look the chain up under the lock, on the registry's own serial queue, so the shared socket queue never waits for a chain update. Create the lock with the registry (a lazy var can be created twice), read the list under it in performHotBoot and the package adapter, and guard the chain-types map, which the types sync publishes from its own queue. - NetworkIssuesCenter, ChainsIssuesCenter: keep listeners and issues under a lock and call listeners after releasing it. Screens add listeners while the event queue publishes the reconnecting events above. - EthereumConnectionPool, AccountInfoUpdatingService: create their locks with the object instead of lazily. Tests: a deterministic test parks a chain update and shows the state change waits for it; stress tests race state changes against updates, the types map and both issue centres; a real-socket test drives ConnectionFactory, FailoverChainConnection and WebSocketEngine into the registry; plus a hot-boot test. On 4.2.1 five of them fail (three crash the host) and Thread Sanitizer reports the race at ChainRegistry.swift:1066 through ConnectionPool and FailoverChainConnection, the device crash's frames. With the fix, 149 related tests pass under Thread Sanitizer with no reports, and the new tests pass 20 times in a row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apple released 4.2.1 (2026.11.11) on 12 November, which closes that version to new uploads, so the chain registry crash fix ships as 4.2.2. The app and every release tool pin 4.2.2 (2026.11.12); 2026.11.11 joins the consumed and supported predecessor builds. Release tooling self-tests pass: build-number check (4.2.2 follows 36 consumed builds, 7 tools agree) and its 19 cases, startup capture, upgrade usability gate, archive signing contract, release identity audit, signed artifact audit and Core Data rehearsal contracts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gate runs the whole ChainRegistryTests class, which gained five startup tests with the launch-crash fix. In the Release -O gate on 42dd1e2 all 483 selected tests passed with none failed or skipped, and the gate stopped only on its count: the five new identities were missing from the reviewed list. Add them and require exactly 483 tests. The list now equals the identities that ran. Tooling only; nothing here is compiled into the app. The gate self-test passes: 6 positive contracts and 50 negative or adversarial cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d touch Kit v3 "Launch, feel and sound", parts 1 and 2: - The Ignite clip plays once per cold start in an overlay window above the unchanged start flow, holds at 2 s while the first screen is not ready (or a biometric prompt is up, for at most 3 s more), then zooms into whatever screen is underneath around the dive's focus point. Tap skips, Reduce Motion and Reduced effects fade the crest straight to the first screen, VoiceOver skips it, and a clip that cannot load or stalls for 1 s falls back to a plain fade. It is silent and never changes authentication order or logic. - A later unlock lands the wallet with a crossfade from 1.06 to 1 (0.35 s). - FWFeedback plays the kit's send and receive sounds under .ambient (session touched only on a background queue, at play time) and its Core Haptics patterns, with UIFeedbackGenerator fallbacks and the system error pattern for failures. Settings gets "Sound effects" and "Haptic feedback" switches, on by default, in ordinary preferences. - Wired to: the final Send/Confirm press (commit), an accepted transfer (send sound and touch plus the solar ignite on the success screen), definite failures (error touch), a copied receive address (commit), and arriving tokens: a balance that grows from a value already known for the same wallet, account and asset, never on first values, price changes or hidden assets, in the foreground only, at most once in 3 s, with the app's own banner. - Debug builds only: Darwin-notification seams show the send success screen and run a synthetic arrival through the real rule, for walks without funds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d aurora Kit v3 "Launch, feel and sound", part 3 (the design pass): - The balance sits on the kit's sunset sky, its sun behind the round badge, under an ink scrim that keeps every text colour at 4.5:1 or more (tested against the brightest pixel behind the text); the solar geometry's tick ring turns slowly around the badge with the lattice faint behind; the sky drifts a few points with scroll, and arriving tokens light the card in gold. - Lit rims (gold to pink to nothing, inner top highlight) on the balance card, the tab bar capsule and the receive QR well; a soft plum shadow under the card. - The wallet total counts up from zero on its first reveal after launch; VoiceOver and copying read the final value. - Round actions press to 0.94 with a spring and a commit tap; the pink Send action carries a slow sheen every 6 s. - Two soft drifting glows (pink, ember, under 18 %) behind the wallet home, one cycle a minute; asset rows rise 12 pt as they enter. - The active tab glows pink with a small gold dot, following the system's own item layout from iOS 26. All motion stops under Reduce Motion and Reduced effects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before prices load the total can read "$0"; claiming that as the first reveal spent the count on zero and the real value then only rolled in. The count now waits for the first value above zero after launch and runs from zero to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first reveal's count could be cut by a price update arriving within its 0.6 s, which then rolled from the half-counted text. The count now continues from the value it shows to the new value. The sky card's scrim is also a little lighter around the badge (30 % at the top; the text area keeps 82 % and more). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A radial CAGradientLayer draws from its start point out to its end point. These three kept the default end point, whose zero horizontal radius paints nothing, so the arrival pulse, the aurora and the active tab's glow never showed. They now reach the edge of their frames; a test renders the aurora and checks both glows are lit and faint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The solar geometry ran through "All done" and touched the line under it, while drawing and once settled. It is now 176 pt (2.2 times the mark's dark disc, its stars drawn inside the frame), stays centred on the mark, and a send success screen reserves 64 pt under the mark so the figure ends 16 pt above the title. A layout test fails if the figure's frame meets the title's, at 375x667, 402x874 and 440x956, at the default and the largest accessibility text size, drawing and settled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.2.2 (2026.11.12), built from 20204c7, is waiting for normal App Review with automatic release after approval. It fixes the 4.2.1 crash on opening in the chain registry. The record lists the release identity, the upload reconciliation and the local validation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
takemiyamakoto
added a commit
that referenced
this pull request
Nov 13, 2026
This reverts commit 89503c7. The next commit, 2cd1011 from #1326, fixes the cause in the registry instead: chain updates are delivered on its serial queue, so they apply in provider order and the test's wait for the last round's node holds again. Taking that commit as it is keeps ChainRegistry.swift and ChainRegistryTests.swift the same on master and in #1326. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin brand kit manifest v4 (19675ac7…; every v3 file byte-identical) and copy tokens/shikiban.json into the app bundle byte for byte, so the launch flight, sync dial, seal, loader and lattice read the kit's numbers rather than retyped ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al, loader
Brand kit v4 "Shikiban" (SPEC 2026-11-15) on iOS:
- Launch flight replaces the v3 zoom: six gates about the dive's focus over
the clip, the mirror opening through the overlay (even-odd mask) with the
corona, the first screen's tiers scaled about the focus from the kit's
depth law and landing at exactly 1, launchArrive at 1000 ms, a tap plays
the rest 2.5x faster, the overlay goes once the opening clears the view.
Hold, skip, reduced motion and the start flow are unchanged. Tiers are
declared by the wallet home, tab bar, list, PIN and welcome screens.
- Later arrivals (PIN unlock) land tier by tier from landingFrom to 1 with
easing.settle, 45 ms apart, replacing v3's 1.06 -> 1.
- Sync dial (18 pt, in the balance card's top-right bracket) counts the
networks the wallet home's tracker connects: connected share in gold,
given-up share in ember, cyan comet and "9 / 14" while syncing; the hero's
tick ring turns 14x faster and brightens while syncing (heaven plate).
- The seal draws a transfer's known stages on the solar geometry in place of
the send confirm loader (ready on the final press, accepted on the hash,
broken on a definite failure); the success screen continues the figure.
- The app's full-page loader shows the three-comet mirror loader.
- Hero composition marks: corner brackets, yamato ticks, asanoha lattice at
5 % with a 9 s band of light (only a mask moves).
- Value tick on wallet rows and the total's line.
- DEBUG seams (notifyutil jp.co.soramitsu.fearless.debug.{seal,sealfail,
dial,dialfail,loader,loaderfill,tick}) and FWShikibanTests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…light - The wallet-choice welcome (OnboardingMain, the first screen of a fresh install) declares its tiers through FWWelcomeView, as the introduction does; before this it flew as one piece and the window behind showed black. - The main window is ink while the flight runs, so a screen that declares nothing never shows black round its edges. - A start flow that replaces its first screen mid-flight (a slow start past the hold) has the new screen fly from that moment; the old one is left at rest. - The sync dial's count sits to its left, so the dial keeps its place. - Gates draw with plain alpha (no screen blend filter on 30 layers). - DEBUG-only FW_FLIGHT log lines (tiers, flight time every 100 ms, overlay). - Tests: the welcome's tiers, a mid-flight screen change, and the reduced-motion landing test reads the crossfade under kCATransition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-FWFlightSnapshots saves what the windows show every 200 ms of the flight to the app's tmp folder, so the flight can be checked frame by frame where a screen recording drops frames under load. DEBUG only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The seal (kit v4 part 3) now follows the cross-chain, Polkaswap swap and
staking confirms as it does the wallet send confirm. Each presenter reports
only the events it really has: the final press authorised (ready), a hash
returned (accepted), a definite failure (break); signed and sent are never
known apart and draw together on acceptance. FWTransferEvents maps each flow's
results; reporting is synchronous and returns before the presenter shows its
result, so nothing waits on the seal.
- Staking (bond extra, unbond, rebond, redeem, payee, controller, payout,
nominate) uses the app's full-page loader: the seal takes its place.
- Cross-chain and swap show a spinner in the button: the seal sits above the
screen on ink at 72 % (never takes a touch) and fades when loading ends; a
failure breaks it first. Cross-chain's success screen continues the figure.
- Feedback is unchanged. DEBUG seams sealflow.{swap,crosschain,staking}[fail]
replay each presenter's sequence on the real confirm screens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
One PR for every open fearless-iOS pull request into
master, so a single @soramitsu/fearless code-owner approval covers all of it. The owner asked on 10 November 2026 to "merge all code into master branches" and chose to fold each repo's open PRs into one.The branch starts at master
dce8790cd. Each PR head is merged unchanged, with one merge commit per PR. Conflicts are resolved in those merge commits, and the merge messages describe each resolution. Merge it with squash, because master requires linear history.Folded in
hotfix/4.2.0-polkaswap-usability@e61d591bf(merged at098dc861e, then again for its docs-only App Review submission record)release/yatagarasu-brand@8a0b91f00, which containsrelease/sunlit-redesign@d3b3d5166hotfix/remote-config-integrity@dab448005, which containshotfix/dev-chains-avail-dedupe@1cbd9038dverify-remote-config.pychecks everyownedSha256pin and fails on a chainId that appears twice.hotfix/ws-unsubscribe-subscription-owners@f0fc6248e, which containshotfix/ws-unsubscribe-subscription-id@694290aa4WebSocketEngine.unsubsribenow ends a subscription on the node, using its subscription ID and the matching unsubscribe method. Polkaswap and every other subscription owner use it, so subscriptions no longer leak.hotfix/ios-test-suite-failures@893c7b9fdfearlessTestsfailure: Japanese Welcome glyphs on English devices, the bold HUD, Send tests reading the system pasteboard, and two PIN and Profile racesLeft out: already in master
claude/xcm-hardening-20261028@2a8e71617release/4.2.0merged it, and #1312 squash-mergedrelease/4.2.0asdce8790cdclaude/repair-dead-rpc-nodes@dc408e712claude/ios-taira-torii-cursor-paging@708388303claude/ios-evm-node-failover@6fb9e8705release/4.2.0All four heads are ancestors of #1312's last head,
4643e9054. That head's tree is master's tree (ccf28daaf), so nothing from these PRs is missing. GitHub can't see this because squash merges drop the commits, which is why they still show as open.Conflicts and how they were resolved
config/todo-debt-baseline.tsv: both sides add the same two generated TonAPI lines. One copy is kept, because the audit rejects duplicates.PinContinuityTests.swift: iOS 4.2.1 (2026.11.11): PIN lifecycle fix and wallet release #1315's PIN lifecycle observers are kept, spaced the way Yatagarasu brand: new icon, marks, welcome crow and call #1325 spaces nested types.WalletSendConfirmPresenter: keeps both new init parameters, iOS 4.2.1 (2026.11.11): PIN lifecycle fix and wallet release #1315'ssubmissionAuthorizerand Fix the failing fearlessTests: Japanese Welcome glyphs, bold HUD, Send pasteboard, Profile PIN race #1322's injectedpasteboard. The tests pass both test doubles.PinSetupViewController: iOS 4.2.1 (2026.11.11): PIN lifecycle fix and wallet release #1315 and Fix the failing fearlessTests: Japanese Welcome glyphs, bold HUD, Send pasteboard, Profile PIN race #1322 each added the samewillMove(toParent:)cancel for create mode. The newer one (iOS 4.2.1 (2026.11.11): PIN lifecycle fix and wallet release #1315,4fc2b3a1a) stays, and the duplicate override is dropped.@retroactiveconformance.project.pbxprojand everything else merged without conflicts. Remote-config pins still verify after the merge.Version
4.2.1 (2026.11.11), the highest version and build number among the folded lines (from #1315). No new bump.
The App Store build 2026.11.11 was archived from
098dc861eon the hotfix line, not from this branch. This branch adds the redesign, the brand and the fixes from #1318, #1323 and #1322 on top of that release.Related Issue
No tracked issue. Owner request, 10 November 2026.
Target Branch
developmasterand is a release or hotfix PRThe head is
release/*becausevalidateonly letsdevelop,release/*andhotfix/*into master.Type of Change
Screenshots / Videos
UI changes come from #1324 and #1325, and their screenshots are in those PRs.
Test Plan
Everything below ran locally on
a53ed605cwith a clean tree. The head,c4035709d, differs from it only bydocs/testflight-4.2.1-2026.11.11.md(#1315's App Review submission record). Atc4035709dI reran the audits that read tracked documents: TestFlight publication evidence (audit and its 70-case test), public artifacts, TODO debt and remote-config pins. All pass. The project doesn't gate on hosted CI (AGENTS.md). Evidence is in the workspace atbuild/reports/ios-consolidated-20261110/(README, logs, result bundles, scripts).fearless1108976a…37cd,fearless.debug.dylibba682b8a…a83d,Assets.caread95423…0ab3,fearlessTestsa8998363…3792. The welcome media hashes equal the brand kit's.fearlessTests, iPhone 17 on iOS 27.0, system language en-US,-testLanguage en -testRegion USfearlessTests, iPhone 16e on iOS 26.2 (the hosted job's device and runtime), system language en-USLegacyTonConnectViewTests.testRealWebKitBridgePublishesReleasedProviderAndHandlesStructuredRepliestimed out after 30 s waiting for its synthetic WebKit navigation, at host load of about 600. It then passed 5 of 5 times alone and 3 of 3 together with the six other TON Connect test classes, at load up to about 900, and it passed in the iOS 27.0 full run. Its test and the TON Connect code are byte-identical to #1315, and the 4.2.1 release runs recorded the same timeout under load.FearlessNative-Package), all 15 test targets, iOS 27.0 simulatorPackages/FearlessWalletConnect(swift test)Packages/ton-swift(swift test)verify-remote-config.py: 949 pinned files, 1,988 owned references.release-contractsstepscheck-ios-build-number: Release 4.2.1 (2026.11.11), and 7 tools agree.70771ec2….Not rerun for this branch:
098dc861epassed them (iOS 4.2.1 (2026.11.11): PIN lifecycle fix and wallet release #1315). The PRs folded on top change UI, remote-config data, subscription teardown and tests, not storage or keys.Required checks
Required for master:
validate,buildandcontinuous-integration/jenkins/pr-merge. Recent history of each:validate(branch flow): it only checks that the head isdevelop,release/*orhotfix/*. This head isrelease/*, so it should pass.build(Codecov Fearless, Xcode 26.6, iPhone 16e, iOS 26.2, English):dce8790cd, fix(subscriptions): end owners' subscriptions on the node with unsubscribe (stacked on #1320) #1323 and Yatagarasu brand: new icon, marks, welcome crow and call #1325, the job fails on the same 46 assertions in 4 tests:FWDesignSystemTests.testNonLatinTitlesAndCJKHudUseBodyFaceAndBoldTextChangesWeightand 3OnboardingStartTests. Japanese text was untagged, so an English device drew Chinese glyph forms, and the bold HUD didn't change weight. Fix the failing fearlessTests: Japanese Welcome glyphs, bold HUD, Send pasteboard, Profile PIN race #1322 fixes both. Its own hostedbuildpassed, and it is included here.timeout-minutes: 120while tests were still running. Recent runs that finished took 77 to 118 minutes (Yatagarasu brand: new icon, marks, welcome crow and call #1325, Fix the failing fearlessTests: Japanese Welcome glyphs, bold HUD, Send pasteboard, Profile PIN race #1322, fix(subscriptions): end owners' subscriptions on the node with unsubscribe (stacked on #1320) #1323), so this PR's run can also hit the limit on a slow runner.continuous-integration/jenkins/pr-merge: no Jenkins is involved. It is a GitHub Actions stand-in job (needs: build) that exits 1 whenbuilddid not pass, so it followsbuild.ios-release-safetyandrelease-contractsaren't required. They passed on every recent PR and pass locally here.This PR's own runs:
validate, bothios-release-safetyand bothrelease-contractspassed. Pushingc4035709drestartedbuild(2026-11-10, about 20:05 JST). The locale tests that failbuildon master pass in both local English runs above.Risks & Rollout
FL_WALLET_CONNECT_PROJECT_IDin release builds from iOS 4.2.1 (2026.11.11): PIN lifecycle fix and wallet release #1315.hotfix/4.2.0-polkaswap-usabilitygets more commits, for example after the 2026.11.11 review, merge them into this branch before approval.Ignite launch, sound, haptics and design (brand kit v3, added 2026-11-13)
The owner asked for the Yatagarasu's Ignite as the launch animation zooming into the wallet, a much cooler design, and tactile feedback and sounds when sending and receiving tokens. Contract: brand kit v3
SPEC.md, manifeste49da29b…. Commits033534317…393811b44(7), fast-forwarded onto this branch. Pushing them dismissed this PR's earlier approval; it needs a fresh code-owner approval.launchArrivetap. Tap skips it, VoiceOver and Reduce Motion skip it, and a clip that does not start or stalls for 1 s falls back to a fade. A later unlock lands the wallet 1.06→1. Authentication order and logic are untouched (RootPresenter,RootInteractorunchanged)..ambientsounds (session set only while playing, previous category restored, decoded off the main thread), Core Haptics withUIFeedbackGeneratorfallbacks. Final Confirm press: tap. Accepted transfer: send sound, haptic and the solar geometry on the success screen (wallet send and cross-chain). Definite failure: system error haptic. Receive Copy: tap. Tokens arriving (a known balance grows while the wallet is on screen; never a first value, price change or hidden asset; at most once in 3 s): receive sound, haptic, gold pulse and banner. Settings → Preferences: "Sound effects" and "Haptic feedback", on by default, 9 languages.fearlessTestson3f18cc505(iOS 27.0 simulator): 3,868 passed, 0 failed, 75 skipped.393811b44(success-screen layout) addsAllDoneSolarGeometryLayoutTests(375×667, 402×874, 440×956; default and largest text); its 28 affected tests pass.audits.shfailures=0; raw-colour ratchet and brand--checkpass.SceneDelegate(three calls aftermakeKeyAndVisible()),RootViewController(FWLaunchSplashScreenconformance),PinSetupWireframe(FWLandingAnimationCoordinator). Seen in passing, not changed: at the largest text size on a 375-wide phone the success screen truncates Hash and Result.Shikiban: launch flight, sync dial and seal (brand kit v4, added 2026-11-15)
The owner asked for more animations that inform, a 3D zoom at startup, and a high-tech look that keeps the Yatagarasu theme, with sacred geometry guiding the composition. Contract: brand kit v4
SPEC.md("Shikiban"), manifest19675ac7…(v3 plustokens/shikiban.json; every v3 file byte-identical). The same design is in the extension (soramitsu/fearless-wallet-web#1117). None of this is in a store build.Commits
380f05f9b…9a4068c0c(5).launchArriveat 1 s into the flight; a tap runs the rest 2.5× faster. The PIN unlock lands tier by tier. Hold, skip, reduced motion and the start flow are unchanged.9a4068c0c): each presenter reports authorised (ready), its hash or success (accepted) and a definite failure (break), synchronously, before it shows its result. Cross-chain and swap show the seal over the confirm screen on 72 % ink (their loading is a button spinner); every staking confirm (bond extra, unbond, rebond, redeem, reward destination, controller, payout, nominate/initial bond) shows it in the full-page loader's place. The cross-chain success screen carries the figure on. Sound and touch unchanged.fearlessTestson9a4068c0c: 3,916 passed, 0 failed, 95 skipped; audits 50 of 50. Captured through DEBUG seams on the real confirm screens; the real presenters' wiring is covered by mapping tests, not a funded end-to-end run.fearlessTestson028d9d487: 3,910 passed, 0 failed, 95 skipped (base2cd1011a4measured the same way: 3,876 / 0 / 95). Audits 49 of 50 on the head; the one failure (test-capture-testflight-startup.py, a subprocess timing race in tooling this branch does not touch) passed alone.build/reports/ios-shikiban/in the workspace.🤖 Generated with Claude Code