@claw-network/node 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +49 -0
  2. package/dist/api/api-key-store.d.ts +74 -0
  3. package/dist/api/api-key-store.d.ts.map +1 -0
  4. package/dist/api/api-key-store.js +170 -0
  5. package/dist/api/api-key-store.js.map +1 -0
  6. package/dist/api/auth.d.ts +30 -0
  7. package/dist/api/auth.d.ts.map +1 -0
  8. package/dist/api/auth.js +115 -0
  9. package/dist/api/auth.js.map +1 -0
  10. package/dist/api/legacy.d.ts +26 -0
  11. package/dist/api/legacy.d.ts.map +1 -0
  12. package/dist/api/legacy.js +281 -0
  13. package/dist/api/legacy.js.map +1 -0
  14. package/dist/api/middleware.d.ts +35 -0
  15. package/dist/api/middleware.d.ts.map +1 -0
  16. package/dist/api/middleware.js +75 -0
  17. package/dist/api/middleware.js.map +1 -0
  18. package/dist/api/response.d.ts +85 -0
  19. package/dist/api/response.d.ts.map +1 -0
  20. package/dist/api/response.js +185 -0
  21. package/dist/api/response.js.map +1 -0
  22. package/dist/api/router.d.ts +45 -0
  23. package/dist/api/router.d.ts.map +1 -0
  24. package/dist/api/router.js +183 -0
  25. package/dist/api/router.js.map +1 -0
  26. package/dist/api/routes/admin.d.ts +11 -0
  27. package/dist/api/routes/admin.d.ts.map +1 -0
  28. package/dist/api/routes/admin.js +124 -0
  29. package/dist/api/routes/admin.js.map +1 -0
  30. package/dist/api/routes/contracts.d.ts +7 -0
  31. package/dist/api/routes/contracts.d.ts.map +1 -0
  32. package/dist/api/routes/contracts.js +665 -0
  33. package/dist/api/routes/contracts.js.map +1 -0
  34. package/dist/api/routes/dao.d.ts +7 -0
  35. package/dist/api/routes/dao.d.ts.map +1 -0
  36. package/dist/api/routes/dao.js +549 -0
  37. package/dist/api/routes/dao.js.map +1 -0
  38. package/dist/api/routes/dev.d.ts +9 -0
  39. package/dist/api/routes/dev.d.ts.map +1 -0
  40. package/dist/api/routes/dev.js +273 -0
  41. package/dist/api/routes/dev.js.map +1 -0
  42. package/dist/api/routes/escrows.d.ts +7 -0
  43. package/dist/api/routes/escrows.d.ts.map +1 -0
  44. package/dist/api/routes/escrows.js +454 -0
  45. package/dist/api/routes/escrows.js.map +1 -0
  46. package/dist/api/routes/identities.d.ts +7 -0
  47. package/dist/api/routes/identities.d.ts.map +1 -0
  48. package/dist/api/routes/identities.js +245 -0
  49. package/dist/api/routes/identities.js.map +1 -0
  50. package/dist/api/routes/markets-capabilities.d.ts +7 -0
  51. package/dist/api/routes/markets-capabilities.d.ts.map +1 -0
  52. package/dist/api/routes/markets-capabilities.js +477 -0
  53. package/dist/api/routes/markets-capabilities.js.map +1 -0
  54. package/dist/api/routes/markets-disputes.d.ts +7 -0
  55. package/dist/api/routes/markets-disputes.d.ts.map +1 -0
  56. package/dist/api/routes/markets-disputes.js +102 -0
  57. package/dist/api/routes/markets-disputes.js.map +1 -0
  58. package/dist/api/routes/markets-info.d.ts +7 -0
  59. package/dist/api/routes/markets-info.d.ts.map +1 -0
  60. package/dist/api/routes/markets-info.js +523 -0
  61. package/dist/api/routes/markets-info.js.map +1 -0
  62. package/dist/api/routes/markets-search.d.ts +7 -0
  63. package/dist/api/routes/markets-search.d.ts.map +1 -0
  64. package/dist/api/routes/markets-search.js +38 -0
  65. package/dist/api/routes/markets-search.js.map +1 -0
  66. package/dist/api/routes/markets-tasks.d.ts +7 -0
  67. package/dist/api/routes/markets-tasks.d.ts.map +1 -0
  68. package/dist/api/routes/markets-tasks.js +539 -0
  69. package/dist/api/routes/markets-tasks.js.map +1 -0
  70. package/dist/api/routes/node.d.ts +7 -0
  71. package/dist/api/routes/node.d.ts.map +1 -0
  72. package/dist/api/routes/node.js +53 -0
  73. package/dist/api/routes/node.js.map +1 -0
  74. package/dist/api/routes/nonce.d.ts +10 -0
  75. package/dist/api/routes/nonce.d.ts.map +1 -0
  76. package/dist/api/routes/nonce.js +65 -0
  77. package/dist/api/routes/nonce.js.map +1 -0
  78. package/dist/api/routes/reputations.d.ts +7 -0
  79. package/dist/api/routes/reputations.d.ts.map +1 -0
  80. package/dist/api/routes/reputations.js +243 -0
  81. package/dist/api/routes/reputations.js.map +1 -0
  82. package/dist/api/routes/transfers.d.ts +7 -0
  83. package/dist/api/routes/transfers.d.ts.map +1 -0
  84. package/dist/api/routes/transfers.js +88 -0
  85. package/dist/api/routes/transfers.js.map +1 -0
  86. package/dist/api/routes/wallets.d.ts +7 -0
  87. package/dist/api/routes/wallets.d.ts.map +1 -0
  88. package/dist/api/routes/wallets.js +132 -0
  89. package/dist/api/routes/wallets.js.map +1 -0
  90. package/dist/api/schemas/common.d.ts +45 -0
  91. package/dist/api/schemas/common.d.ts.map +1 -0
  92. package/dist/api/schemas/common.js +30 -0
  93. package/dist/api/schemas/common.js.map +1 -0
  94. package/dist/api/schemas/contracts.d.ts +284 -0
  95. package/dist/api/schemas/contracts.d.ts.map +1 -0
  96. package/dist/api/schemas/contracts.js +79 -0
  97. package/dist/api/schemas/contracts.js.map +1 -0
  98. package/dist/api/schemas/dao.d.ts +271 -0
  99. package/dist/api/schemas/dao.d.ts.map +1 -0
  100. package/dist/api/schemas/dao.js +78 -0
  101. package/dist/api/schemas/dao.js.map +1 -0
  102. package/dist/api/schemas/identity.d.ts +75 -0
  103. package/dist/api/schemas/identity.d.ts.map +1 -0
  104. package/dist/api/schemas/identity.js +32 -0
  105. package/dist/api/schemas/identity.js.map +1 -0
  106. package/dist/api/schemas/markets.d.ts +822 -0
  107. package/dist/api/schemas/markets.d.ts.map +1 -0
  108. package/dist/api/schemas/markets.js +246 -0
  109. package/dist/api/schemas/markets.js.map +1 -0
  110. package/dist/api/schemas/wallet.d.ts +163 -0
  111. package/dist/api/schemas/wallet.d.ts.map +1 -0
  112. package/dist/api/schemas/wallet.js +54 -0
  113. package/dist/api/schemas/wallet.js.map +1 -0
  114. package/dist/api/server.d.ts +45 -0
  115. package/dist/api/server.d.ts.map +1 -0
  116. package/dist/api/server.js +131 -0
  117. package/dist/api/server.js.map +1 -0
  118. package/dist/api/types.d.ts +69 -0
  119. package/dist/api/types.d.ts.map +1 -0
  120. package/dist/api/types.js +196 -0
  121. package/dist/api/types.js.map +1 -0
  122. package/dist/daemon.d.ts +11 -0
  123. package/dist/daemon.d.ts.map +1 -0
  124. package/dist/daemon.js +248 -0
  125. package/dist/daemon.js.map +1 -0
  126. package/dist/index.d.ts +137 -0
  127. package/dist/index.d.ts.map +1 -0
  128. package/dist/index.js +795 -0
  129. package/dist/index.js.map +1 -0
  130. package/dist/indexer/index.d.ts +10 -0
  131. package/dist/indexer/index.d.ts.map +1 -0
  132. package/dist/indexer/index.js +7 -0
  133. package/dist/indexer/index.js.map +1 -0
  134. package/dist/indexer/indexer.d.ts +60 -0
  135. package/dist/indexer/indexer.d.ts.map +1 -0
  136. package/dist/indexer/indexer.js +408 -0
  137. package/dist/indexer/indexer.js.map +1 -0
  138. package/dist/indexer/query.d.ts +141 -0
  139. package/dist/indexer/query.d.ts.map +1 -0
  140. package/dist/indexer/query.js +244 -0
  141. package/dist/indexer/query.js.map +1 -0
  142. package/dist/indexer/store.d.ts +95 -0
  143. package/dist/indexer/store.d.ts.map +1 -0
  144. package/dist/indexer/store.js +250 -0
  145. package/dist/indexer/store.js.map +1 -0
  146. package/dist/logger.d.ts +13 -0
  147. package/dist/logger.d.ts.map +1 -0
  148. package/dist/logger.js +37 -0
  149. package/dist/logger.js.map +1 -0
  150. package/dist/p2p/sync.d.ts +105 -0
  151. package/dist/p2p/sync.d.ts.map +1 -0
  152. package/dist/p2p/sync.js +875 -0
  153. package/dist/p2p/sync.js.map +1 -0
  154. package/dist/policy/liquidity-policy.d.ts +17 -0
  155. package/dist/policy/liquidity-policy.d.ts.map +1 -0
  156. package/dist/policy/liquidity-policy.js +112 -0
  157. package/dist/policy/liquidity-policy.js.map +1 -0
  158. package/dist/services/chain-config.d.ts +226 -0
  159. package/dist/services/chain-config.d.ts.map +1 -0
  160. package/dist/services/chain-config.js +105 -0
  161. package/dist/services/chain-config.js.map +1 -0
  162. package/dist/services/contract-provider.d.ts +44 -0
  163. package/dist/services/contract-provider.d.ts.map +1 -0
  164. package/dist/services/contract-provider.js +167 -0
  165. package/dist/services/contract-provider.js.map +1 -0
  166. package/dist/services/contracts-service.d.ts +192 -0
  167. package/dist/services/contracts-service.d.ts.map +1 -0
  168. package/dist/services/contracts-service.js +336 -0
  169. package/dist/services/contracts-service.js.map +1 -0
  170. package/dist/services/dao-service.d.ts +245 -0
  171. package/dist/services/dao-service.d.ts.map +1 -0
  172. package/dist/services/dao-service.js +389 -0
  173. package/dist/services/dao-service.js.map +1 -0
  174. package/dist/services/identity-service.d.ts +150 -0
  175. package/dist/services/identity-service.d.ts.map +1 -0
  176. package/dist/services/identity-service.js +286 -0
  177. package/dist/services/identity-service.js.map +1 -0
  178. package/dist/services/index.d.ts +20 -0
  179. package/dist/services/index.d.ts.map +1 -0
  180. package/dist/services/index.js +15 -0
  181. package/dist/services/index.js.map +1 -0
  182. package/dist/services/reputation-service.d.ts +128 -0
  183. package/dist/services/reputation-service.d.ts.map +1 -0
  184. package/dist/services/reputation-service.js +204 -0
  185. package/dist/services/reputation-service.js.map +1 -0
  186. package/dist/services/wallet-service.d.ts +201 -0
  187. package/dist/services/wallet-service.d.ts.map +1 -0
  188. package/dist/services/wallet-service.js +402 -0
  189. package/dist/services/wallet-service.js.map +1 -0
  190. package/package.json +66 -0
