Sign In

@hypercerts-org/sdk-core

Package Overview
Dependencies
Maintainers
5
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hypercerts-org/sdk-core

Framework-agnostic ATProto SDK core for authentication, repository operations, and lexicon management

latest
Source
npmnpm
Version
0.10.0-beta.9
Version published
Weekly downloads
105
1650%
Maintainers
5
Weekly downloads
 
Created
Source

@hypercerts-org/sdk-core

Framework-agnostic ATProto SDK for Hypercerts. Create, manage, and collaborate on hypercerts using the AT Protocol.

pnpm add @hypercerts-org/sdk-core

Quick Start

import { createATProtoSDK } from "@hypercerts-org/sdk-core";

// 1. Create SDK with OAuth configuration
const sdk = createATProtoSDK({
  oauth: {
    clientId: "https://your-app.com/client-metadata.json",
    redirectUri: "https://your-app.com/callback",
    scope: "atproto",
    jwksUri: "https://your-app.com/jwks.json",
    jwkPrivate: process.env.ATPROTO_JWK_PRIVATE!,
  },
  // Optional: URL for handle resolution during OAuth.
  // If omitted, DNS-based resolution is used.
  handleResolver: "https://pds-eu-west4.test.certified.app",
});

// 2. Authenticate user
const authUrl = await sdk.authorize("user.bsky.social");
// Redirect user to authUrl...

// 3. Handle OAuth callback
const session = await sdk.callback(callbackParams);

// 4. Get repository and start creating hypercerts
const repo = sdk.getRepository(session);
const claim = await repo.hypercerts.create({
  title: "Tree Planting Initiative 2025",
  shortDescription: "1000 trees planted in rainforest",
  description: "Planted 1000 trees in the Amazon rainforest region",
  workScope: "Environmental Conservation",
  startDate: "2025-01-01",
  endDate: "2025-12-31",
  rights: {
    name: "Attribution",
    type: "license",
    description: "CC-BY-4.0",
  },
});

Local Development

For local development and testing, you can use HTTP loopback URLs with localhost or 127.0.0.1:

NextJS App Router Example

// lib/atproto.ts
import { createATProtoSDK } from "@hypercerts-org/sdk-core";

const sdk = createATProtoSDK({
  oauth: {
    // Use localhost for client_id (loopback client)
    clientId: "http://localhost/",

    // Use 127.0.0.1 with your app's port for redirect
    redirectUri: "http://127.0.0.1:3000/api/auth/callback",

    scope: "atproto",

    // Serve JWKS from your app
    jwksUri: "http://127.0.0.1:3000/.well-known/jwks.json",

    // Load from environment variable
    jwkPrivate: process.env.ATPROTO_JWK_PRIVATE!,

    // Optional: suppress warnings
    developmentMode: true,
  },
  // Optional: handle resolver for local testing
  handleResolver: "http://localhost:2583",
  logger: console, // Enable debug logging
});

export default sdk;

API Route Setup

// app/api/auth/callback/route.ts
import sdk from "@/lib/atproto";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);

  try {
    const session = await sdk.callback(searchParams);

    // Store session (use secure httpOnly cookie in production)
    return Response.json({ success: true, did: session.sub });
  } catch (error) {
    return Response.json({ error: "Authentication failed" }, { status: 401 });
  }
}

Serve JWKS Endpoint

// app/.well-known/jwks.json/route.ts
export async function GET() {
  const jwk = JSON.parse(process.env.ATPROTO_JWK_PRIVATE!);

  // Return public keys only (remove private key 'd' parameter)
  const publicKeys = jwk.keys.map(({ d, ...publicKey }) => publicKey);

  return Response.json({ keys: publicKeys });
}

Environment Variables

# .env.local
ATPROTO_JWK_PRIVATE='{"keys":[{"kty":"EC","crv":"P-256",...}]}'

Important Notes

Authorization Server Support: The AT Protocol OAuth spec makes loopback support optional. Most AT Protocol servers support loopback clients for development, but verify your target authorization server supports this feature.

Port Matching: Redirect URIs can use any port - the authorization server ignores port numbers when validating loopback redirects (only the path must match).

Production Use: ⚠️ Never use HTTP loopback URLs in production. Always use HTTPS with proper TLS certificates.

