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

@kolny/sdk

v0.1.2

Published

TypeScript SDK for the KOLNY colony program on Solana: PDA derivation, instruction builders and account decoding for registering and operating a forager agent.

Readme

@kolny/sdk

TypeScript access to the KOLNY colony program on Solana: address derivation, instruction construction and account decoding, for a developer registering their own agent as a forager.

What KOLNY is

KOLNY is an autonomous agent fund on Solana. Capital is deposited once into a central vault, then spread across many independent agents called foragers, each trading from its own isolated sub-account. What a forager actually realized -- closed and settled trades, not a backtest and not a mark on an open position -- becomes its pheromone score, and pheromone decides how much capital flows down that trail in the next epoch. Pheromone evaporates every epoch, so a trail that stops earning fades and the capital behind it returns to the vault. No human picks the winning strategy.

That is a description of the mechanism, not a claim about returns. Autonomous agent trading loses money, and the protocol is built around containing that rather than denying it.

Install

npm i @kolny/sdk @solana/web3.js

Node 20 or newer. ESM and CommonJS are both published, with TypeScript declarations for each; see Package format. @solana/web3.js is already a direct dependency of the SDK, so it resolves either way under npm's flat layout, but every example below imports Connection from it directly, so name it explicitly.

That is the whole install. The program interface ships inside the package as @kolny/sdk/idl, so there is nothing to download and no way to end up running one release of the SDK against a different release of the interface. Where that copy comes from, and the two other ways to supply an IDL, are in Where the IDL comes from.

Before anything else

Nothing here sends a transaction. Builders return TransactionInstruction. Signing, simulating and broadcasting stay with the caller, because the wallet does.

The program is live on devnet only. 7whkmFfDcTyoJgf7jFGFmKNFMQn8NoreHnh2wZ9nWbsk is deployed and executable there and all seven colony accounts exist, so the reads below return real state. It is an unaudited test deployment, not production. There is no mainnet deployment and no audit. On mainnet-beta the same address holds nothing, and a read there raises ProgramNotFoundOnChainError instead of reporting an empty colony.

Quickstart

After the one command above, this file runs as written. The examples are ESM and use top-level await, so the file needs to be .mjs, or the project needs "type": "module"; from CommonJS, require("@kolny/sdk") gives the same exports and the awaits go inside an async function.

import { createKolnyClient } from "@kolny/sdk";
import { colonyIdl } from "@kolny/sdk/idl";
import { Connection } from "@solana/web3.js";

const client = createKolnyClient({
  idl: colonyIdl,
  connection: new Connection("https://api.devnet.solana.com", "confirmed"),
});

client.programIdCheck;
// offline: only says the address is not the placeholder
// { usable: true,
//   programId: "7whkmFfDcTyoJgf7jFGFmKNFMQn8NoreHnh2wZ9nWbsk",
//   reason: "program id is a real address, which has not been checked on
//   chain" }

await client.fetchProgramStatus();
// on chain: getAccountInfo plus the executable flag
// { deployed: true, exists: true, executable: true,
//   programId: "7whkmFfDcTyoJgf7jFGFmKNFMQn8NoreHnh2wZ9nWbsk",
//   cluster: "https://api.devnet.solana.com",
//   reason: "the program account exists and is executable" }

colonyIdl is the interface this release was compiled against: the Anchor build output as it stood when the tarball was made, embedded as a module rather than a file, so it loads identically under ESM, CommonJS and a browser bundler and needs neither a network call nor a filesystem. Reading an IDL off disk is still available through @kolny/sdk/idl-node, and is what a clone of the protocol repository wants, but from an installed package its no-argument form finds no Anchor build output above it and throws IdlNotBuiltError. The three ways in are laid out in Where the IDL comes from.

The same code with a mainnet-beta Connection, where the program is not deployed. Only the second result moves:

client.programIdCheck;
// { usable: true,
//   programId: "7whkmFfDcTyoJgf7jFGFmKNFMQn8NoreHnh2wZ9nWbsk",
//   reason: "program id is a real address, which has not been checked on
//   chain" }
// identical to devnet, because it never looked at a chain

await client.fetchProgramStatus();
// { deployed: false, exists: false, executable: false,
//   programId: "7whkmFfDcTyoJgf7jFGFmKNFMQn8NoreHnh2wZ9nWbsk",
//   cluster: "https://api.mainnet-beta.solana.com",
//   reason: "no account exists at the program id on this cluster, so the
//   program is not deployed here" }

Deployment is a fact about the chain

Two checks, and only one of them may use the word "deployed".

