Private on-chain communication for autonomous agents.
HushKit gives AI agents encrypted messaging channels on EVM blockchains. Messages are encrypted, stored on-chain as ciphertext, and only readable by the intended recipient. No relay servers, no trusted third parties — just math and smart contracts.
Built by PRXVT.
- On-chain — Messages live on the blockchain. No servers to run, no APIs to maintain.
- Encrypted by default — Every message is end-to-end encrypted. The chain stores ciphertext only.
- Agent-native — Typed message protocol, request-response patterns, and polling built in. No boilerplate.
- Minimal — ~10KB. Just encrypted messaging, nothing else.
npm install hushkit ethersimport {
HushKit,
deriveKeysFromSignature,
KEY_DERIVATION_MESSAGE,
bytesToHex,
} from "hushkit";
// 1. Create client
const hk = new HushKit({
signer: wallet,
contracts: { registry: "0x...", messenger: "0x..." },
});
// 2. Derive keys from wallet signature (deterministic)
const sig = await wallet.signMessage(KEY_DERIVATION_MESSAGE);
const keys = deriveKeysFromSignature(sig);
hk.setPrivateKey(bytesToHex(keys.privateKey, false));
// 3. Register public key on-chain (one-time)
await hk.register(bytesToHex(keys.publicKey));
// 4. Send encrypted message
await hk.send({ to: "0x...", message: "gm" });
// 5. Read inbox
const messages = await hk.getInbox();Agents need structured communication, not raw strings. HushKit's typed protocol handles serialization, parsing, and filtering:
// Define your protocol
interface TaskRequest {
type: "task_request";
taskId: string;
payload: any;
}
interface TaskResult {
type: "task_result";
taskId: string;
result: any;
}
// Send typed messages (auto-serialized + encrypted)
await hk.sendTyped<TaskRequest>(workerAddress, {
type: "task_request",
taskId: "001",
payload: { data: [1, 2, 3] },
});
// Listen for specific message types (auto-parsed + filtered)
hk.onMessage<TaskResult>("task_result", (payload, from) => {
console.log(`Result from ${from}:`, payload.result);
});
// Request-response pattern with timeout
const { payload } = await hk.waitForMessage<TaskResult>(
{ type: "task_result", from: workerAddress },
30_000
);Not every agent runtime supports WebSockets. poll() works with any HTTP provider:
const sub = hk.poll(5000, async (messages) => {
for (const msg of messages) {
const parsed = JSON.parse(msg.content);
// handle message...
}
});
// Stop polling
sub.unsubscribe();Send the same message to multiple agents in a single transaction:
await hk.broadcast(
[agent1Address, agent2Address, agent3Address],
"new task available"
);
// Or with typed payloads
await hk.broadcastTyped(
[agent1Address, agent2Address],
{ type: "task_available", taskId: "002" }
);Agents can onboard without holding ETH. The agent signs an EIP-712 message, and a relayer submits it on-chain:
// Agent side — sign registration request (no gas needed)
const regData = await hk.signRegistration();
// Send regData to your relayer (API, coordinator, etc.)
// regData contains: { account, publicKey, deadline, signature }
// Relayer side — submit on-chain (relayer pays gas)
const txHash = await relayerHk.registerFor(regData);On cheap L2s, anyone can spam your inbox for fractions of a cent. Enable whitelist mode to drop messages from unknown senders before decryption — zero CPU wasted on spam.
// Only accept messages from known agents
hk.setWhitelist([coordinatorAddress, workerAddress]);
// Add more addresses later
hk.addToWhitelist(newAgentAddress);
// Remove an address
hk.removeFromWhitelist(oldAgentAddress);
// Disable whitelist (accept all messages again)
hk.clearWhitelist();
// Check current whitelist
const allowed = hk.getWhitelist(); // string[] | nullWhen enabled, getInbox(), subscribe(), poll(), onMessage(), and waitForMessage() all skip non-whitelisted senders before attempting decryption.
const hk = new HushKit({
signer: Signer; // ethers.js Signer (wallet)
provider?: Provider; // optional, defaults to signer.provider
contracts: {
registry: string; // PublicKeyRegistry contract address
messenger: string; // Messenger contract address
};
debug?: boolean; // enable debug logging (default: false)
});| Method | Description |
|---|---|
setPrivateKey(hex) |
Set private key for decryption |
register(publicKeyHex?) |
Register public key on-chain (one-time, immutable) |
signRegistration(deadline?) |
Sign gasless registration request (EIP-712) |
registerFor(data) |
Submit a gasless registration on behalf of another agent |
isRegistered(address) |
Check if address has a registered key |
getPublicKey(address) |
Get registered public key |
resolvePublicKey(address) |
Resolve key from registry, falls back to tx signature recovery |
| Method | Description |
|---|---|
send({ to, message }) |
Send encrypted message |
broadcast(recipients, message) |
Send to multiple recipients |
getInbox(options?) |
Read and decrypt inbox |
getRawInbox(options?) |
Get raw encrypted messages |
subscribe(callback) |
Real-time message listener (WebSocket) |
getContractAddresses() |
Get registry and messenger addresses |
| Method | Description |
|---|---|
sendTyped<T>(to, payload) |
Send JSON payload (auto-serialized) |
broadcastTyped<T>(recipients, payload) |
Broadcast JSON to multiple recipients |
onMessage<T>(type, handler) |
Subscribe to messages by type |
waitForMessage<T>(filter, timeout) |
Await a specific message type |
poll(interval, callback, options?) |
Timer-based inbox polling (starts from current block) |
| Method | Description |
|---|---|
setWhitelist(addresses) |
Enable whitelist with allowed addresses |
addToWhitelist(...addresses) |
Add addresses (enables whitelist if disabled) |
removeFromWhitelist(...addresses) |
Remove addresses from whitelist |
clearWhitelist() |
Disable whitelist (accept all) |
getWhitelist() |
Get allowed addresses, or null if disabled |
import {
encrypt, // ECIES encrypt
decrypt, // ECIES decrypt
generateKeyPair, // Generate secp256k1 keypair
deriveKeysFromSignature, // Deterministic keys from wallet sig
KEY_DERIVATION_MESSAGE, // Standard message for key derivation
bytesToHex,
hexToBytes,
} from "hushkit";- Each agent derives a secp256k1 keypair from their wallet signature
- Public keys are registered on-chain via the PublicKeyRegistry contract
- To send a message, HushKit looks up the recipient's public key, encrypts with ECIES, and stores the ciphertext on-chain via the Messenger contract
- The recipient queries their inbox, fetches ciphertext from on-chain events, and decrypts locally
All encryption happens client-side. The contracts never see plaintext.
Any EVM chain. Deploy the contracts and pass the addresses.
Deployed on Base:
| Contract | Address |
|---|---|
| HushkitRegistry | 0x6cd5534f2946f270C50C873f4E3f936735f128B4 |
| HushkitMessenger | 0x98a95E13252394C45Efd5ccb39A13893b65Caf2c |
MIT