HyperDHT
P2P DHT for keyed peer lookup, hole punching, and encrypted direct connections.
HyperDHT is the lower-level keyed connection layer underneath Hyperswarm. Use it when you want to dial a known public key directly or work with raw DHT discovery and record APIs.
Install
npm i hyperdhtQuickstart
import DHT from 'hyperdht'
import { once } from 'node:events'
const serverNode = new DHT()
const clientNode = new DHT()
const keyPair = DHT.keyPair()
const server = serverNode.createServer(socket => {
socket.end('hello from hyperdht')
})
await server.listen(keyPair)
const socket = clientNode.connect(keyPair.publicKey)
await once(socket, 'open')
socket.write('ping')
for await (const chunk of socket) {
console.log(chunk.toString())
}
await server.close()
await Promise.all([serverNode.destroy(), clientNode.destroy()])API Reference
Node setup
new DHT([options])
Create a new DHT node.
| Parameter | Type | Default |
|---|---|---|
options | HyperDHTOptions | {} |
keyPair = DHT.keyPair([seed])
Use this method to generate the required keypair for DHT operations.
- Returns: an object with
{publicKey, secretKey}.
node = DHT.bootstrapper(port, host, [options])
If you want to run your own Hyperswarm network use this method to easily create a bootstrap node.
Servers
node.createServer([options], [onconnection])
Create a new server for accepting incoming encrypted P2P connections.
| Parameter | Type | Description |
|---|---|---|
options | CreateServerOptions | — |
onconnection | function(NoiseSecretStream): void | Shorthand listener for the 'connection' event. |
- Returns:
Server— A server object — callserver.listen(keyPair)to start accepting connections.
{
firewall (remotePublicKey, remoteHandshakePayload) {
// validate if you want a connection from remotePublicKey
// if you do return false, else return true
// remoteHandshakePayload contains their ip and some more info
return true
}
}await server.listen(keyPair)
Make the server listen on a keyPair. To connect to this server use keyPair.publicKey as the connect address.
- Throws:
ALREADY_LISTENINGif the server is already listening.NODE_DESTROYEDif the DHT node has been destroyed.
server.refresh()
Refresh the server, causing it to reannounce its address. This is automatically called on network changes.
Call this to reannounce the server address. HyperDHT also calls it automatically when the network changes.
server.address()
You can also get this info from node.remoteAddress() minus the public key.
- Returns: an object containing the address of the server:
{
;(host, // external IP of the server,
port, // external port of the server if predictable,
publicKey) // public key of the server
}server.on('connection', socket)
Emitted when a new encrypted connection has passed the firewall check.
The socket exposes socket.remotePublicKey, and socket.handshakeHash is a shared identifier for the encrypted session.
server.on('listening')
Emitted when the server is fully listening on a keyPair.
await server.close()
Stop listening.
server.on('close')
Emitted when the server is fully closed.
Clients and sockets
node.connect(remotePublicKey, [options])
Connect to a remote server. Similar to createServer this performs UDP holepunching for P2P connectivity.
| Parameter | Type | Description |
|---|---|---|
remotePublicKey | Buffer|string | Public key of the server to connect to (Buffer, hex string, or z-base32 string). |
options | ConnectOptions | — |
- Returns:
NoiseSecretStream— An encrypted duplex stream — usesocket.on('open', ...)to know when it is ready.
socket.on('open')
Emitted when the encrypted connection has been fully established with the server.
socket.remotePublicKey
The public key of the remote peer.
socket.publicKey
The public key of the local socket.
Peer discovery
node.lookup(topic, [options])
Look for peers in the DHT on the given topic. Topic should be a 32 byte buffer (normally a hash of something).
| Parameter | Type | Default | Description |
|---|---|---|---|
topic | Buffer | — | — |
options | object | {} | Options forwarded to dht-rpc. |
- Returns:
QueryStream— An async-iterable query stream whose values are{ from, to, peers }objects.
| Option |
|---|
from |
to |
peers |
node.announce(topic, keyPair, [relayAddresses], [options])
Announce that you are listening on a key-pair to the DHT under a specific topic.
| Parameter | Type | Default | Description |
|---|---|---|---|
topic | Buffer | — | 32-byte topic buffer to announce under. |
keyPair | {publicKey: Buffer, secretKey: Buffer} | — | The Ed25519 key pair to announce with. |
relayAddresses | Array<{host: string, port: number}> | — | Up to 3 DHT relay node addresses to include in the announcement. |
options | object | {} | Options forwarded to dht-rpc. |
- Returns:
QueryStream— An async-iterable query stream (same shape aslookup()).
const topic = DHT.hash(Buffer.from('my-topic'))
const stream = node.announce(topic, keyPair)
await stream.finished()Servers created with node.createServer() already announce themselves on the key pair they are listening on. Use node.announce() when you also want to publish a topic mapping.
await node.unannounce(topic, keyPair, [options])
Unannounce a key-pair.
| Parameter | Type | Default | Description |
|---|---|---|---|
topic | Buffer | — | 32-byte topic buffer previously used in announce(). |
keyPair | {publicKey: Buffer, secretKey: Buffer} | — | The Ed25519 key pair to unannounce. |
options | object | {} | Options forwarded to dht-rpc. |
- Returns:
Promise<void>— Resolves when the unannounce query has completed.
const topic = DHT.hash(Buffer.from('my-topic'))
await node.unannounce(topic, keyPair)Mutable and immutable records
await node.immutablePut(value, [options])
Store an immutable value in the DHT. When successful, the hash of the value is returned.
| Parameter | Type | Default | Description |
|---|---|---|---|
value | Buffer | — | The value to store (a Buffer). |
options | object | {} | Options forwarded to dht-rpc. |
- Returns:
Promise<{hash: Buffer, closestNodes: Array<object>}>— Object with the 32-bytehashand theclosestNodesthat stored the value.
const { hash } = await node.immutablePut(Buffer.from('hello world'))
const result = await node.immutableGet(hash)
console.log(result.value.toString()) // 'hello world'await node.immutableGet(hash, [options])
Fetch an immutable value from the DHT. When successful, it returns the value corresponding to the hash.
| Parameter | Type | Default | Description |
|---|---|---|---|
hash | Buffer | — | 32-byte hash of the value to fetch (returned by immutablePut()). |
options | object | {} | Options forwarded to dht-rpc. |
- Returns:
Promise<{token: Buffer, from: object, to: object, value: Buffer}\|null>— The result node ornullif not found.
const result = await node.immutableGet(hash)
if (result) console.log(result.value.toString())await node.mutablePut(keyPair, value, [options])
Store a mutable value in the DHT.
| Parameter | Type | Default | Description |
|---|---|---|---|
keyPair | {publicKey: Buffer, secretKey: Buffer} | — | Ed25519 key pair used to sign the value. |
value | Buffer | — | The value to store (a Buffer). |
options | MutablePutOptions | {} | Put options. |
- Returns:
Promise<{publicKey: Buffer, closestNodes: Array<object>, seq: number, signature: Buffer}>— The stored record metadata.
const keyPair = DHT.keyPair()
const { seq } = await node.mutablePut(keyPair, Buffer.from('v1'), { seq: 0 })
console.log('stored at seq', seq)await node.mutableGet(publicKey, [options])
Fetch a mutable value from the DHT.
| Parameter | Type | Default | Description |
|---|---|---|---|
publicKey | Buffer | — | Ed25519 public key of the key pair used to sign the value with mutablePut(). |
options | MutableGetOptions | {} | Fetch options. |
- Returns:
Promise<{token: Buffer, from: object, to: object, seq: number, value: Buffer, signature: Buffer}\|null>— The latest matching result, ornullif not found.
const result = await node.mutableGet(keyPair.publicKey)
if (result) console.log(result.value.toString(), 'seq:', result.seq)Lifecycle
await node.destroy([options])
Fully destroy this DHT node.
| Parameter | Type | Default | Description |
|---|---|---|---|
options | object | {} | Pass { force: true } to skip waiting for servers to unannounce before closing. |
- Returns:
Promise<void>— Resolves when the node is fully shut down.
await node.destroy()
// or, to skip graceful unannounce:
await node.destroy({ force: true })HyperDHT inherits additional lower-level RPC APIs from dht-rpc. Reach for those when you need custom queries beyond the keyed connection and record helpers above.
Types
HyperDHTOptions
Options for creating a HyperDHT node.
| Property | Type | Default | Description |
|---|---|---|---|
bootstrap | Array<string> | — | Bootstrap server addresses ('host:port'). Defaults to the Holepunch public bootstrap nodes. |
keyPair | {publicKey: Buffer, secretKey: Buffer} | — | Default key pair used for server.listen() and connect(). |
connectionKeepAlive | number|false | 5000 | Keep-alive interval in ms for all opened sockets. Set false to disable. |
randomPunchInterval | number | 20000 | Minimum ms between random holepunches. |
CreateServerOptions
Options for node.createServer().
| Property | Type | Default | Description |
|---|---|---|---|
firewall | function(Buffer, object): boolean | — | Called with (remotePublicKey, remoteHandshakePayload). Return true to block, false to allow. |
holepunch | function(number, number, Array, Array): boolean | — | Called before holepunching begins. Return false to abort. |
relayThrough | Buffer|Array<Buffer>|function(): Buffer|null | — | Optionally relay through a specific peer — pass a public key (Buffer), an array of public keys to pick from, or a function returning one. |
relayKeepAlive | number | 5000 | Keep-alive interval in ms for the relay socket. |
ConnectOptions
Options for node.connect().
| Property | Type | Default | Description |
|---|---|---|---|
nodes | Array<{host: string, port: number}> | — | Known DHT nodes close to the remote — speeds up connecting. |
relayAddresses | Array<{host: string, port: number}> | — | Relay server addresses to hole-punch through when a direct connection cannot be established. |
keyPair | {publicKey: Buffer, secretKey: Buffer} | — | Key pair to use for this connection. Defaults to node.defaultKeyPair. |
relayThrough | Buffer|Array<Buffer>|function(): Buffer|null | — | Optionally relay through a specific peer — pass a public key (Buffer), an array of public keys to pick from, or a function returning one. |
MutableGetOptions
Options for node.mutableGet().
| Property | Type | Default | Description |
|---|---|---|---|
seq | number | 0 | Only return values whose seq is >= this number. |
latest | boolean | true | If true, scan the whole query and return the highest seq seen. |
refresh | function(object): boolean | — | Called with the latest result; return true to re-store it, extending its TTL. |
MutablePutOptions
Options for node.mutablePut().
| Property | Type | Default | Description |
|---|---|---|---|
seq | number | 0 | Sequence number for this value. Must be greater than the current stored seq to overwrite. |
signMutable | function(number, Buffer, object): Promise<Buffer> | — | Custom signing function. Defaults to the built-in Ed25519 signer. |
Errors
Coded errors this module can throw — catch them via err.code.
| Error | Thrown when |
|---|---|
ALREADY_LISTENING | if the server is already listening. |
NODE_DESTROYED | if the DHT node has been destroyed. |
See also
- Connect two peers by key with HyperDHT—step-by-step direct peer connection flow.
- Connect to many peers by topic with Hyperswarm—higher-level topic discovery and connection management.
- Hyperswarm—higher-level swarm abstraction built on HyperDHT.
- Secretstream—the Noise-encrypted stream layer that wraps all HyperDHT connections.
- Hyperbeam—one-to-one encrypted pipe CLI built on HyperDHT.
- Hypershell—encrypted remote shell and file-copy tools built on HyperDHT.
- Hypertele—TCP proxy CLI built on HyperDHT.
- Hyperssh—SSH and SSHFS access routed through HyperDHT.