# Nyx Wallet — Documentación para IA y desarrolladores

> Este archivo reúne la documentación técnica pública de Nyx Wallet en un único Markdown, organizado para aportar como contexto a una IA, un asistente de código o una revisión técnica.

## Cómo usar este archivo

- Léelo como referencia técnica del producto, no como una promesa de disponibilidad en todas las redes o entornos.
- Conserva los límites y advertencias de seguridad al responder o generar código.
- Para navegación web, cada capítulo incluye su página de origen en la documentación de Nyx.
- La interfaz de Nyx App Concept es un prototipo: no crea wallets ni opera con fondos reales.

## Índice

- 1. El problema que resuelve
- 2. Modelo de custodia
- 3. Modelo de cuenta
- 4. Autorización: una biometría, una operación
- 5. Primeros pasos
- 6. Ciclo de vida de una wallet
- 7. Autenticación y sesiones
- 8. Patrocinio de gas
- 9. Recuperación social (guardianes)
- 10. Onboarding atómico
- 11. Superficie de la API
- 12. Redes
- 13. Fronteras de seguridad
- 14. Checklist de aceptación
- 15. Preguntas frecuentes
- 16. Referencia rápida de la API pública

---

## 1. El problema que resuelve

_Source page: https://nyx-wallet.ledgit.tech/developers/el-problema-que-resuelve_

Integrar activos digitales en una aplicación obliga hoy a elegir entre dos malas opciones.

Si la aplicación guarda las llaves de sus usuarios, se convierte en custodio: asume la responsabilidad regulatoria, concentra el riesgo y se vuelve el objetivo. Si delega la custodia en el usuario con una frase de recuperación de doce palabras, pierde a la mayoría en el primer paso y condena al resto a perder el acceso tarde o temprano.

Nyx elimina la elección con un reparto criptográfico. La llave privada nunca existe completa en ningún servidor, y tampoco depende de que el usuario recuerde nada.

```
┌─────────────────────────────────────────────────────────────┐
│  Navegador del usuario                                      │
│                                                             │
│   • genera la llave (CSPRNG verificado)                     │
│   • la reparte en 3 fragmentos (Shamir, umbral 2)           │
│   • firma cada operación                                    │
│   • reconstruye la llave para exportarla                    │
│                                                             │
│   Fragmento A ──► IndexedDB del dispositivo                 │
│   Fragmento B ──► Nyx (cifrado en reposo)                   │
│   Fragmento C ──► sobre cifrado en el cliente ──► Nyx       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  API de Nyx                                                 │
│                                                             │
│   • custodia UN fragmento, lo entrega solo a su dueño       │
│   • guarda un sobre que no puede abrir                      │
│   • verifica la ceremonia biométrica                        │
│   • retransmite operaciones YA firmadas al bundler          │
│                                                             │
│   Lo que no hace: firmar, reconstruir, exportar llaves      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  Cadena — cuenta inteligente Safe + ERC-4337                │
└─────────────────────────────────────────────────────────────┘
```

**La consecuencia práctica:** dos fragmentos reconstruyen la llave. Nyx tiene uno y un sobre que no puede abrir. Un compromiso total de la infraestructura de Nyx no produce una sola firma.

---

## 2. Modelo de custodia

_Source page: https://nyx-wallet.ledgit.tech/developers/modelo-de-custodia_

La llave privada se reparte con **Shamir Secret Sharing, umbral 2 de 3**. Cada fragmento vive en un dominio de confianza distinto.

| Fragmento | Dónde vive | Quién lo controla | Protección |
|---|---|---|---|
| **A — dispositivo** | IndexedDB del navegador, particionado por usuario | El usuario | Aislamiento por origen. Nunca sale del dispositivo. |
| **B — servidor** | Base de datos de Nyx | Nyx, pero solo se entrega a su dueño autenticado | Cifrado con patrón de sobre: una clave de datos de 256 bits envuelta por una llave maestra en un HSM gestionado (Google Cloud KMS o Azure Key Vault, seleccionable por configuración). |
| **C — recuperación** | Sobre cifrado que almacena Nyx | El usuario | AES-256-GCM con una llave derivada de la passkey del usuario mediante la extensión PRF de WebAuthn. **El cifrado ocurre en el navegador**: Nyx recibe el sobre ya cerrado. |

### Dónde ocurre cada operación

| Operación | Dónde |
|---|---|
| Generación de la llave | Dispositivo |
| Reparto en fragmentos | Dispositivo |
| Firma de transacciones | Dispositivo |
| Exportación de la llave | Dispositivo |
| Custodia del fragmento B | Nyx |
| Verificación biométrica | Nyx |
| Retransmisión al bundler | Nyx |

### Garantías del SDK

