@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
package/src/Sprinkle/index.ts
CHANGED
|
@@ -1,189 +1,126 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Provider } from "@blaze-cardano/query";
|
|
2
2
|
import {
|
|
3
3
|
Blaze,
|
|
4
|
-
ColdWallet,
|
|
5
4
|
Core,
|
|
6
5
|
HotWallet,
|
|
7
6
|
type Wallet,
|
|
8
7
|
} from "@blaze-cardano/sdk";
|
|
8
|
+
import { CborSet, VkeyWitness, TxCBOR } from "@blaze-cardano/core";
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
Kind,
|
|
19
|
-
type Static,
|
|
20
|
-
type TBigInt,
|
|
21
|
-
type TLiteral,
|
|
22
|
-
type TObject,
|
|
23
|
-
type TSchema,
|
|
24
|
-
type TString,
|
|
25
|
-
type TTuple,
|
|
26
|
-
type TUnion,
|
|
27
|
-
Type,
|
|
28
|
-
type TArray,
|
|
29
|
-
type TThis,
|
|
30
|
-
type TRef,
|
|
31
|
-
type TImport,
|
|
32
|
-
type TOptional,
|
|
33
|
-
OptionalKind,
|
|
34
|
-
} from "@sinclair/typebox";
|
|
10
|
+
selectCancellable,
|
|
11
|
+
inputCancellable,
|
|
12
|
+
passwordCancellable,
|
|
13
|
+
confirmCancellable,
|
|
14
|
+
searchCancellable,
|
|
15
|
+
select,
|
|
16
|
+
} from "./prompts.js";
|
|
17
|
+
import { type TSchema, Type, OptionalKind } from "@sinclair/typebox";
|
|
35
18
|
import * as fs from "fs";
|
|
36
19
|
import * as path from "path";
|
|
37
20
|
export * from "@sinclair/typebox";
|
|
38
21
|
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
export
|
|
89
|
-
|
|
90
|
-
|
|
22
|
+
// Re-export types from types.ts
|
|
23
|
+
export type { TExact } from "./types.js";
|
|
24
|
+
export type {
|
|
25
|
+
IEncryptionOptions,
|
|
26
|
+
ISprinkleOptions,
|
|
27
|
+
IProfileMeta,
|
|
28
|
+
ICurrentProfile,
|
|
29
|
+
IProfileEntry,
|
|
30
|
+
TxDialogResult,
|
|
31
|
+
TxDialogOptions,
|
|
32
|
+
} from "./types.js";
|
|
33
|
+
export { UserCancelledError } from "./types.js";
|
|
34
|
+
import type {
|
|
35
|
+
TExact,
|
|
36
|
+
IEncryptionOptions,
|
|
37
|
+
ISprinkleOptions,
|
|
38
|
+
IProfileMeta,
|
|
39
|
+
ICurrentProfile,
|
|
40
|
+
IProfileEntry,
|
|
41
|
+
TxDialogResult,
|
|
42
|
+
TxDialogOptions,
|
|
43
|
+
} from "./types.js";
|
|
44
|
+
import { UserCancelledError } from "./types.js";
|
|
45
|
+
|
|
46
|
+
// Re-export schemas from schemas.ts
|
|
47
|
+
export {
|
|
48
|
+
NetworkSchema,
|
|
49
|
+
MultisigScriptModule,
|
|
50
|
+
MultisigScript,
|
|
51
|
+
ProviderSettingsSchema,
|
|
52
|
+
WalletSettingsSchema,
|
|
53
|
+
} from "./schemas.js";
|
|
54
|
+
export type { TMultisigScript } from "./schemas.js";
|
|
55
|
+
|
|
56
|
+
// Import and re-export type guards
|
|
57
|
+
import {
|
|
58
|
+
isOptional,
|
|
59
|
+
isImport,
|
|
60
|
+
isArray,
|
|
61
|
+
isBigInt,
|
|
62
|
+
isLiteral,
|
|
63
|
+
isObject,
|
|
64
|
+
isRef,
|
|
65
|
+
isString,
|
|
66
|
+
isThis,
|
|
67
|
+
isTuple,
|
|
68
|
+
isUnion,
|
|
69
|
+
isSensitive,
|
|
70
|
+
} from "./type-guards.js";
|
|
71
|
+
export {
|
|
72
|
+
isOptional,
|
|
73
|
+
isImport,
|
|
74
|
+
isArray,
|
|
75
|
+
isBigInt,
|
|
76
|
+
isLiteral,
|
|
77
|
+
isObject,
|
|
78
|
+
isRef,
|
|
79
|
+
isString,
|
|
80
|
+
isThis,
|
|
81
|
+
isTuple,
|
|
82
|
+
isUnion,
|
|
83
|
+
isSensitive,
|
|
84
|
+
} from "./type-guards.js";
|
|
85
|
+
|
|
86
|
+
// Import schemas for use in this file
|
|
87
|
+
import {
|
|
88
|
+
NetworkSchema,
|
|
89
|
+
ProviderSettingsSchema,
|
|
90
|
+
WalletSettingsSchema,
|
|
91
|
+
} from "./schemas.js";
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}),
|
|
124
|
-
Type.Object({
|
|
125
|
-
AtLeast: Type.Object(
|
|
126
|
-
{
|
|
127
|
-
required: Type.BigInt(),
|
|
128
|
-
scripts: Type.Array(Type.Ref("MultisigScript")),
|
|
129
|
-
},
|
|
130
|
-
{ ctor: 3n },
|
|
131
|
-
),
|
|
132
|
-
}),
|
|
133
|
-
Type.Object({
|
|
134
|
-
Before: Type.Object(
|
|
135
|
-
{
|
|
136
|
-
time: Type.BigInt(),
|
|
137
|
-
},
|
|
138
|
-
{ ctor: 4n },
|
|
139
|
-
),
|
|
140
|
-
}),
|
|
141
|
-
Type.Object({
|
|
142
|
-
After: Type.Object(
|
|
143
|
-
{
|
|
144
|
-
time: Type.BigInt(),
|
|
145
|
-
},
|
|
146
|
-
{ ctor: 5n },
|
|
147
|
-
),
|
|
148
|
-
}),
|
|
149
|
-
Type.Object({
|
|
150
|
-
Script: Type.Object(
|
|
151
|
-
{
|
|
152
|
-
script_hash: Type.String(),
|
|
153
|
-
},
|
|
154
|
-
{ ctor: 6n },
|
|
155
|
-
),
|
|
156
|
-
}),
|
|
157
|
-
]),
|
|
158
|
-
});
|
|
159
|
-
export const MultisigScript = MultisigScriptModule.Import("MultisigScript");
|
|
160
|
-
export type TMultisigScript = TExact<typeof MultisigScript>;
|
|
161
|
-
|
|
162
|
-
export const ProviderSettingsSchema = Type.Union([
|
|
163
|
-
Type.Object({
|
|
164
|
-
type: Type.Literal("blockfrost"),
|
|
165
|
-
projectId: Type.String({ minLength: 1, title: "Blockfrost Project ID" }),
|
|
166
|
-
}),
|
|
167
|
-
Type.Object({
|
|
168
|
-
type: Type.Literal("maestro"),
|
|
169
|
-
apiKey: Type.String({ minLength: 1, title: "Maestro API Key" }),
|
|
170
|
-
}),
|
|
171
|
-
]);
|
|
172
|
-
|
|
173
|
-
export const WalletSettingsSchema = Type.Union([
|
|
174
|
-
Type.Object({
|
|
175
|
-
type: Type.Literal("hot"),
|
|
176
|
-
privateKey: Type.String({
|
|
177
|
-
minLength: 1,
|
|
178
|
-
title: "Hot Wallet Private Key",
|
|
179
|
-
sensitive: true,
|
|
180
|
-
}),
|
|
181
|
-
}),
|
|
182
|
-
Type.Object({
|
|
183
|
-
type: Type.Literal("cold"),
|
|
184
|
-
address: Type.String({ minLength: 1, title: "Cold Wallet Address" }),
|
|
185
|
-
}),
|
|
186
|
-
]);
|
|
93
|
+
// Import and re-export wallet utilities
|
|
94
|
+
import {
|
|
95
|
+
GetProvider as GetProviderFn,
|
|
96
|
+
GetWallet as GetWalletFn,
|
|
97
|
+
GetBlaze as GetBlazeFn,
|
|
98
|
+
generateWalletFromMnemonic,
|
|
99
|
+
} from "./wallet.js";
|
|
100
|
+
export { GetProvider, GetWallet, GetBlaze } from "./wallet.js";
|
|
101
|
+
|
|
102
|
+
// Import encryption utilities
|
|
103
|
+
import {
|
|
104
|
+
collectSensitivePaths,
|
|
105
|
+
getNestedValue,
|
|
106
|
+
setNestedValue,
|
|
107
|
+
bigIntReplacer,
|
|
108
|
+
bigIntReviver,
|
|
109
|
+
encryptSensitiveFields,
|
|
110
|
+
decryptSensitiveFields,
|
|
111
|
+
maskSensitiveFields,
|
|
112
|
+
} from "./encryption.js";
|
|
113
|
+
|
|
114
|
+
// Import tx-dialog utilities
|
|
115
|
+
import {
|
|
116
|
+
getWalletPaymentKeyHash,
|
|
117
|
+
countSignatures,
|
|
118
|
+
hasVkeySigned,
|
|
119
|
+
getRequiredSigners,
|
|
120
|
+
getTxBodyHash,
|
|
121
|
+
formatHash,
|
|
122
|
+
mergeSignatures,
|
|
123
|
+
} from "./tx-dialog.js";
|
|
187
124
|
|
|
188
125
|
export interface IMenuAction<S extends TSchema> {
|
|
189
126
|
title: string;
|
|
@@ -211,6 +148,16 @@ export class Sprinkle<S extends TSchema> {
|
|
|
211
148
|
this.options = options ?? {};
|
|
212
149
|
}
|
|
213
150
|
|
|
151
|
+
// --- Current Profile Accessor ---
|
|
152
|
+
|
|
153
|
+
get currentProfile(): ICurrentProfile | null {
|
|
154
|
+
if (!this.profileId) return null;
|
|
155
|
+
return {
|
|
156
|
+
id: this.profileId,
|
|
157
|
+
...this.profileMeta,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
214
161
|
// --- Profile path helpers ---
|
|
215
162
|
|
|
216
163
|
static sanitizeProfileId(name: string): string {
|
|
@@ -304,7 +251,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
304
251
|
settings: settingsToSave,
|
|
305
252
|
defaults: this.defaults,
|
|
306
253
|
},
|
|
307
|
-
|
|
254
|
+
bigIntReplacer,
|
|
308
255
|
2,
|
|
309
256
|
);
|
|
310
257
|
fs.writeFileSync(filePath, jsonContent, "utf-8");
|
|
@@ -313,31 +260,54 @@ export class Sprinkle<S extends TSchema> {
|
|
|
313
260
|
private async promptProfileMeta(
|
|
314
261
|
defaultName?: string,
|
|
315
262
|
defaultDescription?: string,
|
|
316
|
-
): Promise<{ name: string; description?: string }> {
|
|
317
|
-
const name = await
|
|
263
|
+
): Promise<{ name: string; description?: string } | null> {
|
|
264
|
+
const name = await inputCancellable({
|
|
318
265
|
message: "Profile name:",
|
|
319
266
|
default: defaultName,
|
|
320
267
|
validate: (v) => (v.trim().length > 0 ? true : "Name cannot be empty"),
|
|
321
268
|
});
|
|
322
|
-
|
|
269
|
+
if (name === null) return null; // User cancelled
|
|
270
|
+
const description = await inputCancellable({
|
|
323
271
|
message: "Profile description (optional):",
|
|
324
272
|
default: defaultDescription ?? "",
|
|
325
273
|
});
|
|
274
|
+
if (description === null) return null; // User cancelled
|
|
326
275
|
return { name, description: description || undefined };
|
|
327
276
|
}
|
|
328
277
|
|
|
329
278
|
private async createProfile(): Promise<void> {
|
|
330
|
-
const
|
|
279
|
+
const result = await this.promptProfileMeta();
|
|
280
|
+
if (result === null) return; // User cancelled
|
|
281
|
+
const { name, description } = result;
|
|
331
282
|
const profilesDir = Sprinkle.profilesDir(this.storagePath);
|
|
332
283
|
const id = Sprinkle.findAvailableId(
|
|
333
284
|
profilesDir,
|
|
334
285
|
Sprinkle.sanitizeProfileId(name),
|
|
335
286
|
);
|
|
336
287
|
const now = new Date().toISOString();
|
|
288
|
+
|
|
289
|
+
// Snapshot current state in case we need to restore on cancellation
|
|
290
|
+
const prevProfileId = this.profileId;
|
|
291
|
+
const prevProfileMeta = this.profileMeta;
|
|
292
|
+
const prevDefaults = this.defaults;
|
|
293
|
+
const prevSettings = this.settings;
|
|
294
|
+
|
|
337
295
|
this.profileId = id;
|
|
338
296
|
this.profileMeta = { name, description, createdAt: now, updatedAt: now };
|
|
339
297
|
this.defaults = {};
|
|
340
|
-
|
|
298
|
+
try {
|
|
299
|
+
this.settings = await this.FillInStruct(this.type);
|
|
300
|
+
} catch (e) {
|
|
301
|
+
// Restore previous state on cancellation
|
|
302
|
+
if (e instanceof UserCancelledError) {
|
|
303
|
+
this.profileId = prevProfileId;
|
|
304
|
+
this.profileMeta = prevProfileMeta;
|
|
305
|
+
this.defaults = prevDefaults;
|
|
306
|
+
this.settings = prevSettings;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
throw e;
|
|
310
|
+
}
|
|
341
311
|
this.saveProfile();
|
|
342
312
|
fs.writeFileSync(Sprinkle.activeProfilePath(this.storagePath), id, "utf-8");
|
|
343
313
|
}
|
|
@@ -364,7 +334,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
364
334
|
fs.mkdirSync(profilesDir, { recursive: true });
|
|
365
335
|
fs.writeFileSync(
|
|
366
336
|
Sprinkle.profilePath(this.storagePath, "default"),
|
|
367
|
-
JSON.stringify(profileData,
|
|
337
|
+
JSON.stringify(profileData, bigIntReplacer, 2),
|
|
368
338
|
"utf-8",
|
|
369
339
|
);
|
|
370
340
|
fs.writeFileSync(
|
|
@@ -409,7 +379,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
409
379
|
|
|
410
380
|
private async selectProfile(profiles?: IProfileEntry[]): Promise<void> {
|
|
411
381
|
const available = profiles ?? this.scanProfiles();
|
|
412
|
-
const selection = await
|
|
382
|
+
const selection = await selectCancellable({
|
|
413
383
|
message: "Select a profile:",
|
|
414
384
|
choices: available.map((p) => ({
|
|
415
385
|
name: p.meta.description
|
|
@@ -418,16 +388,19 @@ export class Sprinkle<S extends TSchema> {
|
|
|
418
388
|
value: p.id,
|
|
419
389
|
})),
|
|
420
390
|
});
|
|
421
|
-
|
|
391
|
+
if (selection === null) return; // User cancelled
|
|
392
|
+
await this.loadProfile(selection as string);
|
|
422
393
|
}
|
|
423
394
|
|
|
424
395
|
// --- Profile management (CRUD) ---
|
|
425
396
|
|
|
426
397
|
private async duplicateProfile(): Promise<void> {
|
|
427
|
-
const
|
|
398
|
+
const result = await this.promptProfileMeta(
|
|
428
399
|
`${this.profileMeta.name} (copy)`,
|
|
429
400
|
this.profileMeta.description,
|
|
430
401
|
);
|
|
402
|
+
if (result === null) return; // User cancelled
|
|
403
|
+
const { name, description } = result;
|
|
431
404
|
const profilesDir = Sprinkle.profilesDir(this.storagePath);
|
|
432
405
|
const id = Sprinkle.findAvailableId(
|
|
433
406
|
profilesDir,
|
|
@@ -442,7 +415,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
442
415
|
settings: settingsToSave,
|
|
443
416
|
defaults: this.defaults,
|
|
444
417
|
},
|
|
445
|
-
|
|
418
|
+
bigIntReplacer,
|
|
446
419
|
2,
|
|
447
420
|
);
|
|
448
421
|
fs.writeFileSync(
|
|
@@ -454,10 +427,12 @@ export class Sprinkle<S extends TSchema> {
|
|
|
454
427
|
}
|
|
455
428
|
|
|
456
429
|
private async renameProfile(): Promise<void> {
|
|
457
|
-
const
|
|
430
|
+
const result = await this.promptProfileMeta(
|
|
458
431
|
this.profileMeta.name,
|
|
459
432
|
this.profileMeta.description,
|
|
460
433
|
);
|
|
434
|
+
if (result === null) return; // User cancelled
|
|
435
|
+
const { name, description } = result;
|
|
461
436
|
const newId = Sprinkle.sanitizeProfileId(name);
|
|
462
437
|
const oldId = this.profileId;
|
|
463
438
|
|
|
@@ -496,31 +471,31 @@ export class Sprinkle<S extends TSchema> {
|
|
|
496
471
|
return;
|
|
497
472
|
}
|
|
498
473
|
|
|
499
|
-
const toDelete = await
|
|
474
|
+
const toDelete = await selectCancellable({
|
|
500
475
|
message: "Select a profile to delete:",
|
|
501
|
-
choices:
|
|
502
|
-
|
|
503
|
-
name
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
})),
|
|
508
|
-
{ name: "Cancel", value: "__cancel__" },
|
|
509
|
-
],
|
|
476
|
+
choices: others.map((p) => ({
|
|
477
|
+
name: p.meta.description
|
|
478
|
+
? `${p.meta.name} - ${p.meta.description}`
|
|
479
|
+
: p.meta.name,
|
|
480
|
+
value: p.id,
|
|
481
|
+
})),
|
|
510
482
|
});
|
|
511
483
|
|
|
512
|
-
if (toDelete ===
|
|
484
|
+
if (toDelete === null) return; // User cancelled
|
|
513
485
|
|
|
514
|
-
const
|
|
515
|
-
const
|
|
516
|
-
|
|
486
|
+
const profileId = toDelete as string;
|
|
487
|
+
const profileToDelete = others.find((p) => p.id === profileId);
|
|
488
|
+
const confirmed = await confirmCancellable({
|
|
489
|
+
message: `Delete profile "${profileToDelete?.meta.name ?? profileId}"? This cannot be undone.`,
|
|
517
490
|
default: false,
|
|
518
491
|
});
|
|
519
492
|
|
|
493
|
+
if (confirmed === null) return; // User cancelled
|
|
494
|
+
|
|
520
495
|
if (confirmed) {
|
|
521
|
-
fs.unlinkSync(Sprinkle.profilePath(this.storagePath,
|
|
496
|
+
fs.unlinkSync(Sprinkle.profilePath(this.storagePath, profileId));
|
|
522
497
|
console.log(
|
|
523
|
-
`Profile "${profileToDelete?.meta.name ??
|
|
498
|
+
`Profile "${profileToDelete?.meta.name ?? profileId}" deleted.`,
|
|
524
499
|
);
|
|
525
500
|
}
|
|
526
501
|
}
|
|
@@ -548,14 +523,27 @@ export class Sprinkle<S extends TSchema> {
|
|
|
548
523
|
choices.push({ name: "Settings & Profiles", value: -5 });
|
|
549
524
|
choices.push({ name: "Exit", value: -1 });
|
|
550
525
|
}
|
|
551
|
-
const
|
|
526
|
+
const selectionResult = await selectCancellable({
|
|
552
527
|
message: "Select an option:",
|
|
553
528
|
choices: choices,
|
|
554
529
|
});
|
|
530
|
+
// Handle escape (null) as Back
|
|
531
|
+
if (selectionResult === null) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const selection = selectionResult as number;
|
|
555
535
|
if (selection === -5) {
|
|
556
536
|
const settingsMenu: IMenu<S> = {
|
|
557
537
|
title: "Settings & Profiles",
|
|
558
538
|
items: [
|
|
539
|
+
{
|
|
540
|
+
title: "View settings",
|
|
541
|
+
action: async () => {
|
|
542
|
+
console.log(
|
|
543
|
+
JSON.stringify(this.getDisplaySettings(), bigIntReplacer, 2),
|
|
544
|
+
);
|
|
545
|
+
},
|
|
546
|
+
},
|
|
559
547
|
{
|
|
560
548
|
title: "Edit settings",
|
|
561
549
|
action: async () => {
|
|
@@ -641,49 +629,14 @@ export class Sprinkle<S extends TSchema> {
|
|
|
641
629
|
network: TExact<typeof NetworkSchema>,
|
|
642
630
|
settings: TExact<typeof ProviderSettingsSchema>,
|
|
643
631
|
): Promise<Provider> {
|
|
644
|
-
|
|
645
|
-
case "blockfrost":
|
|
646
|
-
return new Blockfrost({
|
|
647
|
-
network: `cardano-${network}`,
|
|
648
|
-
projectId: settings.projectId,
|
|
649
|
-
});
|
|
650
|
-
case "maestro":
|
|
651
|
-
// Dynamic import - Maestro may or may not be exported depending on @blaze-cardano/query version
|
|
652
|
-
const queryModule = (await import("@blaze-cardano/query")) as any;
|
|
653
|
-
if (!queryModule.Maestro) {
|
|
654
|
-
throw new Error(
|
|
655
|
-
"Maestro is not available in the installed version of @blaze-cardano/query. Please install a version that includes Maestro support.",
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
return new queryModule.Maestro({
|
|
659
|
-
network: network as "mainnet" | "preview" | "preprod",
|
|
660
|
-
apiKey: settings.apiKey,
|
|
661
|
-
});
|
|
662
|
-
default:
|
|
663
|
-
throw new Error("Invalid provider type");
|
|
664
|
-
}
|
|
632
|
+
return GetProviderFn(network, settings);
|
|
665
633
|
}
|
|
666
634
|
|
|
667
635
|
static async GetWallet(
|
|
668
636
|
settings: TExact<typeof WalletSettingsSchema>,
|
|
669
637
|
provider: Provider,
|
|
670
638
|
): Promise<Wallet> {
|
|
671
|
-
|
|
672
|
-
case "hot":
|
|
673
|
-
return HotWallet.fromMasterkey(
|
|
674
|
-
Core.Bip32PrivateKeyHex(settings.privateKey),
|
|
675
|
-
provider,
|
|
676
|
-
provider.network,
|
|
677
|
-
);
|
|
678
|
-
case "cold":
|
|
679
|
-
return new ColdWallet(
|
|
680
|
-
Core.Address.fromBech32(settings.address),
|
|
681
|
-
provider.network,
|
|
682
|
-
provider,
|
|
683
|
-
);
|
|
684
|
-
default:
|
|
685
|
-
throw new Error("Invalid wallet type");
|
|
686
|
-
}
|
|
639
|
+
return GetWalletFn(settings, provider);
|
|
687
640
|
}
|
|
688
641
|
|
|
689
642
|
static async GetBlaze(
|
|
@@ -691,9 +644,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
691
644
|
providerSettings: TExact<typeof ProviderSettingsSchema>,
|
|
692
645
|
walletSettings: TExact<typeof WalletSettingsSchema>,
|
|
693
646
|
): Promise<Blaze<Provider, Wallet>> {
|
|
694
|
-
|
|
695
|
-
const wallet = await Sprinkle.GetWallet(walletSettings, provider);
|
|
696
|
-
return Blaze.from(provider, wallet);
|
|
647
|
+
return GetBlazeFn(network, providerSettings, walletSettings);
|
|
697
648
|
}
|
|
698
649
|
|
|
699
650
|
/**
|
|
@@ -702,39 +653,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
702
653
|
* @returns The Bip32PrivateKey hex string for storage
|
|
703
654
|
*/
|
|
704
655
|
private static async generateWalletFromMnemonic(): Promise<string> {
|
|
705
|
-
|
|
706
|
-
const words = mnemonic.split(" ");
|
|
707
|
-
|
|
708
|
-
console.log("\n=== NEW WALLET GENERATED ===\n");
|
|
709
|
-
console.log("IMPORTANT: Save these 24 words in a secure location.");
|
|
710
|
-
console.log("This is the ONLY way to recover your wallet.\n");
|
|
711
|
-
|
|
712
|
-
// Display in 4 columns
|
|
713
|
-
for (let i = 0; i < 6; i++) {
|
|
714
|
-
console.log(
|
|
715
|
-
`${(i + 1).toString().padStart(2)}. ${words[i]!.padEnd(12)} ` +
|
|
716
|
-
`${(i + 7).toString().padStart(2)}. ${words[i + 6]!.padEnd(12)} ` +
|
|
717
|
-
`${(i + 13).toString().padStart(2)}. ${words[i + 12]!.padEnd(12)} ` +
|
|
718
|
-
`${(i + 19).toString().padStart(2)}. ${words[i + 18]}`,
|
|
719
|
-
);
|
|
720
|
-
}
|
|
721
|
-
console.log("");
|
|
722
|
-
|
|
723
|
-
const confirmed = await confirm({
|
|
724
|
-
message: "Have you saved your recovery phrase?",
|
|
725
|
-
default: false,
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
if (!confirmed) {
|
|
729
|
-
throw new Error("Wallet generation cancelled - recovery phrase not saved");
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
const entropy = Core.mnemonicToEntropy(mnemonic, wordlist);
|
|
733
|
-
const masterKey = Core.Bip32PrivateKey.fromBip39Entropy(
|
|
734
|
-
Buffer.from(entropy),
|
|
735
|
-
"",
|
|
736
|
-
);
|
|
737
|
-
return masterKey.hex();
|
|
656
|
+
return generateWalletFromMnemonic();
|
|
738
657
|
}
|
|
739
658
|
|
|
740
659
|
static async SearchSelect<T>(opts: {
|
|
@@ -742,8 +661,8 @@ export class Sprinkle<S extends TSchema> {
|
|
|
742
661
|
source: (
|
|
743
662
|
term: string | undefined,
|
|
744
663
|
) => Promise<{ name: string; value: T }[]> | { name: string; value: T }[];
|
|
745
|
-
}): Promise<T> {
|
|
746
|
-
return
|
|
664
|
+
}): Promise<T | null> {
|
|
665
|
+
return searchCancellable(opts) as Promise<T | null>;
|
|
747
666
|
}
|
|
748
667
|
|
|
749
668
|
static SettingsPath(storagePath: string): string {
|
|
@@ -775,191 +694,25 @@ export class Sprinkle<S extends TSchema> {
|
|
|
775
694
|
}
|
|
776
695
|
|
|
777
696
|
static bigIntReviver(key: string, value: unknown): unknown {
|
|
778
|
-
|
|
779
|
-
return BigInt(value.slice(0, -1));
|
|
780
|
-
}
|
|
781
|
-
return value;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
private static bigIntReplacer(_key: string, value: unknown): unknown {
|
|
785
|
-
return typeof value === "bigint" ? `${value.toString()}n` : value;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
private static collectSensitivePaths(
|
|
789
|
-
type: TSchema,
|
|
790
|
-
prefix: string = "",
|
|
791
|
-
): string[] {
|
|
792
|
-
const paths: string[] = [];
|
|
793
|
-
if (isObject(type)) {
|
|
794
|
-
const fields = type["properties"] as Record<string, TSchema>;
|
|
795
|
-
for (const [field, fieldType] of Object.entries(fields)) {
|
|
796
|
-
const fieldPath = prefix ? `${prefix}.${field}` : field;
|
|
797
|
-
if (isSensitive(fieldType)) {
|
|
798
|
-
paths.push(fieldPath);
|
|
799
|
-
}
|
|
800
|
-
paths.push(...Sprinkle.collectSensitivePaths(fieldType, fieldPath));
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
if (isUnion(type)) {
|
|
804
|
-
for (const variant of type.anyOf) {
|
|
805
|
-
paths.push(...Sprinkle.collectSensitivePaths(variant, prefix));
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
return paths;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
private static getNestedValue(obj: any, path: string): unknown {
|
|
812
|
-
return path.split(".").reduce((o, k) => o?.[k], obj);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
private static setNestedValue(obj: any, path: string, value: unknown): void {
|
|
816
|
-
const keys = path.split(".");
|
|
817
|
-
const last = keys.pop()!;
|
|
818
|
-
const parent = keys.reduce((o, k) => o?.[k], obj);
|
|
819
|
-
if (parent && typeof parent === "object") {
|
|
820
|
-
parent[last] = value;
|
|
821
|
-
}
|
|
697
|
+
return bigIntReviver(key, value);
|
|
822
698
|
}
|
|
823
699
|
|
|
824
700
|
private encryptSettings(settings: TExact<S>): TExact<S> {
|
|
825
701
|
if (!this.options.encryption) return settings;
|
|
826
|
-
|
|
827
|
-
JSON.stringify(settings, Sprinkle.bigIntReplacer),
|
|
828
|
-
Sprinkle.bigIntReviver,
|
|
829
|
-
);
|
|
830
|
-
const sensitivePaths = Sprinkle.collectSensitivePaths(this.type);
|
|
831
|
-
for (const p of sensitivePaths) {
|
|
832
|
-
const value = Sprinkle.getNestedValue(clone, p);
|
|
833
|
-
if (typeof value === "string" && value.length > 0) {
|
|
834
|
-
Sprinkle.setNestedValue(
|
|
835
|
-
clone,
|
|
836
|
-
p,
|
|
837
|
-
this.options.encryption.encrypt(value),
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
return clone;
|
|
702
|
+
return encryptSensitiveFields(settings, this.type, this.options.encryption);
|
|
842
703
|
}
|
|
843
704
|
|
|
844
705
|
private async decryptSettings(settings: TExact<S>): Promise<TExact<S>> {
|
|
845
706
|
if (!this.options.encryption) return settings;
|
|
846
|
-
|
|
847
|
-
JSON.stringify(settings, Sprinkle.bigIntReplacer),
|
|
848
|
-
Sprinkle.bigIntReviver,
|
|
849
|
-
);
|
|
850
|
-
const sensitivePaths = Sprinkle.collectSensitivePaths(this.type);
|
|
851
|
-
for (const p of sensitivePaths) {
|
|
852
|
-
const value = Sprinkle.getNestedValue(clone, p);
|
|
853
|
-
if (typeof value === "string" && value.length > 0) {
|
|
854
|
-
Sprinkle.setNestedValue(
|
|
855
|
-
clone,
|
|
856
|
-
p,
|
|
857
|
-
await this.options.encryption.decrypt(value),
|
|
858
|
-
);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
return clone;
|
|
707
|
+
return decryptSensitiveFields(settings, this.type, this.options.encryption);
|
|
862
708
|
}
|
|
863
709
|
|
|
864
710
|
saveSettings(): void {
|
|
865
711
|
this.saveProfile();
|
|
866
712
|
}
|
|
867
713
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
* Get the payment key hash from a HotWallet's first address
|
|
872
|
-
*/
|
|
873
|
-
private async getWalletPaymentKeyHash(
|
|
874
|
-
wallet: HotWallet,
|
|
875
|
-
): Promise<string | null> {
|
|
876
|
-
try {
|
|
877
|
-
const addresses = await wallet.getUsedAddresses();
|
|
878
|
-
const address = addresses[0];
|
|
879
|
-
if (!address) return null;
|
|
880
|
-
const paymentCredential = address.asBase()?.getPaymentCredential();
|
|
881
|
-
return paymentCredential?.hash?.toString() ?? null;
|
|
882
|
-
} catch {
|
|
883
|
-
return null;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
/**
|
|
888
|
-
* Count the number of vkey signatures in a transaction's witness set
|
|
889
|
-
*/
|
|
890
|
-
private countSignatures(tx: Core.Transaction): number {
|
|
891
|
-
const vkeys = tx.witnessSet().vkeys();
|
|
892
|
-
return vkeys ? vkeys.size() : 0;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
/**
|
|
896
|
-
* Check if a specific public key has already signed the transaction
|
|
897
|
-
* Compares by vkey (public key bytes)
|
|
898
|
-
*/
|
|
899
|
-
private hasVkeySigned(tx: Core.Transaction, vkeyHex: string): boolean {
|
|
900
|
-
const vkeys = tx.witnessSet().vkeys();
|
|
901
|
-
if (!vkeys) return false;
|
|
902
|
-
const vkeyArray = vkeys.toCore();
|
|
903
|
-
return vkeyArray.some(([vkey]) => vkey === vkeyHex);
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
/**
|
|
907
|
-
* Get the list of required signer key hashes from the transaction body
|
|
908
|
-
*/
|
|
909
|
-
private getRequiredSigners(tx: Core.Transaction): string[] {
|
|
910
|
-
const requiredSigners = tx.body().requiredSigners();
|
|
911
|
-
if (!requiredSigners) return [];
|
|
912
|
-
return Array.from(requiredSigners.values()).map((s) => s.toString());
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
/**
|
|
916
|
-
* Compute the transaction body hash for display
|
|
917
|
-
*/
|
|
918
|
-
private getTxBodyHash(tx: Core.Transaction): string {
|
|
919
|
-
const bodyCbor = tx.body().toCbor();
|
|
920
|
-
return blake2b_256(bodyCbor);
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
/**
|
|
924
|
-
* Format a hash for display: first 8 chars + ... + last 8 chars
|
|
925
|
-
*/
|
|
926
|
-
private formatHash(hash: string): string {
|
|
927
|
-
if (hash.length <= 20) return hash;
|
|
928
|
-
return `${hash.slice(0, 8)}...${hash.slice(-8)}`;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
/**
|
|
932
|
-
* Merge signatures from source transaction into target transaction.
|
|
933
|
-
* Prevents duplicate signatures by comparing vkey (public key).
|
|
934
|
-
* Returns the count of newly added signatures.
|
|
935
|
-
*/
|
|
936
|
-
private mergeSignatures(
|
|
937
|
-
target: Core.Transaction,
|
|
938
|
-
source: Core.Transaction,
|
|
939
|
-
): number {
|
|
940
|
-
const targetWs = target.witnessSet();
|
|
941
|
-
const sourceWs = source.witnessSet();
|
|
942
|
-
|
|
943
|
-
const targetVkeys = targetWs.vkeys()?.toCore() ?? [];
|
|
944
|
-
const sourceVkeys = sourceWs.vkeys()?.toCore() ?? [];
|
|
945
|
-
|
|
946
|
-
// Find vkeys in source that aren't in target (by comparing public key)
|
|
947
|
-
const existingPubKeys = new Set(targetVkeys.map(([vkey]) => vkey));
|
|
948
|
-
const newVkeys = sourceVkeys.filter(
|
|
949
|
-
([vkey]) => !existingPubKeys.has(vkey),
|
|
950
|
-
);
|
|
951
|
-
|
|
952
|
-
if (newVkeys.length === 0) {
|
|
953
|
-
return 0;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// Merge the new vkeys into target
|
|
957
|
-
targetWs.setVkeys(
|
|
958
|
-
CborSet.fromCore([...targetVkeys, ...newVkeys], VkeyWitness.fromCore),
|
|
959
|
-
);
|
|
960
|
-
target.setWitnessSet(targetWs);
|
|
961
|
-
|
|
962
|
-
return newVkeys.length;
|
|
714
|
+
getDisplaySettings(): TExact<S> {
|
|
715
|
+
return maskSensitiveFields(this.settings, this.type);
|
|
963
716
|
}
|
|
964
717
|
|
|
965
718
|
async TxDialog<P extends Provider, W extends Wallet>(
|
|
@@ -993,17 +746,17 @@ export class Sprinkle<S extends TSchema> {
|
|
|
993
746
|
|
|
994
747
|
while (true) {
|
|
995
748
|
// Display transaction status
|
|
996
|
-
const txHash =
|
|
997
|
-
const sigCount =
|
|
998
|
-
const requiredSigners =
|
|
749
|
+
const txHash = getTxBodyHash(currentTx);
|
|
750
|
+
const sigCount = countSignatures(currentTx);
|
|
751
|
+
const requiredSigners = getRequiredSigners(currentTx);
|
|
999
752
|
|
|
1000
753
|
console.log("");
|
|
1001
|
-
console.log(`Transaction: ${
|
|
754
|
+
console.log(`Transaction: ${formatHash(txHash)}`);
|
|
1002
755
|
if (requiredSigners.length > 0) {
|
|
1003
756
|
console.log(`Signatures: ${sigCount} of ${requiredSigners.length} required`);
|
|
1004
757
|
console.log("Required signers:");
|
|
1005
758
|
for (const signer of requiredSigners) {
|
|
1006
|
-
console.log(` - ${
|
|
759
|
+
console.log(` - ${formatHash(signer)}`);
|
|
1007
760
|
}
|
|
1008
761
|
} else {
|
|
1009
762
|
console.log(`Signatures: ${sigCount}`);
|
|
@@ -1036,11 +789,19 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1036
789
|
choices.push({ name: "Submit transaction", value: "submit" });
|
|
1037
790
|
choices.push({ name: "Cancel", value: "cancel" });
|
|
1038
791
|
|
|
1039
|
-
const selection = await
|
|
792
|
+
const selection = await selectCancellable({
|
|
1040
793
|
message: "Select an option:",
|
|
1041
794
|
choices,
|
|
1042
795
|
});
|
|
1043
796
|
|
|
797
|
+
// Handle escape/cancel as cancel action
|
|
798
|
+
if (selection === null) {
|
|
799
|
+
if (hasSignedThisSession) {
|
|
800
|
+
return { action: "signed", tx: currentTx };
|
|
801
|
+
}
|
|
802
|
+
return { action: "cancelled", tx: currentTx };
|
|
803
|
+
}
|
|
804
|
+
|
|
1044
805
|
// Handle selection
|
|
1045
806
|
if (selection === "sign") {
|
|
1046
807
|
if (opts?.beforeSign) {
|
|
@@ -1126,7 +887,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1126
887
|
} else {
|
|
1127
888
|
const signedTx = await blaze.signTransaction(currentTx);
|
|
1128
889
|
// Merge signatures from signed tx into current tx
|
|
1129
|
-
const added =
|
|
890
|
+
const added = mergeSignatures(currentTx, signedTx);
|
|
1130
891
|
if (added > 0) {
|
|
1131
892
|
console.log(`Added ${added} signature(s).`);
|
|
1132
893
|
hasSignedThisSession = true;
|
|
@@ -1159,11 +920,11 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1159
920
|
}
|
|
1160
921
|
|
|
1161
922
|
if (selection === "import") {
|
|
1162
|
-
const cborInput = await
|
|
923
|
+
const cborInput = await inputCancellable({
|
|
1163
924
|
message: "Paste transaction CBOR (hex):",
|
|
1164
925
|
});
|
|
1165
926
|
|
|
1166
|
-
if (
|
|
927
|
+
if (cborInput === null || cborInput.trim() === "") {
|
|
1167
928
|
console.log("No CBOR provided.");
|
|
1168
929
|
continue;
|
|
1169
930
|
}
|
|
@@ -1174,22 +935,22 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1174
935
|
);
|
|
1175
936
|
|
|
1176
937
|
// Validate body hash matches
|
|
1177
|
-
const currentHash =
|
|
1178
|
-
const importedHash =
|
|
938
|
+
const currentHash = getTxBodyHash(currentTx);
|
|
939
|
+
const importedHash = getTxBodyHash(importedTx);
|
|
1179
940
|
|
|
1180
941
|
if (currentHash !== importedHash) {
|
|
1181
|
-
const proceed = await
|
|
1182
|
-
message: `Warning: Imported transaction has different body hash.\nCurrent: ${
|
|
942
|
+
const proceed = await confirmCancellable({
|
|
943
|
+
message: `Warning: Imported transaction has different body hash.\nCurrent: ${formatHash(currentHash)}\nImported: ${formatHash(importedHash)}\nProceed anyway?`,
|
|
1183
944
|
default: false,
|
|
1184
945
|
});
|
|
1185
|
-
if (!proceed) {
|
|
946
|
+
if (proceed === null || !proceed) {
|
|
1186
947
|
console.log("Import cancelled.");
|
|
1187
948
|
continue;
|
|
1188
949
|
}
|
|
1189
950
|
}
|
|
1190
951
|
|
|
1191
952
|
// Merge signatures
|
|
1192
|
-
const added =
|
|
953
|
+
const added = mergeSignatures(currentTx, importedTx);
|
|
1193
954
|
const sourceVkeys = importedTx.witnessSet().vkeys();
|
|
1194
955
|
const sourceCount = sourceVkeys ? sourceVkeys.size() : 0;
|
|
1195
956
|
const skipped = sourceCount - added;
|
|
@@ -1210,13 +971,13 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1210
971
|
}
|
|
1211
972
|
|
|
1212
973
|
if (selection === "submit") {
|
|
1213
|
-
const sigCount =
|
|
974
|
+
const sigCount = countSignatures(currentTx);
|
|
1214
975
|
if (sigCount === 0) {
|
|
1215
|
-
const proceed = await
|
|
976
|
+
const proceed = await confirmCancellable({
|
|
1216
977
|
message: "Warning: Transaction has no signatures. Submit anyway?",
|
|
1217
978
|
default: false,
|
|
1218
979
|
});
|
|
1219
|
-
if (!proceed) {
|
|
980
|
+
if (proceed === null || !proceed) {
|
|
1220
981
|
continue;
|
|
1221
982
|
}
|
|
1222
983
|
}
|
|
@@ -1331,7 +1092,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1331
1092
|
>;
|
|
1332
1093
|
}
|
|
1333
1094
|
if (isOptional(type)) {
|
|
1334
|
-
const shouldSet = await
|
|
1095
|
+
const shouldSet = await selectCancellable({
|
|
1335
1096
|
message: Sprinkle.ExtractMessage(
|
|
1336
1097
|
type,
|
|
1337
1098
|
`Set value for ${path.join(".")}?`,
|
|
@@ -1342,6 +1103,9 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1342
1103
|
],
|
|
1343
1104
|
default: def !== undefined,
|
|
1344
1105
|
});
|
|
1106
|
+
if (shouldSet === null) {
|
|
1107
|
+
throw new UserCancelledError();
|
|
1108
|
+
}
|
|
1345
1109
|
if (!shouldSet) {
|
|
1346
1110
|
return undefined as TExact<U>;
|
|
1347
1111
|
}
|
|
@@ -1360,7 +1124,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1360
1124
|
value: variant,
|
|
1361
1125
|
});
|
|
1362
1126
|
}
|
|
1363
|
-
const
|
|
1127
|
+
const selectionResult = await selectCancellable({
|
|
1364
1128
|
message: Sprinkle.ExtractMessage(
|
|
1365
1129
|
resolved,
|
|
1366
1130
|
`Enter a choice for ${path.join(".")}`,
|
|
@@ -1368,13 +1132,17 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1368
1132
|
choices: choices,
|
|
1369
1133
|
default: def ? `${def}` : undefined,
|
|
1370
1134
|
});
|
|
1135
|
+
if (selectionResult === null) {
|
|
1136
|
+
throw new UserCancelledError();
|
|
1137
|
+
}
|
|
1138
|
+
const selection = selectionResult as TSchema;
|
|
1371
1139
|
return this._fillInStruct(selection, path, defs) as Promise<TExact<U>>;
|
|
1372
1140
|
}
|
|
1373
1141
|
|
|
1374
1142
|
if (isString(type)) {
|
|
1375
1143
|
// Special handling for hot wallet private key - offer generation option
|
|
1376
1144
|
if (type.title === "Hot Wallet Private Key") {
|
|
1377
|
-
const choice = await
|
|
1145
|
+
const choice = await selectCancellable({
|
|
1378
1146
|
message: "Hot wallet setup:",
|
|
1379
1147
|
choices: [
|
|
1380
1148
|
{ name: "Enter existing private key", value: "existing" },
|
|
@@ -1382,11 +1150,19 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1382
1150
|
],
|
|
1383
1151
|
});
|
|
1384
1152
|
|
|
1153
|
+
if (choice === null) {
|
|
1154
|
+
throw new UserCancelledError();
|
|
1155
|
+
}
|
|
1385
1156
|
if (choice === "generate") {
|
|
1386
1157
|
return Sprinkle.generateWalletFromMnemonic() as Promise<TExact<U>>;
|
|
1387
1158
|
}
|
|
1388
1159
|
// Fall through to password prompt for "existing" choice
|
|
1389
|
-
const answer = await
|
|
1160
|
+
const answer = await passwordCancellable({
|
|
1161
|
+
message: "Enter your private key:",
|
|
1162
|
+
});
|
|
1163
|
+
if (answer === null) {
|
|
1164
|
+
throw new UserCancelledError();
|
|
1165
|
+
}
|
|
1390
1166
|
return answer as TExact<U>;
|
|
1391
1167
|
}
|
|
1392
1168
|
|
|
@@ -1397,18 +1173,23 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1397
1173
|
type,
|
|
1398
1174
|
`Enter a string for ${path.join(".")}`,
|
|
1399
1175
|
);
|
|
1400
|
-
let answer: string;
|
|
1176
|
+
let answer: string | null;
|
|
1401
1177
|
if (isSensitive(type)) {
|
|
1402
|
-
answer = await
|
|
1178
|
+
answer = await passwordCancellable({ message });
|
|
1403
1179
|
} else {
|
|
1404
|
-
answer = await
|
|
1405
|
-
|
|
1180
|
+
answer = await inputCancellable({ message, default: defaultString });
|
|
1181
|
+
if (answer !== null) {
|
|
1182
|
+
this.defaults["string"] = answer;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
if (answer === null) {
|
|
1186
|
+
throw new UserCancelledError();
|
|
1406
1187
|
}
|
|
1407
1188
|
return answer as TExact<U>;
|
|
1408
1189
|
}
|
|
1409
1190
|
|
|
1410
1191
|
if (isBigInt(type)) {
|
|
1411
|
-
const answer = await
|
|
1192
|
+
const answer = await inputCancellable({
|
|
1412
1193
|
message: Sprinkle.ExtractMessage(
|
|
1413
1194
|
type,
|
|
1414
1195
|
`Enter a bigint for ${path.join(".")}`,
|
|
@@ -1423,6 +1204,9 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1423
1204
|
}
|
|
1424
1205
|
},
|
|
1425
1206
|
});
|
|
1207
|
+
if (answer === null) {
|
|
1208
|
+
throw new UserCancelledError();
|
|
1209
|
+
}
|
|
1426
1210
|
return BigInt(answer) as TExact<U>;
|
|
1427
1211
|
}
|
|
1428
1212
|
|
|
@@ -1459,14 +1243,17 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1459
1243
|
defs,
|
|
1460
1244
|
);
|
|
1461
1245
|
arr.push(itemValue);
|
|
1462
|
-
const continueAnswer = await
|
|
1246
|
+
const continueAnswer = await selectCancellable({
|
|
1463
1247
|
message: `Add another item to ${path.join(".")}?`,
|
|
1464
1248
|
choices: [
|
|
1465
1249
|
{ name: "Yes", value: true },
|
|
1466
1250
|
{ name: "No", value: false },
|
|
1467
1251
|
],
|
|
1468
1252
|
});
|
|
1469
|
-
|
|
1253
|
+
if (continueAnswer === null) {
|
|
1254
|
+
throw new UserCancelledError();
|
|
1255
|
+
}
|
|
1256
|
+
addMore = continueAnswer as boolean;
|
|
1470
1257
|
}
|
|
1471
1258
|
return arr as TExact<U>;
|
|
1472
1259
|
}
|