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

@unicitylabs/state-transition-sdk

v3.0.1

Published

Generic State Transition Flow engine for value-carrier agents

Readme

State Transition SDK

Overview

The State Transition SDK is a TypeScript library that provides an off-chain token transaction framework. Tokens are managed, stored, and transferred off-chain with only cryptographic commitments published on-chain, ensuring privacy while preventing double-spending through single-spend proofs. This is a low-level SDK, that supports transferring tokens, making payments, and splitting tokens. In this system, tokens are self-contained entities containing complete transaction history and cryptographic proofs attesting to their current state (ownership, value, etc.). State transitions are verified through consultation with blockchain infrastructure (Unicity) to produce proof of single spend.

Key Features

  • Off-chain Privacy: Cryptographic commitments contain no information about tokens, their state, or transaction nature
  • Horizontal Scalability: Millions of transaction commitments per block capability
  • Zero-Knowledge Transactions: Observers cannot determine if commitments refer to token transactions or other processes
  • Offline Transaction Support: Create and serialize transactions without network connectivity
  • TypeScript Support: Full type safety and modern development experience
  • Modular Architecture: Pluggable address schemes, predicates, and token types

Installation

npm install @unicitylabs/state-transition-sdk

Upgrading to 3.0

3.0.1 changes API that 3.0.0 shipped

3.0.1 is a corrective release: 3.0.0 was published but not adopted, and rather than carry its rough edges forward the release fixes them in place. It is not a drop-in patch.

| Change | What breaks | |---|---| | TransferTransaction.fromCBOR(bytes, token) | now (bytes, sourceStateHash, lockScript), and synchronous | | CertifiedTransferTransaction.fromCBOR(bytes, token) | the same | | TransferTransaction.expiresAtFromCBOR | removed; decode the transaction and read expiresAt | | new InclusionProofResponse(...) | replaced by InclusionProofResponse.certified / .notCertified | | new InclusionProof(...) | every field is now required; a proof describes a certified leaf | | InclusionProof.getCertificationData / getReferenceTime | no longer nullable | | InclusionProofVerificationStatus | MISSING_CERTIFICATION_DATA, MISSING_REFERENCE_TIME, INCOMPLETE_INCLUSION_PROOF and INCLUSION_CERTIFICATE_MISSING removed — none can occur now that a proof is complete by construction |

The wire formats are unchanged from 3.0.0, so tokens and proofs move between the two versions; only the API moved.

3.0 changes the formats the SDK shares with the Unicity Service, so it is not interoperable with 2.x in either direction. There is no migration path for tokens already in circulation.

Tokens minted by 2.x cannot be loaded. Token.VERSION is now 2, and Token.fromCBOR rejects an older token with Unsupported Token version: 1. MintTransaction, TransferTransaction and CertificationData moved to version 2 with it. Affected tokens have to be re-minted.

A 3.0 client needs an aggregator that speaks the new protocol, at ghcr.io/unicitynetwork/aggregator-go:sha-ae08165 or later. The certified leaf value is now SHA-256(CBOR([transactionHash, referenceTime])) rather than the transaction hash alone, so proofs from a 2.x-era service do not verify here, and a 2.x client cannot verify proofs from a current one.

Requests carry a deadline. MintTransaction.create, TransferTransaction.create and TokenSplit.split take an optional expiresAt; see Request deadlines below.

Compile-time breaks for anyone building on the verification internals:

| Change | What breaks | |---|---| | InclusionProofVerificationRule.verify no longer takes referenceTime | it is read from the inclusion proof instead | | InclusionProofVerificationStatus.REFERENCE_TIME_MISMATCH removed | replaced by REFERENCE_TIME_AFTER_ROUND and INCOMPLETE_INCLUSION_PROOF | | Certified mint and transfer CBOR is 2 elements, was 3 | the reference time is no longer stored beside the proof that carries it | | expiresAt is validated at the factories | a negative, zero or over-wide deadline now throws instead of failing later inside CBOR encoding |

Quick Start

End-to-end runnable examples live under tests/examples/:

