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

@fourbtd/sdk

v1.0.1

Published

Framework for Networked Persistent Characters: persistent NPC memory and verifiable identity

Readme

FOUR SDK

enter image description here

Framework for Networked Persistent Characters

@fourbtd/sdk gives game characters persistent memory, evolving relationships, inventory ownership, achievements, and portable identity records.

Capture what happened, recall relevant context in a later session, and commit a cryptographic state root to a wallet-signed Solana checkpoint.

Version 1.0.0 · Unpublished Release Candidate

The complete local workflow works without credentials or network access. HTTP operations require your own backend implementing the contract below.

No hosted 4BTD API, Solana program, paid infrastructure, or universal cross-game protocol is included. Publishing metadata and the license require owner review.

Installation and requirements

After the owner publishes this package:


npm  i  @fourbtd/sdk

For this unpublished checkout, run npm ci && npm run build && npm pack, then install

the generated fourbtd-sdk-1.0.0.tgz in your application.

  • Runtime: Node.js 20+; npm 10+ for installation. Use the latest patch of an active Node release for development.

  • Environments: local (default), solana-devnet, solana-mainnet.

  • Modern browsers with Fetch, AbortController, structuredClone and Web Crypto in a secure context.

  • ESM first, with CommonJS, TypeScript declarations and JavaScript source maps.

  • Zod is the only runtime dependency. No wallet SDK or private key is required.

Five-minute offline quickstart

import { FourBTDClient } from '@fourbtd/sdk';

const fourBtd = new FourBTDClient({
  network: 'local',
});

const mira = await fourBtd.characters.create({
  name: 'Mira',
  owner: 'player-one',
  world: 'forest',
  traits: ['curious'],
  goals: ['Find the lost city'],
  metadata: {
    faction: 'explorers',
  },
});

const event = await fourBtd.events.capture({
  characterId: mira.id,
  type: 'quest',
  content: 'A traveler revealed the path to the lost city.',
  importance: 0.9,
  idempotencyKey: 'quest-revelation-1',
});

await fourBtd.memory.commit({
  characterId: mira.id,
  type: 'episodic',
  content: event.content,
  salience: 0.9,
  tags: ['lost-city'],
  provenance: [event.id],
});

const recalled = await fourBtd.memory.recall({
  characterId: mira.id,
  query: 'lost city',
});

console.log(recalled.items.map((memory) => memory.content));

From the checkout, npm run examples runs the quickstart and a complete cross-session

integration journey offline. Other examples in examples/ are compiled by npm run typecheck.

Production HTTP client

Copy .env.example to .env for application configuration. The

template defaults to local mode without credentials. The SDK does not automatically

load environment files or read these variables; load them in your application and

pass them explicitly to the client. Treat blank optional values as undefined

(for example, process.env.FOURBTD_API_KEY || undefined). The offline examples

need no .env file. The template is included in the repository, not the npm tarball.

const fourBtd = new FourBTDClient({
  apiKey: process.env.FOURBTD_API_KEY,
  network: 'solana-devnet',
  baseUrl: 'https://your-backend.example.com',
  timeoutMs: 15_000,
  retries: 2,
  backoffMs: 100,
  maxBackoffMs: 10_000,
  jitter: 0.2,
});

An explicit baseUrl is required for non-local clients without a custom transport;

the SDK does not guess a production hostname. Base paths are supported. HTTPS is required

except for HTTP loopback development endpoints. URLs with credentials, queries or fragments

are rejected. Redirects are never followed. Supply server credentials only from a trusted server.

Events and memory

Events support dialogue, choice, quest, relationship, inventory, achievement,

world and custom. importance is between 0 and 1 (default 0.5); source defaults to

sdk. Optional fields include sessionId, worldId, UTC timestamp, metadata and

idempotencyKey. An event with a session must belong to that session's character while active.

const batch = await fourBtd.events.captureBatch([
  {
    characterId: mira.id,
    type: 'dialogue',
    content: 'Welcome home.',
  },
  {
    characterId: mira.id,
    type: 'choice',
    content: 'Helped the traveler.',
  },
]);

for (const result of batch) {
  if (result.success) {
    console.log(result.data.id);
  } else {
    console.log(result.error.code);
  }
}

const memory = await fourBtd.memory.commit({
  characterId: mira.id,
  type: 'semantic',
  content: 'The river crossing is safe.',
  tags: ['travel'],
});

