# Primeros pasos con el SDK de URnetwork

Esta guía lleva a un desarrollador de un proyecto vacío a una primera
conexión: consigue un JWT de cuenta, instala el binding de tu plataforma,
levanta un dispositivo y confirma que el tráfico fluye. El SDK es un módulo
Go, `github.com/urnetwork/sdk`, expuesto a través de tres capas de bindings:
bindings gomobile para Android y Apple, una compilación WebAssembly para
JavaScript, y bibliotecas c-shared con una ABI de C curada para todo lo
demás. Para la arquitectura detrás de cada binding, lee el
[recorrido del SDK](/docs/tour-sdk).

URnetwork usa dispositivos de salida gestionados por miembros. Los
proveedores no reciben las IPs de origen de tus usuarios en las rutas
retransmitidas. Los dispositivos construidos sobre el SDK además cifran el
tráfico hasta el proveedor por defecto. Lee
[Cómo funciona URnetwork](/docs/overview) para el modelo completo.

## Qué necesitas

Las tres capas dan frente al mismo núcleo sobre el que están construidas las
apps oficiales de URnetwork: el dispositivo (el motor de conexión), el
cliente de la API, el modelo de configuración de espacio de red (con qué
despliegue de la plataforma hablas, con qué endpoints y flags) y los view
controllers, objetos sin interfaz de estado más eventos para las pantallas
comunes, a los que puedes enlazar tu UI o ignorar. La API del operador que
hay detrás está documentada en [/docs/api](/docs/api). Estás construyendo tu
propia app sobre la plataforma alojada de URnetwork: la API, los relés y los
proveedores son el servicio en vivo, y tus usuarios traen o crean cuentas.

Mínimos por plataforma, por binding:

- **Android** — nivel de API 24 o más reciente. El paquete Java es
  `com.bringyour.sdk`.
- **iOS / macOS** — iOS 16.0 y macOS 13.5.
- **JavaScript** — solo navegador.
- **cgo** — Ubuntu 22.04+ (glibc 2.35+) o Windows 10+, amd64 y arm64.
- **Compilar desde el código** — Go 1.26+.

El SDK es MPL-2.0 (Mozilla Public License): enlázalo libremente en apps de
código cerrado. El copyleft a nivel de archivo solo obliga a compartir los
cambios a los propios archivos del SDK.

## Iniciar sesión

Cada fragmento de abajo toma un `byJwt`: el JWT de cuenta, un token de
inicio de sesión firmado que la API de URnetwork emite cuando una cuenta se
autentica. No hay un sistema aparte de claves de API.

Tu app ejecuta un flujo de login una vez a través del cliente de API del
SDK. `authLogin` informa de los métodos de autenticación de un
identificador; `authLoginWithPassword` lo completa (`authVerify` termina un
código enviado por correo o SMS), y la autenticación con cartera y
`networkCreate` son puntos de entrada paralelos. La autenticación con
cartera es solo por firma: envías `wallet_address`, `wallet_message` y
`wallet_signature`, nunca una clave, y ninguna superficie de URnetwork pide
la clave privada ni el mnemónico de una cartera. `networkCreate` sin ningún
método de autenticación es la vía de la cuenta instantánea: acuña una cuenta
permanente y devuelve una `seedphrase`, la frase de recuperación propia de
URnetwork para esa cuenta, generada en el servidor y entregada exactamente
una vez. Muéstrasela al usuario en ese momento, o la cuenta no tendrá vía de
recuperación.

Toda vía devuelve `by_jwt`. Persístelo, llama a `setByJwt` y pásalo al
constructor del dispositivo. No escribes código de renovación; el gestor de
tokens del `Api` rota el JWT. Trata un fallo duro de autenticación como
"ejecuta el login otra vez".

