ade is a CLI tool for the Skill Exchange protocol on Base blockchain. It provides:
- Secure secret management via OS keychains
- DataEscrow smart contract interactions for selling/buying encrypted data
- Swarm decentralized storage integration
- API interactions for skills, bounties, and agents
src/
├── index.ts # CLI entry point, argument parsing, command dispatch
├── commands.ts # All command handlers (read, write, chain operations)
├── routing.ts # Argument parser and command routing logic
├── help.ts # Help text for all commands
├── api.ts # API client with EIP-191 signing
├── secrets.ts # Keychain abstraction interface
├── keychain.ts # OS-native keychain implementation
├── escrow-keys.ts # Escrow key storage helpers
├── swarm.ts # Swarm upload/download via Bee API
├── addresses.ts # Chain configs (Base, Base Sepolia)
├── errors.ts # CLIError class for consistent error handling
├── format.ts # JSON/human output formatting
├── schema.ts # API schema definition
├── abi/ # Contract ABIs
│ └── DataEscrow.ts
├── crypto/ # Encryption utilities
│ ├── escrow.ts # AES-256-GCM encrypt/decrypt for escrow
│ ├── fairdrop.ts # ECDH key exchange for buyer-encrypted keys
│ └── keystore.ts # Fairdrop account keystore encryption
└── utils/
├── events.ts # Event parsing from transaction logs
└── chain.ts # Shared chain transaction helpers
Commands are defined in src/commands.ts and return data objects. Formatting is handled by the caller in index.ts via output().
// Pattern: Commands return data, don't print directly
export async function someCommand(opts: Options, keychain: Keychain = defaultKeychain): Promise<Result> {
// ... implementation
return { field: value }
}Use executeContractTx() from src/utils/chain.ts for all contract writes:
const { hash, receipt } = await executeContractTx({
wallet, pub,
address: chainConfig.contracts.dataEscrow,
functionName: 'functionName',
args: [arg1, arg2],
chainConfig,
description: 'Human readable description',
})Use estimateAndValidateGas() which includes safety cap validation:
const { gasCost } = await estimateAndValidateGas({
pub,
address: chainConfig.contracts.dataEscrow,
functionName: 'functionName',
args: [arg1, arg2],
account: address,
value: amount, // optional
})Always use CLIError for user-facing errors:
throw new CLIError(
'ERR_CODE', // Error code (ERR_MISSING_KEY, ERR_NOT_FOUND, etc.)
'Main error message', // What went wrong
'Recovery hint' // How to fix it (optional)
)Commands accept an optional keychain parameter for testability:
export async function myCommand(opts: Opts, keychain: Keychain = defaultKeychain) {
const key = await keychain.get('SX_KEY')
// ...
}Chain operations require --yes flag in non-TTY mode:
requireConfirmation(opts) // Throws if !opts.yes && !TTY
await confirmAction('Confirm transaction?', opts) // Interactive promptbun test # Run all tests
bun test tests/sell.test.ts # Run specific test file
bun test --watch # Watch mode- Mock keychain - Use
tests/keychain/mock.tsfor isolated tests - Mock fetch - Override
globalThis.fetchfor API/RPC mocking - Restore in afterEach - Always restore globals after tests
import * as mockKeychain from "./keychain/mock";
beforeEach(() => {
mockKeychain.clear();
mockFetch = mock(() => Promise.resolve(new Response("{}")));
globalThis.fetch = mockFetch as unknown as typeof fetch;
});
afterEach(() => {
globalThis.fetch = originalFetch;
delete process.env.SX_KEY;
});Tests in tests/chain-view.test.ts hit real RPCs and may fail due to rate limits. These test read-only chain operations.
- Add handler in
src/commands.ts - Add to routing in
src/routing.ts(META_COMMANDS or resource actions) - Add dispatch in
src/index.ts(handleMeta or handleResource) - Add help text in
src/help.ts - Add tests in
tests/ - Update README.md
- Add config in
src/addresses.ts(CHAIN_BY_ID) - Add viem chain in
src/commands.ts(VIEM_CHAINS)
MAX_FILE_SIZE: 50MB - File size limit forade sellGAS_SAFETY_CAP: 0.01 ETH - Maximum gas cost before warningCHAIN_TIMEOUT_MS: 60 seconds - Transaction confirmation timeoutDEFAULT_KEY_WAIT_TIMEOUT: 86400 seconds (24h) - Key reveal polling timeoutDEFAULT_EXPIRY_DAYS: 7 days - Escrow expiration
| Key | Purpose |
|---|---|
SX_KEY |
Ethereum private key for signing |
SX_RPC |
RPC URL (default: mainnet.base.org) |
BEE_API |
Bee node URL (default: FDS gateway) |
BEE_STAMP |
Postage batch ID (auto-created if using own node) |
ESCROW_<id>_KEY |
Encryption key for escrow |
ESCROW_<id>_SALT |
Salt for key commitment |
ESCROW_<id>_SWARM |
Swarm reference |
ESCROW_<id>_CONTENT_HASH |
Content hash for verification |
FAIRDROP_ACCOUNTS |
JSON array of account subdomains |
FAIRDROP_KEYSTORE_<sub> |
Encrypted keystore for account |
FAIRDROP_ACTIVE |
Currently active account subdomain |
Three methods for setting secrets (agent-friendly):
# Method 1: Value as argument (best for automation)
ade set SX_RPC https://sepolia.base.org
ade set BEE_API http://localhost:1633 # Optional - uses FDS gateway by default
# Method 2: Pipe from stdin
echo "https://mainnet.base.org" | ade set SX_RPC
# Method 3: Interactive prompt (for sensitive values)
ade set SX_KEY
# Enter secret: <hidden input>Note: BEE_API is optional. Both sell and buy commands use the FDS public gateway
(https://gateway.fairdatasociety.org) by default. No postage stamp required!
The chain is auto-detected from the RPC URL's chainId response:
# Use Base Mainnet (default)
ade set SX_RPC https://mainnet.base.org
# Use Base Sepolia testnet
ade set SX_RPC https://sepolia.base.orgContracts are automatically selected based on chain (see src/addresses.ts).
ade sell --file ./data.csv --price 0.1 --yes # Encrypt, upload, create escrow
ade escrows commit-key 42 --yes # After buyer funds
ade escrows reveal-key 42 --yes # Reveal key to buyer (ECDH-encrypted if buyer has account)
ade escrows claim 42 --yes # Claim payment after 24hade account create buyer # Create Fairdrop account (one-time)
ade account unlock buyer # Unlock for session
ade buy 42 --output ./data.csv --yes # Fund, wait for key, download, decryptade respond <bounty-id> --file ./solution.zip --yes # Create escrow for bountyade account create <subdomain> # Create keypair, encrypted with password
ade account unlock <subdomain> # Decrypt and load into session
ade account lock # Clear session
ade account status # Show active account
ade account list # List all accounts
ade account export <subdomain> # Export keystore backup
ade account delete <subdomain> # Delete account- TypeScript strict mode
- No semicolons (handled by formatter)
- Use
console.error()for progress output (stdout is for data) - Prefer
constoverlet - Use explicit return types on exported functions
- Avoid over-engineering - keep solutions minimal
- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
- If something goes sideways, STOP and re-plan immediately - don't keep pushing
- Use plan mode for verification steps, not just building
- Write detailed specs upfront to reduce ambiguity
- Use subagents liberally to keep main context window clean
- Offload research, exploration, and parallel analysis to subagents
- For complex problems, throw more compute at it via subagents
- One task per subagent for focused execution
- After ANY correction from the user: update
tasks/lessons.mdwith the pattern - Write rules for yourself that prevent the same mistake
- Ruthlessly iterate on these lessons until mistake rate drops
- Review lessons at session start for relevant project
- Never mark a task complete without proving it works
- Diff behavior between main and your changes when relevant
- Ask yourself: "Would a staff engineer approve this?"
- Run tests, check logs, demonstrate correctness
- For non-trivial changes: pause and ask "is there a more elegant way?"
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
- Skip this for simple, obvious fixes - don't over-engineer
- Challenge your own work before presenting it
- When given a bug report: just fix it. Don't ask for hand-holding
- Point at logs, errors, failing tests - then resolve them
- Zero context switching required from the user
- Go fix failing CI tests without being told how
- Plan First: Write plan to
tasks/todo.mdwith checkable items - Verify Plan: Check in before starting implementation
- Track Progress: Mark items complete as you go
- Explain Changes: High-level summary at each step
- Document Results: Add review section to
tasks/todo.md - Capture Lessons: Update
tasks/lessons.mdafter corrections
- Simplicity First: Make every change as simple as possible. Impact minimal code.
- No Laziness: Find root causes. No temporary fixes. Senior developer standards.
- Minimal Impact: Changes should only touch what's necessary. Avoid introducing bugs.
This project lives inside a Datacore space. Session lifecycle commands are available:
/wrap-up— write session entry to team journal, commit and push/continue— resume from yesterday's continuation notes;--savepersists current work/standup— generate/post standup from recent team journals/today— daily briefing (incremental if already generated)
| Key | Value |
|---|---|
| Space | 3-fds |
| Journal | ~/Data/3-fds/journal/YYYY-MM-DD.md |
| Org | ~/Data/3-fds/org/next_actions.org |
When /wrap-up runs, use the team journal schema: ## @contributor narrative sections + ## Session Metadata YAML block.