await fourBtd.memory.evolve(
  {
    memoryId: memory.id,
    content: 'The bridge is now damaged.',
    salience: 0.95,
  },
  {
    expectedVersion: memory.version,
  },
);

const context = await fourBtd.memory.hydrate({
  characterId: mira.id,
  query: 'bridge',
  tags: ['travel'],
  types: ['semantic'],
  minSalience: 0.5,
  from: '2020-01-01T00:00:00Z',
  to: '2100-01-01T00:00:00Z',
  limit: 10,
});

await fourBtd.memory.forget(memory.id, 'Player requested removal from recall');

Memory supports episodic, semantic, relationship, and world.

evolve applies an explicit content, salience, tags, or provenance patch. It increments the memory version while preserving its ID. It does not call a language model.

Event provenance must reference events belonging to the same character.

hydrate uses the same filters and ranking as recall:

  • All requested tags must match.

  • types uses OR filtering.

  • UTC creation times are filtered inclusively.

  • Query terms match case-insensitive substrings, with at least one term required to match.

  • Relevance is 70% matching-term fraction + 30% salience.

  • Without query terms, relevance is based entirely on salience.

  • Ties are resolved using the memory ID.

These are deterministic local heuristics, not embedding-based search.

Forgotten memories are excluded from recall and list, but retained for audit. Forgetting a memory does not physically erase it.

Relationships, inventory and achievements

await fourBtd.relationships.update({
  characterId: mira.id,
  subjectId: 'traveler',
  trust: 0.8,
  affinity: 0.6,
  familiarity: 0.4,
  rivalry: 0,
  status: 'friend',
  sharedEvents: [event.id],
});

const ally = await fourBtd.characters.create({
  name: 'Taro',
  owner: 'player-two',
});

await fourBtd.inventory.grant({
  characterId: mira.id,
  itemId: 'potion',
  quantity: 2,
  sourceWorld: 'forest',
});

await fourBtd.inventory.transfer({
  characterId: mira.id,
  toCharacterId: ally.id,
  itemId: 'potion',
  quantity: 1,
});

const ownership = await fourBtd.inventory.verify({
  characterId: ally.id,
  itemId: 'potion',
});

await fourBtd.achievements.unlock({
  characterId: mira.id,
  key: 'generous',
  name: 'A Helping Hand',
});

Relationship scores are clamped to [0, 1] when finite. Omitted scores preserve their existing values, or default to 0 when creating a new relationship.

Shared-event patches replace the existing list and must reference events belonging to the character.

Inventory behavior:

  • Quantities must be positive safe integers.

  • Transfers are atomic and require distinct, active owners.

  • Transfers preserve item provenance.

  • Removing the entire quantity returns null.

  • Existing stacks require matching optional asset addresses.

  • Grants preserve the existing stack's metadata and source world.

  • verify checks recorded offchain ownership, not a Solana token account.

Achievement unlocks are intrinsically idempotent by characterId and key. After the first unlock, the original name, description, and metadata are retained.

Sessions and identity

await fourBtd.identity.create({
  characterId: mira.id,
  wallet: 'local-wallet',
});

const session = await fourBtd.sessions.start({
  characterId: mira.id,
  hydrate: true,
});

const scoped = fourBtd.withContext({
  sessionId: session.id,
  worldId: 'forest',
});

await scoped.events.capture({
  characterId: mira.id,
  type: 'quest',
  content: 'Reached the lost city.',
  importance: 1,
});

const ended = await scoped.sessions.end(session.id, {
  checkpoint: true,
});

if (ended.checkpoint) {
  const verification = await fourBtd.identity.verify({
    checkpointId: ended.checkpoint.id,
  });
}

const hydrated = await fourBtd.sync(mira, {
  checkpoint: true,
});

Run the local identity example using the local client from the quickstart.

start can optionally hydrate the session with character, memory, relationship, inventory, and achievement context.

end creates an extractive summary by joining events with importance >= 0.5 in capture order. Set summarize: false to omit the summary. Repeated calls to end return the already-ended session. Checkpointing requires an existing identity.

State Root

Each local mutation recomputes the character stateRoot using SHA-256 over canonical JSON containing:

  • Character, with its root set to null

  • All events

  • All memories, including forgotten entries

  • Relationships

  • Inventory

  • Achievements

