AGENT REFERENCE

Skill

A complete operational reference for autonomous agents. This page contains everything needed to install, deploy, publish, consume, browse, and verify Mindstate streams programmatically on Base.

Overview

Mindstate is a protocol and TypeScript SDK for encrypted, versioned AI state with cryptographic commitments. The SDK handles deterministic serialization (RFC 8785), AES-256-GCM encryption, storage upload (IPFS, Arweave, or Filecoin), commitment computation (keccak256), X25519 key wrapping, and integrity verification. Secrets never appear on-chain.

The protocol operates at three tiers, each adding a property:

  • Sealed mode — encryption and integrity verification. No chain, no account. Seal capsules and share keys directly.
  • Registry — adds timestamped provenance and verifiable continuity via on-chain commitments. Publisher-managed access control.
  • Token (ERC-3251) — adds consumptive, market-priced access. Holders burn ERC-20 tokens to redeem. Full DeFi composability.

Repository: github.com/Mindstate-AI

Package: @mindstate/sdk

Chain: Base (chain ID 8453)

Peer dependency: ethers v6

Install

Install the SDK and its peer dependency. The SDK uses ethers v6 for all on-chain interactions.

bash
npm install @mindstate/sdk ethers

Initialize a client with a provider and signer:

typescript
import { ethers } from 'ethers';
import { MindstateClient, DEPLOYMENTS } from '@mindstate/sdk';

const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
const signer = new ethers.Wallet(PRIVATE_KEY, provider);

const client = new MindstateClient({ provider, signer });

// Access deployed contract addresses
const { factory, vault, implementation } = DEPLOYMENTS[8453];

Sealed Mode (No Chain)

Seal capsules with AES-256-GCM encryption and keccak256 commitments. Share the key directly via any channel. No wallet, no provider, no gas. This is the simplest integration path.

typescript
import {
  createCapsule, seal, unseal,
  sealAndUpload, downloadAndUnseal,
  wrapKeyForRecipient, unwrapKeyFromEnvelope,
  generateEncryptionKeyPair, IpfsStorage,
} from '@mindstate/sdk';

// Seal any payload — JSON object or structured data
const capsule = createCapsule({
  model: 'gpt-4o',
  systemPrompt: 'You are a research assistant.',
  memory: ['User prefers concise answers'],
  config: { temperature: 0 },
}, { schema: 'agent-config/v1' });

const sealed = seal(capsule);
// sealed.ciphertext      — encrypted bytes
// sealed.encryptionKey   — 32-byte AES key (share this)
// sealed.stateCommitment — keccak256 of canonical plaintext
// sealed.contentHash     — keccak256 of ciphertext

// Option A: share key directly (DM, API, file)
const keyHex = Buffer.from(sealed.encryptionKey)
  .toString('hex');

// Option B: wrap key for a specific recipient
const sender = generateEncryptionKeyPair();
const recipient = generateEncryptionKeyPair();
const envelope = wrapKeyForRecipient(
  sealed.encryptionKey,
  recipient.publicKey,
  sender.secretKey,
);

// Recipient unwraps and decrypts
const key = unwrapKeyFromEnvelope(
  envelope, recipient.secretKey,
);
const restored = unseal(
  sealed.ciphertext, key,
  sealed.stateCommitment, // optional: verify
  sealed.contentHash,     // optional: verify
);

Upload ciphertext to storage for remote sharing:

typescript
const storage = new IpfsStorage({
  gateway: 'https://ipfs.io',
});

// Seal and upload in one call
const { uri, sealed, receipt } = await sealAndUpload(
  capsule, storage,
);

// Recipient downloads and unseals
const restored = await downloadAndUnseal(
  uri, sealed.encryptionKey, storage,
);

When to use sealed mode: agent state backup and restore, secure handoff between environments, sharing proprietary configs with specific recipients, timestamped proof of content (publish the state commitment publicly, reveal the plaintext later).

Registry (On-Chain Provenance)

Publish commitments to an on-chain ledger for timestamped provenance and verifiable continuity. Same encryption as sealed mode, plus an append-only checkpoint chain on Ethereum. Access is controlled by publisher-managed allowlist — no token, no market.

typescript
import {
  MindstateRegistryClient, RegistryAccessMode,
  createCapsule, IpfsStorage,
} from '@mindstate/sdk';

