Repository navigation
Yatagarasu brand: new icon, marks, welcome crow and call - #1325
Closed
takemiyamakoto wants to merge 52 commits into
Closed
takemiyamakoto wants to merge 52 commits into
takemiyamakoto wants to merge 52 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>
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>
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>
The client's request and proposal ids are Int64 (Reown's were RPCID and String), and its JSONRPCError is FearlessWalletConnect.JSONRPCError, qualified because SSFUtils has a type of the same name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live signing check found that the polkadot_signMessage and polkadot_signTransaction answers carried a random 64-bit id. dApps read the answer in JavaScript, where an integer beyond 2^53 becomes a BigInt that plain JSON.stringify cannot write. The id is now between 1 and 2^53 - 1. The reference dApp scripts' in-memory storage writes such values too, and the live signing test fails, instead of skipping, when the dApp never writes its result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SwiftFormat's unusedArguments rule; no change in behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…quests Security review of c82dd63, findings 3, 4 and 6 (client side): - pair(uri:) refuses a link whose topic is a live session's, or a pairing's with another key (ClientError.topicInUse): its key would replace theirs. Opening the same pending link again changes nothing. - respond(topic:requestId:response:) answers only a request the dApp still waits for (ClientError.requestNotFound otherwise); isPending(topic:requestId:) tells the app, expiry included, before the sweep runs. - disconnectAll() ends every session and pairing for logout, and keeps going when one fails. Package tests: 52 run, 0 failures, 2 env-gated skips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of c82dd63: 1. HIGH, app lock: the idle and background lock puts its PIN on the app's own window (SceneWindowFinder.mainWindow), never on the key request window that hides while locked, so no flow ending can dismiss it. A request sheet hidden with its window is not rejected; the main window takes key status back. 2. MEDIUM, blind signing: undecoded call data gets a warning, the contract, the selector and the raw data; transfer, transferFrom, approve, increaseAllowance (maximum = unlimited) and setApprovalForAll are decoded strictly, with a warning for trailing bytes. Typed data shows permits' spender, amount and deadline (DAI allowed: true = unlimited) and every signed field of anything else, with a warning. Polkadot calls other than transfers show the call and its arguments, Utility.batch* and Proxy.* inner calls numbered, with a warning. 3. MEDIUM, forget wallet: every session that granted one of the wallet's accounts ends (any namespace and chain, by account id); logout ends every session and pairing. 5. LOW: typed data whose domain carries an undeclared chainId is declined; a domain without one gets a warning; the hash comment is fixed. 6. LOW: expired or ended requests and proposals leave the queue or close with a notice; the confirmation checks the request is still pending before the PIN and before signing; a transaction already sent still shows its hash. 7. LOW: at most 10 flows wait (more are declined at once), at most 3 links wait for the unlock, and requests are keyed by topic and id. 8. LOW: Polkadot chains match only the exact 32-character CAIP-13 reference. (4 and the client half of 3 and 6 are in the previous commit.) Focused hosted tests: 361 run, 0 failures, 2 env-gated skips; the lock, gate and lifecycle classes 5 times over: 240 runs, 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-review of 4619160, problem C: disconnectAll() forgot one topic, then waited for that peer, before the next. It now forgets every session and pairing locally in one write first (loading the record when the client has not started, and removing earlier SDKs' records of them), then tells every peer at once, best effort. Nothing comes back on a later start. Package tests: 54 run, 0 failures, 2 env-gated skips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-review of 4619160: A. HIGH: permits are read only when the primary type is defined exactly as EIP-2612, DAI or Permit2 define it (compared as the EIP-712 type string), and only from declared fields, so undeclared keys such as allowed:false or expiry:0 can't change what is shown. The token and name come from the domain only when EIP712Domain declares them. Every signed field is listed under the permit too. Undeclared keys get a warning that they are not signed. A value whose shape differs from its declared type is declined. B. MEDIUM: nothing is cut. Typed data over 500 fields or 10 levels, and a Substrate call carrying over 100 calls or past 60,000 characters or 24 levels, is declined before the confirmation opens. Substrate calls get a summary of every call they carry, by call and account with counts and totals: fund-moving and rarer calls first, then larger amounts. Lists of numbers (bytes) show on one line. C. LOW: logout reaches WalletConnect's record even before it was set up. D. LOW: at most 5 requests per session, 5 proposals and 20 flows in all. E. LOW: transfer, transferFrom and approve on a contract that isn't a known token show "Amount or item ID". Focused hosted tests: 383 run, 0 failures, 2 env-gated skips; lock, gate and lifecycle classes 5 times over: 255 runs, 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tocol notes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Third review of c1201eb, F: the ABI encoder that hashes typed data reads a number string as decimal, then as unprefixed hex, then as signed, and encodes negatives in two's complement, so "-1" or -1 for a uint256 signed as 2^256-1, "ff" as 255 and "1e18" as 0x1e18, while the screen showed the text as written. A malformed address was also hashed as other bytes. Before anything is hashed or shown, every value of an atomic declared type, in the domain and at any depth of the message, is now parsed strictly and written back in one canonical form (WalletConnectTypedDataValues): - uintN/intN: decimal without leading zeros, 0x hex, or a JSON integer, within the bit width; negatives only for intN; shown and hashed as decimal. - address: 0x and 40 hex digits. bool: a JSON bool, or 1 and 0. - bytes: even-length 0x hex. bytesN: at most N bytes, right-padded as hashed. - string: a JSON string. Fixed-size arrays must have their size. Anything else is declined. The hash reads only the canonical data, so the signature covers exactly what the confirmation shows. A hex domain chainId is now read as its number and bound to that network. Focused hosted tests: 390 run, 0 failures, 2 env-gated skips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026.11.7 is in App Review and counts as consumed; 2026.11.8 replaces it with WalletConnect back through the in-repo client: wc: pairing and EVM and Polkadot signing behind the PIN, remote vetoes and the requesting site, after three independent security review passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apply shared native lighting, raised surfaces, inset controls, approved brand assets, and informative wallet motion. Preserve accessible layouts and keep sheet rims static across recovery and signing contexts. Add regressions for motion policy, sync state, localized layouts, transaction states, and generated asset integrity.
The Profile authorization fixture called the data source's cellForRowAt directly on every open(). That dequeues outside a table request, and a second open() raises "Attempted to dequeue multiple cells for the same index path". The raised exception unwinds through the async test, which aborts the test host. The sun-lit row spacing exposed this: with tighter footers, scrolling no longer recycled the rows between the two opens. Scroll each row into place and read it with cellForRow(at:), the same way UIKit asks for it. Test code only; the app's data source is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner, 8 November 2026: "on pages like this, we are not constrained to this little circle. make the wings logo span most of the width of the screen". PIN, welcome, onboarding start, About and the launch screens now show the approved mark at full span: the badge's own wing, joint and disc paths without the circle, at about 90% of the screen width and no taller than the circle was. The generator embeds the master path text unchanged, so the wings stay byte-identical. FWWingsView adds the sun's glow and drops it under reduced effects and high contrast. The wallet's hero keeps the round sun because its sync arc needs the circle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Screens from the final build showed two flaws. The PIN page's scroll view cut the glow in a straight line above the wings, so the glow now fades within the room every page keeps above the mark. The About list still had the system's black cells and a black header fade under the lit page; it now shows the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the owner-verified PIN keypad sizing from TestFlight 2026.11.9: 72 pt keys, comfortable spacing and vertical centering. The release line's metrics decide the keypad's sizes; the sun-lit paint and the wide wings sit on top. Both sides' PIN tests are kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dismissed authorization releases Profile a run-loop turn after UIKit ends the transition, so a tap on the very next poll could be ignored and fail the test. The fixture now waits for that release before tapping. A release that never comes still fails, so the test still guards against a stuck Change PIN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rolls the brand the owner approved on 2026-11-10 into the iOS app. UI only: keys, signing, storage, networking and chain logic are untouched. - Generator: scripts/brand/generate-ios-brand-assets.mjs reads the shared kit (brand/yatagarasu, manifest 70771ec29c86...), checks every consumed kit file against the kit manifest and pins all 39 of them and the 73 outputs in design/brand/ios-brand-assets.manifest.json. App icons, vector masters and the welcome media are copied byte for byte; fixed-size slots embed the master drawing verbatim in a sized frame. No renderer. --check / --check --no-kit. - App icons from the kit (development builds: gold crest). Launch screen: white crest, 120 pt, centred on ink; the root screen shows it at the same place. - FWLockupView (replaces FWWingsView): the stacked lockup at 56 % of the window width, at most 280 pt, on PIN, welcome, onboarding start and About; PIN and welcome cap it at 20 % of the window height. The wallet hero and other round slots show crest-badge (crest-badge-small under 32 pt). Winged-sun assets, masters and FWWingsView are removed. - FWWelcomeHeroView: the looping crow, muted, aspect-fill anchored to the top over its poster; it pauses off screen and in the background, and Reduce Motion shows the poster. "Play the crow's call" (all 9 languages) plays the call with sound, crossfading 250 ms in and back; taps during the call are ignored; AVAudioSession .ambient. With motion restricted the sound plays alone. - Palette: ink surfaces, brand pink #E6006F, primary fill #D6006A -> #A8005A (pressed #A8005A), pink text #FF4FA3, solar gold glow, brand text colours. FW tokens moved with the kit's tools/apply-tokens.mjs; colour sets and FWTokens.swift regenerated from them. Semantic colours unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The startup screen showed its status line ("Finishing your update…") and
re-centred the crest together with it, so for a few frames the launch crest
and the startup crest cross-faded at different heights. The crest now stays at
the launch screen's place (120 pt, centred in the safe area) and the status
appears below it; long status text at large sizes still lifts it and scrolls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full fearlessTests run on 7757275 failed two tests that pin brand values the rollout changes on purpose: the neumorphic well's rendered centre (bgDeep is now #0C0722) and FWTokens.sourceSHA256 (design/fw.tokens.json moved with the kit's pink remap). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sweep of Swift, storyboards, xibs and colour sets for the retired magentas found one live value outside the tokens: the staking stories preview's highlighted fill (#FF009A at 50 %) set as a runtime attribute. It moves to #E6006F, as the kit's remap does for the brand mark and glow. The other xib hits are Interface Builder's <namedColor> caches, which resolve from the asset catalog at runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 of 7 tasks
Contributor
Author
|
Folded into #1326, which brings every open iOS PR into master in one change, so one code-owner approval covers it. This PR's branch is kept. |
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
Rolls the Yatagarasu brand (approved by the owner on 10 November 2026) into the iOS app: the crest app icon, the white crest on the launch screen, the stacked lockup on full pages, the crest badge in round slots, the animated crow and its call on the welcome screen, and the pink, ink and gold palette. The Android (#1278 in fearless-Android) and browser-extension rollouts use the same kit, so the apps match.
This is UI only. Keys, signing, storage, networking and chain logic are unchanged.
Stacks on #1324 (
release/sunlit-redesign) and merges after it. Until #1324 lands, the diff against master includes it. This change alone is 4 commits, 196 files, +1,707 / -993 (150 of the files are asset-catalog entries).Assets from the shared kit
scripts/brand/generate-ios-brand-assets.mjsnow reads the kit (brand/yatagarasu, not committed), checks itsmanifest.jsonagainst the pinned SHA-25670771ec29c8607b1964a966066affb15bb02a2032973227d7f7d2ae3c80c048band every consumed file against that manifest, and records all 39 kit files and 73 outputs indesign/brand/ios-brand-assets.manifest.json. There is no renderer: app icons, vector masters and the welcome media are copied byte for byte, and fixed-size slots embed the master drawing verbatim in a sized SVG frame. The script checks that every master path survives unchanged.--checkregenerates and compares;--check --no-kitverifies the committed files against the manifest without the kit.AppIconandAppIconDevfrom the kit's opaque rasters, same filenames and sizes. Development builds show the gold crest.#120A2E. The startup screen that follows keeps the crest in exactly that place, so the hand-off doesn't move it (second commit; before it, the startup status line re-centred the crest and two crests cross-faded at different heights).FWLockupView(replacesFWWingsView) shows the stacked lockup at 56 % of the window width, at most 280 pt, on PIN, welcome, onboarding start and About. PIN and welcome cap it at 20 % of the window height so the keypad and actions keep their room. The in-app PIN check (logout confirmation) keeps its keypad in place and fits the lockup above the heading.crest-badge. Older screens that size themselves from their images (wallet avatars, buttons, the QR logo, the backup banner) keep their asset names and point sizes with the badge inside; slots under 32 pt usecrest-badge-small.fwWings,fwSunBadge,fwSunBadgeSmall,fwSunGlow, the winged-sun masters andFWWingsView.Welcome: the crow and its call
FWWelcomeHeroViewplayshero/welcome-idle.mp4full-bleed behind the top of the page, aspect-fill and anchored to the top, in a muted loopingAVPlayerLayerover the poster. The loop has no audio track, so it never touches the audio session. It pauses off screen and in the background. Reduce Motion (and the app's Reduced effects) shows the poster only.playsSoundtrait). It playshero/welcome-caw.mp4once with sound in a second player, crossfading 250 ms into the clip and back; the loop restarts at its first pose. Taps during the call are ignored.AVAudioSessioncategory.ambient, so the silent switch mutes it and other audio keeps playing; the previous category is restored afterwards. With motion restricted, the call's sound plays alone over the still poster.Colour
#120A2E, wells#0C0722, brand pink#E6006Ffor marks, glows and active navigation, primary fill#D6006A → #A8005Aunder white labels (5.2:1; pressed#A8005A), pink text#FF4FA3for the active tab label (4.5:1 on ink, which#E6006Fis not), solar gold#FFC24Aat the core of the glows, text#FAF6FF/#CFC4E6. Semantic colours (success, warning, danger, price) are unchanged. Sora stays.scripts/design/retint-legacy-colours.py. A sweep of Swift, storyboards, xibs and colour sets for the retired magentas found one live hard-coded value, the staking stories preview's highlighted fill, now#E6006F(8a0b91f00); the remaining xib hits are Interface Builder's<namedColor>caches, which resolve from the asset catalog at runtime.design/fw.tokens.jsonmoved with the kit's owntools/apply-tokens.mjs(17 colours:#FF009A → #E6006F,#D6007F → #D6006A,#A3006A → #A8005A,#FF4DB8 → #FF4FA3), and the colour sets andFWTokens.swiftgradients and digest were regenerated from it.fwBrandis#E6006F;fwTextBrandis#FF4FA3because the contrast contract requires 4.5:1 for text.Related Issue
No tracked issue. Owner request, 10 November 2026.
Target Branch
developmasterand is a release or hotfix PRThe head is named
release/*becausevalidateonly letsrelease/*andhotfix/*into master. It does not change the app version.Type of Change
Screenshots / Videos
Captured on an iOS 27.0 iPhone 17 Pro simulator from
77572759a(the last app-code commit), in the workspace atbuild/reports/ios-yatagarasu-brand/screens/:01-home-screen-icon.png02-launch-screen.png03-welcome-loop-a.png,04-welcome-loop-b.png05-welcome-call.mp4,05-welcome-call-frames.png06-welcome-call-mid.png,07-welcome-after-call.png08-pin-setup.png,13-pin-unlock.png15-pin-check.png10-wallet.png,11-settings.png,12-about.png16-launch-handoff.mp4,16-launch-handoff-frames.pngThe wallet was imported from the public BIP39 test vector in
fearlessTestsfixtures; nothing was captured while the phrase was on screen.Test Plan
Built (Debug, simulator, ad hoc signed) and tested locally. Head
8a0b91f00=77572759aplus two test-only edits (ef6bab1da) and one xib colour (8a0b91f00, build checked).fearlessTests, iOS 27.0 simulator, on77572759a#0E1024→#0C0722) andFWTokens.sourceSHA256; updated inef6bab1daef6bab1da: FWDesignSystemTests, TokenLensViewControllerTests, FWContrastContractTests, FWSunlitTests, StartupRecoveryPresenterTests77572759a--check,--check --no-kit)77572759aand onef6bab1daNew tests:
FWLockupViewTests(width rule per page, tablet maximum, the unlock keypad stays put, artwork and slot sizes, glow),FWWelcomeHeroTests(bundled media, aspect-fill anchored top, loop pauses off screen and in the background, Reduce Motion poster, the call once with sound and taps ignored, background stops the call, sound alone under Reduce Motion, the labelled button),testRootCrestStaysWhereTheLaunchScreenDrawsIt, and updated palette, contrast-contract and PIN tests.The tests caught one layout bug before review: the lockup SVG's own 640 × 505 pt size out-pulled the 56 % width constraint, so pages showed it at the 280 pt maximum.
Walked on the simulator: home-screen icon, launch screen and hand-off, welcome loop and call, PIN setup, unlock, PIN check, wallet, Settings, About.
Not verified: the call's sound was not heard (the simulator recording carries no audio; the tests check the clip has an audio track and plays unmuted at full volume under
.ambient); Reduce Motion was checked in unit tests, not toggled on the simulator; physical devices; a signed release build; iPad screens other than through the tests.Risks & Rollout
70771ec2…, whoseaudio/caw.m4adiffers from the first release only in its MP4 header times (identical audio). The kit generator now pins those times, so later kit rebuilds reproduce this manifest byte for byte.🤖 Generated with Claude Code