Solidity Contract Development Skill
SkillMediaWrite production-quality Solidity contracts. Trigger phrases - write contract, implement function, add feature, add error, gas optimization, event design, contract architecture, or when working in src/ directories.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Solidity Contract Development Skill skill
What this skill tells your AI
The instructions your AI receives, as published by sablier-labs/evm-monorepo in .agents/skills/solidity-coding/SKILL.md and read by ahel’s review.
This skill provides expertise for writing production-quality Solidity contracts following industry best practices.
Bundled References
For detailed patterns and code examples, read these reference files:
| Reference | Content | When to Read |
|---|---|---|
.claude/skills/solidity-coding/references/coding-patterns.md | NatSpec, errors, modifiers, imports, section headers | Before writing any contract code |
.claude/skills/solidity-coding/references/security-practices.md | CEI, access control, EIP-7702, upgrades | When implementing state changes |
.claude/skills/solidity-coding/references/gas-optimization.md | EIP-1153, L2 patterns, Solady, storage caching | When optimizing contract efficiency |
.claude/skills/solidity-coding/references/event-design.md | Event design for The Graph and indexers | When adding events to contracts |
.claude/skills/solidity-coding/references/versioning-migration.md | Interface versioning, storage migration, deprecation | When releasing new contract versions |
.claude/skills/solidity-coding/references/sablier-conventions.md | Sablier-specific naming, patterns, and examples | When working in Sablier repos |
.claude/skills/solidity-coding/references/nft-descriptor.md | Onchain NFT metadata and SVG generation | When implementing tokenURI |
Note: Your repo's agent will provide repo-specific structure (package locations, inheritance hierarchies, etc.)
Quick Conventions Reference
Naming
| Element | Convention | Example |
|---|---|---|
| Contract / Library | PascalCase | TokenVault |
| Interface | I + PascalCase | ITokenVault |
| Function | camelCase | withdrawableAmountOf |
| Variable | camelCase | streamId |
| Constant | SCREAMING_SNAKE | MAX_SEGMENT_COUNT |
| Private/Internal | _underscore prefix | _balances |
| Error | {Contract}_{Description} | TokenVault_Overdraw |
Import Ordering
- Alphabetical order
- External imports first, local imports second
Function Ordering
- Constructor
- Receive/Fallback (if any)
- External functions (view/pure first, then state-changing)
- Public functions (view/pure first, then state-changing)
- Internal functions
- Private functions
Key Principles
- NatSpec: Use
@inheritdocin implementations; full docs live in interfaces - Errors: One specific error per failure mode; never use generic catch-all errors
- CEI Pattern: Always checks → effects → interactions
- SafeERC20: Always use
safeTransferandsafeTransferFrom - Timestamps: Use
uint40for all timestamps - Amounts: Use
uint128for token amounts
Contract Structure Pattern
Standard Directory Layout
src/
├── MainContract.sol # Entry point
├── abstracts/ # Inheritance chain (state, features, base contracts)
├── interfaces/ # Public APIs - NatSpec lives here (use @inheritdoc in impl)
├── libraries/ # Errors.sol, Helpers.sol, Math libraries
└── types/ # Structs, enums, namespace libraries
Inheritance Pattern
Inherit in alphabetical order:
contract TokenVault is Batch, ERC721, ITokenVault, VaultDynamic, VaultLinear { ... }
Writing New Contract Code
Contract Checklist
When writing a new contract or function:
- Correct license and pragma
- Imports ordered: external → internal → local
- Named imports only (use curly braces)
- Section comments for code organization
-
@inheritdocfor interface implementations - Specific errors for each failure mode
- Checks-effects-interactions ordering
- SafeERC20 for token transfers
-
uint40for timestamps,uint128for amounts - Storage packing considered for new structs
- Contract size under 24kb limit
Contract Size Limit
Contracts must stay under the 24kb bytecode limit. Verify with the optimized profile:
forge build --sizes
If a contract exceeds the limit:
- Extract logic into external libraries - Use
public/externallibrary functions instead ofinternal - Split into multiple contracts via inheritance
- Remove unused functions
- Use shorter error messages or custom errors
External Libraries Pattern
Use public library functions to reduce contract size (called via DELEGATECALL rather than inlined):
// Library with PUBLIC functions (not inlined, reduces contract size)
library VaultMath {
function calculateAmount(...) public pure returns (uint128) { ... }
}
library Helpers {
function validateParams(...) public view { ... }
}
// Main contract calls library functions (DELEGATECALL, not inlined)
contract TokenVault {
function _computeAmount(uint256 id) private view returns (uint128) {
return VaultMath.calculateAmount(...);
}
}
Key insight: internal library functions are inlined into the contract bytecode. public/external library
functions are called via DELEGATECALL, keeping bytecode smaller but costing slightly more gas per call.
Stack Too Deep
When you encounter "Stack Too Deep" errors, use an in-memory struct to bundle local variables:
/// @dev Needed to avoid Stack Too Deep.
struct ComputeVars {
address token;
string tokenSymbol;
uint128 depositedAmount;
string json;
ITokenVault vault;
string status;
}
function compute(uint256 id) external view returns (string memory result) {
ComputeVars memory vars;
vars.vault = ITokenVault(address(this));
vars.depositedAmount = vars.vault.getDepositedAmount(id);
vars.token = address(vars.vault.getToken(id));
// ... use vars.field instead of separate local variables
}
Place *Vars structs in types/DataTypes.sol if reused, or inline in the contract if function-specific.
Adding Errors
- Add error to package's
libraries/Errors.sol - Use naming:
{ContractName}_{ErrorDescription} - Add NatSpec:
/// @notice Thrown when... - Include relevant parameters for debugging
/// @notice Thrown when trying to withdraw more than available.
error TokenVault_Overdraw(uint256 id, uint128 amount, uint128 withdrawableAmount);
Adding Functions
- Add signature and full NatSpec to interface
- Implement in contract with
@inheritdoc - Place in correct section (external/public/internal/private)
- Order modifiers: visibility → mutability → override → custom
Common Patterns
Safe Token Transfers
using SafeERC20 for IERC20;
// Safe transfers handle non-standard ERC20s
token.safeTransfer(to, amount);
token.safeTransferFrom(from, to, amount);
Common Tasks
Add a view function
- Add to interface with full NatSpec (notice, dev, param, return)
- Implement with
@inheritdoc InterfaceName - Add appropriate modifiers (e.g.,
notNull(id)if accessing resource state)
Add a state-changing function
- Add to interface with NatSpec (include Notes and Requirements sections)
- Implement with
@inheritdoc - Add modifiers:
noDelegateCall,notNull(id), etc. - Follow CEI pattern
- Emit events after state changes
Add a new error
- Define in
libraries/Errors.solwith section comment - Add NatSpec:
/// @notice Thrown when... - Include debugging parameters
- Use in contract:
revert Errors.ContractName_ErrorDesc(...)
Add a new struct
- Define in
types/directory - Pack fields for gas efficiency (see
.claude/skills/solidity-coding/references/coding-patterns.md) - Add NatSpec for struct and each field
- Use
@paramfor each field in struct NatSpec
Example Invocations
Test this skill with these prompts:
- New function: "Add a
getStreamBalanceview function to the ISablierFlow interface" - Error handling: "Add a
Flow_InsufficientBalanceerror with debugging parameters" - Gas optimization: "Optimize the
_withdrawfunction using storage caching" - Contract structure: "Create the interface for a new
TokenVestingcontract with deposit, withdraw, and claim functions"
Signals
- GitHub stars
- 352
- Forks
- 82
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
solidity-coding-sablier-labs- Source
- github.com/sablier-labs/evm-monorepo