@leofcoin/launch-chain 0.4.19 → 0.4.21
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/README.md +34 -3
- package/exports/index.d.ts +9 -8
- package/exports/index.js +27 -30
- package/exports/validator-cli.d.ts +2 -0
- package/exports/validator-cli.js +72 -0
- package/exports/validator.d.ts +21 -0
- package/exports/validator.js +123 -0
- package/package.json +17 -7
- package/exports/src/index.d.ts +0 -38
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# launch-chain
|
|
2
2
|
|
|
3
3
|
> launch chain & peernet (reuses exposed transports when already running)
|
|
4
4
|
|
|
@@ -7,13 +7,44 @@
|
|
|
7
7
|
```js
|
|
8
8
|
import launch from '@leofcoin/launch-chain'
|
|
9
9
|
|
|
10
|
-
const { chain, endpoints, mode } = await launch(
|
|
10
|
+
const { chain, endpoints, mode } = await launch(
|
|
11
|
+
{ mode: 'direct' },
|
|
12
|
+
process.env.LEOFCOIN_PASSWORD
|
|
13
|
+
)
|
|
11
14
|
// chain is undefined when mode is remote
|
|
12
15
|
// endpoints contain urls to connect to the desired remote
|
|
13
16
|
// when mode is remote means an instance is already running
|
|
14
17
|
// when mode is direct means chain is directly available and no endpoint is needed to interact with it
|
|
15
18
|
```
|
|
16
19
|
|
|
20
|
+
## run a validator
|
|
21
|
+
|
|
22
|
+
No monorepo checkout is required. Run the validator directly from the published package:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npx --package @leofcoin/launch-chain leofcoin-validator
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The first run uses the normal interactive identity-password prompt. A new account must receive
|
|
29
|
+
the validator contract's minimum balance before it can register. An existing validator must be
|
|
30
|
+
online to finalize that registration transaction.
|
|
31
|
+
|
|
32
|
+
For an unattended server, keep the password outside the repository in a mode-`0600` file:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npx --package @leofcoin/launch-chain leofcoin-validator \
|
|
36
|
+
--password-file /secure/leofcoin/password \
|
|
37
|
+
--root /var/lib/leofcoin/peach \
|
|
38
|
+
--interval 5
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The validator also exposes the HTTP API on port `8080` and WebSocket API on port `4040`, so one
|
|
42
|
+
process can replace a separate launch-chain server. Change them with `--http-port` and `--ws-port`,
|
|
43
|
+
or use `--no-endpoints` for a validator that should not expose an API.
|
|
44
|
+
|
|
45
|
+
Never put the password or an identity backup in a command-line argument, service file, or Git.
|
|
46
|
+
Use `leofcoin-validator --help` for all options.
|
|
47
|
+
|
|
17
48
|
## options
|
|
18
49
|
|
|
19
50
|
### default
|
|
@@ -21,7 +52,7 @@ const { chain, endpoints, mode } = await launch()
|
|
|
21
52
|
```js
|
|
22
53
|
{
|
|
23
54
|
network: 'leofcoin:peach',
|
|
24
|
-
stars: ['wss://
|
|
55
|
+
stars: ['wss://star.leofcoin.org'],
|
|
25
56
|
forceRemote: false, // when set to true only tries to connect to an external/local exposed node
|
|
26
57
|
ws: {
|
|
27
58
|
port: 4040,
|
package/exports/index.d.ts
CHANGED
|
@@ -3,15 +3,15 @@ import WSClient from '@leofcoin/endpoint-clients/ws';
|
|
|
3
3
|
import HttpClient from '@leofcoin/endpoint-clients/http';
|
|
4
4
|
type launchMode = 'direct' | 'remote' | 'server';
|
|
5
5
|
type endpointReturns = {
|
|
6
|
-
http
|
|
7
|
-
ws
|
|
6
|
+
http: string[];
|
|
7
|
+
ws: string[];
|
|
8
8
|
};
|
|
9
9
|
type clientReturns = {
|
|
10
|
-
http
|
|
11
|
-
ws
|
|
10
|
+
http: HttpClient[];
|
|
11
|
+
ws: WSClient[];
|
|
12
12
|
};
|
|
13
13
|
type launchReturn = {
|
|
14
|
-
chain
|
|
14
|
+
chain?: Chain;
|
|
15
15
|
mode: launchMode;
|
|
16
16
|
endpoints: endpointReturns;
|
|
17
17
|
clients: clientReturns;
|
|
@@ -23,9 +23,10 @@ type endpointOptions = {
|
|
|
23
23
|
type launchOptions = {
|
|
24
24
|
network?: string;
|
|
25
25
|
networkVersion?: string;
|
|
26
|
-
stars
|
|
27
|
-
forceRemote
|
|
26
|
+
stars?: string[];
|
|
27
|
+
forceRemote?: boolean;
|
|
28
28
|
mode?: launchMode;
|
|
29
|
+
root?: string;
|
|
29
30
|
ws?: endpointOptions[] | undefined;
|
|
30
31
|
http?: endpointOptions[] | undefined;
|
|
31
32
|
};
|
|
@@ -34,5 +35,5 @@ type launchOptions = {
|
|
|
34
35
|
* @param {object} options { ws: boolean || {url: string, port: number}, http: boolean || {url: string, port: number}, network}
|
|
35
36
|
* @returns '{ mode: string, endpoints: object, chain}'
|
|
36
37
|
*/
|
|
37
|
-
declare const launch: (
|
|
38
|
+
declare const launch: (input?: launchOptions, password?: string) => Promise<launchReturn>;
|
|
38
39
|
export { launch as default };
|
package/exports/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const defaultOptions = {
|
|
|
11
11
|
stars: networks.leofcoin.peach.stars,
|
|
12
12
|
forceRemote: false,
|
|
13
13
|
mode: 'direct',
|
|
14
|
+
root: undefined,
|
|
14
15
|
ws: [
|
|
15
16
|
{
|
|
16
17
|
port: 4040
|
|
@@ -72,11 +73,8 @@ const getWS = async (url, networkVersion) => {
|
|
|
72
73
|
* @param {object} options { ws: boolean || {url: string, port: number}, http: boolean || {url: string, port: number}, network}
|
|
73
74
|
* @returns '{ mode: string, endpoints: object, chain}'
|
|
74
75
|
*/
|
|
75
|
-
const launch = async (
|
|
76
|
-
|
|
77
|
-
options = defaultOptions;
|
|
78
|
-
else
|
|
79
|
-
options = { ...defaultOptions, ...options };
|
|
76
|
+
const launch = async (input = {}, password) => {
|
|
77
|
+
const options = { ...defaultOptions, ...input };
|
|
80
78
|
const clients = {
|
|
81
79
|
http: [],
|
|
82
80
|
ws: []
|
|
@@ -89,26 +87,29 @@ const launch = async (options, password) => {
|
|
|
89
87
|
if (options.mode === 'remote') {
|
|
90
88
|
if (options.http) {
|
|
91
89
|
for (const endpoint of options.http) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const url = endpoint.url ?? `http://localhost:${endpoint.port}`;
|
|
91
|
+
const client = await getHttp(url, options.networkVersion);
|
|
92
|
+
if (client) {
|
|
93
|
+
endpoints.http.push(url);
|
|
94
|
+
clients.http.push(client);
|
|
95
|
+
}
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
if (options.ws) {
|
|
100
99
|
for (const endpoint of options.ws) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
const url = endpoint.url ?? `ws://localhost:${endpoint.port}`;
|
|
101
|
+
const client = await getWS(url, options.networkVersion);
|
|
102
|
+
if (client) {
|
|
103
|
+
endpoints.ws.push(url);
|
|
104
|
+
clients.ws.push(client);
|
|
105
|
+
}
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
if (endpoints.http.length === 0 && endpoints.ws.length === 0)
|
|
108
109
|
throw new Error(`no remotes connected`);
|
|
109
110
|
}
|
|
110
111
|
else if (options.mode === 'direct') {
|
|
111
|
-
const node = new Node({ network: options.network, stars: options.stars, networkVersion: options.networkVersion }, password);
|
|
112
|
+
const node = new Node({ network: options.network, stars: options.stars, networkVersion: options.networkVersion, root: options.root }, password);
|
|
112
113
|
await node.ready;
|
|
113
114
|
await nodeConfig({ network: options.network, networkVersion: options.networkVersion });
|
|
114
115
|
chain = new Chain({ network: options.network, stars: options.stars, networkVersion: options.networkVersion });
|
|
@@ -117,11 +118,10 @@ const launch = async (options, password) => {
|
|
|
117
118
|
const importee = await import('@leofcoin/endpoints/ws');
|
|
118
119
|
const wsServer = importee.default;
|
|
119
120
|
for (const endpoint of options.ws) {
|
|
120
|
-
|
|
121
|
-
endpoint.url = `ws://localhost:${endpoint.port}`;
|
|
121
|
+
const url = endpoint.url ?? `ws://localhost:${endpoint.port}`;
|
|
122
122
|
await wsServer(chain, endpoint.port, options.networkVersion);
|
|
123
|
-
endpoints.ws.push(
|
|
124
|
-
const client = await getWS(
|
|
123
|
+
endpoints.ws.push(url);
|
|
124
|
+
const client = await getWS(url, options.networkVersion);
|
|
125
125
|
client && clients.ws.push(client);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -129,17 +129,16 @@ const launch = async (options, password) => {
|
|
|
129
129
|
const importee = await import('@leofcoin/endpoints/http');
|
|
130
130
|
const httpServer = importee.default;
|
|
131
131
|
for (const endpoint of options.http) {
|
|
132
|
-
|
|
133
|
-
endpoint.url = `http://localhost:${endpoint.port}`;
|
|
132
|
+
const url = endpoint.url ?? `http://localhost:${endpoint.port}`;
|
|
134
133
|
await httpServer(chain, endpoint.port, options.networkVersion);
|
|
135
|
-
endpoints.http.push(
|
|
136
|
-
const client = await getHttp(
|
|
134
|
+
endpoints.http.push(url);
|
|
135
|
+
const client = await getHttp(url, options.networkVersion);
|
|
137
136
|
client && clients.http.push(client);
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
else {
|
|
142
|
-
const node = new Node({ network: options.network, stars: options.stars, networkVersion: options.networkVersion }, password);
|
|
141
|
+
const node = new Node({ network: options.network, stars: options.stars, networkVersion: options.networkVersion, root: options.root }, password);
|
|
143
142
|
await node.ready;
|
|
144
143
|
await nodeConfig({ network: options.network, networkVersion: options.networkVersion });
|
|
145
144
|
chain = new Chain({ network: options.network, stars: options.stars, networkVersion: options.networkVersion });
|
|
@@ -148,20 +147,18 @@ const launch = async (options, password) => {
|
|
|
148
147
|
const importee = await import('@leofcoin/endpoints/ws');
|
|
149
148
|
const wsServer = importee.default;
|
|
150
149
|
for (const endpoint of options.ws) {
|
|
151
|
-
|
|
152
|
-
endpoint.url = `ws://localhost:${endpoint.port}`;
|
|
150
|
+
const url = endpoint.url ?? `ws://localhost:${endpoint.port}`;
|
|
153
151
|
await wsServer(chain, endpoint.port, options.networkVersion);
|
|
154
|
-
endpoints.ws.push(
|
|
152
|
+
endpoints.ws.push(url);
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
155
|
if (options.http) {
|
|
158
156
|
const importee = await import('@leofcoin/endpoints/http');
|
|
159
157
|
const httpServer = importee.default;
|
|
160
158
|
for (const endpoint of options.http) {
|
|
161
|
-
|
|
162
|
-
endpoint.url = `http://localhost:${endpoint.port}`;
|
|
159
|
+
const url = endpoint.url ?? `http://localhost:${endpoint.port}`;
|
|
163
160
|
await httpServer(chain, endpoint.port, options.networkVersion);
|
|
164
|
-
endpoints.http.push(
|
|
161
|
+
endpoints.http.push(url);
|
|
165
162
|
}
|
|
166
163
|
}
|
|
167
164
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { startValidator } from './validator.js';
|
|
5
|
+
import '@leofcoin/addresses';
|
|
6
|
+
import '@leofcoin/lib';
|
|
7
|
+
import '@leofcoin/networks';
|
|
8
|
+
import './index.js';
|
|
9
|
+
import '@leofcoin/chain/node';
|
|
10
|
+
import '@leofcoin/chain/chain';
|
|
11
|
+
import '@leofcoin/lib/node-config';
|
|
12
|
+
import '@leofcoin/endpoint-clients/ws';
|
|
13
|
+
import '@leofcoin/endpoint-clients/http';
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const valueAfter = (name) => {
|
|
17
|
+
const index = args.indexOf(name);
|
|
18
|
+
if (index === -1)
|
|
19
|
+
return undefined;
|
|
20
|
+
const value = args[index + 1];
|
|
21
|
+
if (value === undefined || value.startsWith('--'))
|
|
22
|
+
throw new Error(`${name} requires a value`);
|
|
23
|
+
return value;
|
|
24
|
+
};
|
|
25
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
26
|
+
process.stdout.write(`Usage: leofcoin-validator [options]
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
--network <name> Network (default: leofcoin:peach)
|
|
30
|
+
--root <path> Persistent Leofcoin data directory
|
|
31
|
+
--password-file <path> Read the identity password from a private file
|
|
32
|
+
--interval <minutes> Heartbeat interval (default: 5, minimum: 1)
|
|
33
|
+
--http-port <port> HTTP API port (default: 8080)
|
|
34
|
+
--ws-port <port> WebSocket API port (default: 4040)
|
|
35
|
+
--no-endpoints Disable both API endpoints
|
|
36
|
+
-h, --help Show this help
|
|
37
|
+
|
|
38
|
+
Without --password-file, LEOFCOIN_PASSWORD is used when set. Otherwise the
|
|
39
|
+
standard interactive password prompt is shown. Never pass a password directly
|
|
40
|
+
as a command-line argument.
|
|
41
|
+
`);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
const passwordFile = valueAfter('--password-file') ?? process.env.LEOFCOIN_PASSWORD_FILE;
|
|
45
|
+
const password = passwordFile
|
|
46
|
+
? (await readFile(resolve(passwordFile), 'utf8')).trim()
|
|
47
|
+
: process.env.LEOFCOIN_PASSWORD;
|
|
48
|
+
if (passwordFile && !password)
|
|
49
|
+
throw new Error(`empty validator password file: ${resolve(passwordFile)}`);
|
|
50
|
+
const intervalValue = valueAfter('--interval') ?? process.env.LEOFCOIN_TRANSACTION_INTERVAL_MINUTES;
|
|
51
|
+
const noEndpoints = args.includes('--no-endpoints');
|
|
52
|
+
const port = (name, fallback) => {
|
|
53
|
+
const value = Number(valueAfter(name) ?? fallback);
|
|
54
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 65_535)
|
|
55
|
+
throw new Error(`${name} must be a valid port`);
|
|
56
|
+
return value;
|
|
57
|
+
};
|
|
58
|
+
const handle = await startValidator({
|
|
59
|
+
network: valueAfter('--network') ?? process.env.LEOFCOIN_NETWORK,
|
|
60
|
+
root: valueAfter('--root') ?? process.env.LEOFCOIN_DATA_ROOT,
|
|
61
|
+
password,
|
|
62
|
+
intervalMinutes: intervalValue === undefined ? undefined : Number(intervalValue),
|
|
63
|
+
httpPort: noEndpoints ? false : port('--http-port', 8080),
|
|
64
|
+
wsPort: noEndpoints ? false : port('--ws-port', 4040)
|
|
65
|
+
});
|
|
66
|
+
const shutdown = (signal) => {
|
|
67
|
+
process.stdout.write(`received ${signal}; stopping validator\n`);
|
|
68
|
+
handle.stop();
|
|
69
|
+
setTimeout(() => process.exit(0), 100);
|
|
70
|
+
};
|
|
71
|
+
process.once('SIGINT', () => shutdown('SIGINT'));
|
|
72
|
+
process.once('SIGTERM', () => shutdown('SIGTERM'));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Chain from '@leofcoin/chain/chain';
|
|
2
|
+
export type ValidatorOptions = {
|
|
3
|
+
network?: string;
|
|
4
|
+
networkVersion?: string;
|
|
5
|
+
stars?: string[];
|
|
6
|
+
root?: string;
|
|
7
|
+
password?: string;
|
|
8
|
+
intervalMinutes?: number;
|
|
9
|
+
registrationTimeoutMs?: number;
|
|
10
|
+
httpPort?: number | false;
|
|
11
|
+
wsPort?: number | false;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
logger?: Pick<Console, 'log' | 'warn' | 'error'>;
|
|
14
|
+
};
|
|
15
|
+
export type ValidatorHandle = {
|
|
16
|
+
account: string;
|
|
17
|
+
chain: Chain;
|
|
18
|
+
stop: () => void;
|
|
19
|
+
};
|
|
20
|
+
export declare const validateIntervalMinutes: (value: number) => number;
|
|
21
|
+
export declare const startValidator: (options?: ValidatorOptions) => Promise<ValidatorHandle>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import addresses from '@leofcoin/addresses';
|
|
2
|
+
import { signTransaction } from '@leofcoin/lib';
|
|
3
|
+
import networks from '@leofcoin/networks';
|
|
4
|
+
import launch from './index.js';
|
|
5
|
+
import '@leofcoin/chain/node';
|
|
6
|
+
import '@leofcoin/chain/chain';
|
|
7
|
+
import '@leofcoin/lib/node-config';
|
|
8
|
+
import '@leofcoin/endpoint-clients/ws';
|
|
9
|
+
import '@leofcoin/endpoint-clients/http';
|
|
10
|
+
|
|
11
|
+
const delay = (milliseconds, signal) => new Promise((resolve, reject) => {
|
|
12
|
+
if (signal?.aborted)
|
|
13
|
+
return reject(signal.reason ?? new Error('validator stopped'));
|
|
14
|
+
const onAbort = () => {
|
|
15
|
+
clearTimeout(timeout);
|
|
16
|
+
reject(signal?.reason ?? new Error('validator stopped'));
|
|
17
|
+
};
|
|
18
|
+
const timeout = setTimeout(() => {
|
|
19
|
+
signal?.removeEventListener('abort', onAbort);
|
|
20
|
+
resolve();
|
|
21
|
+
}, milliseconds);
|
|
22
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
23
|
+
});
|
|
24
|
+
const validateIntervalMinutes = (value) => {
|
|
25
|
+
if (!Number.isFinite(value) || value < 1)
|
|
26
|
+
throw new Error('validator interval must be at least one minute');
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
29
|
+
const startValidator = async (options = {}) => {
|
|
30
|
+
const network = options.network ?? 'leofcoin:peach';
|
|
31
|
+
const networkVersion = options.networkVersion ?? network.split(':')[1] ?? 'peach';
|
|
32
|
+
const stars = options.stars ?? networks.leofcoin.peach.stars;
|
|
33
|
+
const intervalMinutes = validateIntervalMinutes(options.intervalMinutes ?? 5);
|
|
34
|
+
const registrationTimeoutMs = options.registrationTimeoutMs ?? 5 * 60_000;
|
|
35
|
+
const logger = options.logger ?? console;
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
options.signal?.addEventListener('abort', () => controller.abort(options.signal?.reason), { once: true });
|
|
38
|
+
const launched = await launch({
|
|
39
|
+
mode: 'direct',
|
|
40
|
+
network,
|
|
41
|
+
networkVersion,
|
|
42
|
+
stars,
|
|
43
|
+
root: options.root,
|
|
44
|
+
http: options.httpPort === false ? [] : [{ port: options.httpPort ?? 8080 }],
|
|
45
|
+
ws: options.wsPort === false ? [] : [{ port: options.wsPort ?? 4040 }]
|
|
46
|
+
}, options.password);
|
|
47
|
+
if (!launched.chain)
|
|
48
|
+
throw new Error('validator launch did not create a direct chain runtime');
|
|
49
|
+
const chain = launched.chain;
|
|
50
|
+
const chainRuntime = chain;
|
|
51
|
+
const peernet = globalThis.peernet;
|
|
52
|
+
const account = peernet?.selectedAccount;
|
|
53
|
+
if (!account)
|
|
54
|
+
throw new Error('validator identity has no selected account');
|
|
55
|
+
logger.log(`validator account: ${account}`);
|
|
56
|
+
const isRegistered = () => chainRuntime.staticCall(addresses.validators, 'has', [account]);
|
|
57
|
+
if (!(await isRegistered())) {
|
|
58
|
+
const [balanceValue, minimumValue] = await Promise.all([
|
|
59
|
+
chainRuntime.balanceOf(account),
|
|
60
|
+
chainRuntime.staticCall(addresses.validators, 'minimumBalance')
|
|
61
|
+
]);
|
|
62
|
+
const balance = BigInt(balanceValue);
|
|
63
|
+
const minimumBalance = BigInt(minimumValue);
|
|
64
|
+
if (balance < minimumBalance) {
|
|
65
|
+
throw new Error(`validator account ${account} has balance ${balance}; registration requires at least ${minimumBalance}`);
|
|
66
|
+
}
|
|
67
|
+
const submitted = await chainRuntime.participate(account);
|
|
68
|
+
if (submitted)
|
|
69
|
+
throw new Error('validator registration state changed unexpectedly');
|
|
70
|
+
logger.log('validator registration submitted; waiting for finalization...');
|
|
71
|
+
const deadline = Date.now() + registrationTimeoutMs;
|
|
72
|
+
while (Date.now() < deadline && !(await isRegistered()))
|
|
73
|
+
await delay(5_000, controller.signal);
|
|
74
|
+
if (!(await isRegistered())) {
|
|
75
|
+
throw new Error('validator registration was not finalized; at least one existing validator must be online to produce it');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!(await chainRuntime.participate(account))) {
|
|
79
|
+
throw new Error('validator is registered but participation did not activate');
|
|
80
|
+
}
|
|
81
|
+
logger.log('validator participation active');
|
|
82
|
+
let sending = false;
|
|
83
|
+
const heartbeat = async () => {
|
|
84
|
+
if (controller.signal.aborted || sending)
|
|
85
|
+
return;
|
|
86
|
+
sending = true;
|
|
87
|
+
try {
|
|
88
|
+
const balance = BigInt(await chainRuntime.balanceOf(account));
|
|
89
|
+
if (balance <= 1n) {
|
|
90
|
+
logger.warn(`heartbeat skipped: balance ${balance} is too low`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const raw = await chainRuntime.createTransaction({
|
|
94
|
+
from: account,
|
|
95
|
+
to: chain.nativeToken,
|
|
96
|
+
method: 'transfer',
|
|
97
|
+
params: [account, account, 1n]
|
|
98
|
+
});
|
|
99
|
+
const signed = await signTransaction(raw, peernet.identity);
|
|
100
|
+
const pending = await chainRuntime.sendTransaction(signed);
|
|
101
|
+
const hash = await pending.wait;
|
|
102
|
+
logger.log(`heartbeat transaction finalized: ${hash}`);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
logger.error(`heartbeat transaction failed: ${error?.message ?? error}`);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
sending = false;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
await heartbeat();
|
|
112
|
+
const interval = setInterval(heartbeat, intervalMinutes * 60_000);
|
|
113
|
+
const stop = () => {
|
|
114
|
+
if (controller.signal.aborted)
|
|
115
|
+
return;
|
|
116
|
+
controller.abort(new Error('validator stopped'));
|
|
117
|
+
clearInterval(interval);
|
|
118
|
+
};
|
|
119
|
+
controller.signal.addEventListener('abort', () => clearInterval(interval), { once: true });
|
|
120
|
+
return { account, chain, stop };
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export { startValidator, validateIntervalMinutes };
|
package/package.json
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leofcoin/launch-chain",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"import": "./exports/index.js",
|
|
8
8
|
"types": "./exports/index.d.ts"
|
|
9
|
+
},
|
|
10
|
+
"./validator": {
|
|
11
|
+
"import": "./exports/validator.js",
|
|
12
|
+
"types": "./exports/validator.d.ts"
|
|
9
13
|
}
|
|
10
14
|
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"leofcoin-validator": "./exports/validator-cli.js"
|
|
17
|
+
},
|
|
11
18
|
"files": [
|
|
12
19
|
"exports"
|
|
13
20
|
],
|
|
14
21
|
"type": "module",
|
|
15
22
|
"scripts": {
|
|
16
23
|
"build": "rollup -c",
|
|
17
|
-
"test": "
|
|
24
|
+
"test": "node --test test/*.test.js"
|
|
18
25
|
},
|
|
19
26
|
"repository": {
|
|
20
27
|
"type": "git",
|
|
21
|
-
"url": "
|
|
28
|
+
"url": "https://github.com/leofcoin/monorepo.git",
|
|
29
|
+
"directory": "packages/launch-chain"
|
|
22
30
|
},
|
|
23
31
|
"keywords": [],
|
|
24
32
|
"author": "",
|
|
@@ -26,11 +34,13 @@
|
|
|
26
34
|
"bugs": {
|
|
27
35
|
"url": "https://github.com/leofcoin/launch-chain/issues"
|
|
28
36
|
},
|
|
29
|
-
"homepage": "https://github.com/leofcoin/launch-chain#readme",
|
|
37
|
+
"homepage": "https://github.com/leofcoin/monorepo/tree/main/packages/launch-chain#readme",
|
|
30
38
|
"dependencies": {
|
|
31
|
-
"@leofcoin/
|
|
32
|
-
"@leofcoin/
|
|
33
|
-
"@leofcoin/
|
|
39
|
+
"@leofcoin/addresses": "^1.0.62",
|
|
40
|
+
"@leofcoin/chain": "^1.10.12",
|
|
41
|
+
"@leofcoin/endpoint-clients": "^0.3.16",
|
|
42
|
+
"@leofcoin/endpoints": "^0.4.9",
|
|
43
|
+
"@leofcoin/lib": "^1.3.10",
|
|
34
44
|
"@leofcoin/networks": "^1.1.30"
|
|
35
45
|
},
|
|
36
46
|
"devDependencies": {
|
package/exports/src/index.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import Chain from '@leofcoin/chain/chain';
|
|
2
|
-
import WSClient from '@leofcoin/endpoint-clients/ws';
|
|
3
|
-
import HttpClient from '@leofcoin/endpoint-clients/http';
|
|
4
|
-
type launchMode = 'direct' | 'remote' | 'server';
|
|
5
|
-
type endpointReturns = {
|
|
6
|
-
http?: string[];
|
|
7
|
-
ws?: string[];
|
|
8
|
-
};
|
|
9
|
-
type clientReturns = {
|
|
10
|
-
http?: HttpClient[];
|
|
11
|
-
ws?: WSClient[];
|
|
12
|
-
};
|
|
13
|
-
type launchReturn = {
|
|
14
|
-
chain: Chain;
|
|
15
|
-
mode: launchMode;
|
|
16
|
-
endpoints: endpointReturns;
|
|
17
|
-
clients: clientReturns;
|
|
18
|
-
};
|
|
19
|
-
type endpointOptions = {
|
|
20
|
-
port: number;
|
|
21
|
-
url?: string;
|
|
22
|
-
};
|
|
23
|
-
type launchOptions = {
|
|
24
|
-
network?: string;
|
|
25
|
-
networkVersion?: string;
|
|
26
|
-
stars: string[];
|
|
27
|
-
forceRemote: boolean;
|
|
28
|
-
mode?: launchMode;
|
|
29
|
-
ws?: endpointOptions[] | undefined;
|
|
30
|
-
http?: endpointOptions[] | undefined;
|
|
31
|
-
};
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
* @param {object} options { ws: boolean || {url: string, port: number}, http: boolean || {url: string, port: number}, network}
|
|
35
|
-
* @returns '{ mode: string, endpoints: object, chain}'
|
|
36
|
-
*/
|
|
37
|
-
declare const launch: (options: launchOptions, password: string) => Promise<launchReturn>;
|
|
38
|
-
export { launch as default };
|