@totemsdk/omnia-host 0.10.0 → 0.12.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.
@@ -38,7 +38,7 @@ function createTotemNodeAdapter(options) {
38
38
  getTokensByCreator: (address) => provider.getTokensByCreator(address),
39
39
  broadcastTxPoW: (txpowHex) => provider.broadcastTxPoW(txpowHex),
40
40
  getMinTxPowWork: () => provider.getMinTxPowWork ? provider.getMinTxPowWork() : Promise.resolve(null),
41
- ...(provider.submitTransaction
41
+ ...(provider.submitTransaction && process.env.OMNIA_CHAIN_NO_TXNMINE !== '1'
42
42
  ? { submitTransaction: provider.submitTransaction.bind(provider) }
43
43
  : {}),
44
44
  async getMode() {
package/dist/cli.js CHANGED
@@ -1,10 +1,74 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_path_1 = __importDefault(require("node:path"));
4
8
  const index_js_1 = require("./index.js");
9
+ const pairing_js_1 = require("./pairing.js");
5
10
  const signing_js_1 = require("./signing.js");
6
11
  const identity_js_1 = require("./identity.js");
12
+ const signer_port_js_1 = require("./signer-port.js");
13
+ async function runPair() {
14
+ const config = (0, index_js_1.loadConfigFromEnv)();
15
+ const port = Number(process.env.OMNIA_PAIR_PORT ?? 7789);
16
+ const host = process.env.OMNIA_PAIR_HOST ?? '127.0.0.1';
17
+ const origins = (process.env.OMNIA_PAIR_ORIGINS ?? '')
18
+ .split(',')
19
+ .map((value) => value.trim())
20
+ .filter(Boolean);
21
+ const capabilities = {
22
+ chain: { rpcUrl: config.chainRpcUrl, network: 'minima' },
23
+ lease: { modes: ['local', 'hybrid'] },
24
+ ...(config.p2pHubPort
25
+ ? {
26
+ p2p: {
27
+ hubUrl: `ws://${config.p2pHubHost ?? '127.0.0.1'}:${config.p2pHubPort}`,
28
+ scopes: ['p2p.pubsub'],
29
+ ...(config.p2pHubTopics ? { topicPrefixes: config.p2pHubTopics } : {}),
30
+ },
31
+ }
32
+ : {}),
33
+ };
34
+ const service = (0, pairing_js_1.createPairingService)({
35
+ storePath: node_path_1.default.join(node_path_1.default.dirname(config.dbPath), 'pairing.json'),
36
+ endpoint: `http://${host}:${port}`,
37
+ network: 'minima',
38
+ capabilities,
39
+ ...(origins.length ? { allowedOrigins: origins } : {}),
40
+ });
41
+ const server = (0, pairing_js_1.createPairingHttpServer)(service, { host, port });
42
+ const address = await server.listen();
43
+ const invitation = service.createInvitation();
44
+ if (process.argv.includes('--json')) {
45
+ console.log(JSON.stringify({ ...invitation, endpoint: `http://${address.host}:${address.port}` }, null, 2));
46
+ }
47
+ else {
48
+ console.log('');
49
+ console.log('Pair this node with your Totem extension');
50
+ console.log('');
51
+ console.log(`Node: ${service.nodeId}`);
52
+ console.log(`Network: ${service.network}`);
53
+ console.log(`Fingerprint: ${service.fingerprint}`);
54
+ console.log(`Endpoint: http://${address.host}:${address.port}`);
55
+ console.log(`Expires: ${new Date(invitation.expiresAt).toISOString()}`);
56
+ console.log('');
57
+ console.log(`Code: ${invitation.code}`);
58
+ console.log('');
59
+ console.log('Open Totem → Settings → Pair Node and paste the endpoint + code.');
60
+ }
61
+ await new Promise((resolve) => {
62
+ process.once('SIGINT', resolve);
63
+ process.once('SIGTERM', resolve);
64
+ });
65
+ await server.close();
66
+ }
7
67
  async function main() {
68
+ if (process.argv[2] === 'pair') {
69
+ await runPair();
70
+ return;
71
+ }
8
72
  const config = (0, index_js_1.loadConfigFromEnv)();
9
73
  const signing = config.readOnly || !(0, signing_js_1.hasSigningMaterial)(config)
10
74
  ? undefined
@@ -16,6 +80,7 @@ async function main() {
16
80
  ? {
17
81
  signer: signing.signer,
18
82
  leaseProvider: signing.leaseProvider,
83
+ signerPort: (0, signer_port_js_1.createLocalSignerPort)(signing),
19
84
  transportKeyPair: signing.transportKeyPair,
20
85
  transportPubkey: signing.transportPubkey,
21
86
  identity: identityAndManifest?.identity
package/dist/config.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { CustodyMode } from '@totemsdk/omnia';
1
2
  export interface OmniaHostConfig {
2
3
  port: number;
3
4
  host: string;
@@ -5,6 +6,14 @@ export interface OmniaHostConfig {
5
6
  chainRpcUrl: string;
6
7
  chainRpcPassword?: string;
7
8
  analyticsDbPath?: string;
9
+ /** Durable observation journal DB (default: alongside dbPath). */
10
+ observeDbPath?: string;
11
+ /** Observation network id (multi-network deployments). */
12
+ networkId?: string;
13
+ /** Stable observation source id (default: localPartyId or "omnia-host"). */
14
+ sourceNodeId?: string;
15
+ /** Disable the observation feed entirely (OMNIA_OBSERVE=0). */
16
+ observeDisabled?: boolean;
8
17
  relay?: string;
9
18
  localPubkey?: string;
10
19
  localPartyId?: string;
@@ -28,6 +37,11 @@ export interface OmniaHostConfig {
28
37
  deviceId?: string;
29
38
  /** "1" forces read-only mode even with keys present. */
30
39
  readOnly: boolean;
40
+ /**
41
+ * Deployment custody mode (RFC-058). Defaults to self-custody; hosted custody
42
+ * is explicit and policy-controlled. This does not change on-chain custody.
43
+ */
44
+ custodyMode?: CustodyMode;
31
45
  /** Identity file containing a delegated identity claim (operator root → service delegate). */
32
46
  identityFile?: string;
33
47
  /** EdgeServiceManifest serviceType (default "omnia-router"). */
@@ -42,6 +56,12 @@ export interface OmniaHostConfig {
42
56
  * Undefined disables token auth.
43
57
  */
44
58
  controlToken?: string;
59
+ /** RFC-062 local P2P hub (optional). One Hyperswarm shared by dapps. */
60
+ p2pHubPort?: number;
61
+ p2pHubHost?: string;
62
+ p2pHubToken?: string;
63
+ p2pHubOrigins?: string[];
64
+ p2pHubTopics?: string[];
45
65
  }
46
66
  /** Load the daemon configuration from the documented OMNIA_* environment. */
47
67
  export declare function loadConfigFromEnv(env?: NodeJS.ProcessEnv): OmniaHostConfig;
package/dist/config.js CHANGED
@@ -35,6 +35,27 @@ function parseOrigins(value) {
35
35
  .filter((part) => part.length > 0);
36
36
  return parts && parts.length > 0 ? parts : undefined;
37
37
  }
38
+ function parseOptionalPort(value, name) {
39
+ const trimmed = value?.trim();
40
+ if (!trimmed)
41
+ return undefined;
42
+ const port = Number(trimmed);
43
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
44
+ throw new Error(`${name} must be an integer between 1 and 65535; received ${value}`);
45
+ }
46
+ return port;
47
+ }
48
+ function parseList(value) {
49
+ const parts = value?.split(',').map((part) => part.trim()).filter(Boolean);
50
+ return parts && parts.length > 0 ? parts : undefined;
51
+ }
52
+ function parseCustodyMode(value) {
53
+ const mode = optional(value) ?? 'self';
54
+ if (mode !== 'self' && mode !== 'trust-minimised' && mode !== 'hosted') {
55
+ throw new Error(`OMNIA_CUSTODY must be self, trust-minimised or hosted; received ${value}`);
56
+ }
57
+ return mode;
58
+ }
38
59
  function parseSeed(value) {
39
60
  const seed = optional(value);
40
61
  if (!seed)
@@ -62,13 +83,18 @@ function loadConfigFromEnv(env = process.env) {
62
83
  if (seed && keyfile) {
63
84
  throw new Error('OMNIA_HOST_SEED and OMNIA_HOST_KEYFILE are mutually exclusive — set exactly one of them');
64
85
  }
86
+ const dbPath = env.OMNIA_HOST_DB?.trim() || node_path_1.default.join(dataRoot, 'omnia.sqlite');
65
87
  return {
66
88
  port: parsePort(env.OMNIA_HOST_PORT),
67
89
  host: env.OMNIA_HOST_BIND?.trim() || '127.0.0.1',
68
- dbPath: env.OMNIA_HOST_DB?.trim() || node_path_1.default.join(dataRoot, 'omnia.sqlite'),
90
+ dbPath,
69
91
  chainRpcUrl: env.OMNIA_CHAIN_RPC?.trim() || 'http://127.0.0.1:9005',
70
92
  chainRpcPassword: optional(env.OMNIA_CHAIN_RPC_PASSWORD),
71
93
  analyticsDbPath: optional(env.OMNIA_ANALYTICS_DB),
94
+ observeDbPath: optional(env.OMNIA_OBSERVE_DB) ?? node_path_1.default.join(node_path_1.default.dirname(dbPath), 'observe.sqlite'),
95
+ networkId: optional(env.OMNIA_NETWORK_ID),
96
+ sourceNodeId: optional(env.OMNIA_SOURCE_NODE_ID),
97
+ observeDisabled: env.OMNIA_OBSERVE?.trim() === '0' || env.OMNIA_OBSERVE?.trim().toLowerCase() === 'false',
72
98
  relay: optional(env.OMNIA_RELAY),
73
99
  localPubkey: optional(env.OMNIA_LOCAL_PUBKEY),
74
100
  localPartyId: optional(env.OMNIA_LOCAL_PARTY_ID),
@@ -82,9 +108,15 @@ function loadConfigFromEnv(env = process.env) {
82
108
  keyfilePassphrase: optional(env.OMNIA_HOST_KEYFILE_PASSPHRASE),
83
109
  deviceId: optional(env.OMNIA_HOST_DEVICE_ID),
84
110
  readOnly: env.OMNIA_HOST_READ_ONLY?.trim() === '1',
111
+ custodyMode: parseCustodyMode(env.OMNIA_CUSTODY),
85
112
  identityFile: optional(env.OMNIA_HOST_IDENTITY_FILE),
86
113
  serviceType: optional(env.OMNIA_HOST_SERVICE_TYPE) ?? 'omnia-router',
87
114
  allowedOrigins: parseOrigins(env.OMNIA_ALLOWED_ORIGINS),
115
+ p2pHubPort: parseOptionalPort(env.OMNIA_P2P_HUB_PORT, 'OMNIA_P2P_HUB_PORT'),
116
+ p2pHubHost: optional(env.OMNIA_P2P_HUB_HOST),
117
+ p2pHubToken: optional(env.OMNIA_P2P_HUB_TOKEN),
118
+ p2pHubOrigins: parseOrigins(env.OMNIA_P2P_HUB_ORIGINS),
119
+ p2pHubTopics: parseList(env.OMNIA_P2P_HUB_TOPICS),
88
120
  controlToken: optional(env.OMNIA_CONTROL_TOKEN),
89
121
  };
90
122
  }
package/dist/index.d.ts CHANGED
@@ -19,3 +19,6 @@ export type { HostApiContext } from './api/methods.js';
19
19
  export type { OperationStoreLike } from './api/methods.js';
20
20
  export { DisabledAnalyticsStore, DuckDbAnalyticsStore, initializeDuckDb } from './stores/analytics.js';
21
21
  export type { AnalyticsEvent, AnalyticsStore, DuckDbConnection } from './stores/analytics.js';
22
+ export { createLocalSignerPort, createPolicySignerPort } from './signer-port.js';
23
+ export type { SignerPolicy } from './signer-port.js';
24
+ export * from './observe/index.js';
package/dist/index.js CHANGED
@@ -1,6 +1,20 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.initializeDuckDb = exports.DuckDbAnalyticsStore = exports.DisabledAnalyticsStore = exports.createHostMethods = exports.GoRoutingProvider = exports.InProcessRoutingProvider = exports.createTotemNodeAdapter = exports.OperationStore = exports.SqliteChannelStore = exports.loadHostIdentity = exports.createHostManifest = exports.createHostIdentityAndManifest = exports.encryptSeedForKeyfile = exports.leaseStorageDir = exports.JsonFileStorageAdapter = exports.hasSigningMaterial = exports.createHostSigning = exports.createOmniaHost = exports.loadConfigFromEnv = void 0;
17
+ exports.createPolicySignerPort = exports.createLocalSignerPort = exports.initializeDuckDb = exports.DuckDbAnalyticsStore = exports.DisabledAnalyticsStore = exports.createHostMethods = exports.GoRoutingProvider = exports.InProcessRoutingProvider = exports.createTotemNodeAdapter = exports.OperationStore = exports.SqliteChannelStore = exports.loadHostIdentity = exports.createHostManifest = exports.createHostIdentityAndManifest = exports.encryptSeedForKeyfile = exports.leaseStorageDir = exports.JsonFileStorageAdapter = exports.hasSigningMaterial = exports.createHostSigning = exports.createOmniaHost = exports.loadConfigFromEnv = void 0;
4
18
  var config_js_1 = require("./config.js");
5
19
  Object.defineProperty(exports, "loadConfigFromEnv", { enumerable: true, get: function () { return config_js_1.loadConfigFromEnv; } });
6
20
  var lifecycle_js_1 = require("./lifecycle.js");
@@ -31,3 +45,8 @@ var analytics_js_1 = require("./stores/analytics.js");
31
45
  Object.defineProperty(exports, "DisabledAnalyticsStore", { enumerable: true, get: function () { return analytics_js_1.DisabledAnalyticsStore; } });
32
46
  Object.defineProperty(exports, "DuckDbAnalyticsStore", { enumerable: true, get: function () { return analytics_js_1.DuckDbAnalyticsStore; } });
33
47
  Object.defineProperty(exports, "initializeDuckDb", { enumerable: true, get: function () { return analytics_js_1.initializeDuckDb; } });
48
+ var signer_port_js_1 = require("./signer-port.js");
49
+ Object.defineProperty(exports, "createLocalSignerPort", { enumerable: true, get: function () { return signer_port_js_1.createLocalSignerPort; } });
50
+ Object.defineProperty(exports, "createPolicySignerPort", { enumerable: true, get: function () { return signer_port_js_1.createPolicySignerPort; } });
51
+ // Durable, replayable observation feed (RFC-0011 Amendment A).
52
+ __exportStar(require("./observe/index.js"), exports);
@@ -1,4 +1,7 @@
1
1
  import type { OmniaHostConfig } from './config.js';
2
+ import { ObservationBus } from './observe/bus.js';
3
+ import { SyncSqliteStore } from '@totemsdk/storage/sqlite-sync';
4
+ import type { ObservationStore } from './observe/types.js';
2
5
  import type { OmniaSwarm, ChannelSigner, WotsLeaseProviderLike, MinimalChainProvider } from '@totemsdk/omnia';
3
6
  import { createOmniaSwarm } from '@totemsdk/omnia';
4
7
  import { OperationStore } from './stores/operations.js';
@@ -7,6 +10,7 @@ import { type ControlServer } from './api/jsonrpc.js';
7
10
  import { type TotemNodeAdapter } from './chain/totem-node-adapter.js';
8
11
  import { type RoutingProvider } from './router/routing-provider.js';
9
12
  import type { JsonRpcHandler } from './api/jsonrpc.js';
13
+ import { type HubNetwork } from '@totemsdk/p2p-hub';
10
14
  import { type OperationStoreLike } from './api/methods.js';
11
15
  import type { ChannelFactory, WotsLeaseBundle } from '@totemsdk/omnia-factory';
12
16
  import type { SpliceAcceptance, SpliceLeaseProvider, SpliceProposal } from '@totemsdk/omnia-splice';
@@ -17,6 +21,8 @@ export interface OmniaHostDependencies {
17
21
  operationStore?: OperationStore | Pick<OperationStore, 'close'>;
18
22
  signer?: ChannelSigner;
19
23
  leaseProvider?: WotsLeaseProviderLike;
24
+ /** RFC-058 signer port; defaults to a local signer in CLI deployments. */
25
+ signerPort?: import('@totemsdk/omnia').SignerPort;
20
26
  chainProvider?: MinimalChainProvider;
21
27
  controlServer?: ControlServer;
22
28
  chainAdapter?: TotemNodeAdapter;
@@ -45,6 +51,16 @@ export interface OmniaHostDependencies {
45
51
  };
46
52
  /** Boot-time signed EdgeServiceManifest (getManifest). */
47
53
  manifest?: unknown;
54
+ /** Optional P2P hub network (tests/embedding). */
55
+ p2pHubNetwork?: HubNetwork;
56
+ /** Observation feed: inject a store/bus (tests/embedding) or disable it. */
57
+ observationStore?: ObservationStore;
58
+ observationBus?: ObservationBus;
59
+ /** Synchronous storage adapter for durable factories/splices/VTXOs/pools. */
60
+ hostStateStore?: SyncSqliteStore;
61
+ observe?: boolean;
62
+ networkId?: string;
63
+ sourceNodeId?: string;
48
64
  }
49
65
  export interface OmniaHost {
50
66
  readonly config: OmniaHostConfig;
@@ -53,6 +69,8 @@ export interface OmniaHost {
53
69
  isStarted(): boolean;
54
70
  readonly swarm?: OmniaSwarm;
55
71
  readonly channels?: Map<string, OmniaChannel>;
72
+ /** Loopback WebSocket endpoint for local dapps, when enabled (RFC-062). */
73
+ readonly p2pHubUrl?: string;
56
74
  }
57
75
  /**
58
76
  * Phase-1 lifecycle shell. Subsystems will be attached in later phases while
package/dist/lifecycle.js CHANGED
@@ -1,12 +1,23 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.createOmniaHost = createOmniaHost;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const bus_js_1 = require("./observe/bus.js");
9
+ const emitter_js_1 = require("./observe/emitter.js");
10
+ const store_js_1 = require("./observe/store.js");
11
+ const snapshot_js_1 = require("./observe/snapshot.js");
12
+ const sqlite_sync_1 = require("@totemsdk/storage/sqlite-sync");
13
+ const persistent_map_js_1 = require("./stores/persistent-map.js");
4
14
  const omnia_1 = require("@totemsdk/omnia");
5
15
  const sqlite_store_js_1 = require("./stores/sqlite-store.js");
6
16
  const operations_js_1 = require("./stores/operations.js");
7
17
  const jsonrpc_js_1 = require("./api/jsonrpc.js");
8
18
  const totem_node_adapter_js_1 = require("./chain/totem-node-adapter.js");
9
19
  const routing_provider_js_1 = require("./router/routing-provider.js");
20
+ const p2p_hub_1 = require("@totemsdk/p2p-hub");
10
21
  const methods_js_1 = require("./api/methods.js");
11
22
  const reply_cache_js_1 = require("./reply-cache.js");
12
23
  const omnia_factory_1 = require("@totemsdk/omnia-factory");
@@ -26,9 +37,17 @@ function createOmniaHost(config, dependencies = {}) {
26
37
  : undefined;
27
38
  const injectedOperationStore = dependencies.operationStore;
28
39
  let ownedOperationStore;
40
+ let observationStore = dependencies.observationStore;
41
+ let ownedObservationStore;
42
+ let hostStateStore = dependencies.hostStateStore;
43
+ let ownedHostStateStore;
44
+ const observationBus = dependencies.observationBus ?? new bus_js_1.ObservationBus();
45
+ let observation;
29
46
  let controlServer = dependencies.controlServer;
30
47
  let chainAdapter = dependencies.chainAdapter;
31
48
  let routingProvider = dependencies.routingProvider ?? new routing_provider_js_1.InProcessRoutingProvider();
49
+ let p2pHubServer;
50
+ let p2pHubUrl;
32
51
  return {
33
52
  config,
34
53
  get swarm() {
@@ -37,6 +56,9 @@ function createOmniaHost(config, dependencies = {}) {
37
56
  get channels() {
38
57
  return channelStore;
39
58
  },
59
+ get p2pHubUrl() {
60
+ return p2pHubUrl;
61
+ },
40
62
  async start() {
41
63
  if (started)
42
64
  return;
@@ -53,6 +75,35 @@ function createOmniaHost(config, dependencies = {}) {
53
75
  ownedOperationStore = new operations_js_1.OperationStore(config.dbPath);
54
76
  operationStore = ownedOperationStore;
55
77
  }
78
+ const observeEnabled = dependencies.observe === true || (dependencies.observe !== false && config.observeDisabled !== true && config.observeDbPath !== undefined);
79
+ if (observeEnabled && !observationStore) {
80
+ const observePath = config.observeDbPath ?? node_path_1.default.join(node_path_1.default.dirname(config.dbPath), 'observe.sqlite');
81
+ try {
82
+ ownedObservationStore = await (0, store_js_1.createSqliteObservationStore)(observePath);
83
+ observationStore = ownedObservationStore;
84
+ }
85
+ catch (err) {
86
+ console.error('[omnia-host] observation store disabled:', err instanceof Error ? err.message : String(err));
87
+ }
88
+ }
89
+ if (observationStore) {
90
+ observation = new emitter_js_1.ObservationEmitter({
91
+ store: observationStore,
92
+ bus: observationBus,
93
+ networkId: config.networkId ?? dependencies.networkId ?? 'omnia',
94
+ sourceNodeId: config.sourceNodeId ?? dependencies.sourceNodeId ?? config.localPartyId ?? 'omnia-host',
95
+ });
96
+ }
97
+ if (!hostStateStore) {
98
+ try {
99
+ ownedHostStateStore = new sqlite_sync_1.SyncSqliteStore(config.dbPath);
100
+ hostStateStore = ownedHostStateStore;
101
+ }
102
+ catch (err) {
103
+ console.error('[omnia-host] host-state store disabled:', err instanceof Error ? err.message : String(err));
104
+ }
105
+ }
106
+ const hostMap = (namespace, injected) => injected ?? (hostStateStore ? new persistent_map_js_1.PersistentMap(hostStateStore, namespace) : new Map());
56
107
  if (!swarm) {
57
108
  const relay = config.relay
58
109
  ? { mode: 'self-hosted', relayUrl: config.relay }
@@ -85,6 +136,21 @@ function createOmniaHost(config, dependencies = {}) {
85
136
  ssl: rpc.protocol === 'https:',
86
137
  });
87
138
  }
139
+ if (config.p2pHubPort !== undefined || dependencies.p2pHubNetwork) {
140
+ const native = swarm.nativeSwarm?.();
141
+ const network = dependencies.p2pHubNetwork ?? await (0, p2p_hub_1.createHyperswarmHubNetwork)(native ? { swarm: native } : {});
142
+ p2pHubServer = await (0, p2p_hub_1.createWebSocketHubServer)({
143
+ network,
144
+ host: config.p2pHubHost ?? '127.0.0.1',
145
+ port: config.p2pHubPort ?? 0,
146
+ ...(config.p2pHubToken ? { token: config.p2pHubToken } : {}),
147
+ ...(config.p2pHubOrigins ? { allowedOrigins: config.p2pHubOrigins } : {}),
148
+ ...(config.p2pHubTopics ? { allowedTopicPrefixes: config.p2pHubTopics } : {}),
149
+ });
150
+ const address = await p2pHubServer.listen();
151
+ const host = address.host.includes(':') ? `[${address.host}]` : address.host;
152
+ p2pHubUrl = `ws://${host}:${address.port}`;
153
+ }
88
154
  const localParticipant = dependencies.localParticipant ?? (config.localPartyId && config.localPubkey && config.localAddressIndex !== undefined
89
155
  ? {
90
156
  partyId: config.localPartyId,
@@ -104,7 +170,7 @@ function createOmniaHost(config, dependencies = {}) {
104
170
  },
105
171
  }
106
172
  : undefined);
107
- const factories = dependencies.factories ?? (factoryBundles ? new Map() : undefined);
173
+ const factories = dependencies.factories ?? (factoryBundles ? hostMap('omnia:factory:') : undefined);
108
174
  const localBundle = localParticipant
109
175
  ? factoryBundles?.[localParticipant.partyId]
110
176
  : undefined;
@@ -117,8 +183,8 @@ function createOmniaHost(config, dependencies = {}) {
117
183
  : {}),
118
184
  }
119
185
  : undefined);
120
- const spliceProposals = dependencies.spliceProposals ?? new Map();
121
- const spliceAcceptances = dependencies.spliceAcceptances ?? new Map();
186
+ const spliceProposals = hostMap('omnia:splice:proposal:', dependencies.spliceProposals);
187
+ const spliceAcceptances = hostMap('omnia:splice:acceptance:', dependencies.spliceAcceptances);
122
188
  const onSpliceMessage = async (peer, msg) => {
123
189
  if (!spliceLeaseProvider || !channelStore)
124
190
  return;
@@ -158,6 +224,12 @@ function createOmniaHost(config, dependencies = {}) {
158
224
  if (msg.type === 'SPLICE_FINALIZED' && payload.channel) {
159
225
  const channel = payload.channel;
160
226
  channelStore.set(channel.channelId, channel);
227
+ void observation?.emit({
228
+ domain: 'splice', kind: 'splice.finalized', phase: 'confirmed',
229
+ entity: { type: 'channel', id: channel.channelId },
230
+ amounts: { capacity: channel.totalValue?.toString(), balances: Object.fromEntries(Object.entries(channel.balances ?? {}).map(([k, v]) => [k, v.toString()])) },
231
+ data: { spliceId: payload.spliceId, txId: payload.txId },
232
+ });
161
233
  const acceptance = spliceAcceptances.get(payload.spliceId);
162
234
  if (acceptance && typeof payload.txId === 'string') {
163
235
  await spliceLeaseProvider.wotsLease
@@ -217,6 +289,11 @@ function createOmniaHost(config, dependencies = {}) {
217
289
  if (msg.type === 'FACTORY_PROPOSAL' && payload.factory) {
218
290
  const accepted = await (0, omnia_factory_1.acceptFactory)(payload.factory, localBundle);
219
291
  factories.set(accepted.factoryId, accepted);
292
+ void observation?.emit({
293
+ domain: 'factory', kind: 'factory.accepted', phase: 'accepted',
294
+ entity: { type: 'factory', id: accepted.factoryId },
295
+ data: { participants: accepted.participants },
296
+ });
220
297
  return {
221
298
  type: 'FACTORY_ACCEPT',
222
299
  channelId: accepted.factoryId,
@@ -261,6 +338,47 @@ function createOmniaHost(config, dependencies = {}) {
261
338
  if (reply)
262
339
  await peer.sendMessage(reply);
263
340
  };
341
+ const channelPeers = new Map();
342
+ const onPeerMessage = (peer, msg) => {
343
+ if (msg?.channelId)
344
+ channelPeers.set(msg.channelId, peer);
345
+ void observation?.emit({
346
+ domain: 'transport', kind: 'transport.message', phase: 'observed',
347
+ entity: { type: 'node', id: config.sourceNodeId ?? config.localPartyId ?? 'omnia-host' },
348
+ evidence: { peerPublicKeyDigest: peer?.publicKeyDigest },
349
+ data: { type: msg?.type, channelId: msg?.channelId, nonce: msg?.nonce },
350
+ });
351
+ };
352
+ const onChannelAccepted = (channel, peer) => {
353
+ void observation?.emit({
354
+ domain: 'channel', kind: 'channel.proposal.accepted', phase: 'accepted',
355
+ entity: { type: 'channel', id: channel.channelId },
356
+ parties: { local: localParticipant?.partyId, remote: channel.parties?.find((p) => p.partyId !== localParticipant?.partyId)?.partyId },
357
+ token: { tokenId: channel.tokenId, scale: channel.tokenScale ?? 0 },
358
+ amounts: { capacity: channel.totalValue?.toString(), balances: Object.fromEntries(Object.entries(channel.balances ?? {}).map(([k, v]) => [k, v.toString()])) },
359
+ transitionHash: (0, emitter_js_1.channelTransitionHash)(channel),
360
+ evidence: { peerPublicKeyDigest: peer?.publicKeyDigest },
361
+ });
362
+ };
363
+ const onStateUpdated = (channel, peer) => {
364
+ void observation?.emit({
365
+ domain: 'channel', kind: 'channel.updated', phase: 'committed',
366
+ entity: { type: 'channel', id: channel.channelId },
367
+ parties: { local: localParticipant?.partyId, remote: channel.parties?.find((p) => p.partyId !== localParticipant?.partyId)?.partyId },
368
+ token: { tokenId: channel.tokenId, scale: channel.tokenScale ?? 0 },
369
+ after: { sequence: channel.currentSequence, balances: Object.fromEntries(Object.entries(channel.balances ?? {}).map(([k, v]) => [k, v.toString()])) },
370
+ transitionHash: (0, emitter_js_1.channelTransitionHash)(channel),
371
+ evidence: { peerPublicKeyDigest: peer?.publicKeyDigest },
372
+ });
373
+ };
374
+ const onSettlementProposed = (payload, peer) => {
375
+ void observation?.emit({
376
+ domain: 'settlement', kind: 'settlement.proposed', phase: 'proposed',
377
+ entity: { type: 'channel', id: payload?.channelId ?? 'unknown' },
378
+ evidence: { peerPublicKeyDigest: peer?.publicKeyDigest },
379
+ data: { payload },
380
+ });
381
+ };
264
382
  const onVcMessage = async (peer, msg) => {
265
383
  if (!factories || !localBundle)
266
384
  return;
@@ -270,9 +388,19 @@ function createOmniaHost(config, dependencies = {}) {
270
388
  factories.set(payload.factory.factoryId, payload.factory);
271
389
  if (payload.kind === 'vc-open' && payload.channel) {
272
390
  channelStore?.set(payload.channel.channelId, payload.channel);
391
+ void observation?.emit({
392
+ domain: 'factory', kind: 'factory.vc_opened', phase: 'accepted',
393
+ entity: { type: 'channel', id: payload.channel.channelId },
394
+ data: { factoryId: payload.factory?.factoryId },
395
+ });
273
396
  }
274
397
  else if (payload.kind === 'vc-close' && payload.channelId) {
275
398
  channelStore?.delete(payload.channelId);
399
+ void observation?.emit({
400
+ domain: 'factory', kind: 'factory.vc_closed', phase: 'closed',
401
+ entity: { type: 'channel', id: payload.channelId },
402
+ data: { factoryId: payload.factory?.factoryId },
403
+ });
276
404
  }
277
405
  return;
278
406
  }
@@ -325,7 +453,22 @@ function createOmniaHost(config, dependencies = {}) {
325
453
  onFactoryMessage,
326
454
  onVcMessage,
327
455
  onSpliceMessage,
456
+ onPeerMessage,
457
+ onChannelAccepted,
458
+ onStateUpdated,
459
+ onSettlementProposed,
328
460
  });
461
+ const preparedPay = new Map();
462
+ const preparedSettle = new Map();
463
+ const preparedOpen = new Map();
464
+ const preparedSplice = new Map();
465
+ const preparedFactory = new Map();
466
+ const vtxoPools = hostMap('omnia:vtxo:pool:');
467
+ const vtxos = hostMap('omnia:vtxo:');
468
+ const vtxoReceipts = hostMap('omnia:vtxo:receipt:');
469
+ const preparedVtxoReceipts = new Map();
470
+ const pools = hostMap('omnia:pool:');
471
+ const poolPositions = hostMap('omnia:pool:position:');
329
472
  if (!controlServer) {
330
473
  const readOnly = !dependencies.signer || !dependencies.leaseProvider;
331
474
  if (readOnly) {
@@ -338,14 +481,32 @@ function createOmniaHost(config, dependencies = {}) {
338
481
  isReady: () => started,
339
482
  allowedOrigins: config.allowedOrigins,
340
483
  authToken: config.controlToken,
484
+ observe: observationStore ? {
485
+ store: observationStore,
486
+ bus: observationBus,
487
+ snapshot: async () => (0, snapshot_js_1.createObservationSnapshot)({
488
+ networkId: config.networkId ?? dependencies.networkId ?? 'omnia',
489
+ sourceNodeId: config.sourceNodeId ?? dependencies.sourceNodeId ?? config.localPartyId ?? 'omnia-host',
490
+ streamId: `${config.sourceNodeId ?? dependencies.sourceNodeId ?? config.localPartyId ?? 'omnia-host'}:${config.networkId ?? dependencies.networkId ?? 'omnia'}`,
491
+ cursor: await observationStore.head(),
492
+ channels: channelStore?.values(),
493
+ factories: factories?.values(),
494
+ vtxoPools: vtxoPools.values(),
495
+ vtxos: vtxos.values(),
496
+ pools: pools.values(),
497
+ operations: (dependencies.operationStoreForApi ?? operationStore),
498
+ }),
499
+ } : undefined,
341
500
  methods: dependencies.methods ?? (0, methods_js_1.createHostMethods)({
342
501
  channels: channelStore,
343
502
  routing: routingProvider,
344
503
  operations: dependencies.operationStoreForApi ?? operationStore,
504
+ observe: observation,
345
505
  signer: dependencies.signer,
346
506
  leaseProvider: dependencies.leaseProvider,
347
507
  chainProvider: dependencies.chainProvider ?? chainAdapter,
348
508
  swarm,
509
+ channelPeers,
349
510
  localParticipant,
350
511
  transportPubkey: dependencies.transportPubkey,
351
512
  factories,
@@ -353,6 +514,20 @@ function createOmniaHost(config, dependencies = {}) {
353
514
  spliceProposals,
354
515
  spliceAcceptances,
355
516
  spliceLeaseProvider,
517
+ custodyMode: config.custodyMode ?? 'self',
518
+ signerPort: dependencies.signerPort,
519
+ preparedPay,
520
+ preparedSettle,
521
+ preparedOpen,
522
+ preparedSplice,
523
+ preparedFactory,
524
+ vtxoPools,
525
+ vtxos,
526
+ vtxoReceipts,
527
+ preparedVtxoReceipts,
528
+ pools,
529
+ poolPositions,
530
+ coordinator: !dependencies.signer && !config.readOnly,
356
531
  fundingSigner: config.fundingPubkey
357
532
  ? async (digest) => {
358
533
  const sig = await chainAdapter.signData(config.fundingPubkey, '0x' + Buffer.from(digest).toString('hex'));
@@ -362,6 +537,7 @@ function createOmniaHost(config, dependencies = {}) {
362
537
  readOnly,
363
538
  identity: dependencies.identity,
364
539
  manifest: dependencies.manifest,
540
+ p2pHubUrl,
365
541
  refreshRouting: () => routingProvider.rebuild((0, routing_provider_js_1.channelGraphEdges)(channelStore.values())),
366
542
  }),
367
543
  });
@@ -373,6 +549,9 @@ function createOmniaHost(config, dependencies = {}) {
373
549
  catch (error) {
374
550
  unsubscribe?.();
375
551
  unsubscribe = undefined;
552
+ await p2pHubServer?.close();
553
+ p2pHubServer = undefined;
554
+ p2pHubUrl = undefined;
376
555
  chainAdapter?.close();
377
556
  await swarm?.close();
378
557
  if (ownedOperationStore)
@@ -388,6 +567,9 @@ function createOmniaHost(config, dependencies = {}) {
388
567
  return;
389
568
  unsubscribe?.();
390
569
  unsubscribe = undefined;
570
+ await p2pHubServer?.close();
571
+ p2pHubServer = undefined;
572
+ p2pHubUrl = undefined;
391
573
  await controlServer?.close();
392
574
  chainAdapter?.close();
393
575
  await swarm?.close();
@@ -396,6 +578,11 @@ function createOmniaHost(config, dependencies = {}) {
396
578
  else if (injectedOperationStore)
397
579
  await injectedOperationStore.close();
398
580
  ownedOperationStore = undefined;
581
+ if (ownedObservationStore)
582
+ await ownedObservationStore.close();
583
+ ownedObservationStore = undefined;
584
+ ownedHostStateStore?.close();
585
+ ownedHostStateStore = undefined;
399
586
  ownedStore?.close();
400
587
  ownedStore = undefined;
401
588
  started = false;
@@ -0,0 +1,13 @@
1
+ import type { OmniaObservation } from "./types.js";
2
+ export type ObservationListener = (observation: OmniaObservation) => void;
3
+ /**
4
+ * In-process live fan-out for SSE consumers. The durable journal remains the
5
+ * source of truth: a subscriber that misses a live event still replays it from
6
+ * `ObservationStore.readSince(cursor)`.
7
+ */
8
+ export declare class ObservationBus {
9
+ private readonly listeners;
10
+ subscribe(listener: ObservationListener): () => void;
11
+ publish(observation: OmniaObservation): void;
12
+ get size(): number;
13
+ }