
Security News
White House Authorizes Private Companies to Conduct Offensive Cyber Operations
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.
@hypercerts-org/sdk-core
Advanced tools
Framework-agnostic ATProto SDK core for authentication, repository operations, and lexicon management
Framework-agnostic ATProto SDK for Hypercerts. Create, manage, and collaborate on hypercerts using the AT Protocol.
pnpm add @hypercerts-org/sdk-core
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",
},
});
For local development and testing, you can use HTTP loopback URLs with localhost or 127.0.0.1:
// 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;
// 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 });
}
}
// 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 });
}
# .env.local
ATPROTO_JWK_PRIVATE='{"keys":[{"kty":"EC","crv":"P-256",...}]}'
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/forclientIdandhttp://127.0.0.1:<port>forredirectUriandjwksUri(this is the recommended pattern per AT Protocol spec).
The SDK supports two types of AT Protocol servers:
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
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);
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
ConfigurableAgent that wraps your OAuth session's fetch handlertokenInfo.aud).repo(did), a new Repository is created with the same server configuration// 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
The SDK uses OAuth 2.0 for authentication with granular permission control.
// 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);
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 addressPROFILE_READ / PROFILE_WRITE - Profile accessPOST_WRITE - Create postsSOCIAL_WRITE - Likes, reposts, followsMEDIA_UPLOAD - Image and video uploadsPOSTING_APP - Full posting with mediaEMAIL_AND_PROFILE - Common combinationSee OAuth Permissions Documentation for detailed usage.
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);
// 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 });
}
// 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"],
},
});
await repo.hypercerts.delete("at://did:plc:user123/org.hypercerts.claim/abc123");
// 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,
});
// 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(),
});
Collections organize multiple hypercerts into logical groupings. Projects are a special type of collection with
type="project".
// 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);
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",
},
],
});
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);
// 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();
// 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
});
// Delete collection
await repo.hypercerts.deleteCollection(collectionUri);
// Delete project
await repo.hypercerts.deleteProject(projectUri);
// Remove location from project
await repo.hypercerts.removeLocationFromProject(projectUri);
// 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...");
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}`);
Manage who has access to your repository and what they can do.
// 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)
// 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");
}
// Remove a collaborator
await repo.collaborators.revoke({
userDid: "did:plc:user123",
});
// Transfer repository ownership (irreversible!)
await repo.collaborators.transferOwnership({
newOwnerDid: "did:plc:newowner456",
});
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,
});
The SDK supports two profile types:
app.bsky.actor.profile) - Standard AT Protocol profiles with CDN URLsapp.certified.actor.profile) - Hypercerts profiles with additional fields (pronouns, website)// 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
});
// 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:
https://cdn.bsky.app/...), throws error if profile doesn't existhttps://pds.../xrpc/...), includes pronouns (max 20
graphemes) and website fields, returns null if profile doesn't existWhen to use upsert vs create/update:
upsert*() for convenience when you don't know if a profile exists (e.g., first-time setup flows)create*() when you know the profile doesn't exist (e.g., after checking with get*())update*() when you know the profile exists and only want to modify specific fields| Operation | Method | PDS | SDS | Returns |
|---|---|---|---|---|
| Records | ||||
| Create record | repo.records.create() | ✅ | ✅ | { uri, cid } |
| Get record | repo.records.get() | ✅ | ✅ | Record data |
| Update record | repo.records.update() | ✅ | ✅ | { uri, cid } |
| Delete record | repo.records.delete() | ✅ | ✅ | void |
| List records | repo.records.list() | ✅ | ✅ | { records, cursor? } |
| Hypercerts | ||||
| Create hypercert | repo.hypercerts.create() | ✅ | ✅ | { uri, cid, value } |
| Get hypercert | repo.hypercerts.get() | ✅ | ✅ | Full hypercert |
| Update hypercert | repo.hypercerts.update() | ✅ | ✅ | { uri, cid } |
| Delete hypercert | repo.hypercerts.delete() | ✅ | ✅ | void |
| List hypercerts | repo.hypercerts.list() | ✅ | ✅ | { records, cursor? } |
| Add contribution | repo.hypercerts.addContribution() | ✅ | ✅ | Contribution |
| Add measurement | repo.hypercerts.addMeasurement() | ✅ | ✅ | Measurement |
| Collections | ||||
| Create collection | repo.hypercerts.createCollection() | ✅ | ✅ | { uri, cid, record } |
| Get collection | repo.hypercerts.getCollection() | ✅ | ✅ | Collection data |
| List collections | repo.hypercerts.listCollections() | ✅ | ✅ | { records, cursor? } |
| Update collection | repo.hypercerts.updateCollection() | ✅ | ✅ | { uri, cid } |
| Delete collection | repo.hypercerts.deleteCollection() | ✅ | ✅ | void |
| Attach location | repo.hypercerts.attachLocationToCollection() | ✅ | ✅ | { uri, cid } |
| Remove location | repo.hypercerts.removeLocationFromCollection() | ✅ | ✅ | void |
| Projects | ||||
| Create project | repo.hypercerts.createProject() | ✅ | ✅ | { uri, cid, record } |
| Get project | repo.hypercerts.getProject() | ✅ | ✅ | Project data |
| List projects | repo.hypercerts.listProjects() | ✅ | ✅ | { records, cursor? } |
| Update project | repo.hypercerts.updateProject() | ✅ | ✅ | { uri, cid } |
| Delete project | repo.hypercerts.deleteProject() | ✅ | ✅ | void |
| Attach location | repo.hypercerts.attachLocationToProject() | ✅ | ✅ | { uri, cid } |
| Remove location | repo.hypercerts.removeLocationFromProject() | ✅ | ✅ | void |
| Blobs | ||||
| Upload blob | repo.blobs.upload() | ✅ | ✅ | { ref, mimeType, size } |
| Get blob | repo.blobs.get() | ✅ | ✅ | Blob data |
| Profile | ||||
| Get Bluesky profile | repo.profile.getBskyProfile() | ✅ | ❌ | BskyProfile (CDN URLs) |
| Create Bluesky profile | repo.profile.createBskyProfile() | ✅ | ❌ | { uri, cid } |
| Update Bluesky profile | repo.profile.updateBskyProfile() | ✅ | ❌ | { uri, cid } |
| Upsert Bluesky profile | repo.profile.upsertBskyProfile() | ✅ | ❌ | { uri, cid } |
| Get Certified profile | repo.profile.getCertifiedProfile() | ✅ | ❌ | CertifiedProfile | null (blob URLs) |
| Create Certified profile | repo.profile.createCertifiedProfile() | ✅ | ❌ | { uri, cid } |
| Update Certified profile | repo.profile.updateCertifiedProfile() | ✅ | ❌ | { uri, cid } |
| Upsert Certified profile | repo.profile.upsertCertifiedProfile() | ✅ | ❌ | { uri, cid } |
| Organizations | ||||
| Create org | repo.organizations.create() | ❌ | ✅ | { did, name, ... } |
| Get org | repo.organizations.get() | ❌ | ✅ | Organization |
| List orgs | repo.organizations.list() | ❌ | ✅ | { organizations, cursor? } |
| Collaborators | ||||
| Grant access | repo.collaborators.grant() | ❌ | ✅ | void |
| Revoke access | repo.collaborators.revoke() | ❌ | ✅ | void |
| List collaborators | repo.collaborators.list() | ❌ | ✅ | { collaborators, cursor? } |
| Check access | repo.collaborators.hasAccess() | ❌ | ✅ | boolean |
| Get role | repo.collaborators.getRole() | ❌ | ✅ | Role string |
| Get permissions | repo.collaborators.getPermissions() | ❌ | ✅ | Permissions |
| Transfer ownership | repo.collaborators.transferOwnership() | ❌ | ✅ | void |
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 Type | SDK Alias |
|---|---|
OrgHypercertsClaim.Main | HypercertClaim |
OrgHypercertsClaimRights.Main | HypercertRights |
OrgHypercertsClaimContribution.Main | HypercertContribution |
OrgHypercertsClaimMeasurement.Main | HypercertMeasurement |
OrgHypercertsClaimEvaluation.Main | HypercertEvaluation |
OrgHypercertsCollection.Main | HypercertCollection |
AppCertifiedLocation.Main | HypercertLocation |
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);
}
}
@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
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:
import { createATProtoSDK } from "@hypercerts-org/sdk-core";
import { InMemorySessionStore } from "@hypercerts-org/sdk-core/storage";
const sdk = createATProtoSDK({
oauth: { ... },
sessionStore: new InMemorySessionStore(),
});
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);
The SDK exports lexicon types and validation utilities from the @hypercerts-org/lexicon package for direct record
manipulation and validation.
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(),
};
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);
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();
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,
});
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"
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
MIT
FAQs
Framework-agnostic ATProto SDK core for authentication, repository operations, and lexicon management
The npm package @hypercerts-org/sdk-core receives a total of 101 weekly downloads. As such, @hypercerts-org/sdk-core popularity was classified as not popular.
We found that @hypercerts-org/sdk-core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?

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.

Security News
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.