npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@interludelayer-sdk/cli

v0.1.6

Published

Stand up a local Interlude stack against your own Foundry project with one command.

Readme

@interludelayer-sdk/cli

Send us the bytecode. We deploy it, we pay, we run the node.

cd my-foundry-project
npx @interludelayer-sdk/cli init
npx @interludelayer-sdk/cli gen --contract YourApp
# import {YourAppInterludeSurface} from "./YourAppInterludeSurface.sol";
npx @interludelayer-sdk/cli check
npx @interludelayer-sdk/cli ship
npx @interludelayer-sdk/cli ship --region asia
npx @interludelayer-sdk/cli ship --region tokyo --name pongit

ship talks to https://control.interludelayer.xyz. Nothing to set. The CLI prints an app address and a node URL. --region us|ny|eu|asia|sa|tokyo|mumbai|africa sits the node on Fly metal in that city (California, New York, Paris, Singapore, São Paulo, Tokyo, Mumbai, Johannesburg). Omit it and we pick from where you called ship (Cloudflare country header on control) — same idea as MagicBlock putting the ER next to you. --region always wins. On a TTY ship asks for a short name so the machine is il-tokyo-pongit-<hex>; --name skips the prompt. One writer, so one region — two replicas would both try to publish the same batch. The first node takes a few minutes to come up; a 502 right after the command is the image building. Point the SDK at that URL, not at https://rpc.interludelayer.xyz (Room only).

The hub artifact and the Solidity a contract inherits (Delegatable, Types) are bundled, including bisect / proveStep / BisectGame. Publishing without a pnpm bundle after the hub ABI moves is how npx deploys last week's bytecode. init copies those sources into lib/interlude when they sit outside the project: Foundry will not follow a remapping into the npx cache.

dev is the laptop loop (anvil, a hub, a validator, a node on loopback). A built interlude-node is not bundled, so set INTERLUDE_NODE_BIN outside a checkout. You do not need that binary to ship.

init writes @interludelayer/contracts/=lib/interlude/ into remappings.txt if it is missing, even when nothing inherits Delegatable yet. That is the first hour: vendor, inherit, annotate, gen, init again, check, ship.

init compiles the project, finds the contracts that inherit Delegatable, and writes an interlude.toml. dev reads it and stands up everything a session needs: a base chain, the hub, a bonded validator with published terms, your contract, the delegation, and a node pointed at it. Then it watches, and checks each batch against the chain as it settles.

This exists because the answer to "how do I try this on my contract" used to be "read five shell scripts and write a sixth". None of that work is yours: the hub, the validator and the bond are the same every time.

Keeping your storage

Delegating state used to mean rewriting it. A balance became a Delegated.MapUint256Slot under a hash you chose by hand, and every balances[who] in the contract became BALANCE.get(who).

None of that is needed now. Mark what a node should hold, in place:

/// @custom:interlude global
mapping(address => uint256) internal balances;

then interlude gen --contract Purse writes PurseInterludeSurface.sol beside it — the slots read off solc's storage layout, and one _registerInterludeSurface() to call from your constructor. Inherit it and you are done; the mapping stays an ordinary mapping.

Two modes. global hands the variable over as one partition, which is what a shared book needs: one call can debit one seat and credit another, because both are in the same partition. per-key makes each key its own partition, so a node can hold room 42 while room 43 stays on Monad.

interlude check, and why it is not optional

The generated file asserts slot numbers. Insert a state variable above a delegated one and everything below it shifts — the file would go on naming slots that now belong to something else, and your app would hand a node the wrong storage. Nothing would complain until a commit did.

So the generated file carries a commitment to the layout it was read from, and interlude check recomputes it:

npx @interludelayer-sdk/cli check   # in CI

It exits non-zero and tells you what moved.

One sharp edge

A mapping indexed by a compile-time constant will not work, and the reason is worth understanding. The node learns which entry a hashed slot belongs to by watching the EVM derive it. With a constant key there is nothing to watch: solc can compute the slot itself, so the optimizer folds it into a literal and no hash happens at run time. The write is refused, with an error that says this.

Make the key immutable — or any value not known until deployment — and the derivation stays in the code. src/examples/Purse.sol in the Interlude repo does exactly that, and its test suite holds the property by checking the slot appears nowhere in the runtime bytecode.

What you still do by hand

