towns-protocol/towns/design-philosophy

Claude Code and agent skill by towns-protocol/towns

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Deep design principles for Towns Protocol smart contracts. Use when creating new facets, major refactoring, reviewing architecture decisions, or designing new abstractions.

Install

npx vskill@latest install towns-protocol/towns/design-philosophy

SKILL.md

Design Philosophy for Towns Protocol

When to Use This Skill

  • Creating new facets or contracts
  • Major refactoring work
  • Reviewing architectural decisions
  • Designing new abstractions or APIs
  • Evaluating trade-offs between approaches
  • Identifying and fixing design smells

Reference Examples

PatternExample Location
Modern Mod + Facetsrc/account/facets/hub/
Domain registrationsrc/domains/facets/registrar/

Foundational Philosophy: Designing for Simplicity

The primary objective in Towns Protocol development is to minimize complexity—anything related to the software structure that makes it hard to understand or modify. In a system as sophisticated as this (with Diamond Pattern upgrades, cross-chain validation, and role-based permissions), managing complexity is critical.

Core Principles
  • Strategic Programming: Invest time in design upfront rather than quick tactical fixes. The long-term structure matters more than short-term convenience. Before adding a new facet or feature, design the abstraction carefully.
  • Goal: Obviousness: Code should be obvious. Developers should quickly understand existing code and confidently make changes. If understanding a facet requires reading multiple files and tracing dependencies, the abstraction is leaking.
  • Eliminate Dependencies: Minimize coupling between modules. A change in one facet should rarely require changes in others. Use events and interfaces to decouple components.
  • Eliminate Obscurity: Name entities precisely. Document non-obvious behavior. If a function's purpose isn't clear from its signature and name, the design needs improvement.
  • Avoid Change Amplification: Small logical changes should not require modifying many files. If adding a permission type requires touching 10+ contracts, the architecture has high coupling.
  • Reduce Cognitive Load: Developers should not need extensive context to complete tasks. A facet's interface should be self-documenting. Storage layouts should be isolated.
  • No Unknown Unknowns: It should be obvious which code must be modified for a given change. Clear module boundaries and interfaces prevent hidden dependencies.
Design Philosophy for Solidity Modules

In this codebase, "modules" refers to contracts, facets, libraries, and functions. Apply these principles at all levels.

Module Design: Building Deep, Encapsulated Contracts

1. Strive for Deep Modules

Contracts and facets should be deep: they provide powerful functionality through a simple interface.

✅ Deep Module Example (Good):

solidity
// Simple interface hiding complex validation, fee charging, and registration
interface IL2Registrar {
function register(string calldata label, address owner) external;
function isAvailable(string calldata label) external view returns (bool);
}
// Complex implementation: validates caller is smart account, charges fees,
// validates label format, interacts with registry, sets resolver records
contract L2RegistrarFacet is IL2Registrar {
function register(
string calldata label,
address owner
) external nonReentrant {
L2RegistrarMod.onlySmartAccount();
L2RegistrarMod.validateLabel(label);
L2RegistrarMod.Layout storage $ = L2RegistrarMod.getStorage();
$.chargeFee(label);
$.register(label, owner);
}
}

❌ Shallow Module Example (Bad):

solidity
// Interface complexity matches implementation complexity (shallow)
interface IUserHelper {
function getUserAddress(uint256 userId) external view returns (address);
function setUserAddress(uint256 userId, address addr) external;
function deleteUserAddress(uint256 userId) external;
}
// Implementation is just simple CRUD with no abstraction

Guidelines for Deep Modules:

  • Simple Interface, Complex Implementation: External functions should be minimal and clear. Internal complexity should be hidden in the Mod library.
  • General Purpose Design: Design facets to be somewhat general-purpose. Separate specialized logic from general mechanisms.
  • Powerful Abstractions: A single function like register() can hide validation, fee charging, registry interaction, and resolver setup.
2. Information Hiding and Encapsulation

Information hiding is the most crucial technique for deep facets. In the Diamond Pattern, this is especially important because facets share a proxy but should minimize shared knowledge.

Prevent Information Leakage:

  • ❌ Leakage: If the storage layout details of one facet must be known by another facet, that's leakage.
  • ✅ Solution: Each facet has its own isolated storage slot via the Mod library. Facets communicate through well-defined interfaces.

Example of Information Hiding (Good):

solidity
// L2RegistrarMod hides HOW registration works
library L2RegistrarMod {
using CustomRevert for bytes4;
function register(
Layout storage $,
string calldata subdomain,
address owner
) internal {
// Get registry interface - caller doesn't need to know this
IL2Registry registry = IL2Registry($.registry);
// Compute hashes - caller doesn't need to understand namehashing
bytes32 domainHash = registry.baseDomainHash();
bytes32 subdomainHash = registry.encodeSubdomain(domainHash, subdomain);
// Register in L2 registry - caller doesn't know registry API
registry.createSubdomain(domainHash, subdomain, owner, new bytes[](0), "");
// Set resolver records - caller doesn't manage coin types
bytes memory addr = abi.encodePacked(owner);
AddrResolverFacet($.registry).setAddr(subdomainHash, $.coinType, addr);
AddrResolverFacet($.registry).setAddr(subdomainHash, 60, addr);
emit NameRegistered(subdomain, owner);
}
}
// Facet exposes simple interface - users call register() without knowing internals

Example of Information Leakage (Bad):