Records are included in stored order.

The identity stateRoot represents the last checkpointed root. Sessions and identity records are not included in the state root.

The schema version starts at 4btd.character/v1.

Checkpoints

Checkpoints retain:

  • Wallet

  • Public key

  • Network

  • Portability

  • Transaction signature

  • Slot

  • Confirmation state

  • UTC timestamps

The local adapter automatically records checkpoints and verifies exact records against its own in-process registry. Keep the same adapter or transport across sessions.

Local verification provides offline integrity checking. It is not cryptographic proof of wallet ownership.

Solana verification and wallet delegation

import {
  FourBTDClient,
  InMemoryTransport,
  SolanaIdentityAdapter,
  type CheckpointSigner,
} from '@fourbtd/sdk';

// Your wallet integration implements this interface:
// construct, sign, and submit a transaction containing
// the exact supplied memo, then await finalization.
async function checkpointOnSolana(wallet: string, signer: CheckpointSigner) {
  const adapter = new SolanaIdentityAdapter({
    network: 'solana-devnet',
    rpcUrl: 'https://api.devnet.solana.com',
    signer,
  });

  const sdk = new FourBTDClient({
    network: 'solana-devnet',
    transport: new InMemoryTransport({
      identityAdapter: adapter,
    }),
  });

  const character = await sdk.characters.create({
    name: 'Mira',
    owner: 'player-one',
  });

  await sdk.identity.create({
    characterId: character.id,
    wallet,
  });

  const checkpoint = await sdk.identity.checkpoint({
    characterId: character.id,
  });

  const published = await sdk.identity.publish({
    checkpointId: checkpoint.id,
  });

  return sdk.identity.verify({
    checkpointId: published.id,
  });
}

The signer exposes:

publish(
  checkpoint,
  memo,
  signal?,
): Promise<{
  signature: string;
  slot: number;
}>;

Signer implementations must deduplicate submissions by checkpoint ID. The SDK does not hold private keys or construct wallet transactions.

Without a signer, the Solana adapter still validates keys and can verify supplied checkpoint records directly:

await adapter.verify(checkpoint);

Solana Verification

Verification uses Solana's getTransaction RPC with:

  • jsonParsed encoding

  • finalized commitment

  • Transaction version 0 support

Verification checks:

  • 32-byte base58 public keys

  • 64-byte base58 transaction signatures

  • Successful transaction metadata

  • Exact transaction slot and signature

  • Wallet signer participation

  • Exact checkpointMemo stored through the SPL Memo program

The SPL Memo program is:

MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr

The checkpoint memo binds the schema, character, checkpoint, network, wallet, public key, and state root.

The RPC URL must be trusted and point to the configured Solana cluster. Confirmation is never inferred from a caller-supplied signature alone.

create records a claimed identity. Successful publication and verification establish its wallet signature.

Portability is descriptive metadata, not an interoperability guarantee.

Requests, retries and observability

Every method accepts a final RequestOptions argument:

import { ConflictError, isFourBTDError } from '@fourbtd/sdk';

const controller = new AbortController();

try {
  await fourBtd.characters.update(
    mira.id,
    {
      description: 'Explorer',
    },
    {
      expectedVersion: mira.version,
      idempotencyKey: 'mira-profile-edit-1',
      signal: controller.signal,
      requestId: 'profile-request-1',
    },
  );
} catch (error) {
  if (error instanceof ConflictError) {
    const latest = await fourBtd.characters.get(mira.id);

    // Reconcile your edit against the latest version
    // before submitting a new operation.
  } else if (isFourBTDError(error)) {
    console.error(error.code);
  } else {
    throw error;
  }
}

// Cancels an active request or its retry wait.
controller.abort();

Retries and timeouts

  • Timeout is per attempt and defaults to 30 seconds.

  • Retries default to 2, for a maximum of 3 total attempts.

  • Retry delay is calculated as:

min(maxBackoffMs, backoffMs * 2^attempt)
  * (1 + jitter * (2 * random - 1))

The actual wait is always at least the server-provided Retry-After, expressed as seconds or an HTTP date.

Only reads or mutations with an idempotency key are retried. Retries apply to network errors, 408, 429, and 5xx responses.