IP vs Hostname: Use http://localhost/ for clientId and http://127.0.0.1:<port> for redirectUri and jwksUri (this is the recommended pattern per AT Protocol spec).

Core Concepts

1. PDS vs SDS: Understanding Server Types

The SDK supports two types of AT Protocol servers:

Personal Data Server (PDS)

  • Purpose: User's own data storage (e.g., Bluesky)
  • Use case: Individual hypercerts, personal records
  • Features: Profile management, basic CRUD operations
  • Auto-detected: The SDK automatically discovers the user's PDS from the OAuth session -- no configuration needed

Shared Data Server (SDS)

  • Purpose: Collaborative data storage with access control
  • Use case: Organization hypercerts, team collaboration
  • Features: Organizations, multi-user access, role-based permissions
  • Configured via: servers.sds in SDK config
// Connect to user's PDS (default) -- auto-detected from session
const pdsRepo = sdk.repository(session);
await pdsRepo.hypercerts.create({ ... }); // Creates in user's PDS

// Connect to SDS for collaboration features
const sdsRepo = sdk.repository(session, { server: "sds" });
await sdsRepo.organizations.create({ name: "My Org" }); // SDS-only feature

// Switch to organization repository (still on SDS)
const orgs = await sdsRepo.organizations.list();
const orgRepo = sdsRepo.repo(orgs.organizations[0].did);
await orgRepo.hypercerts.list(); // Queries organization's hypercerts on SDS

How PDS Auto-Detection Works

The SDK automatically discovers each user's PDS URL from their OAuth session. You do not need to configure a PDS URL.

  • During authentication (callback() or restoreSession()), the SDK extracts the user's PDS URL from the OAuth token's aud field, which is resolved from the user's DID Document.

  • When creating a repository (sdk.repository(session)), the SDK uses the cached PDS URL for that session's DID.

  • For sessions created outside the SDK, you can manually resolve the PDS:

    await sdk.resolveSessionPds(session);
    

How Repository Routing Works

The SDK uses a ConfigurableAgent to route requests to different servers while maintaining your OAuth authentication:

  • Initial Repository Creation

    // User authenticates -- PDS URL is automatically cached from the session
    const session = await sdk.callback(params);
    
    // Create PDS repository -- routes to user's auto-detected PDS
    const pdsRepo = sdk.repository(session);
    
    // Create SDS repository -- routes to configured SDS server
    const sdsRepo = sdk.repository(session, { server: "sds" });
    
  • Switching Repositories with .repo()

    // Start with user's SDS repository
    const userSdsRepo = sdk.repository(session, { server: "sds" });
    
    // Switch to organization's repository
    const orgRepo = userSdsRepo.repo("did:plc:org-did");
    
    // All operations on orgRepo still route to SDS, not user's PDS
    await orgRepo.hypercerts.list(); // Queries SDS
    await orgRepo.collaborators.list(); // Queries SDS
    
  • Key Implementation Details

    • Each Repository uses a ConfigurableAgent that wraps your OAuth session's fetch handler
    • The agent routes all requests to the specified server URL (PDS, SDS, or custom)
    • The user's PDS URL is auto-detected from the OAuth session's token info (tokenInfo.aud)
    • When you call .repo(did), a new Repository is created with the same server configuration
    • Your OAuth session provides authentication (DPoP, access tokens), while the agent handles routing
    • This enables simultaneous connections to multiple servers with one authentication session

Common Patterns

// Pattern 1: Personal hypercerts on PDS (auto-detected)
const myRepo = sdk.repository(session);
await myRepo.hypercerts.create({ title: "My Personal Impact" });

// Pattern 2: Organization hypercerts on SDS
const sdsRepo = sdk.repository(session, { server: "sds" });
const orgRepo = sdsRepo.repo(organizationDid);
await orgRepo.hypercerts.create({ title: "Team Impact" });

// Pattern 3: Reading another user's hypercerts
const otherUserRepo = myRepo.repo("did:plc:other-user");
await otherUserRepo.hypercerts.list(); // Read-only access to their PDS

// Pattern 4: Collaborating on organization data
const sdsRepo = sdk.repository(session, { server: "sds" });
await sdsRepo.collaborators.grant({
  userDid: "did:plc:teammate",
  role: "editor",
});
const orgRepo = sdsRepo.repo(organizationDid);
// Teammate can now access orgRepo and create hypercerts

