npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kalkilabs/dhruva-agent-tools

v0.1.3

Published

Framework-agnostic tools for Dhruva Agent Identity (ERC-8004) — EIP-712 registration signing, EIP-191 identity proofs, and on-chain status lookup. Works with Mastra, LangChain, Vercel AI SDK, or plain function calling.

Readme

Framework agnostic tools for Dhruva Agent Identity (ERC-8004 compliant: EIP-8004 spec) focused on:

  • EIP-712 registration signing (owner request verification + agent consent signature)
  • EIP-191 identity proof generation (personal_sign) and verification

Depends only on viem and zod. No agent framework is required. Optional Mastra adapter available via subpath import.

Installation

npm install @kalkilabs/dhruva-agent-tools

Core API Reference

Registration Signing

| Export | Description | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | DhruvaSigningPolicy | Class encapsulating all security checks: owner whitelist, EIP-712 domain validation, deadline bounds, one-time signing guard, owner signature verification, URI binding | | generateRegistrationSignature(account, params) | Sign the agent-side RegisterAgentWallet EIP-712 message (includes agentURIHash) | | verifyOwnerSignature(params) | Verify the owner's RegistrationRequest EIP-712 signature | | accountFromPrivateKey(hex) | Derive a viem LocalAccount from a hex private key |

Produce the agent’s EIP-712 signature (agent)

If you only need the raw agent signature, call generateRegistrationSignature directly:

import {
  accountFromPrivateKey,
  generateRegistrationSignature,
} from "@kalkilabs/dhruva-agent-tools";
import type { Address, Hex } from "viem";

const account = accountFromPrivateKey(process.env.AGENT_PRIVATE_KEY as Hex);

const agentSignature = await generateRegistrationSignature(account, {
  nftOwner: "0xOwner..." as Address,
  agentWallet: account.address,
  agentURI: "ipfs://bafy.../agent.json",
  deadline: 1712345678n,
  registryAddress: "0xRegistry..." as Address,
  chainId: 80002,
});

If you want the recommended “safe by default” flow (whitelist + deadline bounds + one-time guard), use DhruvaSigningPolicy.evaluate(...) (see the examples below).

Recommended signing policy flow (agent)

DhruvaSigningPolicy.evaluate(...) accepts a base64-encoded JSON challenge produced by the Dhruva client. The decoded payload includes the EIP-712 domain + message the owner is asking the agent to co-sign.

  • The policy enforces: whitelist, chain/domain binding, deadline bounds, and one-time signing guard.
  • On success it returns the agent’s RegisterAgentWallet EIP-712 signature (agentSignature).

Identity Proof

| Export | Description | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | signIdentityProof(account, config) | Generate an EIP-191 personal_sign identity proof. The message is entirely agent-generated -- no user input enters the signed payload. | | verifyIdentityProof(proof, maxAgeSeconds?) | Verify an identity proof: prefix, field integrity, signature recovery, and expiration check | | buildIdentityProofMessage(...) | Build the structured plaintext message for an identity proof |

Verifying an identity proof (server/verifier)

signIdentityProof(...) returns a structured object containing the signed plaintext message and metadata (agent address, timestamp, nonce, registryAddress, chainId). A verifier should call verifyIdentityProof(...) and enforce a max age.

signIdentityProof(account, config) takes:

  • chainId: required
  • registryAddress: optional (defaults to DHRUVA_IDENTITY_PROOF_REGISTRY_ADDRESS = 0x2C6AB393D01DBC882640f7C83B420a4859616307)
import { verifyIdentityProof } from "@kalkilabs/dhruva-agent-tools";

// `proof` is the JSON object returned by the agent from signIdentityProof(...)
const result = await verifyIdentityProof(proof, 300); // 5 minutes max age

if (!result.valid) throw new Error(`Invalid identity proof: ${result.reason}`);

// Recovered wallet address that produced the signature
console.log("agentAddress =", result.agentAddress);

