Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@

"containerEnv": {
"DEVCONTAINER": "true",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
"DEVCONTAINERS_NO_TELEMETRY": "true",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
"CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY": "1",
"DO_NOT_TRACK": "1",
"NEXT_TELEMETRY_DISABLED": "1",
"NO_UPDATE_NOTIFIER": "1",
"npm_config_update_notifier": "false"
},

// Persisted volumes to speed installs and avoid repeated network requests
Expand All @@ -24,10 +30,12 @@
"postCreateCommand": {
// Enable corepack and fix workspace ownership for non-root user
"permissions": "sudo corepack enable && sudo chown -R node:node /home/node ${containerWorkspaceFolder}",
// Needed for anthropic.claude-code extension to work with a pnpm claude installation
"aliases": "echo \"alias claude='pnpm claude'\" >> ~/.bash_aliases",
// Needed for AI Coworker extension to work with a pnpm installation
"aliases": "echo \"alias claude='pnpm coworker'\" >> ~/.bash_aliases",
// Install GSD
"gsd": "pnpm dlx get-shit-done-cc@latest --claude --global --force-statusline"
"gsd": "pnpm dlx get-shit-done-cc@latest --claude --global --force-statusline",
// Clipboard support for wl-copy/wl-paste (used by CLAUDE.md)
"clipboard": "sudo apt-get update -qq && sudo apt-get install -y -qq wl-clipboard"
},

// Run each time the container starts. Ensures updated dependencies are installed inside the container user environment.
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@
# Reference repos
contracts/
whitepaper/

# AI tool config symlink
CLAUDE.md
2 changes: 1 addition & 1 deletion .planning/codebase/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## Important Context

**Tooling:**
- `gh` CLI is NOT available and must NOT be installed. Use `pnpm pr` to open PRs and `pnpm review` to fetch PR comments.
- `gh` CLI is NOT available and must NOT be installed. PR and review workflows are handled inline by the AI Coworker (see AGENTS.md).

**Legacy vs. New code:**
- `@ickb/lumos-utils@1.4.2` and `@ickb/v1-core@1.4.2` are **LEGACY and DEPRECATED** npm packages. The apps (`apps/bot`, `apps/tester`, `apps/interface`) still depend on them.
Expand Down
4 changes: 2 additions & 2 deletions .planning/codebase/STACK.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ The repo supports using a local development build of CCC for testing unpublished
**`ccc-dev/record.sh`:**
- Clones the CCC repo (`https://github.com/ckb-devrel/ccc.git`) into `./ccc-dev/ccc/`
- Accepts refs as args: branch names, PR numbers, or commit SHAs
- Merges specified refs onto a `wip` branch (uses Claude CLI for merge conflict resolution)
- Merges specified refs onto a `wip` branch (uses AI Coworker CLI for merge conflict resolution)
- Builds CCC locally: `pnpm build:prepare && pnpm build`
- Run via: `pnpm ccc:record` (default invocation: `bash ccc-dev/record.sh releases/next releases/udt`)
- The `ccc-dev/ccc/` directory is gitignored
- Skips if `ccc-dev/ccc/` already exists (remove it to redo setup)
- Aborts if `ccc-dev/ccc/` has pending work (any changes vs pinned commit, diverged HEAD, or untracked files)

