# Erste Schritte mit dem URnetwork SDK

Dieser Guide bringt einen Entwickler vom leeren Projekt zur ersten
Verbindung: ein Konto-JWT besorgen, das Binding für deine Plattform
installieren, ein Device hochbringen und prüfen, dass Datenverkehr fließt.
Das SDK ist ein Go-Modul, `github.com/urnetwork/sdk`, zugänglich über drei
Binding-Schichten: gomobile-Bindings für Android und Apple, ein
WebAssembly-Build für JavaScript und c-shared-Bibliotheken mit einer
kuratierten C-ABI für alles andere. Für die Architektur hinter jedem
Binding lies die [SDK-Tour](/docs/tour-sdk).

URnetwork nutzt von Mitgliedern betriebene Exit-Geräte. Provider erhalten
die Quell-IPs deiner Nutzer auf Relay-Pfaden nicht. Auf dem SDK gebaute
Devices verschlüsseln den Datenverkehr zum Provider zudem standardmäßig.
Lies [So funktioniert URnetwork](/docs/overview) für das vollständige
Modell.

## Was du brauchst

Alle drei Schichten liegen vor demselben Kern, auf dem die offiziellen
URnetwork-Apps gebaut sind: das Device (die Verbindungs-Engine), der
API-Client, das Network-Space-Konfigurationsmodell (mit welchem
Plattform-Deployment du sprichst, mit welchen Endpunkten und Flags) und die
View-Controller, headless Zustand-plus-Events-Objekte für die üblichen
Bildschirme, an die du deine UI binden oder die du ignorieren kannst. Die
Operator-API dahinter ist unter [/docs/api](/docs/api) dokumentiert. Du
baust deine eigene App auf URnetworks gehosteter Plattform: API, Relays und
Provider sind der Live-Dienst, und deine Nutzer bringen oder erstellen
Konten.

Plattform-Untergrenzen, pro Binding:

- **Android** — API-Level 24 oder neuer. Das Java-Paket ist
  `com.bringyour.sdk`.
- **iOS / macOS** — iOS 16.0 und macOS 13.5.
- **JavaScript** — nur Browser.
- **cgo** — Ubuntu 22.04+ (glibc 2.35+) oder Windows 10+, amd64 und arm64.
- **Bauen aus dem Quellcode** — Go 1.26+.

Das SDK ist MPL-2.0 (Mozilla Public License): Verlinke es frei in
Closed-Source-Apps. Das Copyleft auf Dateiebene verpflichtet nur dazu,
Änderungen an den eigenen Dateien des SDK zu teilen.

## Anmelden

Jeder Schnipsel unten nimmt ein `byJwt`: das Konto-JWT, ein signiertes
Login-Token, das die URnetwork-API ausstellt, wenn sich ein Konto
authentifiziert. Ein separates API-Key-System gibt es nicht.

Deine App fährt einmal einen Login-Flow durch den API-Client des SDK.
`authLogin` meldet die Auth-Methoden eines Identifiers;
`authLoginWithPassword` schließt ihn ab (`authVerify` beendet einen per
E-Mail oder SMS geschickten Code), und Wallet-Auth und `networkCreate` sind
parallele Einstiegspunkte. Wallet-Auth ist signature-only: Du sendest
`wallet_address`, `wallet_message` und `wallet_signature`, nie einen
Schlüssel, und keine URnetwork-Fläche fragt nach dem privaten Schlüssel
oder der Mnemonic einer Wallet. `networkCreate` ganz ohne Auth-Methode ist
der Sofort-Konto-Pfad (instant account): Er prägt ein dauerhaftes Konto und
gibt eine `seedphrase` zurück, URnetworks eigene Wiederherstellungsphrase
für dieses Konto, serverseitig erzeugt und genau einmal zurückgereicht.
Zeig sie dem Nutzer dann, sonst hat das Konto keinen Wiederherstellungsweg.

Jeder Pfad gibt `by_jwt` zurück. Persistiere es, ruf `setByJwt` auf und
übergib es dem Device-Konstruktor. Refresh-Code schreibst du keinen; der
Token-Manager der `Api` rotiert das JWT. Behandle einen harten Auth-Fehler
als „Login erneut ausführen“.

