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
- Python
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"),
})
import os
from standards_sdk_py.hcs21 import Hcs21Client
client = Hcs21Client(
network="testnet",
operator_id=os.environ["HEDERA_OPERATOR_ID"],
operator_private_key=os.environ["HEDERA_OPERATOR_KEY"],
)
Inscribe an Adapter Manifest (HCS-1)Direct link to Inscribe an Adapter Manifest (HCS-1)
- TypeScript
- Go
- Python
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)
# 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{
# ... fill in other fields
# manifestPointer := inscribeToHCS1(manifest)
Create an Adapter Registry TopicDirect link to Create an Adapter Registry Topic
- TypeScript
- Go
- Python
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",
})
client.create_registry_topic(ttl=3600, indexed=False, transaction_memo="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
- Python
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",
)
import os
# Create a new category discovery topic if you don't already have one
client.create_registry_discovery_topic(86400, True, True, "adapter-registry:price:category")
# Register it to an upstream discovery topic
client.register_category_topic(ctx, os.environ["HCS21_DISCOVERY_TOPIC_ID"], categoryTopicID, os.environ["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
- Python
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",
)
client.create_adapter_version_pointer_topic(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
- Python
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"),
)
import os
client.publish_version_pointer(ctx, versionPointerTopicID, registryTopicID, "adapter:npm/@hashgraphonline/x402-bazaar-adapter")
client.publish_category_entry(ctx, categoryTopicID, "npm/@hashgraphonline/x402-bazaar-adapter", versionPointerTopicID, os.environ["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
- Python
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",
},
})
client.publish_declaration(topic_id=registryTopicID, declaration={"op": "register", "adapter_id": "npm/@hashgraphonline/x402-bazaar-adapter@1.3.2", "entity": "agent", "adapter_package": 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
- Python
const pointer = await client.resolveVersionPointer(versionPointerTopicId);
const activeRegistryTopicId =
process.env.HCS21_REGISTRY_TOPIC_ID || pointer.declarationTopicId;
activeTopicID, seq, payer, err := client.ResolveLatestVersionPointer(ctx, versionPointerTopicID)
payer = client.resolve_latest_version_pointer(versionPointerTopicID)
Stream DeclarationsDirect link to Stream Declarations
- TypeScript
- Go
- Python
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,
)
}
latest = client.fetch_declarations(activeTopicID, limit=10, order="desc")
for envelope in latest :
fmt.printf("hcs-21 declaration seq: %d, payer: %s, adapter: %s\n", envelope.SEQUENCE_NUMBER, 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.