Every SDK mutation receives one stable generated idempotency key across retries unless one is supplied explicitly. Persist your own key across process restarts or manually repeated calls when you need the same operation to remain idempotent.

Idempotency

Local idempotency is scoped by network, operation, and key. Reusing the same key with changed payload, context, or version produces a conflict.

Event-level idempotency keys are additionally scoped by character.

Batches support up to 100 inputs and return ordered successes or typed error data. A batch-level key replays the entire result, while event-level keys deduplicate individual events.

Do not reuse a context idempotency key for different mutations of the same operation.

Optimistic concurrency

expectedVersion checks the entity being mutated:

  • Character

  • Memory

  • Relationship

  • Inventory source stack

  • Identity

  • Session

For new relationship or inventory entries, use 0.

Creation of new immutable entities has no previous version to compare against.

sync checks the character version. Session-end checkpointing checks the session version, not a coincidentally matching identity version.

Data handling

Client calls detach inputs and outputs. Unknown external responses fail Zod validation.

Public dates use UTC ISO strings. Metadata must contain valid JSON values, and undefined optional properties are omitted.

Cancellation and timeout abort underlying work. Custom transports and signers must honor abort signals.

Cancellation cannot undo a transaction that has already been submitted to an external system.

Context

const scoped = fourBtd.withContext({
  worldId: 'forest',
  sessionId: session.id,
  playerId: 'player-one',
  trace: {
    feature: 'quest',
  },
  idempotencyKey: 'quest-operation-1',
});

withContext() returns a client that shares the same transport and dependencies.

Explicit method fields override context defaults. Setting a context override to undefined clears that default.

Context defaults apply as follows:

  • worldId → characters, events, and sessions

  • playerId → sessions

  • sessionId → captured events, including batches

  • Context metadata → every transport request

Logging and hooks

Inject a logger with:

debug
info
warn
error

Lifecycle hooks include:

onRequestStart
onRequestSuccess
onRequestRetry
onRequestFailure

Hooks receive the operation, request ID, zero-based attempt, and optional error code or retry delay.

Logger output contains the operation, attempt, lifecycle phase, and redacted trace metadata. Request payloads are never logged.

Logging defaults to a no-op.

Use sensitiveFields to configure additional trace keys for redaction. Known API key values are also scrubbed.

For custom instrumentation:

redact(value, sensitiveFields);

redact() sanitizes nested credentials and configured custom keys.

Instrumentation exceptions never alter requests.

Error details are automatically redacted. Arbitrary external causes and HTTP error bodies are discarded, while typed SDK causes may be retained through the standard cause property.

Pagination

let page = await fourBtd.events.list(mira.id, {
  limit: 20,
});

const events = [...page.items];

while (page.nextCursor) {
  page = await fourBtd.events.list(mira.id, {
    limit: 20,
    cursor: page.nextCursor,
  });

  events.push(...page.items);
}

All list operations return:

{
  items,
  nextCursor: string | null,
}

limit accepts values from 1 to 100 and defaults to 20.

Cursors are opaque and bound to the operation and its filters. Changing filters invalidates an existing cursor.

The local store uses offset-based cursors rather than snapshot isolation. Concurrent insertions or removals can therefore change page membership while paginating.

hydrate returns a context bundle rather than a paginated result. Its limit controls the number of recalled memories, while other context collections are returned in full.

Complete public API reference

API Reference

Every method returns a Promise.

  • R — optional final RequestOptions

  • P — { limit?, cursor? }

  • IDs accept strings as input and are branded on validated output.

The SDK exposes exact TypeScript contracts through:

Input<'module.method'>;
ParsedInput<'module.method'>;
Output<'module.method'>;

operations exports the corresponding Zod input/output schemas and mutation flags.

TypeScript declarations ship in both .d.ts and .d.cts formats.

Module

Methods and results

characters

create(input, R) → Character``get(id, R) → Character``update(id, patch, R) → Character``list({ limit?, cursor?, status?, owner?, world? }, R) → Page<Character>``archive(id, R) → Character``hydrate(id, { limit? }, R) → Hydration

events

capture(input, R) → Event``captureBatch(inputs, R) → BatchResult<Event>[]``get(id, R) → Event``list(characterId, P, R) → Page<Event>

memory