package/dist/index.js ADDED
@@ -0,0 +1,795 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { createRequire } from 'node:module';
3
+ import { join } from 'node:path';
4
+ const require = createRequire(import.meta.url);
5
+ const PKG_VERSION = require('../package.json').version;
6
+ import { privateKeyFromProtobuf } from '@libp2p/crypto/keys';
7
+ import { createEd25519PeerId, createFromProtobuf, exportToProtobuf } from '@libp2p/peer-id-factory';
8
+ import { bytesToUtf8, canonicalizeBytes, createKeyRecord, DEFAULT_P2P_CONFIG, DEFAULT_SNAPSHOT_POLICY, didFromPublicKey, ensureConfig, ensureStorageDirs, eventHashHex, EventStore, LevelStore, listKeyRecords, multibaseDecode, P2PNode, resolveStoragePaths, saveKeyRecord, signSnapshot, SnapshotScheduler, SnapshotStore, TOPIC_EVENTS, TOPIC_MARKETS, } from '@claw-network/core';
9
+ import { CONTENT_TYPE, encodeP2PEnvelopeBytes, InfoContentStore, isMarketEventEnvelope, MarketSearchStore, MemoryContractStore, MemoryDaoStore, MemoryReputationStore, signP2PEnvelope, } from '@claw-network/protocol';
10
+ import { P2PSync } from './p2p/sync.js';
11
+ import { ApiServer } from './api/server.js';
12
+ import { ContractProvider } from './services/index.js';
13
+ import { WalletService } from './services/wallet-service.js';
14
+ import { IdentityService } from './services/identity-service.js';
15
+ import { ReputationService } from './services/reputation-service.js';
16
+ import { ContractsService } from './services/contracts-service.js';
17
+ import { DaoService } from './services/dao-service.js';
18
+ import { IndexerStore, EventIndexer, IndexerQuery } from './indexer/index.js';
19
+ import { ApiKeyStore } from './api/api-key-store.js';
20
+ export const DEFAULT_SYNC_RUNTIME_CONFIG = {
21
+ rangeIntervalMs: 30_000,
22
+ snapshotIntervalMs: 5 * 60_000,
23
+ requestRangeOnStart: true,
24
+ requestSnapshotOnStart: true,
25
+ };
26
+ export const DEFAULT_NODE_RUNTIME_CONFIG = {
27
+ sync: { ...DEFAULT_SYNC_RUNTIME_CONFIG },
28
+ api: { host: '127.0.0.1', port: 9528, enabled: true },
29
+ };
30
+ export class ClawNetNode {
31
+ config;
32
+ p2p;
33
+ sync;
34
+ eventDb;
35
+ stateDb;
36
+ eventStore;
37
+ snapshotStore;
38
+ snapshotScheduler;
39
+ contractStore;
40
+ reputationStore;
41
+ daoStore;
42
+ marketSearchStore;
43
+ infoContentStore;
44
+ rangeTimer;
45
+ snapshotTimer;
46
+ meshAmplifierTimer;
47
+ apiServer;
48
+ contractProvider;
49
+ indexerStore;
50
+ eventIndexer;
51
+ indexerQuery;
52
+ walletService;
53
+ identityService;
54
+ reputationService;
55
+ contractsService;
56
+ daoService;
57
+ apiKeyStore;
58
+ peerId;
59
+ peerPrivateKey;
60
+ startedAt;
61
+ persistedConfig;
62
+ starting;
63
+ stopping;
64
+ cachedDid;
65
+ constructor(config = {}) {
66
+ this.config = {
67
+ ...DEFAULT_NODE_RUNTIME_CONFIG,
68
+ ...config,
69
+ sync: {
70
+ ...DEFAULT_SYNC_RUNTIME_CONFIG,
71
+ ...config.sync,
72
+ },
73
+ api: {
74
+ ...DEFAULT_NODE_RUNTIME_CONFIG.api,
75
+ ...config.api,
76
+ },
77
+ };
78
+ }
79
+ async start() {
80
+ if (this.starting) {
81
+ return this.starting;
82
+ }
83
+ this.starting = this.startInternal();
84
+ return this.starting;
85
+ }
86
+ async startInternal() {
87
+ if (this.p2p) {
88
+ return;
89
+ }
90
+ // Init order: config -> storage -> p2p -> sync -> api
91
+ const paths = resolveStoragePaths(this.config.dataDir);
92
+ await ensureStorageDirs(paths);
93
+ const persisted = await ensureConfig(paths);
94
+ this.persistedConfig = persisted;
95
+ const peerId = await this.loadOrCreatePeerId(paths.keys);
96
+ const privateKey = this.extractPeerPrivateKey(peerId);
97
+ // Auto-create identity key record if none exists and passphrase is available
98
+ await this.ensureIdentityKeyRecord(paths, peerId);
99
+ // Cache DID for synchronous access
100
+ this.cachedDid = (await this.resolveLocalDid()) ?? undefined;
101
+ // Convert PeerId's protobuf private key to PrivateKey object for libp2p v3
102
+ const libp2pPrivateKey = peerId.privateKey
103
+ ? privateKeyFromProtobuf(peerId.privateKey)
104
+ : undefined;
105
+ const p2pConfig = {
106
+ ...DEFAULT_P2P_CONFIG,
107
+ ...this.config.p2p,
108
+ listen: this.config.p2p?.listen ?? persisted.p2p?.listen ?? DEFAULT_P2P_CONFIG.listen,
109
+ bootstrap: this.config.p2p?.bootstrap ?? persisted.p2p?.bootstrap ?? DEFAULT_P2P_CONFIG.bootstrap,
110
+ };
111
+ try {
112
+ this.eventDb = new LevelStore({ path: paths.eventsDb });
113
+ this.stateDb = new LevelStore({ path: paths.stateDb });
114
+ this.eventStore = new EventStore(this.eventDb);
115
+ this.snapshotStore = new SnapshotStore(paths);
116
+ await this.initContractStore();
117
+ await this.initReputationStore();
118
+ await this.initDaoStore();
119
+ await this.initMarketSearchStore();
120
+ await this.initInfoContentStore();
121
+ if (this.config.snapshotBuilder) {
122
+ this.snapshotScheduler = new SnapshotScheduler(this.eventStore, this.snapshotStore, {
123
+ ...DEFAULT_SNAPSHOT_POLICY,
124
+ ...(this.config.snapshotPolicy ?? {}),
125
+ });
126
+ }
127
+ this.p2p = new P2PNode(p2pConfig, libp2pPrivateKey, peerId);
128
+ await this.p2p.start();
129
+ this.peerId = peerId;
130
+ this.peerPrivateKey = privateKey;
131
+ if (!this.startedAt) {
132
+ this.startedAt = Date.now();
133
+ }
134
+ const syncOptions = { ...(this.config.sync ?? {}) };
135
+ delete syncOptions.rangeIntervalMs;
136
+ delete syncOptions.snapshotIntervalMs;
137
+ delete syncOptions.requestRangeOnStart;
138
+ delete syncOptions.requestSnapshotOnStart;
139
+ this.sync = new P2PSync(this.p2p, this.eventStore, this.snapshotStore, {
140
+ peerId: peerId.toString(),
141
+ peerPrivateKey: privateKey,
142
+ resolvePeerPublicKey: (id) => this.p2p?.getPeerPublicKey(id) ?? Promise.resolve(null),
143
+ resolveControllerPublicKey: this.config.resolveControllerPublicKey,
144
+ onEventApplied: (envelope) => this.applyEventStores(envelope),
145
+ ...syncOptions,
146
+ });
147
+ await this.sync.start();
148
+ await this.startSyncLoops();
149
+ // Actively discover peers beyond bootstrap to form a full-mesh topology
150
+ this.startMeshAmplifier();
151
+ if (this.config.api?.enabled !== false) {
152
+ // ── On-chain infrastructure (optional) ─────────────────────────
153
+ if (this.config.chain) {
154
+ const storagePaths = resolveStoragePaths(this.config.dataDir);
155
+ const dbPath = join(storagePaths.root, 'indexer.sqlite');
156
+ this.contractProvider = new ContractProvider(this.config.chain);
157
+ this.indexerStore = new IndexerStore(dbPath);
158
+ this.indexerQuery = new IndexerQuery(this.indexerStore.database);
159
+ this.walletService = new WalletService(this.contractProvider, this.indexerQuery);
160
+ this.identityService = new IdentityService(this.contractProvider, this.indexerQuery);
161
+ this.reputationService = new ReputationService(this.contractProvider, this.indexerQuery);
162
+ this.contractsService = new ContractsService(this.contractProvider, this.indexerQuery);
163
+ this.daoService = new DaoService(this.contractProvider, this.indexerQuery);
164
+ this.eventIndexer = new EventIndexer(this.contractProvider, this.indexerStore, this.config.indexer);
165
+ // Start indexer in background (non-blocking).
166
+ void this.eventIndexer.start();
167
+ }
168
+ // ── API Key store ────────────────────────────────────────────
169
+ {
170
+ const storagePaths2 = resolveStoragePaths(this.config.dataDir);
171
+ const apiKeysDbPath = join(storagePaths2.root, 'api-keys.sqlite');
172
+ this.apiKeyStore = new ApiKeyStore(apiKeysDbPath);
173
+ }
174
+ const apiConfig = {
175
+ host: this.config.api?.host ?? '127.0.0.1',
176
+ port: this.config.api?.port ?? 9528,
177
+ dataDir: this.config.dataDir,
178
+ network: this.config.network ?? persisted.network,
179
+ };
180
+ this.apiServer = new ApiServer(apiConfig, {
181
+ publishEvent: (envelope) => this.publishEvent(envelope),
182
+ eventStore: this.eventStore,
183
+ contractStore: this.contractStore,
184
+ reputationStore: this.reputationStore,
185
+ daoStore: this.daoStore,
186
+ marketStore: this.marketSearchStore,
187
+ infoContentStore: this.infoContentStore,
188
+ walletService: this.walletService,
189
+ identityService: this.identityService,
190
+ reputationService: this.reputationService,
191
+ contractsService: this.contractsService,
192
+ daoService: this.daoService,
193
+ searchMarkets: (query) => {
194
+ if (!this.marketSearchStore) {
195
+ throw new Error('market search unavailable');
196
+ }
197
+ return this.marketSearchStore.search(query);
198
+ },
199
+ getNodeStatus: () => this.buildNodeStatus(),
200
+ getNodePeers: () => this.buildNodePeers(),
201
+ getNodeConfig: () => this.buildNodeConfig(),
202
+ apiKeyStore: this.apiKeyStore,
203
+ });
204
+ await this.apiServer.start();
205
+ }
206
+ }
207
+ catch (error) {
208
+ console.error('[clawnetd] Startup failed:', error?.message ?? error);
209
+ await this.stop();
210
+ throw error;
211
+ }
212
+ finally {
213
+ this.starting = undefined;
214
+ }
215
+ }
216
+ async stop() {
217
+ if (this.stopping) {
218
+ return this.stopping;
219
+ }
220
+ this.stopping = this.stopInternal();
221
+ return this.stopping;
222
+ }
223
+ async stopInternal() {
224
+ // Shutdown order: api -> sync -> p2p -> storage
225
+ this.stopSyncLoops();
226
+ this.stopMeshAmplifier();
227
+ const tasks = [
228
+ async () => this.apiServer?.stop(),
229
+ async () => this.eventIndexer?.stop(),
230
+ async () => this.contractProvider?.destroy(),
231
+ async () => this.sync?.stop(),
232
+ async () => this.p2p?.stop(),
233
+ async () => this.eventDb?.close(),
234
+ async () => this.stateDb?.close(),
235
+ ];
236
+ for (const task of tasks) {
237
+ try {
238
+ await task();
239
+ }
240
+ catch {
241
+ // Best-effort shutdown; keep going to release remaining resources.
242
+ }
243
+ }
244
+ // Synchronous cleanup (SQLite)
245
+ try {
246
+ this.indexerStore?.close();
247
+ }
248
+ catch { /* ignore */ }
249
+ try {
250
+ this.apiKeyStore?.close();
251
+ }
252
+ catch { /* ignore */ }
253
+ this.apiServer = undefined;
254
+ this.sync = undefined;
255
+ this.p2p = undefined;
256
+ this.eventDb = undefined;
257
+ this.stateDb = undefined;
258
+ this.eventStore = undefined;
259
+ this.snapshotStore = undefined;
260
+ this.snapshotScheduler = undefined;
261
+ this.reputationStore = undefined;
262
+ this.marketSearchStore = undefined;
263
+ this.contractProvider = undefined;
264
+ this.indexerStore = undefined;
265
+ this.apiKeyStore = undefined;
266
+ this.eventIndexer = undefined;
267
+ this.indexerQuery = undefined;
268
+ this.walletService = undefined;
269
+ this.identityService = undefined;
270
+ this.reputationService = undefined;
271
+ this.contractsService = undefined;
272
+ this.daoService = undefined;
273
+ this.peerId = undefined;
274
+ this.peerPrivateKey = undefined;
275
+ this.stopping = undefined;
276
+ }
277
+ getPeerId() {
278
+ if (!this.peerId) {
279
+ return null;
280
+ }
281
+ return this.peerId.toString();
282
+ }
283
+ /** Return the node's DID (synchronous, cached after start). */
284
+ getDid() {
285
+ return this.cachedDid ?? null;
286
+ }
287
+ getHealth() {
288
+ const p2p = Boolean(this.p2p);
289
+ const sync = Boolean(this.sync);
290
+ const eventStore = Boolean(this.eventStore);
291
+ const apiExpected = this.config.api?.enabled !== false;
292
+ const api = apiExpected ? Boolean(this.apiServer) : true;
293
+ const ok = p2p && sync && eventStore && api;
294
+ return { ok, checks: { p2p, sync, eventStore, api } };
295
+ }
296
+ async buildNodeStatus() {
297
+ const did = await this.resolveLocalDid();
298
+ // In chain-enabled mode, expose indexed chain height (authoritative for API clients).
299
+ // Fall back to event log length for pure P2P/off-chain mode.
300
+ const blockHeight = this.indexerStore?.lastIndexedBlock ??
301
+ (this.eventStore ? await this.eventStore.getLogLength() : 0);
302
+ const peers = this.p2p?.getPeers().length ?? 0;
303
+ const connections = this.p2p?.getConnections().length ?? 0;
304
+ const network = this.resolveNetwork();
305
+ const uptime = this.startedAt ? Math.floor((Date.now() - this.startedAt) / 1000) : 0;
306
+ return {
307
+ did: did ?? '',
308
+ peerId: this.p2p?.getPeerId() ?? this.peerId?.toString() ?? '',
309
+ synced: Boolean(this.p2p),
310
+ blockHeight,
311
+ peers,
312
+ connections,
313
+ network,
314
+ version: process.env.CLAWNET_VERSION ?? PKG_VERSION,
315
+ uptime,
316
+ };
317
+ }
318
+ async buildNodePeers() {
319
+ const peerIds = this.p2p?.getPeers() ?? [];
320
+ const connectionPeerIds = this.p2p?.getConnections() ?? [];
321
+ const allPeerIds = [...new Set([...peerIds, ...connectionPeerIds])];
322
+ return {
323
+ peers: allPeerIds.map((peerId) => ({
324
+ peerId,
325
+ pubsub: peerIds.includes(peerId),
326
+ connected: connectionPeerIds.includes(peerId),
327
+ })),
328
+ total: allPeerIds.length,
329
+ };
330
+ }
331
+ async buildNodeConfig() {
332
+ const dataDir = resolveStoragePaths(this.config.dataDir).root;
333
+ const network = this.resolveNetwork();
334
+ const p2pPort = this.resolveP2PPort();
335
+ const apiPort = this.config.api?.port ?? 9528;
336
+ const apiEnabled = this.config.api?.enabled !== false;
337
+ return {
338
+ dataDir,
339
+ network,
340
+ p2pPort,
341
+ apiPort,
342
+ apiEnabled,
343
+ };
344
+ }
345
+ async publishEvent(envelope) {
346
+ if (!this.p2p || !this.eventStore || !this.peerId || !this.peerPrivateKey) {
347
+ throw new Error('node not started');
348
+ }
349
+ if (!envelope.sig) {
350
+ throw new Error('event signature missing');
351
+ }
352
+ const hash = typeof envelope.hash === 'string' && envelope.hash.length
353
+ ? envelope.hash
354
+ : eventHashHex(envelope);
355
+ const canonical = canonicalizeBytes(envelope);
356
+ await this.eventStore.appendEvent(hash, canonical);
357
+ await this.applyEventStores(envelope);
358
+ const p2pEnvelope = await signP2PEnvelope({
359
+ v: 1,
360
+ topic: TOPIC_EVENTS,
361
+ sender: this.peerId.toString(),
362
+ ts: BigInt(Date.now()),
363
+ contentType: CONTENT_TYPE,
364
+ payload: canonical,
365
+ }, this.peerPrivateKey);
366
+ const bytes = encodeP2PEnvelopeBytes(p2pEnvelope);
367
+ await this.p2p.publish(TOPIC_EVENTS, bytes);
368
+ if (isMarketEventEnvelope(envelope)) {
369
+ const marketEnvelope = await signP2PEnvelope({
370
+ v: 1,
371
+ topic: TOPIC_MARKETS,
372
+ sender: this.peerId.toString(),
373
+ ts: BigInt(Date.now()),
374
+ contentType: CONTENT_TYPE,
375
+ payload: canonical,
376
+ }, this.peerPrivateKey);
377
+ const marketBytes = encodeP2PEnvelopeBytes(marketEnvelope);
378
+ await this.p2p.publish(TOPIC_MARKETS, marketBytes);
379
+ }
380
+ return hash;
381
+ }
382
+ async initReputationStore() {
383
+ if (!this.eventStore) {
384
+ return;
385
+ }
386
+ const store = new MemoryReputationStore();
387
+ let cursor = null;
388
+ while (true) {
389
+ const { events, cursor: next } = await this.eventStore.getEventLogRange(cursor, 200);
390
+ if (!events.length) {
391
+ break;
392
+ }
393
+ for (const bytes of events) {
394
+ const envelope = this.parseEventEnvelope(bytes);
395
+ if (!envelope) {
396
+ continue;
397
+ }
398
+ try {
399
+ await store.applyEvent(envelope);
400
+ }
401
+ catch {
402
+ continue;
403
+ }
404
+ }
405
+ if (!next) {
406
+ break;
407
+ }
408
+ cursor = next;
409
+ }
410
+ this.reputationStore = store;
411
+ }
412
+ async initContractStore() {
413
+ if (!this.eventStore) {
414
+ return;
415
+ }
416
+ const store = new MemoryContractStore();
417
+ let cursor = null;
418
+ while (true) {
419
+ const { events, cursor: next } = await this.eventStore.getEventLogRange(cursor, 200);
420
+ if (!events.length) {
421
+ break;
422
+ }
423
+ for (const bytes of events) {
424
+ const envelope = this.parseEventEnvelope(bytes);
425
+ if (!envelope) {
426
+ continue;
427
+ }
428
+ try {
429
+ await store.applyEvent(envelope);
430
+ }
431
+ catch {
432
+ continue;
433
+ }
434
+ }
435
+ if (!next) {
436
+ break;
437
+ }
438
+ cursor = next;
439
+ }
440
+ this.contractStore = store;
441
+ }
442
+ async initDaoStore() {
443
+ if (!this.eventStore) {
444
+ return;
445
+ }
446
+ const store = new MemoryDaoStore();
447
+ let cursor = null;
448
+ while (true) {
449
+ const { events, cursor: next } = await this.eventStore.getEventLogRange(cursor, 200);
450
+ if (!events.length) {
451
+ break;
452
+ }
453
+ for (const bytes of events) {
454
+ const envelope = this.parseEventEnvelope(bytes);
455
+ if (!envelope) {
456
+ continue;
457
+ }
458
+ try {
459
+ await store.applyEvent(envelope);
460
+ }
461
+ catch {
462
+ continue;
463
+ }
464
+ }
465
+ if (!next) {
466
+ break;
467
+ }
468
+ cursor = next;
469
+ }
470
+ this.daoStore = store;
471
+ }
472
+ async initMarketSearchStore() {
473
+ if (!this.eventStore || !this.stateDb) {
474
+ return;
475
+ }
476
+ const store = new MarketSearchStore(this.stateDb);
477
+ try {
478
+ await store.loadFromStore();
479
+ await store.syncFromEventLog(this.eventStore);
480
+ }
481
+ catch {
482
+ await store.rebuildFromEventLog(this.eventStore);
483
+ }
484
+ this.marketSearchStore = store;
485
+ }
486
+ async initInfoContentStore() {
487
+ if (!this.eventStore || !this.stateDb) {
488
+ return;
489
+ }
490
+ const store = new InfoContentStore(this.stateDb);
491
+ try {
492
+ await store.loadFromStore();
493
+ await store.syncFromEventLog(this.eventStore);
494
+ }
495
+ catch {
496
+ await store.rebuildFromEventLog(this.eventStore);
497
+ }
498
+ this.infoContentStore = store;
499
+ }
500
+ async applyEventStores(envelope) {
501
+ if (this.contractStore) {
502
+ try {
503
+ await this.contractStore.applyEvent(envelope);
504
+ }
505
+ catch {
506
+ // Ignore malformed events for contract aggregation.
507
+ }
508
+ }
509
+ if (this.reputationStore) {
510
+ try {
511
+ await this.reputationStore.applyEvent(envelope);
512
+ }
513
+ catch {
514
+ // Ignore malformed events for reputation aggregation.
515
+ }
516
+ }
517
+ if (this.daoStore) {
518
+ try {
519
+ await this.daoStore.applyEvent(envelope);
520
+ }
521
+ catch {
522
+ // Ignore malformed events for DAO aggregation.
523
+ }
524
+ }
525
+ if (this.marketSearchStore) {
526
+ try {
527
+ await this.marketSearchStore.applyEvent(envelope);
528
+ }
529
+ catch {
530
+ // Ignore malformed events for market indexing.
531
+ }
532
+ }
533
+ if (this.infoContentStore) {
534
+ try {
535
+ await this.infoContentStore.applyEvent(envelope);
536
+ }
537
+ catch {
538
+ // Ignore malformed events for info content linking.
539
+ }
540
+ }
541
+ }
542
+ parseEventEnvelope(bytes) {
543
+ try {
544
+ return JSON.parse(bytesToUtf8(bytes));
545
+ }
546
+ catch {
547
+ return null;
548
+ }
549
+ }
550
+ resolveNetwork() {
551
+ return this.config.network ?? this.persistedConfig?.network ?? 'devnet';
552
+ }
553
+ resolveP2PPort() {
554
+ const listen = this.config.p2p?.listen ?? this.persistedConfig?.p2p?.listen ?? DEFAULT_P2P_CONFIG.listen;
555
+ for (const addr of listen) {
556
+ const match = addr.match(/\/tcp\/(\d+)/);
557
+ if (match) {
558
+ return Number.parseInt(match[1], 10);
559
+ }
560
+ }
561
+ return 9527;
562
+ }
563
+ async resolveLocalDid() {
564
+ try {
565
+ const paths = resolveStoragePaths(this.config.dataDir);
566
+ const records = await listKeyRecords(paths);
567
+ if (!records.length) {
568
+ return null;
569
+ }
570
+ const sorted = records
571
+ .map((record) => ({ record, createdAt: Date.parse(record.createdAt ?? '') }))
572
+ .sort((a, b) => {
573
+ const left = Number.isFinite(a.createdAt) ? a.createdAt : Number.MAX_SAFE_INTEGER;
574
+ const right = Number.isFinite(b.createdAt) ? b.createdAt : Number.MAX_SAFE_INTEGER;
575
+ return left - right;
576
+ });
577
+ const primary = sorted[0]?.record;
578
+ if (!primary?.publicKey) {
579
+ return null;
580
+ }
581
+ const publicKeyBytes = multibaseDecode(primary.publicKey);
582
+ return didFromPublicKey(publicKeyBytes);
583
+ }
584
+ catch {
585
+ return null;
586
+ }
587
+ }
588
+ async startSyncLoops() {
589
+ if (!this.sync || !this.eventStore) {
590
+ return;
591
+ }
592
+ const syncConfig = this.config.sync ?? {};
593
+ const requestSnapshotOnStart = syncConfig.requestSnapshotOnStart ?? true;
594
+ const requestRangeOnStart = syncConfig.requestRangeOnStart ?? true;
595
+ if (requestSnapshotOnStart) {
596
+ const latest = await this.snapshotStore?.loadLatestSnapshot();
597
+ await this.sync.requestSnapshot(latest?.hash ?? '');
598
+ }
599
+ if (requestRangeOnStart) {
600
+ const latestHash = await this.eventStore.getLatestEventHash();
601
+ await this.sync.requestRange(latestHash ?? '');
602
+ }
603
+ if (syncConfig.rangeIntervalMs && syncConfig.rangeIntervalMs > 0) {
604
+ this.rangeTimer = setInterval(() => {
605
+ void this.requestRangeTick();
606
+ }, syncConfig.rangeIntervalMs);
607
+ }
608
+ if (syncConfig.snapshotIntervalMs && syncConfig.snapshotIntervalMs > 0) {
609
+ this.snapshotTimer = setInterval(() => {
610
+ void this.requestSnapshotTick();
611
+ }, syncConfig.snapshotIntervalMs);
612
+ }
613
+ }
614
+ stopSyncLoops() {
615
+ if (this.rangeTimer) {
616
+ clearInterval(this.rangeTimer);
617
+ this.rangeTimer = undefined;
618
+ }
619
+ if (this.snapshotTimer) {
620
+ clearInterval(this.snapshotTimer);
621
+ this.snapshotTimer = undefined;
622
+ }
623
+ }
624
+ /**
625
+ * Persistent connection watchdog with two phases:
626
+ *
627
+ * Phase 1 (0–60 s): Aggressive mesh amplification every 5 s.
628
+ * Discovers peers via DHT random-walk + peerStore dial.
629
+ * Phase 2 (60 s+): Every 30 s, check connection count. If below
630
+ * `minConnections` (1), re-dial bootstrap peers and
631
+ * run another amplify cycle to rejoin the mesh.
632
+ *
633
+ * This replaces the old one-shot amplifier that stopped permanently after 60 s
634
+ * and left the node unable to reconnect after transient network disruptions.
635
+ */
636
+ startMeshAmplifier() {
637
+ let attempts = 0;
638
+ const aggressiveAttempts = 12; // 12 × 5 s = 60 s
639
+ const aggressiveIntervalMs = 5_000;
640
+ const watchdogIntervalMs = 30_000;
641
+ const amplify = async () => {
642
+ attempts++;
643
+ try {
644
+ const n = await this.p2p?.amplifyMesh() ?? 0;
645
+ if (n > 0) {
646
+ console.log(`[mesh] +${n} new peer(s) discovered via DHT walk`);
647
+ }
648
+ }
649
+ catch {
650
+ // best-effort
651
+ }
652
+ if (attempts === aggressiveAttempts) {
653
+ // Transition from aggressive phase to watchdog phase
654
+ if (this.meshAmplifierTimer) {
655
+ clearInterval(this.meshAmplifierTimer);
656
+ }
657
+ const conns = this.p2p?.getConnections().length ?? 0;
658
+ console.log(`[mesh] aggressive phase complete — ${conns} peer connection(s), switching to watchdog`);
659
+ this.meshAmplifierTimer = setInterval(() => void watchdog(), watchdogIntervalMs);
660
+ }
661
+ };
662
+ const watchdog = async () => {
663
+ const conns = this.p2p?.getConnections().length ?? 0;
664
+ if (conns > 0)
665
+ return; // mesh is healthy
666
+ console.log(`[mesh] 0 connections detected — attempting reconnect`);
667
+ try {
668
+ const bootstrapCount = await this.p2p?.reconnectBootstrap() ?? 0;
669
+ if (bootstrapCount > 0) {
670
+ console.log(`[mesh] reconnected to ${bootstrapCount} bootstrap peer(s)`);
671
+ }
672
+ // Also try DHT walk to discover non-bootstrap peers
673
+ const n = await this.p2p?.amplifyMesh() ?? 0;
674
+ if (n > 0) {
675
+ console.log(`[mesh] +${n} additional peer(s) via DHT walk`);
676
+ }
677
+ if (bootstrapCount === 0 && n === 0) {
678
+ console.log(`[mesh] reconnect failed — will retry in ${watchdogIntervalMs / 1000}s`);
679
+ }
680
+ }
681
+ catch {
682
+ // best-effort reconnect
683
+ }
684
+ };
685
+ // First attempt immediately, then every 5 s for the aggressive phase
686
+ void amplify();
687
+ this.meshAmplifierTimer = setInterval(() => void amplify(), aggressiveIntervalMs);
688
+ }
689
+ stopMeshAmplifier() {
690
+ if (this.meshAmplifierTimer) {
691
+ clearInterval(this.meshAmplifierTimer);
692
+ this.meshAmplifierTimer = undefined;
693
+ }
694
+ }
695
+ async requestRangeTick() {
696
+ if (!this.sync || !this.eventStore) {
697
+ return;
698
+ }
699
+ const latestHash = await this.eventStore.getLatestEventHash();
700
+ await this.sync.requestRange(latestHash ?? '');
701
+ }
702
+ async requestSnapshotTick() {
703
+ if (!this.sync || !this.snapshotStore) {
704
+ return;
705
+ }
706
+ const latest = await this.snapshotStore.loadLatestSnapshot();
707
+ await this.sync.requestSnapshot(latest?.hash ?? '');
708
+ await this.maybeCreateSnapshot();
709
+ }
710
+ async maybeCreateSnapshot() {
711
+ if (!this.snapshotScheduler || !this.snapshotStore || !this.eventStore) {
712
+ return;
713
+ }
714
+ if (!this.config.snapshotBuilder) {
715
+ return;
716
+ }
717
+ const should = await this.snapshotScheduler.shouldSnapshot();
718
+ if (!should) {
719
+ return;
720
+ }
721
+ const lastSnapshot = await this.snapshotStore.loadLatestSnapshot();
722
+ const base = await this.config.snapshotBuilder({
723
+ eventStore: this.eventStore,
724
+ snapshotStore: this.snapshotStore,
725
+ lastSnapshot,
726
+ });
727
+ if (!base) {
728
+ return;
729
+ }
730
+ if (!this.peerPrivateKey || !this.peerId) {
731
+ return;
732
+ }
733
+ const signed = await signSnapshot(base, this.peerId.toString(), this.peerPrivateKey);
734
+ await this.snapshotStore.saveSnapshot(signed);
735
+ }
736
+ async loadOrCreatePeerId(keysDir) {
737
+ const path = join(keysDir, 'peer-id.bin');
738
+ try {
739
+ const data = await readFile(path);
740
+ return createFromProtobuf(new Uint8Array(data));
741
+ }
742
+ catch (error) {
743
+ if (error.code !== 'ENOENT') {
744
+ throw error;
745
+ }
746
+ }
747
+ const peerId = await createEd25519PeerId();
748
+ const protobuf = exportToProtobuf(peerId);
749
+ await writeFile(path, protobuf);
750
+ return peerId;
751
+ }
752
+ async ensureIdentityKeyRecord(paths, peerId) {
753
+ const passphrase = this.config.passphrase;
754
+ if (!passphrase) {
755
+ throw new Error('CLAW_PASSPHRASE is required. Without it the node cannot create an identity (DID) ' +
756
+ 'and will be unable to sign transactions, join markets, or operate a wallet. ' +
757
+ 'Set it via --passphrase <str> or the CLAW_PASSPHRASE environment variable.');
758
+ }
759
+ const existing = await listKeyRecords(paths);
760
+ if (existing.length > 0) {
761
+ return; // Identity already exists
762
+ }
763
+ if (!peerId.privateKey) {
764
+ return;
765
+ }
766
+ // Extract Ed25519 key pair from PeerId
767
+ const libp2pKey = privateKeyFromProtobuf(peerId.privateKey);
768
+ const privKeyRaw = libp2pKey.raw.length >= 32 ? libp2pKey.raw.slice(0, 32) : libp2pKey.raw;
769
+ const pubKeyRaw = libp2pKey.publicKey.raw;
770
+ const record = createKeyRecord(pubKeyRaw, privKeyRaw, passphrase);
771
+ await saveKeyRecord(paths, record);
772
+ }
773
+ extractPeerPrivateKey(peerId) {
774
+ if (!peerId?.privateKey) {
775
+ throw new Error('PeerId missing private key');
776
+ }
777
+ const privateKey = privateKeyFromProtobuf(peerId.privateKey);
778
+ if (privateKey.type !== 'Ed25519') {
779
+ throw new Error(`Unsupported peer key type ${privateKey.type}`);
780
+ }
781
+ const raw = privateKey.raw;
782
+ if (raw.length === 32) {
783
+ return raw;
784
+ }
785
+ if (raw.length >= 32) {
786
+ return raw.slice(0, 32);
787
+ }
788
+ throw new Error('Invalid peer private key length');
789
+ }
790
+ }
791
+ export { DEFAULT_P2P_SYNC_CONFIG } from './p2p/sync.js';
792
+ export * from './p2p/sync.js';
793
+ export { ApiKeyStore } from './api/api-key-store.js';
794
+ export { getApiKeyAuth } from './api/auth.js';
795
+ //# sourceMappingURL=index.js.map