Constants

| Export | Description | | --------------------------------------------------- | --------------------------------------------------------- | | buildDhruvaEIP712Domain(registryAddress, chainId) | Build the EIP-712 domain separator | | DHRUVA_EIP712_NAME / DHRUVA_EIP712_VERSION | Domain name ("DhruvaAgentIdentity") and version ("1") | | REGISTRATION_REQUEST_TYPES | Owner-side EIP-712 type (includes agentURIHash) | | REGISTER_AGENT_WALLET_TYPES | Agent-side EIP-712 type (includes agentURIHash) | | DHRUVA_IDENTITY_PROOF_PREFIX | Prefix for EIP-191 identity proof messages | | DHRUVA_IDENTITY_PROOF_REGISTRY_ADDRESS | Default registry address used in identity proofs |

Integration Guides

Mastra

The @kalkilabs/dhruva-agent-tools package is framework-agnostic and does not ship a Mastra adapter folder. Use the core exports and wrap them with Mastra’s createTool() in your app.

import { createTool } from "@mastra/core/tools";
import { z } from "zod";
import {
	DhruvaSigningPolicy,
	signIdentityProof,
	accountFromPrivateKey,
	DHRUVA_IDENTITY_PROOF_REGISTRY_ADDRESS,
	getRegistrationStatus,
} from "@kalkilabs/dhruva-agent-tools";
import type { Address, Hex } from "viem";

const agentPrivateKey = process.env.AGENT_PRIVATE_KEY as Hex;
const account = accountFromPrivateKey(agentPrivateKey);
const registryAddress = process.env.DHRUVA_REGISTRY_ADDRESS as Address;
const chainId = Number(process.env.DHRUVA_CHAIN_ID);

const identityProofTool = createTool({
	id: "dhruva-identity-proof",
	description: "Generate a cryptographic identity proof for this agent.",
	inputSchema: z.object({}),
	outputSchema: z.any(),
	execute: async () => {
		return await signIdentityProof(account, {
			registryAddress: DHRUVA_IDENTITY_PROOF_REGISTRY_ADDRESS,
			chainId,
		});
	},
});

const policy = new DhruvaSigningPolicy({
	agentPrivateKey,
	chainId,
	whitelistedOwners: (process.env.DHRUVA_WHITELISTED_OWNERS ?? "")
		.split(",")
		.filter(Boolean) as Address[],
});

const registrationTool = createTool({
	id: "dhruva-registration-signer",
	description: "Verify owner request and produce the agent consent signature.",
	inputSchema: z.object({
		base64Challenge: z.string(),
		chainId: z.number(),
	}),
	outputSchema: z.any(),
	execute: async (input) => {
		return await policy.evaluate({
			base64Challenge: input.base64Challenge,
			chainId: input.chainId,
		});
	},
});

const statusTool = createTool({
	id: "dhruva-registration-status",
	description: "Check this agent’s on-chain registration status.",
	inputSchema: z.object({}),
	outputSchema: z.any(),
	execute: async () => {
		return await getRegistrationStatus({
			agentPrivateKey,
			registryAddress,
			chainId,
			rpcUrl: process.env.DHRUVA_RPC_URL as string,
		});
	},
});

const agent = new Agent({
	name: "my-agent",
	model: openai("gpt-4o"),
	tools: {
		"dhruva-registration-signer": registrationTool,
		"dhruva-identity-proof": identityProofTool,
		"dhruva-registration-status": statusTool,
	},
});

LangChain / LangGraph

Use the core functions with DynamicStructuredTool:

import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import {
	DhruvaSigningPolicy,
	signIdentityProof,
	accountFromPrivateKey,
} from "@kalkilabs/dhruva-agent-tools";
import type { Address, Hex } from "viem";

const account = accountFromPrivateKey(process.env.AGENT_PRIVATE_KEY as Hex);
const chainId = Number(process.env.DHRUVA_CHAIN_ID);