Tokens are shipped between parties as CBOR — use token.toCBOR() on the sender side and Token.fromCBOR(bytes) on the receiver side.

Core Components

StateTransitionClient

A thin client over the aggregator. As a consumer you'll typically:

  1. Build a MintTransaction or TransferTransaction.
  2. Submit its CertificationData and wait for an inclusion proof.
  3. Turn it into a certified transaction and apply it to a Token (Token.mint / token.transfer).
  4. Call token.verify(...) on the receiving side.

StateTransitionClient covers step 2 only:

  • submitCertificationRequest() - Submit a commitment to the aggregator
  • getInclusionProof() - Retrieve an inclusion proof for a state id

Request deadlines

Every certification request carries an exclusive deadline. Supply one as expiresAt, in Unix seconds, and the Unicity Service admits the request only to a round whose reference time is strictly below it:

const transaction = await MintTransaction.create(networkId, recipient, {
  expiresAt: BigInt(Math.floor(Date.now() / 1000)) + 3600n,
});

The value is a wall-clock instant in Unix seconds, not a round number or block height, and it is compared against the round's reference time — which is the timestamp of the consensus seal, i.e. the root chain's clock, not yours. The two can differ by seconds, so leave enough margin to absorb the skew and the time a request spends queued. Hour-scale deadlines are unaffected; second-scale ones are not.

Omit it, or pass null, and the service derives a deadline from consensus time instead. That suits a caller with no trustworthy clock: the assigned value is service metadata, never recorded in the leaf and never re-checked by a later verifier, so it does not have to be agreed on in advance.

An explicit deadline is different — the transaction hash commits to it, so it travels with the token and every verifier re-checks it against the reference time the leaf was created under. Submitting after it has passed is answered with CertificationStatus.REQUEST_EXPIRED; a service that has not yet been given a consensus reference time answers SERVICE_NOT_READY.

See Security Features for what a deadline does and does not guarantee.

Transaction Flow

  1. Minting: Create new tokens
  2. Transfer: Submit state transitions between owners

Transfer flow

Prerequisites Recipient knows some info about token, like token type for generating address.

A[Start]
A --> B[Recipient Generates Predicate]
B --> C[Recipient Shares Predicate with Sender]
C --> D[Sender Creates Transaction]
D --> E[Sender Submits Transaction]
E --> F[Sender Retrieves Inclusion Proof]
F --> G[Sender Creates Certified Transaction]
G --> H[Sender Updates Token with Certified Transaction]
H --> I[Sender Sends Token to Recipient]
I --> J[End]

Architecture

Token Structure

A Token is a self-contained, CBOR-serializable record that bundles its genesis with an ordered transfer history:

  • genesis: a CertifiedMintTransaction (a MintTransaction plus its InclusionProof). The mint transaction carries networkId, tokenId, tokenType, salt, recipient, optional justification, optional data, and expiresAt, an exclusive request deadline in Unix seconds that is null when the Unicity Service assigns the deadline instead.
  • transactions: an ordered list of CertifiedTransferTransaction entries, each wrapping a TransferTransaction (recipient, state mask, optional data, and expiresAt) with its InclusionProof.

See src/transaction/Token.ts for the authoritative shape.

Privacy Model

  • Commitment-based: Only cryptographic commitments published on-chain
  • Self-contained: Tokens include complete transaction history
  • Zero-knowledge: No information leaked about token or transaction details
  • Minimal footprint: Blockchain only stores commitment hashes

Security Features

  • Double-spend prevention: Enforced through inclusion proofs
  • Cryptographic verification: All state transitions cryptographically verified
  • Predicate flexibility: Multiple ownership models supported
  • Provenance tracking: Complete audit trail in token history

Request deadlines are enforced by the service, not by verification

A request may carry an exclusive deadline (expiresAt), and the Unicity Service only admits it to a round whose reference time is strictly below that deadline. Verification re-checks the deadline against the reference time the leaf reports, and rejects a leaf claiming to postdate the round that certified it.