2. Authentication & OAuth Permissions

The SDK uses OAuth 2.0 for authentication with granular permission control.

Basic Authentication

// First-time user authentication
const authUrl = await sdk.authorize("user.bsky.social");
// Redirect user to authUrl to complete OAuth flow

// Handle the OAuth callback
const session = await sdk.callback({
  code: "...",
  state: "...",
  iss: "...",
});

// Restore existing session for returning users
const session = await sdk.restoreSession("did:plc:user123");

// Get repository for authenticated user
const repo = sdk.getRepository(session);

OAuth Scopes & Permissions

Control exactly what your app can access using type-safe permission builders:

import { PermissionBuilder, ScopePresets, buildScope } from "@hypercerts-org/sdk-core";

// Use ready-made presets
const scope = ScopePresets.EMAIL_AND_PROFILE; // Request email + profile access
const scope = ScopePresets.POSTING_APP; // Full posting capabilities

// Or build custom permissions
const scope = buildScope(
  new PermissionBuilder()
    .accountEmail("read") // Read user's email
    .repoWrite("app.bsky.feed.post") // Create/update posts
    .blob(["image/*", "video/*"]) // Upload media
    .build(),
);

// Use in OAuth configuration
const sdk = createATProtoSDK({
  oauth: {
    clientId: "your-client-id",
    redirectUri: "https://your-app.com/callback",
    scope: scope, // Your custom scope
    // ... other config
  },
});

Available Presets:

  • EMAIL_READ - User's email address
  • PROFILE_READ / PROFILE_WRITE - Profile access
  • POST_WRITE - Create posts
  • SOCIAL_WRITE - Likes, reposts, follows
  • MEDIA_UPLOAD - Image and video uploads
  • POSTING_APP - Full posting with media
  • EMAIL_AND_PROFILE - Common combination

See OAuth Permissions Documentation for detailed usage.

3. Working with Hypercerts

Creating a Hypercert

const hypercert = await repo.hypercerts.create({
  title: "Climate Research Project",
  description: "Research on carbon capture technologies",
  image: imageBlob, // optional: File or Blob
  externalUrl: "https://example.com/project",

  impact: {
    scope: ["Climate Change", "Carbon Capture"],
    work: {
      from: "2024-01-01",
      to: "2025-12-31",
    },
    contributors: ["did:plc:researcher1", "did:plc:researcher2"],
  },

  rights: {
    license: "CC-BY-4.0",
    allowsDerivatives: true,
    transferrable: false,
  },
});

console.log("Created hypercert:", hypercert.uri);

Retrieving Hypercerts

// Get a specific hypercert by URI
const hypercert = await repo.hypercerts.get("at://did:plc:user123/org.hypercerts.claim/abc123");

// List all hypercerts in the repository
const { records } = await repo.hypercerts.list();
for (const claim of records) {
  console.log(claim.value.title);
}

// List with pagination
const { records, cursor } = await repo.hypercerts.list({ limit: 10 });
if (cursor) {
  const nextPage = await repo.hypercerts.list({ limit: 10, cursor });
}

Updating a Hypercert

// Update an existing hypercert
await repo.hypercerts.update("at://did:plc:user123/org.hypercerts.claim/abc123", {
  title: "Updated Climate Research Project",
  description: "Expanded scope to include renewable energy",
  impact: {
    scope: ["Climate Change", "Carbon Capture", "Renewable Energy"],
    work: { from: "2024-01-01", to: "2026-12-31" },
    contributors: ["did:plc:researcher1", "did:plc:researcher2"],
  },
});

Deleting a Hypercert

await repo.hypercerts.delete("at://did:plc:user123/org.hypercerts.claim/abc123");

4. Contributions and Measurements

Adding Contributions

// Add a contribution to a hypercert
const contribution = await repo.hypercerts.addContribution({
  claim: "at://did:plc:user123/org.hypercerts.claim/abc123",
  contributor: "did:plc:contributor456",
  description: "Led the research team and conducted field studies",
  contributionType: "Work",
  percentage: 40.0,
});

Adding Measurements

