@polkadot-apps/signer 0.1.1 → 0.1.2

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.
Files changed (2) hide show
  1. package/README.md +318 -0
  2. package/package.json +5 -4
package/README.md ADDED
@@ -0,0 +1,318 @@
1
+ # @polkadot-apps/signer
2
+
3
+ Multi-provider signer manager for Polkadot -- Host API, browser extensions, and dev accounts.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @polkadot-apps/signer
9
+ ```
10
+
11
+ **Optional peer dependency**: `@novasamatech/product-sdk` is required only when using the Host API provider (Polkadot Desktop/Mobile containers).
12
+
13
+ ```bash
14
+ pnpm add @novasamatech/product-sdk
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```typescript
20
+ import { SignerManager } from "@polkadot-apps/signer";
21
+
22
+ const manager = new SignerManager();
23
+
24
+ const result = await manager.connect();
25
+ if (!result.ok) throw result.error;
26
+
27
+ const accounts = result.value;
28
+ manager.selectAccount(accounts[0].address);
29
+
30
+ const signer = manager.getSigner();
31
+ // Pass `signer` to polkadot-api transaction calls
32
+ ```
33
+
34
+ ## Auto-detection
35
+
36
+ `connect()` detects the runtime environment and picks the best available provider:
37
+
38
+ - **Inside a container** (Polkadot Desktop/Mobile): tries Host API first, falls back to Extension.
39
+ - **Outside a container** (standalone browser): tries Extension first, falls back to Dev.
40
+
41
+ Override auto-detection by passing a provider type explicitly:
42
+
43
+ ```typescript
44
+ await manager.connect("dev"); // force dev accounts
45
+ await manager.connect("extension"); // force browser extension
46
+ await manager.connect("host"); // force Host API
47
+ ```
48
+
49
+ Check the environment programmatically:
50
+
51
+ ```typescript
52
+ import { isInsideContainer } from "@polkadot-apps/signer";
53
+
54
+ if (isInsideContainer()) {
55
+ console.log("Running inside Polkadot Desktop/Mobile");
56
+ }
57
+ ```
58
+
59
+ ## State management
60
+
61
+ Subscribe to state changes to drive your UI. The callback fires on every status transition, account list change, or account selection.
62
+
63
+ ```typescript
64
+ const unsubscribe = manager.subscribe((state) => {
65
+ console.log(state.status); // "disconnected" | "connecting" | "connected"
66
+ console.log(state.accounts); // readonly SignerAccount[]
67
+ console.log(state.selectedAccount); // SignerAccount | null
68
+ console.log(state.activeProvider); // "host" | "extension" | "dev" | null
69
+ console.log(state.error); // SignerError | null
70
+ });
71
+
72
+ // Get a snapshot without subscribing
73
+ const snapshot = manager.getState();
74
+
75
+ // Clean up
76
+ unsubscribe();
77
+ ```
78
+
79
+ ## Account selection and signing
80
+
81
+ ```typescript
82
+ const selectResult = manager.selectAccount("5GrwvaEF...");
83
+ if (!selectResult.ok) {
84
+ console.error(selectResult.error);
85
+ }
86
+
87
+ const signer = manager.getSigner(); // PolkadotSigner | null
88
+
89
+ // Sign raw bytes (for non-transaction signing)
90
+ const signResult = await manager.signRaw(new Uint8Array([1, 2, 3]));
91
+ if (signResult.ok) {
92
+ console.log(signResult.value); // Uint8Array signature
93
+ }
94
+ ```
95
+
96
+ ## Host API features
97
+
98
+ When connected via the Host API provider, additional methods are available for product accounts, aliases, and ring VRF proofs.
99
+
100
+ ```typescript
101
+ const productAccount = manager.getProductAccount("myDapp", 0);
102
+ const alias = manager.getProductAccountAlias("myDapp", 0);
103
+ const proof = await manager.createRingVRFProof("myDapp", 0, message);
104
+ ```
105
+
106
+ ## Providers
107
+
108
+ Each provider handles a different signing backend. You rarely need to use providers directly -- `SignerManager` manages them for you. They are exported for advanced use cases or testing.
109
+
110
+ ### DevProvider
111
+
112
+ Local dev accounts from the well-known Substrate mnemonic.
113
+
114
+ ```typescript
115
+ import { DevProvider } from "@polkadot-apps/signer";
116
+
117
+ const provider = new DevProvider({
118
+ names: ["Alice", "Bob"],
119
+ ss58Prefix: 42,
120
+ keyType: "sr25519", // or "ed25519"
121
+ });
122
+ ```
123
+
124
+ ### ExtensionProvider
125
+
126
+ Browser wallet extensions discovered via `window.injectedWeb3`.
127
+
128
+ ```typescript
129
+ import { ExtensionProvider } from "@polkadot-apps/signer";
130
+
131
+ const provider = new ExtensionProvider({
132
+ extensionName: "polkadot-js",
133
+ dappName: "My App",
134
+ injectionWait: 1_000,
135
+ });
136
+
137
+ // List available extensions
138
+ const extensions = await manager.getAvailableExtensions();
139
+ ```
140
+
141
+ ### HostProvider
142
+
143
+ Polkadot Desktop/Mobile via product-sdk. Requires `@novasamatech/product-sdk`.
144
+
145
+ ```typescript
146
+ import { HostProvider } from "@polkadot-apps/signer";
147
+
148
+ const provider = new HostProvider({
149
+ hostTimeout: 10_000,
150
+ dappName: "My App",
151
+ });
152
+
153
+ // Inject Spektr compatibility layer
154
+ HostProvider.injectSpektr();
155
+ ```
156
+
157
+ ## Cleanup
158
+
159
+ Always destroy the manager when your application unmounts to release resources and close connections.
160
+
161
+ ```typescript
162
+ manager.disconnect();
163
+ manager.destroy();
164
+ ```
165
+
166
+ ## Error handling
167
+
168
+ All errors extend `SignerError`. Use type guards to narrow errors by provider.
169
+
170
+ ```typescript
171
+ import {
172
+ isHostError,
173
+ isExtensionError,
174
+ HostUnavailableError,
175
+ ExtensionNotFoundError,
176
+ NoAccountsError,
177
+ } from "@polkadot-apps/signer";
178
+
179
+ const result = await manager.connect();
180
+ if (!result.ok) {
181
+ if (isHostError(result.error)) {
182
+ console.error("Host API problem:", result.error.message);
183
+ } else if (isExtensionError(result.error)) {
184
+ console.error("Extension problem:", result.error.message);
185
+ }
186
+ }
187
+ ```
188
+
189
+ ## API
190
+
191
+ ### `SignerManager`
192
+
193
+ #### `constructor(options?: SignerManagerOptions)`
194
+
195
+ Create a new manager instance.
196
+
197
+ #### `getState(): SignerState`
198
+
199
+ Return a snapshot of the current status, accounts, selected account, active provider, and error.
200
+
201
+ #### `subscribe(callback): () => void`
202
+
203
+ Subscribe to state changes. Returns an unsubscribe function.
204
+
205
+ | Parameter | Type | Description |
206
+ |-----------|------|-------------|
207
+ | `callback` | `(state: SignerState) => void` | Called on every state transition. |
208
+
209
+ #### `connect(providerType?): Promise<Result<SignerAccount[], SignerError>>`
210
+
211
+ Connect to accounts. Auto-detects the provider when no type is given.
212
+
213
+ | Parameter | Type | Description |
214
+ |-----------|------|-------------|
215
+ | `providerType` | `ProviderType` | Optional. `"host"`, `"extension"`, or `"dev"`. |
216
+
217
+ #### `disconnect(): void`
218
+
219
+ Disconnect the active provider and clear accounts.
220
+
221
+ #### `selectAccount(address): Result<SignerAccount, SignerError>`
222
+
223
+ Select an account by SS58 address.
224
+
225
+ #### `getSigner(): PolkadotSigner | null`
226
+
227
+ Return the signer for the currently selected account, or `null`.
228
+
229
+ #### `signRaw(data): Promise<Result<Uint8Array, SignerError>>`
230
+
231
+ Sign raw bytes with the selected account.
232
+
233
+ #### `getProductAccount(dappName, index): ProductAccount`
234
+
235
+ Host API only. Get a product account by dapp name and index.
236
+
237
+ #### `getProductAccountAlias(dappName, index): ContextualAlias`
238
+
239
+ Host API only. Get the contextual alias for a product account.
240
+
241
+ #### `createRingVRFProof(dappName, index, message): Promise<RingLocation>`
242
+
243
+ Host API only. Create a ring VRF proof.
244
+
245
+ #### `getAvailableExtensions(): Promise<string[]>`
246
+
247
+ List browser extension names found in `window.injectedWeb3`.
248
+
249
+ #### `destroy(): void`
250
+
251
+ Release all resources. The manager must not be used after this call.
252
+
253
+ ### Standalone functions
254
+
255
+ | Function | Signature | Description |
256
+ |----------|-----------|-------------|
257
+ | `isInsideContainer` | `() => boolean` | Synchronous container environment check. |
258
+ | `ok` | `<T>(value: T) => Result<T, never>` | Create a successful `Result`. |
259
+ | `err` | `<E>(error: E) => Result<never, E>` | Create a failed `Result`. |
260
+ | `isHostError` | `(e: SignerError) => boolean` | Type guard for Host API errors. |
261
+ | `isExtensionError` | `(e: SignerError) => boolean` | Type guard for extension errors. |
262
+
263
+ ## Types
264
+
265
+ ```typescript
266
+ type ConnectionStatus = "disconnected" | "connecting" | "connected";
267
+
268
+ type ProviderType = "host" | "extension" | "dev";
269
+
270
+ interface SignerAccount {
271
+ address: string;
272
+ h160Address: `0x${string}`;
273
+ publicKey: Uint8Array;
274
+ name: string | null;
275
+ source: ProviderType;
276
+ getSigner(): PolkadotSigner;
277
+ }
278
+
279
+ interface SignerState {
280
+ status: ConnectionStatus;
281
+ accounts: readonly SignerAccount[];
282
+ selectedAccount: SignerAccount | null;
283
+ activeProvider: ProviderType | null;
284
+ error: SignerError | null;
285
+ }
286
+
287
+ interface SignerManagerOptions {
288
+ ss58Prefix?: number; // default: 42
289
+ hostTimeout?: number; // default: 10_000
290
+ extensionTimeout?: number; // default: 1_000
291
+ maxRetries?: number; // default: 3
292
+ createProvider?: ProviderFactory;
293
+ dappName?: string; // default: "polkadot-app"
294
+ persistence?: AccountPersistence | null;
295
+ }
296
+
297
+ type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
298
+ ```
299
+
300
+ ### Error classes
301
+
302
+ | Class | Extends | When thrown |
303
+ |-------|---------|------------|
304
+ | `SignerError` | `Error` | Base class for all signer errors. |
305
+ | `HostUnavailableError` | `SignerError` | Host API not found or not responding. |
306
+ | `HostRejectedError` | `SignerError` | Host API rejected the request. |
307
+ | `HostDisconnectedError` | `SignerError` | Host API connection lost. |
308
+ | `ExtensionNotFoundError` | `SignerError` | Browser extension not installed or not injected. |
309
+ | `ExtensionRejectedError` | `SignerError` | User rejected the extension prompt. |
310
+ | `SigningFailedError` | `SignerError` | Signing operation failed. |
311
+ | `NoAccountsError` | `SignerError` | Provider returned zero accounts. |
312
+ | `TimeoutError` | `SignerError` | Operation timed out. |
313
+ | `AccountNotFoundError` | `SignerError` | Selected address not in the account list. |
314
+ | `DestroyedError` | `SignerError` | Method called after `destroy()`. |
315
+
316
+ ## License
317
+
318
+ Apache-2.0
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@polkadot-apps/signer",
3
- "version": "0.1.1",
3
+ "description": "Multi-provider signer manager for Polkadot — Host API, browser extensions, and dev accounts",
4
+ "version": "0.1.2",
4
5
  "type": "module",
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",
@@ -19,9 +20,9 @@
19
20
  },
20
21
  "dependencies": {
21
22
  "polkadot-api": "^1.23.3",
22
- "@polkadot-apps/address": "0.3.1",
23
- "@polkadot-apps/keys": "0.3.2",
24
- "@polkadot-apps/logger": "0.1.3"
23
+ "@polkadot-apps/address": "0.3.2",
24
+ "@polkadot-apps/keys": "0.3.3",
25
+ "@polkadot-apps/logger": "0.1.4"
25
26
  },
26
27
  "peerDependencies": {
27
28
  "@novasamatech/product-sdk": "^0.6.6"