npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nyx_wallet

v0.5.0

Published

SDK de navegador de Nyx Wallet v3: genera la llave en el dispositivo, la reparte 2-de-3 y firma en el cliente

Readme

Nyx Wallet SDK

SDK ESM para integrar Nyx Wallet en una aplicación web o PWA. Crea y opera cuentas inteligentes ERC-4337; la clave se genera en el navegador, se divide en tres fragmentos y Nyx solo custodia uno de ellos. Las operaciones sensibles requieren una ceremonia WebAuthn.

[email protected] se publicó con un defecto de empaquetado: no contiene el SDK compilado. No debe utilizarse. La línea 0.1.x conserva compatibilidad con ethers v5; para una app con ethers v6 instala [email protected] o posterior dentro de 0.2.x.

Instalación

El paquete publicado es nyx_wallet en npmjs.org. No requiere .npmrc, GitHub Packages ni un token de GitHub para instalarlo. La licencia es propietaria: disponibilidad en npm no concede una licencia de redistribución, modificación o uso fuera de los términos acordados con Ledgit.

bun add [email protected] ethers@^6.0.0 shamir-secret-sharing@^0.0.4

ethers debe ser v6. ethers y shamir-secret-sharing son dependencias de pares para que la aplicación controle una sola copia de cada una. No fuerces la línea 0.1.x sobre una app v6 con overrides, ni rebajes toda la app a v5. 0.4.0 todavía no está publicada en npm (latest es 0.3.0); ver el estado y por qué las versiones publicadas no sirven para crear wallets en ../docs/README-DOCS.md.

Este SDK es solo de navegador. Se importa desde el código cliente de la PWA; nunca desde Adonis, un route handler, un Server Component, un job ni otro proceso Node. La API key identifica al cliente durante login y el JWT de Nyx es por usuario y sesión: ninguno es un secreto global de backend ni permite firmar fuera del dispositivo.

Requisitos de la integración

Antes de crear una wallet, la aplicación debe disponer de lo siguiente:

  • Un navegador en contexto seguro (https) con crypto.getRandomValues, IndexedDB y WebAuthn.
  • Una sesión autenticada contra la API de Nyx.
  • El descriptor real de los contratos Safe/EntryPoint desplegados en la red objetivo.
  • Una credencial WebAuthn registrada para el usuario: su identificador y clave publica P-256 en JWK. El SDK la verifica en cada operación protegida.
  • Un proveedor de clave de recuperación. createPasskeyRecoveryKeyProvider() usa WebAuthn PRF; si la plataforma no lo soporta, la aplicación debe llevar al usuario por su flujo de guardianes.
  • Un almacén local para el fragmento del dispositivo y una bóveda de recuperación que proteja el material antes de persistirlo. Nunca use localStorage para ninguno de los dos.

Los valores del despliegue no son valores de ejemplo: deben venir de la configuración verificada de cada red. Inventarlos puede producir una dirección válida en apariencia, pero que nadie controla.

Crear una wallet

import {
  createDeviceStore,
  createPasskeyRecoveryKeyProvider,
  createWallet,
  type BiometricCredential,
  type RecoveryVault,
  type SafeDeployment,
} from "nyx_wallet";

const deployment: SafeDeployment = {
  chainId: 80002,
  proxyFactory: contracts.safeProxyFactory,
  singleton: contracts.safeSingleton,
  module: contracts.safe4337Module,
  moduleSetup: contracts.safeModuleSetup,
  entryPoint: contracts.entryPointV07,
  proxyCreationCode: contracts.safeProxyCreationCode,
  // Opcional: solo para wallets NUEVAS tras auditar y desplegar NyxOnboardingBatchModule.
  // onboardingBatchModule: contracts.nyxOnboardingBatchModule,
};

// IndexedDB, particionado por usuario. El SDK proporciona este almacén para el fragmento local.
const deviceStore = createDeviceStore({ userId: session.userId });

// Implementación de la aplicación: cifra el valor antes de persistirlo y ofrece read/write/wipe.
// No debe enviar nunca el valor sin cifrar a Nyx ni a un servicio de analítica.
const recoveryVault: RecoveryVault = createEncryptedRecoveryVault();

// Resultado del registro WebAuthn de este usuario, recuperado de la fuente de confianza.
const biometricCredential: BiometricCredential = await api.getWalletCredential();

const wallet = await createWallet(
  {
    apiBaseUrl: "https://api.example.com",
    accessToken: session.accessToken,
    deviceStore,
    recoveryVault,
    recoveryKeyProvider: createPasskeyRecoveryKeyProvider(),
    biometricCredential,
    // Indicarlo cuando el RP de la credencial no sea el dominio que sirve la aplicación.
    biometricRpId: "auth.example.com",
  },
  {
    name: "Cuenta principal",
    blockchain: "polygon",
    network: "testnet",
    deployment,
  },
);

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