// Add a measurement/evaluation
const measurement = await repo.hypercerts.addMeasurement({
  claim: "at://did:plc:user123/org.hypercerts.claim/abc123",
  type: "Impact",
  value: 1000,
  unit: "trees planted",
  verifiedBy: "did:plc:auditor789",
  verificationMethod: "On-site inspection with GPS verification",
  measuredAt: new Date().toISOString(),
});

5. Collections and Projects

Collections organize multiple hypercerts into logical groupings. Projects are a special type of collection with type="project".

Creating a Collection

// Create a collection with weighted items
const collection = await repo.hypercerts.createCollection({
  title: "Climate Projects 2024",
  shortDescription: "Our climate impact portfolio",
  description: "A curated collection of climate-related hypercerts",
  items: [
    {
      itemIdentifier: { uri: hypercert1Uri, cid: hypercert1Cid },
      itemWeight: "0.5",
    },
    {
      itemIdentifier: { uri: hypercert2Uri, cid: hypercert2Cid },
      itemWeight: "0.3",
    },
    {
      itemIdentifier: { uri: hypercert3Uri, cid: hypercert3Cid },
      itemWeight: "0.2",
    },
  ],
  avatar: avatarBlob, // optional
  banner: bannerBlob, // optional
});

console.log("Created collection:", collection.uri);

Creating a Project

Projects are collections with automatic type="project":

const project = await repo.hypercerts.createProject({
  title: "Rainforest Restoration",
  shortDescription: "Multi-year restoration initiative",
  description: "Comprehensive rainforest restoration project",
  items: [
    {
      itemIdentifier: { uri: activity1Uri, cid: activity1Cid },
      itemWeight: "0.6",
    },
    {
      itemIdentifier: { uri: activity2Uri, cid: activity2Cid },
      itemWeight: "0.4",
    },
  ],
});

Attaching Locations

Both collections and projects can have location data attached as a sidecar record:

// Attach location to a project
const locationResult = await repo.hypercerts.attachLocationToProject(projectUri, {
  lpVersion: "1.0",
  srs: "EPSG:4326",
  locationType: "coordinate-decimal",
  location: "https://example.com/location.geojson", // or use a Blob
  name: "Project Site",
  description: "Main restoration site coordinates",
});

// Also works for collections
await repo.hypercerts.attachLocationToCollection(collectionUri, locationParams);

Listing and Retrieving

// Get a specific collection
const collection = await repo.hypercerts.getCollection(collectionUri);

// List all collections
const { records } = await repo.hypercerts.listCollections();

// Get a specific project
const project = await repo.hypercerts.getProject(projectUri);

// List all projects
const { records } = await repo.hypercerts.listProjects();

Updating Collections and Projects

// Update collection
await repo.hypercerts.updateCollection(collectionUri, {
  title: "Updated Title",
  items: [
    /* updated items */
  ],
  avatar: newAvatarBlob, // or null to remove
});

// Update project (same API)
await repo.hypercerts.updateProject(projectUri, {
  shortDescription: "Updated description",
  banner: null, // removes banner
});

Deleting

// Delete collection
await repo.hypercerts.deleteCollection(collectionUri);

// Delete project
await repo.hypercerts.deleteProject(projectUri);

// Remove location from project
await repo.hypercerts.removeLocationFromProject(projectUri);

6. Blob Operations (Images & Files)

// Upload an image or file
const blobResult = await repo.blobs.upload(imageFile);
console.log("Blob uploaded:", blobResult.ref.$link);

// Download a blob
const blobData = await repo.blobs.get("did:plc:user123", "bafyreiabc123...");

7. Organizations (SDS only)

Organizations allow multiple users to collaborate on shared repositories.

// Create an organization
const org = await repo.organizations.create({
  name: "Climate Research Institute",
  description: "Leading research on climate solutions",
  handle: "climate-research", // optional: unique handle
});

console.log("Organization DID:", org.did);

// List all organizations you belong to
const { organizations } = await repo.organizations.list();
for (const org of organizations) {
  console.log(`${org.name} (${org.role})`);
}

// List with pagination
const { organizations, cursor } = await repo.organizations.list({ limit: 10 });

// Get a specific organization
const org = await repo.organizations.get("did:plc:org123");
console.log(`${org.name} - ${org.description}`);

8. Collaborator Management (SDS only)

Manage who has access to your repository and what they can do.

Granting Access