commit(input, R) → Memory``recall(query, R) → Page<RecallResult>``list(characterId, P, R) → Page<Memory>``forget(id, reason?, R) → Memory``hydrate(query, R) → Page<RecallResult>``evolve({ memoryId, content?, salience?, tags?, provenance? }, R) → Memory

relationships

get(characterId, subjectId, R) → Relationship``update(input, R) → Relationship``list(characterId, P, R) → Page<Relationship>

inventory

grant(input, R) → InventoryItem``transfer({ characterId, toCharacterId, itemId, quantity }, R) → { from: InventoryItem | null, to: InventoryItem }``remove({ characterId, itemId, quantity }, R) → InventoryItem | null``verify({ characterId, itemId, quantity? }, R) → Verification``list(characterId, P, R) → Page<InventoryItem>

achievements

unlock({ characterId, key, name, description?, metadata? }, R) → Achievement``get(id, R) → Achievement``list(characterId, P, R) → Page<Achievement>

identity

resolve({ characterId }, R) → Identity``create({ characterId, wallet, publicKey?, portability? }, R) → Identity``checkpoint({ characterId }, R) → Checkpoint``publish({ checkpointId }, R) → Checkpoint``verify({ checkpointId }, R) → Verification``getHistory(characterId, P, R) → Page<Checkpoint>

sessions

start({ characterId, worldId?, playerId?, hydrate?, metadata? }, R) → Session``end(id, { summarize?, checkpoint? }, R) → Session``get(id, R) → Session

client

sync({ id }, { checkpoint?, ...R }) → Hydration``health(R) → { status: "ok", network, timestamp }``withContext(overrides) → FourBTDClient

Characters

Character creation requires name and owner.

Optional fields include:

description
traits
goals
world
metadata

metadata must contain valid JSON values.

Character updates accept a partial creation input.

Archived characters remain readable but reject new character-state mutations. archive is idempotent.

Character versions track profile updates and archival. Memory, relationship, inventory, session, and identity versions track changes to their respective entities.

Use state roots to detect aggregate character-state changes.

Public Exports

The SDK exports branded IDs for:

Character
Event
Memory
Session
Player
World
Item
Achievement
Checkpoint

It also exports entity, pagination, error, and configuration types, along with all runtime schemas.

Core interfaces include:

Transport;
Storage;
StoreState;
IdentityAdapter;
CheckpointSigner;
Clock;
IdGenerator;
Logger;

Concrete adapters and transports are also exported.

Utilities include:

canonical;
stateRoot;
redact;
systemClock;
randomId;
noopLogger;
checkpointMemo;
isSolanaPublicKey;
validateUrl;

Errors

Typed SDK errors include:

FourBTDError;
AuthenticationError;
AuthorizationError;
ValidationError;
NotFoundError;
ConflictError;
RateLimitError;
TimeoutError;
NetworkError;
ServerError;
AbortError;

All SDK errors expose:

code
message
status?
requestId?
details?
retryAfterMs?
retryable

toJSON() returns safe error data.

Error utilities are also exported:

isFourBTDError;
isRetryableError;
errorForStatus;

Transport, storage and adapter contracts

import { FourBTDClient, InMemoryTransport, type Transport } from '@fourbtd/sdk';

const delegate = new InMemoryTransport();

const transport: Transport = {
  execute: (request) => delegate.execute(request),
};

const sdk = new FourBTDClient({
  network: 'local',
  transport,
});

Custom Transport

Transport.execute(TransportRequest) → Promise<unknown> receives:

  • Operation

  • Validated input

  • Network

  • Detached context

  • Request ID

  • Idempotency key

  • Expected version

  • AbortSignal

Return raw data matching operations[operation].output, not the HTTP envelope.

Throw typed SDK errors for known failures.

An injected transport takes precedence over built-in transport selection. Configure its storage and identity adapter directly.

In-Memory Transport

new InMemoryTransport({
  storage?,
  clock?,
  idGenerator?,
  identityAdapter?,
});

InMemoryTransport uses MemoryStorage and LocalIdentityAdapter by default.

Storage.transaction(work) must:

  • Atomically serialize work against a StoreState

  • Roll back on failure

  • Detach returned results

The built-in store keeps data and idempotency records in memory without eviction. Share the store to preserve state across multiple clients within the same process.

Durable storage is an application-level injection.

Use a separate transport and store for each network and tenant.

A signer performs an external side effect and must enforce checkpoint-ID idempotency independently of storage rollback.