programIdCheck is offline. It says the address is not the known placeholder, which is a property of a string, and its own reason says it has not been checked on chain. fetchProgramStatus() is the on-chain lookup, and it requires both that the account exists and that it is executable, because an address can hold an ordinary account.

The two blocks above are the whole argument, and they are real output rather than an illustration. One program id, one build of this SDK, two endpoints: programIdCheck is byte-for-byte the same on both, and deployed is true on one and false on the other. A check that cannot tell those two clusters apart has no business using the word.

The distinction is not academic. An earlier version called the offline check deployment.deployed, and on a cluster the program had never reached, a caller was told the program existed and the missing colony accounts were an initialization problem. That sent the reader to run an initialization that could not work, and ruled out the real cause, which was a cluster mix-up. Every read helper now verifies the program on chain first and throws ProgramNotFoundOnChainError if it is not there.

ProgramNotDeployedError is the offline half of the same guard: if an IDL still carrying Anchor's example key is loaded, or that key is passed as programId, every operation implying a live program refuses before a round trip rather than computing addresses that match nothing.

Mainnet is not deployed, and deploying it changes every address

There is no mainnet program. Devnet is a test deployment: unaudited, holding test tokens, and reset-able without notice. Nothing on it should be treated as production, and no figure read from it describes a real fund.

A mainnet deployment mints a new program keypair, so the mainnet program id will not be this one. Every PDA in the table below derives from the program id, so every colony address changes with it. That failure is the quiet kind: a devnet address pasted into mainnet code is still a valid address, it just names an account that will never exist.

So derive, never hard-code. createKolnyClient takes the id from the IDL and colonyAddresses(client.programId) derives the rest, which makes a rebuilt IDL the only thing that has to change. An address literal copied out of a devnet session is the one thing in this SDK that a mainnet deployment silently breaks.

Addresses

The seed table is transcribed from packages/anchor-program/README.md. The program, the indexer, the front end and this SDK must derive identically or every address silently diverges: accounts read back as missing, and an initialization would create a second, unreachable set.

import { colonyAddresses, foragerStatePda, foragerVaultPda } from "@kolny/sdk";
import { PublicKey } from "@solana/web3.js";

const { config, brood, cache, trailBoard, vaultBase } = colonyAddresses(client.programId);
config.address.toBase58(); // "64abUz1zEsF8USkGQcpHxJRjWAxJH4jUaKmM5uKun173"
config.bump; // 247

const operator = new PublicKey("11111111111111111111111111111112");
const record = foragerStatePda(client.programId, operator, 42n);
const subAccount = foragerVaultPda(client.programId, record.address); // from the record

Every derivation returns { address, bump }, never a bare PublicKey. Calling .toBase58() on the returned object is a TypeError; the field is .address. The bump comes back with it because an instruction that re-derives the same PDA on chain needs it, and recomputing it at the call site is how the two sides drift apart.

| PDA | Seeds | |---|---| | colonyConfigPda | [b"colony"] | | broodVaultStatePda | [b"brood"] | | riskCacheStatePda | [b"cache"] | | trailBoardPda | [b"trail_board"] | | vaultBasePda | [b"brood_vault"] | | cacheVaultPda | [b"cache_vault"] | | incineratorVaultPda | [b"incinerator"] | | foragerStatePda | [b"forager", operator, forager_id_le] | | foragerVaultPda | [b"forager_vault", forager_state] | | depositorPositionPda | [b"position", depositor] | | redemptionRequestPda | [b"redeem", depositor, request_id_le] |

colonyAddresses returns the seven singletons at once. Against the devnet program id, that is:

config            64abUz1zEsF8USkGQcpHxJRjWAxJH4jUaKmM5uKun173   bump 247
brood             HV2A82xp6SyuFqaCSVyC4zFhW8qQTAr5pSz468WrTkoV   bump 253
cache             44Ci19oaaJtfJ3UyoBjpGhyjTykN6iiUyWUqHeo1PEQB   bump 254
trailBoard        AAyhHjRrRc47vKNRpRt3voUwwGyQK3mT153qvjamYTXr   bump 250
vaultBase         4BWECNHbvu6Rr8w9xFdjttqomzWxinhGAGWzZYaSSwdi   bump 255
cacheVault        5M1chg5bn7EAta2RMakEiTmyXoCvVrgDzotrjLTsLBQb   bump 255
incineratorVault  4yGZkXUHTRw8BH3yKAL1o4B7G6fyVrHvsTCg5PUT5Zw4   bump 255

Those are the devnet addresses and they are printed here as output, not as constants to copy. A mainnet deployment changes every line of that block, which is the whole point of the section above.

