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

@title-protocol/sdk

v0.1.11

Published

Title Protocol TypeScript SDK

Readme

@title-protocol/sdk

TypeScript SDK for Title Protocol — the identity layer for digital content.

Records digital content attribution on Solana using C2PA provenance, Trusted Execution Environments (TEE), and compressed NFTs.

Install

npm install @title-protocol/sdk

Quick Start

import { fetchGlobalConfig, TitleClient } from "@title-protocol/sdk";

// 1. Fetch on-chain config (all data comes from Solana RPC)
const config = await fetchGlobalConfig("devnet");

// 2. Create client
const client = new TitleClient(config);

// 3. Register content (encrypt → upload → verify → store → sign)
const result = await client.register({
  content: imageBuffer,             // Uint8Array — C2PA-signed content
  ownerWallet: "YourSolana...",     // Base58 wallet address
  processorIds: ["core-c2pa"],      // processors to run in TEE (required)
  delegateMint: true,               // Gateway broadcasts the TX + stores signed_json
});

// result.contents — verified content details (contentHash, storageUri, signedJson)
// result.txSignatures — already on-chain

Specific Node

// By gateway endpoint URL
const result = await client.register({
  content: imageBuffer,
  ownerWallet: "YourSolana...",
  processorIds: ["core-c2pa"],
  delegateMint: true,
  gatewayEndpoint: "http://54.250.143.52:3000",
});

// Or via selectNodeByEndpoint
const node = await client.selectNodeByEndpoint("http://54.250.143.52:3000");
const result = await client.register({
  content: imageBuffer,
  ownerWallet: "YourSolana...",
  processorIds: ["core-c2pa"],
  delegateMint: true,
  node,
});

Custom RPC

import { Connection } from "@solana/web3.js";
import { fetchGlobalConfig, TitleClient } from "@title-protocol/sdk";

const conn = new Connection("https://devnet.helius-rpc.com/?api-key=...");
const config = await fetchGlobalConfig(conn, "devnet");
const client = new TitleClient(config);

With Custom Storage

If you want to store signed_json yourself instead of using Gateway delegation:

const result = await client.register({
  content: imageBuffer,
  ownerWallet: wallet,
  processorIds: ["core-c2pa", "phash-v1"],
  storeSignedJson: async (json) => {
    // Persist to your own storage. Return a retrievable URI.
    return await uploadToArweave(json);
  },
  recentBlockhash: blockhash,
});
// result.partialTxs — Base64 partial TXs to co-sign with your wallet

API

fetchGlobalConfig

Fetch GlobalConfig + all TeeNodeAccount PDAs from Solana. No HTTP requests — purely on-chain data.

fetchGlobalConfig("devnet")                    // default RPC
fetchGlobalConfig(connection, "devnet")        // custom RPC
fetchGlobalConfig(connection, programId)       // custom program (node operators)

TitleClient

const client = new TitleClient(globalConfig);

| Method | Description | |--------|-------------| | register(options) | Full registration flow: encrypt → upload → verify → store → sign | | selectNode() | Select a healthy TEE node (health-check + random selection) | | selectNodeByEndpoint(url) | Select a node by gateway endpoint URL (health-checked) |

RegisterOptions

| Field | Type | Required | Description | |-------|------|----------|-------------| | content | Uint8Array | Yes | Content binary (C2PA-signed image, etc.) | | ownerWallet | string | Yes | Owner wallet address (Base58) | | processorIds | string[] | Yes | Processors to run (e.g. ["core-c2pa"]) | | storeSignedJson | (json: string) => Promise<string> | No | Callback to persist signed_json. Optional if Gateway supports store_signed_json. | | extensionInputs | Record<string, unknown> | No | Auxiliary inputs for WASM extensions | | node | TeeSession | No | Specific node to use (auto-selected if omitted) | | gatewayEndpoint | string | No | Gateway URL to select a specific node (e.g. "http://54.250.143.52:3000") | | delegateMint | boolean | No | If true, Gateway broadcasts TX. Default: false | | recentBlockhash | string | No | Required when delegateMint is false |

When delegateMint is false, result.partialTxs contains Base64-encoded VersionedTransaction (v0). Co-sign and broadcast:

import { VersionedTransaction, Connection } from "@solana/web3.js";

for (const txB64 of result.partialTxs!) {
  const tx = VersionedTransaction.deserialize(Buffer.from(txB64, "base64"));
  tx.sign([walletKeypair]);
  const sig = await connection.sendRawTransaction(tx.serialize());
  await connection.confirmTransaction(sig, "confirmed");
}

Low-level Methods

For advanced use cases, the underlying Gateway endpoints are available:

| Method | Description | |--------|-------------| | getUploadUrl(url, size, type) | Get a presigned upload URL | | upload(url, payload) | Upload encrypted payload to temporary storage | | verifyRaw(url, request) | Call /verify (returns encrypted response) | | signRaw(url, request) | Call /sign | | signAndMintRaw(url, request) | Call /sign-and-mint |

Crypto

| Function | Description | |----------|-------------| | encryptPayload(teePk, data) | Full E2EE: ECDH + HKDF + AES-256-GCM | | decryptResponse(key, nonce, ct) | Decrypt Base64-encoded TEE response | | generateEphemeralKeyPair() | Generate X25519 keypair | | deriveSharedSecret(sk, pk) | X25519 ECDH | | deriveSymmetricKey(shared) | HKDF-SHA256 → 32-byte AES key | | encrypt(key, plaintext) | AES-256-GCM encrypt | | decrypt(key, nonce, ct) | AES-256-GCM decrypt |

Security

The SDK automatically validates TEE responses inside register():

  • wasm_hash check: Extension signed_json's wasm_hash is verified against GlobalConfig's trusted_wasm_modules
  • These checks can also be performed manually by reading the on-chain GlobalConfig directly

Encryption Protocol

All content is end-to-end encrypted — node operators cannot see raw content:

  1. Generate ephemeral X25519 keypair
  2. ECDH with TEE's X25519 public key → shared secret
  3. HKDF-SHA256 → 32-byte symmetric key
  4. AES-256-GCM encrypt payload

The TEE encrypts its response with the same symmetric key.

License

Apache-2.0