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

@three-ws/names

v0.1.1

Published

ENS + SNS name resolution, *.threews.sol subdomain minting, and pay-by-name in one import. The three.ws agent-identity SDK.

Readme


@three-ws/names is the official client for the three.ws naming layer — the identity plumbing behind every agent on the platform. It resolves human-readable names to on-chain addresses across ENS (Ethereum) and SNS (Solana), mints <label>.threews.sol subdomains under the platform-owned parent, and routes USDC payments to a recipient identified by name instead of a 44-character base58 key. It wraps the public /api/sns, /api/sns-subdomain, /api/threews/subdomain, and /api/x402/pay-by-name endpoints. If your agent has a wallet, this gives it a name people can read, type, and pay.

Why

A wallet address is unusable as an identity. Nobody types FeMbDoX7R1Psc4GEcvJdsbNbZA3bfztcyDCatJVJpump from memory, and nobody should have to. The naming layer turns identity into a string:

  • Resolve in one call. resolve('vitalik.eth') and resolve('bonfida.sol') hit the right registry automatically — ENS via an Ethereum RPC with failover, SNS via Bonfida — and a bare label is tried against both.
  • Mint a name your agent owns. mintSubdomain('alice') registers alice.threews.sol on-chain, writes a Brave-resolvable URL record, and transfers ownership to the agent's wallet — all in one signed transaction. The platform absorbs the gas; the agent's wallet never signs.
  • Pay by name. payByName('alice.threews.sol', '5') resolves the recipient and builds (or sends) a USDC transfer — handles, .sol domains, and raw addresses all route through the same call, with a recipient-poisoning guard.

Hand-rolling this means three different on-chain SDKs (Bonfida SNS, ethers, SPL token), RPC failover, label validation, an availability denylist, and a preview-then-send flow that survives a name being re-pointed mid-flight. This package is that, done once.

This is the SDK twin of the ens_sns_resolve MCP tool — the same resolution engine, exposed as plain functions instead of an MCP tool.

Install

npm install @three-ws/names

Zero runtime dependencies. Works in Node 18+ and the browser (uses fetch). Minting and mode: 'send' payments require a signed-in three.ws session or a bearer token. The browser prep lane needs no auth — it returns an unsigned transaction for a wallet to sign.

Quick start

Resolution needs no key:

import { resolve } from '@three-ws/names';

const eth = await resolve('vitalik.eth');
console.log(eth.address); // → 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045

const sol = await resolve('bonfida.sol');
console.log(sol.address);     // → owner base58 wallet
console.log(sol.allDomains);  // → other .sol domains the owner holds

Mint a subdomain for an agent and pay it by name:

import { mintSubdomain, payByName } from '@three-ws/names';

// Registers alice.threews.sol on-chain, transfers it to the agent's wallet.
const { full_name, signature } = await mintSubdomain({
  agentId: 'agt_7Yq…',
  label: 'alice',           // optional — defaults to the agent's slugified name
  token: process.env.THREEWS_TOKEN,
});
console.log(full_name); // → alice.threews.sol

// Build an unsigned 5-USDC transfer to that name for a browser wallet to sign.
const { tx_base64, recipient } = await payByName('alice.threews.sol', '5', {
  payerWallet: myWallet.publicKey.toBase58(),
});
console.log(recipient.address, recipient.source); // resolved wallet + 'sns'

API

resolve(name, options?) → Promise<ResolveResult>

Resolve a name to an address. .eth goes to ENS, .sol to SNS, and a bare label (vitalik) is tried against the .sol registry. Wraps GET /api/sns?name=<name> for .sol and the ENS resolver for .eth.

Returns ResolveResult

| Field | Type | Notes | |---|---|---| | name | string | The resolved name, e.g. alice.sol or vitalik.eth. | | address | string \| null | Owner wallet. null when resolved is false. | | network | 'solana' \| 'ethereum' | Which registry answered. | | resolved | boolean | false is a routine "no such name", not an error. | | allDomains | string[] | (SNS) other .sol domains the owner holds. | | favoriteDomain | string \| null | (SNS) the owner's primary .sol, if set. |

A .sol miss returns 200 with resolved: false — the reverse-lookup that runs on every page load makes "no domain" an expected answer, so it is never a 404. Malformed input is a 400.

reverseLookup(address) → Promise<ResolveResult>

Find the primary .sol for a wallet. Wraps GET /api/sns?address=<base58>. Returns the same envelope with name populated (or null if the wallet has no favorite domain).

checkSubdomain(label) → Promise<Availability>