Numeric seeds are eight bytes, little-endian. u64Seed is the one place that is decided; it throws on a non-integer or out-of-range value rather than truncating, because a truncated seed produces a perfectly valid address that matches nothing. test/pda.test.ts transcribes the table a second time by hand and derives with findProgramAddressSync directly, so the evidence is two independent transcriptions agreeing rather than a helper checked against itself. It also asserts that a big-endian id, and the one-byte id the project guide records as a past mistake, both produce different addresses.

Where the IDL comes from

Three ways in. Which one is right depends on where the code is running, and only the first needs nothing but npm i.

  • Installed from npm: @kolny/sdk/idl exports the interface the release was built against. This is the form used everywhere above.

    import { colonyIdl } from "@kolny/sdk/idl";
    const { colonyIdl } = require("@kolny/sdk/idl");

    A named export in both formats, and no default export, so the CommonJS line above cannot silently hand you a module namespace where an IDL was meant.

  • Inside a clone of the protocol repository: loadIdlFromDisk() with no argument reads packages/anchor-program/target/idl/kolny_colony.json, found by walking up from the working directory, so your own anchor build is picked up with no second step -- which is what you want while changing the program, and what the bundled copy cannot give you. There is no such build output above an installed package, so from node_modules the walk finds nothing and the call throws IdlNotBuiltError. defaultIdlPath() returns the path it found, or undefined, if you want to branch on which situation you are in.

  • Some other IDL: pass it. createKolnyClient({ idl }) takes any object and loadIdlFromDisk(path) reads one from a path you name, which covers a fork, a pinned older interface, or the copy tracking main in the repository:

    curl -sLO https://raw.githubusercontent.com/kolny-labs/kolny/main/idl/kolny_colony.json
    const idl = loadIdlFromDisk("./kolny_colony.json");

    Note what that download is: the head of main, which is not necessarily the release you installed. Reach for it when the difference is the point.

  • Browser: import the main entry and give it either @kolny/sdk/idl or an IDL you fetched yourself. Neither touches the filesystem. @kolny/sdk/idl-node is the only entry that uses fs, and it is a separate one for exactly that reason.

assertColonyIdl checks the program name, address and shape on every path including the bundled one, so the wrong interface is an error naming the mismatch rather than garbage downstream.

Why the bundled copy is not the drift it looks like

The case against vendoring an IDL is real and this package still makes it: a snapshot checked into a source tree falls behind the program the moment either side moves, and a drifted IDL is worse than a missing one because the wrong discriminators keep decoding into plausible values. Nothing is checked in. The module behind @kolny/sdk/idl is generated from the Anchor build output before every compile, typecheck and test run, and never committed, so inside the repository that build output remains the only source of truth.

What ships is a different kind of thing. A published tarball is already an immutable snapshot of one moment, so freezing that moment's interface beside the modules compiled against it is version pinning, not drift: @kolny/[email protected] carries the 0.1.1 interface and cannot be handed a newer one by accident. Three checks keep that honest.

  • The generator refuses to run without the Anchor output and puts it through assertColonyIdl before embedding it, so a build cannot quietly produce a package whose interface is missing or wrong.
  • createKolnyClient runs assertColonyIdl again at construction, on the bundled IDL and a supplied one alike.
  • An address inside an IDL proves nothing about a chain, so fetchProgramStatus() looks the program up on the cluster you actually connected to and requires it to be executable.

The mismatch that a download step invites -- one release of the SDK paired with whatever interface happened to be at that URL -- is the one thing bundling removes outright.

Instructions

Account order and the signer/writable flags come from the IDL, never from lists written out here. A hard-coded order is a second source of truth that drifts the first time an account is inserted into a context, and the on-chain failure is a constraint error that says nothing useful.

| Builder | Notes | |---|---| | registerForager | Record only. Starts as Scout with no bond | | openForagerVault | Sub-account. Derives from the record, so it comes second | | topUpBond | Posts the bond, in base-asset atoms | | registerForagerSequence | The three above, in dependency order, for one transaction | | fundScout | One fixed exploration ticket | | promoteForager | Permissionless; criteria are read from chain state | | heartbeat | Liveness, against the non-response slash condition | | deposit / withdraw | Shares round down, favouring existing holders | | requestRedemption | Queue when idle liquidity is short | | beginSettlement / settleForager / finalizeSettlement | The three-phase crank | | rebalanceForager | Moves capital toward target |

buildInstruction is the general form if you need one that has no named builder; it takes a name-to-address map and reports a missing account by name.

Amounts are bigint throughout. The Borsh coder wants BN and is given it at the boundary, so no caller has to hold a BN for a u64 balance.

