Skip to content

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
masterfrom
release/consolidated-20261110
Open

takemiyamakoto wants to merge 105 commits into
masterfrom
release/consolidated-20261110

Conversation

@takemiyamakoto

@takemiyamakoto takemiyamakoto commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

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

PR Head merged What it brings
#1315 hotfix/4.2.0-polkaswap-usability @e61d591bf (merged at 098dc861e, then again for its docs-only App Review submission record) iOS 4.2.1, build 2026.11.11 (the App Store release line): Polkaswap fixes, key-exposure fixes, the in-repo WalletConnect client (connect and sign), the Solana snapshot lifetime fix, the PIN setup lifecycle fix, and release records
#1325, which contains #1324 release/yatagarasu-brand @8a0b91f00, which contains release/sunlit-redesign @d3b3d5166 The sun-lit redesign (neumorphic surfaces, one light source, informative motion) and the Yatagarasu brand (crest icon, launch screen, lockup, crest badge, the welcome crow and its call, the pink, ink and gold palette)
#1318, which contains #1317 hotfix/remote-config-integrity @dab448005, which contains hotfix/dev-chains-avail-dedupe @1cbd9038d Debug and Dev builds list Avail DA Mainnet once, so chain sync works again. The bundled list and the production chain types list each chain once. verify-remote-config.py checks every ownedSha256 pin and fails on a chainId that appears twice.
#1323, which contains #1320 hotfix/ws-unsubscribe-subscription-owners @f0fc6248e, which contains hotfix/ws-unsubscribe-subscription-id @694290aa4 WebSocketEngine.unsubsribe now 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.
#1322 hotfix/ios-test-suite-failures @893c7b9fd Fixes every fearlessTests failure: Japanese Welcome glyphs on English devices, the bold HUD, Send tests reading the system pasteboard, and two PIN and Profile races

Left out: already in master

PR Head Why
#1308 claude/xcm-hardening-20261028 @2a8e71617 Already in master. release/4.2.0 merged it, and #1312 squash-merged release/4.2.0 as dce8790cd
#1309 claude/repair-dead-rpc-nodes @dc408e712 Same as #1308
#1310 claude/ios-taira-torii-cursor-paging @708388303 Same as #1308
#1311 claude/ios-evm-node-failover @6fb9e8705 Same as #1308. The EVM balance isolation fix that followed it also came in through release/4.2.0

All 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

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 098dc861e on 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

  • This PR targets develop
  • This PR targets master and is a release or hotfix PR