The write guard. An accessor could refuse a base-chain write while the node held the state; balances[to] += amount cannot be intercepted, so whenNotDelegated(partition) goes on the functions that seed or repair delegated state:

function credit(address who, uint256 amount) external onlyOwner whenNotDelegated(Types.GLOBAL) {
    balances[who] += amount;
}

Forgetting it does not lose funds. The hub checks every diff's oldValue against what it holds, so a base-chain write behind the node's back makes the next commit fail rather than land — the session stalls until forceClose rather than settling something wrong.

What you write

The smallest useful config is four lines. $HUB is filled in with the address of the hub this command deploys, which does not exist when you write the file.

[app]
contract = "Counter"
args = ["$HUB"]

That gets you:

== ready
  base chain  http://127.0.0.1:8547    # anvil on this machine
  node        http://127.0.0.1:8555    # the local node, not the internet
  hub         0x5fbdb231…
  app         0xdc64a140…

Those two URLs are loopback: they only answer on the laptop that ran dev. The public Room node is https://rpc.interludelayer.xyz, against Monad testnet. The demo is at demo.interludelayer.xyz/room. Your app gets a different URL from ship.

The line that matters is the one after, once a batch lands:

ok batch 1: 3 transaction(s), root 0x880d08a370b1… recognised by Monad

That is not the node reporting on itself. The root it serves is compared against what the validator signed on chain, and the transactions behind it are handed to the hub's isBatchLog, which recomputes the root and says whether they are the ones the signature covers. A node that served a different list would fail this.

Configuration

Everything below has a default, and the defaults are the numbers the demo scripts in this repository converged on.

[app]
contract = "Chips"        # a Foundry artifact name; this command deploys it
args = ["$HUB", "1000"]   # constructor arguments, as strings
delegate = "all"          # "all", or a 32-byte key for one partition

# Calls to make after the app is deployed and before it is delegated. Anything that seeds
# delegated state belongs here: once a partition is handed over, the write guard refuses
# base-chain writes to it, so seeding afterwards fails — correctly.
[[setup]]
signature = "credit(address,uint256)"
args = ["0x90F79bf6EB2c4f870365E785982E1f101E93b906", "1000"]

[chain]
port = 8547
block_time = 0.3          # Monad's is 0.4

[node]
port = 8555
chain_id = 4242           # must differ from the base chain's, see below
commit_interval = 5       # seconds
data_dir = ".interlude/data"

[env]
file = "app/.env.local"   # merged, not overwritten
vars = { NEXT_PUBLIC_NODE = "$NODE_RPC", NEXT_PUBLIC_APP = "$APP" }

Placeholders: $HUB, $APP, $ADMIN, $VALIDATOR, $RESOLVER, $BASE_RPC, $NODE_RPC. Using one before the thing it names exists is an error rather than an empty string — $APP in the app's own constructor arguments, for instance.

chain_id under [node] has to differ from the base chain's. Delegatable.isEphemeral() compares the two, and if they match, every delegated write reverts. The command refuses to go further rather than letting you find that out one revert at a time.

When a constructor is not enough

Some apps need more bring-up than arguments can express. Point at a Foundry script instead:

[app]
script = "script/DeployChips.s.sol:DeployChips"
address_from = "Chips"
delegate = "all"

In this mode the script owns the whole bootstrap — including the hub — and this command reads the addresses back out of Foundry's broadcast file rather than deploying a second hub beside the one your script just made. The broadcast file rather than the console output, so you are not obliged to print addresses in a shape a regular expression expects.

Requirements

forge, cast and anvil on PATH (getfoundry.sh), and a node binary. In an Interlude checkout the binary is built for you on first run, which takes a minute; elsewhere, set INTERLUDE_NODE_BIN to one.

  • INTERLUDE_NODE_BIN — a built interlude-node. In a checkout the command builds it on first run. Outside one, this has to be set: the node is a Rust binary, not something npm can ship.
  • INTERLUDE_CONTRACTS_OUT — optional. The hub artifact is bundled in this package. Set this only to point at a different forge out/ you compiled yourself.
  • INTERLUDE_CONTRACTS — optional. Directory holding Delegatable.sol. The same sources are bundled in this package; set this to point at a checkout you are editing.

Constructor arguments are read from config as value types only — addresses, integers, booleans, strings and fixed bytes. A tuple or an array is expressible in TOML and would be guesswork to map onto an ABI, and guessing wrong means deploying a contract configured differently from what the file says. Use script for those.