const client = new MindstateRegistryClient({
  registryAddress: '0x5011E607A02c9960f6BB360477667Ef47ED76739',
  provider,
  signer,
});

// Create a stream — caller becomes the publisher
const streamId = await client.createStream(
  'Agent Alpha State',
  RegistryAccessMode.Allowlist,
);

// Grant read access to specific addresses
await client.addReader(streamId, '0xAlice...');
await client.addReader(streamId, '0xBob...');

// Publish: serialize → encrypt → upload → commit
const storage = new IpfsStorage({
  gateway: 'https://ipfs.io',
});
const capsule = createCapsule({
  model: 'gpt-4o',
  weights: 'lora-v3.safetensors',
  evalScore: 0.91,
});

const { checkpointId, sealedCapsule } =
  await client.publish(streamId, capsule, {
    storage,
    label: 'v3.0-stable',
  });

Deliver keys and consume checkpoints:

typescript
// Publisher: deliver key envelope on-chain
const consumerPubKey = await client.getEncryptionKey(
  '0xAlice...',
);
await client.deliverKeyEnvelope(
  streamId, '0xAlice...', checkpointId,
  envelope.wrappedKey,
  envelope.nonce,
  envelope.senderPublicKey,
);

// Consumer: fetch, decrypt, verify
const { capsule } = await client.consume(
  streamId, checkpointId, {
    keyDelivery: delivery,
    encryptionKeyPair: consumerKeys,
    storage,
  },
);

Browse and query streams:

typescript
// Full timeline (oldest first)
const timeline = await client.getTimeline(streamId);

// Resolve a tag to its checkpoint
const stable = await client.resolveTag(
  streamId, 'stable',
);

// Check reader access
const canRead = await client.isReader(
  streamId, '0xAlice...',
);

When to use the registry: research priority claims (commit hash now, reveal later), model release provenance (tamper-evident version history), regulatory audit trails (timestamped state changes), team collaboration (shared access with on-chain record).

Deployed Contracts (Base Mainnet)

The protocol is live on Base (chain ID 8453). Access addresses programmatically via DEPLOYMENTS[8453].

typescript
import { DEPLOYMENTS } from '@mindstate/sdk';

const base = DEPLOYMENTS[8453];
// base.factory        — MindstateLaunchFactory
// base.vault          — MindstateVault
// base.feeCollector   — FeeCollector
// base.implementation — MindstateToken (clone template)
// base.cloneFactory   — MindstateFactory (lightweight clones)
// base.registry       — MindstateRegistry (standalone ledger)
// base.weth           — WETH on Base
// base.v3Factory      — Uniswap V3 Factory
// base.positionManager — Uniswap V3 NonfungiblePositionManager

Core Types

The fundamental data structures used across the SDK.

typescript
interface Capsule {
  version: string;                    // Protocol version ("1.0.0")
  schema?: string;                    // "agent/v1", "model-weights/v1", etc.
  payload: Record<string, unknown>;   // Anything you want
}

interface IdentityKernel {
  id: string;                         // Persistent identifier (bytes32 hex)
  constraints: Record<string, unknown>;
  selfAmendmentRules?: Record<string, unknown>;
  signature?: string;
}

interface ExecutionManifest {
  modelId: string;                    // e.g. "claude-opus-4-6"
  modelVersion: string;
  toolVersions: Record<string, string>;
  determinismParams: Record<string, unknown>;
  environment: Record<string, unknown>;
  timestamp: string;                  // ISO 8601
}

interface MemorySegment {
  key: string;                        // Addressable name
  contentHash: string;                // Integrity check
  data: unknown;                      // Segment content
}

interface MemoryIndex {
  segments: MemorySegment[];          // Ordered collection
}

// On-chain checkpoint record
interface CheckpointRecord {
  checkpointId: string;               // bytes32 identifier
  predecessorId: string;              // Prior checkpoint (bytes32(0) for genesis)
  stateCommitment: string;            // keccak256 of canonical plaintext
  ciphertextHash: string;             // Hash of encrypted payload
  ciphertextUri: string;              // Storage URI (IPFS CID, ar://, fil://)
  manifestHash: string;               // Optional secondary commitment
  publishedAt: bigint;                // block.timestamp
  blockNumber: bigint;                // block.number
}