**`.pnpmfile.cjs`:**
- A pnpm `readPackage` hook that auto-discovers all packages in `ccc-dev/ccc/packages/*/package.json`
Expand Down
16 changes: 6 additions & 10 deletions .planning/codebase/STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
│ │ ├── REFS # Pinned branch HEADs
│ │ └── resolutions/ # Merge conflict resolutions
│ ├── ccc/ # Gitignored: ephemeral CCC clone (auto-generated)
│ ├── record.sh # Records new pins with Claude conflict resolution
│ ├── record.sh # Records new pins with AI Coworker conflict resolution
│ ├── replay.sh # Deterministically rebuilds ccc/ from pins
│ └── tsc.mjs # TypeScript compilation script override
├── contracts/ # Reference: Rust on-chain contracts (git-ignored, clone via `pnpm reference`)
Expand Down Expand Up @@ -345,19 +345,15 @@
- System: Record/replay mechanism for deterministic, conflict-free builds
- `pins/`: Committed directory containing:
- `REFS`: Pinned branch HEADs for merging
- `resolutions/`: Serialized conflict resolutions with Claude aid
- `resolutions/`: Serialized conflict resolutions with AI Coworker aid
- `ccc/`: Generated from pins; auto-deleted and rebuilt on `pnpm install`
- Activation: `.pnpmfile.cjs` hook triggers `replay.sh` and overrides package resolution
- Commands:
- Record: `pnpm ccc:record releases/next releases/udt` (requires Claude CLI)
- Record: `pnpm ccc:record releases/next releases/udt` (requires AI Coworker CLI)
- Status: `pnpm ccc:status` (check for pending work in `ccc/`)
- Rebuild: `pnpm install` (automatic)
- Clean: `pnpm ccc:nuke && pnpm install`

**scripts/:**
- Purpose: Developer convenience scripts
- Files: `pr.sh` (GitHub PR creation), `review.sh` (fetch PR comments)
- Execution: Via `pnpm pr` and `pnpm review` shortcuts
- Committed: Yes
- Clean (re-replay): `pnpm ccc:clean && pnpm install` (guarded)
- Reset (published): `pnpm ccc:reset && pnpm install` (guarded)

**node_modules/:**
- Purpose: Installed npm/pnpm dependencies
Expand Down
37 changes: 37 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# AI Coworker Configuration

This file is the tool-agnostic agent config:

- Refer to yourself as "AI Coworker" in docs and comments, not by product or company name
- Never add AI tool attribution or branding to PR descriptions, commit messages, or code comments
- Do not install or use `gh` CLI
- **Routine Pre-PR Validation**: `pnpm check:full`, it wipes derived state and regenerates from scratch. If `ccc-dev/ccc/` has pending work, the wipe is skipped to prevent data loss — re-record or push CCC changes first for a clean validation
- **Open a PR**: Push the branch, then construct and present a GitHub compare URL
(`quick_pull=1`) to the user. Base branch is `master`. Prefill "title" (concise, under 70 chars) and "body" (markdown with ## Why and ## Changes sections)
- **Fetch PR review comments**: Use the GitHub REST API via curl. Fetch all three comment types (issue comments, reviews, and inline comments). Reviewers reply asynchronously — poll every minute until comments arrive
- **Copy to clipboard**:
```
head -c -1 <<'EOF' | wl-copy
content goes here
EOF
```

# CCC Local Development (ccc-dev/)

The `ccc-dev/` system uses a record/replay mechanism for deterministic builds of a local CCC fork:

- `ccc-dev/pins/` is **committed** to git (base SHAs, merge refs, conflict resolutions), regenerated by `pnpm ccc:record`
- `ccc-dev/ccc/` is **not in git** — it is rebuilt from pins on `pnpm install`
- The developer may have **pending work** in `ccc-dev/ccc/`. Run `pnpm ccc:status` (exit 0 = safe to wipe, exit 1 = has custom work) before any operation that would destroy it. `pnpm ccc:record`, `pnpm ccc:clean`, and `pnpm ccc:reset` already guard against this automatically
- `.pnpmfile.cjs` silently rewrites all `@ckb-ccc/*` dependencies to `workspace:*` when `ccc-dev/ccc/` exists. Local CCC packages override published ones without any visible change in package.json files
- `pnpm install` has a side effect: if `ccc-dev/pins/REFS` exists but `ccc-dev/ccc/` does not, it automatically runs `ccc-dev/replay.sh` to rebuild CCC from pins. This is intentional
- `ccc-dev/patch.sh` rewrites CCC package exports to point at `.ts` source instead of `.d.ts`, then creates a deterministic git commit (fixed author/date) so record and replay produce the same `pins/HEAD` hash. This is why imports from `@ckb-ccc/*` resolve to TypeScript source files inside `node_modules` — it is not a bug
- `ccc-dev/tsc.mjs` is a custom `tsc` wrapper that filters out diagnostics originating from `ccc-dev/ccc/`. CCC source does not satisfy this repo's strict tsconfig (`verbatimModuleSyntax`, `noUncheckedIndexedAccess`, `noImplicitOverride`), so the wrapper suppresses those errors while still reporting errors in stack source

# Reference Repos

`contracts/` and `whitepaper/` (cloned via `pnpm reference`) are made **read-only** with `chmod -R a-w`. To refresh, delete the directory and re-run `pnpm reference`

# Versioning

All packages use version `1001.0.0` (Epoch Semantic Versioning), managed by changesets (`pnpm changeset`)
46 changes: 25 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ This monorepo is developing the **new generation** of iCKB libraries, replacing

**New packages** (under `packages/`, built on CCC):

| Package | Purpose | Status |
|---|---|---|
| Package | Purpose | Status |
| ------------- | -------------------------------------------------------------------------- | ------------------ |
| `@ickb/utils` | Blockchain primitives, transaction helpers, epoch arithmetic, UDT handling | Active development |
| `@ickb/dao` | Nervos DAO abstraction layer | Active development |
| `@ickb/order` | Limit order cell management | Active development |
| `@ickb/core` | iCKB core protocol logic (deposits, receipts, owned owner) | Active development |
| `@ickb/sdk` | High-level SDK composing all packages | Active development |
| `@ickb/dao` | Nervos DAO abstraction layer | Active development |
| `@ickb/order` | Limit order cell management | Active development |
| `@ickb/core` | iCKB core protocol logic (deposits, receipts, owned owner) | Active development |
| `@ickb/sdk` | High-level SDK composing all packages | Active development |

**Apps migration status:**

| App | Purpose | Stack |
|---|---|---|
| `apps/faucet` | Testnet CKB distribution | **Migrated** to new packages + CCC |
| `apps/sampler` | iCKB exchange rate sampling | **Migrated** to new packages + CCC |
| `apps/bot` | Automated order matching | Legacy (`@ickb/v1-core` + Lumos) |
| `apps/tester` | Order creation simulator | Legacy (`@ickb/v1-core` + Lumos) |
| `apps/interface` | React web UI | Legacy (`@ickb/v1-core` + Lumos) |
| App | Purpose | Stack |
| ---------------- | --------------------------- | ---------------------------------- |
| `apps/faucet` | Testnet CKB distribution | **Migrated** to new packages + CCC |
| `apps/sampler` | iCKB exchange rate sampling | **Migrated** to new packages + CCC |
| `apps/bot` | Automated order matching | Legacy (`@ickb/v1-core` + Lumos) |
| `apps/tester` | Order creation simulator | Legacy (`@ickb/v1-core` + Lumos) |
| `apps/interface` | React web UI | Legacy (`@ickb/v1-core` + Lumos) |

**Key upstream contributions:** UDT and Epoch support were contributed to CCC upstream and have been merged. Some local utilities may overlap with features now available natively in CCC.

Expand All @@ -36,6 +36,7 @@ graph TD;
C["@ickb/dao"] --> A;
C --> B;
D["@ickb/core"] --> A;
D --> B;
D --> C;
E["@ickb/order"] --> A;
E --> B;
Expand All @@ -58,10 +59,10 @@ graph TD;
When `ccc-dev/pins/REFS` is committed, `pnpm install` automatically sets up the CCC local development environment on first run (by replaying pinned merges via `ccc-dev/replay.sh`). No manual setup step is needed — just clone and install:

```bash
git clone <repo-url> && cd stack && pnpm install
git clone git@github.com:ickb/stack.git && cd stack && pnpm install
```

To redo the setup from scratch: `rm -rf ccc-dev/ccc && pnpm install`.
To redo the setup from scratch: `pnpm ccc:clean && pnpm install`.

See [ccc-dev/README.md](ccc-dev/README.md) for recording new pins, developing CCC PRs, and the full workflow.

Expand All @@ -80,12 +81,15 @@ This clones two repos into the project root (both are git-ignored and made read-

## Developer Scripts

| Command | Description |
|---|---|
| `pnpm pr` | Open a GitHub PR creation page for the current branch. Uses Claude to auto-generate title and body when available, falls back to branch name and commit log. |
| `pnpm review` | Fetch and display PR review comments from GitHub for the current branch (or `pnpm review -- --pr <number>` for a specific PR). |

> **Note:** `gh` CLI is not available in this environment. Use `pnpm pr` and `pnpm review` instead.
| Command | Description |
| ------------------- | ------------------------------------------------------------------------------------- |
| `pnpm coworker` | Launch an interactive AI Coworker session (full autonomy, opus model). |
| `pnpm coworker:ask` | One-shot AI query for scripting (sonnet model, stateless). Used by `pnpm ccc:record`. |
| `pnpm ccc:status` | Check if CCC clone matches pinned state. Exit 0 = safe to wipe. |
| `pnpm ccc:record` | Record CCC pins (clone, merge refs, build). Guarded against pending work. |
| `pnpm ccc:clean` | Remove CCC clone, keep pins (guarded). Re-replay on next `pnpm install`. |
| `pnpm ccc:reset` | Remove CCC clone and pins (guarded). Restores published CCC packages. |
| `pnpm check:full` | Wipe derived state and validate from scratch. Skips wipe if CCC has pending work. |

## Epoch Semantic Versioning

Expand Down
18 changes: 10 additions & 8 deletions apps/bot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,16 @@ import type { Cell, Header, Transaction } from "@ckb-lumos/base";
async function main(): Promise<void> {
const { CHAIN, RPC_URL, BOT_PRIVATE_KEY, BOT_SLEEP_INTERVAL } = process.env;
if (!CHAIN) {
throw Error("Invalid env CHAIN: Empty");
throw new Error("Invalid env CHAIN: Empty");
}
if (!isChain(CHAIN)) {
throw Error("Invalid env CHAIN: " + CHAIN);
throw new Error("Invalid env CHAIN: " + CHAIN);
}
if (!BOT_PRIVATE_KEY) {
throw Error("Empty env BOT_PRIVATE_KEY");
throw new Error("Empty env BOT_PRIVATE_KEY");
}
if (!BOT_SLEEP_INTERVAL || Number(BOT_SLEEP_INTERVAL) < 1) {
throw Error("Invalid env BOT_SLEEP_INTERVAL");
throw new Error("Invalid env BOT_SLEEP_INTERVAL");
}

const chainConfig = await chainConfigFrom(
Expand All @@ -89,7 +89,9 @@ async function main(): Promise<void> {
const sleepInterval = Number(BOT_SLEEP_INTERVAL) * 1000;

for (;;) {
await new Promise((r) => setTimeout(r, 2 * Math.random() * sleepInterval));
await new Promise((r) => {
setTimeout(r, 2 * Math.random() * sleepInterval);
});
// console.log();

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
Expand Down Expand Up @@ -188,7 +190,7 @@ async function main(): Promise<void> {
);

const gain =
i == 0 && j == 0
i === 0 && j === 0
? 0n
: !isPopulated(tx)
? negInf
Expand Down Expand Up @@ -762,7 +764,7 @@ async function getTxsOutputs(
)) {
const txHash = tx.hash;
if (!txHash) {
throw Error("Empty tx hash");
throw new Error("Empty tx hash");
}
result.set(
txHash,
Expand Down Expand Up @@ -877,7 +879,7 @@ function secp256k1Blake160(
tx = prepareSigningEntries(tx, { config });
const message = tx.get("signingEntries").get(0)?.message;
if (!message) {
throw Error("Empty message to sign");
throw new Error("Empty message to sign");
}
const sig = key.signRecoverable(message, privateKey);

Expand Down
4 changes: 3 additions & 1 deletion apps/faucet/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export async function main(): Promise<void> {
const capacityManager = CapacityManager.withEmptyData();

for (;;) {
await new Promise((r) => setTimeout(r, 120000));
await new Promise((r) => {
setTimeout(r, 120000);
});
console.log();

const executionLog: {
Expand Down
6 changes: 3 additions & 3 deletions apps/sampler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export async function main(): Promise<void> {
// Fetch header for the found block number and log it.
const header = await client.getHeaderByNumber(blockNumber);
if (!header) {
throw Error("Header not found");
throw new Error("Header not found");
}

logRow(header, note);
Expand Down Expand Up @@ -156,8 +156,8 @@ function logRow(header: ccc.ClientBlockHeader, note: string): void {
* @public
*/
export function samples(startMs: bigint, endMs: bigint, n: number): Date[] {
if (endMs < startMs) throw Error("endMs must be bigger than startMs");
if (n < 1) throw Error("n must be a positive number");
if (endMs < startMs) throw new Error("endMs must be bigger than startMs");
if (n < 1) throw new Error("n must be a positive number");

// Convert bigints (ms) to Dates for year extraction.
const start = new Date(Number(startMs));
Expand Down
22 changes: 12 additions & 10 deletions apps/tester/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,16 @@ async function main(): Promise<void> {
const { CHAIN, RPC_URL, TESTER_PRIVATE_KEY, TESTER_SLEEP_INTERVAL } =
process.env;
if (!CHAIN) {
throw Error("Invalid env CHAIN: Empty");
throw new Error("Invalid env CHAIN: Empty");
}
if (!isChain(CHAIN)) {
throw Error("Invalid env CHAIN: " + CHAIN);
throw new Error("Invalid env CHAIN: " + CHAIN);
}
if (!TESTER_PRIVATE_KEY) {
throw Error("Empty env TESTER_PRIVATE_KEY");
throw new Error("Empty env TESTER_PRIVATE_KEY");
}
if (!TESTER_SLEEP_INTERVAL || Number(TESTER_SLEEP_INTERVAL) < 1) {
throw Error("Invalid env TESTER_SLEEP_INTERVAL");
throw new Error("Invalid env TESTER_SLEEP_INTERVAL");
}

const chainConfig = await chainConfigFrom(
Expand All @@ -74,7 +74,9 @@ async function main(): Promise<void> {
const sleepInterval = Number(TESTER_SLEEP_INTERVAL) * 1000;

for (;;) {
await new Promise((r) => setTimeout(r, 2 * Math.random() * sleepInterval));
await new Promise((r) => {
setTimeout(r, 2 * Math.random() * sleepInterval);
});
console.log();

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
Expand Down Expand Up @@ -195,15 +197,15 @@ async function main(): Promise<void> {
);

if (freeIckbUdt < 0n) {
throw Error("Negative iCKB after the tx");
throw new Error("Negative iCKB after the tx");
}
if (isCkb2Udt) {
if (freeCkb < 1000n * CKB) {
throw Error("Not enough CKB, less than 1000 CKB after the tx");
throw new Error("Not enough CKB, less than 1000 CKB after the tx");
}
} else {
if (freeCkb < 0n) {
throw Error("Not enough CKB to execute the transaction");
throw new Error("Not enough CKB to execute the transaction");
}
}

Expand Down Expand Up @@ -387,7 +389,7 @@ async function getTxsOutputs(
)) {
const txHash = tx.hash;
if (!txHash) {
throw Error("Empty tx hash");
throw new Error("Empty tx hash");
}
result.set(
txHash,
Expand Down Expand Up @@ -449,7 +451,7 @@ function secp256k1Blake160(
tx = prepareSigningEntries(tx, { config });
const message = tx.get("signingEntries").get(0)?.message;
if (!message) {
throw Error("Empty message to sign");
throw new Error("Empty message to sign");
}
const sig = key.signRecoverable(message, privateKey);

Expand Down
Loading