@sundaeswap/sprinkles 0.3.0 → 0.5.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/dist/cjs/Sprinkle/__tests__/encryption.test.js +20 -8
- package/dist/cjs/Sprinkle/__tests__/encryption.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/enhancements.test.js +41 -16
- package/dist/cjs/Sprinkle/__tests__/enhancements.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +85 -38
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/settings-persistence.test.js +120 -0
- package/dist/cjs/Sprinkle/__tests__/settings-persistence.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/show-menu.test.js +93 -7
- package/dist/cjs/Sprinkle/__tests__/show-menu.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js +21 -0
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
- package/dist/cjs/Sprinkle/encryption.js +131 -0
- package/dist/cjs/Sprinkle/encryption.js.map +1 -0
- package/dist/cjs/Sprinkle/index.js +318 -352
- package/dist/cjs/Sprinkle/index.js.map +1 -1
- package/dist/cjs/Sprinkle/prompts.js +393 -0
- package/dist/cjs/Sprinkle/prompts.js.map +1 -0
- package/dist/cjs/Sprinkle/schemas.js +97 -0
- package/dist/cjs/Sprinkle/schemas.js.map +1 -0
- package/dist/cjs/Sprinkle/tx-dialog.js +101 -0
- package/dist/cjs/Sprinkle/tx-dialog.js.map +1 -0
- package/dist/cjs/Sprinkle/type-guards.js +42 -0
- package/dist/cjs/Sprinkle/type-guards.js.map +1 -0
- package/dist/cjs/Sprinkle/types.js +49 -0
- package/dist/cjs/Sprinkle/types.js.map +1 -0
- package/dist/cjs/Sprinkle/wallet.js +98 -0
- package/dist/cjs/Sprinkle/wallet.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/encryption.test.js +20 -8
- package/dist/esm/Sprinkle/__tests__/encryption.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/enhancements.test.js +41 -16
- package/dist/esm/Sprinkle/__tests__/enhancements.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +85 -38
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/settings-persistence.test.js +120 -0
- package/dist/esm/Sprinkle/__tests__/settings-persistence.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/show-menu.test.js +94 -8
- package/dist/esm/Sprinkle/__tests__/show-menu.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js +21 -0
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
- package/dist/esm/Sprinkle/encryption.js +117 -0
- package/dist/esm/Sprinkle/encryption.js.map +1 -0
- package/dist/esm/Sprinkle/index.js +172 -337
- package/dist/esm/Sprinkle/index.js.map +1 -1
- package/dist/esm/Sprinkle/prompts.js +385 -0
- package/dist/esm/Sprinkle/prompts.js.map +1 -0
- package/dist/esm/Sprinkle/schemas.js +91 -0
- package/dist/esm/Sprinkle/schemas.js.map +1 -0
- package/dist/esm/Sprinkle/tx-dialog.js +90 -0
- package/dist/esm/Sprinkle/tx-dialog.js.map +1 -0
- package/dist/esm/Sprinkle/type-guards.js +24 -0
- package/dist/esm/Sprinkle/type-guards.js.map +1 -0
- package/dist/esm/Sprinkle/types.js +42 -0
- package/dist/esm/Sprinkle/types.js.map +1 -0
- package/dist/esm/Sprinkle/wallet.js +90 -0
- package/dist/esm/Sprinkle/wallet.js.map +1 -0
- package/dist/types/Sprinkle/encryption.d.ts +43 -0
- package/dist/types/Sprinkle/encryption.d.ts.map +1 -0
- package/dist/types/Sprinkle/index.d.ts +13 -174
- package/dist/types/Sprinkle/index.d.ts.map +1 -1
- package/dist/types/Sprinkle/prompts.d.ts +94 -0
- package/dist/types/Sprinkle/prompts.d.ts.map +1 -0
- package/dist/types/Sprinkle/schemas.d.ts +125 -0
- package/dist/types/Sprinkle/schemas.d.ts.map +1 -0
- package/dist/types/Sprinkle/tx-dialog.d.ts +37 -0
- package/dist/types/Sprinkle/tx-dialog.d.ts.map +1 -0
- package/dist/types/Sprinkle/type-guards.d.ts +22 -0
- package/dist/types/Sprinkle/type-guards.d.ts.map +1 -0
- package/dist/types/Sprinkle/types.d.ts +62 -0
- package/dist/types/Sprinkle/types.d.ts.map +1 -0
- package/dist/types/Sprinkle/wallet.d.ts +27 -0
- package/dist/types/Sprinkle/wallet.d.ts.map +1 -0
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Sprinkle/__tests__/encryption.test.ts +21 -8
- package/src/Sprinkle/__tests__/enhancements.test.ts +41 -15
- package/src/Sprinkle/__tests__/fill-in-struct.test.ts +104 -38
- package/src/Sprinkle/__tests__/settings-persistence.test.ts +108 -0
- package/src/Sprinkle/__tests__/show-menu.test.ts +96 -8
- package/src/Sprinkle/__tests__/tx-dialog.test.ts +21 -0
- package/src/Sprinkle/encryption.ts +130 -0
- package/src/Sprinkle/index.ts +265 -478
- package/src/Sprinkle/prompts.ts +481 -0
- package/src/Sprinkle/schemas.ts +111 -0
- package/src/Sprinkle/tx-dialog.ts +100 -0
- package/src/Sprinkle/type-guards.ts +51 -0
- package/src/Sprinkle/types.ts +73 -0
- package/src/Sprinkle/wallet.ts +133 -0
|
@@ -1,106 +1,36 @@
|
|
|
1
1
|
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
2
2
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
3
3
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { Kind, Type, OptionalKind } from "@sinclair/typebox";
|
|
4
|
+
import { Core, HotWallet } from "@blaze-cardano/sdk";
|
|
5
|
+
import { CborSet, VkeyWitness, TxCBOR } from "@blaze-cardano/core";
|
|
6
|
+
import { selectCancellable, inputCancellable, passwordCancellable, confirmCancellable, searchCancellable } from "./prompts.js";
|
|
7
|
+
import { OptionalKind } from "@sinclair/typebox";
|
|
9
8
|
import * as fs from "fs";
|
|
10
9
|
import * as path from "path";
|
|
11
10
|
export * from "@sinclair/typebox";
|
|
12
|
-
const isOptional = t => t[OptionalKind] === "Optional";
|
|
13
|
-
const isImport = t => t[Kind] === "Import";
|
|
14
|
-
const isArray = t => t[Kind] === "Array";
|
|
15
|
-
const isBigInt = t => t[Kind] === "BigInt";
|
|
16
|
-
// const isBoolean = (t: TSchema): t is TBoolean => t[Kind] === "Boolean";
|
|
17
|
-
const isLiteral = t => t[Kind] === "Literal";
|
|
18
|
-
// const isNumber = (t: TSchema): t is TNumber => t[Kind] === "Number";
|
|
19
|
-
const isObject = t => t[Kind] === "Object";
|
|
20
|
-
// const isRecord = (t: TSchema): t is TRecord => t[Kind] === "Record";
|
|
21
|
-
const isRef = t => t[Kind] === "Ref";
|
|
22
|
-
const isString = t => t[Kind] === "String";
|
|
23
|
-
const isThis = t => t[Kind] === "This";
|
|
24
|
-
const isTuple = t => t[Kind] === "Tuple";
|
|
25
|
-
const isUnion = t => t[Kind] === "Union";
|
|
26
|
-
// const isAny = (t: TSchema): t is TAny => t[Kind] === "Any";
|
|
27
11
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
AtLeast: Type.Object({
|
|
51
|
-
required: Type.BigInt(),
|
|
52
|
-
scripts: Type.Array(Type.Ref("MultisigScript"))
|
|
53
|
-
}, {
|
|
54
|
-
ctor: 3n
|
|
55
|
-
})
|
|
56
|
-
}), Type.Object({
|
|
57
|
-
Before: Type.Object({
|
|
58
|
-
time: Type.BigInt()
|
|
59
|
-
}, {
|
|
60
|
-
ctor: 4n
|
|
61
|
-
})
|
|
62
|
-
}), Type.Object({
|
|
63
|
-
After: Type.Object({
|
|
64
|
-
time: Type.BigInt()
|
|
65
|
-
}, {
|
|
66
|
-
ctor: 5n
|
|
67
|
-
})
|
|
68
|
-
}), Type.Object({
|
|
69
|
-
Script: Type.Object({
|
|
70
|
-
script_hash: Type.String()
|
|
71
|
-
}, {
|
|
72
|
-
ctor: 6n
|
|
73
|
-
})
|
|
74
|
-
})])
|
|
75
|
-
});
|
|
76
|
-
export const MultisigScript = MultisigScriptModule.Import("MultisigScript");
|
|
77
|
-
export const ProviderSettingsSchema = Type.Union([Type.Object({
|
|
78
|
-
type: Type.Literal("blockfrost"),
|
|
79
|
-
projectId: Type.String({
|
|
80
|
-
minLength: 1,
|
|
81
|
-
title: "Blockfrost Project ID"
|
|
82
|
-
})
|
|
83
|
-
}), Type.Object({
|
|
84
|
-
type: Type.Literal("maestro"),
|
|
85
|
-
apiKey: Type.String({
|
|
86
|
-
minLength: 1,
|
|
87
|
-
title: "Maestro API Key"
|
|
88
|
-
})
|
|
89
|
-
})]);
|
|
90
|
-
export const WalletSettingsSchema = Type.Union([Type.Object({
|
|
91
|
-
type: Type.Literal("hot"),
|
|
92
|
-
privateKey: Type.String({
|
|
93
|
-
minLength: 1,
|
|
94
|
-
title: "Hot Wallet Private Key",
|
|
95
|
-
sensitive: true
|
|
96
|
-
})
|
|
97
|
-
}), Type.Object({
|
|
98
|
-
type: Type.Literal("cold"),
|
|
99
|
-
address: Type.String({
|
|
100
|
-
minLength: 1,
|
|
101
|
-
title: "Cold Wallet Address"
|
|
102
|
-
})
|
|
103
|
-
})]);
|
|
12
|
+
// Re-export types from types.ts
|
|
13
|
+
|
|
14
|
+
export { UserCancelledError } from "./types.js";
|
|
15
|
+
import { UserCancelledError } from "./types.js";
|
|
16
|
+
|
|
17
|
+
// Re-export schemas from schemas.ts
|
|
18
|
+
export { NetworkSchema, MultisigScriptModule, MultisigScript, ProviderSettingsSchema, WalletSettingsSchema } from "./schemas.js";
|
|
19
|
+
// Import and re-export type guards
|
|
20
|
+
import { isOptional, isImport, isArray, isBigInt, isLiteral, isObject, isRef, isString, isThis, isTuple, isUnion, isSensitive } from "./type-guards.js";
|
|
21
|
+
export { isOptional, isImport, isArray, isBigInt, isLiteral, isObject, isRef, isString, isThis, isTuple, isUnion, isSensitive } from "./type-guards.js";
|
|
22
|
+
|
|
23
|
+
// Import schemas for use in this file
|
|
24
|
+
|
|
25
|
+
// Import and re-export wallet utilities
|
|
26
|
+
import { GetProvider as GetProviderFn, GetWallet as GetWalletFn, GetBlaze as GetBlazeFn, generateWalletFromMnemonic } from "./wallet.js";
|
|
27
|
+
export { GetProvider, GetWallet, GetBlaze } from "./wallet.js";
|
|
28
|
+
|
|
29
|
+
// Import encryption utilities
|
|
30
|
+
import { bigIntReplacer, bigIntReviver, encryptSensitiveFields, decryptSensitiveFields, maskSensitiveFields } from "./encryption.js";
|
|
31
|
+
|
|
32
|
+
// Import tx-dialog utilities
|
|
33
|
+
import { countSignatures, getRequiredSigners, getTxBodyHash, formatHash, mergeSignatures } from "./tx-dialog.js";
|
|
104
34
|
export class Sprinkle {
|
|
105
35
|
constructor(type, storagePath, options) {
|
|
106
36
|
_defineProperty(this, "storagePath", void 0);
|
|
@@ -119,6 +49,16 @@ export class Sprinkle {
|
|
|
119
49
|
this.options = options ?? {};
|
|
120
50
|
}
|
|
121
51
|
|
|
52
|
+
// --- Current Profile Accessor ---
|
|
53
|
+
|
|
54
|
+
get currentProfile() {
|
|
55
|
+
if (!this.profileId) return null;
|
|
56
|
+
return {
|
|
57
|
+
id: this.profileId,
|
|
58
|
+
...this.profileMeta
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
122
62
|
// --- Profile path helpers ---
|
|
123
63
|
|
|
124
64
|
static sanitizeProfileId(name) {
|
|
@@ -197,32 +137,42 @@ export class Sprinkle {
|
|
|
197
137
|
meta: this.profileMeta,
|
|
198
138
|
settings: settingsToSave,
|
|
199
139
|
defaults: this.defaults
|
|
200
|
-
},
|
|
140
|
+
}, bigIntReplacer, 2);
|
|
201
141
|
fs.writeFileSync(filePath, jsonContent, "utf-8");
|
|
202
142
|
}
|
|
203
143
|
async promptProfileMeta(defaultName, defaultDescription) {
|
|
204
|
-
const name = await
|
|
144
|
+
const name = await inputCancellable({
|
|
205
145
|
message: "Profile name:",
|
|
206
146
|
default: defaultName,
|
|
207
147
|
validate: v => v.trim().length > 0 ? true : "Name cannot be empty"
|
|
208
148
|
});
|
|
209
|
-
|
|
149
|
+
if (name === null) return null; // User cancelled
|
|
150
|
+
const description = await inputCancellable({
|
|
210
151
|
message: "Profile description (optional):",
|
|
211
152
|
default: defaultDescription ?? ""
|
|
212
153
|
});
|
|
154
|
+
if (description === null) return null; // User cancelled
|
|
213
155
|
return {
|
|
214
156
|
name,
|
|
215
157
|
description: description || undefined
|
|
216
158
|
};
|
|
217
159
|
}
|
|
218
160
|
async createProfile() {
|
|
161
|
+
const result = await this.promptProfileMeta();
|
|
162
|
+
if (result === null) return; // User cancelled
|
|
219
163
|
const {
|
|
220
164
|
name,
|
|
221
165
|
description
|
|
222
|
-
} =
|
|
166
|
+
} = result;
|
|
223
167
|
const profilesDir = Sprinkle.profilesDir(this.storagePath);
|
|
224
168
|
const id = Sprinkle.findAvailableId(profilesDir, Sprinkle.sanitizeProfileId(name));
|
|
225
169
|
const now = new Date().toISOString();
|
|
170
|
+
|
|
171
|
+
// Snapshot current state in case we need to restore on cancellation
|
|
172
|
+
const prevProfileId = this.profileId;
|
|
173
|
+
const prevProfileMeta = this.profileMeta;
|
|
174
|
+
const prevDefaults = this.defaults;
|
|
175
|
+
const prevSettings = this.settings;
|
|
226
176
|
this.profileId = id;
|
|
227
177
|
this.profileMeta = {
|
|
228
178
|
name,
|
|
@@ -231,7 +181,19 @@ export class Sprinkle {
|
|
|
231
181
|
updatedAt: now
|
|
232
182
|
};
|
|
233
183
|
this.defaults = {};
|
|
234
|
-
|
|
184
|
+
try {
|
|
185
|
+
this.settings = await this.FillInStruct(this.type);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
// Restore previous state on cancellation
|
|
188
|
+
if (e instanceof UserCancelledError) {
|
|
189
|
+
this.profileId = prevProfileId;
|
|
190
|
+
this.profileMeta = prevProfileMeta;
|
|
191
|
+
this.defaults = prevDefaults;
|
|
192
|
+
this.settings = prevSettings;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw e;
|
|
196
|
+
}
|
|
235
197
|
this.saveProfile();
|
|
236
198
|
fs.writeFileSync(Sprinkle.activeProfilePath(this.storagePath), id, "utf-8");
|
|
237
199
|
}
|
|
@@ -256,7 +218,7 @@ export class Sprinkle {
|
|
|
256
218
|
fs.mkdirSync(profilesDir, {
|
|
257
219
|
recursive: true
|
|
258
220
|
});
|
|
259
|
-
fs.writeFileSync(Sprinkle.profilePath(this.storagePath, "default"), JSON.stringify(profileData,
|
|
221
|
+
fs.writeFileSync(Sprinkle.profilePath(this.storagePath, "default"), JSON.stringify(profileData, bigIntReplacer, 2), "utf-8");
|
|
260
222
|
fs.writeFileSync(Sprinkle.activeProfilePath(this.storagePath), "default", "utf-8");
|
|
261
223
|
// Backup legacy file
|
|
262
224
|
fs.renameSync(legacyPath, `${legacyPath}.bak`);
|
|
@@ -293,23 +255,26 @@ export class Sprinkle {
|
|
|
293
255
|
}
|
|
294
256
|
async selectProfile(profiles) {
|
|
295
257
|
const available = profiles ?? this.scanProfiles();
|
|
296
|
-
const selection = await
|
|
258
|
+
const selection = await selectCancellable({
|
|
297
259
|
message: "Select a profile:",
|
|
298
260
|
choices: available.map(p => ({
|
|
299
261
|
name: p.meta.description ? `${p.meta.name} - ${p.meta.description}` : p.meta.name,
|
|
300
262
|
value: p.id
|
|
301
263
|
}))
|
|
302
264
|
});
|
|
265
|
+
if (selection === null) return; // User cancelled
|
|
303
266
|
await this.loadProfile(selection);
|
|
304
267
|
}
|
|
305
268
|
|
|
306
269
|
// --- Profile management (CRUD) ---
|
|
307
270
|
|
|
308
271
|
async duplicateProfile() {
|
|
272
|
+
const result = await this.promptProfileMeta(`${this.profileMeta.name} (copy)`, this.profileMeta.description);
|
|
273
|
+
if (result === null) return; // User cancelled
|
|
309
274
|
const {
|
|
310
275
|
name,
|
|
311
276
|
description
|
|
312
|
-
} =
|
|
277
|
+
} = result;
|
|
313
278
|
const profilesDir = Sprinkle.profilesDir(this.storagePath);
|
|
314
279
|
const id = Sprinkle.findAvailableId(profilesDir, Sprinkle.sanitizeProfileId(name));
|
|
315
280
|
const now = new Date().toISOString();
|
|
@@ -324,15 +289,17 @@ export class Sprinkle {
|
|
|
324
289
|
},
|
|
325
290
|
settings: settingsToSave,
|
|
326
291
|
defaults: this.defaults
|
|
327
|
-
},
|
|
292
|
+
}, bigIntReplacer, 2);
|
|
328
293
|
fs.writeFileSync(path.join(profilesDir, `${id}.json`), jsonContent, "utf-8");
|
|
329
294
|
console.log(`Profile "${name}" created as a copy.`);
|
|
330
295
|
}
|
|
331
296
|
async renameProfile() {
|
|
297
|
+
const result = await this.promptProfileMeta(this.profileMeta.name, this.profileMeta.description);
|
|
298
|
+
if (result === null) return; // User cancelled
|
|
332
299
|
const {
|
|
333
300
|
name,
|
|
334
301
|
description
|
|
335
|
-
} =
|
|
302
|
+
} = result;
|
|
336
303
|
const newId = Sprinkle.sanitizeProfileId(name);
|
|
337
304
|
const oldId = this.profileId;
|
|
338
305
|
this.profileMeta.name = name;
|
|
@@ -360,25 +327,26 @@ export class Sprinkle {
|
|
|
360
327
|
console.log("Cannot delete the only profile.");
|
|
361
328
|
return;
|
|
362
329
|
}
|
|
363
|
-
const toDelete = await
|
|
330
|
+
const toDelete = await selectCancellable({
|
|
364
331
|
message: "Select a profile to delete:",
|
|
365
|
-
choices:
|
|
332
|
+
choices: others.map(p => ({
|
|
366
333
|
name: p.meta.description ? `${p.meta.name} - ${p.meta.description}` : p.meta.name,
|
|
367
334
|
value: p.id
|
|
368
|
-
}))
|
|
369
|
-
name: "Cancel",
|
|
370
|
-
value: "__cancel__"
|
|
371
|
-
}]
|
|
335
|
+
}))
|
|
372
336
|
});
|
|
373
|
-
if (toDelete ===
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
337
|
+
if (toDelete === null) return; // User cancelled
|
|
338
|
+
|
|
339
|
+
const profileId = toDelete;
|
|
340
|
+
const profileToDelete = others.find(p => p.id === profileId);
|
|
341
|
+
const confirmed = await confirmCancellable({
|
|
342
|
+
message: `Delete profile "${profileToDelete?.meta.name ?? profileId}"? This cannot be undone.`,
|
|
377
343
|
default: false
|
|
378
344
|
});
|
|
345
|
+
if (confirmed === null) return; // User cancelled
|
|
346
|
+
|
|
379
347
|
if (confirmed) {
|
|
380
|
-
fs.unlinkSync(Sprinkle.profilePath(this.storagePath,
|
|
381
|
-
console.log(`Profile "${profileToDelete?.meta.name ??
|
|
348
|
+
fs.unlinkSync(Sprinkle.profilePath(this.storagePath, profileId));
|
|
349
|
+
console.log(`Profile "${profileToDelete?.meta.name ?? profileId}" deleted.`);
|
|
382
350
|
}
|
|
383
351
|
}
|
|
384
352
|
|
|
@@ -419,14 +387,24 @@ export class Sprinkle {
|
|
|
419
387
|
value: -1
|
|
420
388
|
});
|
|
421
389
|
}
|
|
422
|
-
const
|
|
390
|
+
const selectionResult = await selectCancellable({
|
|
423
391
|
message: "Select an option:",
|
|
424
392
|
choices: choices
|
|
425
393
|
});
|
|
394
|
+
// Handle escape (null) as Back
|
|
395
|
+
if (selectionResult === null) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const selection = selectionResult;
|
|
426
399
|
if (selection === -5) {
|
|
427
400
|
const settingsMenu = {
|
|
428
401
|
title: "Settings & Profiles",
|
|
429
402
|
items: [{
|
|
403
|
+
title: "View settings",
|
|
404
|
+
action: async () => {
|
|
405
|
+
console.log(JSON.stringify(this.getDisplaySettings(), bigIntReplacer, 2));
|
|
406
|
+
}
|
|
407
|
+
}, {
|
|
430
408
|
title: "Edit settings",
|
|
431
409
|
action: async () => {
|
|
432
410
|
this.settings = await this.EditStruct(this.type, this.settings);
|
|
@@ -494,40 +472,13 @@ export class Sprinkle {
|
|
|
494
472
|
return sprinkle;
|
|
495
473
|
}
|
|
496
474
|
static async GetProvider(network, settings) {
|
|
497
|
-
|
|
498
|
-
case "blockfrost":
|
|
499
|
-
return new Blockfrost({
|
|
500
|
-
network: `cardano-${network}`,
|
|
501
|
-
projectId: settings.projectId
|
|
502
|
-
});
|
|
503
|
-
case "maestro":
|
|
504
|
-
// Dynamic import - Maestro may or may not be exported depending on @blaze-cardano/query version
|
|
505
|
-
const queryModule = await import("@blaze-cardano/query");
|
|
506
|
-
if (!queryModule.Maestro) {
|
|
507
|
-
throw new Error("Maestro is not available in the installed version of @blaze-cardano/query. Please install a version that includes Maestro support.");
|
|
508
|
-
}
|
|
509
|
-
return new queryModule.Maestro({
|
|
510
|
-
network: network,
|
|
511
|
-
apiKey: settings.apiKey
|
|
512
|
-
});
|
|
513
|
-
default:
|
|
514
|
-
throw new Error("Invalid provider type");
|
|
515
|
-
}
|
|
475
|
+
return GetProviderFn(network, settings);
|
|
516
476
|
}
|
|
517
477
|
static async GetWallet(settings, provider) {
|
|
518
|
-
|
|
519
|
-
case "hot":
|
|
520
|
-
return HotWallet.fromMasterkey(Core.Bip32PrivateKeyHex(settings.privateKey), provider, provider.network);
|
|
521
|
-
case "cold":
|
|
522
|
-
return new ColdWallet(Core.Address.fromBech32(settings.address), provider.network, provider);
|
|
523
|
-
default:
|
|
524
|
-
throw new Error("Invalid wallet type");
|
|
525
|
-
}
|
|
478
|
+
return GetWalletFn(settings, provider);
|
|
526
479
|
}
|
|
527
480
|
static async GetBlaze(network, providerSettings, walletSettings) {
|
|
528
|
-
|
|
529
|
-
const wallet = await Sprinkle.GetWallet(walletSettings, provider);
|
|
530
|
-
return Blaze.from(provider, wallet);
|
|
481
|
+
return GetBlazeFn(network, providerSettings, walletSettings);
|
|
531
482
|
}
|
|
532
483
|
|
|
533
484
|
/**
|
|
@@ -536,30 +487,10 @@ export class Sprinkle {
|
|
|
536
487
|
* @returns The Bip32PrivateKey hex string for storage
|
|
537
488
|
*/
|
|
538
489
|
static async generateWalletFromMnemonic() {
|
|
539
|
-
|
|
540
|
-
const words = mnemonic.split(" ");
|
|
541
|
-
console.log("\n=== NEW WALLET GENERATED ===\n");
|
|
542
|
-
console.log("IMPORTANT: Save these 24 words in a secure location.");
|
|
543
|
-
console.log("This is the ONLY way to recover your wallet.\n");
|
|
544
|
-
|
|
545
|
-
// Display in 4 columns
|
|
546
|
-
for (let i = 0; i < 6; i++) {
|
|
547
|
-
console.log(`${(i + 1).toString().padStart(2)}. ${words[i].padEnd(12)} ` + `${(i + 7).toString().padStart(2)}. ${words[i + 6].padEnd(12)} ` + `${(i + 13).toString().padStart(2)}. ${words[i + 12].padEnd(12)} ` + `${(i + 19).toString().padStart(2)}. ${words[i + 18]}`);
|
|
548
|
-
}
|
|
549
|
-
console.log("");
|
|
550
|
-
const confirmed = await confirm({
|
|
551
|
-
message: "Have you saved your recovery phrase?",
|
|
552
|
-
default: false
|
|
553
|
-
});
|
|
554
|
-
if (!confirmed) {
|
|
555
|
-
throw new Error("Wallet generation cancelled - recovery phrase not saved");
|
|
556
|
-
}
|
|
557
|
-
const entropy = Core.mnemonicToEntropy(mnemonic, wordlist);
|
|
558
|
-
const masterKey = Core.Bip32PrivateKey.fromBip39Entropy(Buffer.from(entropy), "");
|
|
559
|
-
return masterKey.hex();
|
|
490
|
+
return generateWalletFromMnemonic();
|
|
560
491
|
}
|
|
561
492
|
static async SearchSelect(opts) {
|
|
562
|
-
return
|
|
493
|
+
return searchCancellable(opts);
|
|
563
494
|
}
|
|
564
495
|
static SettingsPath(storagePath) {
|
|
565
496
|
return `${storagePath}${path.sep}settings.json`;
|
|
@@ -583,155 +514,21 @@ export class Sprinkle {
|
|
|
583
514
|
}
|
|
584
515
|
}
|
|
585
516
|
static bigIntReviver(key, value) {
|
|
586
|
-
|
|
587
|
-
return BigInt(value.slice(0, -1));
|
|
588
|
-
}
|
|
589
|
-
return value;
|
|
590
|
-
}
|
|
591
|
-
static bigIntReplacer(_key, value) {
|
|
592
|
-
return typeof value === "bigint" ? `${value.toString()}n` : value;
|
|
593
|
-
}
|
|
594
|
-
static collectSensitivePaths(type, prefix = "") {
|
|
595
|
-
const paths = [];
|
|
596
|
-
if (isObject(type)) {
|
|
597
|
-
const fields = type["properties"];
|
|
598
|
-
for (const [field, fieldType] of Object.entries(fields)) {
|
|
599
|
-
const fieldPath = prefix ? `${prefix}.${field}` : field;
|
|
600
|
-
if (isSensitive(fieldType)) {
|
|
601
|
-
paths.push(fieldPath);
|
|
602
|
-
}
|
|
603
|
-
paths.push(...Sprinkle.collectSensitivePaths(fieldType, fieldPath));
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
if (isUnion(type)) {
|
|
607
|
-
for (const variant of type.anyOf) {
|
|
608
|
-
paths.push(...Sprinkle.collectSensitivePaths(variant, prefix));
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return paths;
|
|
612
|
-
}
|
|
613
|
-
static getNestedValue(obj, path) {
|
|
614
|
-
return path.split(".").reduce((o, k) => o?.[k], obj);
|
|
615
|
-
}
|
|
616
|
-
static setNestedValue(obj, path, value) {
|
|
617
|
-
const keys = path.split(".");
|
|
618
|
-
const last = keys.pop();
|
|
619
|
-
const parent = keys.reduce((o, k) => o?.[k], obj);
|
|
620
|
-
if (parent && typeof parent === "object") {
|
|
621
|
-
parent[last] = value;
|
|
622
|
-
}
|
|
517
|
+
return bigIntReviver(key, value);
|
|
623
518
|
}
|
|
624
519
|
encryptSettings(settings) {
|
|
625
520
|
if (!this.options.encryption) return settings;
|
|
626
|
-
|
|
627
|
-
const sensitivePaths = Sprinkle.collectSensitivePaths(this.type);
|
|
628
|
-
for (const p of sensitivePaths) {
|
|
629
|
-
const value = Sprinkle.getNestedValue(clone, p);
|
|
630
|
-
if (typeof value === "string" && value.length > 0) {
|
|
631
|
-
Sprinkle.setNestedValue(clone, p, this.options.encryption.encrypt(value));
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
return clone;
|
|
521
|
+
return encryptSensitiveFields(settings, this.type, this.options.encryption);
|
|
635
522
|
}
|
|
636
523
|
async decryptSettings(settings) {
|
|
637
524
|
if (!this.options.encryption) return settings;
|
|
638
|
-
|
|
639
|
-
const sensitivePaths = Sprinkle.collectSensitivePaths(this.type);
|
|
640
|
-
for (const p of sensitivePaths) {
|
|
641
|
-
const value = Sprinkle.getNestedValue(clone, p);
|
|
642
|
-
if (typeof value === "string" && value.length > 0) {
|
|
643
|
-
Sprinkle.setNestedValue(clone, p, await this.options.encryption.decrypt(value));
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
return clone;
|
|
525
|
+
return decryptSensitiveFields(settings, this.type, this.options.encryption);
|
|
647
526
|
}
|
|
648
527
|
saveSettings() {
|
|
649
528
|
this.saveProfile();
|
|
650
529
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Get the payment key hash from a HotWallet's first address
|
|
656
|
-
*/
|
|
657
|
-
async getWalletPaymentKeyHash(wallet) {
|
|
658
|
-
try {
|
|
659
|
-
const addresses = await wallet.getUsedAddresses();
|
|
660
|
-
const address = addresses[0];
|
|
661
|
-
if (!address) return null;
|
|
662
|
-
const paymentCredential = address.asBase()?.getPaymentCredential();
|
|
663
|
-
return paymentCredential?.hash?.toString() ?? null;
|
|
664
|
-
} catch {
|
|
665
|
-
return null;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Count the number of vkey signatures in a transaction's witness set
|
|
671
|
-
*/
|
|
672
|
-
countSignatures(tx) {
|
|
673
|
-
const vkeys = tx.witnessSet().vkeys();
|
|
674
|
-
return vkeys ? vkeys.size() : 0;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
/**
|
|
678
|
-
* Check if a specific public key has already signed the transaction
|
|
679
|
-
* Compares by vkey (public key bytes)
|
|
680
|
-
*/
|
|
681
|
-
hasVkeySigned(tx, vkeyHex) {
|
|
682
|
-
const vkeys = tx.witnessSet().vkeys();
|
|
683
|
-
if (!vkeys) return false;
|
|
684
|
-
const vkeyArray = vkeys.toCore();
|
|
685
|
-
return vkeyArray.some(([vkey]) => vkey === vkeyHex);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Get the list of required signer key hashes from the transaction body
|
|
690
|
-
*/
|
|
691
|
-
getRequiredSigners(tx) {
|
|
692
|
-
const requiredSigners = tx.body().requiredSigners();
|
|
693
|
-
if (!requiredSigners) return [];
|
|
694
|
-
return Array.from(requiredSigners.values()).map(s => s.toString());
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* Compute the transaction body hash for display
|
|
699
|
-
*/
|
|
700
|
-
getTxBodyHash(tx) {
|
|
701
|
-
const bodyCbor = tx.body().toCbor();
|
|
702
|
-
return blake2b_256(bodyCbor);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* Format a hash for display: first 8 chars + ... + last 8 chars
|
|
707
|
-
*/
|
|
708
|
-
formatHash(hash) {
|
|
709
|
-
if (hash.length <= 20) return hash;
|
|
710
|
-
return `${hash.slice(0, 8)}...${hash.slice(-8)}`;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
/**
|
|
714
|
-
* Merge signatures from source transaction into target transaction.
|
|
715
|
-
* Prevents duplicate signatures by comparing vkey (public key).
|
|
716
|
-
* Returns the count of newly added signatures.
|
|
717
|
-
*/
|
|
718
|
-
mergeSignatures(target, source) {
|
|
719
|
-
const targetWs = target.witnessSet();
|
|
720
|
-
const sourceWs = source.witnessSet();
|
|
721
|
-
const targetVkeys = targetWs.vkeys()?.toCore() ?? [];
|
|
722
|
-
const sourceVkeys = sourceWs.vkeys()?.toCore() ?? [];
|
|
723
|
-
|
|
724
|
-
// Find vkeys in source that aren't in target (by comparing public key)
|
|
725
|
-
const existingPubKeys = new Set(targetVkeys.map(([vkey]) => vkey));
|
|
726
|
-
const newVkeys = sourceVkeys.filter(([vkey]) => !existingPubKeys.has(vkey));
|
|
727
|
-
if (newVkeys.length === 0) {
|
|
728
|
-
return 0;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// Merge the new vkeys into target
|
|
732
|
-
targetWs.setVkeys(CborSet.fromCore([...targetVkeys, ...newVkeys], VkeyWitness.fromCore));
|
|
733
|
-
target.setWitnessSet(targetWs);
|
|
734
|
-
return newVkeys.length;
|
|
530
|
+
getDisplaySettings() {
|
|
531
|
+
return maskSensitiveFields(this.settings, this.type);
|
|
735
532
|
}
|
|
736
533
|
async TxDialog(blaze, tx, opts) {
|
|
737
534
|
let currentTx = tx;
|
|
@@ -759,16 +556,16 @@ export class Sprinkle {
|
|
|
759
556
|
}
|
|
760
557
|
while (true) {
|
|
761
558
|
// Display transaction status
|
|
762
|
-
const txHash =
|
|
763
|
-
const sigCount =
|
|
764
|
-
const requiredSigners =
|
|
559
|
+
const txHash = getTxBodyHash(currentTx);
|
|
560
|
+
const sigCount = countSignatures(currentTx);
|
|
561
|
+
const requiredSigners = getRequiredSigners(currentTx);
|
|
765
562
|
console.log("");
|
|
766
|
-
console.log(`Transaction: ${
|
|
563
|
+
console.log(`Transaction: ${formatHash(txHash)}`);
|
|
767
564
|
if (requiredSigners.length > 0) {
|
|
768
565
|
console.log(`Signatures: ${sigCount} of ${requiredSigners.length} required`);
|
|
769
566
|
console.log("Required signers:");
|
|
770
567
|
for (const signer of requiredSigners) {
|
|
771
|
-
console.log(` - ${
|
|
568
|
+
console.log(` - ${formatHash(signer)}`);
|
|
772
569
|
}
|
|
773
570
|
} else {
|
|
774
571
|
console.log(`Signatures: ${sigCount}`);
|
|
@@ -818,11 +615,25 @@ export class Sprinkle {
|
|
|
818
615
|
name: "Cancel",
|
|
819
616
|
value: "cancel"
|
|
820
617
|
});
|
|
821
|
-
const selection = await
|
|
618
|
+
const selection = await selectCancellable({
|
|
822
619
|
message: "Select an option:",
|
|
823
620
|
choices
|
|
824
621
|
});
|
|
825
622
|
|
|
623
|
+
// Handle escape/cancel as cancel action
|
|
624
|
+
if (selection === null) {
|
|
625
|
+
if (hasSignedThisSession) {
|
|
626
|
+
return {
|
|
627
|
+
action: "signed",
|
|
628
|
+
tx: currentTx
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
action: "cancelled",
|
|
633
|
+
tx: currentTx
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
826
637
|
// Handle selection
|
|
827
638
|
if (selection === "sign") {
|
|
828
639
|
if (opts?.beforeSign) {
|
|
@@ -888,7 +699,7 @@ export class Sprinkle {
|
|
|
888
699
|
} else {
|
|
889
700
|
const signedTx = await blaze.signTransaction(currentTx);
|
|
890
701
|
// Merge signatures from signed tx into current tx
|
|
891
|
-
const added =
|
|
702
|
+
const added = mergeSignatures(currentTx, signedTx);
|
|
892
703
|
if (added > 0) {
|
|
893
704
|
console.log(`Added ${added} signature(s).`);
|
|
894
705
|
hasSignedThisSession = true;
|
|
@@ -920,10 +731,10 @@ export class Sprinkle {
|
|
|
920
731
|
continue;
|
|
921
732
|
}
|
|
922
733
|
if (selection === "import") {
|
|
923
|
-
const cborInput = await
|
|
734
|
+
const cborInput = await inputCancellable({
|
|
924
735
|
message: "Paste transaction CBOR (hex):"
|
|
925
736
|
});
|
|
926
|
-
if (
|
|
737
|
+
if (cborInput === null || cborInput.trim() === "") {
|
|
927
738
|
console.log("No CBOR provided.");
|
|
928
739
|
continue;
|
|
929
740
|
}
|
|
@@ -931,21 +742,21 @@ export class Sprinkle {
|
|
|
931
742
|
const importedTx = Core.Transaction.fromCbor(TxCBOR(cborInput.trim()));
|
|
932
743
|
|
|
933
744
|
// Validate body hash matches
|
|
934
|
-
const currentHash =
|
|
935
|
-
const importedHash =
|
|
745
|
+
const currentHash = getTxBodyHash(currentTx);
|
|
746
|
+
const importedHash = getTxBodyHash(importedTx);
|
|
936
747
|
if (currentHash !== importedHash) {
|
|
937
|
-
const proceed = await
|
|
938
|
-
message: `Warning: Imported transaction has different body hash.\nCurrent: ${
|
|
748
|
+
const proceed = await confirmCancellable({
|
|
749
|
+
message: `Warning: Imported transaction has different body hash.\nCurrent: ${formatHash(currentHash)}\nImported: ${formatHash(importedHash)}\nProceed anyway?`,
|
|
939
750
|
default: false
|
|
940
751
|
});
|
|
941
|
-
if (!proceed) {
|
|
752
|
+
if (proceed === null || !proceed) {
|
|
942
753
|
console.log("Import cancelled.");
|
|
943
754
|
continue;
|
|
944
755
|
}
|
|
945
756
|
}
|
|
946
757
|
|
|
947
758
|
// Merge signatures
|
|
948
|
-
const added =
|
|
759
|
+
const added = mergeSignatures(currentTx, importedTx);
|
|
949
760
|
const sourceVkeys = importedTx.witnessSet().vkeys();
|
|
950
761
|
const sourceCount = sourceVkeys ? sourceVkeys.size() : 0;
|
|
951
762
|
const skipped = sourceCount - added;
|
|
@@ -964,13 +775,13 @@ export class Sprinkle {
|
|
|
964
775
|
continue;
|
|
965
776
|
}
|
|
966
777
|
if (selection === "submit") {
|
|
967
|
-
const sigCount =
|
|
778
|
+
const sigCount = countSignatures(currentTx);
|
|
968
779
|
if (sigCount === 0) {
|
|
969
|
-
const proceed = await
|
|
780
|
+
const proceed = await confirmCancellable({
|
|
970
781
|
message: "Warning: Transaction has no signatures. Submit anyway?",
|
|
971
782
|
default: false
|
|
972
783
|
});
|
|
973
|
-
if (!proceed) {
|
|
784
|
+
if (proceed === null || !proceed) {
|
|
974
785
|
continue;
|
|
975
786
|
}
|
|
976
787
|
}
|
|
@@ -1059,7 +870,7 @@ export class Sprinkle {
|
|
|
1059
870
|
return this._fillInStruct(resolvedType, path, defs, def);
|
|
1060
871
|
}
|
|
1061
872
|
if (isOptional(type)) {
|
|
1062
|
-
const shouldSet = await
|
|
873
|
+
const shouldSet = await selectCancellable({
|
|
1063
874
|
message: Sprinkle.ExtractMessage(type, `Set value for ${path.join(".")}?`),
|
|
1064
875
|
choices: [{
|
|
1065
876
|
name: "Yes",
|
|
@@ -1070,6 +881,9 @@ export class Sprinkle {
|
|
|
1070
881
|
}],
|
|
1071
882
|
default: def !== undefined
|
|
1072
883
|
});
|
|
884
|
+
if (shouldSet === null) {
|
|
885
|
+
throw new UserCancelledError();
|
|
886
|
+
}
|
|
1073
887
|
if (!shouldSet) {
|
|
1074
888
|
return undefined;
|
|
1075
889
|
}
|
|
@@ -1089,17 +903,21 @@ export class Sprinkle {
|
|
|
1089
903
|
value: variant
|
|
1090
904
|
});
|
|
1091
905
|
}
|
|
1092
|
-
const
|
|
906
|
+
const selectionResult = await selectCancellable({
|
|
1093
907
|
message: Sprinkle.ExtractMessage(resolved, `Enter a choice for ${path.join(".")}`),
|
|
1094
908
|
choices: choices,
|
|
1095
909
|
default: def ? `${def}` : undefined
|
|
1096
910
|
});
|
|
911
|
+
if (selectionResult === null) {
|
|
912
|
+
throw new UserCancelledError();
|
|
913
|
+
}
|
|
914
|
+
const selection = selectionResult;
|
|
1097
915
|
return this._fillInStruct(selection, path, defs);
|
|
1098
916
|
}
|
|
1099
917
|
if (isString(type)) {
|
|
1100
918
|
// Special handling for hot wallet private key - offer generation option
|
|
1101
919
|
if (type.title === "Hot Wallet Private Key") {
|
|
1102
|
-
const choice = await
|
|
920
|
+
const choice = await selectCancellable({
|
|
1103
921
|
message: "Hot wallet setup:",
|
|
1104
922
|
choices: [{
|
|
1105
923
|
name: "Enter existing private key",
|
|
@@ -1109,33 +927,44 @@ export class Sprinkle {
|
|
|
1109
927
|
value: "generate"
|
|
1110
928
|
}]
|
|
1111
929
|
});
|
|
930
|
+
if (choice === null) {
|
|
931
|
+
throw new UserCancelledError();
|
|
932
|
+
}
|
|
1112
933
|
if (choice === "generate") {
|
|
1113
934
|
return Sprinkle.generateWalletFromMnemonic();
|
|
1114
935
|
}
|
|
1115
936
|
// Fall through to password prompt for "existing" choice
|
|
1116
|
-
const answer = await
|
|
937
|
+
const answer = await passwordCancellable({
|
|
1117
938
|
message: "Enter your private key:"
|
|
1118
939
|
});
|
|
940
|
+
if (answer === null) {
|
|
941
|
+
throw new UserCancelledError();
|
|
942
|
+
}
|
|
1119
943
|
return answer;
|
|
1120
944
|
}
|
|
1121
945
|
const defaultString = def ? def : this.defaults["string"];
|
|
1122
946
|
const message = Sprinkle.ExtractMessage(type, `Enter a string for ${path.join(".")}`);
|
|
1123
947
|
let answer;
|
|
1124
948
|
if (isSensitive(type)) {
|
|
1125
|
-
answer = await
|
|
949
|
+
answer = await passwordCancellable({
|
|
1126
950
|
message
|
|
1127
951
|
});
|
|
1128
952
|
} else {
|
|
1129
|
-
answer = await
|
|
953
|
+
answer = await inputCancellable({
|
|
1130
954
|
message,
|
|
1131
955
|
default: defaultString
|
|
1132
956
|
});
|
|
1133
|
-
|
|
957
|
+
if (answer !== null) {
|
|
958
|
+
this.defaults["string"] = answer;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (answer === null) {
|
|
962
|
+
throw new UserCancelledError();
|
|
1134
963
|
}
|
|
1135
964
|
return answer;
|
|
1136
965
|
}
|
|
1137
966
|
if (isBigInt(type)) {
|
|
1138
|
-
const answer = await
|
|
967
|
+
const answer = await inputCancellable({
|
|
1139
968
|
message: Sprinkle.ExtractMessage(type, `Enter a bigint for ${path.join(".")}`),
|
|
1140
969
|
default: def ? def.toString() : undefined,
|
|
1141
970
|
validate: s => {
|
|
@@ -1147,6 +976,9 @@ export class Sprinkle {
|
|
|
1147
976
|
}
|
|
1148
977
|
}
|
|
1149
978
|
});
|
|
979
|
+
if (answer === null) {
|
|
980
|
+
throw new UserCancelledError();
|
|
981
|
+
}
|
|
1150
982
|
return BigInt(answer);
|
|
1151
983
|
}
|
|
1152
984
|
if (isLiteral(type)) {
|
|
@@ -1170,7 +1002,7 @@ export class Sprinkle {
|
|
|
1170
1002
|
while (addMore) {
|
|
1171
1003
|
const itemValue = await this._fillInStruct(itemType, path.concat([`[${arr.length}]`]), defs);
|
|
1172
1004
|
arr.push(itemValue);
|
|
1173
|
-
const continueAnswer = await
|
|
1005
|
+
const continueAnswer = await selectCancellable({
|
|
1174
1006
|
message: `Add another item to ${path.join(".")}?`,
|
|
1175
1007
|
choices: [{
|
|
1176
1008
|
name: "Yes",
|
|
@@ -1180,6 +1012,9 @@ export class Sprinkle {
|
|
|
1180
1012
|
value: false
|
|
1181
1013
|
}]
|
|
1182
1014
|
});
|
|
1015
|
+
if (continueAnswer === null) {
|
|
1016
|
+
throw new UserCancelledError();
|
|
1017
|
+
}
|
|
1183
1018
|
addMore = continueAnswer;
|
|
1184
1019
|
}
|
|
1185
1020
|
return arr;
|