Check whether <label>.threews.sol is free. Wraps GET /api/sns-subdomain?label=<label> (no auth).

| Field | Type | Notes | |---|---|---| | label | string | Normalized label. | | parent | string | threews.sol. | | full_name | string | <label>.threews.sol. | | available | boolean | true if no on-chain owner. | | owner | string \| null | Current on-chain owner, if any. |

mintSubdomain(input) → Promise<MintResult>

Mint <label>.threews.sol, write its Brave URL record, and transfer ownership to a wallet — atomically, in one platform-signed transaction. Wraps POST /api/sns-subdomain. Requires auth.

Input

| Field | Type | Notes | |---|---|---| | agentId | string | Required. The agent the subdomain attaches to. | | label | string | Optional. Defaults to the agent's slugified name. 1–63 chars [a-z0-9-]. | | ownerAddress | string | Optional base58 wallet to receive ownership. Must be linked to your account. Defaults to the agent's own Solana wallet. | | space | number | Optional, 1000–10000. Registry bytes reserved. Default 2000. | | token | string | Bearer token (or rely on a session cookie). |

Returns MintResult: { ok, agent_id, full_name, parent, owner, signature, explorer, url_record, agent_url }. The new subdomain's URL record points at https://three.ws/a/<agentId>, and the agent's meta.sns_domain is set so x402 manifests can show recipient_name without an extra round-trip.

For a user/username-claim subdomain (showcased at /u/<label>, where the label must equal your username), use claimSubdomain instead.

claimSubdomain(input) → Promise<ClaimResult>

Claim <username>.threews.sol for the signed-in user, with its URL record set to https://three.ws/u/<username>. Wraps POST /api/threews/subdomain. The label must equal your account username — divergent labels would let users impersonate other handles. Pass ownerWallet (linked to your account) or fall back to your default agent's Solana wallet. releaseSubdomain(label) wraps the DELETE route to drop the local claim (on-chain ownership is unchanged).

payByName(name, amountUsdc, options?) → Promise<PayResult>

Pay a recipient by name in USDC. Wraps POST /api/x402/pay-by-name. The name resolves across three namespaces in order: @username → that user's default agent wallet; .sol domain (including foo.threews.sol) → on-chain owner; raw base58 → pass-through.

Options

| Option | Type | Default | Notes | |---|---|---|---| | mode | 'prep' \| 'send' | 'prep' | prep returns an unsigned tx; send has the agent sign and broadcast. | | payerWallet | string | — | Required for prep. Base58 fee-payer + source. | | agentId | string | — | Required for send. The agent that signs (must be yours). | | expectedAddress | string | — | (send) the address you previewed. If the name now resolves elsewhere, the call rejects with recipient_changed before signing. | | message | string | — | Optional memo. | | token | string | — | Bearer token for send. |

Returns (prep) { recipient, amount_usdc, tx_base64, blockhash, last_valid_block_height, mint } — decode tx_base64 into a VersionedTransaction, sign with the payer wallet, and submit. Returns (send) { recipient, payer, amount_usdc, signature, mode }. amount_usdc accepts a string or number, must be > 0 and ≤ 10000.

resolvePayee(name) → Promise<Payee>

Resolve-only, no payment. Wraps GET /api/x402/pay-by-name?name=<name>. Returns { name, address, source, resolved, claim? } where source is one of address, sns, or username. A 404 means the name resolved nowhere.

How it works

Three namespaces, one ergonomic surface. Each call routes by the shape of the string you pass:

            resolve / payByName
                    │
        ┌───────────┼─────────────────────────┐
   *.eth│       *.sol│                base58 / @handle
        ▼            ▼                          ▼
   ENS resolver  Bonfida SNS              users.username
   (eth RPC,     resolve() + reverse      → default agent
    failover,    domains + fav-domain        Solana wallet
    3s timeout)                          (.threews.sol also
        │            │                    surfaces a DB claim)
        ▼            ▼                          ▼
   0x… address   owner base58            recipient address
                    │
   mintSubdomain ──▶ createSubdomain → URL record → transferSubdomain
   (one VersionedTransaction, signed by the platform parent-owner keypair)
  • ENS resolves on Ethereum mainnet through a failover provider, bounded by a 3-second timeout. A reverse lookup of the owner's primary name is best-effort.
  • SNS resolves through Bonfida — owner wallet, the owner's other .sol domains, and their favorite domain.
  • Subdomain minting is a single on-chain transaction: createSubdomain (parent owner becomes owner) → createRecordV2Instruction writes the URL record while the platform still owns it (so <label>.threews.sol resolves in Brave) → transferSubdomain hands it to the agent's wallet. The platform's parent-owner keypair (THREEWS_SOL_PARENT_SECRET_BASE58) is the only signer and fee payer — gas is under 0.01 SOL and absorbed, so the agent's wallet never has to sign.
  • Pay-by-name uses the USDC mint (EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v, 6 decimals), creating the recipient's associated token account idempotently. mode: 'send' enforces the agent's per-transaction spend limit and rejects off-curve recipients.

