Skip to content

Commit 4837a67

Browse files
committed
ad and tracker blocker
1 parent 81815d7 commit 4837a67

57 files changed

Lines changed: 34354 additions & 11911 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

NEXTSTEPS2.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# NEXTSTEPS2 — configuration to finish the Bittensor / wallet sign in
2+
#### (and, at the end, the Stripe checkout config)
3+
4+
Follow-up to [BITTENSOR.md](BITTENSOR.md). The code is built and compiling across
5+
`server`, `sdk`, `android`, `apple`, and `mmm/ur.io`. What remains is **configuration**:
6+
one WalletConnect project id in three config files, three deploys in order, and a
7+
short QA pass over four values that could not be verified from the repo.
8+
9+
Nothing below blocks a build. Every unset value degrades gracefully (see
10+
[If the project id is left empty](#if-the-project-id-is-left-empty)).
11+
12+
---
13+
14+
## 1. WalletConnect Cloud project id
15+
16+
One project id is shared by all clients.
17+
18+
**Create it:** a project at the WalletConnect / Reown Cloud dashboard →
19+
copy the **Project ID**. In the project settings:
20+
21+
- Name: `URnetwork`, URL: `https://ur.io`, icon: the ur.io favicon
22+
(this metadata is what the wallet shows on the approval screen — the clients
23+
already send the same values, so keep them consistent).
24+
- Allowed domains / origins: `ur.io` (add `localhost` for local dev of the web ui).
25+
26+
**Then set it in three places.** All three are placeholders today.
27+
28+
### Android — `android/app/local.properties`
29+
30+
```properties
31+
WALLETCONNECT_PROJECT_ID=<project id>
32+
```
33+
34+
Surfaces as `BuildConfig.WALLETCONNECT_PROJECT_ID` (wired in `app/app/build.gradle`
35+
next to `BUNDLER_RPC_URL`), and is passed to the bridge as `wc_project_id` by
36+
`ui/login/LoginUtils.kt`. Gradle prints a warning when it is unset.
37+
38+
> `local.properties` is **not** in version control — it must also be set on the
39+
> build machine / CI, the same way `BUNDLER_RPC_URL` is.
40+
41+
### Apple — `apple/app/URnetwork-Info.plist`
42+
43+
```xml
44+
<key>URWalletConnectProjectId</key>
45+
<string><project id></string>
46+
```
47+
48+
Read by `ConnectWalletProviderViewModel.openBittensorSignIn` and passed to the
49+
bridge as `wc_project_id`.
50+
51+
### Web + bridge — `mmm/ur.io` build environment
52+
53+
```sh
54+
PUBLIC_WALLETCONNECT_PROJECT_ID=<project id>
55+
```
56+
57+
**`PUBLIC_`, not `VITE_`** — production is built by Astro, whose `envPrefix` is
58+
`PUBLIC_`, so a `VITE_*` variable never reaches the bundle (this is also why the
59+
other client config there — `PUBLIC_SOLANA_MERCHANT`, `PUBLIC_SOLANA_USDC_MINT`
60+
uses that prefix). A WalletConnect project id is a public client identifier meant
61+
to ship in the browser bundle, so `PUBLIC_` is correct on its own merits too.
62+
There is no `.env` file in the repo today — set it in the build environment, or
63+
add `mmm/ur.io/react/.env`. It is read by **both**:
64+
65+
- `react/src/components/WalletConnect.jsx` — the `/wallet-connect` bridge the
66+
native apps open. Precedence: the `wc_project_id` query param the app sends
67+
**wins**; this env var is the fallback for direct visits.
68+
- `react/src/auth/walletAuth.js` — the web ui's own "Continue with Bittensor" /
69+
"Continue with Solana" buttons in the login dialog.
70+
71+
The astro site imports these from `react/src` (`@ur` alias), so one value covers
72+
both builds.
73+
74+
### If the project id is left empty
75+
76+
No crash and no dead buttons — wallet sign in simply falls back to **injected
77+
wallets only**:
78+
79+
| surface | with a project id | without |
80+
|---|---|---|
81+
| web login dialog (desktop) | extension, else QR | extension only |
82+
| web login dialog (mobile) | wallet app deep link | *no wallet path* |
83+
| app sign in → bridge (desktop) | extension, else QR | extension only |
84+
| app sign in → bridge (mobile) | wallet app deep link | wallet's in-app browser only |
85+
86+
Desktop users with an extension (Phantom/Solflare; Bittensor Wallet, SubWallet,
87+
Talisman, polkadot-js) work either way. **Mobile wallet pairing is the thing the
88+
project id buys.**
89+
90+
---
91+
92+
## 2. Deploy order
93+
94+
1. **`server`** — sr25519 verification + the `TAO` blockchain. Must be live
95+
**before** any client that can produce a Bittensor login, or those logins fail
96+
with `unsupported blockchain`. No DB migration is needed (`blockchain
97+
varchar(32)` already fits `TAO`).
98+
2. **`mmm/ur.io`** — the `/wallet-connect` bridge (bittensor provider +
99+
WalletConnect) and the web login dialog. The shipped apps point at
100+
`https://ur.io/wallet-connect`, so the site must be redeployed before app
101+
releases reach users.
102+
3. **`android` / `apple`** releases, built against the rebuilt SDK artifacts
103+
(already regenerated locally: `.aar`, `.xcframework`, cgo headers).
104+
105+
---
106+
107+
## 3. Verify in QA (four values written from spec, not from this repo)
108+
109+
Each is isolated as a named constant with a comment. If a wallet rejects the
110+
session proposal or the server rejects a signature, start here.
111+
112+
| # | value | where |
113+
|---|---|---|
114+
| 1 | Bittensor CAIP chain id `polkadot:2f0555cc76fc2840a25a6ea3b9637146` (first 32 hex of the finney genesis hash) | `react/src/components/WalletConnect.jsx`, `react/src/auth/walletAuth.js` |
115+
| 2 | Solana CAIP chain id `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` (mainnet-beta) | `react/src/auth/walletAuth.js` |
116+
| 3 | WalletConnect Solana signature is base58 → decoded to base64 for the server | `react/src/auth/walletAuth.js` (`signInWithSolana`) |
117+
| 4 | **One real-wallet signature end to end.** sr25519 is verified round-trip against `go-schnorrkel`, but not yet cross-implementation. Sign in once with a real wallet (Bittensor Wallet extension, polkadot-js, or a WalletConnect wallet) and confirm the server accepts it. | `server/model/auth_bittensor.go` |
118+
119+
The injected-wallet path touches neither chain id — that is the common desktop
120+
case, and it exercises the server verification the same way.
121+
122+
---
123+
124+
## 4. Behavior worth knowing before QA
125+
126+
- **Signed message**: all clients sign the static `"Welcome to URnetwork"`, the
127+
same string the Solana flow already signs. No client sends a nonce; the server
128+
only enforces one *when present* (`handleLoginWallet`). If nonce presence is
129+
ever made mandatory, every wallet client must be updated together.
130+
- **Bittensor wallets are storage only.** They are recorded for future use and
131+
can never be the payout wallet: the apps hide "Make default", and the server
132+
rejects it in `SetPayoutWallet` **and** skips the auto-set-first-wallet-as-default
133+
on wallet creation. A Bittensor *signup* also creates no payout wallet
134+
(`NetworkCreate` only auto-creates one for `SOL`/`MATIC`).
135+
- **Connect wallet** for Bittensor is paste-an-address (ss58, checksum-validated
136+
server side) — no signature, per the decision in BITTENSOR.md.
137+
- **Sign-up parity**: an unlinked wallet routes to create-network with the wallet
138+
on every client, including the web dialog (which swaps the email/password
139+
fields for the connected wallet's address).
140+
141+
---
142+
143+
## 5. Stripe checkout (the non-Apple upgrade path)
144+
145+
`POST /stripe/create-checkout-session` now takes a `ui_mode`:
146+
147+
- **`hosted`** (default, back-compatible) → returns `checkout_url` for the system browser. This is what the website already uses.
148+
- **`embedded`** → returns `client_secret` + `publishable_key`, for **inline** checkout: the desktop apps load `https://ur.io/checkout?client_secret=<cs>&redirect_link=urnetwork://…` in an embedded webview (WebView2 / WebKitGTK). On completion the page returns `urnetwork://…?status=complete&session_id=…`, and the app polls the subscription balance.
149+
150+
A single Stripe Checkout Session **cannot** return both a `client_secret` and a `url` — the two are mutually exclusive by `ui_mode` — so the caller picks the mode. Note embedded checkout has **no cancel URL** (the customer never leaves the page), so the app must provide its own close affordance.
151+
152+
The subscription is granted by the **`invoice.paid` webhook**, never by the client. The client only polls afterwards.
153+
154+
### Config to fill in
155+
156+
```yaml
157+
# config/<env>/stripe.yml (the config repo — outside android/apple/server)
158+
checkout:
159+
return_url: "https://ur.io/checkout?complete=1&session_id={CHECKOUT_SESSION_ID}" # NEW, required for embedded
160+
```
161+
162+
```sh
163+
# mmm/ur.io build environment
164+
PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_… # pk_test_… on canary
165+
```
166+
167+
Both are optional-but-degrading: without `return_url`, embedded mode refuses cleanly ("Checkout is not configured") instead of stranding someone mid-payment in a webview; without the publishable key the build warns and `/checkout` says the same. A **malformed** key fails the build on purpose.
168+
169+
---
170+
171+
## Appendix — unrelated build config still outstanding
172+
173+
Not part of Bittensor, but needed to ship the desktop SDK artifacts (the cgo
174+
header already exports `TAO`):
175+
176+
- **windows/arm64** SDK dll — needs `llvm-mingw` on the build host
177+
(`x86_64` builds today with the installed mingw-w64).
178+
- **linux** SDK `.so` — needs `zig` (`sdk/cgo/Makefile` pins the glibc 2.35 floor
179+
via `zig cc`).
180+
181+
Until both are installed, `make -C sdk/cgo build_windows build_linux` cannot
182+
produce `URnetworkSdkWindows.zip` / `URnetworkSdkLinux.zip`, and the
183+
`windows` / `linux` apps cannot link the connect-drawer work against the current
184+
SDK.

app/URnetwork-Info.plist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
<string>solflare</string>
4040
<string>phantom</string>
4141
</array>
42+
<!-- WalletConnect Cloud project id for the Bittensor wallet sign in, passed
43+
to the ur.io/wallet-connect bridge. When empty, the bridge falls back to
44+
injected wallets only (browser extension / wallet in-app browser). -->
45+
<key>URWalletConnectProjectId</key>
46+
<string></string>
4247
<key>NEVPNConfiguration</key>
4348
<array>
4449
<string>network.ur.extension</string>

app/extension/PacketTunnelProvider.swift

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
3232
private var close: (() -> Void)?
3333
private var memoryPressureSource: DispatchSourceMemoryPressure?
3434
private var connected: Bool = false
35+
// the dns servers currently applied to the tunnel, used to detect when the
36+
// network settings need a re-apply after a dns settings change
37+
private var appliedTunnelDnsServers: [String] = []
3538
private var stopped: Bool = false
3639
private var shouldSaveKeyMaterial: Bool = true
3740
private let packetReadLock = NSLock()
@@ -271,6 +274,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
271274
device.setProvideControlMode(localState.getProvideControlMode())
272275
device.setProvideNetworkMode(localState.getProvideNetworkMode())
273276
device.setRouteLocal(localState.getRouteLocal())
277+
// the device restores the blocker from its own local state at creation,
278+
// but seed it here too: when the app and the extension do not share
279+
// storage, a toggle made before the tunnel ever ran would otherwise be
280+
// lost on the first start
281+
device.setBlockerEnabled(localState.getBlockerEnabled())
274282
device.setCanShowRatingDialog(localState.getCanShowRatingDialog())
275283
device.setCanPromptIntroFunnel(localState.getCanPromptIntroFunnel())
276284
device.setCanRefer(localState.getCanRefer())
@@ -377,6 +385,19 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
377385
let defaultLocationChangeSub = device.add(DefaultLocationChangeListener { location in
378386
try? localState.setDefaultLocation(location)
379387
})
388+
// re-apply the network settings when the dns settings change the tunnel
389+
// dns servers (e.g. unencrypted local servers set or cleared)
390+
let dnsResolverSettingsChangeSub = device.add(DnsResolverSettingsChangeListener { _ in
391+
DispatchQueue.main.async {
392+
if self.tunnelDnsServers() != self.appliedTunnelDnsServers {
393+
self.setTunnelNetworkSettings(self.networkSettings()) { error in
394+
if let error = error {
395+
self.logger.error("[PacketTunnelProvider]failed to set tunnel network settings: \(error.localizedDescription)")
396+
}
397+
}
398+
}
399+
}
400+
})
380401
let updateWindowStatus = { (windowStatus: SdkWindowStatus?) in
381402
var connected = false
382403
if let windowStatus = windowStatus {
@@ -480,6 +501,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
480501
provideSecretKeysSub?.close()
481502
locationChangeSub?.close()
482503
defaultLocationChangeSub?.close()
504+
dnsResolverSettingsChangeSub?.close()
483505
windowStatusChangeSub?.close()
484506
provideNetworkModeChangeSub?.close()
485507
// packetContext.wrappingIncrement(ordering: .relaxed)
@@ -536,18 +558,21 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
536558
let ipv6Settings = NEIPv6Settings()
537559
networkSettings.ipv6Settings = ipv6Settings
538560

539-
// DNS from the SDK device's tunnel DNS setting (default plain 1.1.1.1). Plain
540-
// :53 lets the UpgradeMux intercept and upgrade it; a DoH setting is applied at
541-
// the OS level and bypasses the mux.
561+
// DNS from the SDK device, like the tunnel address: the dns settings'
562+
// unencrypted local servers when set, otherwise the default plain 1.1.1.1
563+
// (see `tunnelDnsServers`). Plain :53 lets the UpgradeMux intercept and
564+
// upgrade it; a DoH setting is applied at the OS level and bypasses the mux.
565+
let dnsServers = self.tunnelDnsServers()
566+
self.appliedTunnelDnsServers = dnsServers
542567
let tunnelDns = self.device?.tunnelDnsSetting()
543-
let dnsServer = (tunnelDns?.server.isEmpty == false) ? tunnelDns!.server : "1.1.1.1"
544568
let dnsSettings: NEDNSSettings
545569
if let tunnelDns = tunnelDns, tunnelDns.doh, !tunnelDns.dohUrl.isEmpty {
570+
let dnsServer = tunnelDns.server.isEmpty ? "1.1.1.1" : tunnelDns.server
546571
let dohSettings = NEDNSOverHTTPSSettings(servers: [dnsServer])
547572
dohSettings.serverURL = URL(string: tunnelDns.dohUrl)
548573
dnsSettings = dohSettings
549574
} else {
550-
dnsSettings = NEDNSSettings(servers: [dnsServer])
575+
dnsSettings = NEDNSSettings(servers: dnsServers)
551576
}
552577
// route every DNS query to the tunnel resolver (empty string matches all
553578
// domains). without this the OS may not send :53 queries into the tunnel, so
@@ -561,6 +586,24 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
561586
return networkSettings
562587
}
563588

589+
/// The plain-dns servers for the tunnel, from the sdk device like the tunnel
590+
/// address: the dns settings' unencrypted local servers when set, otherwise
591+
/// the default plain 1.1.1.1 (which the UpgradeMux can intercept and upgrade).
592+
/// The tunnel is ipv4-only (no ipv6 addresses or routes), so only the ipv4
593+
/// resolvers apply.
594+
func tunnelDnsServers() -> [String] {
595+
var servers: [String] = []
596+
if let addresses = self.device?.tunnelDnsAddressesIpv4() {
597+
for i in 0..<addresses.len() {
598+
servers.append(addresses.get(i))
599+
}
600+
}
601+
if servers.isEmpty {
602+
servers = ["1.1.1.1"]
603+
}
604+
return servers
605+
}
606+
564607
private func hasStaleLocalState(_ localState: SdkLocalState, byJwt: String, instanceId: SdkId) -> Bool {
565608
let storedByJwt = localState.getByJwt()
566609
let storedInstanceId = localState.getInstanceId()?.string()
@@ -865,6 +908,19 @@ private class WindowStatusChangeListener: NSObject, SdkWindowStatusChangeListene
865908
}
866909
}
867910

911+
private class DnsResolverSettingsChangeListener: NSObject, SdkDnsResolverSettingsChangeListenerProtocol {
912+
913+
private let c: (_ dnsResolverSettings: SdkDnsResolverSettings?) -> Void
914+
915+
init(c: @escaping (_ dnsResolverSettings: SdkDnsResolverSettings?) -> Void) {
916+
self.c = c
917+
}
918+
919+
func dnsResolverSettingsChanged(_ dnsResolverSettings: SdkDnsResolverSettings?) {
920+
c(dnsResolverSettings)
921+
}
922+
}
923+
868924
private class ProvideNetworkModeChangeListener: NSObject, SdkProvideNetworkModeChangeListenerProtocol {
869925

870926
private let c: (_ mode: String?) -> Void

0 commit comments

Comments
 (0)