@yunggenius/nil-auth
v0.2.0
Published
Ethereum wallet authentication for Nillion
Maintainers
Readme
@nillion/nil-auth
Ethereum wallet authentication for Nillion SecretVault.
Features
- 🔐 Passwordless Authentication - Sign in with MetaMask or any Web3 wallet
- 🆔 DID Generation - Automatic decentralized identifier creation
- 🔑 Token Management - Secure token generation with automatic refresh
- ✅ Subscription Checking - Verify NilDB and NilAI service access
- ⚡ Auto-reconnect - Seamless session restoration
- 🔄 Auto-refresh - Tokens refresh automatically before expiry
- 🛡️ Secure Logging - Sensitive values automatically sanitized
- 🎣 React Hooks - First-class React integration with optimized re-renders
- 📦 Tree-shakeable - Only bundle what you use
- 🔧 Configurable - Flexible storage and network options
Installation
npm install @nillion/nil-auth
# or
yarn add @nillion/nil-auth
# or
pnpm add @nillion/nil-authPeer Dependencies
npm install @nillion/nuc @nillion/secretvaults viemQuick Start
Vanilla JavaScript
import { createNilAuth, NETWORKS, checkSubscription } from "@nillion/nil-auth";
// 1. Create auth instance
const auth = createNilAuth({
network: NETWORKS.testnet,
autoRefresh: true,
});
// 2. Listen for events
auth.on("authenticated", (session) => {
console.log("Authenticated:", session.did);
});
auth.on("error", (error) => {
console.error("Error:", error.code, error.message);
});
// 3. Connect wallet
await auth.connect();
// 4. Check subscription status
if (auth.isAuthenticated()) {
const session = auth.getSession();
// Simple API: just pass DID and service
const result = await checkSubscription(session.did, "nildb");
console.log("NilDB subscribed:", result.subscribed);
}
// 5. Disconnect
await auth.disconnect();React
import { NilAuthProvider, useNilAuth, useSubscription } from "@nillion/nil-auth/react";
// 1. Wrap your app
function App() {
return (
<NilAuthProvider config={{ network: "testnet" }}>
<MyApp />
</NilAuthProvider>
);
}
// 2. Use hooks in components
function ConnectButton() {
const { isAuthenticated, isLoading, connect, disconnect } = useNilAuth();
const { nildb, nilai } = useSubscription();
if (isLoading) return <button disabled>Loading...</button>;
if (isAuthenticated) {
return (
<div>
<p>NilDB: {nildb ? "✓" : "✗"} | NilAI: {nilai ? "✓" : "✗"}</p>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}
return <button onClick={connect}>Connect Wallet</button>;
}Configuration
Basic Configuration
import { createNilAuth, NETWORKS, LocalStorageAdapter } from "@nillion/nil-auth";
const auth = createNilAuth({
// Network preset: "testnet" or "mainnet"
network: "testnet",
// Storage adapter
storage: new LocalStorageAdapter(),
// Storage key prefix
storagePrefix: "myapp_",
// Auto-connect on initialization
autoConnect: true,
// Auto-refresh tokens before expiry
autoRefresh: true,
// Builder name for registration
builderName: "My App",
// Retry configuration
retry: {
maxAttempts: 3,
initialDelayMs: 1000,
maxDelayMs: 10000,
backoffMultiplier: 2,
},
// Debug logging (sensitive values auto-sanitized)
debug: false,
// Callbacks
onAuthStateChange: (state) => console.log("State:", state.status),
onError: (error) => console.error("Error:", error.code),
});Network Configuration
Using Built-in Networks
// Use testnet (default)
const auth = createNilAuth({ network: "testnet" });
// Use mainnet
const auth = createNilAuth({ network: "mainnet" });Custom Network Configuration
const auth = createNilAuth({
network: {
chainId: "nillion-chain-custom-1",
nilchainRpc: "https://rpc.custom.nillion.network",
nilauthUrl: "https://nilauth.custom.nillion.network",
nildbNodes: [
"https://nildb-n1.custom.nillion.network",
"https://nildb-n2.custom.nillion.network",
],
},
});Register Custom Networks
import { registerNetwork, createNilAuth } from "@nillion/nil-auth";
// Register once at app startup
registerNetwork("devnet", {
chainId: "nillion-chain-devnet-1",
nilchainRpc: "http://localhost:26657",
nilauthUrl: "http://localhost:3000",
nildbNodes: ["http://localhost:8080"],
});
// Then use it anywhere
const auth = createNilAuth({ network: "devnet" });Environment Variables
Override network settings via environment variables:
# .env or .env.local
NILLION_NETWORK=testnet
NILLION_CHAIN_ID=nillion-chain-testnet-1
NILLION_NILCHAIN_RPC=https://rpc.testnet.nillion.network
NILLION_NILAUTH_URL=https://nilauth.testnet.nillion.network
NILLION_NILDB_NODES=https://nildb-n1.nillion.network,https://nildb-n2.nillion.networkEnvironment variables take precedence over code configuration, making it easy to:
- Switch networks between environments (dev, staging, prod)
- Update endpoints without code changes
- Keep sensitive URLs out of source control
Validate Configuration
import { validateNetworkConfig } from "@nillion/nil-auth";
try {
validateNetworkConfig({
chainId: "my-chain",
nilchainRpc: "https://rpc.example.com",
nilauthUrl: "https://auth.example.com",
nildbNodes: ["https://db1.example.com"],
});
console.log("Configuration is valid!");
} catch (error) {
console.error("Invalid configuration:", error.message);
}API Reference
NilAuth Class
class NilAuth {
// Lifecycle
connect(options?: ConnectOptions): Promise<void>;
disconnect(): Promise<void>;
// State
getState(): AuthState;
getSession(): Session | null;
isConnected(): boolean;
isAuthenticated(): boolean;
// Token Management
refreshTokens(): Promise<void>;
getTimeUntilExpiry(): Promise<number | null>;
isExpired(): Promise<boolean>;
// Events
on<E extends AuthEvent>(event: E, handler: (payload) => void): () => void;
off<E extends AuthEvent>(event: E, handler: (payload) => void): void;
// Advanced
getSigner(): Signer | null;
}Subscription Checking
Check if users have active subscriptions for Nillion services:
import {
checkSubscription,
requireSubscription,
checkAllSubscriptions,
} from "@nillion/nil-auth";
// Check single service (simplified API - just pass DID and service)
const result = await checkSubscription(session.did, "nildb");
// Returns: { subscribed: boolean, service: ServiceType, did: string }
// Throw if not subscribed (guard pattern)
await requireSubscription(session.did, "nildb");
// Throws NoSubscriptionError if not subscribed
// Check all services at once
const status = await checkAllSubscriptions(session.did);
// Returns: { nildb: boolean, nilai: boolean }
// You can also specify a custom nilauth URL if needed
const result = await checkSubscription(session.did, "nildb", {
nilauthUrl: "https://nilauth.custom.example.com",
timeout: 5000, // optional timeout in ms
});Service Types
| Service | Description |
|---------|-------------|
| nildb | NilDB / SecretVault access |
| nilai | NilAI service access |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| stateChange | AuthState | Any state change |
| connected | { address, did } | Wallet connected |
| authenticated | Session | Session established |
| disconnected | void | User logged out |
| error | NilAuthError | Error occurred |
Storage Adapters
import {
LocalStorageAdapter, // Browser localStorage (default)
SessionStorageAdapter, // Browser sessionStorage
MemoryStorageAdapter, // In-memory (testing/SSR)
} from "@nillion/nil-auth";
// Custom storage (e.g., React Native AsyncStorage)
class AsyncStorageAdapter implements Storage {
async get(key: string): Promise<string | null> { ... }
async set(key: string, value: string): Promise<void> { ... }
async remove(key: string): Promise<void> { ... }
async clear(): Promise<void> { ... }
}Error Handling
import {
NilAuthError,
NoWalletError,
UserRejectedError,
WalletLockedError,
NoSubscriptionError,
NetworkError,
StorageError,
} from "@nillion/nil-auth";
auth.on("error", (error) => {
switch (error.code) {
case "NO_WALLET":
showInstallWalletPrompt();
break;
case "USER_REJECTED":
// User cancelled - no action needed
break;
case "WALLET_CONNECTION":
if (error instanceof WalletLockedError) {
showUnlockWalletPrompt();
} else {
showWalletError(error.message);
}
break;
case "NO_SUBSCRIPTION":
// error.service is "nildb" | "nilai" | undefined
showSubscriptionRequired(error.did, error.service);
break;
case "NETWORK_ERROR":
showNetworkError(error.endpoint);
break;
case "STORAGE_ERROR":
// Storage errors include type: quota_exceeded, private_browsing, etc.
console.log(error.getUserMessage());
break;
default:
showGenericError(error.message);
}
});React Hooks
useNilAuth
Main hook for authentication:
const {
state, // AuthState
isConnected, // boolean
isAuthenticated, // boolean
isLoading, // boolean
walletAddress, // string | null
did, // string | null
error, // NilAuthError | null
connect, // () => Promise<void>
disconnect, // () => Promise<void>
} = useNilAuth();useSession
Get the current session:
const session = useSession();
// Returns Session | nulluseAuthState (with selectors)
Subscribe to specific state for optimal re-renders:
// Only re-renders when status changes
const status = useAuthState((state) => state.status);
// Convenience hooks
const status = useAuthStatus(); // state.status
const isAuth = useIsAuthenticated(); // boolean
const error = useAuthError(); // NilAuthError | nulluseSubscription
Check user's subscription status for Nillion services:
const {
isSubscribed, // boolean - true if subscribed to any (or specified) service
nildb, // boolean - NilDB subscription status
nilai, // boolean - NilAI subscription status
isLoading, // boolean - check in progress
error, // Error | null
refetch, // () => void - manually refresh status
} = useSubscription();
// Or check specific service only
const { isSubscribed, isLoading } = useSubscription("nildb");Features:
- Auto-fetches when authenticated
- Caches results for 30 seconds
- Auto-refetches on session change
useIsSubscribed
Convenience hook for checking a single service:
const hasNilDB = useIsSubscribed("nildb");
const hasNilAI = useIsSubscribed("nilai");
// Use in components
if (!hasNilDB) {
return <SubscriptionRequired service="nildb" />;
}AuthGuard
Protect routes:
<AuthGuard
loadingFallback={<Spinner />}
unauthenticatedFallback={<LoginPrompt />}
onUnauthenticated={() => router.push("/login")}
>
<ProtectedContent />
</AuthGuard>Security
Sensitive Value Sanitization
All logging automatically sanitizes:
- JWT tokens →
[REDACTED_TOKEN] - DIDs →
did:method:abc123...[REDACTED] - Hex signatures →
0x1234abcd...[REDACTED] - Token objects →
{ rootToken: "[REDACTED]" }
Enable debug logging safely:
const auth = createNilAuth({
debug: true, // Safe - tokens won't appear in console
});Storage Security
- Tokens stored with TTL (23 hours default)
- Expired tokens automatically cleared
- Supports custom encrypted storage adapters
TypeScript
Full TypeScript support with exported types:
import type {
// Auth types
AuthState,
AuthStatus,
Session,
NilAuthConfig,
NilAuthError,
// Storage types
Storage,
StorageWithTTL,
// Subscription types
ServiceType, // "nildb" | "nilai"
SubscriptionResult, // { subscribed, service, did }
CheckSubscriptionOptions,
} from "@nillion/nil-auth";
// React-specific types
import type {
UseSubscriptionReturn,
} from "@nillion/nil-auth/react";Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build
npm run build
# Lint
npm run lintChangelog
v0.2.0
Dependency Updates:
- Updated @nillion/nuc - Now requires
^1.1.1(previously^1.0.0) - Updated @nillion/secretvaults - Now requires
^1.1.0(previously^1.0.0) - API Compatibility - Updated invocation token generation to use
.expiresIn()method with backward compatibility support
Breaking Changes:
- Peer dependencies updated - users must install compatible versions:
npm install @nillion/nuc@^1.1.1 @nillion/secretvaults@^1.1.0
v0.1.8
Improvements:
- Simplified Subscription API -
checkSubscription(),requireSubscription(), andcheckAllSubscriptions()now use a simpler signature:// Before: checkSubscription(nilauthClient, did, service) // After: checkSubscription(did, service) await checkSubscription(session.did, "nildb"); - Direct HTTP Fetch - Subscription checks now use direct API calls for improved reliability
- Optional Config - Pass custom
nilauthUrlortimeoutvia options parameter
v0.1.3
Bug Fixes:
- EIP-712 Signing Compatibility - Fixed
TokenGenerationError: Failed to generate root tokencaused byInvalid primary type 'undefined'. ThesignTypedDatafunction now properly handles:- Full EIP-712 payload objects passed as a single parameter
primaryTypeembedded in thetypesobject- Improved error messages when
primaryTypecannot be determined
New Features:
- Subscription Checking - Added utilities to verify NilDB and NilAI service access:
checkSubscription()- Check single service subscriptionrequireSubscription()- Throw if not subscribed (guard pattern)checkAllSubscriptions()- Check all services at onceuseSubscription()- React hook for subscription statususeIsSubscribed()- Convenience hook for single service check
v0.1.0
- Initial release
- Ethereum wallet authentication via MetaMask
- DID generation and token management
- Auto-refresh and auto-reconnect
- React hooks and provider
- Storage adapters (localStorage, sessionStorage, memory)
- Secure logging with sanitized sensitive values
Compatibility
| Package | Version |
|---------|---------|
| @nillion/nuc | ^1.1.1 |
| @nillion/secretvaults | ^1.1.0 |
| viem | ^2.0.0 |
| react | ^18.0.0 || ^19.0.0 (optional) |
License
MIT