La cuenta es también la unidad de facturación: la cuenta cuyo JWT sostiene
el dispositivo es aquella cuyo plan se mide. El gratuito es una cuota de
datos diaria, Pro una mensual grande; números vigentes en
[ur.io/products](https://ur.io/products). Plantea una cuenta por usuario
final, que la vía de la cuenta instantánea hace sin fricción, en lugar de
una cuenta embebida que agrupe el uso y la conducta de todos los usuarios en
un solo actor. Cuando una cuenta se queda sin datos, la transferencia se
detiene hasta que la cuota se renueva, así que di "sin datos" en tu UX, no
un error de red genérico.

El SDK también posee el flujo del plan de pago, porque el cliente nunca debe
nombrar su propio precio: registra una intención con
`createSolanaPaymentIntent` (`reference` de `createPaymentReference`, `plan`
`"monthly"` o `"yearly"`), toma `amountUsd` del resultado y pásalo a
`buildSolanaPaymentUrl`. La ABI de C tiene la llamada de intención, aún no
el constructor de la URL.

## Instalar

Los artefactos precompilados son assets de versión en
[github.com/urnetwork/build](https://github.com/urnetwork/build/releases).
Las versiones van por fecha, `vYYYY.M.D-<code>` (p. ej.
`v2026.7.22-999364023`), no SemVer: fija una versión y actualiza
deliberadamente, porque la versión dice cuándo se cortó, no si la API se
movió.

**Android.** Descarga `URnetworkSdk-<version>.aar` (más `-sources.jar` para
la navegación en el IDE) y déjalos en un directorio que tu módulo Gradle ya
escanee, p. ej.:

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

**iOS / macOS.** Descarga `URnetworkSdk.xcframework.zip`, descomprímelo y
referéncialo desde un paquete Swift local como un `binaryTarget`:

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

Todos los tipos llevan el prefijo `Sdk` (`SdkDeviceLocal`,
`SdkNetworkSpace`, ...). El xcframework lleva `ios/arm64`,
`iossimulator/arm64`, `macos/arm64` y `macos/amd64`; no hay slice Intel del
simulador de iOS.

**JavaScript.** El paquete npm es
[`@urnetwork/sdk-js`](https://www.npmjs.com/package/@urnetwork/sdk-js), un
cargador solo para navegador que descarga el `wasm_exec.js` de Go más el
wasm del SDK y los instancia en la página. Instala la etiqueta `nightly`; se
corta del SDK actual con el mismo esquema de versiones por fecha y trae el
wasm. `latest` va meses por detrás y no lleva wasm alguno, así que un
`npm install` a secas te deja un cargador sin nada que cargar.

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

El wasm ronda los 43 MB, el núcleo Go entero, así que no va a encoger, pero
comprime bien: sírvelo como asset estático (gzip o brotli, con caché
agresiva) y cárgalo en diferido, solo cuando el usuario llegue a una
superficie de conexión. No dejes nunca que un bundler lo incruste o lo
transforme, y distribuye siempre `sdk.wasm` y `wasm_exec.js` de la misma
compilación; el pegamento está emparejado por ABI con la toolchain de Go que
compiló el wasm, y la compilación del SDK exige que los dos coincidan byte a
byte.

**cgo.** Bibliotecas c-shared, la misma capa sobre la que están construidas
las apps de Linux y Windows de URnetwork que se distribuyen, con dos
cabeceras curadas al lado:

- `libURnetworkSdk.so` — Linux.
- `URnetworkSdk.dll` (+ `urnetwork_sdk.def`) — Windows.
- `urnetwork_sdk.h` — la ABI de C plana.
- `urnetwork_sdk.hpp` — un envoltorio RAII C++17 de solo cabecera sobre
  ella, que requiere [nlohmann/json](https://github.com/nlohmann/json) en
  tu ruta de includes.

Cualquier cosa con una interfaz de funciones foráneas de C (Rust bindgen,
Python ctypes, C# P/Invoke) se acopla a esta ABI de handles y JSON; la
cabecera C++ es la única comodidad de lenguaje. Con MSVC, genera primero la
biblioteca de importación desde el archivo `.def`:

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

**Desde el código.** Necesitas Go 1.26+, y para móvil `make init` primero:
fija el gomobile exacto con el que se cortan los bindings e instala el
checksec que ejecuta el target de Android. `build_android` también necesita
`ANDROID_NDK_HOME` definido, porque elimina `.comment` con el
`llvm-objcopy` del NDK. `sdk/build-android.sh` y `sdk/build-ios.sh`
envuelven ambos. Desde `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` se apoya en la lista de omisiones de gomobile, así que un
símbolo ausente es un desajuste de versiones, no un binding caído en
silencio; gomobile no enlaza slices de structs (las listas cruzan como
`SdkStringList`, `SdkIdList`, ...) ni `context.Context`.

Las evaluaciones de seguridad de terceros, y sus límites, están recogidas en
el [modelo de amenazas](/docs/threat-model).

## El permiso del túnel

El consentimiento del sistema operativo, y el túnel en sí, pertenecen a tu
app. El SDK empieza en la capa de paquetes:

- En **Android**, el `VpnService` es tuyo: tu app lo declara, obtiene el
  consentimiento de VPN con `VpnService.prepare()` y llama a
  `establish()`. El SDK toma el relevo en ese descriptor de archivo.
- En las **plataformas Apple**, la división es un requisito de Apple, no
  del SDK: los túneles de paquetes corren en una NetworkExtension aparte
  con límites de memoria estrictos, así que el proveedor del túnel posee el
  dispositivo mientras el proceso de tu app se acopla a él en remoto.
- En un **navegador**, una página no puede poseer una interfaz de red, así
  que no hay permiso que pedir, y nada en la capa JavaScript tuneliza el
  tráfico de la propia página. Los dos modelos de dispositivo de JavaScript
  manejan en su lugar un dispositivo en la plataforma. La extensión de
  URnetwork cubre las pestañas del navegador; una app nativa cubre la
  máquina entera.

Para saber qué registra URnetwork sobre las conexiones, lee el
[modelo de amenazas](/docs/threat-model).

## Conectar

Un dispositivo nuevo arranca con los valores por defecto de la red. La
sesión cliente-proveedor va sellada de fábrica: `setPerformanceProfile`
sale con `PostQuantumEncryption` activado, el flag detrás del control
"Cifrado poscuántico" (Post Quantum Encryption) de las apps, y toda
compilación actual de proveedor habilita el lado que responde. Con el flag
activado el cliente es fail-closed: no lleva datos de aplicación en claro, y
omite todo proveedor con el que no puede sellar en lugar de usarlo sin
sellar. Desactívalo y el tráfico vuelve a poder tomar la ruta estándar.
`AllowDirect` está desactivado por defecto; es el ajuste de velocidad
opcional que quita el salto anonimizador y le entrega a ese proveedor la IP
real de tu usuario, las apps lo muestran invertido como "Anonimización
fuerte" (Strong Anonymization), y los perfiles de dispositivo alojados lo
fuerzan a desactivado. La protección contra fugas te toca cablearla a ti:
el kill switch (interruptor de corte) es la primitiva `setRouteLocal`, un
dispositivo arranca con el enrutamiento local permitido, y
`setRouteLocal(false)` hace que el tráfico se detenga en lugar de recurrir
a la ruta local cuando el túnel está caído. El SDK no incrusta analítica ni
informes de fallos; sus únicas conexiones son los endpoints de la
plataforma y los relés y proveedores que usa el dispositivo.

### Android

Orden de arranque: crea un `NetworkSpaceManager` apuntado a almacenamiento
privado de la app (el directorio guarda estado local, credenciales
incluidas), crea el espacio de red y luego pon el JWT de cuenta en su
cliente de API. La clave es un nombre de host más un nombre de entorno;
producción es `("ur.network", "main")`, y `updateNetworkSpace` es lo que
crea uno, mientras que `getNetworkSpace` solo relee un espacio que ya
existe, así que devuelve null en una instalación recién hecha. Las URLs
derivan de la clave (`https://api.<host>`, `wss://connect.<host>`; un
entorno distinto de `main` prefija el servicio), pero la derivación
prefiere `migrationHostName`, que producción fija en `bringyour.com`: las
apps distribuidas llegan a `api.bringyour.com`, y `api.ur.network` no
resuelve. Después crea el dispositivo y entrégale el descriptor de archivo
del túnel:

```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` toma posesión del fd desacoplado y bombea paquetes en ambas
direcciones hasta que lo cierras. No vuelvas a tocar el fd desde Java
después de desacoplarlo.

Tres argumentos del constructor merecen cuidado. `instanceId`: genera un
`Sdk.newId()` en el primer arranque, persístelo y reutilízalo durante la
vida de la instalación (un dispositivo vivo por proceso); un id nuevo en
cada arranque añade una entrada fantasma a la lista de dispositivos de la
cuenta. `keyMaterial`: null vale para un cliente puro, pero si el
dispositivo va a proveer capacidad alguna vez, persiste `getKeyMaterial()`
en el almacenamiento seguro de la plataforma. Es la identidad de proveedor
del dispositivo, y perderla reinicia el historial de fiabilidad del
proveedor. `memoryTargetByteCount`: un presupuesto de bytes contra el que
el dispositivo dimensiona sus búferes y el ritmo del GC; elige uno que
quepa en el techo real de tu proceso (ver el [recorrido](/docs/tour-sdk)).
`DeviceLocal` va dentro del proceso: si tu proceso muere, el túnel muere
con él, así que ejecuta el `VpnService` como servicio en primer plano y
recrea el dispositivo al reiniciar con el mismo `instanceId` y el mismo
material de claves.

### iOS / macOS

El `NEPacketTunnelProvider` posee el `SdkDeviceLocal` y el flujo de
paquetes, mientras el proceso de la app se acopla a él como dispositivo
remoto por loopback:

```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)
```

La app acuña el material de claves y los PEM del RPC
(`SdkGenerateDeviceRpcKeyMaterial`) y se los entrega a la extensión en la
configuración de proveedor del `NETunnelProviderProtocol`; la extensión lee
`rpc_server_pem`, `rpc_client_pem` y `rpc_listen_hostport` de ahí. No hay
app group en esta vía. Ver el [recorrido](/docs/tour-sdk) para saber por
qué existe la división y cómo funciona la reconexión.

### JavaScript

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

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

El wasm registra sus exports en `window`, así que ejecuta una instancia del
módulo por página. `init` es idempotente dentro de un módulo (una segunda
llamada devuelve la misma instancia), pero dos copias del cargador, digamos
dos bundles o dos frames compartiendo un realm, compiten por los mismos
globales. Inicializa una vez en un singleton a nivel de módulo, nunca
dentro del ciclo de vida de un componente.

Dos modelos de dispositivo:

- `sdk.createProxyDevice(...)` — un cliente ligero de URLs de proxy
  alojadas, para cuando todo lo que necesitas es "una URL de proxy que
  salga por URnetwork".
- `sdk.createPlatformDeviceRemote({...})` — un `DeviceRemote` completo que
  habla device-RPC con un dispositivo alojado por un websocket, para una UI
  de conexión real (ubicaciones, listeners, estadísticas):

```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` lanza una excepción si el wasm cargado es
anterior al binding de `DeviceRemote`; ese es el síntoma de una etiqueta
npm vieja.

### cgo

El contrato de la ABI, en corto:

- Los objetos son **handles `uint64_t` opacos**. `urnet_release(h)` libera
  el handle sin detener el objeto, así que llama antes a su
  `*_close`/`*_stop` donde exista.
- Las cadenas `char*` devueltas pertenecen al llamador; libéralas con
  `urnet_free_string`.
- Los datos estructurados cruzan la frontera como cadenas **JSON UTF-8**;
  los ids son cadenas UUID, los tiempos son milisegundos de época Unix
  (`0` = ninguno).
- Los callbacks se disparan en **hilos arbitrarios gestionados por Go**;
  reencamínalos a tu propio hilo. Sus cadenas y búferes viven solo durante
  la llamada, y los handles que te entregan son tuyos para liberar.
- Las llamadas que pueden fallar toman un `char** out_error`; en caso de
  fallo se rellena con un mensaje que liberas con `urnet_free_string`.
  Pasa `NULL` para ignorar el texto.

La cabecera lleva una división por plataforma: `urnet_new_io_loop`, la
bomba de fd que usa la app de Linux, está dentro de
`#if !defined(_WIN32)` y no aparece en el `.def` de Windows. En Windows
mueves los paquetes con `urnet_device_local_send_packet` y
`urnet_device_local_add_receive_packet`.

Un ejemplo funcional de extremo a extremo vive en `cgo/smoke`;
`make smoke_hpp` en `sdk/cgo` compila el test de humo del envoltorio C++
contra una compilación de host y lo ejecuta.

## Comprueba que funciona

Levanta el dispositivo y comprueba la salida: cualquier comprobación de
"cuál es mi IP" a través del túnel debería informar ahora de la dirección
de un proveedor, no la de la máquina. El mismo estado es visible en código
y en la cuenta: los listeners se disparan según cambia la conexión (el
fragmento de JavaScript de arriba registra cada cambio de ubicación según
llega), y el dispositivo aparece en la lista de dispositivos de la cuenta
bajo el `deviceDescription` que pasaste. En la ABI de C,
`urnet_live_handle_count()` informa de los handles vivos; comprueba en los
tests de fugas que vuelve a su valor de partida.

## Si algo falla

El soporte son los canales abiertos: issues en los repositorios
([github.com/urnetwork](https://github.com/urnetwork)), comentarios de
producto en [feedback.ur.io](https://feedback.ur.io) e informes de
seguridad a security@ur.io (política de divulgación en
[ur.io/vdp](https://ur.io/vdp)). Hoy no hay un nivel de soporte de SDK de
pago. Dos atascos tienen explicación fácil: una transferencia detenida
sobre un túnel que funciona suele ser una cuenta sin datos, y un cargador
de JavaScript sin nada que cargar es la etiqueta npm `latest`. El
[recorrido](/docs/tour-sdk) cubre los modos de fallo que conocer antes de
publicar.

## Qué sigue

- **Haz el [recorrido del SDK](/docs/tour-sdk).** La arquitectura detrás de
  cada binding: divisiones de procesos, hilos, autenticación, reconexión y
  cómo dimensionar el objetivo de memoria.
- **Cuéntales a tus usuarios a qué se unen.** Si tu app enruta tráfico a
  través de URnetwork, la [visión general](/docs/overview) es el relato
  canónico de la ruta y de lo que cada parte puede ver, y el
  [modelo de amenazas](/docs/threat-model) es el registro completo que hay
  detrás.
- **Mira las apps terminadas** para ver la experiencia que tendrán tus
  usuarios: [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) y el
  [navegador](/docs/getting-started-browser).
