iCKB Stack is the TypeScript SDK, liquidity bot and browser interface for iCKB, built on CCC.
- SDK: read account state, build a funded conversion, sign, send and wait.
- Node actors: run the bot, testnet stimulus generator or rate sampler.
- Bot policy: matching, rebalancing, funding and their tradeoffs.
- Interface: run the app and understand its wallet and conversion model.
- Maturity estimates: how the interface estimates when funds return.
- Test kit and scripts: test helpers and repository tooling.
- Working agreement: how to change and validate this tree.
Design rationale lives with the feature it explains: a reason comment for a branch or constant, or a section in its owning documentation for a choice spanning several functions. Follow the links above for both behavior and the reasons behind it.
sdk/ is the only published package, @ickb/sdk. It has one public entry point and depends at runtime only on @ckb-ccc/core. It contains no Node built-ins and never imports sdk/node, so integrators can use it in the browser.
sdk/node/, interface/ and testkit/ are private workspaces. The three Node entrypoints share preflight, configuration and logging under sdk/node/src/shared/; they never import each other. Development, tests and bot deployments run from TypeScript source. The SDK's build emits dist/ for publishing, not for local workspace execution.
The former library boundaries separated concepts rather than independent consumer contracts. One package removes the cost of maintaining several entry points and releases; its internal layering is a convention rather than a second dependency graph. There are no advanced or internal public entry points. The SDK layout follows the on-chain scripts and the conversion workflow.
Stack assumes user-owned cells have locks whose signatures bind the whole transaction, as standard sighash wallet flows do. An output lock does not execute when the output is created, so it cannot itself protect the recipient of that newly created output. Passing a raw ccc.Script is safe only when its lock provides the same input, output and recipient binding. Delegated-signature and OTX-style integrations must establish that separately.
Install the pinned pnpm 12.4.2 with npm; pnpm install then fetches the Node the lockfile pins (devEngines.runtime in package.json) into node_modules/node and runs every script on it; a host that runs the bot from source points its units at that binary. Inside the checkout run npm as pnpm exec npm ...: npm 11 refuses to run on a host Node that differs from the pin, and pnpm exec puts the pinned one first on PATH. From a plain checkout:
pnpm install --frozen-lockfile
CI=true pnpm checkCCC is an ordinary dependency resolved through the catalog in pnpm-workspace.yaml and the lockfile. No local fork, build step or workspace alias is required. The catalog range is also what the published SDK gives integrators, so an exact catalog pin would constrain them too.
The lockfile holds the CCC 1.23.0 set. CCC 1.23 made mol.union dynamic-size, so the order master pointer uses mol.fixedUnion, whose two variants are the same 36 bytes. The gate exercises core and the SDK paths; connector changes are only exercised by live wallet testing, so a CCC update that moves the connector is followed by one.
TypeScript stays on 6 until typescript-eslint supports 7; the reason sits with the hold in pnpm-workspace.yaml. Type checking already runs on the native preview.
pnpm 12 supplies the trusted-publishing support the previous pin lacked. Its lockfile format is not backward-compatible with pnpm 10. The release age, build-script permissions and advisory exceptions live with their reasons in pnpm-workspace.yaml.
pnpm check runs the full lint suite and interface build against installed dependencies; its result is a function of the tree. pnpm fresh depends on the date instead: the audit fails on a newly published advisory; pnpm outdated fails when a dependency has a newer release past the release age, majors included, and when a pinned action has any newer release, since pnpm applies no release age to actions; the Node check, scripts/tooling/pins.ts, fails when the pinned major has a newer security release that is at least three days old. CI first installs from the pinned lockfile, then runs the gate on the pinned Node; pull requests also run the freshness checks, release pushes do not, so a release cannot fail on an advisory or a version that appeared after the review. pnpm itself is pinned by packageManager and bumped by hand. Tooling explains the checks worth maintaining.
A push to the default branch releases a package only when its version differs from the previous commit. The reviewed version change is the release decision:
- A change to
sdk/package.jsonpublishes@ickb/sdkto npm through trusted publishing, with provenance and no registry token in the workflow. - A change to
interface/package.jsonbuilds the interface again and deploys it to GitHub Pages atickb.org.
Both jobs require the gate to pass, then build their package again in a fresh job where none of the gate's tooling runs; the SDK job installs only the SDK's declared dependencies, resolved from the lockfile by pnpm deploy. Other merges release nothing. If a release job fails, rerun that run's failed jobs, unless a later release of the same package has already run: the next merge compares with its own parent and releases nothing. The ruleset admits only merge commits of pull requests, whose first parent is the previous tip, so comparing with it covers the whole change. Tags, a manual trigger or a second confirmation environment would add another release step for the same maintainer without another reviewer. A mistaken publish still consumes a version number and needs a bump.
The rewrite's versioned packages start at 9000.0.1, above the old @ickb/sdk line at 1000.0.82 (9000.0.0 was never published: its release run failed before the settings below existed). The new API is not a patch of the old one, and keeping the package name avoids changing integrators' install lines. The high version preserves increasing version order; it is not a stability claim. A future lower-numbered line would need deliberate dist-tag management and would not satisfy a consumer's ^9000 range.
The publish job depends on the npm trusted publisher registered for @ickb/sdk: organization ickb, repository stack, workflow ci.yaml, environment npm, whose branch policy in this repository admits master only; the package requires two-factor authentication and disallows tokens, so no token can publish and this workflow is the only unattended path. The deploy job depends on this repository's Pages settings: source GitHub Actions, custom domain ickb.org. Renaming the workflow file or moving the repository breaks the release until those settings follow. The workflow owns the release mechanics.
Each actor runs one turn and prints JSON to stdout. The operator reads the outcome and decides the next action; systemd supplies continuous cadence when wanted. See runtime configuration and deployment for the units and key-file setup.
To exercise matching, run a stimulus turn, then a bot turn. Correlate the generator's committed order outpoints with the bot's matched orders and committed transaction; a generator draw can also be skipped or produce an order the bot cannot profitably fill. The journal guide names the preconditions for each observable case.
This source code, crafted with care by Phroi, is freely available on GitHub and released under the MIT License.