- La llave se genera con el CSPRNG de la plataforma. **Si el SDK no puede verificar que la fuente de aleatoriedad es segura, aborta** en lugar de continuar con una llave débil.
- El sobre de recuperación se sella con una copia de la llave de envoltura, nunca con el búfer del llamante, y **se rechazan explícitamente llaves degeneradas**. Un error de integración no puede dejar a Nyx con dos fragmentos.
- Ninguna ruta de la API devuelve la llave privada completa. La ruta autenticada de custodia sí entrega el fragmento B a su dueño. No existe endpoint de exportación: la exportación ocurre entera en el cliente.

### Sin custodia también significa sin rescate

Si un usuario pierde su dispositivo **y** todas sus vías de recuperación, nadie puede devolverle el acceso: ni Nyx, ni la aplicación integradora. Es la contracara necesaria de que nadie pueda robárselo.

Por eso el producto expone el estado de recuperación de cada cuenta como un dato que la aplicación puede leer y mostrar, para insistirle al usuario antes de que importe. Ver [§9](#9-recuperación-social-guardianes).

---

## 3. Modelo de cuenta

_Source page: https://nyx-wallet.ledgit.tech/developers/modelo-de-cuenta_

Cada wallet de Nyx es una **cuenta inteligente Safe con el módulo ERC-4337** (EntryPoint v0.7), no una dirección derivada directamente de una llave.

La dirección se calcula de forma determinista con CREATE2 **antes de que la cuenta exista en la cadena**, a partir del firmante inicial y un `saltNonce`. Puede recibir fondos antes de desplegarse, y el despliegue se materializa con la primera operación.

Esto cambia tres reglas del juego:

**Rotar el firmante no cambia la dirección.** La rotación facilita responder a un compromiso de llave, pero no garantiza evitar pérdidas si un atacante actúa antes. Los tokens registrados a nombre de esa dirección no se enteran.

**El gas lo puede pagar un tercero.** Un paymaster hace posible que el usuario opere sin haber comprado nunca la moneda nativa de la red. Ver [§8](#8-patrocinio-de-gas).

**Varias llamadas caben en una sola operación atómica**, con una única aprobación biométrica. Ver [§10](#10-onboarding-atómico).

### Verificar una dirección antes de confiar en ella

El SDK expone el cálculo para que puedas comprobar cualquier dirección de forma independiente:

```ts
import { computeAccountAddress } from "nyx_wallet";

const address = computeAccountAddress({
  signer: ownerAddress,
  saltNonce: 0,
  deployment,
});
```

> **⚠️ Los siete valores de `deployment` no tienen valor por defecto, a propósito.** Un valor inventado no produce un error: produce una dirección plausible que no controla nadie. Lo que se envíe ahí se pierde. Estos valores los entrega Nyx por red y son verificables en la cadena.

```ts
interface SafeDeployment {
  chainId: number;
  proxyFactory: string;
  singleton: string;
  module: string;
  moduleSetup: string;
  entryPoint: string;
  proxyCreationCode: string;
  onboardingBatchModule?: string; // opcional, ver §10
}
```

---

## 4. Autorización: una biometría, una operación

_Source page: https://nyx-wallet.ledgit.tech/developers/autorizacion-una-biometria-una-operacion_

Toda operación sensible —firmar, mover fondos, rotar el firmante, exportar la llave— exige una **ceremonia WebAuthn fresca**, verificada en el servidor.

El desafío está **atado a la operación concreta**: destinatario, importe, gas, patrocinio y firma entran en la huella que se firma. Una aprobación para un pago de un céntimo no autoriza el que vacía la cuenta. El testigo es de un solo uso.

No existe ningún parámetro para saltarse la biometría, ni para que la aplicación aporte una aserción "ya validada". Si el usuario cancela, si el autenticador no está disponible o si la aserción está incompleta, **el SDK descarta la operación y no llama a la red**.

---

## 5. Primeros pasos

_Source page: https://nyx-wallet.ledgit.tech/developers/primeros-pasos_

### Requisitos

- **Navegador en contexto seguro (HTTPS)** con `crypto.getRandomValues`, IndexedDB y WebAuthn. El SDK es exclusivamente de navegador: nunca se importa desde Node, un route handler, un Server Component ni un job.
- **`ethers` v6.**
- Una **API key de cliente** emitida por Nyx y tu dominio dado de alta como origen permitido.
- Los **valores de `SafeDeployment`** para la red que vayas a usar.
- Se recomienda **PWA instalada** cuando el navegador no garantice persistencia del almacenamiento.

### Instalación

```sh
bun add nyx_wallet ethers@^6 shamir-secret-sharing@^0.0.4
```

`ethers` y `shamir-secret-sharing` son dependencias de pares, para que tu aplicación controle una sola copia de cada una.

### Configuración

```ts
import {
  createDeviceStore,
  createPasskeyRecoveryKeyProvider,
  type NyxWalletConfig,
} from "nyx_wallet";

const config: NyxWalletConfig = {
  apiBaseUrl: "https://<tu-entorno-nyx>/api",

  // JWT de sesión del usuario, emitido por Nyx. Solo en memoria.
  accessToken: session.accessToken,

  // Fragmento A. IndexedDB particionado por usuario.
  deviceStore: createDeviceStore({ userId: session.userId }),

  // Tu bóveda para el sobre de recuperación. Ver el aviso de abajo.
  recoveryVault,

  // De dónde sale la llave que sella el sobre.
  recoveryKeyProvider: createPasskeyRecoveryKeyProvider(),

  // La passkey registrada del usuario: id y clave pública P-256.
  biometricCredential,

  // Solo si el RP de la passkey no es el dominio que sirve tu app.
  biometricRpId: "auth.example.com",
};
```

> **⚠️ `recoveryVault` recibe el fragmento de recuperación sin envolver.** Tu implementación de `write()` debe cifrarlo con la llave que entrega `recoveryKeyProvider` antes de tocar disco. Si ese fragmento llega en claro a cualquier sitio que Nyx pueda leer, Nyx pasa a tener dos fragmentos y el modelo entero se cae. **Nunca uses `localStorage`** para el fragmento del dispositivo ni para el sobre.

**`recoveryKeyProvider` es obligatorio y el SDK no trae uno por defecto.** Es deliberado: una wallet que nace sin sobre de recuperación es una wallet cuyo dueño lo pierde todo el día que se le muera el teléfono. Eso no puede ocurrir por descuido ni por un valor por defecto provisional que alguien olvidó cambiar.

---

## 6. Ciclo de vida de una wallet

_Source page: https://nyx-wallet.ledgit.tech/developers/ciclo-de-vida-de-una-wallet_

### 6.1 Alta

El alta es **una sola llamada atómica**: la wallet, el fragmento del servidor y el sobre de recuperación se registran juntos o no se registra nada.

```ts
import { createWallet } from "nyx_wallet";

const wallet = await createWallet(config, {
  name: "Cuenta principal",
  blockchain: "polygon",
  network: "mainnet",   // por defecto "testnet", a propósito
  deployment,
});

console.log(wallet.walletId, wallet.address);
```

Lo que ocurre por dentro, en este orden: se verifica la fuente de aleatoriedad, se genera la llave, se reparte en tres fragmentos, **se sella el sobre de recuperación**, y solo entonces se registra todo en una única petición.

El orden importa. Si el autenticador no entrega la llave de recuperación —el caso típico es una passkey que vive en otro dispositivo y se usa por el flujo del QR—, la operación **aborta antes de tocar la red y antes de escribir nada en local**. No queda nada que limpiar y no puede nacer una wallet sin recuperación.

**Persiste en tu dominio solo la referencia pública:**

```
walletId · address · saltNonce · chainId · versión de integración
```

Nunca material de llave, fragmentos, aserciones reutilizables ni la llave de recuperación.

El usuario no ve nada de esto. Crear la wallet no exige biometría; solo firmar y exportar la exigen.

### 6.2 Reapertura en el mismo dispositivo

```ts
import { openWallet } from "nyx_wallet";

const wallet = await openWallet(config, walletId, { deployment, saltNonce });
```

El `deployment` debe ser **el mismo** que se usó al crear la wallet.

### 6.3 Mover fondos

```ts
const operation = await wallet.sendFunds({
  to: "0x…",
  value: "1000000000000000",  // wei
  nonce: accountNonce,         // del EntryPoint, ver aviso
});

console.log(operation.userOpHash, operation.sponsored);
```

Aquí es donde el usuario ve Face ID o su huella.

> **⚠️ `sendFunds` mueve el dinero de la cuenta. `signTransaction` no.** `signTransaction` firma una transacción EOA normal con la llave del firmante interno, y esa transacción sale de la dirección **del firmante**, que no es `wallet.address`. Si te mandan fondos a `wallet.address` y tratas de moverlos con `signTransaction`, no se mueven. `signTransaction` y `signMessage` existen para integraciones que necesitan una firma de ese firmante, no para mover fondos.

**El `nonce` no tiene valor por defecto.** El SDK no trae un proveedor con el que consultarlo, y asumir `0` sería correcto exactamente una vez —la primera— y silenciosamente incorrecto siempre después. Consúltalo al EntryPoint con el proveedor de tu aplicación.

### 6.4 Seguir una operación

El navegador **nunca recibe la URL del bundler**. Consulta el recibo a través de Nyx:

```ts
import { getUserOperationReceipt } from "nyx_wallet";

const receipt = await getUserOperationReceipt({
  apiBaseUrl: config.apiBaseUrl,
  accessToken: session.accessToken,
  userOpHash: operation.userOpHash,
});
```

**`null` significa que el bundler todavía no la ha incluido, no que la haya rechazado.**

### 6.5 Recuperación en un dispositivo nuevo

Cuando el almacén del dispositivo está vacío —perdió el teléfono, cambió de navegador, borró datos— no se usa `openWallet`, se usa recuperación:

```ts
import { recoverWalletWithPasskey, createDeviceStore } from "nyx_wallet";

// La recuperación toma su propia configuración, más acotada que la de la wallet:
// no necesita almacén de dispositivo ni credencial biométrica, porque todavía
// no hay wallet abierta en este dispositivo.
const result = await recoverWalletWithPasskey(
  {
    apiBaseUrl: "https://<tu-entorno-nyx>/api",
    accessToken: session.accessToken,
    keyProvider: createPasskeyRecoveryKeyProvider(),
    deployment,          // el MISMO que se usó al crear la wallet
    saltNonce,
  },
  walletId,
);

if (result.status === "needs-guardians") {
  // Estado esperado de producto, no un error: llevar al flujo de guardianes.
  return startGuardianRecovery(walletId, result.detail);
}

// Guarda el fragmento del dispositivo nuevo antes de continuar.
const deviceStore = createDeviceStore({ userId: session.userId });
await deviceStore.write(result.material.walletId, result.material.device);
```

La recuperación reconstruye la llave en el cliente, **genera tres fragmentos nuevos** y actualiza el fragmento de Nyx y el sobre. El juego anterior queda matemáticamente revocado: el fragmento anterior no combina con el nuevo juego. Esto no revoca una llave completa que un atacante ya hubiera reconstruido. **La dirección de la cuenta no cambia.**

`needs-guardians` se devuelve exactamente en dos condiciones: no hay PRF utilizable en esta plataforma, o la passkey de este dispositivo no puede abrir el sobre. Los errores de red, autenticación o integridad **no** se convierten en un falso flujo de recuperación social.

### 6.6 Exportar la llave

```ts
const privateKey = await wallet.exportPrivateKey();
```

Ocurre entera en el dispositivo y exige biometría. No hay ningún endpoint que devuelva esto.

La exportación no es opcional de quitar: es la definición de autocustodia. Lo que sí decide tu aplicación es dónde se muestra y con cuánta fricción alrededor.

### 6.7 Cerrar

```ts
wallet.close();
```

Invalida el manejador y suelta las referencias en memoria al terminar la sesión.

> **Límite honesto del lenguaje:** JavaScript no permite sobrescribir de forma fiable las cadenas que ya creó una librería. `close()` suelta la referencia, pero no puede garantizar que los bytes desaparezcan del heap antes de que pase el recolector de basura. Donde el tipo lo permite, el SDK sí sobrescribe en sitio.

---

## 7. Autenticación y sesiones

_Source page: https://nyx-wallet.ledgit.tech/developers/autenticacion-y-sesiones_

Nyx es multi-cliente. Cada aplicación integradora tiene su propio cliente, su API key, sus métodos de autenticación habilitados y sus orígenes permitidos.

Hay **dos credenciales distintas** y confundirlas es el error de integración más común:

| Credencial | Qué identifica | Dónde va | Qué autoriza |
|---|---|---|---|
| **API key** | La aplicación | Cabecera `x-api-key` | Identificar al cliente durante el login. **No autoriza custodia ni operaciones.** |
| **JWT de sesión** | El usuario | `Authorization: Bearer` | Las rutas de custodia y de cuenta, siempre en nombre de ese usuario. |

El JWT es el `accessToken` que se pasa al SDK. Vive **solo en memoria** durante la sesión: no es una variable de entorno, no se persiste y no se escribe en logs ni en analítica.

**Métodos de autenticación disponibles**, activables por cliente: código de un solo uso por correo, Google OAuth, TOTP con aplicación de autenticación, y passkeys WebAuthn.

El SDK no gestiona el login. Espera un JWT ya emitido por Nyx.

---

## 8. Patrocinio de gas

_Source page: https://nyx-wallet.ledgit.tech/developers/patrocinio-de-gas_

Obligar al usuario a conseguir la moneda nativa de la red antes de poder mover su propio dinero es lo contrario de una wallet invisible. Por eso las tres operaciones de cuenta —`deployAccount`, `sendFunds` y `rotateSigner`— **piden patrocinio por defecto**.

La cotización se solicita **antes de firmar**, porque los datos del paymaster forman parte de los bytes firmados.

**Denegar nunca rompe la operación.** Si Nyx no patrocina —por política, por tope agotado o por indisponibilidad— la operación puede continuar pagando su propio gas si la cuenta dispone del saldo y de la infraestructura necesarios. Quien llama no tiene que ramificar ni capturar nada:

```ts
const op = await wallet.sendFunds({ to, value, nonce });

op.sponsored           // boolean: quién pagó
op.sponsorshipReason   // presente solo si sponsored es false, en lenguaje de usuario
```

Para desactivar la consulta en una llamada concreta:

```ts
await wallet.sendFunds({ to, value, nonce, sponsorship: false });
```

El patrocinio es un control del servidor, no un ajuste del cliente: solo se patrocinan cuentas que Nyx conoce, hay un tope, y la decisión se toma fuera del navegador. El patrocinio evita que el usuario tenga que adquirir gas cuando está disponible; no sustituye a un bundler configurado.

---

## 9. Recuperación social (guardianes)

_Source page: https://nyx-wallet.ledgit.tech/developers/recuperacion-social-guardianes_

Cuando la passkey no viaja con el usuario, entra la red de guardianes.

**Un guardián no tiene ningún fragmento de la llave.** No firma, no ve saldos, no mueve fondos. Lo único que puede hacer es **votar**, junto con otros, para nombrar un firmante nuevo en la cuenta inteligente. Es un mecanismo de recuperación, no de acceso.

### Cómo está diseñado el quórum

El roster por defecto es **Nyx + la passkey del usuario + una segunda credencial del usuario**: dos de los tres asientos son del propio usuario.

**El umbral se deriva, no es una constante:**

```
umbral = (guardianes que no son del usuario) + 1
```

Con el roster por defecto sale 2. Si una aplicación integradora entra como guardián, el umbral sube a 3, de modo que la coalición {Nyx, aplicación} **sigue sin alcanzarlo**. Un asiento de integrador solo entra en un roster si el usuario lo autoriza con una firma de su segunda credencial; el servidor lo rechaza si no.

Nyx puede ocupar **como máximo un asiento** por roster, y cada voto lleva una firma verificable contra la dirección que ese guardián registró: **Nyx no puede fabricar el voto de nadie**.

### Protecciones del proceso

- Hay un **plazo de espera** antes de que una recuperación sea efectiva, aunque el umbral ya esté cubierto.
- El **firmante actual puede vetarla** mientras conserve acceso.
- Los cambios de roster están atados al dueño registrado de la cuenta.

### Estado de recuperación

La aplicación puede leer y mostrar en qué situación está el usuario:

| Estado | Significa |
|---|---|
| `NO_GUARDIANS` | No existe ninguna vía de recuperación social para esta cuenta. |
| `NO_SECOND_CREDENTIAL` | Hay guardianes, pero el usuario solo aporta una credencial: **no hay red de recuperación**. |
| `CONFIGURED` | El usuario aporta dos credenciales propias: la red existe. |

Este estado existe porque el diseño tiene una consecuencia incómoda que no puede pasar en silencio: un usuario que no configura su segunda credencial se queda sin red. Es más honesto que la alternativa —una red que en realidad controlan Nyx y la aplicación entre las dos— pero el usuario tiene que enterarse cuando decide no configurarla, no en unos términos que nadie lee.

---

## 10. Onboarding atómico

_Source page: https://nyx-wallet.ledgit.tech/developers/onboarding-atomico_

`executeOnboardingBatch` ejecuta dos o más llamadas como **una sola UserOperation**, con **exactamente una ceremonia biométrica** sobre la operación final.

El caso de uso típico: desplegar la cuenta, aprobar un importe exacto y ejecutar la compra, sin pedirle al usuario tres aprobaciones seguidas para algo que él percibe como una sola acción.

```ts
const result = await wallet.executeOnboardingBatch({
  intentId: crypto.randomUUID(),   // un solo uso, emitido por tu backend
  chainId: deployment.chainId,
  sender: wallet.address,
  nonce: accountNonce,
  calls: [
    { to: token, value: "0", data: approveExactAmount },
    { to: router, value: "0", data: executeQuotedAction },
  ],
  expiresAt: Math.floor(Date.now() / 1000) + 300,
  deployAccount: false,            // true solo para la primera operación
});
```

**El módulo revierte el batch entero si una llamada falla.** No existe un estado donde el `approve` tuvo éxito y la acción siguiente no.

El método **no acepta** `skipBiometric`, ni un testigo, ni una aserción aportada por la aplicación.

**Es una capacidad opt-in y no modifica cuentas existentes.** Requiere que el `deployment` usado **al crear la wallet** incluyera la dirección del módulo de batch; si falta, la llamada aborta antes de abrir WebAuthn. Habilitar un módulo Safe privilegiado es una decisión de seguridad que exige auditoría independiente, dirección verificada por red y aprobación explícita: Nyx no proporciona una dirección por defecto ni lo activa al instalar el SDK.

### Validación en el backend del integrador

El SDK publica un validador puro, **sin ninguna API de firma**, para que tu backend contraste lo que recibe contra el intent que emitió:

```ts
import {
  assertUserOperationMatchesOnboardingIntent,
  hashOnboardingIntent,
} from "nyx_wallet/onboarding";

// Al emitir: persiste el intent canónico, su hash, usuario, sesión y wallet, con TTL corto.
const intentHash = hashOnboardingIntent(intent);

// Antes de retransmitir: compara igualdad, no reinterpretes la calldata.
assertUserOperationMatchesOnboardingIntent({ intent, userOperation, deployment });
```

El `deployment` lo carga tu backend desde su propio registro por `chainId`. **No se acepta desde el navegador**: el validador demuestra correspondencia dentro de una configuración de confianza, no convierte una configuración manipulada en una garantía.

El consumo del intent debe ser atómico: `issued → submitted` una sola vez, guardando el `userOpHash`. Un reintento del mismo intent devuelve ese mismo hash; nunca se firma ni se retransmite una segunda operación.

---

## 11. Superficie de la API

_Source page: https://nyx-wallet.ledgit.tech/developers/superficie-de-la-api_

Todo cuelga del prefijo `/api`. La `apiBaseUrl` del SDK termina en `/api`, no en la raíz.

### Autenticación — `x-api-key`

| Método | Ruta | Para qué |
|---|---|---|
| `POST` | `/auth/send-otp` | Envía un código de un solo uso al correo del usuario |
| `POST` | `/auth/login` | Inicia sesión con correo y código; devuelve el JWT |
| `POST` | `/auth/totp-login` | Inicia sesión con un código TOTP |
| `GET` | `/auth/google` | Arranca el flujo de Google OAuth |
| `POST` | `/auth/refresh-token` | Renueva el JWT |
| `GET` | `/auth/check-auth-status` | Consulta el estado de autenticación |

### Passkeys — WebAuthn

| Método | Ruta | Para qué |
|---|---|---|
| `POST` | `/webauthn/register/options` | Opciones de registro de una passkey |
| `POST` | `/webauthn/register/verify` | Verifica y persiste el registro |
| `POST` | `/webauthn/authenticate/options` | Opciones de autenticación |
| `POST` | `/webauthn/authenticate/verify` | Verifica la aserción |

### Custodia — JWT del usuario

| Método | Ruta | Para qué |
|---|---|---|
| `POST` | `/custody/wallets` | Registra una wallet generada en el cliente, con su fragmento de servidor y su sobre. **Atómico.** |
| `GET` | `/custody/wallets/{walletId}/share` | Entrega al dueño el fragmento que custodia el servidor |
| `GET` | `/custody/wallets/{walletId}/recovery-envelope` | Entrega al dueño su sobre de recuperación |
| `POST` | `/custody/wallets/{walletId}/recovery-envelope` | **Rota** el sobre de una wallet que ya tiene uno |
| `POST` | `/custody/wallets/{walletId}/reshare` | Reemplaza atómicamente fragmento y sobre tras una recuperación |

El cuerpo del alta muestra el modelo entero de un vistazo:

```jsonc
POST /api/custody/wallets
{
  "name": "Cuenta principal",
  "blockchainType": "polygon",
  "networkType": "testnet",
  "address": "0x…",
  "serverShare": "<base64 del fragmento B>",
  "envelopeCiphertext": "<base64 del sobre, cifrado en el navegador>",
  "envelopeFormat": "webauthn-prf-v1"
}
```

Eso es todo lo que Nyx llega a ver de una llave: **un fragmento y un sobre cerrado.**

La ruta de rotación del sobre responde **409** sobre una wallet que todavía no tiene ninguno. No sirve para darle el primero: eso es trabajo del alta, y es deliberado. Que el servidor aceptara registrar una wallet sin sobre *era* el fallo.

### Cuenta inteligente — JWT del usuario

| Método | Ruta | Para qué |
|---|---|---|
| `POST` | `/account/address` | Calcula la dirección de una cuenta antes de desplegarla |
| `POST` | `/account/operations/sponsorship` | Pide que Nyx patrocine el gas, antes de firmar |
| `POST` | `/account/operations` | Retransmite al bundler una operación **ya firmada** |
| `GET` | `/account/operations/{userOpHash}/receipt` | Consulta el recibo sin exponer el bundler |

`POST /account/operations` **rechaza cualquier operación sin firma**. No es validación cosmética: deja escrito en el código que por ahí no pasa nada que Nyx pudiera haber autorizado.

### Guardianes

| Método | Ruta | Para qué |
|---|---|---|
| `POST` | `/account/guardians` | Fija o reemplaza el roster |
| `GET` | `/account/guardians` | Lee el roster |
| `POST` | `/account/guardians/recovery` | Un guardián propone un firmante nuevo |
| `GET` | `/account/guardians/recovery/{recoveryId}` | Estado: votos, plazo, veto |
| `POST` | `/account/guardians/recovery/{recoveryId}/vote` | Un guardián se suma |
| `POST` | `/account/guardians/recovery/{recoveryId}/veto` | El firmante actual cancela |

### Salud

| Método | Ruta | Para qué |
|---|---|---|
| `GET` | `/health` | Vivacidad del proceso |
| `GET` | `/health/readiness` | **Si esta instancia puede operar de verdad** |

`readiness` comprueba las dependencias reales del camino de escritura: base de datos, caché, cliente de cifrado, configuración de la cuenta inteligente, nodo y bundler. **No responde "listo" si el camino de escritura más importante está muerto.** Compruébalo desde tu entorno antes de crear la primera wallet.

### Lo que la API no tiene, a propósito

- Ninguna ruta devuelve una llave privada ni una frase de recuperación.
- Ninguna ruta firma en nombre del usuario.
- El bundler nunca se expone al navegador.

---

## 12. Redes

_Source page: https://nyx-wallet.ledgit.tech/developers/redes_

El SDK y la API son agnósticos de la cadena mientras sea EVM y exista un despliegue de los contratos Safe y del EntryPoint v0.7.

| Red | Chain ID |
|---|---|
| Polygon Amoy (pruebas) | 80002 |
| Polygon | 137 |
| Ethereum Sepolia (pruebas) | 11155111 |
| Ethereum | 1 |

Las direcciones de contratos son públicas y verificables en la cadena, pero **las entrega Nyx por red**: no hay valor por defecto, y uno inventado produce una dirección que no controla nadie. **Confirma con Nyx qué redes están habilitadas para tu cliente** antes de diseñar sobre una en concreto; la habilitación de una red incluye bundler, patrocinio y despliegue verificado, no solo un identificador de cadena.

---

## 13. Fronteras de seguridad

_Source page: https://nyx-wallet.ledgit.tech/developers/fronteras-de-seguridad_

Un modelo de seguridad solo es creíble si dice también lo que no cubre.

### Lo que garantiza el SDK

- La llave se genera con el CSPRNG de la plataforma, o el SDK aborta.
- Nyx recibe un solo fragmento. Firma y exportación ocurren en el cliente.
- El sobre de recuperación se cifra en el cliente; Nyx nunca recibe el fragmento de recuperación en claro.
- Las operaciones protegidas validan la credencial WebAuthn configurada, no un simple indicador de biometría.
- Un campo de paymaster suelto, fuera del flujo de patrocinio, **aborta**: ese campo entra en la firma, e ignorarlo produciría una firma válida sobre una operación distinta de la que se manda.

### Lo que le toca a tu aplicación

- **Un XSS en tu aplicación puede usar una wallet abierta.** El SDK no sustituye una política de seguridad del frontend: CSP, aislamiento de origen, revisión de dependencias.
- **Cifrar el sobre de recuperación** antes de persistirlo, y no usar `localStorage` para material sensible.
- **No persistir el JWT** de Nyx ni escribirlo en logs, analítica o trazas.
- **Decidir qué se firma.** Nyx ejecuta lo que el usuario aprueba; la política de qué se le propone firmar vive en tu producto.

### Límites conocidos

- **La disponibilidad de WebAuthn PRF depende del autenticador, del navegador y de la plataforma.** Pruébalo en los dispositivos que vayas a soportar antes de ofrecer la recuperación por passkey como única vía. Por eso existe la recuperación social como segunda ruta.
- **JavaScript no permite borrar de forma garantizada toda la memoria.** Ver [§6.7](#67-cerrar).
- **Si el usuario pierde su dispositivo y todas sus vías de recuperación, nadie puede devolverle el acceso.** Es una propiedad del modelo, no un defecto.

---

## 14. Checklist de aceptación

_Source page: https://nyx-wallet.ledgit.tech/developers/checklist-de-aceptacion_

No des una integración por terminada hasta que se cumpla todo esto:

- [ ] Instalación limpia, typecheck y build con la versión vigente del SDK y `ethers` v6.
- [ ] `GET /api/health/readiness` responde `READY` desde tu entorno.
- [ ] Alta de wallet, reapertura en el mismo dispositivo y **recuperación en otro distinto**.
- [ ] La operación se **rechaza** si el usuario cancela la ceremonia biométrica.
- [ ] Una transferencia y una llamada a contrato en la red acordada, seguidas por `userOpHash` hasta el recibo de inclusión.
- [ ] Una operación con patrocinio concedido, o evidencia explícita de una denegación segura.
- [ ] **Ninguna llave privada completa ni frase de recuperación** en peticiones, logs o telemetría. El transporte autorizado del fragmento B usa HTTPS; los fragmentos no deben filtrarse a logs o analítica.
- [ ] El estado de recuperación del usuario se muestra en la interfaz y se le insiste si no está configurado.

---

## 15. Preguntas frecuentes

_Source page: https://nyx-wallet.ledgit.tech/developers/preguntas-frecuentes_

**¿El SDK puede correr en el servidor?**
No. Es exclusivamente de navegador y requiere contexto seguro, WebAuthn e IndexedDB. Un SDK de wallet en el servidor sería un servidor capaz de firmar, que es justo lo que este diseño existe para impedir.

**¿Puedo usar mi propio sistema de autenticación?**
El SDK necesita un JWT de sesión emitido por Nyx. Nyx ofrece varios métodos de login y cada cliente activa los que necesita. Reutilizar la sesión de tu aplicación requiere un intercambio de token explícito; un JWT de otro emisor no sirve por parecido de nombre.

**¿Puedo importar una wallet existente?**
No, y es deliberado. Importar una llave privada exigiría que alguien la tuviera completa. Los activos de una wallet anterior se mueven con una transacción firmada por su dueño mientras todavía controla esa llave.

**¿Qué pasa si Nyx desaparece?**
El usuario conserva su fragmento en el dispositivo. La exportación debe prepararse mientras conserve acceso al material y a los servicios requeridos por el flujo; no se promete exportación offline si Nyx deja de operar. La cuenta es un contrato Safe estándar que cualquier herramienta del ecosistema entiende.

**¿El código del SDK es público?**
Está publicado en npm y es inspeccionable. Corre en el navegador del usuario, así que su código es legible por naturaleza: **la seguridad de Nyx no depende de que sea secreto**. Que sea auditable juega a favor de una wallet. La licencia es propietaria; publicarlo responde a una necesidad técnica de distribución, no concede derechos de redistribución ni de uso fuera de los términos acordados.

**¿Nyx puede mover los fondos de un usuario?**
No. Guarda un fragmento de tres y un sobre que no puede abrir. Con eso no se reconstruye una llave ni se produce una firma.

---

## 16. Referencia rápida de la API pública

_Source page: https://nyx-wallet.ledgit.tech/developers/referencia-rapida-de-la-api-publica_

| API | Uso |
|---|---|
| `createWallet(config, options)` | Crea la cuenta, registra el fragmento de Nyx y sella el sobre, en una sola llamada |
| `openWallet(config, walletId, options)` | Abre una wallet con el material disponible en este dispositivo |
| `recoverWallet(config, walletId)` | Recupera desde el sobre y re-reparte los fragmentos. Toma su propia configuración, más acotada — ver [§6.5](#65-recuperación-en-un-dispositivo-nuevo) |
| `recoverWalletWithPasskey(config, walletId)` | Variante que expresa la falta de PRF como `needs-guardians` |
| `createDeviceStore({ userId })` | Almacén IndexedDB para el fragmento del dispositivo |
| `createPasskeyRecoveryKeyProvider()` | Proveedor de llave de recuperación sobre WebAuthn PRF |
| `computeAccountAddress(identity)` | Calcula y permite verificar la dirección CREATE2 |
| `getUserOperationReceipt(query)` | Recibo de una operación sin exponer el bundler |
| `wallet.sendFunds(transfer)` | **Mueve fondos desde la cuenta** |
| `wallet.deployAccount(options?)` | Materializa la cuenta en la cadena |
| `wallet.rotateSigner(rotation)` | Cambia el firmante sin cambiar la dirección |
| `wallet.executeOnboardingBatch(intent)` | Batch atómico con una sola biometría |
| `wallet.exportPrivateKey()` | Exportación soberana, en el dispositivo |
| `wallet.signTransaction(tx)` / `signMessage(msg)` | Firma **del firmante interno** — no mueve fondos de la cuenta |
| `wallet.close()` | Invalida el manejador |
| `nyx_wallet/onboarding` | Canonicaliza y contrasta intents en el backend, sin API de firma |

---

## Referencia del producto

- Documentación web: https://nyx-wallet.ledgit.tech/developers
- Sitio de producto: https://nyx-wallet.ledgit.tech/
- Desarrollada por: Ledgit (https://ledgit.tech/) y AOS Factory (https://aosfactory.com/)