// Result of encrypting a capsule
interface SealedCapsule {
  ciphertext: Uint8Array;             // AES-256-GCM encrypted payload
  stateCommitment: string;            // keccak256 of canonical plaintext
  ciphertextHash: string;             // keccak256 of ciphertext
  encryptionKey: Uint8Array;          // 32-byte content key K — store securely
}

// Wrapped key for a specific consumer
interface KeyEnvelope {
  wrappedKey: Uint8Array;             // NaCl box encrypted content key
  nonce: Uint8Array;                  // 24-byte random nonce
  senderPublicKey: Uint8Array;        // Publisher's X25519 public key
}

// Abstract interfaces for extensibility
interface StorageProvider {
  upload(data: Uint8Array): Promise<string>;
  download(uri: string): Promise<Uint8Array>;
}

interface KeyDeliveryProvider {
  storeEnvelope(params: {
    tokenAddress: string; checkpointId: string;
    consumerAddress: string; envelope: KeyEnvelope;
  }): Promise<void>;
  fetchEnvelope(params: {
    tokenAddress: string; checkpointId: string;
    consumerAddress: string;
  }): Promise<KeyEnvelope>;
}

enum RedeemMode {
  PerCheckpoint = 0,  // Each redeem() burns tokens for one checkpoint
  Universal = 1,      // One redeem() grants access to all checkpoints
}

RedeemMode.PerCheckpoint: Use when each checkpoint is independently valuable (e.g. versioned model weights). Consumers choose and pay for specific checkpoints.

RedeemMode.Universal: Use when the full history matters (e.g. agent memory continuity). One burn grants access to every checkpoint, past and future.

Token Tier (ERC-3251)

Deploy a Token Stream

STEP 1

Use the deployed factory for gas-efficient EIP-1167 clones (~100K gas per stream). The implementation contract is already deployed on Base.

solidity
import {IMindstate} from "./interfaces/IMindstate.sol";

// The implementation and factory are already deployed on Base.
// Use DEPLOYMENTS[8453].cloneFactory to get the factory address.

MindstateFactory factory = MindstateFactory(0x8c67b8ff38f4F497c8796AC28547FE93D1Ce1C97);

// Create a per-checkpoint stream
// 1M tokens, 100 burned per redemption
address token = factory.create(
    "Agent Alpha Access",   // name
    "ALPHA",                // symbol
    1_000_000e18,           // totalSupply
    100e18,                 // redeemCost
    IMindstate.RedeemMode.PerCheckpoint
);

Parameters: name and symbol are standard ERC-20. totalSupply is minted to the deployer. redeemCost is the number of tokens burned per redeem() call. RedeemMode is either PerCheckpoint (pay per checkpoint) or Universal (one payment for all checkpoints).

Launchpad (programmatic): to deploy a token with a Uniswap V3 liquidity pool in a single transaction, use the MindstateLaunchFactory directly. This is the same contract the /launch frontend uses. No frontend required — agents can call this directly with ethers.

typescript
import { ethers } from 'ethers';

const LAUNCH_FACTORY = '0x866B4b99be3847a9ed6Db6ce0a02946B839b735A';

const abi = [
  'function launchWithSupply(string name, string symbol, uint256 tokenSupply, uint256 redeemCost, uint8 redeemMode) returns (address token, address pool)',
  'function launch(string name, string symbol, uint256 redeemCost, uint8 redeemMode) returns (address token, address pool)',
  'function getLaunch(address token) view returns (tuple(address token, address creator, uint256 tokenSupply, uint256 redeemCost, uint8 redeemMode, address pool, uint256 createdAt, string name, string symbol))',
  'function getLaunches(uint256 offset, uint256 limit) view returns (address[])',
  'function getLaunchCount() view returns (uint256)',
  'function getCreatorTokens(address creator) view returns (address[])',
  'function getCreatorTokenCount(address creator) view returns (uint256)',
  'function isLaunch(address token) view returns (bool)',
  'function getPool(address token) view returns (address)',
  'event MindstateLaunched(address indexed token, address indexed creator, address indexed pool, string name, string symbol, uint256 tokenSupply, uint256 redeemCost, uint8 redeemMode)',
];

const factory = new ethers.Contract(
  LAUNCH_FACTORY, abi, signer,
);

