Skip to content

Commit d0e12cc

Browse files
feat(hcs-16): complete implementation of HCS-16
Signed-off-by: Gaurang Torvekar <2285764+gaurangtorvekar@users.noreply.github.com>
1 parent 4caa88f commit d0e12cc

9 files changed

Lines changed: 830 additions & 125 deletions

File tree

demo/hcs-16/create-flora-demo.ts

Lines changed: 151 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,195 @@
11
import 'dotenv/config';
2-
import { Client, PrivateKey, KeyList } from '@hashgraph/sdk';
2+
import {
3+
Client,
4+
PrivateKey,
5+
KeyList,
6+
AccountCreateTransaction,
7+
Hbar,
8+
} from '@hashgraph/sdk';
39
import { HCS16Client } from '../../src/hcs-16/sdk';
10+
import { HCS11Client } from '../../src/hcs-11/client';
411
import { HCS16BaseClient } from '../../src/hcs-16/base-client';
512
import { FloraTopicType } from '../../src/hcs-16/types';
613

714
async function main() {
8-
const network = (process.env.HEDERA_NETWORK as 'mainnet' | 'testnet') || 'testnet';
9-
const operatorId = process.env.HEDERA_OPERATOR_ID || process.env.HEDERA_ACCOUNT_ID;
10-
const operatorKey = process.env.HEDERA_OPERATOR_KEY || process.env.HEDERA_PRIVATE_KEY;
15+
const network =
16+
(process.env.HEDERA_NETWORK as 'mainnet' | 'testnet') || 'testnet';
17+
const operatorId =
18+
process.env.HEDERA_OPERATOR_ID || process.env.HEDERA_ACCOUNT_ID;
19+
const operatorKey =
20+
process.env.HEDERA_OPERATOR_KEY || process.env.HEDERA_PRIVATE_KEY;
1121
if (!operatorId || !operatorKey) {
12-
console.error('Missing HEDERA_OPERATOR_ID/HEDERA_OPERATOR_KEY in environment');
22+
console.error(
23+
'Missing HEDERA_OPERATOR_ID/HEDERA_OPERATOR_KEY in environment',
24+
);
1325
process.exit(1);
1426
}
1527

1628
const client = new HCS16Client({ network, operatorId, operatorKey });
29+
const hcs11 = new HCS11Client({
30+
network,
31+
auth: { operatorId, privateKey: operatorKey } as any,
32+
} as any);
1733

34+
console.log('1) 🔐 Creating Flora member keys (2-of-3 threshold)');
35+
const threshold = 2;
1836
const k1 = PrivateKey.generateECDSA();
1937
const k2 = PrivateKey.generateECDSA();
2038
const k3 = PrivateKey.generateECDSA();
21-
const keyList = new KeyList([k1.publicKey, k2.publicKey, k3.publicKey], 2);
39+
const memberKeys = [k1, k2, k3];
2240

23-
const payerClient = network === 'mainnet' ? Client.forMainnet() : Client.forTestnet();
41+
const payerClient =
42+
network === 'mainnet' ? Client.forMainnet() : Client.forTestnet();
2443
payerClient.setOperator(operatorId, operatorKey);
44+
console.log(' ↳ 🧾 Creating member accounts');
45+
const memberAccounts: string[] = [];
46+
for (const [index, key] of memberKeys.entries()) {
47+
const resp = await new AccountCreateTransaction()
48+
.setKey(key.publicKey)
49+
.setInitialBalance(new Hbar(5))
50+
.execute(payerClient);
51+
const receipt = await resp.getReceipt(payerClient);
52+
if (!receipt.accountId) {
53+
throw new Error(`Failed to create Flora member account ${index + 1}`);
54+
}
55+
const accountId = receipt.accountId.toString();
56+
memberAccounts.push(accountId);
57+
console.log(` • Member ${index + 1}: ${accountId}`);
58+
}
59+
60+
const keyList = new KeyList(
61+
memberKeys.map(key => key.publicKey),
62+
threshold,
63+
);
64+
const submitKeyList = new KeyList(
65+
memberKeys.map(key => key.publicKey),
66+
1,
67+
);
68+
2569
const { buildHcs16CreateAccountTx } = await import('../../src/hcs-16/tx');
26-
const accountTx = buildHcs16CreateAccountTx({ keyList, initialBalanceHbar: 2, maxAutomaticTokenAssociations: -1 });
70+
const accountTx = buildHcs16CreateAccountTx({
71+
keyList,
72+
initialBalanceHbar: 2,
73+
maxAutomaticTokenAssociations: -1,
74+
});
75+
console.log(' ↳ 🧾 Submitting AccountCreateTransaction');
2776
const accountResp = await accountTx.execute(payerClient);
2877
const accountReceipt = await accountResp.getReceipt(payerClient);
2978
if (!accountReceipt.accountId) {
3079
throw new Error('Failed to create Flora account');
3180
}
3281
const floraAccountId = accountReceipt.accountId.toString();
33-
console.log('Flora account created:', floraAccountId);
82+
console.log('Flora account created:', floraAccountId);
3483

35-
const comm = await client.createFloraTopic({ floraAccountId, topicType: FloraTopicType.COMMUNICATION });
36-
const tx = await client.createFloraTopic({ floraAccountId, topicType: FloraTopicType.TRANSACTION });
37-
const state = await client.createFloraTopic({ floraAccountId, topicType: FloraTopicType.STATE });
38-
console.log('Flora topics:', { communication: comm, transaction: tx, state });
84+
console.log('2) 🧵 Creating Flora topics (CTopic/TTopic/STopic)');
85+
const comm = await client.createFloraTopic({
86+
floraAccountId,
87+
topicType: FloraTopicType.COMMUNICATION,
88+
adminKey: keyList,
89+
submitKey: submitKeyList,
90+
signerKeys: memberKeys.slice(0, threshold),
91+
});
92+
const tx = await client.createFloraTopic({
93+
floraAccountId,
94+
topicType: FloraTopicType.TRANSACTION,
95+
adminKey: keyList,
96+
submitKey: submitKeyList,
97+
signerKeys: memberKeys.slice(0, threshold),
98+
});
99+
const state = await client.createFloraTopic({
100+
floraAccountId,
101+
topicType: FloraTopicType.STATE,
102+
adminKey: keyList,
103+
submitKey: submitKeyList,
104+
signerKeys: memberKeys.slice(0, threshold),
105+
});
106+
console.log(' ✅ Topics ready:', {
107+
communication: comm,
108+
transaction: tx,
109+
state,
110+
});
111+
112+
// Publish minimal Flora profile and update Flora memo (hcs-11:<resource>)
113+
const profile: any = {
114+
version: '1.0',
115+
type: 3,
116+
display_name: 'Example Flora',
117+
// Minimal valid profile per HCS-11 Flora schema
118+
members: memberAccounts.map(accountId => ({ accountId })),
119+
threshold,
120+
topics: { communication: comm, transaction: tx, state },
121+
inboundTopicId: comm,
122+
outboundTopicId: tx,
123+
};
124+
try {
125+
console.log('3) 🗂️ Publishing HCS-11 Flora profile and updating memo');
126+
const { profileResource } = await client.publishFloraProfileAndMemo({
127+
hcs11,
128+
floraAccountId,
129+
profile,
130+
});
131+
console.log(' ✅ Profile published:', profileResource);
132+
} catch (e) {
133+
console.warn(
134+
' ⚠️ Skipping profile inscription in demo environment:',
135+
(e as Error).message,
136+
);
137+
console.warn(
138+
' ℹ️ Ensure registrar availability and correct operator signatures to enable inscription.',
139+
);
140+
}
141+
142+
const signingMemberAccount = memberAccounts[0];
143+
const signingKey = memberKeys[0];
39144

145+
console.log('4) 📣 Publishing flora_created (CTopic)');
40146
const receipt = await client.sendFloraCreated({
41147
topicId: comm,
42-
operatorId: `${operatorId}@${floraAccountId}`,
148+
operatorId: `${signingMemberAccount}@${floraAccountId}`,
43149
floraAccountId,
44150
topics: { communication: comm, transaction: tx, state },
151+
signerKey: signingKey,
45152
});
46-
console.log('flora_created receipt status:', receipt.status.toString());
153+
console.log(' ✅ flora_created status:', receipt.status.toString());
47154
const stateHash = '0x' + Date.now().toString(16);
48-
await client.sendStateUpdate({ topicId: state, operatorId: `${operatorId}@${floraAccountId}`, hash: stateHash });
49-
console.log('state_update sent with hash:', stateHash);
155+
console.log('5) 🧩 Publishing state_update (STopic)');
156+
await client.sendStateUpdate({
157+
topicId: state,
158+
operatorId: `${signingMemberAccount}@${floraAccountId}`,
159+
hash: stateHash,
160+
signerKey: signingKey,
161+
});
162+
console.log(' ✅ state_update hash:', stateHash);
163+
console.log('6) 🧾 Publishing state_hash (HCS-17 on STopic)');
164+
await client.sendStateHash({
165+
topicId: state,
166+
stateHash,
167+
accountId: floraAccountId,
168+
topics: [comm, tx, state],
169+
signerKey: signingKey,
170+
});
171+
console.log(' ✅ state_hash committed via HCS-17');
50172

51173
const helper = new HCS16BaseClient({ network });
52174
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
53175
let printed = false;
54-
for (let i = 0; i < 5; i++) {
176+
console.log('7) 🔎 Reading back latest messages');
177+
for (let i = 0; i < 6; i++) {
55178
try {
56-
const commMsgs = await helper.getRecentMessages(comm, { limit: 1, order: 'desc' });
57-
const stateMsgs = await helper.getRecentMessages(state, { limit: 1, order: 'desc' });
179+
const commMsgs = await helper.getRecentMessages(comm, {
180+
limit: 1,
181+
order: 'desc',
182+
});
183+
const stateMsgs = await helper.getRecentMessages(state, {
184+
limit: 1,
185+
order: 'desc',
186+
});
58187
if (commMsgs.length > 0) {
59-
console.log('Latest flora_created message:', commMsgs[0]);
188+
console.log(' 📨 Latest flora_created message:', commMsgs[0]);
60189
printed = true;
61190
}
62191
if (stateMsgs.length > 0) {
63-
console.log('Latest state_update message:', stateMsgs[0]);
192+
console.log(' 📨 Latest state_update message:', stateMsgs[0]);
64193
printed = true;
65194
}
66195
if (printed) {

0 commit comments

Comments
 (0)