// Grant different levels of access
await repo.collaborators.grant({
  userDid: "did:plc:user123",
  role: "editor", // viewer | editor | admin | owner
});

// Roles explained:
// - viewer: Read-only access
// - editor: Can create and edit records
// - admin: Can manage collaborators and settings
// - owner: Full control (same as repository owner)

Managing Collaborators

// List all collaborators with pagination
const { collaborators, cursor } = await repo.collaborators.list();
for (const collab of collaborators) {
  console.log(`${collab.userDid} - ${collab.role}`);
}

// List next page
if (cursor) {
  const nextPage = await repo.collaborators.list({ cursor, limit: 20 });
}

// Check if a user has access
const hasAccess = await repo.collaborators.hasAccess("did:plc:user123");

// Get a specific user's role
const role = await repo.collaborators.getRole("did:plc:user123");
console.log(`User role: ${role}`); // "editor", "admin", etc.

// Get current user's permissions
const permissions = await repo.collaborators.getPermissions();
if (permissions.admin) {
  console.log("You can manage collaborators");
}
if (permissions.create) {
  console.log("You can create records");
}

Revoking Access

// Remove a collaborator
await repo.collaborators.revoke({
  userDid: "did:plc:user123",
});

Transferring Ownership

// Transfer repository ownership (irreversible!)
await repo.collaborators.transferOwnership({
  newOwnerDid: "did:plc:newowner456",
});

9. Generic Record Operations

For working with any ATProto record type:

// Create a generic record
const record = await repo.records.create({
  collection: "org.hypercerts.claim",
  record: {
    $type: "org.hypercerts.claim",
    title: "My Claim",
    // ... record data
  },
});

// Get a record
const record = await repo.records.get({
  collection: "org.hypercerts.claim",
  rkey: "abc123",
});

// Update a record
await repo.records.update({
  collection: "org.hypercerts.claim",
  rkey: "abc123",
  record: {
    $type: "org.hypercerts.claim",
    title: "Updated Title",
    // ... updated data
  },
});

// Delete a record
await repo.records.delete({
  collection: "org.hypercerts.claim",
  rkey: "abc123",
});

// List records with pagination
const { records, cursor } = await repo.records.list({
  collection: "org.hypercerts.claim",
  limit: 50,
});

10. Profile Management (PDS only)

The SDK supports two profile types:

  • Bluesky Profile (app.bsky.actor.profile) - Standard AT Protocol profiles with CDN URLs
  • Certified Profile (app.certified.actor.profile) - Hypercerts profiles with additional fields (pronouns, website)

Bluesky Profile

// Get Bluesky profile
const bskyProfile = await repo.profile.getBskyProfile();
console.log(`${bskyProfile.displayName} (@${bskyProfile.handle})`);
console.log(bskyProfile.avatar); // CDN URL: "https://cdn.bsky.app/..."

// Create Bluesky profile
await repo.profile.createBskyProfile({
  displayName: "Alice",
  description: "Climate researcher",
  avatar: avatarBlob, // Upload blob
  banner: bannerBlob,
});

// Update Bluesky profile
await repo.profile.updateBskyProfile({
  displayName: "Alice Smith",
  description: null, // Remove description
});

Certified Profile

// Get Certified profile (returns null if it doesn't exist)
const certProfile = await repo.profile.getCertifiedProfile();
if (certProfile) {
  console.log(certProfile.displayName);
  console.log(certProfile.pronouns); // "she/her"
  console.log(certProfile.website); // "https://alice.com"
  console.log(certProfile.avatar); // Blob URL: "https://pds.../xrpc/..."
} else {
  console.log("User hasn't created a Certified profile yet");
}

// Create Certified profile
await repo.profile.createCertifiedProfile({
  displayName: "Alice",
  description: "Climate scientist and impact certificate advocate",
  pronouns: "she/her",
  website: "https://alice.com",
  avatar: avatarBlob, // Upload blob
  banner: bannerBlob,
});

// Update Certified profile
await repo.profile.updateCertifiedProfile({
  pronouns: "they/them",
  website: null, // Remove website
});

// Upsert Certified profile (create if missing, update if exists)
await repo.profile.upsertCertifiedProfile({
  displayName: "Alice",
  description: "Climate scientist",
  pronouns: "she/her",
  website: "https://alice.com",
  avatar: avatarBlob,
});

