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

@keicoin/player-economy

v0.1.2

Published

Noncustodial player shops for Kei: list, buy, cancel, and gift through the player's own wallet, with honest pending state and chart-ready history.

Readme

@keicoin/player-economy

Shops that belong to the players, not the game. Listing, buying, cancelling and gifting through the player's own wallet, with no server in the middle and nothing holding anybody's money.

Part of kei-transaction — real currencies and items for browser games. Install kei-transaction instead unless you are counting bytes; these sub-packages exist for bundle size, not as a puzzle you have to solve.

bun add @keicoin/player-economy     # or npm / pnpm / yarn

Sixty seconds

import { Kei } from 'kei-transaction'

const kei = await Kei.start({
  shop: {
    currency: gold.id,                                        // your money, not Kei
    catalogue: [{ key: 'sword', asset: sword.id, title: 'Iron Sword' }],
    directory,                                                // which chains to read
  },
})

await kei.shop.list({ item: 'sword', qty: 2, each: 120 })     // open a stall
const shelves = await kei.shop.browse()                        // everybody else's
await kei.shop.buy(shelves.listings[0])                        // one block, both legs
await kei.shop.gift({ to: friend, item: 'sword' })             // no price, no offer

There is a runnable version in examples/player-shops.

What it is

@keicoin/economy is the issuer's half of an economy: recipes the game declares, stocked from the game's own account. This is the player's half, and the difference is who signs.

| | @keicoin/economy | @keicoin/player-economy | |---|---|---| | Whose account | the issuer's | the player's | | Where stock comes from | stock(), which will mint it | never mints — you list what you hold | | Who sets the price | the recipe both halves import | the seller, per listing | | Runs in | server, for stocking | the browser |

Nothing here is custodial and nothing is stored. A stall is a set of swap_offer blocks on one player's own chain; a sale is one swap_accept that moves both legs or neither (SPEC §9.2). The world it is embedded in cannot list, cancel, buy, or gift for anybody, because it has no key for their account — and there is no API that pretends otherwise.

The directory is the whole backend

An offer lives on its author's chain and Kei ships no indexer (SPEC §9.4), so something has to remember which chains are worth reading. That is a list of addresses:

import { createDirectory } from 'kei-transaction'

const directory = createDirectory()      // bounded LRU, default 128
directory.watch(playerAddress)           // a `watch` route, or your player table

Or implement the interface over whatever you already have — it is one method:

const directory = { accounts: () => fetch('/players').then(r => r.json()) }

A wrong directory can hide a stall. It cannot move an item: every listing is re-read from the chain and checked field by field against the row you rendered before anything is signed.

const shelves = await kei.shop.browse()
shelves.coverage   // { asked, read, failed, truncated, dropped, skipped, complete }

A shop over a roster is a floor, never a census, and coverage is how a view says so instead of implying nobody is selling.

each is not price

await shop.list({ item: 'sword', qty: 10, each: 12 })   // 120 for the lot
await shop.list({ item: 'sword', qty: 10, price: 12 })  // 12 for the lot

Exactly one, and naming both or neither is refused with both meanings spelled out. The multiplication happens in raw integers, so a currency with real decimal places is not listed at a rounded price.

Three balances, not one

const funds = await shop.funds()
funds.confirmed   // what the chain says is spendable
funds.incoming    // owed to you and not yet signed for (SPEC §5.6.3)
funds.committed   // signed a moment ago and not yet read back
funds.spendable   // confirmed - committed. The only one a spend is checked against
funds.projected   // what it becomes if everything lands

Showing only confirmed makes a shop look stuck for a second after every action. Adding the others in makes it offer money the ledger will refuse. Each comes with its raw integer beside it, because a JS number cannot hold eighteen decimal places and a balance comparison must not round.

await shop.sync()      // collect arrivals, re-read your stall, report what left
shop.pending()         // what this wallet has signed and not read back
shop.on('change', ({ pending }) => redraw())

sync() reports a departure once, with a sentence saying whether somebody bought it or the seller took it back — those are different facts to a player.

Price history

const series  = await shop.history({ item: 'sword' })
const candles = await shop.candles({ item: 'sword', every: '1h' })

The prices and every statistic over them are consensus. The order is not: the block-lattice has no clock (SPEC §5.5), so the sequence is the node's own first-seen time and series.ordering says exactly that rather than leaving you to find out. See @keicoin/market for the full contract.

Status

Built on M5's market, against the mock ledger and over M3's HTTP transport. The design record — what this refuses, what it costs, and what it still cannot do — is docs/decisions-player-economy.md.

There is no mainnet and nothing here holds value.

See the full documentation.

MIT.