1
1
const fs = require ( 'fs-extra' ) ;
2
2
const findPrefix = require ( 'find-npm-prefix' ) ;
3
+ const os = require ( 'os' ) ;
3
4
const { prompt } = require ( 'enquirer' ) ;
4
5
const { table, getBorderCharacters } = require ( 'table' ) ;
5
6
const { step } = require ( './helpers' ) ;
6
- const { green, red, bold, gray, reset } = require ( 'chalk' ) ;
7
+ const { green, red, bold, gray } = require ( 'chalk' ) ;
7
8
const Client = require ( 'mina-signer' ) ;
8
-
9
+ const { prompts } = require ( './prompts' ) ;
10
+ const { PrivateKey, PublicKey } = require ( 'snarkyjs' ) ;
11
+ const HOME_DIR = os . homedir ( ) ;
9
12
const log = console . log ;
10
13
11
14
/**
@@ -32,14 +35,34 @@ async function config() {
32
35
return ;
33
36
}
34
37
38
+ let isFeepayerCached = false ;
39
+ let defaultFeepayerAlias ;
40
+ let cachedFeepayerAliases ;
41
+ let defaultFeepayerAddress ;
42
+
43
+ try {
44
+ cachedFeepayerAliases = getCachedFeepayerAliases ( HOME_DIR ) ;
45
+ defaultFeepayerAlias = cachedFeepayerAliases [ 0 ] ;
46
+ defaultFeepayerAddress = getCachedFeepayerAddress (
47
+ HOME_DIR ,
48
+ defaultFeepayerAlias
49
+ ) ;
50
+
51
+ isFeepayerCached = true ;
52
+ } catch ( err ) {
53
+ if ( err . code !== 'ENOENT' ) {
54
+ console . error ( err ) ;
55
+ }
56
+ }
57
+
35
58
// Checks if developer has the legacy networks in config.json and renames it to deploy aliases.
36
59
if ( Object . prototype . hasOwnProperty . call ( config , 'networks' ) ) {
37
60
Object . assign ( config , { deployAliases : config . networks } ) ;
38
61
delete config . networks ;
39
62
}
40
63
41
64
// Build table of existing deploy aliases found in their config.json
42
- let tableData = [ [ bold ( 'Name' ) , bold ( 'Url ' ) , bold ( 'Smart Contract' ) ] ] ;
65
+ let tableData = [ [ bold ( 'Name' ) , bold ( 'URL ' ) , bold ( 'Smart Contract' ) ] ] ;
43
66
for ( const deployAliasName in config . deployAliases ) {
44
67
const { url, smartContract } = config . deployAliases [ deployAliasName ] ;
45
68
tableData . push ( [
@@ -74,84 +97,115 @@ async function config() {
74
97
const msg = '\n ' + table ( tableData , tableConfig ) . replaceAll ( '\n' , '\n ' ) ;
75
98
log ( msg ) ;
76
99
77
- console . log ( 'Add a new deploy alias:' ) ;
100
+ console . log ( 'Enter values to create a deploy alias:' ) ;
78
101
79
- // TODO: Later, show pre-configured list to choose from or let user
80
- // add a custom deploy alias.
102
+ const {
103
+ deployAliasPrompts,
104
+ initialFeepayerPrompts,
105
+ recoverFeepayerPrompts,
106
+ otherFeepayerPrompts,
107
+ feepayerAliasPrompt,
108
+ } = prompts ;
81
109
82
- function formatPrefixSymbol ( state ) {
83
- // Shows a cyan question mark when not submitted.
84
- // Shows a green check mark when submitted.
85
- // Shows a red "x" if ctrl+C is pressed.
110
+ const initialPromptResponse = await prompt ( [
111
+ ...deployAliasPrompts ( config ) ,
112
+ ...initialFeepayerPrompts (
113
+ defaultFeepayerAlias ,
114
+ defaultFeepayerAddress ,
115
+ isFeepayerCached
116
+ ) ,
117
+ ] ) ;
86
118
87
- // Can't override the validating prefix or styling unfortunately
88
- // https://github.com/enquirer/enquirer/blob/8d626c206733420637660ac7c2098d7de45e8590/lib/prompt.js#L125
89
- // if (state.validating) return ''; // use no symbol, instead of pointer
119
+ let recoverFeepayerResponse ;
120
+ let feepayerAliasResponse ;
121
+ let otherFeepayerResponse ;
90
122
91
- if ( ! state . submitted ) return state . symbols . question ;
92
- return state . cancelled ? red ( state . symbols . cross ) : state . symbols . check ;
123
+ if ( initialPromptResponse . feepayer === 'recover' ) {
124
+ recoverFeepayerResponse = await prompt (
125
+ recoverFeepayerPrompts ( cachedFeepayerAliases )
126
+ ) ;
93
127
}
94
128
95
- const response = await prompt ( [
96
- {
97
- type : 'input' ,
98
- name : 'deployAliasName' ,
99
- message : ( state ) => {
100
- const style = state . submitted && ! state . cancelled ? green : reset ;
101
- return style ( 'Choose a name (can be anything):' ) ;
102
- } ,
103
- prefix : formatPrefixSymbol ,
104
- validate : async ( val ) => {
105
- val = val . toLowerCase ( ) . trim ( ) . replace ( ' ' , '-' ) ;
106
- if ( ! val ) return red ( 'Name is required.' ) ;
107
- if ( Object . keys ( config . deployAliases ) . includes ( val ) ) {
108
- return red ( 'Name already exists.' ) ;
109
- }
110
- return true ;
111
- } ,
112
- result : ( val ) => val . toLowerCase ( ) . trim ( ) . replace ( ' ' , '-' ) ,
113
- } ,
114
- {
115
- type : 'input' ,
116
- name : 'url' ,
117
- message : ( state ) => {
118
- const style = state . submitted && ! state . cancelled ? green : reset ;
119
- return style ( 'Set the Mina GraphQL API URL to deploy to:' ) ;
120
- } ,
121
- prefix : formatPrefixSymbol ,
122
- validate : ( val ) => {
123
- if ( ! val ) return red ( 'Url is required.' ) ;
124
- return true ;
125
- } ,
126
- result : ( val ) => val . trim ( ) . replace ( / / , '' ) ,
127
- } ,
128
- {
129
- type : 'input' ,
130
- name : 'fee' ,
131
- message : ( state ) => {
132
- const style = state . submitted && ! state . cancelled ? green : reset ;
133
- return style ( 'Set transaction fee to use when deploying (in MINA):' ) ;
134
- } ,
135
- prefix : formatPrefixSymbol ,
136
- validate : ( val ) => {
137
- if ( ! val ) return red ( 'Fee is required.' ) ;
138
- if ( isNaN ( val ) ) return red ( 'Fee must be a number.' ) ;
139
- if ( val < 0 ) return red ( "Fee can't be negative." ) ;
140
- return true ;
141
- } ,
142
- result : ( val ) => val . trim ( ) . replace ( / / , '' ) ,
143
- } ,
144
- ] ) ;
129
+ if ( initialPromptResponse ?. feepayer === 'create' ) {
130
+ console . log ( 'inside create if' ) ;
131
+ feepayerAliasResponse = await prompt (
132
+ feepayerAliasPrompt ( cachedFeepayerAliases )
133
+ ) ;
134
+ }
135
+
136
+ if ( initialPromptResponse . feepayer === 'other' ) {
137
+ otherFeepayerResponse = await prompt (
138
+ otherFeepayerPrompts ( cachedFeepayerAliases )
139
+ ) ;
140
+
141
+ if ( otherFeepayerResponse . feepayer === 'recover' ) {
142
+ recoverFeepayerResponse = await prompt (
143
+ recoverFeepayerPrompts ( cachedFeepayerAliases )
144
+ ) ;
145
+ }
146
+
147
+ if ( otherFeepayerResponse . feepayer === 'create' ) {
148
+ feepayerAliasResponse = await prompt (
149
+ feepayerAliasPrompt ( cachedFeepayerAliases )
150
+ ) ;
151
+ }
152
+ }
153
+
154
+ const promptResponse = {
155
+ ...initialPromptResponse ,
156
+ ...recoverFeepayerResponse ,
157
+ ...otherFeepayerResponse ,
158
+ ...feepayerAliasResponse ,
159
+ } ;
145
160
146
161
// If user presses "ctrl + c" during interactive prompt, exit.
147
- const { deployAliasName, url, fee } = response ;
162
+ let {
163
+ deployAliasName,
164
+ url,
165
+ fee,
166
+ feepayer,
167
+ feepayerAlias,
168
+ feepayerKey,
169
+ alternateCachedFeepayerAlias,
170
+ } = promptResponse ;
171
+
148
172
if ( ! deployAliasName || ! url || ! fee ) return ;
149
173
150
- const keyPair = await step (
151
- `Create key pair at keys/${ deployAliasName } .json` ,
174
+ let feepayerKeyPair ;
175
+ switch ( feepayer ) {
176
+ case 'create' :
177
+ feepayerKeyPair = await createKeyPairStep ( HOME_DIR , feepayerAlias ) ;
178
+ break ;
179
+ case 'recover' :
180
+ feepayerKeyPair = await recoverKeyPairStep (
181
+ HOME_DIR ,
182
+ feepayerKey ,
183
+ feepayerAlias
184
+ ) ;
185
+ break ;
186
+ case 'defaultCache' :
187
+ feepayerAlias = defaultFeepayerAlias ;
188
+ feepayerKeyPair = await savedKeyPairStep (
189
+ HOME_DIR ,
190
+ defaultFeepayerAlias ,
191
+ defaultFeepayerAddress
192
+ ) ;
193
+ break ;
194
+ case 'alternateCachedFeepayer' :
195
+ feepayerAlias = alternateCachedFeepayerAlias ;
196
+ feepayerKeyPair = await savedKeyPairStep (
197
+ HOME_DIR ,
198
+ alternateCachedFeepayerAlias
199
+ ) ;
200
+ break ;
201
+ default :
202
+ break ;
203
+ }
204
+
205
+ await step (
206
+ `Create zkApp key pair at keys/${ deployAliasName } .json` ,
152
207
async ( ) => {
153
- const client = new Client ( { network : 'testnet' } ) ; // TODO: Make this configurable for mainnet and testnet.
154
- let keyPair = client . genKeys ( ) ;
208
+ const keyPair = createKeyPair ( 'testnet' ) ;
155
209
fs . outputJsonSync ( `${ DIR } /keys/${ deployAliasName } .json` , keyPair , {
156
210
spaces : 2 ,
157
211
} ) ;
@@ -160,9 +214,16 @@ async function config() {
160
214
) ;
161
215
162
216
await step ( `Add deploy alias to config.json` , async ( ) => {
217
+ if ( ! feepayerAlias ) {
218
+ // No fee payer alias, return early to prevent creating a deploy alias with invalid fee payer
219
+ log ( red ( `Invalid fee payer alias ${ feepayerAlias } " .` ) ) ;
220
+ process . exit ( 1 ) ;
221
+ }
163
222
config . deployAliases [ deployAliasName ] = {
164
223
url,
165
224
keyPath : `keys/${ deployAliasName } .json` ,
225
+ feepayerKeyPath : `${ HOME_DIR } /.cache/zkapp-cli/keys/${ feepayerAlias } .json` ,
226
+ feepayerAlias,
166
227
fee,
167
228
} ;
168
229
fs . outputJsonSync ( `${ DIR } /config.json` , config , { spaces : 2 } ) ;
@@ -176,18 +237,112 @@ async function config() {
176
237
`\nSuccess!\n` +
177
238
`\nNext steps:` +
178
239
`\n - If this is a testnet, request tMINA at:\n https://faucet.minaprotocol.com/?address=${ encodeURIComponent (
179
- keyPair . publicKey
240
+ feepayerKeyPair . publicKey
180
241
) } &?explorer=${ explorerName } ` +
181
242
`\n - To deploy, run: \`zk deploy ${ deployAliasName } \`` ;
182
243
183
244
log ( green ( str ) ) ;
184
245
}
185
246
247
+ // Creates a new feepayer key pair
248
+ async function createKeyPairStep ( directory , feepayerAlias ) {
249
+ if ( ! feepayerAlias ) {
250
+ // No fee payer alias, return early to prevent generating key pair with undefined alias
251
+ log ( red ( `Invalid fee payer alias ${ feepayerAlias } .` ) ) ;
252
+ return ;
253
+ }
254
+ return await step (
255
+ `Create fee payer key pair at ${ HOME_DIR } /.cache/zkapp-cli/keys/${ feepayerAlias } .json` ,
256
+ async ( ) => {
257
+ const keyPair = createKeyPair ( 'testnet' ) ;
258
+
259
+ fs . outputJsonSync (
260
+ `${ directory } /.cache/zkapp-cli/keys/${ feepayerAlias } .json` ,
261
+ keyPair ,
262
+ {
263
+ spaces : 2 ,
264
+ }
265
+ ) ;
266
+ return keyPair ;
267
+ }
268
+ ) ;
269
+ }
270
+
271
+ async function recoverKeyPairStep ( directory , feepayerKey , feepayerAlias ) {
272
+ return await step (
273
+ `Recover fee payer keypair from ${ feepayerKey } and add to ${ HOME_DIR } /.cache/zkapp-cli/keys/${ feepayerAlias } .json` ,
274
+ async ( ) => {
275
+ const feepayorPrivateKey = PrivateKey . fromBase58 ( feepayerKey ) ;
276
+ const feepayerAddress = feepayorPrivateKey . toPublicKey ( ) ;
277
+
278
+ const keyPair = {
279
+ privateKey : feepayerKey ,
280
+ publicKey : PublicKey . toBase58 ( feepayerAddress ) ,
281
+ } ;
282
+
283
+ fs . outputJsonSync (
284
+ `${ directory } /.cache/zkapp-cli/keys/${ feepayerAlias } .json` ,
285
+ keyPair ,
286
+ {
287
+ spaces : 2 ,
288
+ }
289
+ ) ;
290
+ return keyPair ;
291
+ }
292
+ ) ;
293
+ }
294
+ // Returns a cached keypair from a given feepayer alias
295
+ async function savedKeyPairStep ( directory , feepayerAlias , address ) {
296
+ if ( ! feepayerAlias ) {
297
+ // No fee payer alias, return early to prevent generating key pair with undefined alias
298
+ log ( red ( `Invalid fee payer alias: ${ feepayerAlias } .` ) ) ;
299
+ process . exit ( 1 ) ;
300
+ }
301
+ const keyPair = fs . readJSONSync (
302
+ `${ directory } /.cache/zkapp-cli/keys/${ feepayerAlias } .json`
303
+ ) ;
304
+
305
+ if ( ! address ) address = keyPair . publicKey ;
306
+
307
+ return await step (
308
+ `Use stored fee payer ${ feepayerAlias } (public key: ${ address } ) ` ,
309
+
310
+ async ( ) => {
311
+ return keyPair ;
312
+ }
313
+ ) ;
314
+ }
315
+
316
+ // Check if feepayer alias/aliases are stored on users machine and returns an array of them.
317
+ function getCachedFeepayerAliases ( directory ) {
318
+ let aliases = fs . readdirSync ( `${ directory } /.cache/zkapp-cli/keys/` ) ;
319
+
320
+ aliases = aliases
321
+ . filter ( ( fileName ) => fileName . includes ( 'json' ) )
322
+ . map ( ( name ) => name . slice ( 0 , - 5 ) ) ;
323
+
324
+ return aliases ;
325
+ }
326
+
327
+ function getCachedFeepayerAddress ( directory , feePayorAlias ) {
328
+ const address = fs . readJSONSync (
329
+ `${ directory } /.cache/zkapp-cli/keys/${ feePayorAlias } .json`
330
+ ) . publicKey ;
331
+
332
+ return address ;
333
+ }
334
+
335
+ function createKeyPair ( network ) {
336
+ const client = new Client ( { network } ) ;
337
+ return client . genKeys ( ) ;
338
+ }
339
+
186
340
function getExplorerName ( graphQLUrl ) {
187
341
return new URL ( graphQLUrl ) . hostname
188
342
. split ( '.' )
189
343
. filter ( ( item ) => item === 'minascan' || item === 'minaexplorer' ) ?. [ 0 ] ;
190
344
}
345
+
191
346
module . exports = {
192
347
config,
193
348
} ;
0 commit comments