Environment

THREEWS_SOL_PARENT_SECRET_BASE58 — the base58-encoded 64-byte ed25519 secret for the wallet that owns the parent .sol (default threews.sol, overridable with THREEWS_SOL_PARENT_DOMAIN). This is server-side only; it gates all subdomain minting. When it is absent the mint endpoints answer 503 config_missing rather than failing mid-transaction — the SDK surfaces that as a clean error, never a fake success.

Pricing / payment

| Action | Cost | |---|---| | resolve, reverseLookup, checkSubdomain, resolvePayee | Free. Public, rate-limited per IP. | | mintSubdomain, claimSubdomain | Free to the caller — the platform absorbs the sub-0.01-SOL gas. Requires auth. | | payByName | The transferred USDC amount, plus Solana network fees. | | ens_sns_resolve MCP tool | $0.0005 USDC over x402 — the metered equivalent of resolve. |

.sol reads are cached server-side (5 min positive, 60 s negative), and ENS resolutions are cached 5 min in-memory, so repeated UX previews don't hammer the RPC pools.

Errors & edge cases

Every state is designed. Resolution failures return data, not exceptions; only real faults reject.

| State | HTTP | Meaning | Recovery | |---|---|---|---| | resolved: false | 200 | Name has no owner. Not an error. | Show "unclaimed"; offer to mint. | | validation_error | 400 | Malformed name, label, or amount. | Fix the input. Labels are 1–63 chars [a-z0-9-]. | | unauthorized | 401 | Mint or mode: 'send' without auth. | Sign in or pass a bearer token. | | forbidden | 403 | ownerAddress not linked to your account. | Link the wallet, or omit it. | | per_tx_exceeded | 403 | Send over the agent's per-transaction limit. | Lower the amount or raise the agent's limit. | | not_found | 404 | Agent missing, or payee resolved nowhere. | Check the agent ID / name. | | agent_missing_wallet | 412 | Agent has no Solana wallet to attach to. | Provision one via POST /api/agents/:id/solana. | | conflict | 409 | <label>.threews.sol already registered. | Pick another label. | | recipient_changed | 409 | Name re-pointed since you previewed it. | Re-preview, then re-send. | | no_username / username_mismatch | 409 | Claim label ≠ your username. | Set/match your username first. | | config_missing | 503 | Platform parent-owner key not configured. | Server-side THREEWS_SOL_PARENT_SECRET_BASE58 must be set. | | ens_timeout | 503 | ENS RPC exceeded 3 s. | Retry; the failover provider rotates endpoints. |

Examples

Under the hood — resolve .sol with raw fetch (what resolve wraps):

const r = await fetch('https://three.ws/api/sns?name=bonfida.sol');
const { data } = await r.json();
// → { name: 'bonfida.sol', address: '…', network: 'solana', resolved: true }

Agent identity, end to end — give an agent a name, then receive a payment:

import { mintSubdomain, resolvePayee } from '@three-ws/names';

const { full_name } = await mintSubdomain({ agentId, label: 'oracle', token });
// → oracle.threews.sol, owned by the agent's wallet, URL record → /a/<agentId>

const payee = await resolvePayee(full_name);
// → { name: 'oracle.threews.sol', address: '…', source: 'sns', claim: … }

Browser pay-by-name — build, sign in the wallet, submit:

import { payByName } from '@three-ws/names';
import { VersionedTransaction } from '@solana/web3.js';

const { tx_base64 } = await payByName('alice.threews.sol', '2.5', {
  payerWallet: wallet.publicKey.toBase58(),
});
const tx = VersionedTransaction.deserialize(Buffer.from(tx_base64, 'base64'));
const signed = await wallet.signTransaction(tx);
const sig = await connection.sendRawTransaction(signed.serialize());

Agent-signed send — the agent's custodial wallet pays, with a poisoning guard:

const preview = await resolvePayee('alice.threews.sol');
const { signature } = await payByName('alice.threews.sol', '5', {
  mode: 'send',
  agentId,
  expectedAddress: preview.address, // rejects if the name re-points before signing
  token,
});

Related