@sundaeswap/sprinkles 0.2.1 → 0.3.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__/fill-in-struct.test.js +52 -1
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/cjs/Sprinkle/index.js +52 -1
- package/dist/cjs/Sprinkle/index.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +53 -2
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/esm/Sprinkle/index.js +53 -2
- package/dist/esm/Sprinkle/index.js.map +1 -1
- package/dist/types/Sprinkle/index.d.ts +6 -0
- package/dist/types/Sprinkle/index.d.ts.map +1 -1
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Sprinkle/__tests__/fill-in-struct.test.ts +55 -2
- package/src/Sprinkle/index.ts +71 -2
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { describe, expect, test, mock, beforeEach } from "bun:test";
|
|
2
|
-
import { Sprinkle, Type } from "../index.js";
|
|
1
|
+
import { describe, expect, test, mock, beforeEach, spyOn } from "bun:test";
|
|
2
|
+
import { Sprinkle, Type, WalletSettingsSchema } from "../index.js";
|
|
3
3
|
|
|
4
4
|
// Mock @inquirer/prompts
|
|
5
5
|
const mockSelect = mock();
|
|
6
6
|
const mockInput = mock();
|
|
7
|
+
const mockPassword = mock();
|
|
8
|
+
const mockConfirm = mock();
|
|
7
9
|
|
|
8
10
|
mock.module("@inquirer/prompts", () => ({
|
|
9
11
|
select: mockSelect,
|
|
10
12
|
input: mockInput,
|
|
13
|
+
password: mockPassword,
|
|
14
|
+
confirm: mockConfirm,
|
|
11
15
|
}));
|
|
12
16
|
|
|
13
17
|
describe("FillInStruct", () => {
|
|
@@ -204,4 +208,53 @@ describe("FillInStruct", () => {
|
|
|
204
208
|
const result = await sprinkle.FillInStruct(schema);
|
|
205
209
|
expect(result).toEqual({ asset: ["policy123", "token456"] });
|
|
206
210
|
});
|
|
211
|
+
|
|
212
|
+
test("hot wallet private key shows setup choice", async () => {
|
|
213
|
+
const schema = Type.String({ title: "Hot Wallet Private Key" });
|
|
214
|
+
|
|
215
|
+
// Select "existing" option
|
|
216
|
+
mockSelect.mockResolvedValueOnce("existing");
|
|
217
|
+
mockPassword.mockResolvedValueOnce("my-private-key");
|
|
218
|
+
|
|
219
|
+
const result = await sprinkle.FillInStruct(schema);
|
|
220
|
+
|
|
221
|
+
// Verify select was called with correct options
|
|
222
|
+
expect(mockSelect.mock.calls[0][0].message).toBe("Hot wallet setup:");
|
|
223
|
+
expect(mockSelect.mock.calls[0][0].choices).toEqual([
|
|
224
|
+
{ name: "Enter existing private key", value: "existing" },
|
|
225
|
+
{ name: "Generate new wallet", value: "generate" },
|
|
226
|
+
]);
|
|
227
|
+
expect(result).toBe("my-private-key");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("hot wallet existing key prompts for password", async () => {
|
|
231
|
+
const schema = Type.String({ title: "Hot Wallet Private Key" });
|
|
232
|
+
|
|
233
|
+
mockSelect.mockResolvedValueOnce("existing");
|
|
234
|
+
mockPassword.mockResolvedValueOnce("deadbeef1234");
|
|
235
|
+
|
|
236
|
+
const result = await sprinkle.FillInStruct(schema);
|
|
237
|
+
|
|
238
|
+
expect(mockPassword).toHaveBeenCalledWith({
|
|
239
|
+
message: "Enter your private key:",
|
|
240
|
+
});
|
|
241
|
+
expect(result).toBe("deadbeef1234");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("full wallet settings schema with existing key", async () => {
|
|
245
|
+
// Select "hot" variant
|
|
246
|
+
mockSelect.mockImplementationOnce(async (opts: any) => {
|
|
247
|
+
return opts.choices[0].value; // hot wallet object
|
|
248
|
+
});
|
|
249
|
+
// Select "existing" key option
|
|
250
|
+
mockSelect.mockResolvedValueOnce("existing");
|
|
251
|
+
mockPassword.mockResolvedValueOnce("abc123privatekey");
|
|
252
|
+
|
|
253
|
+
const result = await sprinkle.FillInStruct(WalletSettingsSchema);
|
|
254
|
+
|
|
255
|
+
expect(result).toEqual({
|
|
256
|
+
type: "hot",
|
|
257
|
+
privateKey: "abc123privatekey",
|
|
258
|
+
});
|
|
259
|
+
});
|
|
207
260
|
});
|
package/src/Sprinkle/index.ts
CHANGED
|
@@ -6,7 +6,13 @@ import {
|
|
|
6
6
|
HotWallet,
|
|
7
7
|
type Wallet,
|
|
8
8
|
} from "@blaze-cardano/sdk";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
CborSet,
|
|
11
|
+
VkeyWitness,
|
|
12
|
+
blake2b_256,
|
|
13
|
+
TxCBOR,
|
|
14
|
+
wordlist,
|
|
15
|
+
} from "@blaze-cardano/core";
|
|
10
16
|
import { confirm, input, password, search, select } from "@inquirer/prompts";
|
|
11
17
|
import {
|
|
12
18
|
Kind,
|
|
@@ -167,7 +173,11 @@ export const ProviderSettingsSchema = Type.Union([
|
|
|
167
173
|
export const WalletSettingsSchema = Type.Union([
|
|
168
174
|
Type.Object({
|
|
169
175
|
type: Type.Literal("hot"),
|
|
170
|
-
privateKey: Type.String({
|
|
176
|
+
privateKey: Type.String({
|
|
177
|
+
minLength: 1,
|
|
178
|
+
title: "Hot Wallet Private Key",
|
|
179
|
+
sensitive: true,
|
|
180
|
+
}),
|
|
171
181
|
}),
|
|
172
182
|
Type.Object({
|
|
173
183
|
type: Type.Literal("cold"),
|
|
@@ -686,6 +696,47 @@ export class Sprinkle<S extends TSchema> {
|
|
|
686
696
|
return Blaze.from(provider, wallet);
|
|
687
697
|
}
|
|
688
698
|
|
|
699
|
+
/**
|
|
700
|
+
* Generates a new wallet from a BIP39 mnemonic phrase.
|
|
701
|
+
* Displays the 24-word recovery phrase and requires user confirmation.
|
|
702
|
+
* @returns The Bip32PrivateKey hex string for storage
|
|
703
|
+
*/
|
|
704
|
+
private static async generateWalletFromMnemonic(): Promise<string> {
|
|
705
|
+
const mnemonic = Core.generateMnemonic(wordlist, 256); // 24 words
|
|
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();
|
|
738
|
+
}
|
|
739
|
+
|
|
689
740
|
static async SearchSelect<T>(opts: {
|
|
690
741
|
message: string;
|
|
691
742
|
source: (
|
|
@@ -1321,6 +1372,24 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1321
1372
|
}
|
|
1322
1373
|
|
|
1323
1374
|
if (isString(type)) {
|
|
1375
|
+
// Special handling for hot wallet private key - offer generation option
|
|
1376
|
+
if (type.title === "Hot Wallet Private Key") {
|
|
1377
|
+
const choice = await select({
|
|
1378
|
+
message: "Hot wallet setup:",
|
|
1379
|
+
choices: [
|
|
1380
|
+
{ name: "Enter existing private key", value: "existing" },
|
|
1381
|
+
{ name: "Generate new wallet", value: "generate" },
|
|
1382
|
+
],
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
if (choice === "generate") {
|
|
1386
|
+
return Sprinkle.generateWalletFromMnemonic() as Promise<TExact<U>>;
|
|
1387
|
+
}
|
|
1388
|
+
// Fall through to password prompt for "existing" choice
|
|
1389
|
+
const answer = await password({ message: "Enter your private key:" });
|
|
1390
|
+
return answer as TExact<U>;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1324
1393
|
const defaultString = (def ? def : this.defaults["string"]) as
|
|
1325
1394
|
| string
|
|
1326
1395
|
| undefined;
|