// Deploy token + Uniswap V3 pool in one transaction
const tx = await factory.launchWithSupply(
  'Agent Alpha Access',             // name
  'ALPHA',                          // symbol
  ethers.parseEther('1000000'),     // totalSupply (1M tokens)
  ethers.parseEther('100'),         // redeemCost (100 per redeem)
  1,                                // 0 = PerCheckpoint, 1 = Universal
);

const receipt = await tx.wait();

// Parse the MindstateLaunched event
const iface = new ethers.Interface(abi);
for (const log of receipt.logs) {
  try {
    const parsed = iface.parseLog({
      topics: log.topics, data: log.data,
    });
    if (parsed?.name === 'MindstateLaunched') {
      console.log('Token:', parsed.args.token);
      console.log('Pool:', parsed.args.pool);
    }
  } catch {}
}

Query existing launches:

typescript
// Read-only — no signer needed
const factory = new ethers.Contract(
  LAUNCH_FACTORY, abi, provider,
);

// Total launches
const count = await factory.getLaunchCount();

// Paginated list of token addresses
const tokens = await factory.getLaunches(0, 20);

// Full metadata for a specific token
const info = await factory.getLaunch(tokenAddress);
// info.token, info.creator, info.pool,
// info.tokenSupply, info.redeemCost, info.redeemMode,
// info.createdAt, info.name, info.symbol

// All tokens by a specific creator
const myTokens = await factory.getCreatorTokens(
  myAddress,
);

// Check if an address is a launchpad token
const valid = await factory.isLaunch(tokenAddress);

// Get the V3 pool for a token
const pool = await factory.getPool(tokenAddress);

Publish a Checkpoint

STEP 2

Serialize a capsule, encrypt it, upload to storage, and commit on-chain. The publish() method handles the entire flow.

typescript
import {
  MindstateClient, createCapsule, createAgentCapsule,
  generateEncryptionKeyPair, IpfsStorage,
  // For on-chain delivery (recommended on Base):
  OnChainKeyDelivery, OnChainPublisherKeyManager,
  // For off-chain delivery (IPFS-based):
  // StorageKeyDelivery, PublisherKeyManager,
} from '@mindstate/sdk';

// --- Storage ---
const storage = new IpfsStorage({
  gateway: 'https://ipfs.io',
  apiUrl: 'http://localhost:5001',  // Local IPFS node
});

// --- Key management (choose one) ---

// Option A: On-chain delivery (recommended on Base — no IPFS needed for keys)
const publisherKeys = generateEncryptionKeyPair();
const delivery = new OnChainKeyDelivery(signer);
const keyManager = new OnChainPublisherKeyManager(publisherKeys, delivery);

// Option B: Off-chain delivery (IPFS-based)
// const delivery = new StorageKeyDelivery(storage);
// const keyManager = new PublisherKeyManager(publisherKeys, delivery);

// --- Build a capsule ---

// Generic capsule — any payload
const capsule = createCapsule(
  { model: 'gpt-4-turbo', weights: '...', config: { temperature: 0 } },
  { schema: 'model-weights/v1' },
);

// Or use the agent/v1 convention for AI agent state
const agentCapsule = createAgentCapsule({
  identityKernel: {
    id: '0xabc...def',
    constraints: { purpose: 'autonomous-agent' },
  },
  executionManifest: {
    modelId: 'claude-opus-4-6',
    modelVersion: '2025-05-14',
    toolVersions: { web: '1.0.0', code: '2.0.0' },
    determinismParams: { temperature: 0 },
    environment: { runtime: 'node' },
    timestamp: new Date().toISOString(),
  },
  // Optional: structured memory
  // memoryIndex: { segments: [{ key: 'context', contentHash: '...', data: {...} }] },
});

// --- Publish: serialize -> encrypt -> upload -> commit ---
const client = new MindstateClient({ provider, signer });
const { checkpointId, sealedCapsule } =
  await client.publish(tokenAddress, capsule, { storage });

// IMPORTANT: Store the encryption key for future consumer deliveries.
// This key is needed to fulfill redemptions. Lose it and the checkpoint
// is permanently undeliverable.
keyManager.storeKey(checkpointId, sealedCapsule.encryptionKey);

// Optional: tag the checkpoint for easy resolution
await client.tagCheckpoint(tokenAddress, checkpointId, 'stable');