// Upsert Bluesky profile
await repo.profile.upsertBskyProfile({
  displayName: "Alice",
  description: "Impact researcher",
  avatar: avatarBlob,
});

Key Differences:

  • Bluesky: Returns avatar/banner as CDN URLs (https://cdn.bsky.app/...), throws error if profile doesn't exist
  • Certified: Returns avatar/banner as PDS blob URLs (https://pds.../xrpc/...), includes pronouns (max 20 graphemes) and website fields, returns null if profile doesn't exist

When to use upsert vs create/update:

  • Use upsert*() for convenience when you don't know if a profile exists (e.g., first-time setup flows)
  • Use create*() when you know the profile doesn't exist (e.g., after checking with get*())
  • Use update*() when you know the profile exists and only want to modify specific fields

API Reference

Repository Operations

OperationMethodPDSSDSReturns
Records
Create recordrepo.records.create(){ uri, cid }
Get recordrepo.records.get()Record data
Update recordrepo.records.update(){ uri, cid }
Delete recordrepo.records.delete()void
List recordsrepo.records.list(){ records, cursor? }
Hypercerts
Create hypercertrepo.hypercerts.create(){ uri, cid, value }
Get hypercertrepo.hypercerts.get()Full hypercert
Update hypercertrepo.hypercerts.update(){ uri, cid }
Delete hypercertrepo.hypercerts.delete()void
List hypercertsrepo.hypercerts.list(){ records, cursor? }
Add contributionrepo.hypercerts.addContribution()Contribution
Add measurementrepo.hypercerts.addMeasurement()Measurement
Collections
Create collectionrepo.hypercerts.createCollection(){ uri, cid, record }
Get collectionrepo.hypercerts.getCollection()Collection data
List collectionsrepo.hypercerts.listCollections(){ records, cursor? }
Update collectionrepo.hypercerts.updateCollection(){ uri, cid }
Delete collectionrepo.hypercerts.deleteCollection()void
Attach locationrepo.hypercerts.attachLocationToCollection(){ uri, cid }
Remove locationrepo.hypercerts.removeLocationFromCollection()void
Projects
Create projectrepo.hypercerts.createProject(){ uri, cid, record }
Get projectrepo.hypercerts.getProject()Project data
List projectsrepo.hypercerts.listProjects(){ records, cursor? }
Update projectrepo.hypercerts.updateProject(){ uri, cid }
Delete projectrepo.hypercerts.deleteProject()void
Attach locationrepo.hypercerts.attachLocationToProject(){ uri, cid }
Remove locationrepo.hypercerts.removeLocationFromProject()void
Blobs
Upload blobrepo.blobs.upload(){ ref, mimeType, size }
Get blobrepo.blobs.get()Blob data
Profile
Get Bluesky profilerepo.profile.getBskyProfile()BskyProfile (CDN URLs)
Create Bluesky profilerepo.profile.createBskyProfile(){ uri, cid }
Update Bluesky profilerepo.profile.updateBskyProfile(){ uri, cid }
Upsert Bluesky profilerepo.profile.upsertBskyProfile(){ uri, cid }
Get Certified profilerepo.profile.getCertifiedProfile()CertifiedProfile | null (blob URLs)
Create Certified profilerepo.profile.createCertifiedProfile(){ uri, cid }
Update Certified profilerepo.profile.updateCertifiedProfile(){ uri, cid }
Upsert Certified profilerepo.profile.upsertCertifiedProfile(){ uri, cid }
Organizations
Create orgrepo.organizations.create(){ did, name, ... }
Get orgrepo.organizations.get()Organization
List orgsrepo.organizations.list(){ organizations, cursor? }
Collaborators
Grant accessrepo.collaborators.grant()void
Revoke accessrepo.collaborators.revoke()void
List collaboratorsrepo.collaborators.list(){ collaborators, cursor? }
Check accessrepo.collaborators.hasAccess()boolean
Get rolerepo.collaborators.getRole()Role string
Get permissionsrepo.collaborators.getPermissions()Permissions
Transfer ownershiprepo.collaborators.transferOwnership()void

Type System

Types are generated from ATProto lexicon definitions and exported with friendly aliases:

import type {
  HypercertClaim,
  HypercertRights,
  HypercertContribution,
  HypercertMeasurement,
  HypercertEvaluation,
  HypercertCollection,
  HypercertLocation,
} from "@hypercerts-org/sdk-core";

// For validation, use namespaced imports
import { OrgHypercertsClaim } from "@hypercerts-org/sdk-core";

if (OrgHypercertsClaim.isRecord(data)) {
  // data is typed as HypercertClaim
}
Lexicon TypeSDK Alias
OrgHypercertsClaim.MainHypercertClaim
OrgHypercertsClaimRights.MainHypercertRights
OrgHypercertsClaimContribution.MainHypercertContribution
OrgHypercertsClaimMeasurement.MainHypercertMeasurement
OrgHypercertsClaimEvaluation.MainHypercertEvaluation
OrgHypercertsCollection.MainHypercertCollection
AppCertifiedLocation.MainHypercertLocation

Error Handling

import {
  ValidationError,
  NetworkError,
  AuthenticationError,
  SDSRequiredError,
} from "@hypercerts-org/sdk-core/errors";

try {
  await repo.hypercerts.create({ ... });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error("Invalid hypercert data:", error.message);
  } else if (error instanceof NetworkError) {
    console.error("Network issue:", error.message);
  } else if (error instanceof AuthenticationError) {
    console.error("Authentication failed:", error.message);
  } else if (error instanceof SDSRequiredError) {
    console.error("This operation requires SDS:", error.message);
  }
}

Package Entrypoints

@hypercerts-org/sdk-core
├── /              → Full SDK (createATProtoSDK, Repository, types, errors)
├── /types         → TypeScript types (re-exported from @hypercerts-org/lexicon)
├── /errors        → Error classes
├── /lexicons      → LexiconRegistry, HYPERCERT_LEXICONS, HYPERCERT_COLLECTIONS
├── /storage       → InMemorySessionStore, InMemoryStateStore
└── /testing       → createMockSession, MockSessionStore

Advanced Usage

Multi-Server Routing with ConfigurableAgent

The ConfigurableAgent allows you to create custom agents that route to specific servers:

import { ConfigurableAgent } from "@hypercerts-org/sdk-core";

// Authenticate once with your PDS
const session = await sdk.callback(params);

// Create agents for different servers using the same session
const pdsAgent = new ConfigurableAgent(session, "https://bsky.social");
const sdsAgent = new ConfigurableAgent(session, "https://sds.hypercerts.org");
const orgAgent = new ConfigurableAgent(session, "https://sds-org-a.example.com");

// Use agents directly with AT Protocol APIs
await pdsAgent.com.atproto.repo.createRecord({...});
await sdsAgent.com.atproto.repo.listRecords({...});

// Or pass to Repository for high-level operations
// (Repository internally uses ConfigurableAgent)

This is useful for:

  • Connecting to multiple SDS instances simultaneously
  • Testing against different server environments
  • Building tools that work across multiple organizations
  • Direct AT Protocol API access with custom routing

Custom Session Storage

import { createATProtoSDK } from "@hypercerts-org/sdk-core";
import { InMemorySessionStore } from "@hypercerts-org/sdk-core/storage";

const sdk = createATProtoSDK({
  oauth: { ... },
  sessionStore: new InMemorySessionStore(),
});

Testing with Mocks

import { createMockSession, MockSessionStore } from "@hypercerts-org/sdk-core/testing";

const mockSession = createMockSession({
  did: "did:plc:test123",
  handle: "test.user",
});

const mockStore = new MockSessionStore();
await mockStore.set(mockSession);

Working with Lexicons

The SDK exports lexicon types and validation utilities from the @hypercerts-org/lexicon package for direct record manipulation and validation.

Lexicon Types

All lexicon types are available with proper TypeScript support:

import type {
  HypercertClaim,
  HypercertRights,
  HypercertContribution,
  HypercertCollection,
  HypercertMeasurement,
  HypercertEvaluation,
  HypercertLocation,
  StrongRef,
} from "@hypercerts-org/sdk-core";

// Create a properly typed hypercert claim
const claim: HypercertClaim = {
  $type: "org.hypercerts.claim",
  title: "Community Garden Project",
  shortDescription: "Urban garden serving 50 families", // REQUIRED
  description: "Detailed description...",
  workScope: "Food Security",
  workTimeFrameFrom: "2024-01-01T00:00:00Z", // Note: Capital 'F'
  workTimeFrameTo: "2024-12-31T00:00:00Z", // Note: Capital 'F'
  rights: { uri: "at://...", cid: "..." },
  createdAt: new Date().toISOString(),
};

Validation

Validate records before creating them:

import { validate, OrgHypercertsClaim, HYPERCERT_COLLECTIONS } from "@hypercerts-org/sdk-core";

// Validate using the lexicon package
const validation = validate(
  HYPERCERT_COLLECTIONS.CLAIM, // "org.hypercerts.claim"
  claim,
);

if (!validation.valid) {
  console.error("Validation failed:", validation.error);
}

// Or use type-specific validators
const isValid = OrgHypercertsClaim.isMain(claim);
const validationResult = OrgHypercertsClaim.validateMain(claim);

Using LexiconRegistry

The SDK automatically initializes a LexiconRegistry with all hypercert lexicons. You can access it to validate records or register custom lexicons:

// Access the registry from the SDK
const registry = sdk.getLexiconRegistry();

// Validate a record
const result = registry.validate("org.hypercerts.claim.activity", claimData);

if (!result.valid) {
  console.error("Invalid record:", result.error);
}

// Register a custom lexicon
registry.registerFromJSON({
  lexicon: 1,
  id: "org.myapp.customRecord",
  defs: {
    main: {
      type: "record",
      key: "tid",
      record: {
        type: "object",
        required: ["$type", "title"],
        properties: {
          $type: { type: "string", const: "org.myapp.customRecord" },
          title: { type: "string" },
          createdAt: { type: "string", format: "datetime" },
        },
      },
    },
  },
});

// Check if a lexicon is registered
if (registry.isRegistered("org.myapp.customRecord")) {
  console.log("Custom lexicon is ready to use");
}

You can also access the registry from a Repository instance:

const repo = sdk.repository(session);
const registry = repo.getLexiconRegistry();

Creating Records with Proper Types

import type { HypercertContribution, StrongRef } from "@hypercerts-org/sdk-core";
import { HYPERCERT_COLLECTIONS } from "@hypercerts-org/sdk-core";

// Create a contribution record
const contribution: HypercertContribution = {
  $type: HYPERCERT_COLLECTIONS.CONTRIBUTION,
  hypercert: {
    uri: "at://did:plc:abc/org.hypercerts.claim/xyz",
    cid: "bafyrei...",
  } as StrongRef,
  contributors: ["did:plc:contributor1", "did:plc:contributor2"],
  role: "implementer",
  description: "On-ground implementation team",
  workTimeframeFrom: "2024-01-01T00:00:00Z", // Note: lowercase 'f' for contributions
  workTimeframeTo: "2024-06-30T00:00:00Z", // Note: lowercase 'f' for contributions
  createdAt: new Date().toISOString(),
};

// Use with repository operations
await repo.records.create({
  collection: HYPERCERT_COLLECTIONS.CONTRIBUTION,
  record: contribution,
});

Available Lexicon Collections

import { HYPERCERT_COLLECTIONS } from "@hypercerts-org/sdk-core";

// Collection NSIDs
HYPERCERT_COLLECTIONS.CLAIM; // "org.hypercerts.claim"
HYPERCERT_COLLECTIONS.RIGHTS; // "org.hypercerts.claim.rights"
HYPERCERT_COLLECTIONS.CONTRIBUTION; // "org.hypercerts.claim.contribution"
HYPERCERT_COLLECTIONS.MEASUREMENT; // "org.hypercerts.claim.measurement"
HYPERCERT_COLLECTIONS.EVALUATION; // "org.hypercerts.claim.evaluation"
HYPERCERT_COLLECTIONS.EVIDENCE; // "org.hypercerts.claim.evidence"
HYPERCERT_COLLECTIONS.COLLECTION; // "org.hypercerts.collection"
HYPERCERT_COLLECTIONS.LOCATION; // "app.certified.location"

Development

pnpm install        # Install dependencies
pnpm build          # Build the package
pnpm test           # Run tests
pnpm test:coverage  # Run tests with coverage
pnpm test:watch     # Run tests in watch mode

License

MIT

Resources

Keywords

atproto

FAQs

Package last updated on 02 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts