Skip to main content

What you will learn

  • The three ground rules that apply to every contribution
  • How to set up the development environment
  • Code organization by trust domain
  • Coding standards, naming conventions, and style rules
  • Testing requirements and security invariants
  • The pull request and commit process
  • Common gotchas that catch contributors

Ground rules

Three rules apply to every change:

Security First

The six security invariants listed below are non-negotiable. No PR that weakens them will be merged.

Test Everything

Every change must pass the existing test suite, and new features must include tests.

Respect Trust Domains

Every line of code belongs to exactly one of the three trust domains. Never blur these boundaries.
If you are unsure whether a proposed change fits the architecture, open an issue to discuss before writing code.

Getting started

1

Fork and clone

git clone https://github.com/<your-fork>/clavion.git
cd clavion
2

Install dependencies

Requires Node.js >= 20 and npm >= 9.
npm install
3

Build all packages

TypeScript project references, compiled in dependency order.
npm run build
4

Run all tests

Confirm a clean baseline.
npm test
5

Verify the server starts

npm start
curl http://localhost:3100/v1/health
# Expected: {"status":"ok","version":"0.1.0","uptime":...}
If any step fails, see the Dev Setup Guide for prerequisites (Anvil, Docker, environment variables).

Code organization

The repository is an npm-workspaces monorepo with packages organized by trust domain. The trust domain model is the single most important architectural concept in this project.

Domain A — Untrusted

Adapters and plugins that run agent code. No keys, no direct RPC access, no signing.
PackageDescription
@clavion/adapter-openclawOpenClaw thin skill wrappers
@clavion/adapter-mcpMCP server for Claude Desktop, Cursor, IDEs
@clavion/plugin-elizaElizaOS (ai16z) plugin with 5 actions
@clavion/adapter-telegramTelegram bot (agent + approval UI)

Domain B — Trusted

The secure core. Keys, policy enforcement, signing, audit logging, RPC access.
PackageDescription
@clavion/coreAPI server, transaction builders, approval flow
@clavion/signerEncrypted keystore and signing
@clavion/auditAppend-only audit trace (SQLite)
@clavion/policyPolicy engine and config validation
@clavion/preflightRisk scoring and simulation
@clavion/registrySkill manifest validation and registry
@clavion/typesShared interfaces, schemas, RPC types

Domain C — Limited Trust

Sandboxed execution. No key access, API-only communication with Core.
PackageDescription
@clavion/sandboxContainer isolation runner

Tooling

PackageDescription
@clavion/cliKey management CLI (import, generate, list)
@clavion/sdkSDK interface (stub, planned for v0.2)
Additional directories: tests/ (cross-package integration, security, and E2E tests), tools/ (fixture generation, hash utilities), examples/, docs/, docker/.

Coding standards

TypeScript

  • Strict mode is enforced (strict: true in the root tsconfig, plus noUncheckedIndexedAccess, noUnusedLocals, noUnusedParameters).
  • ESM with Node16 module resolution. All packages use "type": "module" and the Node16 module/moduleResolution settings.
  • additionalProperties: false on every JSON schema. No undocumented fields are allowed to pass validation.
  • viem is the preferred EVM library over ethers.

Naming conventions

ElementConventionExamples
Fileskebab-caserisk-scorer.ts, audit-trace-service.ts
ClassesPascalCasePolicyEngine, WalletService
Interfaces / TypesPascalCaseTxIntent, PolicyConfig, RpcClient
FunctionscamelCasebuildFromIntent, computeRiskScore
ConstantsUPPER_SNAKE_CASEMAX_SCORE, HIGH_SLIPPAGE_BPS

Code style

  • Prettier for formatting. Run npm run format:check before submitting.
  • ESLint for linting. Run npm run lint before submitting.
  • Follow the formatting conventions already present in the codebase. When in doubt, let Prettier decide.

Testing requirements