Fulfill Redemptions

STEP 3

When a consumer burns tokens on-chain, the publisher wraps the decryption key for that consumer and delivers it. Two delivery methods are available.

On-chain delivery (recommended on Base)

Keys are delivered via the contract's deliverKeyEnvelope() function. No IPFS, no index URI. Consumers read directly from contract state (free, no gas).

typescript
import {
  OnChainKeyDelivery, OnChainPublisherKeyManager,
  generateEncryptionKeyPair, MINDSTATE_ABI,
} from '@mindstate/sdk';

const publisherKeys = generateEncryptionKeyPair();
const delivery = new OnChainKeyDelivery(signer);
const keyManager = new OnChainPublisherKeyManager(publisherKeys, delivery);

// Load stored content keys (from when you published)
keyManager.storeKey(checkpointId, contentKey);

// Listen for Redeemed events, then for each:
const consumerPubKey = await client.getEncryptionKey(
  tokenAddress,
  consumerAddress,
);

await keyManager.fulfillRedemption(
  tokenAddress,
  checkpointId,
  consumerAddress,
  ethers.getBytes(consumerPubKey),
);
// Done — consumer can now read the envelope from the contract

Off-chain delivery (IPFS / Arweave)

Keys are uploaded as encrypted envelopes to a StorageProvider. The publisher maintains an index URI that consumers load to find their envelopes.

typescript
import {
  StorageKeyDelivery, PublisherKeyManager,
  IpfsStorage, generateEncryptionKeyPair,
} from '@mindstate/sdk';

const storage = new IpfsStorage({ gateway: 'https://ipfs.io' });
const publisherKeys = generateEncryptionKeyPair();
const delivery = new StorageKeyDelivery(storage);
const keyManager = new PublisherKeyManager(publisherKeys, delivery);

// Load stored content keys
keyManager.storeKey(checkpointId, contentKey);

// Fulfill:
const consumerPubKey = await client.getEncryptionKey(
  tokenAddress, consumerAddress,
);
await keyManager.fulfillRedemption(
  tokenAddress, checkpointId, consumerAddress,
  ethers.getBytes(consumerPubKey),
);

// Publish the updated index so consumers can discover envelopes
const indexUri = await delivery.publishIndex();
// Share indexUri with consumers

The publisher must be online to observe Redeemed events and fulfill them. See the Auto-Fulfiller section below for a turnkey watcher script.

Consume a Checkpoint

STEP 4

Register an encryption key, redeem access by burning tokens, fetch the key envelope, decrypt, and verify. The consume() method handles the full flow with pre-flight envelope verification before burning tokens.

typescript
import {
  MindstateClient, generateEncryptionKeyPair,
  // Choose one delivery method:
  OnChainKeyDelivery,       // On-chain (recommended on Base)
  // StorageKeyDelivery,    // Off-chain (IPFS-based)
  IpfsStorage,
} from '@mindstate/sdk';

const storage = new IpfsStorage({ gateway: 'https://ipfs.io' });
const consumerKeys = generateEncryptionKeyPair();
const client = new MindstateClient({ provider, signer });

// REQUIRED: Register your X25519 public key on-chain (once per token).
// The publisher reads this to wrap the decryption key for you.
await client.registerEncryptionKey(tokenAddress, consumerKeys.publicKey);

// --- On-chain delivery ---
const delivery = new OnChainKeyDelivery(provider);  // Read-only, no signer

// --- Off-chain delivery ---
// const delivery = new StorageKeyDelivery(storage);
// await delivery.loadIndex(publisherIndexUri);

// Get the latest checkpoint (or a specific one)
const head = await client.getHead(tokenAddress);

// Consume: verifies envelope exists -> burns tokens -> decrypts -> verifies
const { capsule, checkpoint } = await client.consume(tokenAddress, head, {
  keyDelivery: delivery,
  encryptionKeyPair: consumerKeys,
  storage,
});

// capsule.payload contains the decrypted state
// capsule.schema tells you the format (e.g. "agent/v1")
// checkpoint has the on-chain record (predecessorId, publishedAt, etc.)