Every builder returns a TransactionInstruction and stops there. Nothing in this package signs, simulates or broadcasts.

Accounts

Layouts come from the IDL through Anchor's coder. Integers are normalized to bigint on the way out, because BN.toNumber() on a u64 balance or a u128 share count loses precision silently.

const snapshot = await client.fetchColonySnapshot();
// config, brood, cache, navBaseUnits, cacheReserveRatioBps

const foragers = await client.fetchAllForagers(); // by account discriminator

Against the devnet colony, that returns:

snapshot.config.epoch                1n
snapshot.config.activeForagerCount   0
snapshot.config.paused               false
snapshot.brood.baseMint              So11111111111111111111111111111111111111112
snapshot.brood.idleBase              200000000n
snapshot.navBaseUnits                200000000n
snapshot.cacheReserveRatioBps        0n
foragers.length                      2
foragers[0].statusName               "scout"
foragers[0].bond                     10000000n
foragers[0].pheromone                0n

Read on devnet, 2026-08-17. The base mint is devnet wSOL at nine decimals, so navBaseUnits of 200000000n is 0.2 wSOL and the bond is 0.01. Two foragers are registered and both are still scouts, which is why activeForagerCount is zero. These are test figures on a test cluster and describe nothing about a fund.

A read returns null for an account that does not exist, because "no colony here" is a real answer the Trail Board has to render. What it never does is report an empty colony for an undeployed program: those look identical in a chart and mean completely different things, so the second case throws.

Endpoints

DEFAULT_RPC_ENDPOINT is the public mainnet RPC, https://api.mainnet-beta.solana.com. No paid endpoint is built in: a paid URL carries its key in the query string, and an SDK shipping one would hand that key to every consumer's bundle. Pass your own Connection for a private endpoint, built server-side.

The client never opens a connection itself, so the read helpers work only on the Connection you hand them, and a missing one is NoConnectionError rather than a silent default. That matters right now: the program is on devnet, not on mainnet, so devnet work needs a devnet Connection passed in explicitly. A client built against mainnet does not fail vaguely -- fetchProgramStatus() returns deployed: false and every read raises ProgramNotFoundOnChainError naming the endpoint:

Cannot read the colony config: no account exists at the program id on this
cluster, so the program is not deployed here

  program id : 7whkmFfDcTyoJgf7jFGFmKNFMQn8NoreHnh2wZ9nWbsk
  endpoint   : https://api.mainnet-beta.solana.com
  account    : does not exist

Token-2022

The token program defaults to SPL Token. For a mint under Token-2022, pass tokenProgramId: TOKEN_2022_PROGRAM_ID to createKolnyClient, and remember that ATAs must be derived with the same program id or they resolve to an address that does not exist.

Package format

| | | |---|---| | ESM | import * as sdk from "@kolny/sdk" -- 50 exports | | CommonJS | require("@kolny/sdk") -- the same 50 exports | | Types | dist/esm/index.d.ts, reached through the "types" condition of the exports map | | IDL entry | @kolny/sdk/idl -- colonyIdl, the bundled interface; no fs, no import.meta, ESM and CommonJS both | | Node entry | @kolny/sdk/idl-node -- loadIdlFromDisk, defaultIdlPath, IdlNotBuiltError; ESM and CommonJS both | | Engine | Node 20 or newer |

Both formats are built from one source and smoke-tested by loading them, and the declarations resolve under "moduleResolution": "nodenext" with strict on. There is no separate types package to install.

Development

Against a clone of the protocol repository, not an installed package:

npm install
npm run typecheck
npm test
npm run build      # ESM to dist/esm, CommonJS to dist/cjs

src/idl-json.ts is generated, not written: scripts/generate-idl-module.mjs rebuilds it from packages/anchor-program/target/idl/kolny_colony.json and all four commands above run it first, so it cannot be the stale copy. It is not committed. The generator exits non-zero when the Anchor output is missing or fails assertColonyIdl, because a build that skipped it would produce a tarball that looks complete and leaves every consumer without an interface. npm pack and npm publish rebuild through prepack for the same reason.

Both outputs are smoke-tested by loading them. Three interop details cost a build each and are worth knowing. @coral-xyz/anchor is CommonJS only and marks itself __esModule, so its real exports hang off default under Node ESM and off the namespace under CommonJS, and the code takes whichever is populated. import.meta cannot appear in a CommonJS build and __dirname cannot appear in an ESM one, so the disk loader resolves its path from the working directory rather than from the module. And that same constraint is why the bundled IDL is a generated module rather than a .json file copied into dist: a module needs no path resolution at all, and it survives a browser bundler, which reading a sibling file does not.

License

MIT.