All pull requests must satisfy:
  1. npm test passes — this runs unit and integration tests.
  2. New features include unit tests. If you add a builder, service, route, or adapter method, add corresponding tests.
  3. Fund-affecting features include security tests. Changes to signing, policy enforcement, approval flow, or key management must include tests that verify Domain B integrity.
  4. Mock RPC factories implement all RpcClient methods, including readNativeBalance. Incomplete mocks cause runtime failures in unrelated tests.
  5. Test fixtures live in tools/fixtures/. When adding a new valid fixture, also add its pre-computed hash to hash-fixtures.ts (the canonicalization test iterates all entries).
CategoryCommandRequirements
Unitnpm run test:unitNone
Integrationnpm run test:integrationNone
Securitynpm run test:securityDocker (for sandbox tests)
E2Enpm run test:e2eAnvil + BASE_RPC_URL
Tests that require Docker or Anvil skip gracefully when those dependencies are unavailable.

Security rules (non-negotiable)

These six invariants are the foundation of the project’s security model. Every contributor must understand and uphold them.
These invariants are non-negotiable. Any PR that weakens any of them will not be merged.
  1. Private keys exist only in Domain B — never in Domain A (skills/adapters) or Domain C (sandbox).
  2. Every signature passes PolicyEngine + Preflight — there are no bypass paths.
  3. Skills have no direct RPC access — only ISCL Core contacts the blockchain.
  4. All fund-affecting operations use TxIntent v1 — no arbitrary calldata signing.
  5. All critical steps are audit logged — correlated by intentId.
  6. Approval tokens are single-use with TTL — no replay.

Additional design rules

  • New crypto logic goes in Domain B only, inside the appropriate package.
  • New skill-facing functionality must be exposed via the ISCL API, never through direct module access.
  • New external network calls must go through the RPC allowlist in Domain B.
  • Sandbox code belongs to Domain C: no key access, no unrestricted network.
  • Cross-domain communication always goes through the ISCL Core API (localhost HTTP).
If your change touches signing, key management, policy enforcement, or approval flow, flag it in the PR description. These changes receive extra review scrutiny.

Pull request process

1

Create a feature branch

git checkout -b feat/my-feature main
2

Write code and tests

Follow the coding standards and testing requirements above.
3

Run linting and formatting checks

npm run lint
npm run format:check
4

Run the full test suite

npm run build
npm test
All tests must pass. If you have Docker available, also run npm run test:security.
5

Submit a pull request

Include a clear description of what changed and why:
  • A summary of the change (what problem it solves or what feature it adds).
  • Which trust domain(s) the change touches.
  • How it was tested.
6

PR review

Security-sensitive changes (Domain B, key management, policy, approval) require extra scrutiny and may take longer to review.
7

Merge to main

After approval.

Commit style

The project follows Conventional Commits. Use the appropriate prefix for each commit.
PrefixPurposeExample
feat:New featurefeat: add swap_exact_out support for 1inch
fix:Bug fixfix: prevent approval token replay across intents
chore:Maintenance taskschore: remove old doc/ directory
build:Build system changesbuild: move Docker files to docker/
docs:Documentation onlydocs: add community files and restructure documentation
refactor:Code restructuring (no behavior change)refactor: extract @clavion/types package
test:Test additions or changestest: add Domain B integrity tests for replay protection
Keep commit messages concise. The first line should be under 72 characters. Use the body for additional context when needed.

Common gotchas

These are recurring pitfalls that have caught contributors before. Save yourself debugging time by reading them.
Business logic like deadline expiration must be enforced in code, not in the schema. The schema validates types, patterns, and required fields — it does not enforce runtime constraints.
When embedding the schema in a wrapper object, hoist $defs to the wrapper root. AJV cannot resolve $ref that points into a nested $defs.
The server uses strict: true, so query parameters arrive as strings. Use type: "string" with a pattern in route schemas, not type: "integer".
Without passing promptFn to buildApp(), the approval service falls through to readline and the test hangs indefinitely.
When you add a new valid fixture to tools/fixtures/valid-intents.ts, you must also add its canonical hash to tools/fixtures/hash-fixtures.ts. The canonicalization test iterates all entries.
The 1inch swap builder returns a Promise. All call sites must await it. Missing await will result in a [object Promise] being used as the transaction data.
Packages like ajv-formats and canonicalize do not have proper ESM exports. Always use the createRequire pattern:
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const addFormats = require("ajv-formats");

Questions and feedback

Next steps