2
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
4
import {
5
+ ApiNamespace ,
5
6
Config as IronfishConfig ,
6
7
ConfigOptions ,
7
8
createRootLogger ,
@@ -10,14 +11,16 @@ import {
10
11
InternalOptions ,
11
12
IronfishSdk ,
12
13
Logger ,
14
+ RpcClient ,
13
15
RpcConnectionError ,
16
+ RpcMemoryClient ,
14
17
} from '@ironfish/sdk'
15
- import { Command , Config } from '@oclif/core'
16
- import { CLIError , ExitError } from '@oclif/core/lib/errors'
18
+ import { Command , Config , Errors , ux } from '@oclif/core'
17
19
import {
20
+ ConfigFlag ,
18
21
ConfigFlagKey ,
22
+ DataDirFlag ,
19
23
DataDirFlagKey ,
20
- NetworkIdFlagKey ,
21
24
RpcAuthFlagKey ,
22
25
RpcHttpHostFlagKey ,
23
26
RpcHttpPortFlagKey ,
@@ -42,15 +45,16 @@ import {
42
45
WalletNodeUseTcpFlagKey ,
43
46
} from './flags'
44
47
import { IronfishCliPKG } from './package'
48
+ import * as ui from './ui'
45
49
import { hasUserResponseError } from './utils'
46
50
import { WalletConfig , WalletConfigOptions } from './walletConfig'
51
+ import { walletNode } from './walletNode'
47
52
48
53
export type SIGNALS = 'SIGTERM' | 'SIGINT' | 'SIGUSR2'
49
54
50
55
export type FLAGS =
51
56
| typeof DataDirFlagKey
52
57
| typeof ConfigFlagKey
53
- | typeof NetworkIdFlagKey
54
58
| typeof RpcUseIpcFlagKey
55
59
| typeof RpcUseTcpFlagKey
56
60
| typeof RpcTcpHostFlagKey
@@ -75,8 +79,6 @@ export abstract class IronfishCommand extends Command {
75
79
// run() is called and it provides a lot of value
76
80
sdk ! : IronfishSdk
77
81
78
- walletConfig ! : WalletConfig
79
-
80
82
/**
81
83
* Use this logger instance for debug/error output.
82
84
* Actual command output should use `this.log` instead.
@@ -88,16 +90,26 @@ export abstract class IronfishCommand extends Command {
88
90
*/
89
91
closing = false
90
92
93
+ client : RpcClient | null = null
94
+
95
+ walletConfig ! : WalletConfig
96
+
97
+ public static baseFlags = {
98
+ [ VerboseFlagKey ] : VerboseFlag ,
99
+ [ ConfigFlagKey ] : ConfigFlag ,
100
+ [ DataDirFlagKey ] : DataDirFlag ,
101
+ }
102
+
91
103
constructor ( argv : string [ ] , config : Config ) {
92
104
super ( argv , config )
93
105
this . logger = createRootLogger ( ) . withTag ( this . ctor . id )
94
106
}
95
107
96
- abstract start ( ) : Promise < void > | void
108
+ abstract start ( ) : Promise < unknown > | void
97
109
98
- async run ( ) : Promise < void > {
110
+ async run ( ) : Promise < unknown > {
99
111
try {
100
- await this . start ( )
112
+ return await this . start ( )
101
113
} catch ( error : unknown ) {
102
114
if ( hasUserResponseError ( error ) ) {
103
115
this . log ( error . codeMessage )
@@ -107,9 +119,9 @@ export abstract class IronfishCommand extends Command {
107
119
}
108
120
109
121
this . exit ( 1 )
110
- } else if ( error instanceof ExitError ) {
122
+ } else if ( error instanceof Errors . ExitError ) {
111
123
throw error
112
- } else if ( error instanceof CLIError ) {
124
+ } else if ( error instanceof Errors . CLIError ) {
113
125
throw error
114
126
} else if ( error instanceof RpcConnectionError ) {
115
127
this . log ( `Cannot connect to your node, start your node first.` )
@@ -123,16 +135,15 @@ export abstract class IronfishCommand extends Command {
123
135
} else {
124
136
throw error
125
137
}
138
+ } finally {
139
+ this . client ?. close ( )
126
140
}
127
141
128
142
this . exit ( 0 )
129
143
}
130
144
131
145
async init ( ) : Promise < void > {
132
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
133
- const commandClass = this . constructor as any
134
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
135
- const { flags } = await this . parse ( commandClass )
146
+ const { flags } = await this . parse ( this . ctor )
136
147
137
148
// Get the flags from the flag object which is unknown
138
149
const dataDirFlag = getFlag ( flags , DataDirFlagKey )
@@ -160,11 +171,13 @@ export abstract class IronfishCommand extends Command {
160
171
const rpcTcpHostFlag = getFlag ( flags , RpcTcpHostFlagKey )
161
172
if ( typeof rpcTcpHostFlag === 'string' ) {
162
173
configOverrides . rpcTcpHost = rpcTcpHostFlag
174
+ configOverrides . enableRpcTcp = true
163
175
}
164
176
165
177
const rpcTcpPortFlag = getFlag ( flags , RpcTcpPortFlagKey )
166
178
if ( typeof rpcTcpPortFlag === 'number' ) {
167
179
configOverrides . rpcTcpPort = rpcTcpPortFlag
180
+ configOverrides . enableRpcTcp = true
168
181
}
169
182
170
183
const rpcConnectHttpFlag = getFlag ( flags , RpcUseHttpFlagKey )
@@ -178,11 +191,13 @@ export abstract class IronfishCommand extends Command {
178
191
const rpcHttpHostFlag = getFlag ( flags , RpcHttpHostFlagKey )
179
192
if ( typeof rpcHttpHostFlag === 'string' ) {
180
193
configOverrides . rpcHttpHost = rpcHttpHostFlag
194
+ configOverrides . enableRpcHttp = true
181
195
}
182
196
183
197
const rpcHttpPortFlag = getFlag ( flags , RpcHttpPortFlagKey )
184
198
if ( typeof rpcHttpPortFlag === 'number' ) {
185
199
configOverrides . rpcHttpPort = rpcHttpPortFlag
200
+ configOverrides . enableRpcHttp = true
186
201
}
187
202
188
203
const rpcTcpTlsFlag = getFlag ( flags , RpcTcpTlsFlagKey )
@@ -206,11 +221,6 @@ export abstract class IronfishCommand extends Command {
206
221
internalOverrides . rpcAuthToken = rpcAuthFlag
207
222
}
208
223
209
- const networkId = getFlag ( flags , NetworkIdFlagKey )
210
- if ( typeof networkId === 'number' ) {
211
- internalOverrides . networkId = networkId
212
- }
213
-
214
224
this . sdk = await IronfishSdk . init ( {
215
225
pkg : IronfishCliPKG ,
216
226
configOverrides : configOverrides ,
@@ -326,9 +336,111 @@ export abstract class IronfishCommand extends Command {
326
336
closeFromSignal ( signal : NodeJS . Signals ) : Promise < unknown > {
327
337
throw new Error ( `Not implemented closeFromSignal: ${ signal } ` )
328
338
}
339
+
340
+ // Override the built-in logJson method to implement our own colorizer that
341
+ // works with default terminal colors instead of requiring a theme to be
342
+ // configured.
343
+ logJson ( json : unknown ) : void {
344
+ ux . stdout ( ui . json ( json ) )
345
+ }
346
+
347
+ async connectRpcConfig (
348
+ forceLocal = false ,
349
+ forceRemote = false ,
350
+ ) : Promise < Pick < RpcClient , 'config' > > {
351
+ forceRemote = forceRemote || this . sdk . config . get ( 'enableRpcTcp' )
352
+
353
+ if ( ! forceLocal ) {
354
+ if ( forceRemote ) {
355
+ await this . sdk . client . connect ( )
356
+ return this . sdk . client
357
+ }
358
+
359
+ const connected = await this . sdk . client . tryConnect ( )
360
+ if ( connected ) {
361
+ return this . sdk . client
362
+ }
363
+ }
364
+
365
+ // This connection uses a wallet node since that is the most granular type
366
+ // of node available. This can be refactored in the future if needed.
367
+ const node = await walletNode ( {
368
+ connectNodeClient : false ,
369
+ walletConfig : this . walletConfig ,
370
+ sdk : this . sdk ,
371
+ } )
372
+
373
+ const clientMemory = new RpcMemoryClient (
374
+ this . sdk . logger ,
375
+ node . rpc . getRouter ( [ ApiNamespace . config ] ) ,
376
+ )
377
+ this . client = clientMemory
378
+ return clientMemory
379
+ }
380
+
381
+ async connectRpcWallet (
382
+ options : {
383
+ forceLocal ?: boolean
384
+ forceRemote ?: boolean
385
+ connectNodeClient ?: boolean
386
+ } = {
387
+ forceLocal : false ,
388
+ forceRemote : false ,
389
+ connectNodeClient : false ,
390
+ } ,
391
+ ) : Promise < RpcClientWallet > {
392
+ const forceRemote =
393
+ options . forceRemote || this . sdk . config . get ( 'enableRpcTcp' )
394
+
395
+ if ( ! options . forceLocal ) {
396
+ if ( forceRemote ) {
397
+ await this . sdk . client . connect ( )
398
+ this . client = this . sdk . client
399
+ return this . sdk . client
400
+ }
401
+
402
+ const connected = await this . sdk . client . tryConnect ( )
403
+ if ( connected ) {
404
+ this . client = this . sdk . client
405
+ return this . sdk . client
406
+ }
407
+ }
408
+
409
+ const namespaces = [
410
+ ApiNamespace . config ,
411
+ ApiNamespace . faucet ,
412
+ ApiNamespace . rpc ,
413
+ ApiNamespace . wallet ,
414
+ ApiNamespace . worker ,
415
+ ]
416
+
417
+ const node = await walletNode ( {
418
+ connectNodeClient : ! ! options . connectNodeClient ,
419
+ sdk : this . sdk ,
420
+ walletConfig : this . walletConfig ,
421
+ } )
422
+
423
+ const clientMemory = new RpcMemoryClient (
424
+ this . sdk . logger ,
425
+ node . rpc . getRouter ( namespaces ) ,
426
+ )
427
+
428
+ await node . waitForOpen ( )
429
+ if ( options . connectNodeClient ) {
430
+ await node . connectRpc ( )
431
+ }
432
+
433
+ this . client = clientMemory
434
+ return clientMemory
435
+ }
329
436
}
330
437
331
- function getFlag ( flags : unknown , flag : FLAGS ) : unknown | null {
438
+ export type RpcClientWallet = Pick <
439
+ RpcClient ,
440
+ 'config' | 'rpc' | 'wallet' | 'worker' | 'faucet'
441
+ >
442
+
443
+ function getFlag ( flags : unknown , flag : FLAGS ) : unknown {
332
444
return typeof flags === 'object' && flags !== null && flag in flags
333
445
? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
334
446
( flags as any ) [ flag ]
0 commit comments