Das Konto ist auch die Abrechnungseinheit: Das Konto, dessen JWT das Device
hält, ist das, dessen Tarif gemessen wird. Gratis ist ein tägliches
Datenkontingent, Pro ein großes monatliches; aktuelle Zahlen auf
[ur.io/products](https://ur.io/products). Plane ein Konto pro Endnutzer —
was der Sofort-Konto-Pfad reibungslos macht — statt eines eingebetteten
Kontos, das Nutzung und Verhalten aller Nutzer in einem einzigen Akteur
sammelt. Läuft einem Konto das Datenvolumen aus, stockt der Transfer, bis
sich das Kontingent auffrischt — sag in deiner UX also „kein Datenvolumen
mehr“, nicht ein generischer Netzwerkfehler.

Das SDK besitzt auch den Bezahl-Tarif-Flow, denn der Client darf nie seinen
eigenen Preis nennen: Registriere einen Intent mit
`createSolanaPaymentIntent` (`reference` aus `createPaymentReference`,
`plan` `"monthly"` oder `"yearly"`), nimm `amountUsd` aus dem Ergebnis und
übergib es an `buildSolanaPaymentUrl`. Die C-ABI hat den Intent-Aufruf,
noch nicht den URL-Builder.

## Installieren

Vorgebaute Artefakte sind Release-Assets auf
[github.com/urnetwork/build](https://github.com/urnetwork/build/releases).
Versionen sind datumsbasiert, `vYYYY.M.D-<code>` (z. B.
`v2026.7.22-999364023`), kein SemVer: Pinne ein Release und aktualisiere
bewusst, denn die Version sagt, wann sie geschnitten wurde, nicht, ob sich
die API bewegt hat.

**Android.** Lade `URnetworkSdk-<version>.aar` herunter (plus
`-sources.jar` für die IDE-Navigation) und leg beide in ein Verzeichnis,
das dein Gradle-Modul schon scannt, z. B.:

```groovy
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
}
```

**iOS / macOS.** Lade `URnetworkSdk.xcframework.zip` herunter, entpacke es
und referenziere es aus einem lokalen Swift-Package als `binaryTarget`:

```swift
// Package.swift
targets: [
    .binaryTarget(name: "URnetworkSdk", path: "URnetworkSdk.xcframework")
]
```

Alle Typen tragen das Präfix `Sdk` (`SdkDeviceLocal`, `SdkNetworkSpace`,
...). Das xcframework trägt `ios/arm64`, `iossimulator/arm64`,
`macos/arm64` und `macos/amd64`; eine Intel-Slice für den iOS-Simulator
gibt es nicht.

**JavaScript.** Das npm-Paket ist
[`@urnetwork/sdk-js`](https://www.npmjs.com/package/@urnetwork/sdk-js),
ein Nur-Browser-Loader, der Gos `wasm_exec.js` plus das SDK-wasm lädt und
beide in der Seite instanziiert. Installiere den Tag `nightly`; er wird
nach demselben datierten Versionsschema aus dem aktuellen SDK geschnitten
und liefert das wasm mit. `latest` liegt Monate zurück und trägt gar kein
wasm — ein schlichtes `npm install` hinterlässt dir also einen Loader ohne
etwas zu laden.

```sh
npm install @urnetwork/sdk-js@nightly   # then pin the version it resolved to
```

Das wasm ist rund 43 MB, der gesamte Go-Kern, es wird also nicht
schrumpfen, aber es komprimiert gut: Liefer es als statisches Asset aus
(gzip oder brotli, hart gecacht) und lade es lazy, erst wenn der Nutzer
eine Verbindungs-Oberfläche erreicht. Lass nie einen Bundler es inlinen
oder transformieren, und liefere `sdk.wasm` und `wasm_exec.js` immer aus
demselben Build; der Glue-Code ist ABI-gepaart mit der Go-Toolchain, die
das wasm kompiliert hat, und der Build des SDK erzwingt, dass die beiden
Byte für Byte übereinstimmen.

**cgo.** C-shared-Bibliotheken, dieselbe Schicht, auf der die
ausgelieferten URnetwork-Apps für Linux und Windows gebaut sind, mit zwei
kuratierten Headern daneben:

- `libURnetworkSdk.so` — Linux.
- `URnetworkSdk.dll` (+ `urnetwork_sdk.def`) — Windows.
- `urnetwork_sdk.h` — die schlichte C-ABI.
- `urnetwork_sdk.hpp` — ein header-only C++17-RAII-Wrapper darüber, der
  [nlohmann/json](https://github.com/nlohmann/json) auf deinem
  Include-Pfad verlangt.

Alles mit einem C-Foreign-Function-Interface (Rust bindgen, Python ctypes,
C# P/Invoke) bildet sich auf diese Handles-und-JSON-ABI ab; der C++-Header
ist die eine Sprach-Annehmlichkeit. Mit MSVC erzeugst du zuerst die
Importbibliothek aus der `.def`-Datei:

```bat
lib /def:urnetwork_sdk.def /machine:x64 /out:URnetworkSdk.lib
```

**Aus dem Quellcode.** Du brauchst Go 1.26+ und für Mobile zuerst
`make init`: Es pinnt exakt das gomobile, mit dem die Bindings geschnitten
werden, und installiert das checksec, das das Android-Target ausführt.
`build_android` braucht zusätzlich ein gesetztes `ANDROID_NDK_HOME`, denn
es strippt `.comment` mit dem `llvm-objcopy` des NDK.
`sdk/build-android.sh` und `sdk/build-ios.sh` wickeln beides ein. Aus
`sdk/build`:

```sh
make init             # pinned gomobile + checksec; run before the mobile targets
make build_android    # AAR
make build_apple      # xcframework (build_ios is an alias)
make build_js         # wasm + loader
make build_linux      # c-shared .so, cross-compiled with zig
make build_windows    # c-shared .dll; not in `all` — the shipped one builds in a VM
```

`build_android` gatet auf gomobiles Skip-Liste — ein fehlendes Symbol ist
also ein Versionskonflikt, kein stiller Binding-Ausfall; gomobile bindet
keine Slices von Structs (Listen kreuzen als `SdkStringList`, `SdkIdList`,
...) und kein `context.Context`.

Externe Sicherheitsprüfungen, und ihre Grenzen, behandelt das
[Bedrohungsmodell](/docs/threat-model).

## Die Tunnel-Berechtigung

Die Betriebssystem-Zustimmung, und der Tunnel selbst, gehören deiner App.
Das SDK beginnt an der Paketschicht:

- Auf **Android** ist der `VpnService` deiner: Deine App deklariert ihn,
  holt die VPN-Zustimmung mit `VpnService.prepare()` ein und ruft
  `establish()` auf. Das SDK übernimmt an diesem File-Descriptor.
- Auf **Apple-Plattformen** ist die Aufteilung Apples Anforderung, nicht
  die des SDK: Paket-Tunnel laufen in einer separaten NetworkExtension mit
  engen Speichergrenzen — der Tunnel-Provider besitzt also das Device,
  während sich dein App-Prozess aus der Ferne daran anhängt.
- In einem **Browser** kann eine Seite kein Netzwerk-Interface besitzen,
  es gibt also keine Berechtigung zu erfragen, und nichts in der
  JavaScript-Schicht tunnelt den eigenen Datenverkehr der Seite. Beide
  JavaScript-Device-Modelle steuern stattdessen ein Device auf der
  Plattform. Die URnetwork-Erweiterung deckt Browser-Tabs ab; eine native
  App deckt die ganze Maschine ab.

Was URnetwork über Verbindungen speichert, steht im
[Bedrohungsmodell](/docs/threat-model).

## Verbinden

Ein neues Device startet auf den Netzwerk-Standardwerten. Die
Client-Provider-Sitzung ist ab Werk versiegelt: `setPerformanceProfile`
liefert `PostQuantumEncryption` eingeschaltet aus, das Flag hinter dem
App-Schalter „Post-Quanten-Verschlüsselung“ (Post Quantum Encryption), und
jeder aktuelle Provider-Build aktiviert die Responder-Seite. Mit
eingeschaltetem Flag arbeitet der Client fail-closed: Er trägt keine
Anwendungsdaten im Klartext und überspringt einen Provider, mit dem er nicht
versiegeln kann, statt ihn unversiegelt zu nutzen. Schalt es aus, und der
Datenverkehr kann wieder den Standardpfad nehmen. `AllowDirect` ist
standardmäßig aus; es ist die Opt-in-Geschwindigkeitseinstellung, die den
anonymisierenden Hop entfernt und diesem Provider die echte IP deines
Nutzers übergibt; die Apps zeigen sie invertiert als „Starke
Anonymisierung“ (Strong Anonymization), und gehostete Device-Profile
erzwingen sie aus. Leak-Schutz verdrahtest du selbst: Der Kill Switch ist
das Primitiv `setRouteLocal`, ein Device startet mit erlaubtem lokalem
Routing, und `setRouteLocal(false)` lässt Datenverkehr stoppen statt
zurückfallen, wenn der Tunnel unten ist. Das SDK bettet keine Analytics und
kein Crash-Reporting ein; seine einzigen Verbindungen sind die
Plattform-Endpunkte und die Relays und Provider, die das Device nutzt.

### Android

Bootstrap-Reihenfolge: Erstelle einen `NetworkSpaceManager`, der auf
app-privaten Speicher zeigt (das Verzeichnis hält lokalen Zustand,
einschließlich Zugangsdaten), erstelle den Network Space, dann setz das
Konto-JWT auf seinem API-Client. Der Schlüssel ist ein Hostname plus ein
Umgebungsname; Produktion ist `("ur.network", "main")`, und
`updateNetworkSpace` ist, was einen erstellt, während `getNetworkSpace`
nur einen existierenden Space zurückliest — auf einer frischen
Installation gibt es also null zurück. URLs leiten sich aus dem Schlüssel
ab (`https://api.<host>`, `wss://connect.<host>`; eine
Nicht-`main`-Umgebung stellt dem Dienst ein Präfix voran), aber die
Ableitung bevorzugt `migrationHostName`, das die Produktion auf
`bringyour.com` setzt: Die ausgelieferten Apps erreichen
`api.bringyour.com`, und `api.ur.network` löst nicht auf. Dann erstelle das
Device und übergib ihm den File-Descriptor des Tunnels:

```kotlin
import com.bringyour.sdk.Sdk

val manager = Sdk.newNetworkSpaceManager(context.filesDir.absolutePath)
val key = Sdk.newNetworkSpaceKey("ur.network", "main")
val networkSpace = manager.updateNetworkSpace(key) { it.migrationHostName = "bringyour.com" }
networkSpace.api.setByJwt(byJwt)

val device = Sdk.newDeviceLocalWithMemoryTarget(
    networkSpace,
    byJwt,
    deviceDescription,   // free-form, shown in the account's device list
    deviceSpec,          // e.g. Build.MODEL
    appVersion,
    Sdk.newId(),         // instanceId; persist and reuse per install
    /* enableRpc */ false,
    keyMaterial,         // persisted DeviceLocalKeyMaterial, or null for ephemeral
    memoryTargetByteCount,
)

// inside your VpnService, after establish():
val detachedFd = pfd.detachFd()          // ParcelFileDescriptor -> raw fd
val ioLoop = Sdk.newIoLoop(device, detachedFd) { /* done callback */ }
```

`newIoLoop` übernimmt den Besitz des detachten fd und pumpt Pakete in
beide Richtungen, bis du es schließt. Fass den fd nach dem Detachen nicht
wieder aus Java an.

Drei Konstruktor-Argumente verdienen Sorgfalt. `instanceId`: Erzeuge
einmal beim ersten Start ein `Sdk.newId()`, persistiere es und verwende es
für die Lebensdauer der Installation wieder (ein lebendes Device pro
Prozess); eine frische ID bei jedem Start fügt der Geräteliste des Kontos
einen Phantom-Eintrag hinzu. `keyMaterial`: null ist fein für einen reinen
Client, aber wenn das Device je Kapazität bereitstellen wird, persistiere
`getKeyMaterial()` im sicheren Speicher der Plattform. Es ist die
Provider-Identität des Device, und sie zu verlieren setzt die
Zuverlässigkeits-Historie des Providers zurück. `memoryTargetByteCount`:
ein Byte-Budget, an dem das Device seine Puffer und sein GC-Pacing
bemisst; wähl eines, das zur echten Obergrenze deines Prozesses passt
(siehe die [Tour](/docs/tour-sdk)). `DeviceLocal` ist in-process: Stirbt
dein Prozess, stirbt der Tunnel mit ihm — fahr den `VpnService` also als
Foreground-Service und erstelle das Device beim Neustart mit derselben
`instanceId` und demselben Key-Material neu.

### iOS / macOS

Der `NEPacketTunnelProvider` besitzt das `SdkDeviceLocal` und den
Paketfluss, während sich der App-Prozess als Remote-Device über Loopback
daran anhängt:

```swift
// in the packet tunnel provider (owns the device):
var err: NSError?
let device = SdkNewDeviceLocalWithMemoryTarget(
    networkSpace, byJwt, deviceDescription, deviceSpec, appVersion,
    instanceId, /* enableRpc */ true, keyMaterial, memoryTarget, &err)

// in the app process (attaches to it):
let remote = SdkNewDeviceRemoteWithDefaults(networkSpace, byJwt, instanceId, &err)
try remote?.setRpcServer(clientPem, serverCertPem: serverCertPem, hostPort: hostPort)
```

Die App prägt das Key-Material und die RPC-PEMs
(`SdkGenerateDeviceRpcKeyMaterial`) und übergibt sie der Extension in der
Provider-Konfiguration des `NETunnelProviderProtocol`; die Extension liest
`rpc_server_pem`, `rpc_client_pem` und `rpc_listen_hostport` von dort.
Eine App Group gibt es auf diesem Pfad nicht. Siehe die
[Tour](/docs/tour-sdk) dafür, warum die Aufteilung existiert und wie die
Wiederverbindung funktioniert.

### JavaScript

```js
import { URNetwork } from "@urnetwork/sdk-js";

const sdk = await URNetwork.init({
  wasmUrl: "/wasm/sdk.wasm",
  wasmExecUrl: "/wasm/wasm_exec.js",
});
```

Das wasm registriert seine Exporte auf `window`, fahr also eine
Modul-Instanz pro Seite. `init` ist innerhalb eines Moduls idempotent (ein
zweiter Aufruf gibt dieselbe Instanz zurück), aber zwei Kopien des
Loaders — etwa zwei Bundles oder zwei Frames, die sich einen Realm
teilen — rangeln um dieselben Globals. Initialisiere einmal in einem
Singleton auf Modulebene, nie in einem Komponenten-Lifecycle.

Zwei Device-Modelle:

- `sdk.createProxyDevice(...)` — ein leichtgewichtiger Client gehosteter
  Proxy-URLs, für den Fall, dass du nur „eine Proxy-URL, die über
  URnetwork austritt“ brauchst.
- `sdk.createPlatformDeviceRemote({...})` — ein volles `DeviceRemote`, das
  über einen WebSocket Device-RPC mit einem gehosteten Device spricht, für
  eine echte Verbindungs-UI (Standorte, Listener, Statistiken):

```js
const device = sdk.createPlatformDeviceRemote({
  apiUrl: "api.bringyour.com",
  platformUrl: "connect.bringyour.com",
  byJwt,
  proxyUrl,        // from the platform's proxy config endpoint
  signedProxyId,   // HMAC auth token, not the JWT — see the tour
});

const unsub = device.addConnectLocationChangeListener((loc) => {
  console.log("location:", loc?.name);
});
device.setConnectLocation({ bestAvailable: true });
```

`createPlatformDeviceRemote` wirft, wenn das geladene wasm älter ist als
das `DeviceRemote`-Binding; das ist das Symptom eines alten npm-Tags.

### cgo

Der ABI-Vertrag, in Kürze:

- Objekte sind **opake `uint64_t`-Handles**. `urnet_release(h)` gibt das
  Handle frei, ohne das Objekt zu stoppen — ruf also zuerst sein
  `*_close`/`*_stop` auf, wo eines existiert.
- Zurückgegebene `char*`-Strings gehören dem Aufrufer; gib sie mit
  `urnet_free_string` frei.
- Strukturierte Daten kreuzen die Grenze als **UTF-8-JSON**-Strings; IDs
  sind UUID-Strings, Zeiten sind Unix-Epoch-Millisekunden (`0` = keine).
- Callbacks feuern auf **beliebigen Go-verwalteten Threads**; marshalle
  auf deinen eigenen Thread. Ihre Strings und Puffer leben nur für den
  Aufruf, und Handles, die sie dir übergeben, gibst du selbst frei.
- Fehlbare Aufrufe nehmen ein `char** out_error`; im Fehlerfall wird es
  auf eine Meldung gesetzt, die du mit `urnet_free_string` freigibst.
  Übergib `NULL`, um den Text zu ignorieren.

Der Header trägt eine Plattform-Aufteilung: `urnet_new_io_loop`, die
fd-Pumpe, die die Linux-App nutzt, sitzt in `#if !defined(_WIN32)` und
fehlt in der Windows-`.def`. Auf Windows bewegst du Pakete mit
`urnet_device_local_send_packet` und
`urnet_device_local_add_receive_packet`.

Ein funktionierendes Ende-zu-Ende-Beispiel liegt unter `cgo/smoke`;
`make smoke_hpp` in `sdk/cgo` baut den C++-Wrapper-Smoke-Test gegen einen
Host-Build und führt ihn aus.

## Prüfen, ob es funktioniert

Bring das Device hoch und prüf den Exit: Jeder „Wie ist meine IP“-Check
durch den Tunnel sollte jetzt die Adresse eines Providers melden, nicht die
der Maschine. Derselbe Zustand ist im Code und im Konto sichtbar: Listener
feuern, wenn sich die Verbindung ändert (der JavaScript-Schnipsel oben
loggt jeden Standortwechsel, sobald er eintrifft), und das Device erscheint
in der Geräteliste des Kontos unter der `deviceDescription`, die du
übergeben hast. Auf der C-ABI meldet `urnet_live_handle_count()` lebende
Handles; assertiere in Leak-Tests, dass er zur Baseline zurückkehrt.

## Wenn etwas schiefgeht

Support sind die offenen Kanäle: Issues auf den Repos
([github.com/urnetwork](https://github.com/urnetwork)), Produkt-Feedback
auf [feedback.ur.io](https://feedback.ur.io) und Sicherheitsmeldungen an
security@ur.io (Offenlegungsrichtlinie auf [ur.io/vdp](https://ur.io/vdp)).
Einen bezahlten SDK-Support-Tarif gibt es heute nicht. Zwei Aussetzer haben
einfache Erklärungen: Ein Transfer-Stillstand auf einem funktionierenden
Tunnel ist meist ein Konto ohne Datenvolumen, und ein JavaScript-Loader
ohne etwas zu laden ist der npm-Tag `latest`. Die [Tour](/docs/tour-sdk)
behandelt die Fehlermodi, die du kennen solltest, bevor du auslieferst.

## Wie es weitergeht

- **Mach die [SDK-Tour](/docs/tour-sdk).** Die Architektur hinter jedem
  Binding: Prozess-Aufteilungen, Threading, Auth, Wiederverbindung und die
  Bemessung des Memory-Targets.
- **Sag deinen Nutzern, worauf sie sich einlassen.** Wenn deine App
  Datenverkehr durch URnetwork leitet, ist der [Überblick](/docs/overview)
  die maßgebliche Darstellung des Pfads und dessen, was jede Partei sehen
  kann, und das [Bedrohungsmodell](/docs/threat-model) die vollständige
  Akte dahinter.
- **Sieh dir die fertigen Apps an**, für das Erlebnis, das deine Nutzer
  bekommen: [Android](/docs/getting-started-android),
  [iOS](/docs/getting-started-ios), [macOS](/docs/getting-started-macos),
  [Windows](/docs/getting-started-windows),
  [Linux](/docs/getting-started-linux) und den
  [Browser](/docs/getting-started-browser).
