@parity/product-sdk-signer 0.1.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/LICENSE +201 -0
- package/dist/index.d.ts +558 -0
- package/dist/index.js +1578 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
- package/src/errors.ts +207 -0
- package/src/index.ts +46 -0
- package/src/providers/dev.ts +293 -0
- package/src/providers/host.ts +694 -0
- package/src/providers/index.ts +15 -0
- package/src/providers/types.ts +50 -0
- package/src/retry.ts +250 -0
- package/src/signer-manager.ts +592 -0
- package/src/sleep.ts +102 -0
- package/src/types.ts +177 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { seedToAccount } from "@parity/product-sdk-keys";
|
|
2
|
+
import { createLogger } from "@parity/product-sdk-logger";
|
|
3
|
+
|
|
4
|
+
import type { SignerError } from "../errors.js";
|
|
5
|
+
import type { Result, SignerAccount } from "../types.js";
|
|
6
|
+
import { ok } from "../types.js";
|
|
7
|
+
import type { SignerProvider, Unsubscribe } from "./types.js";
|
|
8
|
+
|
|
9
|
+
const log = createLogger("signer:dev");
|
|
10
|
+
|
|
11
|
+
/** The well-known Substrate development mnemonic phrase. */
|
|
12
|
+
const DEV_PHRASE = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
|
13
|
+
|
|
14
|
+
/** Standard Substrate dev account names. */
|
|
15
|
+
const DEFAULT_DEV_NAMES = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"] as const;
|
|
16
|
+
|
|
17
|
+
export type DevAccountName = (typeof DEFAULT_DEV_NAMES)[number];
|
|
18
|
+
|
|
19
|
+
/** Supported key types for dev account derivation. */
|
|
20
|
+
export type DevKeyType = "sr25519" | "ed25519";
|
|
21
|
+
|
|
22
|
+
/** Options for the dev account provider. */
|
|
23
|
+
export interface DevProviderOptions {
|
|
24
|
+
/** Which dev accounts to create. Default: all 6 standard accounts. */
|
|
25
|
+
names?: readonly string[];
|
|
26
|
+
/** Custom mnemonic phrase instead of DEV_PHRASE. */
|
|
27
|
+
mnemonic?: string;
|
|
28
|
+
/** SS58 prefix for address encoding. Default: 42 */
|
|
29
|
+
ss58Prefix?: number;
|
|
30
|
+
/** Key type for account derivation. Default: "sr25519" */
|
|
31
|
+
keyType?: DevKeyType;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Provider for Substrate development accounts.
|
|
36
|
+
*
|
|
37
|
+
* Uses the well-known DEV_PHRASE with hard derivation paths (`//Alice`, `//Bob`, etc.)
|
|
38
|
+
* to create deterministic accounts for local development and testing.
|
|
39
|
+
*/
|
|
40
|
+
export class DevProvider implements SignerProvider {
|
|
41
|
+
readonly type = "dev" as const;
|
|
42
|
+
private readonly names: readonly string[];
|
|
43
|
+
private readonly mnemonic: string;
|
|
44
|
+
private readonly ss58Prefix: number;
|
|
45
|
+
private readonly keyType: DevKeyType;
|
|
46
|
+
|
|
47
|
+
constructor(options?: DevProviderOptions) {
|
|
48
|
+
this.names = options?.names ?? DEFAULT_DEV_NAMES;
|
|
49
|
+
this.mnemonic = options?.mnemonic ?? DEV_PHRASE;
|
|
50
|
+
this.ss58Prefix = options?.ss58Prefix ?? 42;
|
|
51
|
+
this.keyType = options?.keyType ?? "sr25519";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async connect(): Promise<Result<SignerAccount[], SignerError>> {
|
|
55
|
+
log.debug("creating dev accounts", { names: this.names, keyType: this.keyType });
|
|
56
|
+
|
|
57
|
+
const accounts: SignerAccount[] = this.names.map((name) => {
|
|
58
|
+
const derived = seedToAccount(
|
|
59
|
+
this.mnemonic,
|
|
60
|
+
`//${name}`,
|
|
61
|
+
this.ss58Prefix,
|
|
62
|
+
this.keyType,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
address: derived.ss58Address,
|
|
67
|
+
h160Address: derived.h160Address,
|
|
68
|
+
publicKey: derived.publicKey,
|
|
69
|
+
name,
|
|
70
|
+
source: "dev" as const,
|
|
71
|
+
getSigner: () => derived.signer,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
log.info("dev accounts ready", { count: accounts.length });
|
|
76
|
+
return ok(accounts);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
disconnect(): void {
|
|
80
|
+
// Dev accounts are stateless — nothing to clean up.
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onStatusChange(
|
|
84
|
+
_callback: (status: "disconnected" | "connecting" | "connected") => void,
|
|
85
|
+
): Unsubscribe {
|
|
86
|
+
// Dev accounts are always "connected" — no status changes to emit.
|
|
87
|
+
return () => {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
onAccountsChange(_callback: (accounts: SignerAccount[]) => void): Unsubscribe {
|
|
91
|
+
// Dev accounts are static — no account changes to emit.
|
|
92
|
+
return () => {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (import.meta.vitest) {
|
|
97
|
+
const { test, expect, describe } = import.meta.vitest;
|
|
98
|
+
|
|
99
|
+
// Well-known Alice address on generic substrate (prefix 42)
|
|
100
|
+
const ALICE_ADDRESS = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
|
|
101
|
+
|
|
102
|
+
describe("DevProvider", () => {
|
|
103
|
+
test("connect returns 6 accounts by default", async () => {
|
|
104
|
+
const provider = new DevProvider();
|
|
105
|
+
const result = await provider.connect();
|
|
106
|
+
expect(result.ok).toBe(true);
|
|
107
|
+
if (result.ok) {
|
|
108
|
+
expect(result.value).toHaveLength(6);
|
|
109
|
+
expect(result.value.map((a) => a.name)).toEqual([
|
|
110
|
+
"Alice",
|
|
111
|
+
"Bob",
|
|
112
|
+
"Charlie",
|
|
113
|
+
"Dave",
|
|
114
|
+
"Eve",
|
|
115
|
+
"Ferdie",
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("all accounts have source 'dev'", async () => {
|
|
121
|
+
const provider = new DevProvider();
|
|
122
|
+
const result = await provider.connect();
|
|
123
|
+
if (result.ok) {
|
|
124
|
+
for (const account of result.value) {
|
|
125
|
+
expect(account.source).toBe("dev");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("Alice has well-known address", async () => {
|
|
131
|
+
const provider = new DevProvider();
|
|
132
|
+
const result = await provider.connect();
|
|
133
|
+
if (result.ok) {
|
|
134
|
+
expect(result.value[0].address).toBe(ALICE_ADDRESS);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("addresses are deterministic", async () => {
|
|
139
|
+
const a = new DevProvider();
|
|
140
|
+
const b = new DevProvider();
|
|
141
|
+
const ra = await a.connect();
|
|
142
|
+
const rb = await b.connect();
|
|
143
|
+
if (ra.ok && rb.ok) {
|
|
144
|
+
expect(ra.value.map((x) => x.address)).toEqual(rb.value.map((x) => x.address));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("each account has 32-byte publicKey", async () => {
|
|
149
|
+
const provider = new DevProvider();
|
|
150
|
+
const result = await provider.connect();
|
|
151
|
+
if (result.ok) {
|
|
152
|
+
for (const account of result.value) {
|
|
153
|
+
expect(account.publicKey).toBeInstanceOf(Uint8Array);
|
|
154
|
+
expect(account.publicKey.length).toBe(32);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("getSigner returns signer with matching publicKey", async () => {
|
|
160
|
+
const provider = new DevProvider();
|
|
161
|
+
const result = await provider.connect();
|
|
162
|
+
if (result.ok) {
|
|
163
|
+
for (const account of result.value) {
|
|
164
|
+
const signer = account.getSigner();
|
|
165
|
+
expect(signer.publicKey).toEqual(account.publicKey);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("custom names subset", async () => {
|
|
171
|
+
const provider = new DevProvider({ names: ["Alice", "Bob"] });
|
|
172
|
+
const result = await provider.connect();
|
|
173
|
+
if (result.ok) {
|
|
174
|
+
expect(result.value).toHaveLength(2);
|
|
175
|
+
expect(result.value.map((a) => a.name)).toEqual(["Alice", "Bob"]);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("custom mnemonic produces different addresses", async () => {
|
|
180
|
+
const customMnemonic =
|
|
181
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
|
182
|
+
const defaultProvider = new DevProvider();
|
|
183
|
+
const customProvider = new DevProvider({ mnemonic: customMnemonic });
|
|
184
|
+
const defResult = await defaultProvider.connect();
|
|
185
|
+
const cusResult = await customProvider.connect();
|
|
186
|
+
if (defResult.ok && cusResult.ok) {
|
|
187
|
+
expect(defResult.value[0].address).not.toBe(cusResult.value[0].address);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("custom ss58Prefix changes address encoding", async () => {
|
|
192
|
+
const generic = new DevProvider({ ss58Prefix: 42 });
|
|
193
|
+
const polkadot = new DevProvider({ ss58Prefix: 0 });
|
|
194
|
+
const rg = await generic.connect();
|
|
195
|
+
const rp = await polkadot.connect();
|
|
196
|
+
if (rg.ok && rp.ok) {
|
|
197
|
+
// Different address strings
|
|
198
|
+
expect(rg.value[0].address).not.toBe(rp.value[0].address);
|
|
199
|
+
// Same underlying public key
|
|
200
|
+
expect(rg.value[0].publicKey).toEqual(rp.value[0].publicKey);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("disconnect is idempotent", () => {
|
|
205
|
+
const provider = new DevProvider();
|
|
206
|
+
// Should not throw
|
|
207
|
+
provider.disconnect();
|
|
208
|
+
provider.disconnect();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("onStatusChange returns no-op unsubscribe", () => {
|
|
212
|
+
const provider = new DevProvider();
|
|
213
|
+
const callback = () => {};
|
|
214
|
+
const unsub = provider.onStatusChange(callback);
|
|
215
|
+
expect(typeof unsub).toBe("function");
|
|
216
|
+
unsub(); // should not throw
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("onAccountsChange returns no-op unsubscribe", () => {
|
|
220
|
+
const provider = new DevProvider();
|
|
221
|
+
const callback = () => {};
|
|
222
|
+
const unsub = provider.onAccountsChange(callback);
|
|
223
|
+
expect(typeof unsub).toBe("function");
|
|
224
|
+
unsub(); // should not throw
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("type is 'dev'", () => {
|
|
228
|
+
const provider = new DevProvider();
|
|
229
|
+
expect(provider.type).toBe("dev");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("empty names array returns zero accounts", async () => {
|
|
233
|
+
const provider = new DevProvider({ names: [] });
|
|
234
|
+
const result = await provider.connect();
|
|
235
|
+
if (result.ok) {
|
|
236
|
+
expect(result.value).toHaveLength(0);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("default keyType is sr25519 (backward compatible)", async () => {
|
|
241
|
+
const provider = new DevProvider();
|
|
242
|
+
const result = await provider.connect();
|
|
243
|
+
if (result.ok) {
|
|
244
|
+
// Alice address matches well-known sr25519 address
|
|
245
|
+
expect(result.value[0].address).toBe(ALICE_ADDRESS);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("DevProvider ed25519", () => {
|
|
251
|
+
test("ed25519 produces different addresses than sr25519", async () => {
|
|
252
|
+
const sr = new DevProvider({ keyType: "sr25519" });
|
|
253
|
+
const ed = new DevProvider({ keyType: "ed25519" });
|
|
254
|
+
const srResult = await sr.connect();
|
|
255
|
+
const edResult = await ed.connect();
|
|
256
|
+
if (srResult.ok && edResult.ok) {
|
|
257
|
+
expect(srResult.value[0].address).not.toBe(edResult.value[0].address);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("ed25519 addresses are deterministic", async () => {
|
|
262
|
+
const a = new DevProvider({ keyType: "ed25519" });
|
|
263
|
+
const b = new DevProvider({ keyType: "ed25519" });
|
|
264
|
+
const ra = await a.connect();
|
|
265
|
+
const rb = await b.connect();
|
|
266
|
+
if (ra.ok && rb.ok) {
|
|
267
|
+
expect(ra.value.map((x) => x.address)).toEqual(rb.value.map((x) => x.address));
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("ed25519 getSigner has matching publicKey", async () => {
|
|
272
|
+
const provider = new DevProvider({ keyType: "ed25519" });
|
|
273
|
+
const result = await provider.connect();
|
|
274
|
+
if (result.ok) {
|
|
275
|
+
for (const account of result.value) {
|
|
276
|
+
const signer = account.getSigner();
|
|
277
|
+
expect(signer.publicKey).toEqual(account.publicKey);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("ed25519 accounts have 32-byte publicKey", async () => {
|
|
283
|
+
const provider = new DevProvider({ keyType: "ed25519" });
|
|
284
|
+
const result = await provider.connect();
|
|
285
|
+
if (result.ok) {
|
|
286
|
+
for (const account of result.value) {
|
|
287
|
+
expect(account.publicKey).toBeInstanceOf(Uint8Array);
|
|
288
|
+
expect(account.publicKey.length).toBe(32);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|