HCS-21 Server Client (HCS21Client)
Use HCS21Client inside Node.js services (registries, CI/CD, Petal orchestration) to create adapter registry topics, inscribe manifests or registry metadata via HCS-1, and publish or stream adapter declarations.
InitializationDirect link to Initialization
- TypeScript
- Go
import {
AdapterManifest,
HCS21Client,
Logger,
} from '@hashgraphonline/standards-sdk';
const logger = new Logger({ module: 'hcs-21-server', level: 'info' });
const client = new HCS21Client({
network: 'testnet',
operatorId: process.env.HEDERA_OPERATOR_ID!,
operatorKey: process.env.HEDERA_OPERATOR_KEY!,
logLevel: 'info',
});
import (
"os"
"github.com/hashgraph-online/standards-sdk-go/pkg/hcs21"
)
client, err := hcs21.NewClient(hcs21.ClientConfig{
Network: "testnet",
OperatorAccountID: os.Getenv("HEDERA_OPERATOR_ID"),
OperatorPrivateKey: os.Getenv("HEDERA_OPERATOR_KEY"),
})
Inscribe an Adapter Manifest (HCS-1)Direct link to Inscribe an Adapter Manifest (HCS-1)
- TypeScript
- Go
const manifest: AdapterManifest = {
meta: {
spec_version: '1.0',
adapter_version: '1.3.2',
generated: new Date().toISOString(),
},
adapter: {
id: 'npm/@hashgraphonline/x402-bazaar-adapter@1.3.2',
name: 'X402 Bazaar Agent Adapter',
maintainers: [{ name: 'Hashgraph Online', contact: 'ops@hashgraph.online' }],
license: 'Apache-2.0',
},
package: {
registry: 'npm',
dist_tag: 'stable',
artifacts: [
{
url: 'npm://@hashgraphonline/x402-bazaar-adapter@1.3.2',
digest: 'sha384-demo-digest',
signature: 'demo-signature',
},
],
},
runtime: {
platforms: ['node>=20.10.0'],
primary: 'node',
entry: 'dist/index.js',
dependencies: ['@hashgraphonline/standards-sdk@^1.8.0'],
env: ['X402_API_KEY'],
},
capabilities: {
discovery: true,
communication: true,
protocols: ['x402', 'uaid'],
},
consensus: {
entity_schema: 'hcs-21.entity-consensus@1',
required_fields: ['entity_id', 'registry', 'state_hash', 'epoch'],
hashing: 'sha384',
},
};
const manifestPointer = await client.inscribeMetadata({ document: manifest });
// manifestPointer.pointer -> "hcs://1/<topic>"
// manifestPointer.manifestSequence -> sequence number for this manifest
// manifestPointer.totalCostHbar -> network fee paid for the inscription
// Note: Inscription of adapter manifests uses the HCS-1 protocol.
// In the Go SDK, use the `inscriber` package to write JSON metadata to HCS-1.
manifest := map[string]any{
"meta": map[string]any{
"spec_version": "1.0",
"adapter_version": "1.3.2",
},
"adapter": map[string]any{
"id": "npm/@hashgraphonline/x402-bazaar-adapter@1.3.2",
"name": "X402 Bazaar Agent Adapter",
},
// ... fill in other fields
}
// manifestPointer := inscribeToHCS1(manifest)
Create an Adapter Registry TopicDirect link to Create an Adapter Registry Topic
- TypeScript
- Go
const registryTopicId = await client.createRegistryTopic({
ttl: 3600,
indexed: 0,
transactionMemo: 'flora adapter registry',
});
registryTopicID, _, err := client.CreateRegistryTopic(ctx, hcs21.CreateRegistryTopicOptions{
TTL: 3600,
Indexed: false,
TransactionMemo: "flora adapter registry",
})
Create an Adapter Category Topic (HCS-21 indexed)Direct link to Create an Adapter Category Topic (HCS-21 indexed)
Adapter categories organize adapters inside a discovery list. Each entry uses the slug adapter:<namespace>/<name> and points to a version pointer topic.
- TypeScript
- Go
const categoryTopicId =
process.env.HCS21_CATEGORY_TOPIC_ID ||
(await client.createAdapterCategoryTopic({
ttl: 86400,
metaTopicId: process.env.HCS21_REGISTRY_METADATA_POINTER, // optional HCS-1 registry manifest
transactionMemo: 'adapter-registry:price:category',
}));
await client.registerCategoryTopic({
discoveryTopicId: process.env.HCS21_DISCOVERY_TOPIC_ID!,
categoryTopicId,
metadata: process.env.HCS21_REGISTRY_METADATA_POINTER,
memo: 'adapter-registry:price',
});
// Create a new category discovery topic if you don't already have one
categoryTopicID, _, err := client.CreateRegistryDiscoveryTopic(ctx, 86400, true, true, "adapter-registry:price:category")
// Register it to an upstream discovery topic
_, _, err = client.RegisterCategoryTopic(
ctx,
os.Getenv("HCS21_DISCOVERY_TOPIC_ID"),
categoryTopicID,
os.Getenv("HCS21_REGISTRY_METADATA_POINTER"),
"adapter-registry:price",
)
Create a Version Pointer Topic (HCS-2 Non-Indexed)Direct link to Create a Version Pointer Topic (HCS-2 Non-Indexed)
Version pointers are hcs-2:1:<ttl> topics. Only the latest message matters; it references the live HCS-21 declaration topic for that adapter or registry.
- TypeScript
- Go
const versionPointerTopicId =
process.env.HCS21_VERSION_TOPIC_ID ||
(await client.createAdapterVersionPointerTopic({
ttl: 3600,
memoOverride: 'hcs-2:1:3600',
transactionMemo: 'adapter-registry:pointer',
}));
versionPointerTopicID, _, err := client.CreateAdapterVersionPointerTopic(
ctx,
3600,
true,
true,
"adapter-registry:pointer",
)
Publish the Current Registry TopicDirect link to Publish the Current Registry Topic
Publish the active HCS-21 topic into the version pointer, then list that pointer inside the category topic.
- TypeScript
- Go
await client.publishVersionPointer({
versionTopicId: versionPointerTopicId,
declarationTopicId: registryTopicId,
memo: 'adapter:npm/@hashgraphonline/x402-bazaar-adapter',
});
await client.publishCategoryEntry({
categoryTopicId,
adapterId: 'npm/@hashgraphonline/x402-bazaar-adapter',
versionTopicId: versionPointerTopicId,
metadata: process.env.HCS21_REGISTRY_METADATA_POINTER,
});
_, _, err = client.PublishVersionPointer(
ctx,
versionPointerTopicID,
registryTopicID,
"adapter:npm/@hashgraphonline/x402-bazaar-adapter",
"",
)
_, _, err = client.PublishCategoryEntry(
ctx,
categoryTopicID,
"npm/@hashgraphonline/x402-bazaar-adapter",
versionPointerTopicID,
os.Getenv("HCS21_REGISTRY_METADATA_POINTER"),
)
- Rotating to a new registry topic only requires another
publishVersionPointercall with the newdeclarationTopicId. - Category entries stay fixed; consumers always resolve the slug (
adapter:npm/...) to discover the active pointer before streaming declarations.
Publish Adapter DeclarationsDirect link to Publish Adapter Declarations
- TypeScript
- Go
await client.publishDeclaration({
topicId: registryTopicId,
declaration: {
op: 'register',
adapterId: manifest.adapter.id,
entity: 'agent',
adapterPackage: {
registry: 'npm',
name: '@hashgraphonline/x402-bazaar-adapter',
version: '1.3.2',
integrity: 'sha384-demo-digest',
},
manifest: manifestPointer.pointer,
manifestSequence: manifestPointer.manifestSequence,
config: {
type: 'flora',
account: '0.0.9876',
threshold: '2/3',
ctopic: '0.0.700111',
ttopic: '0.0.700112',
stopic: '0.0.700113',
},
stateModel: 'hcs-21.entity-consensus@1',
},
});
_, _, err = client.PublishDeclaration(ctx, hcs21.PublishDeclarationOptions{
TopicID: registryTopicID,
Declaration: hcs21.AdapterDeclaration{
Op: "register",
AdapterID: "npm/@hashgraphonline/x402-bazaar-adapter@1.3.2",
Entity: "agent",
AdapterPackage: hcs21.AdapterPackage{
Registry: "npm",
Name: "@hashgraphonline/x402-bazaar-adapter",
Version: "1.3.2",
Integrity: "sha384-demo-digest",
},
Manifest: "hcs://1/0.0.1234",
ManifestSequence: 1,
Config: map[string]any{
"type": "flora",
"account": "0.0.9876",
"threshold": "2/3",
"ctopic": "0.0.700111",
"ttopic": "0.0.700112",
"stopic": "0.0.700113",
},
StateModel: "hcs-21.entity-consensus@1",
},
})
Resolve the Active Registry TopicDirect link to Resolve the Active Registry Topic
Consumers (and automated publishers) should resolve the version pointer topic before streaming or publishing. The helper returns the active HCS-21 topic ID plus metadata.
- TypeScript
- Go
const pointer = await client.resolveVersionPointer(versionPointerTopicId);
const activeRegistryTopicId =
process.env.HCS21_REGISTRY_TOPIC_ID || pointer.declarationTopicId;
activeTopicID, seq, payer, err := client.ResolveLatestVersionPointer(ctx, versionPointerTopicID)
Stream DeclarationsDirect link to Stream Declarations
- TypeScript
- Go
const latest = await client.fetchDeclarations(activeRegistryTopicId, {
limit: 10,
order: 'desc',
});
latest.forEach(envelope => {
logger.info('hcs-21 declaration', {
sequence: envelope.sequenceNumber,
payer: envelope.payer,
adapter: envelope.declaration.adapter_id,
stateModel: envelope.declaration.state_model,
});
});
latest, err := client.FetchDeclarations(ctx, activeTopicID, hcs21.FetchDeclarationsOptions{
Limit: 10,
Order: "desc",
})
for _, envelope := range latest {
fmt.Printf("hcs-21 declaration seq: %d, payer: %s, adapter: %s\n",
envelope.SequenceNumber,
envelope.Payer,
envelope.Declaration.AdapterID,
)
}
The HCS21Client filters non-hcs-21 payloads, enforces the 1 KB limit, and validates every declaration against the updated schema.