createWallet crea una cuenta inteligente; wallet.address es la dirección de esa cuenta, no la dirección del firmante interno. La cuenta se registra junto con un único fragmento para Nyx y un sobre de recuperación cifrado en el cliente.

La dirección del firmante es wallet.signerAddress (EIP-55), disponible desde 0.5.0 en los manejadores que devuelven createWallet y openWallet. Es el valor que espera signer en POST /api/account/address y ownerSigner en el roster de guardianes. Leerla no pide biometría ni va a la red: el manejador ya la tiene calculada. No se debe obtener firmando un mensaje y recuperando el firmante de la firma —verifyMessage(m, await wallet.signMessage(m))— ni exportando la clave: las dos rutas cuestan una ceremonia para llegar a un dato público que signerAddress entrega directamente. recoverWallet devuelve el mismo dato en material.signerAddress, para registrar al dueño desde el dispositivo nuevo.

Operar una cuenta

Cada operación sensible solicita verificación biométrica. Las operaciones de cuenta se envían a la API de Nyx, que las retransmite al bundler; la firma se produce en el dispositivo.

await wallet.deployAccount();

const operation = await wallet.sendFunds({
  to: "0x...",
  value: "1000000000000000", // wei
  nonce: accountNonce,        // consultar antes al EntryPoint o al servicio de cuentas
});

console.log(operation.userOpHash);

Para seguir esa operación, el navegador no recibe SMART_ACCOUNT_BUNDLER_URL. Consulta Nyx con el JWT de la sesión mediante el helper; null significa que el bundler todavía no la ha incluido, no que haya sido rechazada:

import { getUserOperationReceipt } from "nyx_wallet";

const receipt = await getUserOperationReceipt({
  apiBaseUrl: "https://api.example.com",
  accessToken: session.accessToken,
  userOpHash: operation.userOpHash,
});

Nyx hace eth_getUserOperationReceipt en el servidor. La URL y cualquier credencial del bundler permanecen fuera del frontend y no deben declararse como variables públicas de la aplicación.

Por defecto, las tres operaciones de cuenta (deployAccount, sendFunds y rotateSigner) piden antes a Nyx el patrocinio de gas. La cotización se obtiene antes de firmar porque el paymaster forma parte de los bytes firmados. Si Nyx la deniega, alcanza su límite o no está disponible, la operación sigue enviándose sin patrocinio; el resultado lo declara en operation.sponsored y, si procede, operation.sponsorshipReason. Para no consultar el patrocinio en una llamada concreta, pasa sponsorship: false:

const operation = await wallet.sendFunds({
  to: "0x...",
  value: "1000000000000000",
  nonce: accountNonce,
  sponsorship: false,
});

El patrocinio evita que el usuario tenga que adquirir gas cuando está disponible; no sustituye un bundler configurado ni garantiza que Nyx cubra cada operación.

Onboarding atómico con una biometría

wallet.executeOnboardingBatch(intent) ejecuta dos o más llamadas como una sola UserOperation Safe/ERC-4337. El SDK pide exactamente una ceremonia WebAuthn fresca sobre la UserOperation final —incluye llamadas, valores, gas, paymaster, factory y firma— y no acepta skipBiometric, un testigo ni una aserción aportada por la aplicación.

La firma Safe comienza con validAfter (6 bytes) || validUntil (6 bytes) || ownerSignature. Para este método, validAfter es 0 y validUntil es exactamente intent.expiresAt; el BFF debe rechazar cualquier otra ventana antes del relay.

La capacidad es opt-in y no modifica Safes existentes. Requiere que el descriptor usado al crear la wallet incluya la dirección verificable y auditada de NyxOnboardingBatchModule; ese módulo queda habilitado en el initializer de la Safe. Si falta, executeOnboardingBatch aborta antes de abrir WebAuthn. Nyx no proporciona una dirección por defecto ni activa/despliega ese módulo al instalar el SDK.

const intent = {
  // Id aleatorio de un solo uso emitido por el backend/BFF del integrador.
  intentId: crypto.randomUUID(),
  chainId: deployment.chainId,
  sender: wallet.address,
  nonce: accountNonce,
  // Cada llamada queda ordenada y dentro de la calldata autenticada.
  calls: [
    { to: approvedToken, value: "0", data: approveExactAmount },
    { to: protocolRouter, value: "0", data: executeQuotedAction },
  ],
  // Obligatorio: también se firma como `validUntil` de la SafeOp.
  expiresAt: Math.floor(Date.now() / 1000) + 300,
  // `true` solo para la primera UserOperation counterfactual (nonce 0).
  deployAccount: false,
};

const result = await wallet.executeOnboardingBatch(intent);
console.log(result.userOpHash, result.sponsored);

El módulo ejecuta las llamadas desde la Safe mediante permisos de módulo y revierte el batch entero si una falla: no existe un estado approve exitoso con la acción siguiente fallida. Es un módulo Safe privilegiado; su despliegue, auditoría, dirección por red y activación son una decisión explícita de seguridad, no una variable que pueda inventar el frontend.

Demo experimental QA en Polygon Amoy

El repositorio incluye NyxOnboardingBatchModule y una utilidad de despliegue solo para la demo QA de Amoy (chainId 80002). El flujo se documenta en docs/DEPLOY-NYX-ONBOARDING-BATCH-AMOY.md: compila el fuente con solc 0.8.23-fixed, comprueba la red y queda en dry-run si no se confirman explícitamente --broadcast --confirm-amoy-80002.

Las variables NYX_QA_AMOY_DEPLOYER_PRIVATE_KEY (local) y los secrets del environment de GitHub llamado qa-batch-deploy (AMOY_DEPLOYER_PRIVATE_KEY/AMOY_RPC_URL) pertenecen solo al operador de despliegue QA. Ese environment aún requiere que un administrador configure revisores obligatorios y política de ramas en GitHub Settings antes de cualquier uso tipo producción. No son configuración de una aplicación integradora, no van en el frontend, no se publican en npm y no se reutilizan en producción. Tras el despliegue, un operador debe registrar la dirección en la configuración QA de SafeDeployment.onboardingBatchModule antes de crear nuevas Safes.

Esta entrega contiene el SDK y el módulo para QA; no contiene ni activa el BFF de Indahouse. El BFF sigue siendo un componente separado que emite/persiste intents y valida la UserOperation antes de relay como se describe abajo. No se debe anunciar la capacidad como lista para producción hasta completar auditoría independiente, configuración por red y aprobación de release.

Addendum técnico: orden real de firma y biometría

La implementación actual construye y firma primero la UserOperation en memoria y pide la única ceremonia WebAuthn inmediatamente después. Es un orden intencional y distinto de una formulación estricta de “biometría antes de reconstruir material”: el desafío WebAuthn del relay es la huella de la UserOperation final, incluida su firma, factory, gas y paymaster. Pedir la aserción antes impediría que cubriera esos bytes exactos; pedir una segunda ceremonia para la firma no es aceptable.

Esto no convierte la firma previa en autorización remota: no sale una UserOperation firmada del dispositivo hasta que la aserción WebAuthn fresca se obtiene y se canjea. Si WebAuthn no existe, el usuario cancela o falta la aserción completa, el SDK descarta la operación y no llama al relay. La cotización de patrocinio que puede ocurrir antes contiene sólo el borrador sin firma. Si un despliegue exige que la biometría impida incluso reconstruir material, requiere un rediseño del protocolo; esta versión no afirma satisfacer esa variante más fuerte.

Contrato para un BFF de integrador

El navegador puede enviar la UserOperation ya firmada junto con intentId; el BFF nunca firma ni recibe material de clave. Al emitir el intent, el BFF debe persistir su forma canónica y hashOnboardingIntent(intent), asociadas a usuario, sesión y wallet, con TTL corto (normalmente 2–5 minutos). Antes de relay debe comparar el objeto recibido con ese registro mediante el validador puro assertUserOperationMatchesOnboardingIntent:

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

// Al emitir: guardar intent, hash, usuario/sesión y estado `issued` con TTL.
const intentHash = hashOnboardingIntent(intent);

// Antes de relay: recuperar por intentId y comprobar igualdad, no reinterpretar calldata.
assertUserOperationMatchesOnboardingIntent({ intent, userOperation, deployment });

deployment no puede venir de la petición del navegador: el BFF lo carga de su propio registro por chainId, con factory, singleton, bytecode y módulos previamente verificados on-chain. El validador demuestra la correspondencia dentro de esa configuración confiable; no puede convertir una configuración manipulada o un contrato no auditado en una garantía de seguridad.

Para deployAccount: true, el validador no acepta un factoryData meramente no vacío: decodifica SafeProxyFactory.createProxyWithNonce, exige el singleton y el initializer Safe configurados (incluidos Safe4337Module y el módulo batch), recalcula CREATE2 y exige que derive exactamente el sender del intent. Eso enlaza el despliegue counterfactual a la Safe prevista; no reemplaza la verificación de la firma, que siguen haciendo Safe y el EntryPoint.

El consumo debe ser atómico: issued -> submitted una sola vez y guardar el userOpHash. Un reintento del mismo intent devuelve ese mismo hash; después del recibo pasa a completed o failed, y al vencer sin enviar a expired. No se implementa ningún BFF en este paquete. El alta inicial de WebAuthn conserva su propio gesto de navegador; la única biometría de este método cubre solo las llamadas on-chain del batch.

sendFunds mueve fondos desde wallet.address. No se debe usar signTransaction para ese fin: firma una transacción EOA desde la dirección del firmante interno —wallet.signerAddress— que no es la cuenta inteligente. signTransaction y signMessage existen para integraciones que necesitan una firma de ese firmante.

También están disponibles:

  • wallet.rotateSigner({ newSigner, nonce }) para cambiar el firmante sin cambiar la dirección de la cuenta.
  • wallet.exportPrivateKey() para la exportación soberana. No debe exponerse como acción por defecto en la interfaz.
  • wallet.close() para invalidar el manejador y soltar sus referencias en memoria al terminar la sesión. walletId, address y signerAddress siguen legibles después: son datos públicos de la wallet, no material.

Abrir y recuperar una wallet

En el mismo dispositivo, se abre una wallet existente proporcionando el mismo despliegue usado al crearla:

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

En un dispositivo nuevo, se debe usar la recuperación, no openWallet. recoverWallet y recoverWalletWithPasskey reconstruyen la clave en el cliente, generan tres fragmentos nuevos y actualizan el fragmento de Nyx y el sobre cifrado. El fragmento del dispositivo perdido queda inutilizable. La aplicación debe guardar el campo device que devuelve la recuperación en su DeviceStore antes de continuar.

recoverWalletWithPasskey devuelve needs-guardians cuando WebAuthn PRF no está disponible o el passkey de este dispositivo no puede abrir el sobre. Ese resultado es un estado esperado de producto y debe llevar al flujo de guardianes; los errores de red, autenticación o integridad no se convierten en un falso flujo de recuperación social.

Modelo de seguridad y límites

El SDK impone estas propiedades:

  • La clave se genera con el CSPRNG de la plataforma; si no puede comprobarlo, aborta.
  • Nyx recibe un solo fragmento. Las firmas y la exportación de clave se realizan en el cliente.
  • Un sobre de recuperación se cifra en el cliente con la clave aportada por RecoveryKeyProvider; Nyx no recibe el fragmento de recuperación en claro.
  • Las operaciones protegidas validan la credencial WebAuthn configurada, no solo un indicador de biometría.

También hay límites que la integración debe asumir:

  • Un XSS en la aplicación puede usar una wallet abierta. El SDK no sustituye CSP, aislamiento de origen, revisión de dependencias ni una política de seguridad del frontend.
  • JavaScript no permite borrar de forma fiable las cadenas que haya creado una librería. close() invalida el manejador y suelta referencias, pero no garantiza la sobrescritura de todo el heap.
  • La disponibilidad de WebAuthn PRF depende del autenticador, navegador y plataforma. Debe probarse en los dispositivos que vaya a soportar el producto antes de ofrecer la recuperación por passkey como única vía.

API pública principal

| API | Uso | | --- | --- | | createWallet(config, options) | Crea la cuenta, registra el fragmento de Nyx y sella el sobre de recuperación. | | openWallet(config, walletId, options) | Abre una wallet desde material disponible en el dispositivo actual. | | recoverWallet(config, walletId) | Recupera desde el sobre, redivide los fragmentos y revoca matemáticamente el juego anterior. | | recoverWalletWithPasskey(config, walletId) | Variante que expresa la falta de portabilidad de PRF como needs-guardians. | | createDeviceStore({ userId }) | Crea el almacén IndexedDB para el fragmento del dispositivo. | | createPasskeyRecoveryKeyProvider() | Crea el proveedor de clave de recuperación basado en WebAuthn PRF. | | computeAccountAddress(identity) | Calcula y permite verificar la dirección CREATE2 de una cuenta. | | wallet.signerAddress | Dirección EIP-55 del firmante (EOA) de la cuenta. Sin biometría ni red. Desde 0.5.0. | | wallet.executeOnboardingBatch(intent) | Firma y retransmite un batch Safe/ERC-4337 atómico con una biometría fresca. | | nyx_wallet/onboarding | Canonicaliza/hashea intents y permite a un BFF contrastar una UserOperation sin API de firma. |

Licencia

Este software es propietario y confidencial. Su uso, copia, modificación, distribución, ingeniería inversa o explotación comercial requiere autorización escrita de Nyx Wallet. Consulta LICENSE para el texto completo.