const identityProofTool = new DynamicStructuredTool({
	name: "dhruva-identity-proof",
	description:
		"Generate a cryptographic identity proof proving the agent controls its wallet address.",
	schema: z.object({}),
	func: async () => {
		const proof = await signIdentityProof(account, {
			chainId,
		});
		return JSON.stringify(proof);
	},
});

const policy = new DhruvaSigningPolicy({
	agentPrivateKey: process.env.AGENT_PRIVATE_KEY as Hex,
	chainId,
	whitelistedOwners: (process.env.DHRUVA_WHITELISTED_OWNERS ?? "")
		.split(",")
		.filter(Boolean) as Address[],
});

const registrationTool = new DynamicStructuredTool({
	name: "dhruva-registration-signer",
	description:
		"Handle Dhruva Agent Identity registration requests with EIP-712 mutual authentication.",
	schema: z.object({
		base64Challenge: z.string(),
		chainId: z.number(),
	}),
	func: async (input) => {
		const result = await policy.evaluate({
			base64Challenge: input.base64Challenge,
			chainId: input.chainId,
		});
		return JSON.stringify(result);
	},
});

Vercel AI SDK

Use the core functions with the tool() helper:

import { tool } from "ai";
import { z } from "zod";
import {
	signIdentityProof,
	accountFromPrivateKey,
	DHRUVA_IDENTITY_PROOF_REGISTRY_ADDRESS,
} from "@kalkilabs/dhruva-agent-tools";
import type { Address, Hex } from "viem";

const account = accountFromPrivateKey(process.env.AGENT_PRIVATE_KEY as Hex);
const chainId = Number(process.env.DHRUVA_CHAIN_ID);

const identityProofTool = tool({
	description:
		"Generate a cryptographic identity proof proving the agent controls its wallet.",
	parameters: z.object({}),
	execute: async () => {
		return await signIdentityProof(account, {
			registryAddress: DHRUVA_IDENTITY_PROOF_REGISTRY_ADDRESS,
			chainId,
		});
	},
});

Plain Function Calling (No Framework)

import {
	DhruvaSigningPolicy,
	signIdentityProof,
	verifyIdentityProof,
	accountFromPrivateKey,
	DHRUVA_IDENTITY_PROOF_REGISTRY_ADDRESS,
} from "@kalkilabs/dhruva-agent-tools";
import type { Address, Hex } from "viem";

const account = accountFromPrivateKey(process.env.AGENT_PRIVATE_KEY as Hex);
const chainId = Number(process.env.DHRUVA_CHAIN_ID);

// Generate an identity proof
const proof = await signIdentityProof(account, {
	registryAddress: DHRUVA_IDENTITY_PROOF_REGISTRY_ADDRESS,
	chainId,
});

// Verify a proof (verifier side)
const result = await verifyIdentityProof(proof, 300);
console.log(result.valid, result.agentAddress);

Security Model

| Protection | Mechanism | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Private key isolation | All config interfaces accept typed parameters sourced from process.env at initialization time. Private keys never appear in tool input schemas or LLM-visible data. | | Owner whitelist | DhruvaSigningPolicy rejects registration requests from non-whitelisted owners | | One-time signing guard | Agent can only consent to registration once per DhruvaSigningPolicy instance | | EIP-712 domain separation | Signatures are bound to a specific registry address and chain ID, preventing cross-chain replay | | URI binding | agentURIHash is included in both RegistrationRequest and RegisterAgentWallet EIP-712 types, binding signatures to a specific registration file | | Deadline validation | Rejects expired deadlines and deadlines too far in the future | | Identity proof anti-replay | Each proof includes a cryptographic nonce (crypto.getRandomValues) and timestamp; verifier checks expiration | | Agent-controlled messages | Identity proofs are entirely agent-generated -- no user input enters the signed payload, preventing social engineering |

License

MIT