Pre-flight check: The consume() method verifies that a key envelope exists for the consumer before burning tokens. If no envelope is found (publisher hasn't fulfilled yet), it throws rather than wasting tokens.

Browse and Discover

STEP 5

Read-only exploration of checkpoint streams. No signer or tokens required.

typescript
import { MindstateExplorer } from '@mindstate/sdk';

const explorer = new MindstateExplorer(provider);

// Full timeline (oldest first)
const timeline = await explorer.getTimeline(tokenAddress);

// 5 most recent checkpoints (newest first)
const recent = await explorer.getRecent(tokenAddress, 5);

// Resolve a tag to its full checkpoint record
const stable = await explorer.resolveTag(tokenAddress, 'stable');

// Walk lineage from any checkpoint back to genesis
const lineage = await explorer.getLineage(tokenAddress, checkpointId);

// All tags for a token
const tags = await explorer.getAllTags(tokenAddress);
// Map<string, string> — tag name -> checkpoint ID

// Enriched timeline — on-chain tags + off-chain descriptions
const enriched = await explorer.getEnrichedTimeline(tokenAddress, {
  storage,
  indexUri: publisherIndexUri,  // Off-chain delivery only
});

Storage Architecture

Mindstate supports a three-tier storage model. Use StorageRouter to auto-route downloads by URI scheme.

HotIPFSActive development, recent checkpoints
WarmFilecoinProduction snapshots, compliance archives
ColdArweaveCanonical releases, genesis states (~$8/GB one-time)
typescript
import {
  StorageRouter, IpfsStorage, ArweaveStorage, FilecoinStorage,
  DefaultTierPolicy, PromotionTierPolicy,
} from '@mindstate/sdk';

// Multi-backend router — auto-routes downloads by URI prefix
const router = new StorageRouter();
router.register('ipfs', new IpfsStorage({ gateway: 'https://ipfs.io' }));
router.register('arweave', new ArweaveStorage({ gateway: 'https://arweave.net' }));
router.register('filecoin', new FilecoinStorage({ gateway: 'https://...' }));
router.setDefaultProvider(router.getProvider('ipfs')!);

// Downloads auto-route: ipfs://... -> IPFS, ar://... -> Arweave, etc.
const data = await router.download(ciphertextUri);

// --- Tier policies for publish-time routing ---

// DefaultTierPolicy: everything goes to hot (IPFS)
const basic = new DefaultTierPolicy();

// PromotionTierPolicy: auto-promotes based on tags/labels
const smart = new PromotionTierPolicy({
  coldTags: ['stable', 'release', 'canonical', 'genesis'],
  warmTags: ['archive', 'compliance', 'audit'],
  promoteGenesis: true,  // Genesis checkpoints auto-promote to cold
});

// Use with publish:
const { checkpointId } = await client.publish(tokenAddress, capsule, {
  storage: router,
  tierPolicy: smart,
  tierProviders: { hot: ipfs, warm: filecoin, cold: arweave },
});

MindstateClient API

Full method reference for the high-level client.

Read operations (provider only)

getHead(token)Latest checkpoint ID (bytes32)
getCheckpoint(token, id)Full checkpoint record
getCheckpointCount(token)Total checkpoint count
getRedeemMode(token)RedeemMode enum value
getRedeemCost(token)Tokens burned per redeem (bigint)
hasRedeemed(token, account, id)Check if account redeemed
getEncryptionKey(token, account)Account's X25519 public key
getPublisher(token)Publisher address

Write operations (signer required)

publish(token, capsule, opts)Full flow: serialize, encrypt, upload, commit
consume(token, id, opts)Full flow: verify envelope, burn, decrypt, verify
publishCheckpoint(...)Low-level: commit pre-sealed checkpoint
redeem(token, id)Burn tokens to redeem a checkpoint
registerEncryptionKey(token, pk)Register X25519 public key (once)
tagCheckpoint(token, id, tag)Tag a checkpoint (e.g. "stable")
resolveTag(token, tag)Resolve tag to checkpoint ID
updateCiphertextUri(token, id, uri)Migrate storage URI (e.g. IPFS to Arweave)

Explorer methods (read-only, no signer)

getTimeline(token)Full history, oldest first
getRecent(token, count)N most recent, newest first
getLineage(token, id)Walk predecessors to genesis
resolveTag(token, tag)Tag to full checkpoint record
getAllTags(token)All tag assignments (Map)
getEnrichedTimeline(token, opts)Tags + off-chain descriptions

Verification and Utilities

Standalone functions for capsule construction, encryption, commitment computation, and verification.

Capsule construction

typescript
import {
  createCapsule, createAgentCapsule,
  serializeCapsule, deserializeCapsule,
} from '@mindstate/sdk';

const capsule = createCapsule(payload, { schema: 'my-app/v1' });
const agentCapsule = createAgentCapsule({
  identityKernel, executionManifest, memoryIndex,
});

const bytes = serializeCapsule(capsule);    // RFC 8785 canonical JSON
const restored = deserializeCapsule(bytes); // Parse and validate

Encryption

typescript
import {
  generateContentKey, encrypt, decrypt,
  generateEncryptionKeyPair, wrapKey, unwrapKey,
} from '@mindstate/sdk';

// Symmetric (AES-256-GCM)
const key = generateContentKey();                // 32 bytes
const sealed = encrypt(plaintext, key);          // IV || ciphertext || tag
const plain = decrypt(sealed, key);

// Asymmetric (X25519 NaCl box — for key wrapping)
const keyPair = generateEncryptionKeyPair();
const envelope = wrapKey(contentKey, recipientPubKey, senderSecretKey);
const unwrapped = unwrapKey(envelope, recipientSecretKey);

Commitments

typescript
import {
  computeStateCommitment, computeCiphertextHash,
  computeMetadataHash,
} from '@mindstate/sdk';

const sc = computeStateCommitment(capsule);      // keccak256(canonicalize(capsule))
const ch = computeCiphertextHash(ciphertext);    // keccak256(ciphertext)
const mh = computeMetadataHash(arbitraryValue);  // keccak256(canonicalize(value))

Verification

typescript
import {
  verifyStateCommitment, verifyCiphertextHash,
  verifyCheckpointLineage, verifyAndDecrypt,
} from '@mindstate/sdk';

// Individual checks (throw on mismatch)
verifyStateCommitment(capsule, expectedCommitment);
verifyCiphertextHash(ciphertext, expectedHash);
verifyCheckpointLineage(checkpoints);  // Linked-list integrity

// Full verify + decrypt in one call
const capsule = verifyAndDecrypt(
  ciphertext, key, expectedStateCommitment, expectedCiphertextHash,
);

Security Model

Understanding the cryptographic guarantees is essential for safe integration.

Content encryption: AES-256-GCM with a random 32-byte key K. Format: IV (12 bytes) || ciphertext || auth tag (16 bytes). K never appears on-chain.

Key wrapping: NaCl box (X25519 ECDH + XSalsa20-Poly1305). The content key K is wrapped for a specific consumer using their registered X25519 public key. The resulting KeyEnvelope contains: the wrapped key (encrypted), a 24-byte random nonce, and the sender's public key.

Envelope safety: Key envelopes are indistinguishable from random noise to anyone who does not hold the consumer's X25519 private key. They are safe to store on IPFS, on-chain, or anywhere else. The security comes from the encryption, not the transport. An attacker who reads the envelope sees only the encrypted key, nonce, and sender public key. Decryption requires computing an ECDH shared secret, which requires the consumer's or publisher's private key. Neither is ever stored on-chain.

State commitments: keccak256(canonicalize(capsule)) is stored on-chain. After decryption, consumers verify the commitment matches. This proves the decrypted content is exactly what the publisher committed to.

Lineage integrity: Each checkpoint stores its predecessor's ID (bytes32(0) for genesis). verifyCheckpointLineage() walks the chain and verifies linked-list integrity, proving no checkpoints were inserted or removed.

Auto-Fulfiller (Turnkey Watcher)

A runnable script that watches for Redeemed events and automatically delivers decryption keys on-chain. No servers, no IPFS. Run it locally and consumers can fetch keys directly from the contract.

bash
cd sdk && npx tsx examples/auto-fulfiller-onchain.ts

Required environment variables:

MINDSTATE_TOKENToken contract address (0x...)
RPC_URLJSON-RPC endpoint (e.g. https://mainnet.base.org)
PUBLISHER_KEYPublisher's Ethereum private key
PUBLISHER_X25519Publisher's X25519 secret key (64 hex chars)

Optional:

CREATED_AT_BLOCKToken deploy block (enables catch-up on boot)
KEY_STORE_PATHPath to key JSON (default: .mindstate-keys.json)

Key store format (.mindstate-keys.json):

json
{
  "0xf825...2a417": "0x<content-key-hex>",
  "0x9337...93473": "0x<content-key-hex>"
}

The script polls every 12 seconds. On startup with CREATED_AT_BLOCK, it catches up on all historical redemptions and skips those already delivered. Add new content keys to the JSON file each time you publish a checkpoint.

Contract ABI

The full MindstateToken ABI is exported as MINDSTATE_ABI and the zero-value constant as ZERO_BYTES32.

typescript
import { MINDSTATE_ABI, ZERO_BYTES32 } from '@mindstate/sdk';
import { ethers } from 'ethers';

const contract = new ethers.Contract(tokenAddress, MINDSTATE_ABI, signer);

// View functions
await contract.publisher();                        // address
await contract.head();                             // bytes32
await contract.checkpointCount();                  // uint256
await contract.getCheckpoint(checkpointId);        // tuple
await contract.getCheckpointIdAtIndex(0);          // bytes32
await contract.resolveTag('stable');               // bytes32
await contract.getCheckpointTag(checkpointId);     // string
await contract.redeemMode();                       // uint8 (0 or 1)
await contract.redeemCost();                       // uint256
await contract.hasRedeemed(account, checkpointId); // bool
await contract.getEncryptionKey(account);          // bytes32
await contract.balanceOf(account);                 // uint256
await contract.hasKeyEnvelope(account, cpId);      // bool
await contract.getKeyEnvelope(account, cpId);      // (bytes, bytes24, bytes32)

// State-changing functions
await contract.publish(stateCommitment, ciphertextHash, ciphertextUri, manifestHash, label);
await contract.redeem(checkpointId);
await contract.registerEncryptionKey(publicKeyBytes32);
await contract.tagCheckpoint(checkpointId, 'stable');
await contract.updateCiphertextUri(checkpointId, newUri);
await contract.deliverKeyEnvelope(consumer, cpId, wrappedKey, nonce, senderPubKey);
await contract.transferPublisher(newPublisher);

// Events to listen for
// CheckpointPublished, CheckpointTagged, Redeemed,
// EncryptionKeyRegistered, CiphertextUriUpdated,
// PublisherTransferred, KeyEnvelopeDelivered

Indexer REST API

For applications that need fast queries across many tokens, run the standalone indexer service. REST API at http://localhost:3000:

bash
cd indexer && npm install && npm run build
FACTORY_ADDRESS=0x866B4b99be3847a9ed6Db6ce0a02946B839b735A \
RPC_URL=https://mainnet.base.org \
npm start
GET/tokensList all indexed tokens
GET/tokens/:address/checkpointsTimeline (?backend= filter)
GET/tokens/:address/checkpoints/:idSingle checkpoint details
GET/tokens/:address/checkpoints/:id/migrationsURI migration history
GET/tokens/:address/storageStorage backend distribution
GET/tokens/:address/tagsAll tags for a token
GET/tokens/:address/tags/:tagResolve a tag to its checkpoint
GET/tokens/:address/latestMost recent checkpoint
GET/healthIndexer status

Common Patterns and Failure Modes

Reference for handling typical scenarios and errors.

Consumer has no encryption key: getEncryptionKey() returns ZERO_BYTES32. The consumer must call registerEncryptionKey() before redeeming. Cannot deliver without it.

Insufficient token balance: redeem() reverts if the consumer's balance is less than redeemCost. Check with balanceOf() before calling.

Already redeemed: In PerCheckpoint mode, redeem() reverts if the consumer already redeemed that specific checkpoint. Check with hasRedeemed(account, checkpointId).

No envelope yet: consume() performs a pre-flight check — if no envelope is found, it throws before burning tokens. Wait for the publisher to fulfill.

Lost content key: If the publisher loses a content key, that checkpoint is permanently undeliverable. Always persist keys immediately after publish().

Storage migration: If an IPFS CID becomes unpinned, use updateCiphertextUri() to migrate to a new storage URI (e.g. Arweave). The ciphertextHash commitment still holds — the encrypted bytes are identical.

Publisher transfer: transferPublisher(newAddress) transfers publish rights to a new address. Only the current publisher can call this.