@title-protocol/sdk
v0.1.11
Published
Title Protocol TypeScript SDK
Maintainers
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/sdkQuick 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-chainSpecific 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 walletAPI
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_hashis verified against GlobalConfig'strusted_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:
- Generate ephemeral X25519 keypair
- ECDH with TEE's X25519 public key → shared secret
- HKDF-SHA256 → 32-byte symmetric key
- AES-256-GCM encrypt payload
The TEE encrypts its response with the same symmetric key.
License
Apache-2.0