The head is release/* because validate only lets develop, release/* and hotfix/* into master.

Type of Change

  • feat (new feature)
  • fix (bug fix)
  • refactor (no functional change)
  • chore/build (tooling, CI, deps)
  • docs

Screenshots / Videos

UI changes come from #1324 and #1325, and their screenshots are in those PRs.

Test Plan

Everything below ran locally on a53ed605c with a clean tree. The head, c4035709d, differs from it only by docs/testflight-4.2.1-2026.11.11.md (#1315's App Review submission record). At c4035709d I 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 at build/reports/ios-consolidated-20261110/ (README, logs, result bundles, scripts).

Check Result
Build for testing (Debug, simulator, Xcode 27.2, no keys, signed ad hoc) Succeeded. fearless 1108976a…37cd, fearless.debug.dylib ba682b8a…a83d, Assets.car ead95423…0ab3, fearlessTests a8998363…3792. The welcome media hashes equal the brand kit's.
Full fearlessTests, iPhone 17 on iOS 27.0, system language en-US, -testLanguage en -testRegion US 3,916 tests: 3,841 passed, 0 failed, 75 skipped (44 min)
Full fearlessTests, iPhone 16e on iOS 26.2 (the hosted job's device and runtime), system language en-US 3,916 tests: 3,840 passed, 75 skipped, 1 failed. LegacyTonConnectViewTests.testRealWebKitBridgePublishesReleasedProviderAndHandlesStructuredReplies timed 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.
Native package (FearlessNative-Package), all 15 test targets, iOS 27.0 simulator 506 passed, 0 failed. This includes SSFUtilsTests 56/56 and SSFXCMTests 161/161.
Packages/FearlessWalletConnect (swift test) 54 tests, 0 failures, 2 skipped
Packages/ton-swift (swift test) 41 tests, 0 failures
Codecov Fearless job's audit and contract steps 29 of 29 pass. This includes verify-remote-config.py: 949 pinned files, 1,988 owned references.
iOS Release Safety job's release-contracts steps 18 of 18 pass. check-ios-build-number: Release 4.2.1 (2026.11.11), and 7 tools agree.
Brand assets and legacy colour sets 3 of 3 pass. 73 outputs match kit manifest 70771ec2….

Not rerun for this branch:

Required checks

Required for master: validate, build and continuous-integration/jenkins/pr-merge. Recent history of each:

This PR's own runs: validate, both ios-release-safety and both release-contracts passed. Pushing c4035709d restarted build (2026-11-10, about 20:05 JST). The locale tests that fail build on master pass in both local English runs above.

Risks & Rollout

  • Size: this lands about four weeks of work in one squash: 4.2.1, the redesign, the brand and three fix PRs. A rollback is a single revert of the squash commit.
  • No migrations or new secrets. The PRs' own risk notes still apply, for example about 5.5 MB of welcome media from Yatagarasu brand: new icon, marks, welcome crow and call #1325, and WalletConnect needing FL_WALLET_CONNECT_PROJECT_ID in release builds from iOS 4.2.1 (2026.11.11): PIN lifecycle fix and wallet release #1315.
  • The hotfix line is still live. If hotfix/4.2.0-polkaswap-usability gets 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, manifest e49da29b…. Commits 033534317…393811b44 (7), fast-forwarded onto this branch. Pushing them dismissed this PR's earlier approval; it needs a fresh code-owner approval.

  • Launch: a window above the unchanged start flow plays the Ignite once per cold start, holds at 2 s while the first screen is not ready or Face ID is up (3 s cap), then zooms 1.18→1 into whatever is underneath with the launchArrive tap. 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, RootInteractor unchanged).
  • Sound and touch: .ambient sounds (session set only while playing, previous category restored, decoded off the main thread), Core Haptics with UIFeedbackGenerator fallbacks. 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.
  • Design: sky card with a scrim, turning solar geometry and gold pulse; lit rims on the balance card, tab bar and receive QR well; count-up on the first total; round actions press to 0.94 with a tap; Send sheen; aurora; tab glow with a gold dot; rows rise 12 pt.
  • Tests: full fearlessTests on 3f18cc505 (iOS 27.0 simulator): 3,868 passed, 0 failed, 75 skipped. 393811b44 (success-screen layout) adds AllDoneSolarGeometryLayoutTests (375×667, 402×874, 440×956; default and largest text); its 28 affected tests pass. audits.sh failures=0; raw-colour ratchet and brand --check pass.
  • Not verified: nobody has heard the sounds or felt the haptics (the simulator ran only the fallback); no device, iPad, Face ID or real transfer.
  • Overlaps with the 4.2.2 launch-crash work: SceneDelegate (three calls after makeKeyAndVisible()), RootViewController (FWLaunchSplashScreen conformance), 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"), manifest 19675ac7… (v3 plus tokens/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).

  • Launch flight: from the clip's dive, gates (circle over square, each 1/√2 of the last, then the eight-lobed mirror) rush out of the focus, the mirror opens on the first screen inside a corona, and the screen flies up with its parts at their own depths (wallet home, tab bar, list, PIN, both welcome screens). launchArrive at 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.
  • Sync dial: in the balance card's top-right corner, eight segments lit to the share of chains connected, ember for unreachable ones, a cyan comet and the count ("8 / 14") while any are outstanding; the tick ring turns 14× faster meanwhile. It counts chain-registry connection events: chains without a socket (EVM over HTTPS, TON, Bitcoin, Solana) are not counted.
  • Seal: on the wallet send confirm, the solar geometry shows the transfer on its way, closes and ignites on acceptance and carries on into the success screen; a failure breaks it. The app knows only "authorised" and "accepted", so the middle stages draw together.
  • Seal on cross-chain, swap and staking (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. fearlessTests on 9a4068c0c: 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.
  • Also: the full-page loader is three comets on the mirror's rim; wallet rows tick when their value changes and the total draws a line; the balance card has corner marks, √2 ticks and an asanoha lattice a band of light crosses. No new strings.
  • Tests: fearlessTests on 028d9d487: 3,910 passed, 0 failed, 95 skipped (base 2cd1011a4 measured 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.
  • Not verified: the flight on a device (simulator recordings under heavy machine load dropped middle frames in later runs; the app's log and in-app snapshots show every phase); haptics on hardware; a real transfer; iPad and VoiceOver. Not done: dialog arrival and the fee-estimate loader. Evidence: build/reports/ios-shikiban/ in the workspace.

🤖 Generated with Claude Code

takemiyamakoto and others added 30 commits November 7, 2026 10:48
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>
Brings hotfix/4.2.0-polkaswap-usability from 098dc86 to e61d591, one docs-only
commit (docs/testflight-4.2.1-2026.11.11.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nvzhu
nvzhu previously approved these changes Nov 10, 2026
takemiyamakoto and others added 10 commits November 13, 2026 04:44
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>
takemiyamakoto and others added 4 commits November 13, 2026 10:22
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>
takemiyamakoto and others added 5 commits November 15, 2026 12:20
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants