@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.
- 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 +18 -0
- package/dist/lifecycle.js +190 -3
- 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/persistent-map.d.ts +31 -0
- package/dist/stores/persistent-map.js +172 -0
- 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,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 ?
|
|
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
|
|
121
|
-
const spliceAcceptances = dependencies.spliceAcceptances
|
|
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
|
+
}
|