@totemsdk/omnia-host 0.1.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 +21 -0
- package/README.md +38 -0
- package/dist/api/jsonrpc.d.ts +33 -0
- package/dist/api/jsonrpc.js +114 -0
- package/dist/api/methods.d.ts +26 -0
- package/dist/api/methods.js +412 -0
- package/dist/chain/totem-node-adapter.d.ts +23 -0
- package/dist/chain/totem-node-adapter.js +78 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +32 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +49 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +23 -0
- package/dist/lifecycle.d.ts +45 -0
- package/dist/lifecycle.js +141 -0
- package/dist/router/go-routing-provider.d.ts +13 -0
- package/dist/router/go-routing-provider.js +66 -0
- package/dist/router/routing-provider.d.ts +21 -0
- package/dist/router/routing-provider.js +67 -0
- package/dist/stores/analytics.d.ts +36 -0
- package/dist/stores/analytics.js +53 -0
- package/dist/stores/operations.d.ts +25 -0
- package/dist/stores/operations.js +101 -0
- package/dist/stores/sqlite-store.d.ts +21 -0
- package/dist/stores/sqlite-store.js +97 -0
- package/package.json +74 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTotemNodeAdapter = createTotemNodeAdapter;
|
|
4
|
+
const chain_provider_1 = require("@totemsdk/chain-provider");
|
|
5
|
+
const minima_rpc_1 = require("@totemsdk/minima-rpc");
|
|
6
|
+
function createTotemNodeAdapter(options) {
|
|
7
|
+
const client = options.client ?? (0, minima_rpc_1.createMinimaRpcClient)({
|
|
8
|
+
host: options.host,
|
|
9
|
+
port: options.port,
|
|
10
|
+
password: options.password,
|
|
11
|
+
ssl: options.ssl ?? false,
|
|
12
|
+
});
|
|
13
|
+
const provider = options.provider ?? new chain_provider_1.MinimaRpcProvider(client);
|
|
14
|
+
const listeners = new Set();
|
|
15
|
+
const pollIntervalMs = options.pollIntervalMs ?? 5_000;
|
|
16
|
+
let timer;
|
|
17
|
+
let stopped = false;
|
|
18
|
+
async function poll() {
|
|
19
|
+
if (stopped || listeners.size === 0)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
const tip = await provider.getTip();
|
|
23
|
+
for (const listener of listeners)
|
|
24
|
+
listener(tip);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
if (!stopped && listeners.size > 0)
|
|
28
|
+
timer = setTimeout(() => void poll(), pollIntervalMs);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
getCoins: (query) => provider.getCoins(query),
|
|
33
|
+
getCoin: (coinId) => provider.getCoin(coinId),
|
|
34
|
+
getProof: (coinId) => provider.getProof(coinId),
|
|
35
|
+
getTip: () => provider.getTip(),
|
|
36
|
+
getToken: (tokenId) => provider.getToken(tokenId),
|
|
37
|
+
searchTokens: (query) => provider.searchTokens(query),
|
|
38
|
+
getTokensByCreator: (address) => provider.getTokensByCreator(address),
|
|
39
|
+
broadcastTxPoW: (txpowHex) => provider.broadcastTxPoW(txpowHex),
|
|
40
|
+
async getMode() {
|
|
41
|
+
const result = await client.runCommand('getmode');
|
|
42
|
+
if (!result || typeof result !== 'object')
|
|
43
|
+
return undefined;
|
|
44
|
+
const mode = result.mode;
|
|
45
|
+
return typeof mode === 'string' ? mode : undefined;
|
|
46
|
+
},
|
|
47
|
+
async waitForConfirmation(coinId, confirmationOptions = {}) {
|
|
48
|
+
const timeoutMs = confirmationOptions.timeoutMs ?? 120_000;
|
|
49
|
+
const interval = confirmationOptions.pollIntervalMs ?? pollIntervalMs;
|
|
50
|
+
const deadline = Date.now() + timeoutMs;
|
|
51
|
+
while (Date.now() < deadline) {
|
|
52
|
+
const coin = await provider.getCoin(coinId);
|
|
53
|
+
if (coin && !coin.spent)
|
|
54
|
+
return;
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Timed out waiting for coin confirmation: ${coinId}`);
|
|
58
|
+
},
|
|
59
|
+
subscribe(listener) {
|
|
60
|
+
listeners.add(listener);
|
|
61
|
+
if (listeners.size === 1)
|
|
62
|
+
void poll();
|
|
63
|
+
return () => {
|
|
64
|
+
listeners.delete(listener);
|
|
65
|
+
if (listeners.size === 0 && timer) {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
timer = undefined;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
close() {
|
|
72
|
+
stopped = true;
|
|
73
|
+
listeners.clear();
|
|
74
|
+
if (timer)
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_js_1 = require("./index.js");
|
|
4
|
+
async function main() {
|
|
5
|
+
const host = (0, index_js_1.createOmniaHost)((0, index_js_1.loadConfigFromEnv)());
|
|
6
|
+
let closing = false;
|
|
7
|
+
let resolveStopped;
|
|
8
|
+
const stopped = new Promise((resolve) => {
|
|
9
|
+
resolveStopped = resolve;
|
|
10
|
+
});
|
|
11
|
+
const shutdown = async (signal) => {
|
|
12
|
+
if (closing)
|
|
13
|
+
return;
|
|
14
|
+
closing = true;
|
|
15
|
+
console.log(`[omnia-host] Received ${signal}; shutting down`);
|
|
16
|
+
try {
|
|
17
|
+
await host.close();
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
resolveStopped();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
process.once('SIGINT', () => { void shutdown('SIGINT'); });
|
|
24
|
+
process.once('SIGTERM', () => { void shutdown('SIGTERM'); });
|
|
25
|
+
await host.start();
|
|
26
|
+
console.log(`[omnia-host] Starting on port ${host.config.port}`);
|
|
27
|
+
await stopped;
|
|
28
|
+
}
|
|
29
|
+
main().catch((err) => {
|
|
30
|
+
console.error('[omnia-host] Fatal startup error:', err);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
});
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface OmniaHostConfig {
|
|
2
|
+
port: number;
|
|
3
|
+
host: string;
|
|
4
|
+
dbPath: string;
|
|
5
|
+
chainRpcUrl: string;
|
|
6
|
+
chainRpcPassword?: string;
|
|
7
|
+
analyticsDbPath?: string;
|
|
8
|
+
relay?: string;
|
|
9
|
+
localPubkey?: string;
|
|
10
|
+
localPartyId?: string;
|
|
11
|
+
localAddressIndex?: number;
|
|
12
|
+
localSettlementAddress?: string;
|
|
13
|
+
nodeMode?: string;
|
|
14
|
+
wsPath: string;
|
|
15
|
+
}
|
|
16
|
+
/** Load the daemon configuration from the documented OMNIA_* environment. */
|
|
17
|
+
export declare function loadConfigFromEnv(env?: NodeJS.ProcessEnv): OmniaHostConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadConfigFromEnv = loadConfigFromEnv;
|
|
7
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function parsePort(value) {
|
|
10
|
+
if (!value)
|
|
11
|
+
return 50052;
|
|
12
|
+
const port = Number(value);
|
|
13
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
14
|
+
throw new Error(`OMNIA_HOST_PORT must be an integer between 1 and 65535; received ${value}`);
|
|
15
|
+
}
|
|
16
|
+
return port;
|
|
17
|
+
}
|
|
18
|
+
function optional(value) {
|
|
19
|
+
const trimmed = value?.trim();
|
|
20
|
+
return trimmed ? trimmed : undefined;
|
|
21
|
+
}
|
|
22
|
+
function optionalInteger(value) {
|
|
23
|
+
const trimmed = value?.trim();
|
|
24
|
+
if (!trimmed)
|
|
25
|
+
return undefined;
|
|
26
|
+
const parsed = Number(trimmed);
|
|
27
|
+
if (!Number.isInteger(parsed) || parsed < 0)
|
|
28
|
+
throw new Error(`OMNIA_LOCAL_ADDRESS_INDEX must be a non-negative integer; received ${value}`);
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
/** Load the daemon configuration from the documented OMNIA_* environment. */
|
|
32
|
+
function loadConfigFromEnv(env = process.env) {
|
|
33
|
+
const dataRoot = node_path_1.default.join(node_os_1.default.homedir(), '.totem', 'omnia');
|
|
34
|
+
return {
|
|
35
|
+
port: parsePort(env.OMNIA_HOST_PORT),
|
|
36
|
+
host: env.OMNIA_HOST_BIND?.trim() || '127.0.0.1',
|
|
37
|
+
dbPath: env.OMNIA_HOST_DB?.trim() || node_path_1.default.join(dataRoot, 'omnia.sqlite'),
|
|
38
|
+
chainRpcUrl: env.OMNIA_CHAIN_RPC?.trim() || 'http://127.0.0.1:9005',
|
|
39
|
+
chainRpcPassword: optional(env.OMNIA_CHAIN_RPC_PASSWORD),
|
|
40
|
+
analyticsDbPath: optional(env.OMNIA_ANALYTICS_DB),
|
|
41
|
+
relay: optional(env.OMNIA_RELAY),
|
|
42
|
+
localPubkey: optional(env.OMNIA_LOCAL_PUBKEY),
|
|
43
|
+
localPartyId: optional(env.OMNIA_LOCAL_PARTY_ID),
|
|
44
|
+
localAddressIndex: optionalInteger(env.OMNIA_LOCAL_ADDRESS_INDEX),
|
|
45
|
+
localSettlementAddress: optional(env.OMNIA_LOCAL_SETTLEMENT_ADDRESS),
|
|
46
|
+
nodeMode: optional(env.OMNIA_NODE_MODE),
|
|
47
|
+
wsPath: env.OMNIA_WS_PATH?.trim() || '/rpc',
|
|
48
|
+
};
|
|
49
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type { OmniaHostConfig } from './config.js';
|
|
2
|
+
export { loadConfigFromEnv } from './config.js';
|
|
3
|
+
export type { OmniaHost } from './lifecycle.js';
|
|
4
|
+
export { createOmniaHost } from './lifecycle.js';
|
|
5
|
+
export { SqliteChannelStore } from './stores/sqlite-store.js';
|
|
6
|
+
export { OperationStore } from './stores/operations.js';
|
|
7
|
+
export type { OperationRecord, OperationStatus } from './stores/operations.js';
|
|
8
|
+
export { createTotemNodeAdapter } from './chain/totem-node-adapter.js';
|
|
9
|
+
export type { TotemNodeAdapter, TotemNodeAdapterOptions, ConfirmationOptions } from './chain/totem-node-adapter.js';
|
|
10
|
+
export { InProcessRoutingProvider } from './router/routing-provider.js';
|
|
11
|
+
export type { RoutingProvider, RouteQuery } from './router/routing-provider.js';
|
|
12
|
+
export { GoRoutingProvider } from './router/go-routing-provider.js';
|
|
13
|
+
export { createHostMethods } from './api/methods.js';
|
|
14
|
+
export type { HostApiContext } from './api/methods.js';
|
|
15
|
+
export type { OperationStoreLike } from './api/methods.js';
|
|
16
|
+
export { DisabledAnalyticsStore, DuckDbAnalyticsStore, initializeDuckDb } from './stores/analytics.js';
|
|
17
|
+
export type { AnalyticsEvent, AnalyticsStore, DuckDbConnection } from './stores/analytics.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
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.createOmniaHost = exports.loadConfigFromEnv = void 0;
|
|
4
|
+
var config_js_1 = require("./config.js");
|
|
5
|
+
Object.defineProperty(exports, "loadConfigFromEnv", { enumerable: true, get: function () { return config_js_1.loadConfigFromEnv; } });
|
|
6
|
+
var lifecycle_js_1 = require("./lifecycle.js");
|
|
7
|
+
Object.defineProperty(exports, "createOmniaHost", { enumerable: true, get: function () { return lifecycle_js_1.createOmniaHost; } });
|
|
8
|
+
var sqlite_store_js_1 = require("./stores/sqlite-store.js");
|
|
9
|
+
Object.defineProperty(exports, "SqliteChannelStore", { enumerable: true, get: function () { return sqlite_store_js_1.SqliteChannelStore; } });
|
|
10
|
+
var operations_js_1 = require("./stores/operations.js");
|
|
11
|
+
Object.defineProperty(exports, "OperationStore", { enumerable: true, get: function () { return operations_js_1.OperationStore; } });
|
|
12
|
+
var totem_node_adapter_js_1 = require("./chain/totem-node-adapter.js");
|
|
13
|
+
Object.defineProperty(exports, "createTotemNodeAdapter", { enumerable: true, get: function () { return totem_node_adapter_js_1.createTotemNodeAdapter; } });
|
|
14
|
+
var routing_provider_js_1 = require("./router/routing-provider.js");
|
|
15
|
+
Object.defineProperty(exports, "InProcessRoutingProvider", { enumerable: true, get: function () { return routing_provider_js_1.InProcessRoutingProvider; } });
|
|
16
|
+
var go_routing_provider_js_1 = require("./router/go-routing-provider.js");
|
|
17
|
+
Object.defineProperty(exports, "GoRoutingProvider", { enumerable: true, get: function () { return go_routing_provider_js_1.GoRoutingProvider; } });
|
|
18
|
+
var methods_js_1 = require("./api/methods.js");
|
|
19
|
+
Object.defineProperty(exports, "createHostMethods", { enumerable: true, get: function () { return methods_js_1.createHostMethods; } });
|
|
20
|
+
var analytics_js_1 = require("./stores/analytics.js");
|
|
21
|
+
Object.defineProperty(exports, "DisabledAnalyticsStore", { enumerable: true, get: function () { return analytics_js_1.DisabledAnalyticsStore; } });
|
|
22
|
+
Object.defineProperty(exports, "DuckDbAnalyticsStore", { enumerable: true, get: function () { return analytics_js_1.DuckDbAnalyticsStore; } });
|
|
23
|
+
Object.defineProperty(exports, "initializeDuckDb", { enumerable: true, get: function () { return analytics_js_1.initializeDuckDb; } });
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { OmniaHostConfig } from './config.js';
|
|
2
|
+
import type { OmniaSwarm, ChannelSigner, WotsLeaseProviderLike, MinimalChainProvider } from '@totemsdk/omnia';
|
|
3
|
+
import { createOmniaSwarm } from '@totemsdk/omnia';
|
|
4
|
+
import { OperationStore } from './stores/operations.js';
|
|
5
|
+
import type { OmniaChannel } from '@totemsdk/omnia';
|
|
6
|
+
import { type ControlServer } from './api/jsonrpc.js';
|
|
7
|
+
import { type TotemNodeAdapter } from './chain/totem-node-adapter.js';
|
|
8
|
+
import { type RoutingProvider } from './router/routing-provider.js';
|
|
9
|
+
import type { JsonRpcHandler } from './api/jsonrpc.js';
|
|
10
|
+
import { type OperationStoreLike } from './api/methods.js';
|
|
11
|
+
import type { ChannelFactory, WotsLeaseBundle } from '@totemsdk/omnia-factory';
|
|
12
|
+
import type { SpliceAcceptance, SpliceLeaseProvider, SpliceProposal } from '@totemsdk/omnia-splice';
|
|
13
|
+
export interface OmniaHostDependencies {
|
|
14
|
+
createSwarm?: (config: Parameters<typeof createOmniaSwarm>[0]) => Promise<OmniaSwarm>;
|
|
15
|
+
swarm?: OmniaSwarm;
|
|
16
|
+
channelStore?: Map<string, OmniaChannel>;
|
|
17
|
+
operationStore?: OperationStore | Pick<OperationStore, 'close'>;
|
|
18
|
+
signer?: ChannelSigner;
|
|
19
|
+
leaseProvider?: WotsLeaseProviderLike;
|
|
20
|
+
chainProvider?: MinimalChainProvider;
|
|
21
|
+
controlServer?: ControlServer;
|
|
22
|
+
chainAdapter?: TotemNodeAdapter;
|
|
23
|
+
routingProvider?: RoutingProvider;
|
|
24
|
+
methods?: Map<string, JsonRpcHandler>;
|
|
25
|
+
operationStoreForApi?: OperationStoreLike;
|
|
26
|
+
localParticipant?: import('@totemsdk/omnia').ChannelParticipant;
|
|
27
|
+
factories?: Map<string, ChannelFactory>;
|
|
28
|
+
factoryBundles?: Record<string, WotsLeaseBundle>;
|
|
29
|
+
spliceProposals?: Map<string, SpliceProposal>;
|
|
30
|
+
spliceAcceptances?: Map<string, SpliceAcceptance>;
|
|
31
|
+
spliceLeaseProvider?: SpliceLeaseProvider;
|
|
32
|
+
}
|
|
33
|
+
export interface OmniaHost {
|
|
34
|
+
readonly config: OmniaHostConfig;
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
isStarted(): boolean;
|
|
38
|
+
readonly swarm?: OmniaSwarm;
|
|
39
|
+
readonly channels?: Map<string, OmniaChannel>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Phase-1 lifecycle shell. Subsystems will be attached in later phases while
|
|
43
|
+
* preserving idempotent startup and shutdown for the CLI and embedding users.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createOmniaHost(config: OmniaHostConfig, dependencies?: OmniaHostDependencies): OmniaHost;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOmniaHost = createOmniaHost;
|
|
4
|
+
const omnia_1 = require("@totemsdk/omnia");
|
|
5
|
+
const sqlite_store_js_1 = require("./stores/sqlite-store.js");
|
|
6
|
+
const operations_js_1 = require("./stores/operations.js");
|
|
7
|
+
const jsonrpc_js_1 = require("./api/jsonrpc.js");
|
|
8
|
+
const totem_node_adapter_js_1 = require("./chain/totem-node-adapter.js");
|
|
9
|
+
const routing_provider_js_1 = require("./router/routing-provider.js");
|
|
10
|
+
const methods_js_1 = require("./api/methods.js");
|
|
11
|
+
/**
|
|
12
|
+
* Phase-1 lifecycle shell. Subsystems will be attached in later phases while
|
|
13
|
+
* preserving idempotent startup and shutdown for the CLI and embedding users.
|
|
14
|
+
*/
|
|
15
|
+
function createOmniaHost(config, dependencies = {}) {
|
|
16
|
+
let started = false;
|
|
17
|
+
let swarm = dependencies.swarm;
|
|
18
|
+
let unsubscribe;
|
|
19
|
+
let channelStore = dependencies.channelStore;
|
|
20
|
+
let ownedStore;
|
|
21
|
+
let operationStore = dependencies.operationStore && 'get' in dependencies.operationStore
|
|
22
|
+
? dependencies.operationStore
|
|
23
|
+
: undefined;
|
|
24
|
+
const injectedOperationStore = dependencies.operationStore;
|
|
25
|
+
let ownedOperationStore;
|
|
26
|
+
let controlServer = dependencies.controlServer;
|
|
27
|
+
let chainAdapter = dependencies.chainAdapter;
|
|
28
|
+
let routingProvider = dependencies.routingProvider ?? new routing_provider_js_1.InProcessRoutingProvider();
|
|
29
|
+
return {
|
|
30
|
+
config,
|
|
31
|
+
get swarm() {
|
|
32
|
+
return swarm;
|
|
33
|
+
},
|
|
34
|
+
get channels() {
|
|
35
|
+
return channelStore;
|
|
36
|
+
},
|
|
37
|
+
async start() {
|
|
38
|
+
if (started)
|
|
39
|
+
return;
|
|
40
|
+
if (!channelStore) {
|
|
41
|
+
ownedStore = new sqlite_store_js_1.SqliteChannelStore(config.dbPath);
|
|
42
|
+
channelStore = ownedStore;
|
|
43
|
+
}
|
|
44
|
+
if (!operationStore && !injectedOperationStore) {
|
|
45
|
+
ownedOperationStore = new operations_js_1.OperationStore(config.dbPath);
|
|
46
|
+
operationStore = ownedOperationStore;
|
|
47
|
+
}
|
|
48
|
+
if (!swarm) {
|
|
49
|
+
const relay = config.relay
|
|
50
|
+
? { mode: 'self-hosted', relayUrl: config.relay }
|
|
51
|
+
: { mode: 'native' };
|
|
52
|
+
swarm = await (dependencies.createSwarm ?? omnia_1.createOmniaSwarm)({
|
|
53
|
+
localPubkey: config.localPubkey,
|
|
54
|
+
relay,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (!chainAdapter) {
|
|
58
|
+
const rpc = new URL(config.chainRpcUrl);
|
|
59
|
+
chainAdapter = (0, totem_node_adapter_js_1.createTotemNodeAdapter)({
|
|
60
|
+
host: rpc.hostname,
|
|
61
|
+
port: Number(rpc.port || (rpc.protocol === 'https:' ? 443 : 80)),
|
|
62
|
+
password: config.chainRpcPassword,
|
|
63
|
+
ssl: rpc.protocol === 'https:',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const localParticipant = dependencies.localParticipant ?? (config.localPartyId && config.localPubkey && config.localAddressIndex !== undefined
|
|
67
|
+
? {
|
|
68
|
+
partyId: config.localPartyId,
|
|
69
|
+
publicKeyDigest: config.localPubkey,
|
|
70
|
+
addressIndex: config.localAddressIndex,
|
|
71
|
+
settlementAddress: config.localSettlementAddress,
|
|
72
|
+
}
|
|
73
|
+
: undefined);
|
|
74
|
+
unsubscribe = (0, omnia_1.createOmniaIntegration)(swarm, channelStore, {
|
|
75
|
+
signer: dependencies.signer,
|
|
76
|
+
leaseProvider: dependencies.leaseProvider,
|
|
77
|
+
chainProvider: dependencies.chainProvider ?? chainAdapter,
|
|
78
|
+
});
|
|
79
|
+
if (!controlServer) {
|
|
80
|
+
controlServer = (0, jsonrpc_js_1.createControlServer)({
|
|
81
|
+
host: config.host,
|
|
82
|
+
port: config.port,
|
|
83
|
+
wsPath: config.wsPath,
|
|
84
|
+
isReady: () => started,
|
|
85
|
+
methods: dependencies.methods ?? (0, methods_js_1.createHostMethods)({
|
|
86
|
+
channels: channelStore,
|
|
87
|
+
routing: routingProvider,
|
|
88
|
+
operations: dependencies.operationStoreForApi ?? operationStore,
|
|
89
|
+
signer: dependencies.signer,
|
|
90
|
+
leaseProvider: dependencies.leaseProvider,
|
|
91
|
+
chainProvider: dependencies.chainProvider ?? chainAdapter,
|
|
92
|
+
swarm,
|
|
93
|
+
localParticipant,
|
|
94
|
+
factories: dependencies.factories,
|
|
95
|
+
factoryBundles: dependencies.factoryBundles,
|
|
96
|
+
spliceProposals: dependencies.spliceProposals,
|
|
97
|
+
spliceAcceptances: dependencies.spliceAcceptances,
|
|
98
|
+
spliceLeaseProvider: dependencies.spliceLeaseProvider,
|
|
99
|
+
refreshRouting: () => routingProvider.rebuild((0, routing_provider_js_1.channelGraphEdges)(channelStore.values())),
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await controlServer.listen();
|
|
105
|
+
started = true;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
unsubscribe?.();
|
|
109
|
+
unsubscribe = undefined;
|
|
110
|
+
chainAdapter?.close();
|
|
111
|
+
await swarm?.close();
|
|
112
|
+
if (ownedOperationStore)
|
|
113
|
+
await ownedOperationStore.close();
|
|
114
|
+
ownedOperationStore = undefined;
|
|
115
|
+
ownedStore?.close();
|
|
116
|
+
ownedStore = undefined;
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
async close() {
|
|
121
|
+
if (!started)
|
|
122
|
+
return;
|
|
123
|
+
unsubscribe?.();
|
|
124
|
+
unsubscribe = undefined;
|
|
125
|
+
await controlServer?.close();
|
|
126
|
+
chainAdapter?.close();
|
|
127
|
+
await swarm?.close();
|
|
128
|
+
if (ownedOperationStore)
|
|
129
|
+
await ownedOperationStore.close();
|
|
130
|
+
else if (injectedOperationStore)
|
|
131
|
+
await injectedOperationStore.close();
|
|
132
|
+
ownedOperationStore = undefined;
|
|
133
|
+
ownedStore?.close();
|
|
134
|
+
ownedStore = undefined;
|
|
135
|
+
started = false;
|
|
136
|
+
},
|
|
137
|
+
isStarted() {
|
|
138
|
+
return started;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChannelGraphEdge, CrossTokenRoute, Route } from '@totemsdk/omnia-router';
|
|
2
|
+
import type { RouteQuery, RoutingProvider } from './routing-provider.js';
|
|
3
|
+
/** Optional phase-10 provider for a compiled Go router speaking JSONL over stdio. */
|
|
4
|
+
export declare class GoRoutingProvider implements RoutingProvider {
|
|
5
|
+
private readonly child;
|
|
6
|
+
private readonly pending;
|
|
7
|
+
private nextId;
|
|
8
|
+
constructor(binaryPath?: string);
|
|
9
|
+
rebuild(edges: Iterable<ChannelGraphEdge>): void;
|
|
10
|
+
getRoute(query: RouteQuery): Promise<Route | CrossTokenRoute | null>;
|
|
11
|
+
close(): void;
|
|
12
|
+
private call;
|
|
13
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GoRoutingProvider = void 0;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_readline_1 = require("node:readline");
|
|
6
|
+
/** Optional phase-10 provider for a compiled Go router speaking JSONL over stdio. */
|
|
7
|
+
class GoRoutingProvider {
|
|
8
|
+
child;
|
|
9
|
+
pending = new Map();
|
|
10
|
+
nextId = 1;
|
|
11
|
+
constructor(binaryPath = 'omnia-router') {
|
|
12
|
+
this.child = (0, node_child_process_1.spawn)(binaryPath, [], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
13
|
+
const lines = (0, node_readline_1.createInterface)({ input: this.child.stdout });
|
|
14
|
+
lines.on('line', (line) => {
|
|
15
|
+
try {
|
|
16
|
+
const response = JSON.parse(line);
|
|
17
|
+
const pending = response.id === undefined ? undefined : this.pending.get(response.id);
|
|
18
|
+
if (!pending || response.id === undefined)
|
|
19
|
+
return;
|
|
20
|
+
this.pending.delete(response.id);
|
|
21
|
+
pending.resolve(response);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
for (const pending of this.pending.values())
|
|
25
|
+
pending.reject(error);
|
|
26
|
+
this.pending.clear();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const fail = (error) => {
|
|
30
|
+
for (const pending of this.pending.values())
|
|
31
|
+
pending.reject(error);
|
|
32
|
+
this.pending.clear();
|
|
33
|
+
};
|
|
34
|
+
this.child.once('error', fail);
|
|
35
|
+
this.child.once('exit', (code) => fail(new Error(`Go router exited with code ${code ?? 'unknown'}`)));
|
|
36
|
+
}
|
|
37
|
+
rebuild(edges) {
|
|
38
|
+
for (const edge of edges) {
|
|
39
|
+
void this.call('addChannel', edge);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async getRoute(query) {
|
|
43
|
+
const response = await this.call('findRoute', {
|
|
44
|
+
from: query.from,
|
|
45
|
+
to: query.to,
|
|
46
|
+
amount: query.amount.toString(),
|
|
47
|
+
tokenId: query.tokenId,
|
|
48
|
+
maxHops: query.maxHops,
|
|
49
|
+
});
|
|
50
|
+
if (response.error)
|
|
51
|
+
throw new Error(response.error);
|
|
52
|
+
return response.result ?? null;
|
|
53
|
+
}
|
|
54
|
+
close() {
|
|
55
|
+
this.child.stdin.end();
|
|
56
|
+
this.child.kill();
|
|
57
|
+
}
|
|
58
|
+
call(method, params) {
|
|
59
|
+
const id = this.nextId++;
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
this.pending.set(id, { resolve, reject });
|
|
62
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.GoRoutingProvider = GoRoutingProvider;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ChannelGraphEdge, type CrossTokenRoute, type Route } from '@totemsdk/omnia-router';
|
|
2
|
+
import type { OmniaChannel } from '@totemsdk/omnia';
|
|
3
|
+
export interface RouteQuery {
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
amount: bigint;
|
|
7
|
+
tokenId: string;
|
|
8
|
+
targetTokenId?: string;
|
|
9
|
+
maxHops?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface RoutingProvider {
|
|
12
|
+
rebuild(edges: Iterable<ChannelGraphEdge>): void;
|
|
13
|
+
getRoute(query: RouteQuery): Route | CrossTokenRoute | null | Promise<Route | CrossTokenRoute | null>;
|
|
14
|
+
}
|
|
15
|
+
export declare function channelGraphEdges(channels: Iterable<OmniaChannel>): ChannelGraphEdge[];
|
|
16
|
+
/** Default in-process TypeScript routing engine. */
|
|
17
|
+
export declare class InProcessRoutingProvider implements RoutingProvider {
|
|
18
|
+
private graph;
|
|
19
|
+
rebuild(edges: Iterable<ChannelGraphEdge>): void;
|
|
20
|
+
getRoute(query: RouteQuery): Route | CrossTokenRoute | null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InProcessRoutingProvider = void 0;
|
|
4
|
+
exports.channelGraphEdges = channelGraphEdges;
|
|
5
|
+
const omnia_router_1 = require("@totemsdk/omnia-router");
|
|
6
|
+
function channelGraphEdges(channels) {
|
|
7
|
+
const edges = [];
|
|
8
|
+
for (const channel of channels) {
|
|
9
|
+
if (channel.status === 'closed')
|
|
10
|
+
continue;
|
|
11
|
+
const [first, second] = channel.parties;
|
|
12
|
+
if (!first || !second)
|
|
13
|
+
continue;
|
|
14
|
+
const pending = channel.pendingHTLCs
|
|
15
|
+
.filter((htlc) => htlc.status === 'pending')
|
|
16
|
+
.reduce((sum, htlc) => sum + htlc.amount, 0n);
|
|
17
|
+
const capacity = channel.totalValue > pending ? channel.totalValue - pending : 0n;
|
|
18
|
+
edges.push({
|
|
19
|
+
channelId: channel.channelId,
|
|
20
|
+
from: first.publicKeyDigest,
|
|
21
|
+
to: second.publicKeyDigest,
|
|
22
|
+
tokenId: channel.tokenId,
|
|
23
|
+
availableBalance: channel.balances[first.partyId] ?? 0n,
|
|
24
|
+
htlcCapacity: capacity,
|
|
25
|
+
feeRate: 0n,
|
|
26
|
+
}, {
|
|
27
|
+
channelId: channel.channelId,
|
|
28
|
+
from: second.publicKeyDigest,
|
|
29
|
+
to: first.publicKeyDigest,
|
|
30
|
+
tokenId: channel.tokenId,
|
|
31
|
+
availableBalance: channel.balances[second.partyId] ?? 0n,
|
|
32
|
+
htlcCapacity: capacity,
|
|
33
|
+
feeRate: 0n,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return edges;
|
|
37
|
+
}
|
|
38
|
+
/** Default in-process TypeScript routing engine. */
|
|
39
|
+
class InProcessRoutingProvider {
|
|
40
|
+
graph = (0, omnia_router_1.createChannelGraph)();
|
|
41
|
+
rebuild(edges) {
|
|
42
|
+
this.graph = (0, omnia_router_1.createChannelGraph)();
|
|
43
|
+
for (const edge of edges) {
|
|
44
|
+
const current = this.graph.edgesByChannel.get(edge.channelId) ?? [];
|
|
45
|
+
const duplicate = current.findIndex((item) => item.from === edge.from);
|
|
46
|
+
if (duplicate >= 0)
|
|
47
|
+
current[duplicate] = edge;
|
|
48
|
+
else
|
|
49
|
+
current.push(edge);
|
|
50
|
+
this.graph.edgesByChannel.set(edge.channelId, current);
|
|
51
|
+
const nodeEdges = this.graph.nodeEdges.get(edge.from) ?? [];
|
|
52
|
+
const nodeIndex = nodeEdges.findIndex((item) => item.channelId === edge.channelId && item.from === edge.from);
|
|
53
|
+
if (nodeIndex >= 0)
|
|
54
|
+
nodeEdges[nodeIndex] = edge;
|
|
55
|
+
else
|
|
56
|
+
nodeEdges.push(edge);
|
|
57
|
+
this.graph.nodeEdges.set(edge.from, nodeEdges);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
getRoute(query) {
|
|
61
|
+
if (query.targetTokenId && query.targetTokenId !== query.tokenId) {
|
|
62
|
+
return (0, omnia_router_1.findCrossTokenRoute)(this.graph, query.from, query.to, query.amount, query.tokenId, query.targetTokenId, { maxHops: query.maxHops });
|
|
63
|
+
}
|
|
64
|
+
return (0, omnia_router_1.findRoute)(this.graph, query.from, query.to, query.amount, query.tokenId, { maxHops: query.maxHops });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.InProcessRoutingProvider = InProcessRoutingProvider;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface AnalyticsEvent {
|
|
2
|
+
eventId: string;
|
|
3
|
+
kind: string;
|
|
4
|
+
occurredAt: number;
|
|
5
|
+
channelId?: string;
|
|
6
|
+
operationId?: string;
|
|
7
|
+
payload: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface AnalyticsStore {
|
|
10
|
+
append(event: AnalyticsEvent): Promise<void>;
|
|
11
|
+
query(kind?: string, limit?: number): Promise<AnalyticsEvent[]>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* No-op analytics backend for phase 9 deployments without DuckDB enabled.
|
|
16
|
+
* The interface keeps analytics off the channel critical path.
|
|
17
|
+
*/
|
|
18
|
+
export declare class DisabledAnalyticsStore implements AnalyticsStore {
|
|
19
|
+
append(_event: AnalyticsEvent): Promise<void>;
|
|
20
|
+
query(_kind?: string, _limit?: number): Promise<AnalyticsEvent[]>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** Adapter boundary for the optional DuckDB implementation. */
|
|
24
|
+
export interface DuckDbConnection {
|
|
25
|
+
run(sql: string, ...params: unknown[]): Promise<void>;
|
|
26
|
+
all<T>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare class DuckDbAnalyticsStore implements AnalyticsStore {
|
|
30
|
+
private readonly db;
|
|
31
|
+
constructor(db: DuckDbConnection);
|
|
32
|
+
append(event: AnalyticsEvent): Promise<void>;
|
|
33
|
+
query(kind?: string, limit?: number): Promise<AnalyticsEvent[]>;
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export declare function initializeDuckDb(db: DuckDbConnection): Promise<void>;
|