@totemsdk/omnia-host 0.10.0 → 0.11.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.
- package/LICENSE +202 -21
- package/NOTICE +60 -0
- package/README.md +44 -0
- package/dist/api/jsonrpc.d.ts +12 -0
- package/dist/api/jsonrpc.js +100 -1
- package/dist/api/methods.d.ts +101 -3
- package/dist/api/methods.js +1664 -23
- package/dist/chain/totem-node-adapter.js +1 -1
- package/dist/cli.js +65 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +33 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +20 -1
- package/dist/lifecycle.d.ts +15 -0
- package/dist/lifecycle.js +171 -0
- package/dist/observe/bus.d.ts +13 -0
- package/dist/observe/bus.js +29 -0
- package/dist/observe/emitter.d.ts +61 -0
- package/dist/observe/emitter.js +133 -0
- package/dist/observe/index.d.ts +5 -0
- package/dist/observe/index.js +21 -0
- package/dist/observe/snapshot.d.ts +38 -0
- package/dist/observe/snapshot.js +101 -0
- package/dist/observe/store.d.ts +31 -0
- package/dist/observe/store.js +82 -0
- package/dist/observe/types.d.ts +106 -0
- package/dist/observe/types.js +14 -0
- package/dist/pairing.d.ts +109 -0
- package/dist/pairing.js +231 -0
- package/dist/signer-port.d.ts +27 -0
- package/dist/signer-port.js +79 -0
- package/dist/stores/operations.d.ts +3 -2
- package/dist/stores/operations.js +96 -83
- package/dist/stores/sqlite-store.d.ts +8 -5
- package/dist/stores/sqlite-store.js +45 -42
- package/package.json +11 -9
|
@@ -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
|
|
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);
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { OmniaHostConfig } from './config.js';
|
|
2
|
+
import { ObservationBus } from './observe/bus.js';
|
|
3
|
+
import type { ObservationStore } from './observe/types.js';
|
|
2
4
|
import type { OmniaSwarm, ChannelSigner, WotsLeaseProviderLike, MinimalChainProvider } from '@totemsdk/omnia';
|
|
3
5
|
import { createOmniaSwarm } from '@totemsdk/omnia';
|
|
4
6
|
import { OperationStore } from './stores/operations.js';
|
|
@@ -7,6 +9,7 @@ import { type ControlServer } from './api/jsonrpc.js';
|
|
|
7
9
|
import { type TotemNodeAdapter } from './chain/totem-node-adapter.js';
|
|
8
10
|
import { type RoutingProvider } from './router/routing-provider.js';
|
|
9
11
|
import type { JsonRpcHandler } from './api/jsonrpc.js';
|
|
12
|
+
import { type HubNetwork } from '@totemsdk/p2p-hub';
|
|
10
13
|
import { type OperationStoreLike } from './api/methods.js';
|
|
11
14
|
import type { ChannelFactory, WotsLeaseBundle } from '@totemsdk/omnia-factory';
|
|
12
15
|
import type { SpliceAcceptance, SpliceLeaseProvider, SpliceProposal } from '@totemsdk/omnia-splice';
|
|
@@ -17,6 +20,8 @@ export interface OmniaHostDependencies {
|
|
|
17
20
|
operationStore?: OperationStore | Pick<OperationStore, 'close'>;
|
|
18
21
|
signer?: ChannelSigner;
|
|
19
22
|
leaseProvider?: WotsLeaseProviderLike;
|
|
23
|
+
/** RFC-058 signer port; defaults to a local signer in CLI deployments. */
|
|
24
|
+
signerPort?: import('@totemsdk/omnia').SignerPort;
|
|
20
25
|
chainProvider?: MinimalChainProvider;
|
|
21
26
|
controlServer?: ControlServer;
|
|
22
27
|
chainAdapter?: TotemNodeAdapter;
|
|
@@ -45,6 +50,14 @@ export interface OmniaHostDependencies {
|
|
|
45
50
|
};
|
|
46
51
|
/** Boot-time signed EdgeServiceManifest (getManifest). */
|
|
47
52
|
manifest?: unknown;
|
|
53
|
+
/** Optional P2P hub network (tests/embedding). */
|
|
54
|
+
p2pHubNetwork?: HubNetwork;
|
|
55
|
+
/** Observation feed: inject a store/bus (tests/embedding) or disable it. */
|
|
56
|
+
observationStore?: ObservationStore;
|
|
57
|
+
observationBus?: ObservationBus;
|
|
58
|
+
observe?: boolean;
|
|
59
|
+
networkId?: string;
|
|
60
|
+
sourceNodeId?: string;
|
|
48
61
|
}
|
|
49
62
|
export interface OmniaHost {
|
|
50
63
|
readonly config: OmniaHostConfig;
|
|
@@ -53,6 +66,8 @@ export interface OmniaHost {
|
|
|
53
66
|
isStarted(): boolean;
|
|
54
67
|
readonly swarm?: OmniaSwarm;
|
|
55
68
|
readonly channels?: Map<string, OmniaChannel>;
|
|
69
|
+
/** Loopback WebSocket endpoint for local dapps, when enabled (RFC-062). */
|
|
70
|
+
readonly p2pHubUrl?: string;
|
|
56
71
|
}
|
|
57
72
|
/**
|
|
58
73
|
* Phase-1 lifecycle shell. Subsystems will be attached in later phases while
|
package/dist/lifecycle.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
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");
|
|
4
12
|
const omnia_1 = require("@totemsdk/omnia");
|
|
5
13
|
const sqlite_store_js_1 = require("./stores/sqlite-store.js");
|
|
6
14
|
const operations_js_1 = require("./stores/operations.js");
|
|
7
15
|
const jsonrpc_js_1 = require("./api/jsonrpc.js");
|
|
8
16
|
const totem_node_adapter_js_1 = require("./chain/totem-node-adapter.js");
|
|
9
17
|
const routing_provider_js_1 = require("./router/routing-provider.js");
|
|
18
|
+
const p2p_hub_1 = require("@totemsdk/p2p-hub");
|
|
10
19
|
const methods_js_1 = require("./api/methods.js");
|
|
11
20
|
const reply_cache_js_1 = require("./reply-cache.js");
|
|
12
21
|
const omnia_factory_1 = require("@totemsdk/omnia-factory");
|
|
@@ -26,9 +35,15 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
26
35
|
: undefined;
|
|
27
36
|
const injectedOperationStore = dependencies.operationStore;
|
|
28
37
|
let ownedOperationStore;
|
|
38
|
+
let observationStore = dependencies.observationStore;
|
|
39
|
+
let ownedObservationStore;
|
|
40
|
+
const observationBus = dependencies.observationBus ?? new bus_js_1.ObservationBus();
|
|
41
|
+
let observation;
|
|
29
42
|
let controlServer = dependencies.controlServer;
|
|
30
43
|
let chainAdapter = dependencies.chainAdapter;
|
|
31
44
|
let routingProvider = dependencies.routingProvider ?? new routing_provider_js_1.InProcessRoutingProvider();
|
|
45
|
+
let p2pHubServer;
|
|
46
|
+
let p2pHubUrl;
|
|
32
47
|
return {
|
|
33
48
|
config,
|
|
34
49
|
get swarm() {
|
|
@@ -37,6 +52,9 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
37
52
|
get channels() {
|
|
38
53
|
return channelStore;
|
|
39
54
|
},
|
|
55
|
+
get p2pHubUrl() {
|
|
56
|
+
return p2pHubUrl;
|
|
57
|
+
},
|
|
40
58
|
async start() {
|
|
41
59
|
if (started)
|
|
42
60
|
return;
|
|
@@ -53,6 +71,25 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
53
71
|
ownedOperationStore = new operations_js_1.OperationStore(config.dbPath);
|
|
54
72
|
operationStore = ownedOperationStore;
|
|
55
73
|
}
|
|
74
|
+
const observeEnabled = dependencies.observe === true || (dependencies.observe !== false && config.observeDisabled !== true && config.observeDbPath !== undefined);
|
|
75
|
+
if (observeEnabled && !observationStore) {
|
|
76
|
+
const observePath = config.observeDbPath ?? node_path_1.default.join(node_path_1.default.dirname(config.dbPath), 'observe.sqlite');
|
|
77
|
+
try {
|
|
78
|
+
ownedObservationStore = await (0, store_js_1.createSqliteObservationStore)(observePath);
|
|
79
|
+
observationStore = ownedObservationStore;
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error('[omnia-host] observation store disabled:', err instanceof Error ? err.message : String(err));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (observationStore) {
|
|
86
|
+
observation = new emitter_js_1.ObservationEmitter({
|
|
87
|
+
store: observationStore,
|
|
88
|
+
bus: observationBus,
|
|
89
|
+
networkId: config.networkId ?? dependencies.networkId ?? 'omnia',
|
|
90
|
+
sourceNodeId: config.sourceNodeId ?? dependencies.sourceNodeId ?? config.localPartyId ?? 'omnia-host',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
56
93
|
if (!swarm) {
|
|
57
94
|
const relay = config.relay
|
|
58
95
|
? { mode: 'self-hosted', relayUrl: config.relay }
|
|
@@ -85,6 +122,21 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
85
122
|
ssl: rpc.protocol === 'https:',
|
|
86
123
|
});
|
|
87
124
|
}
|
|
125
|
+
if (config.p2pHubPort !== undefined || dependencies.p2pHubNetwork) {
|
|
126
|
+
const native = swarm.nativeSwarm?.();
|
|
127
|
+
const network = dependencies.p2pHubNetwork ?? await (0, p2p_hub_1.createHyperswarmHubNetwork)(native ? { swarm: native } : {});
|
|
128
|
+
p2pHubServer = await (0, p2p_hub_1.createWebSocketHubServer)({
|
|
129
|
+
network,
|
|
130
|
+
host: config.p2pHubHost ?? '127.0.0.1',
|
|
131
|
+
port: config.p2pHubPort ?? 0,
|
|
132
|
+
...(config.p2pHubToken ? { token: config.p2pHubToken } : {}),
|
|
133
|
+
...(config.p2pHubOrigins ? { allowedOrigins: config.p2pHubOrigins } : {}),
|
|
134
|
+
...(config.p2pHubTopics ? { allowedTopicPrefixes: config.p2pHubTopics } : {}),
|
|
135
|
+
});
|
|
136
|
+
const address = await p2pHubServer.listen();
|
|
137
|
+
const host = address.host.includes(':') ? `[${address.host}]` : address.host;
|
|
138
|
+
p2pHubUrl = `ws://${host}:${address.port}`;
|
|
139
|
+
}
|
|
88
140
|
const localParticipant = dependencies.localParticipant ?? (config.localPartyId && config.localPubkey && config.localAddressIndex !== undefined
|
|
89
141
|
? {
|
|
90
142
|
partyId: config.localPartyId,
|
|
@@ -158,6 +210,12 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
158
210
|
if (msg.type === 'SPLICE_FINALIZED' && payload.channel) {
|
|
159
211
|
const channel = payload.channel;
|
|
160
212
|
channelStore.set(channel.channelId, channel);
|
|
213
|
+
void observation?.emit({
|
|
214
|
+
domain: 'splice', kind: 'splice.finalized', phase: 'confirmed',
|
|
215
|
+
entity: { type: 'channel', id: channel.channelId },
|
|
216
|
+
amounts: { capacity: channel.totalValue?.toString(), balances: Object.fromEntries(Object.entries(channel.balances ?? {}).map(([k, v]) => [k, v.toString()])) },
|
|
217
|
+
data: { spliceId: payload.spliceId, txId: payload.txId },
|
|
218
|
+
});
|
|
161
219
|
const acceptance = spliceAcceptances.get(payload.spliceId);
|
|
162
220
|
if (acceptance && typeof payload.txId === 'string') {
|
|
163
221
|
await spliceLeaseProvider.wotsLease
|
|
@@ -217,6 +275,11 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
217
275
|
if (msg.type === 'FACTORY_PROPOSAL' && payload.factory) {
|
|
218
276
|
const accepted = await (0, omnia_factory_1.acceptFactory)(payload.factory, localBundle);
|
|
219
277
|
factories.set(accepted.factoryId, accepted);
|
|
278
|
+
void observation?.emit({
|
|
279
|
+
domain: 'factory', kind: 'factory.accepted', phase: 'accepted',
|
|
280
|
+
entity: { type: 'factory', id: accepted.factoryId },
|
|
281
|
+
data: { participants: accepted.participants },
|
|
282
|
+
});
|
|
220
283
|
return {
|
|
221
284
|
type: 'FACTORY_ACCEPT',
|
|
222
285
|
channelId: accepted.factoryId,
|
|
@@ -261,6 +324,47 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
261
324
|
if (reply)
|
|
262
325
|
await peer.sendMessage(reply);
|
|
263
326
|
};
|
|
327
|
+
const channelPeers = new Map();
|
|
328
|
+
const onPeerMessage = (peer, msg) => {
|
|
329
|
+
if (msg?.channelId)
|
|
330
|
+
channelPeers.set(msg.channelId, peer);
|
|
331
|
+
void observation?.emit({
|
|
332
|
+
domain: 'transport', kind: 'transport.message', phase: 'observed',
|
|
333
|
+
entity: { type: 'node', id: config.sourceNodeId ?? config.localPartyId ?? 'omnia-host' },
|
|
334
|
+
evidence: { peerPublicKeyDigest: peer?.publicKeyDigest },
|
|
335
|
+
data: { type: msg?.type, channelId: msg?.channelId, nonce: msg?.nonce },
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
const onChannelAccepted = (channel, peer) => {
|
|
339
|
+
void observation?.emit({
|
|
340
|
+
domain: 'channel', kind: 'channel.proposal.accepted', phase: 'accepted',
|
|
341
|
+
entity: { type: 'channel', id: channel.channelId },
|
|
342
|
+
parties: { local: localParticipant?.partyId, remote: channel.parties?.find((p) => p.partyId !== localParticipant?.partyId)?.partyId },
|
|
343
|
+
token: { tokenId: channel.tokenId, scale: channel.tokenScale ?? 0 },
|
|
344
|
+
amounts: { capacity: channel.totalValue?.toString(), balances: Object.fromEntries(Object.entries(channel.balances ?? {}).map(([k, v]) => [k, v.toString()])) },
|
|
345
|
+
transitionHash: (0, emitter_js_1.channelTransitionHash)(channel),
|
|
346
|
+
evidence: { peerPublicKeyDigest: peer?.publicKeyDigest },
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
const onStateUpdated = (channel, peer) => {
|
|
350
|
+
void observation?.emit({
|
|
351
|
+
domain: 'channel', kind: 'channel.updated', phase: 'committed',
|
|
352
|
+
entity: { type: 'channel', id: channel.channelId },
|
|
353
|
+
parties: { local: localParticipant?.partyId, remote: channel.parties?.find((p) => p.partyId !== localParticipant?.partyId)?.partyId },
|
|
354
|
+
token: { tokenId: channel.tokenId, scale: channel.tokenScale ?? 0 },
|
|
355
|
+
after: { sequence: channel.currentSequence, balances: Object.fromEntries(Object.entries(channel.balances ?? {}).map(([k, v]) => [k, v.toString()])) },
|
|
356
|
+
transitionHash: (0, emitter_js_1.channelTransitionHash)(channel),
|
|
357
|
+
evidence: { peerPublicKeyDigest: peer?.publicKeyDigest },
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
const onSettlementProposed = (payload, peer) => {
|
|
361
|
+
void observation?.emit({
|
|
362
|
+
domain: 'settlement', kind: 'settlement.proposed', phase: 'proposed',
|
|
363
|
+
entity: { type: 'channel', id: payload?.channelId ?? 'unknown' },
|
|
364
|
+
evidence: { peerPublicKeyDigest: peer?.publicKeyDigest },
|
|
365
|
+
data: { payload },
|
|
366
|
+
});
|
|
367
|
+
};
|
|
264
368
|
const onVcMessage = async (peer, msg) => {
|
|
265
369
|
if (!factories || !localBundle)
|
|
266
370
|
return;
|
|
@@ -270,9 +374,19 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
270
374
|
factories.set(payload.factory.factoryId, payload.factory);
|
|
271
375
|
if (payload.kind === 'vc-open' && payload.channel) {
|
|
272
376
|
channelStore?.set(payload.channel.channelId, payload.channel);
|
|
377
|
+
void observation?.emit({
|
|
378
|
+
domain: 'factory', kind: 'factory.vc_opened', phase: 'accepted',
|
|
379
|
+
entity: { type: 'channel', id: payload.channel.channelId },
|
|
380
|
+
data: { factoryId: payload.factory?.factoryId },
|
|
381
|
+
});
|
|
273
382
|
}
|
|
274
383
|
else if (payload.kind === 'vc-close' && payload.channelId) {
|
|
275
384
|
channelStore?.delete(payload.channelId);
|
|
385
|
+
void observation?.emit({
|
|
386
|
+
domain: 'factory', kind: 'factory.vc_closed', phase: 'closed',
|
|
387
|
+
entity: { type: 'channel', id: payload.channelId },
|
|
388
|
+
data: { factoryId: payload.factory?.factoryId },
|
|
389
|
+
});
|
|
276
390
|
}
|
|
277
391
|
return;
|
|
278
392
|
}
|
|
@@ -325,7 +439,22 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
325
439
|
onFactoryMessage,
|
|
326
440
|
onVcMessage,
|
|
327
441
|
onSpliceMessage,
|
|
442
|
+
onPeerMessage,
|
|
443
|
+
onChannelAccepted,
|
|
444
|
+
onStateUpdated,
|
|
445
|
+
onSettlementProposed,
|
|
328
446
|
});
|
|
447
|
+
const preparedPay = new Map();
|
|
448
|
+
const preparedSettle = new Map();
|
|
449
|
+
const preparedOpen = new Map();
|
|
450
|
+
const preparedSplice = new Map();
|
|
451
|
+
const preparedFactory = new Map();
|
|
452
|
+
const vtxoPools = new Map();
|
|
453
|
+
const vtxos = new Map();
|
|
454
|
+
const vtxoReceipts = new Map();
|
|
455
|
+
const preparedVtxoReceipts = new Map();
|
|
456
|
+
const pools = new Map();
|
|
457
|
+
const poolPositions = new Map();
|
|
329
458
|
if (!controlServer) {
|
|
330
459
|
const readOnly = !dependencies.signer || !dependencies.leaseProvider;
|
|
331
460
|
if (readOnly) {
|
|
@@ -338,14 +467,32 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
338
467
|
isReady: () => started,
|
|
339
468
|
allowedOrigins: config.allowedOrigins,
|
|
340
469
|
authToken: config.controlToken,
|
|
470
|
+
observe: observationStore ? {
|
|
471
|
+
store: observationStore,
|
|
472
|
+
bus: observationBus,
|
|
473
|
+
snapshot: async () => (0, snapshot_js_1.createObservationSnapshot)({
|
|
474
|
+
networkId: config.networkId ?? dependencies.networkId ?? 'omnia',
|
|
475
|
+
sourceNodeId: config.sourceNodeId ?? dependencies.sourceNodeId ?? config.localPartyId ?? 'omnia-host',
|
|
476
|
+
streamId: `${config.sourceNodeId ?? dependencies.sourceNodeId ?? config.localPartyId ?? 'omnia-host'}:${config.networkId ?? dependencies.networkId ?? 'omnia'}`,
|
|
477
|
+
cursor: await observationStore.head(),
|
|
478
|
+
channels: channelStore?.values(),
|
|
479
|
+
factories: factories?.values(),
|
|
480
|
+
vtxoPools: vtxoPools.values(),
|
|
481
|
+
vtxos: vtxos.values(),
|
|
482
|
+
pools: pools.values(),
|
|
483
|
+
operations: (dependencies.operationStoreForApi ?? operationStore),
|
|
484
|
+
}),
|
|
485
|
+
} : undefined,
|
|
341
486
|
methods: dependencies.methods ?? (0, methods_js_1.createHostMethods)({
|
|
342
487
|
channels: channelStore,
|
|
343
488
|
routing: routingProvider,
|
|
344
489
|
operations: dependencies.operationStoreForApi ?? operationStore,
|
|
490
|
+
observe: observation,
|
|
345
491
|
signer: dependencies.signer,
|
|
346
492
|
leaseProvider: dependencies.leaseProvider,
|
|
347
493
|
chainProvider: dependencies.chainProvider ?? chainAdapter,
|
|
348
494
|
swarm,
|
|
495
|
+
channelPeers,
|
|
349
496
|
localParticipant,
|
|
350
497
|
transportPubkey: dependencies.transportPubkey,
|
|
351
498
|
factories,
|
|
@@ -353,6 +500,20 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
353
500
|
spliceProposals,
|
|
354
501
|
spliceAcceptances,
|
|
355
502
|
spliceLeaseProvider,
|
|
503
|
+
custodyMode: config.custodyMode ?? 'self',
|
|
504
|
+
signerPort: dependencies.signerPort,
|
|
505
|
+
preparedPay,
|
|
506
|
+
preparedSettle,
|
|
507
|
+
preparedOpen,
|
|
508
|
+
preparedSplice,
|
|
509
|
+
preparedFactory,
|
|
510
|
+
vtxoPools,
|
|
511
|
+
vtxos,
|
|
512
|
+
vtxoReceipts,
|
|
513
|
+
preparedVtxoReceipts,
|
|
514
|
+
pools,
|
|
515
|
+
poolPositions,
|
|
516
|
+
coordinator: !dependencies.signer && !config.readOnly,
|
|
356
517
|
fundingSigner: config.fundingPubkey
|
|
357
518
|
? async (digest) => {
|
|
358
519
|
const sig = await chainAdapter.signData(config.fundingPubkey, '0x' + Buffer.from(digest).toString('hex'));
|
|
@@ -362,6 +523,7 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
362
523
|
readOnly,
|
|
363
524
|
identity: dependencies.identity,
|
|
364
525
|
manifest: dependencies.manifest,
|
|
526
|
+
p2pHubUrl,
|
|
365
527
|
refreshRouting: () => routingProvider.rebuild((0, routing_provider_js_1.channelGraphEdges)(channelStore.values())),
|
|
366
528
|
}),
|
|
367
529
|
});
|
|
@@ -373,6 +535,9 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
373
535
|
catch (error) {
|
|
374
536
|
unsubscribe?.();
|
|
375
537
|
unsubscribe = undefined;
|
|
538
|
+
await p2pHubServer?.close();
|
|
539
|
+
p2pHubServer = undefined;
|
|
540
|
+
p2pHubUrl = undefined;
|
|
376
541
|
chainAdapter?.close();
|
|
377
542
|
await swarm?.close();
|
|
378
543
|
if (ownedOperationStore)
|
|
@@ -388,6 +553,9 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
388
553
|
return;
|
|
389
554
|
unsubscribe?.();
|
|
390
555
|
unsubscribe = undefined;
|
|
556
|
+
await p2pHubServer?.close();
|
|
557
|
+
p2pHubServer = undefined;
|
|
558
|
+
p2pHubUrl = undefined;
|
|
391
559
|
await controlServer?.close();
|
|
392
560
|
chainAdapter?.close();
|
|
393
561
|
await swarm?.close();
|
|
@@ -396,6 +564,9 @@ function createOmniaHost(config, dependencies = {}) {
|
|
|
396
564
|
else if (injectedOperationStore)
|
|
397
565
|
await injectedOperationStore.close();
|
|
398
566
|
ownedOperationStore = undefined;
|
|
567
|
+
if (ownedObservationStore)
|
|
568
|
+
await ownedObservationStore.close();
|
|
569
|
+
ownedObservationStore = undefined;
|
|
399
570
|
ownedStore?.close();
|
|
400
571
|
ownedStore = undefined;
|
|
401
572
|
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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ObservationBus = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* In-process live fan-out for SSE consumers. The durable journal remains the
|
|
6
|
+
* source of truth: a subscriber that misses a live event still replays it from
|
|
7
|
+
* `ObservationStore.readSince(cursor)`.
|
|
8
|
+
*/
|
|
9
|
+
class ObservationBus {
|
|
10
|
+
listeners = new Set();
|
|
11
|
+
subscribe(listener) {
|
|
12
|
+
this.listeners.add(listener);
|
|
13
|
+
return () => this.listeners.delete(listener);
|
|
14
|
+
}
|
|
15
|
+
publish(observation) {
|
|
16
|
+
for (const listener of this.listeners) {
|
|
17
|
+
try {
|
|
18
|
+
listener(observation);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// A broken consumer must never break the mutation path.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
get size() {
|
|
26
|
+
return this.listeners.size;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.ObservationBus = ObservationBus;
|