solidity
// Exposing internal structure forces all callers to know implementation details
function registerDomain(
bytes32 domainHash,
bytes32 subdomainHash,
uint256 coinType,
bytes memory encodedAddress
) external;
// Now external code must understand namehashing, coin types, address encoding
3. Avoid Temporal Decomposition

Do not structure code solely based on the order of operations. This often creates information leakage and tight coupling.

❌ Temporal Decomposition (Bad):

solidity
contract Step1ValidateCaller { ... }
contract Step2ChargeFee { ... }
contract Step3RegisterDomain { ... }
// Each step exposes intermediate state to the next

✅ Knowledge-Based Organization (Good):

solidity
library L2RegistrarMod {
// Contains ALL knowledge about domain registration
function register(
Layout storage $,
string calldata subdomain,
address owner
) internal {
// Internally: interact with registry, set resolvers, emit events
}
// Contains ALL knowledge about caller validation
function onlySmartAccount() internal view {
if (msg.sender.code.length == 0) {
L2Registrar__NotSmartAccount.selector.revertWith();
}
// Check interface support...
}
// Contains ALL knowledge about label validation
function validateLabel(string calldata label) internal pure {
if (!isValidLabel(label)) L2Registrar__InvalidLabel.selector.revertWith();
}
}
4. Different Layers, Different Abstractions

When tracing an operation through layers, the abstraction should change at each boundary.

Example of Proper Layering:

solidity
// Layer 1: External API (user-facing abstraction)
// User thinks: "I'm registering a domain name"
function register(string calldata label, address owner) external nonReentrant {
L2RegistrarMod.onlySmartAccount();
L2RegistrarMod.validateLabel(label);
L2RegistrarMod.Layout storage $ = L2RegistrarMod.getStorage();
$.chargeFee(label);
$.register(label, owner);
}
// Layer 2: Mod Library (business logic abstraction)
// System thinks: "I'm managing domain state and registry interactions"
function register(Layout storage $, string calldata subdomain, address owner) internal {
IL2Registry registry = IL2Registry($.registry);
bytes32 domainHash = registry.baseDomainHash();
// ...
}
// Layer 3: Registry Contract (storage abstraction)
// System thinks: "I'm managing ENS-compatible records"
function createSubdomain(bytes32 parent, string calldata label, address owner, ...) external;

Each layer has a distinct vocabulary and level of abstraction. External API uses business terms (register, label). Mod uses domain terms (subdomain, registry). Low-level uses ENS terms (namehash, resolver).

5. Pull Complexity Downward

When encountering unavoidable complexity, resolve it within the module rather than pushing it onto callers.

❌ Pushing Complexity Up (Bad):

solidity
// Caller must understand coin types, encoding, and resolver setup
function registerDomain(
string calldata label,
address owner,
uint256 coinType,
bytes calldata encodedOwner,
address resolverAddress
) external;

✅ Pulling Complexity Down (Good):

solidity
// Simple interface; complexity handled in Mod library
function register(string calldata label, address owner) external {
L2RegistrarMod.Layout storage $ = L2RegistrarMod.getStorage();
$.register(label, owner);
// Mod internally handles: coin type calculation, address encoding, resolver setup
}

Code Clarity: Naming, Comments, and Error Handling

1. Naming Conventions

Names are a form of abstraction and documentation. They create a mental model.

Precision and Image:

  • Names must be precise, unambiguous, and intuitive
  • The name should convey what the entity is and is not
  • Avoid vague names like manager, handler, helper, utils

Examples:

❌ Vague Names:

  • manager → Use L2RegistrarMod (handles domain registration logic)
  • check() → Use onlySmartAccount() (validates caller is smart account)
  • process() → Use chargeFee() (charges registration fee)
  • data → Use Layout (storage layout for the facet)

Consistency:

  • Use consistent terminology across the codebase
  • If you call it a "label" in one place, don't call it a "subdomain" in the interface
  • Follow the pattern: FeatureMod.sol, FeatureFacet.sol, IFeature.sol

Red Flag: If you struggle to find a simple, precise name, the entity likely has an unclear purpose or poor design.

2. Strategic Use of Comments

Comments capture knowledge that cannot be represented in code. They are fundamental to abstraction.

What to Comment:

  • The What (Abstraction): What does this function/contract provide at a high level?
  • The Why (Rationale): Why was this design chosen? What problem does it solve?
  • The Non-Obvious: What cannot be inferred from the code itself?

What NOT to Comment:

  • The How (Implementation): The code already shows how it works
  • Redundant Information: Don't repeat what's in the function name

Interface Documentation (NatSpec):

Every public/external function must have NatSpec that defines the abstraction:

solidity
/// @notice Registers a subdomain for a Towns smart account
/// @dev Validates caller is IModularAccount, charges fee, creates subdomain
/// @param label The subdomain label (e.g., "alice" for alice.towns.eth)
/// @param owner The address that will own the subdomain
function register(string calldata label, address owner) external;

Implementation Comments:

Use implementation comments sparingly for non-obvious logic:

solidity
// ENSIP-11: Maps EVM chainId to ENS coinType by setting MSB (bit 31).
// This avoids collisions with SLIP-44 native coin types and enables
// deterministic L2 address resolution. Formula: coinType = 0x80000000 | chainId
$.coinType = 0x80000000 | block.chainid;

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
66
Repository forks
62
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 18 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/towns-protocol/towns/design-philosophy/badge)](https://verified-skill.com/skills/towns-protocol/towns/design-philosophy)

Something wrong with this skill or its page? Tell us.