Items
Items use the same native asset model as tokens. A unique item has supply 1 and zero decimals. A supply greater than one describes interchangeable units of one fungible item type; it is not a collection. A collection is many separate supply-1 tokens that share an issuer and metadata prefix.
Ownership is durable ledger state. Combat position, cooldowns, animation state, and other fast-changing facts still belong to the game server; Kei is not a 60 Hz database.
Run a complete ownership transfer
This no-network playground creates a unique sword, mints it to one wallet, transfers it with that player's key, and reads the new owner directly from the ledger.
bun install --frozen-lockfile
bun run docs/playgrounds/items.ts
# {"kind":"item","item":"Sword of Testing","ownerChanged":true}The docs render the executable file directly, and the site test suite runs it.
import { strict as assert } from 'node:assert'
import { Kei, randomSeed } from 'kei-transaction'
const node = await Kei.mock()
const game = await Kei.server({ seed: 'C'.repeat(64), node })
await game.faucet(20_000)
const player = await Kei.start({ node, seed: randomSeed() })
const friend = await Kei.start({ node, seed: randomSeed() })
const sword = await game.items.create({
name: 'Sword of Testing',
description: 'A unique item used by the documentation playground.',
transfer: 'open',
})
await game.items.mint(sword.id, player.address)
await player.sync()
assert.equal(await player.items.owner(sword.id), player.address)
await player.items.transfer(sword.id, friend.address)
await friend.sync()
assert.equal(await friend.items.owner(sword.id), friend.address)
console.log(JSON.stringify({ kind: 'item', item: sword.name, ownerChanged: true }))Create an item type
const potion = await game.items.create({
name: 'Health Potion',
description: 'One interchangeable potion unit.',
image: './potion.png',
supply: 100,
transfer: 'open',
})Omit supply for a unique item. Use transfer: 'none' for a soulbound item.
| Shape | supply | Good fit |
|---|---|---|
| Unique item | omitted (defaults to one) | A named sword or one-off collectible. |
| Fungible item type | a fixed value greater than one | Interchangeable copies, such as potions or ammunition. |
| Soulbound | either shape, with transfer: 'none' | Achievements that should never acquire a secondary-market price. |
To model a collection, create many supply-1 item tokens under the same issuer and give their metadata a shared prefix. Kei deliberately has no collection primitive; applications may group those item ids for display.
Each item type is an asset and therefore pays the escalating issuance burn. Do not create a new asset type for every durability change, session, or stack.
Mint and transfer
await game.items.mint(sword.id, playerAddress)
await kei.items.transfer(sword.id, recipientAddress)The issuer mints. The current owner signs a transfer. A mint first arrives as a receivable; call sync() before trying to transfer it. The playground keeps that line visible because removing it produces the useful failure "balance is 0" rather than silently pretending the player already holds the item.
Query ownership
await kei.items.owner(sword.id)
await kei.items.ownedBy(playerAddress)Ownership is part of ledger state and can be queried directly; no separate inventory indexer is required.
owner() is defined only for a supply-1 item and answers that unique item's owner. For a fungible item type, query a known account's balance instead. ownedBy() answers the assets a known account holds. It is not a global catalogue search or marketplace index; a product-wide browse view still needs the app to know which accounts or item types it intends to read.
Keep live game state elsewhere
Put durable ownership on the ledger. Keep rapidly changing state such as durability ticks, live stack counts, position, or cooldowns in the game system that owns the real-time loop.
The practical split is:
| Fact | Authority |
|---|---|
| Who owns the sword | Kei ledger |
| Whether the sword may transfer | Immutable asset policy on the Kei ledger |
| Current durability, equipped slot, attack animation | The game system that owns the live loop |
The playground proves creation, receivable collection, player-signed transfer, and ownership lookup against the deterministic mock. It does not prove a public network's uptime or make the item valuable.