This repository is the home for dotns developer CLIent tooling. The goal is to make DotNS interactions reproducible, auditable, and consistent across environments and languages.
DotNS refers to the protocol. Repository, package, and binary names use lowercase (for example dotns-sdk, @dotns/*).
DotNS will be accessed from multiple surfaces: scripts, command-line tools, web apps, backend services, indexers. If each surface re-implements name parsing, ABI handling, network configuration, and transaction encoding, behaviour drifts. Drift shows up as subtle incompatibilities: a name that validates in one place but not another, a call encoded differently across clients, or a release that “works” for one consumer and breaks another.
This monorepo exists to concentrate shared logic in one place, with explicit boundaries, shared primitives, and a small set of versioned artefacts that multiple runtimes can consume.
Client-side here means off-chain code that reads and writes DotNS contracts. The repository is cross-platform:
- TypeScript packages are the primary surface, using Bun workspaces under
packages/*. - Rust crates may exist when they reduce duplication or provide tooling that should not depend on Node, under
crates/*as a Cargo workspace. - Shared artefacts that must be consistent across languages (ABIs, deployment addresses, schemas) live in
shared/*(for exampleshared/abi/,shared/deployments/,shared/schemas/) and are treated as the source of truth for cross-runtime consumers. The contracts themselves live in the main DotNS repository can be found here
Examples:
- name parsing, normalisation, and validation
- namehash helpers and deterministic encodings
- contract call wrappers and typed interfaces
- network configuration and deployment address sets
- scripts that operate across networks (smoke tests, migrations, verification)
- higher-level flows (register, set resolver records, set reverse, Store writes) as composable functions
Non-goals:
- the contracts themselves
- UI code that is primarily presentation
- one-off scripts that are not expected to be maintained
- ad hoc protocol extensions that are not implemented in the contracts
packages/*: TypeScript packages (Bun workspace)crates/*: Rust crates (Cargo workspace, optional)shared/*: cross-language artefacts (ABIs, deployments, schemas)scripts/*: repo-level scripts (fetch / generate / check)
The intent is to keep a small set of packages and crates with clear boundaries:
- core modules are pure and dependency-light
- integration modules talk to networks and contracts
- apps (if any) compose the above but do not duplicate protocol logic
The repository may start with a single package. The structure exists to make growth predictable rather than ad hoc.
This repository consumes ABIs published from the DotNS contracts repository as release assets. Tooling must not depend on local build artefacts from the contracts repository.
This is required for reproducibility: a given dotns-sdk commit should be able to target a specific contracts release without requiring a developer to compile contracts locally or infer which artefact set is current.
ABIs are treated as generated inputs and must not be edited by hand. Updates must be performed by a script that fetches a specific contracts release tag and writes the results into shared/abi/ (or a dedicated ABI package if needed). Consumers across languages must read from the same canonical ABI bundle.
Install dependencies:
bun installRun typechecking and tests:
bun run typecheck
bun testBuild all workspace packages:
bun run buildIf you add repo-level scripts, keep them in scripts/ and make them callable via the root package.json scripts. Prefer deterministic inputs (explicit network, explicit release tag).
Rust crates live under crates/* and are built with Cargo. They should treat shared/* as the canonical input for ABIs and deployments and must not duplicate protocol rules in an incompatible way.
Typical commands:
cargo test --workspace
cargo build --workspaceAdd a new module only when you can state a clear boundary. I want a new package is not a boundary. This code is pure name parsing and should not depend on RPC clients is a boundary.
Rules of thumb:
- If it can be pure, make it pure. Put it in its own package or crate with minimal dependencies.
- Avoid circular dependencies. If two modules need shared types, extract the shared types.
- Do not import contract JSON from arbitrary paths. Use the canonical ABI bundle under
shared/*. - Keep APIs small and testable without a live chain.
- Create a directory under
packages/<name>. - Add a
package.jsonwith a scoped name (for example@dotns/<name>),type: "module", and standard scripts (build,test,typecheckif needed). - Add a local
tsconfig.jsonextending the roottsconfig.base.json. - Wire internal dependencies using
workspace:*. - Add at least one test that asserts behaviour at the boundary you are introducing.
Minimal package.json template:
{
"name": "@dotns/<name>",
"version": "0.1.0",
"type": "module",
"exports": {
".": "./dist/index.js"
},
"types": "./dist/index.d.ts",
"scripts": {
"build": "bunx tsc -p tsconfig.json",
"test": "bun test"
}
}Then run:
bun install
bun run build
bun test- Create
crates/<name>and add it to the Cargo workspace. - Keep dependencies minimal and avoid embedding network assumptions that belong in
shared/*. - Prefer consuming ABIs and deployments from
shared/*rather than copying them. - Add unit tests for parsing, encoding, and invariants.
This repository exists to reduce protocol drift, not to create a new source of drift.
Prefer:
- Deterministic behaviour over convenience defaults
- Explicit inputs over environment magic
- Small modules with tests over large kitchen sink helpers
- Changes accompanied by invariant-style tests, especially when touching name parsing, hashing, or transaction encoding
If a change alters how a name is interpreted or how a transaction is encoded, treat it like a consensus change for clients: document it, test it, and assume downstream consumers will break if it is ambiguous.