Neither check establishes when the leaf was created. The reference time is chosen by the service, and the inclusion proof authenticates the value it chose rather than the moment it chose it: a service that receives a request after its deadline can insert the leaf later and record a pre-deadline reference time in it, and every client-side check still passes. Closing that would need signed evidence of the creation round, which an inclusion proof does not currently carry.

So treat expiresAt as an instruction to an honest service — the guarantee that a late request is dropped rather than executed — and not as something a verifier can prove after the fact. It is not a defence against a service that is itself dishonest; that case is covered by consensus over the aggregator, not by this field.

Development

Building

npm run build

Testing

Run the default suite (unit + functional tests):

npm test

Run the example flows (requires a reachable aggregator; URL is read from each example's config.json):

npm run test:examples

Run the integration suite. It owns the aggregator it talks to: Testcontainers starts the stack in tests/integration/docker — a BFT root node, mongodb, redis and a pinned aggregator build — waits for consensus to certify a round, and tears it down when the run ends. Nothing external is involved and there is nothing to set up:

npm run test:integration

The chain starts empty every run, and the aggregator is published on an ephemeral port, so concurrent runs and CI jobs cannot collide. There is deliberately no way to point this suite at an aggregator it did not start — a run that could be aimed elsewhere would not be exercising the compose file it exists to test. Pointing the SDK at a service someone else is running is what the e2e suite below is for.

This is where the wire formats get checked. Certification data, the transaction encodings, the inclusion proof and the reference-time-bound leaf value are all shared with the service, and the fake aggregator in tests/functional derives them with the very code under test — only a real service can tell whether the two still agree.

Run the end-to-end suite against a deployed network. Unlike the integration suite this one has no service of its own, so point it at an endpoint and supply the matching trust base:

AGGREGATOR_URL=https://gateway.testnet2.unicity.network \
TRUST_BASE_PATH=/path/to/trust-base.json \
AGGREGATOR_API_KEY=<key, if the endpoint requires one> \
npm run test:e2e

The integration suite runs in CI; the e2e suite does not, since it needs a live network to be pointed at.

Linting

Lint all code (source and tests):

npm run lint

Lint with auto-fix:

npm run lint:fix

Network Configuration

  • Test gateway: https://gateway.testnet2.unicity.network. It fronts a sharded aggregator, so a plain AggregatorClient pointed at that one URL is enough. certification_request requires an API key (AggregatorClient's second argument); reading inclusion proofs does not.
  • Trust base: network-specific, and the network the SDK mints on is taken from it (trustBase.networkId), so the trust base and the gateway must belong to the same network.
  • Network identifiers: NetworkId.MAINNET, NetworkId.TESTNET and NetworkId.LOCAL are the named constants; any other id a trust base carries resolves through NetworkId.fromId().
  • Token type: caller-supplied; use TokenType.generate() or construct from explicit bytes

Unicity Signature Standard

The Unicity Network uses a standardized signature format to ensure data integrity and cryptographic proof of ownership. All cryptographic operations use the secp256k1 elliptic curve, SHA-256 hashing, and 33-byte compressed public keys.

The standard is designed for efficiency and broad compatibility across different programming environments, including Node.js, browsers, and Go.

Signature Format

A Unicity signature is a 65-byte array, structured as the concatenation of three components: [R || S || V].

| Component | Size (bytes) | Offset | Description | | :----------- | :------------- | :----- | :------------------------------------------------------------------------------------------------------------ | | R | 32 | 0 | The R value of the ECDSA signature. | | S | 32 | 32 | The S value of the ECDSA signature. | | V | 1 | 64 | The recovery ID (0 or 1). This value allows for the recovery of the public key directly from the signature. |

Process Overview

1. Signing The raw message data is first hashed using SHA-256. The resulting 32-byte hash is then signed using the signer's 32-byte secp256k1 private key to produce the 65-byte signature.

2. Verification The verifier hashes the original message using SHA-256. Using this hash and the signature, the verifier recovers the public key. The recovered key is then serialized into the compressed format and compared byte-for-byte against the expected 33-byte compressed public key to confirm validity.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support


Note: This SDK is part of the Unicity ecosystem. For production use, ensure you understand the security implications and test thoroughly in the testnet environment.