Resolve .bso domain names via bitsocial TXT records.
npm install @bitsocial/bso-resolverIf you're wiring this into pkc-js, create resolver instances per provider:
import Pkc from "@pkc/pkc-js";
import { BsoResolver } from "@bitsocial/bso-resolver";
const chainProviderUrls = [
"viem", // uses viem's default public transport
"https://mainnet.infura.io/v3/YOUR_KEY",
"wss://mainnet.infura.io/ws/v3/YOUR_KEY",
];
const resolvers = chainProviderUrls.map((url) => new BsoResolver({
key: `bso-${url}`,
provider: url,
}));
const pkc = await Pkc({ nameResolvers: resolvers });
// Access a resolver instance later, it should not be needed in general:
const resolver = pkc.clients.nameResolvers["bso-viem"].resolver;
// Later, when shutting down:
await pkc.destroy(); // should cascade to resolver.destroy() for each resolverimport { BsoResolver } from "@bitsocial/bso-resolver";
// Create a resolver instance
const resolver = new BsoResolver({ key: "bso-viem", provider: "viem" });
// Check if a name can be resolved
resolver.canResolve({ name: "example.bso" }); // true
resolver.canResolve({ name: "example.com" }); // false
// Resolve a name
const record = await resolver.resolve({ name: "example.bso" });
// { publicKey: "12D3KooW...", ...otherPotentialFieldsInTheFuture }
// Resolve using a custom RPC URL
const resolver2 = new BsoResolver({
key: "bso-infura",
provider: "https://mainnet.infura.io/v3/YOUR_KEY",
});
// Optional cancellation with AbortController
const controller = new AbortController();
const record2 = await resolver.resolve({
name: "example.bso",
abortSignal: controller.signal,
});
// Clean up when done
await resolver.destroy();
await resolver2.destroy();Creates a resolver instance with a shared viem client and persistent cache. Both are lazily initialized on the first resolve() call.
key- Unique identifier for this resolver instance (e.g.`bso-${new URL(chainProviderUrl).hostname}`)provider- Either"viem"for the default public transport, or an HTTP(S) RPC URL or a Websocket RPC URLdataPath(optional, Node only) - Enables SQLite persistence for the cache. Browser builds do not support SQLite and will throw ifdataPathis provided.
const resolver = new BsoResolver({
key: "bso-viem",
provider: "viem",
dataPath: "/path/to/data", // optional — enables SQLite persistence
});resolver.resolve({ name, abortSignal? }): Promise<{ publicKey: string, ...otherFields } | undefined>
Resolves a .bso name by looking up the bitsocial TXT record.
name- The domain name to resolve (e.g."example.bso")abortSignal(optional) - Abort signal used to cancel an in-flight resolve
Returns a parsed object from the bitsocial TXT record, or undefined if not found.
TXT value format: <ipnsPublicKey>[;key=value;other=value] -> { publicKey, key, other }
Returns true if the name ends with .bso (case-insensitive).
Releases shared resources (viem client, cache/DB connection). The underlying resource is only closed when the last resolver using it is destroyed. Idempotent — safe to call multiple times.
After destroy(), calling resolve() will throw.
| Environment | dataPath provided? |
Cache backend |
|---|---|---|
| Node | Yes | SQLite via better-sqlite3 (stored at <dataPath>/.bso-resolver/bso-cache.sqlite) |
| Browser | No | IndexedDB (bso-resolver-cache database) |
| Any | No + no IndexedDB | In-memory Map |
All cache entries expire after 1 hour (TTL).
Resolvers share a single viem client via an internal reference-counted registry. No conflicts.
Resolvers share a single SQLite connection via an internal reference-counted registry. All operations go through the same better-sqlite3 instance (synchronous, single-threaded). No conflicts.
Each process opens its own connection. SQLite WAL mode allows concurrent reads. Writes are serialized by SQLite internally with a 5-second busy timeout (busy_timeout = 5000). Cache writes are simple INSERT OR REPLACE operations that complete in microseconds, so contention is negligible.
IndexedDB handles concurrency natively via transactions. No conflicts.
Call resolver.destroy() when done. Resources (DB connections, client references) are released when the last resolver using them is destroyed. Calling destroy() is idempotent and safe to call multiple times.
Run the full test suite with:
npm testInstall the Playwright browser binaries used by the browser suite with:
npm run test:browser:installRun the browser suite on Playwright's Chromium and Firefox engines with:
npm run test:browserOn Linux CI or fresh machines, Playwright may also require:
npx playwright install --with-deps chromium firefoxThe package publishes separate Node and browser entry points.
- Browser-aware bundlers should resolve the root package import to the browser build automatically.
- Explicit subpaths are also available:
@bitsocial/bso-resolver/browser@bitsocial/bso-resolver/node
This package is not yet published to npm. To set up automated publishing:
- Create the
@bitsocialorganization on npmjs.com - Do an initial manual publish:
npm login && npm run build && npm publish --access public - On npmjs.com, go to the package settings → Publishing access → Configure trusted publishing
- Add: owner=
bitsocialhq, repo=bso-resolver, workflow=publish.yml - Apply the stashed changes (
git stash pop) which add the.github/workflows/publish.ymlworkflow andpublishConfigtopackage.json
After setup, releases created by release-it will automatically trigger npm publishing with provenance via OIDC trusted publishing.
GPL-2.0-only