feat(ios,macos): seedphrase sign-in, instant account creation, multi-method auth settings#331
Open
Ryanmello07 wants to merge 122 commits into
Open
feat(ios,macos): seedphrase sign-in, instant account creation, multi-method auth settings#331Ryanmello07 wants to merge 122 commits into
Ryanmello07 wants to merge 122 commits into
Conversation
Updates the local binary target path from to so it resolves against the Ryanmello07/urnetwork-sdk fork checked out next to this repo.
Builds the forked urnetwork-sdk for iOS, then archives and packages an unsigned IPA on pushes to beta/custom-server and workflow_dispatch. Uploads the IPA to a GitHub prerelease on the forked iOS repo.
…an find the built xcframework
Upstream renamed AuthNetworkClientArgs.Description to DeviceDescription
in the Go SDK specifically because 'description' collides with the
read-only NSObject.description property in the gomobile Swift bindings -
which is exactly the compile error this fixes ("cannot assign to
property: 'description' is immutable").
Ports Android's NetworkServerSelector.kt to iOS/macOS. Lets a user point the app at a different network domain (self-hosted or alternate) instead of the hardcoded ur.network, or set explicit API/connect url overrides. - NetworkConfig.swift: names the official host/env/store/wallet/sso defaults that were previously inlined in DeviceManager with a "TODO: this should be moved into a config" comment. Values are unchanged - this only names them so a reset button has something to reset to. Note ios's wallet="circle" default is intentionally left alone (not copied from Android's "solana"/"ethos" flavor values, which govern something else - Solana wallet sign-in already works correctly here independent of this field). - DeviceManager.swift: exposes the underlying SdkNetworkSpaceManager (previously a local var, only used once at init) plus current/ configured api+connect url accessors, and an applyNetworkSpace(...) method that switches the active network space. No SDK/Go changes needed - NetworkSpaceManager/NetworkSpace already exist in the shared Go SDK and are used identically by Android. - NetworkServerUtils.swift: Swift port of the Kotlin normalization helpers (host/url normalization, insecure-scheme detection, derived service url preview). - NetworkServerSheet(+ViewModel): the settings sheet itself, modeled on the existing UpdateReferralNetworkSheet pattern already used elsewhere in this codebase. - LoginInitialView.swift: adds a "Change Network API" entry point next to (but outside the iOS-only gating of) "Try Guest Mode", available on both iOS and macOS per direction - unlike Solana sign-in, which stays iOS-only since it needs a browser-based flow on desktop that isn't built yet.
…rnetwork-ios into beta/custom-server # Conflicts: # app/network/Authenticate/LoginInitial/LoginInitialView.swift
UrTextField.placeholder is LocalizedStringKey, which only auto-converts from string literals, not String variables. Wrap the three computed placeholder values (officialHostName, derivedApiUrl, derivedConnectUrl) explicitly.
The SDK build (make build_apple) already produces a macOS xcframework slice (macos/arm64, macos/amd64), and the app project already declares macosx in SUPPORTED_PLATFORMS, but the workflow never built or published a macOS app. Add an unsigned macOS archive + zip package and attach it to the prerelease alongside the iOS IPA.
The macOS SSOButtons variant showed the Solana sign-in button unconditionally, unlike the iOS variant which only shows it when a Solana wallet app is installed. Remove it from macOS entirely; wallet address entry elsewhere (post-login wallet management) is unaffected.
Same NetworkClientInfo.Description -> DeviceDescription rename as Android's SettingsViewModel fix. This one silently compiled (Swift's NSObject.description is a valid read-only String getter), but returned the object's default description instead of the actual device description text in the Settings device list.
- apply(hostName:apiUrl:connectUrl:) now normalizes its own hostName parameter instead of reading viewModel.normalizedHostName, so the method behaves correctly regardless of view-model state (previously harmless only because every call site happened to pass a value already in sync with the view model). - Fix a misleading error message shown when fetching the Solana wallet-auth challenge from the server fails, before any wallet interaction happens - it previously said 'error connecting to your wallet'.
NetworkServerSheet.swift doesn't reference any Sdk* types directly - it takes plain String/closure parameters and delegates the actual SDK calls to DeviceManager.
Same bug as Android's NetworkServerSelector (this file is an explicit Swift port of that normalization logic, per its own doc comment): normalizeNetworkHost preserved a trailing :port, which derivedServiceUrl then prepended a service prefix to, producing invalid hosts like 'api.192.168.1.5:8080'. Not flagged by Copilot on this PR, but the identical root cause was flagged on the Android PR and applies here too.
…ng Solana
Bittensor sign-in was still signing a static hardcoded message
('Welcome to URnetwork') via the ur.io/wallet-connect bridge - the same
replay-risk this project fixed for Solana. Give it the identical
server-issued-challenge flow:
- prepareBittensorChallenge() mirrors prepareSolanaChallenge(), fetching
a fresh challenge via the same AuthWalletChallenge SDK API with
blockchain="bittensor".
- handleBittensorSignIn() now fetches the challenge before opening the
wallet-connect bridge, instead of using the static welcomeMessage.
- handleBittensorWalletResult() gained the same isSigningForCreateNetwork
gate handleSolanaWalletResult() already had, so a fresh challenge gets
signed before creating a network from a wallet login too.
- Fixed a real pre-existing bug in the .create branch of
handleAuthLoginResult(): it unconditionally re-fetched a Solana
challenge and re-presented the Solana sheet regardless of which wallet
the user actually authenticated with, so a Bittensor login landing in
.create would have incorrectly tried to re-sign via Solana. It now
branches on connectWalletProviderViewModel.connectedWalletProvider.
CAVEAT: the exact blockchain identifier string the backend's
/auth/wallet-challenge endpoint expects for Bittensor is unverified from
this client-side change alone - "bittensor" was chosen by symmetry with
the existing "solana" value sent for that wallet. This needs to be
confirmed against the live server (or with the backend team) before
this is considered production-ready; if the identifier is wrong, the
challenge fetch will fail closed (clear network error, sign-in blocked)
rather than silently falling back to an insecure client-side message.
…server#402 The 'bittensor' blockchain string sent to /auth/wallet-challenge was previously an educated guess (by symmetry with Solana's 'solana'). urnetwork/server#402 has since been extended to accept it (and 'tao'/ 'TAO', case-insensitively) - this is now a confirmed contract, not an assumption.
The ad and tracker blocker upstream merge added .bittensor to the ConnectedWalletProvider enum but the isWalletAppInstalled switch wasn't updated. Bittensor wallets are browser extensions (not native apps) so return true like the macOS codepath — the ur.io/wallet-connect bridge handles detection.
…tion, bittensor switch - Guard against empty messageTemplate in prepareSolanaChallenge and prepareBittensorChallenge (return false instead of silently accepting). - Preserve IPv6 literals when stripping ports in normalizeNetworkHost: bracketed [2001:db8::1]:8080 keeps the address, bare IPv6 is not mangled, IPv4 host:port continues to strip correctly. - Add .bittensor case to isWalletAppInstalled switch (browser extension wallets are always reachable via the ur.io bridge). Copilot review on apple#327.
…igninWithSolanaSheet, isSigningMessage, claimSeekerTokenMessage)
…Locally hack - Replace parseAuthMethods to read from networkUser.authTypes array - Remove @published var hasSeedphraseLocally from SettingsViewModel - Remove error-substring detection for 'already exists' - Remove hasSeedphrase() helper, replace with authTypesContains() - Seedphrase section reads from authTypes to show generate vs regenerate - macOS form updated with same authTypes closure pattern
…ations - Added networkUserViewModel reference to ViewModel - Refresh NetworkUser data after generate/regenerate/removeAuth - Pass networkUserViewModel through to ViewModel init
The SDK's new AuthTypes field is a Go pointer (*StringList), which gomobile bridges to Swift as SdkStringList?. Update the seedphrase detection and auth-methods parsing helpers to safely unwrap it instead of assuming a non-optional value, matching the pattern already used in DnsSettingsStore/BlockActionsStore.
Not meant to ship — was accidentally committed alongside the auth_types refactor.
The seedphrase-display/success-handling flow was wrapped in #if os(iOS) only, with no macOS branch. macOS users creating an instant account got no seedphrase display and no success handling at all -- the account was created server-side but the app just sat on the create screen. Use .sheet(item:) + .interactiveDismissDisabled(true) on macOS, since .fullScreenCover isn't available there.
Three issues in the same file: - None of AddAuthSheet's four success paths (wallet, Apple, Google, generic addAuth) called refreshNetworkUser() after linking a new auth method, so Settings kept showing the old method list until an unrelated refresh happened. Now takes a NetworkUserViewModel and refreshes after each successful add. - pollForWalletConnection() polled for up to 60s with no cancellation wired to sheet dismissal, and the try? around Task.sleep silently swallowed cancellation errors even if something did cancel it. Now stores the connect Task, cancels it (and clears the pending wallet signature handler) in onDisappear, and the poll loop checks Task.isCancelled explicitly instead of relying on the swallowed throw. - Removed handleWalletAuth(), a function with zero call sites anywhere in the repo -- superseded by the connectWallet/signWalletChallenge flow.
macOS's Sign-In Methods section (net-new in this PR) only ever rendered the single legacy authType/userAuth fields and had no per-method Remove button -- the confirmation dialog for it was already wired in SettingsView.swift but nothing on macOS ever triggered it. A user with Google+Apple+Seedphrase all linked saw only one method on macOS and couldn't remove any of them there, while iOS correctly listed and could remove each one. Extracted authTypesContains()/parseAuthMethods()/methodDisplayName() (previously private to SettingsForm-iOS, and partly reimplemented inline on macOS) into a shared AuthMethods.swift, and rebuilt macOS's Sign-In Methods section to ForEach over authTypes with a Remove button, matching iOS.
ProfileView branched on the legacy singular authType field to choose claimNetworkName() vs saveNetworkName(). AddAuth never updates that field server-side (only RemoveAuth's COALESCE does), so an account created as seedphrase-only keeps authType == "seedphrase" forever, even after the user binds a real email/Google/Apple identity -- Profile would keep calling the first-time claim flow indefinitely instead of switching to the normal rename flow once a verified identity exists. Replaced the authType: String? param with needsNameClaim: Bool, computed from the live authTypes array as "no email/google/apple bound yet", matching the server's own requireEmailOrSsoBound gate that both claim-name and change-name are subject to.
isSeedphraseValid only checked the raw string was non-empty after trimming, so a single stray character enabled the Sign In button and hit the server with obviously-invalid input. validateWordCount() already computed the real 12/24-word check but only drove a warning label, never the actual submit gate. Now isSeedphraseValid uses the same word-count check.
…etwork createInstantAccount's callback never checked result.verificationRequired, unlike sibling createNetwork on the same shared NetworkCreateCallback/ result type. If the server ever returns verificationRequired for an instant-account response, the code previously fell through to a generic "No seedphrase in result" error instead of a clear message.
These two new macOS buttons applied .disabled(isLoginActionInFlight) but, unlike sibling Apple/Auth-Code buttons in the same view, never dimmed on that state -- they went non-interactive while staying at full opacity, giving no visual cue.
executeGenerateSeedphrase, executeRegenerateSeedphrase, and executeRemoveAuth each fired a refreshNetworkUser() network round-trip unconditionally after their do/catch block, so a caught failure still triggered a redundant fetch of the user record that couldn't have changed. Scoped the refresh to the success path in each.
The needsNameClaim computation (added in 8cb76da) checked authTypes for email/google/apple but missed "phone" -- a distinct auth_type value from "email" server-side, and a reachable sign-up/login path in this app (LoginInitialView/CreateNetworkView both offer "Email or phone number"). A phone-authenticated account was incorrectly treated as having no identity method bound, routing it to the claim-name flow instead of the normal change-name flow. Found in re-review before this landed anywhere.
…tity for name claim
ProfileView reads the displayed name from the cached JWT, which the
change/claim-name calls don't reissue, so the screen (and anywhere
else the JWT-derived name is read) kept showing the old name until
the SDK's next background refresh. Force a refresh on success, the
same way SubscriptionBalanceViewModel does after a balance change.
Also recognize a bound wallet ("solana") as a valid identity method
for needsNameClaim, matching the backend's requireVerifiedIdentityBound
change that now accepts wallet-bound users for claim/change-name.
Cold launch already gets a fresh JWT (the SDK's token manager refreshes once immediately on start), but resuming from the background doesn't -- the token manager only refreshes on its own schedule (~14 days out from expiration) or when explicitly asked. Add a scenePhase hook to force a refresh on foreground so a network name (or anything else JWT-derived) changed from another device or the web dashboard shows up without needing a full logout/login.
…d multi-method auth settings Adds a full seedphrase-based auth flow alongside the existing email/SSO/wallet paths: - Seedphrase login and instant (no-verification) account creation, with a seedphrase display/backup screen on both iOS and macOS. - A rebuilt Settings > Sign-In Methods section (shared between iOS and macOS) that lists and manages every bound auth method live from the server's auth_types array, with an AddAuthSheet supporting email/password, Apple, Google, and Solana wallet binding. - Profile's claim-vs-change network name flow now checks live auth_types instead of a frozen legacy field, so it correctly detects when a user still needs to claim a name. - Guest mode removed (GuestModeSheet, GuestUpgradeViewModel, and the associated create-network guest path deleted). This is the exact diff behind #4, squashed into one commit for this upstream PR (that branch's own history is ~70 commits, mostly iterative CI/build-error/type-checker-timeout fixes with no standalone review value). Depends on urnetwork/sdk#132 (AuthTypes/WalletAddress fields and the seedphrase/wallet-auth endpoints this UI calls) -- opening as a draft until that lands, since this won't build against a Package.swift pointed at stock upstream sdk/main until then.
…eedphrase-auth-ios
…t/seedphrase-auth-upstream
…-auth-upstream Brings ~25 real fix commits from the actively-developed fork branch into the upstream-facing PR (urnetwork#331) that had drifted behind since its initial squash: JWT-refresh-on-foreground timing, stale UI after network name change, phone auth in name-claim identity check, macOS Sign-In Methods list/remove parity, several AddAuthSheet bugs (stale UI, uncancelled wallet poll, dead code), and SwiftUI type-checker splits/syntax fixes in SettingsView. Only conflict was LoginInitialView.swift: both branches had already independently landed the identical "dim disabled macOS buttons" opacity fix -- pure duplicate content differing only by a blank line, resolved by keeping one copy.
Not for upstream
Ryanmello07
commented
Jul 21, 2026
Comment on lines
+4
to
+6
| // see https://developer.apple.com/documentation/xcode/distributing-binary-frameworks-as-swift-packages | ||
| // Points to the local checkout of the forked SDK: | ||
| // https://github.com/Ryanmello07/urnetwork-sdk | ||
| // Build the XCFramework from that repo before using this package. |
Contributor
Author
There was a problem hiding this comment.
Undo changes, not for upstream use
Comment on lines
+16
to
+18
| path: "../../../sdk/build/apple/URnetworkSdk.xcframework" | ||
| path: "../../../urnetwork-sdk/build/apple/URnetworkSdk.xcframework" |
Contributor
Author
There was a problem hiding this comment.
Undo changes, not for upstream use
| var connectedWalletProvider: ConnectedWalletProvider? = nil | ||
|
|
||
| let welcomeMessage = "Welcome to URnetwork" | ||
| let claimSeekerTokenMessage = "Claim point multiplier by holding Seeker Pre-order or Saga Genesis token" |
Contributor
Author
There was a problem hiding this comment.
We should follow upstream and remove seeker token stuff
Addresses the three review comments left on this PR before upstream submission: - app/URnetworkSdk/Package.swift: restore upstream's original doc comment and xcframework path (../../../sdk/... instead of the fork's ../../../urnetwork-sdk/...), undoing local dev pointers to the forked SDK checkout. - Remove the fork-only "Claim Seeker Token" flow from Settings: drop ConnectWalletProviderViewModel.claimSeekerTokenMessage, the SolanaSignMessageSheet .sheet(...) block in SettingsView wired to it, and the associated presentSigninWithSolanaSheet/ isSigningMessage/setIsSigningMessage state in SettingsViewModel. The unrelated login-flow usage of SolanaSignMessageSheet and the handleWalletDeepLink/.onOpenURL wallet AddAuth deep-link handling in Settings are untouched.
Leftover from the removed fork-only Settings seeker-token sheet (SettingsView.swift/SettingsViewModel.swift, previous commit) -- this string-table entry had no remaining code reference. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QtgqtCmKJRXdsQ5ktiqwkg
Ryanmello07
marked this pull request as ready for review
July 23, 2026 22:49
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
Ports the seedphrase-auth feature from our beta fork (
Ryanmello07/urnetwork-ios#4, built againstbeta/custom-server) upstream. Adds:auth_typesarray, with anAddAuthSheetsupporting email/password, Apple, Google, and Solana wallet binding.auth_typesinstead of a frozen legacy field.GuestModeSheet,GuestUpgradeViewModel, and the associated create-network guest path deleted).This is the net diff of the beta PR, squashed into one commit — that branch's own history is ~70 commits, mostly iterative CI/build-error/type-checker-timeout fixes with no standalone review value upstream.
This depends on urnetwork/sdk#132 (
AuthTypes/WalletAddressfields and the seedphrase/wallet-auth endpoints this UI calls), which is still open.Package.swifthere points at the stocksdk/mainsibling checkout, so this won't build until #132 merges. Opening as a draft for that reason — will mark ready for review once the SDK PR lands and CI is green here.Test plan
urnetwork/sdk#132merges🤖 Generated with Claude Code