JavaScript SDK

@midnight-auth/sdk

Installation

npm install @midnight-auth/sdk

Quick Start

import { MidnightAuth } from '@midnight-auth/sdk';

const auth = new MidnightAuth({
  baseUrl: 'https://auth.yourapp.com/api/v1',
  apiKey: 'ma_your_api_key_here',   // from Console → Apps

  // Optional: Enable chain-native login (trustless)
  midnight: {
    indexerUrl: 'http://indexer.testnet.midnight.network/graphql',
    indexerWsUrl: 'ws://indexer.testnet.midnight.network/graphql/ws',
    contractAddress: '0xabc...',
    network: 'testnet',
  },
});

// Check API health
const health = await auth.health();
console.log(health.status); // "ok"
// Chain-native login active?
console.log(auth.login.isChainNative); // true

Configuration

ParamTypeRequiredDescription
baseUrlstringYesMidnight Auth API base URL
apiKeystringYesPartner app API key (ma_...)
timeoutnumberNoRequest timeout in ms (default: 30000)
fetchfunctionNoCustom fetch implementation
midnightobjectNoMidnight chain config for trustless login
midnight.indexerUrlstringYes*Midnight indexer GraphQL URL
midnight.indexerWsUrlstringYes*Midnight indexer WebSocket URL
midnight.contractAddressstringYes*Deployed contract address
midnight.networkstringYes*'testnet' | 'mainnet'

* Required when midnight is provided

Login Flow

Your app initiates a login session, shows a QR code, and polls until the user approves in their wallet.

auth.login.initiate(options)

Start a new login session. Returns QR code and deep link for the wallet.

const session = await auth.login.initiate({
  requirements: ['email', 'phone'],  // credential types to verify
  redirectUrl: 'https://myapp.com/dashboard', // optional
});

// session.sessionId   — unique session ID
// session.qrCodeUrl   — data:image/png base64 QR code
// session.deepLink    — midnight-auth://login?session=...
// session.expiresAt   — ISO timestamp (5 min TTL)
// session.appName     — your registered app name
ParamTypeRequiredDescription
requirementsstring[]YesCredential types: 'email', 'phone', 'social', 'humanity', 'age'
redirectUrlstringNoURL to redirect after login

auth.login.waitForResult(sessionId, options)

Polls session status until verified, expired, or denied. Recommended approach.

const result = await auth.login.waitForResult(session.sessionId, {
  timeoutMs: 120_000,    // max wait (default: 2 min)
  intervalMs: 2_000,     // poll interval (default: 2s)
  onStatusChange: (status) => {
    console.log('Status:', status); // 'pending' → 'verified'
  },
});

if (result.verified) {
  console.log(result.userId);       // "usr_7x8m2k..."
  console.log(result.emailDomain);  // "gmail.com"
}

auth.login.getStatus(sessionId)

Manual status check (use if you prefer custom polling).

const status = await auth.login.getStatus(session.sessionId);
// status.status: 'pending' | 'verified' | 'expired' | 'denied'

auth.login.getResult(sessionId)

Fetch verified result data. Only returns data when status is 'verified'.

const result = await auth.login.getResult(session.sessionId);

🌙 Chain-Native Login (Trustless)

When midnight config is provided, login calls bypass the API entirely and interact directly with the Midnight blockchain. Your app trusts blockchain math, not our servers.

// With midnight config, login is fully trustless:
const session = await auth.login.initiate({
  requirements: ['email', 'phone'],
});
// ↑ Creates session ON-CHAIN (not via API)

const result = await auth.login.waitForResult(session.sessionId);
// ↑ Reads verification result FROM CHAIN (not via API)

if (result.verified) {
  // You can trust this result — it's from blockchain consensus
  console.log(result.userId);       // "usr_7x8m2k..."
  console.log(result.emailDomain);  // "gmail.com"
  console.log(result.phoneCountry); // "US"
}

// Without midnight config, same methods work via API (legacy)
// Just remove the midnight property from config
Privacy note: Login results contain only booleans and derived metadata — never raw PII. The user's credentials are verified via zero-knowledge proofs on-chain.

Login Result Shape

The result object only contains booleans and derived metadata — never raw PII. Fields are present only if the matching credential was required and verified.

{
  verified: true,
  userId: "usr_7a8b3c...",          // app-specific pseudonymous ID
  verifiedAt: "2026-02-26T10:00:00Z",

  // Email credential
  emailVerified: true,
  emailDomain: "gmail.com",         // domain only, not the address
  domainType: "consumer",           // "consumer" | "business" | "edu"

  // Phone credential
  phoneVerified: true,
  phoneCountry: "US",               // country code only
  carrierType: "mobile",            // "mobile" | "voip" | "landline"

  // Social credential
  hasSocial: true,
  socialPlatform: "twitter",
  accountAgeMonths: 36,
  followerRange: "1k-10k",

  // Humanity credential
  isHuman: true,
  humanityScore: 0.98,

  // Age credential
  over18: true,
  over21: true,
  over25: false
}

Onboarding Flow

Issue verifiable credentials to users' wallets. Each step issues an ACDC credential.

auth.startOnboarding(userAid)

Convenience method that returns scoped helpers for each step.

const ob = await auth.startOnboarding('D_user_keri_aid_here');

// Email verification
await ob.sendEmailCode('user@example.com');
await ob.verifyEmail('123456');

// Phone verification
await ob.sendPhoneCode('+1234567890');
await ob.verifyPhone('654321');

// Social auth
const { authorizationUrl } = await ob.startSocialAuth('twitter');
window.location.href = authorizationUrl; // redirect to OAuth

// Humanity check
await ob.verifyHumanity('hcaptcha_token_here');

// Check progress
const status = await ob.getStatus();
console.log(status.completedSteps); // ['email', 'phone']

Drop-in Widget

Zero-config onboarding UI. Mounts a modal that walks users through each step.

import { mountWidget } from '@midnight-auth/sdk/widget';

const widget = mountWidget({
  container: '#onboarding',
  baseUrl: 'https://auth.yourapp.com/api/v1',
  apiKey: 'ma_your_key',
  theme: 'dark',                    // 'dark' | 'light'
  steps: ['email', 'phone'],        // which steps to show
  captchaSiteKey: 'your_hcaptcha_key',
  socialProviders: ['twitter', 'github'],
  onEvent: (event) => {
    // event.step: 'wallet' | 'email' | 'phone' | 'social' | 'humanity'
    // event.status: 'started' | 'completed' | 'failed'
    console.log(event);
  },
  onComplete: (sessionId) => {
    console.log('Onboarding done!', sessionId);
  },
});

// Cleanup
widget.destroy();

Error Handling

import { MidnightAuthError } from '@midnight-auth/sdk';

try {
  await auth.login.initiate({ requirements: ['email'] });
} catch (err) {
  if (err instanceof MidnightAuthError) {
    console.log(err.statusCode); // 403
    console.log(err.message);    // "Invalid or inactive API key"
    console.log(err.body);       // full error body from API
  }
}