Identity Adapter

IdentityAdapter.create(input, network)
  → { wallet, publicKey, confirmation }

IdentityAdapter.publish(checkpoint, signal?)
  → Checkpoint

IdentityAdapter.verify(checkpoint, signal?)
  → { valid, reason? }

The local transport owns identity storage, state hashing, and checkpoint history.

An identity adapter must not change checkpoint identity during publication.

HTTP backends own their identity implementation. Passing an identityAdapter cannot inject wallet functionality into a remote server.

Injectable Dependencies

Clock provides:

now(): Date

sleep(
  ms: number,
  signal?: AbortSignal,
): Promise<void>

IdGenerator(kind) must produce unique, non-empty IDs.

random supplies retry jitter.

These dependencies, along with logger and lifecycle hooks, can be injected through ClientOptions for deterministic and reproducible application behavior.

HTTP Backend Contract

Confirm the following contract before deployment.

Endpoints

Every operation uses POST:

/v1/{module}/{method}

Special client endpoints:

/v1/sync
/v1/health

Request body:

{
  "input": {},
  "context": {}
}

Headers

Content-Type: application/json
X-Request-Id: <request-id>
X-Fourbtd-Network: <network>
Authorization: Bearer <api-key>
Idempotency-Key: <key>
If-Match: <version>

Authorization, Idempotency-Key, and If-Match are included when applicable.

If-Match uses an unquoted decimal version.

Success Responses

Successful responses use:

{
  "data": "<operation output>"
}

All successful responses are schema validated.

Unknown entity fields are stripped, and public timestamps use UTC ISO 8601.

Error Mapping

Status

SDK error

401

Authentication

403

Authorization

404

Not found

408

Timeout

409, 412

Conflict

429

Rate limit

5xx

Server

Other failures

Validation

HTTP error bodies are not reflected into SDK error messages.

Retry-After establishes the minimum retry delay.

Backend Requirements

Backend mutations must atomically deduplicate idempotency keys and honor optimistic versions.

The backend contract should also define:

  • Batch ordering and partial results

  • Pagination scope

  • Recall ranking

  • Retention behavior

  • Authentication

  • Tenant isolation

Do not connect the transport directly to an unrelated REST API without an adapter.

Browser & Security

Use the ESM entry with an ESM-aware bundler for tree shaking:

{
  "sideEffects": false
}

Direct browser usage requires bundling the bare Zod dependency or providing it through an import map.

Local browser usage requires no API key.

Production browser applications should communicate through your authenticated application backend. Never embed a server API key in client-side code.

Cross-origin HTTP servers must explicitly allow:

  • Your application origin

  • POST

  • SDK request headers

CommonJS consumers can use:

const { FourBTDClient } = require('@fourbtd/sdk');

Read SECURITY.md for trust boundaries and privacy considerations.

An offchain assertion does not become true simply because its hash is recorded onchain. Inventory transfers also do not transfer blockchain assets.

Treat context and memory as untrusted application content when constructing AI prompts.

Development

npm ci
npm run format
npm run lint
npm run typecheck
npm test
npm run coverage
npm run build
npm run examples
npm run consumer
npm run verify

CI runs:

  • Formatting

  • Linting

  • Strict type checking

  • Tests

  • 90% minimum line, statement, function, and branch coverage

  • Production build

  • Offline examples

  • Temporary ESM, CommonJS, and TypeScript consumer installation

  • Package inspection on Node.js 20, 22, and 24

consumer creates the package tarball, installs it in an isolated temporary project, verifies imports and declarations, checks packed paths, and cleans up the temporary project. Installation prefers cached dependencies but can access the npm registry when the cache is incomplete. The local examples and consumer runtime checks require no API key or backend access.

npm pack --dry-run

This rebuilds through prepack and displays the package allowlist.

Published packages contain only:

  • Built JavaScript

  • Type declarations

  • Source maps

  • README

  • License

  • Security policy

  • Changelog

  • Package metadata

Tests, development configuration, credentials, and development dependencies are not included.

Before release, replace owner metadata, license information, and security contact details. Confirm the backend contract and review provenance configuration.

This repository never publishes automatically.

Contributing

Contributions should include behavior-focused tests and update relevant examples and contracts alongside API changes.

Before submitting a pull request:

npm run verify