@parity/product-sdk-signer 0.8.2 → 0.9.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/index.d.ts +37 -66
- package/dist/index.js +38 -42
- package/dist/index.js.map +1 -1
- package/package.json +3 -7
- package/src/errors.ts +2 -1
- package/src/providers/host.ts +198 -365
- package/src/signer-manager.ts +30 -1
- package/src/types.ts +10 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as polkadot_api from 'polkadot-api';
|
|
2
2
|
import { PolkadotSigner } from 'polkadot-api';
|
|
3
3
|
import { SS58String } from '@parity/product-sdk-address';
|
|
4
|
-
import { AllocatableResource, AllocationOutcome } from '@parity/product-sdk-host';
|
|
4
|
+
import { AllocatableResource, AllocationOutcome, RemotePermission } from '@parity/product-sdk-host';
|
|
5
5
|
|
|
6
6
|
/** Function that unsubscribes a listener when called. */
|
|
7
7
|
type Unsubscribe = () => void;
|
|
@@ -151,10 +151,11 @@ interface SignerManagerOptions {
|
|
|
151
151
|
* plus an `AbortSignal` that fires if the user disconnects or
|
|
152
152
|
* destroys the manager mid-flight.
|
|
153
153
|
*
|
|
154
|
-
* `requestResourceAllocation` throws on failure (
|
|
155
|
-
* `@parity/product-sdk-host` export of the same name
|
|
156
|
-
*
|
|
157
|
-
* the
|
|
154
|
+
* `requestResourceAllocation` throws on failure (it adapts the
|
|
155
|
+
* `Result`-returning `@parity/product-sdk-host` export of the same name,
|
|
156
|
+
* re-throwing the typed error on the `err` channel); errors thrown from
|
|
157
|
+
* `onConnect` are logged but do not affect the connected state — the next
|
|
158
|
+
* reconnect retries.
|
|
158
159
|
*
|
|
159
160
|
* @example
|
|
160
161
|
* ```ts
|
|
@@ -165,7 +166,7 @@ interface SignerManagerOptions {
|
|
|
165
166
|
* { tag: "AutoSigning", value: undefined },
|
|
166
167
|
* ]);
|
|
167
168
|
* if (signal.aborted) return;
|
|
168
|
-
* if (outcomes.some((o) => o
|
|
169
|
+
* if (outcomes.some((o) => o !== "Allocated")) {
|
|
169
170
|
* logWarning("partial permissions", outcomes);
|
|
170
171
|
* }
|
|
171
172
|
* } catch (cause) {
|
|
@@ -186,9 +187,10 @@ interface ConnectContext {
|
|
|
186
187
|
*/
|
|
187
188
|
signal: AbortSignal;
|
|
188
189
|
/**
|
|
189
|
-
* Request a batch of host resource allocations.
|
|
190
|
-
* `requestResourceAllocation` from `@parity/product-sdk-host`
|
|
191
|
-
*
|
|
190
|
+
* Request a batch of host resource allocations. Adapts
|
|
191
|
+
* `requestResourceAllocation` from `@parity/product-sdk-host` (which returns
|
|
192
|
+
* a `Result`) to this throwing contract — returns the unwrapped outcomes on
|
|
193
|
+
* success, throws the typed host error on failure.
|
|
192
194
|
*/
|
|
193
195
|
requestResourceAllocation: (resources: AllocatableResource[]) => Promise<AllocationOutcome[]>;
|
|
194
196
|
}
|
|
@@ -206,7 +208,8 @@ declare class SignerError extends Error {
|
|
|
206
208
|
* - The app is loaded outside a Polkadot host container (a regular browser tab
|
|
207
209
|
* under `npm run dev`, no iframe, no WebView). This is the dominant case
|
|
208
210
|
* during local development.
|
|
209
|
-
* - The
|
|
211
|
+
* - The host environment is detected but the TruAPI transport can't be reached
|
|
212
|
+
* (no injected message port / unresolvable host origin).
|
|
210
213
|
*
|
|
211
214
|
* Branch with `instanceof HostUnavailableError` to surface a "open this app
|
|
212
215
|
* in a Polkadot host, or pick a dev provider" message to the user.
|
|
@@ -274,17 +277,19 @@ interface HostProviderOptions {
|
|
|
274
277
|
*/
|
|
275
278
|
dappName?: string;
|
|
276
279
|
/**
|
|
277
|
-
* Custom
|
|
278
|
-
*
|
|
280
|
+
* Custom accounts-provider loader. Defaults to `@parity/product-sdk-host`'s
|
|
281
|
+
* `getAccountsProvider`, which returns `null` outside a host container.
|
|
282
|
+
* Override for testing or custom host setups.
|
|
279
283
|
* @internal
|
|
280
284
|
*/
|
|
281
|
-
|
|
285
|
+
loadAccountsProvider?: () => Promise<AccountsProvider | null>;
|
|
282
286
|
/**
|
|
283
|
-
* Custom
|
|
284
|
-
* `
|
|
287
|
+
* Custom `ChainSubmit` permission requester. Defaults to a thin adapter over
|
|
288
|
+
* `@parity/product-sdk-host`'s `requestPermission` that unwraps its `Result`
|
|
289
|
+
* (throwing the typed error on the `err` channel). Override for testing.
|
|
285
290
|
* @internal
|
|
286
291
|
*/
|
|
287
|
-
|
|
292
|
+
requestChainSubmitPermissionFn?: (permission: RemotePermission) => Promise<boolean>;
|
|
288
293
|
/**
|
|
289
294
|
* Whether to request the host's `ChainSubmit` permission after a
|
|
290
295
|
* successful `connect()`. Without this, subsequent signing requests are
|
|
@@ -304,7 +309,7 @@ interface HostProviderOptions {
|
|
|
304
309
|
* `dotNsIdentifier`, skipping the legacy fetch entirely. For apps
|
|
305
310
|
* that sign exclusively with a per-dapp derived account.
|
|
306
311
|
*
|
|
307
|
-
* Signing
|
|
312
|
+
* Signing goes through the host's `createTransaction` path (see PR #96).
|
|
308
313
|
*/
|
|
309
314
|
productAccount?: {
|
|
310
315
|
/** App identifier (e.g., `"playground.dot"`). */
|
|
@@ -373,9 +378,12 @@ interface NeverthrowResultAsync<T, E> {
|
|
|
373
378
|
}
|
|
374
379
|
/** @internal */
|
|
375
380
|
interface AccountsProvider {
|
|
376
|
-
|
|
381
|
+
getLegacyAccounts: () => NeverthrowResultAsync<RawAccount[], unknown>;
|
|
382
|
+
getLegacyAccountSigner: (account: {
|
|
383
|
+
publicKey: Uint8Array;
|
|
384
|
+
}) => polkadot_api.PolkadotSigner;
|
|
377
385
|
getProductAccount: (dotNsIdentifier: string, derivationIndex?: number) => NeverthrowResultAsync<RawAccount, unknown>;
|
|
378
|
-
getProductAccountSigner: (account: ProductAccount
|
|
386
|
+
getProductAccountSigner: (account: ProductAccount) => polkadot_api.PolkadotSigner;
|
|
379
387
|
getProductAccountAlias: (dotNsIdentifier: string, derivationIndex?: number) => NeverthrowResultAsync<ContextualAlias, unknown>;
|
|
380
388
|
getUserId: () => NeverthrowResultAsync<{
|
|
381
389
|
primaryUsername: string;
|
|
@@ -385,49 +393,14 @@ interface AccountsProvider {
|
|
|
385
393
|
unsubscribe: () => void;
|
|
386
394
|
} | (() => void);
|
|
387
395
|
}
|
|
388
|
-
/** @internal */
|
|
389
|
-
interface HostApiPermissionBridge {
|
|
390
|
-
/**
|
|
391
|
-
* Request a Host API permission. Product-sdk's `hostApi.permission(...)`
|
|
392
|
-
* takes a tagged enum like `enumValue("v1", { tag: "TransactionSubmit" })`
|
|
393
|
-
* and returns a neverthrow ResultAsync.
|
|
394
|
-
*/
|
|
395
|
-
permission: (request: unknown) => NeverthrowResultAsync<unknown, unknown>;
|
|
396
|
-
}
|
|
397
|
-
/** @internal */
|
|
398
|
-
interface HostApiEnumHelper {
|
|
399
|
-
enumValue: (version: string, value: {
|
|
400
|
-
tag: string;
|
|
401
|
-
value?: unknown;
|
|
402
|
-
}) => unknown;
|
|
403
|
-
}
|
|
404
|
-
/** @internal */
|
|
405
|
-
interface ProductSdkModule {
|
|
406
|
-
createAccountsProvider: () => AccountsProvider;
|
|
407
|
-
/** Present from product-sdk ≥ 0.6; used to request TransactionSubmit. */
|
|
408
|
-
hostApi?: HostApiPermissionBridge;
|
|
409
|
-
/**
|
|
410
|
-
* `sandboxTransport.isCorrectEnvironment()` returns `false` when the app
|
|
411
|
-
* is loaded outside a Polkadot host container (e.g. a regular browser
|
|
412
|
-
* tab). Calling `getLegacyAccounts()` / `getProductAccount()` in that
|
|
413
|
-
* state surfaces the upstream `Environment is not correct` exception,
|
|
414
|
-
* so we pre-check during `connect()` and raise a specific
|
|
415
|
-
* {@link HostUnavailableError} with actionable guidance instead.
|
|
416
|
-
*/
|
|
417
|
-
sandboxTransport?: {
|
|
418
|
-
isCorrectEnvironment(): boolean;
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
396
|
/**
|
|
422
397
|
* Provider for the Host API (Polkadot Desktop / Android).
|
|
423
398
|
*
|
|
424
|
-
*
|
|
425
|
-
* outside a host container — e.g.
|
|
426
|
-
*
|
|
427
|
-
* the
|
|
428
|
-
*
|
|
429
|
-
* before any host RPC call, so the user never sees the upstream
|
|
430
|
-
* `Environment is not correct` exception leaking through.
|
|
399
|
+
* Backed by `@parity/product-sdk-host`'s `getAccountsProvider`, which talks to
|
|
400
|
+
* the host over `@parity/truapi`. Apps running outside a host container — e.g.
|
|
401
|
+
* a plain browser tab during `npm run dev` — get a `HOST_UNAVAILABLE` error
|
|
402
|
+
* (the provider resolves to `null`) with actionable guidance, surfaced before
|
|
403
|
+
* any host RPC call.
|
|
431
404
|
*
|
|
432
405
|
* Supports both non-product accounts (user's external wallets) and product
|
|
433
406
|
* accounts (app-scoped derived accounts managed by the host).
|
|
@@ -437,8 +410,8 @@ declare class HostProvider implements SignerProvider {
|
|
|
437
410
|
private readonly ss58Prefix;
|
|
438
411
|
private readonly maxRetries;
|
|
439
412
|
private readonly retryDelay;
|
|
440
|
-
private readonly
|
|
441
|
-
private readonly
|
|
413
|
+
private readonly loadAccountsProvider;
|
|
414
|
+
private readonly requestChainSubmitPermissionFn;
|
|
442
415
|
private readonly requestChainSubmitPermission;
|
|
443
416
|
private readonly productAccount;
|
|
444
417
|
private readonly dappName;
|
|
@@ -467,11 +440,9 @@ declare class HostProvider implements SignerProvider {
|
|
|
467
440
|
* Convenience method for when you already have the product account details.
|
|
468
441
|
* Requires a prior successful `connect()` call.
|
|
469
442
|
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
* metadata-driven decoding, rather than going through the PJS bridge
|
|
474
|
-
* that throws on unknown extensions.
|
|
443
|
+
* Signing routes through the host's `createTransaction` path, so unknown
|
|
444
|
+
* signed extensions (e.g. `AsPgas` on Paseo Next) are forwarded to the host
|
|
445
|
+
* as opaque bytes for metadata-driven decoding.
|
|
475
446
|
*/
|
|
476
447
|
getProductAccountSigner(account: ProductAccount): polkadot_api.PolkadotSigner;
|
|
477
448
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '@parity/product-sdk-logger';
|
|
2
|
-
import { requestResourceAllocation, getHostLocalStorage } from '@parity/product-sdk-host';
|
|
2
|
+
import { requestResourceAllocation, getAccountsProvider, requestPermission, getHostLocalStorage } from '@parity/product-sdk-host';
|
|
3
3
|
import { seedToAccount } from '@parity/product-sdk-keys';
|
|
4
4
|
import { ss58Encode, deriveH160 } from '@parity/product-sdk-address';
|
|
5
5
|
|
|
@@ -180,20 +180,21 @@ async function withRetry(fn, options) {
|
|
|
180
180
|
|
|
181
181
|
// src/providers/host.ts
|
|
182
182
|
var log2 = createLogger("signer:host");
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return await import('@novasamatech/host-api-wrapper');
|
|
183
|
+
async function defaultLoadAccountsProvider() {
|
|
184
|
+
return await getAccountsProvider();
|
|
186
185
|
}
|
|
187
|
-
async function
|
|
188
|
-
|
|
186
|
+
async function defaultRequestChainSubmitPermission(permission) {
|
|
187
|
+
const result = await requestPermission(permission);
|
|
188
|
+
if (!result.ok) throw result.error;
|
|
189
|
+
return result.value;
|
|
189
190
|
}
|
|
190
191
|
var HostProvider = class {
|
|
191
192
|
type = "host";
|
|
192
193
|
ss58Prefix;
|
|
193
194
|
maxRetries;
|
|
194
195
|
retryDelay;
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
loadAccountsProvider;
|
|
197
|
+
requestChainSubmitPermissionFn;
|
|
197
198
|
requestChainSubmitPermission;
|
|
198
199
|
productAccount;
|
|
199
200
|
dappName;
|
|
@@ -205,8 +206,8 @@ var HostProvider = class {
|
|
|
205
206
|
this.ss58Prefix = options?.ss58Prefix ?? 42;
|
|
206
207
|
this.maxRetries = options?.maxRetries ?? 3;
|
|
207
208
|
this.retryDelay = options?.retryDelay ?? 500;
|
|
208
|
-
this.
|
|
209
|
-
this.
|
|
209
|
+
this.loadAccountsProvider = options?.loadAccountsProvider ?? defaultLoadAccountsProvider;
|
|
210
|
+
this.requestChainSubmitPermissionFn = options?.requestChainSubmitPermissionFn ?? defaultRequestChainSubmitPermission;
|
|
210
211
|
this.requestChainSubmitPermission = options?.requestChainSubmitPermission ?? options?.requestTransactionSubmitPermission ?? true;
|
|
211
212
|
this.productAccount = options?.productAccount;
|
|
212
213
|
this.dappName = options?.dappName;
|
|
@@ -288,10 +289,7 @@ var HostProvider = class {
|
|
|
288
289
|
if (!this.accountsProvider) {
|
|
289
290
|
throw new Error("Host provider is disconnected");
|
|
290
291
|
}
|
|
291
|
-
return this.accountsProvider.getProductAccountSigner(
|
|
292
|
-
productAccount,
|
|
293
|
-
PRODUCT_SIGNER_TYPE
|
|
294
|
-
);
|
|
292
|
+
return this.accountsProvider.getProductAccountSigner(productAccount);
|
|
295
293
|
}
|
|
296
294
|
});
|
|
297
295
|
} catch (cause) {
|
|
@@ -309,17 +307,15 @@ var HostProvider = class {
|
|
|
309
307
|
* Convenience method for when you already have the product account details.
|
|
310
308
|
* Requires a prior successful `connect()` call.
|
|
311
309
|
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* metadata-driven decoding, rather than going through the PJS bridge
|
|
316
|
-
* that throws on unknown extensions.
|
|
310
|
+
* Signing routes through the host's `createTransaction` path, so unknown
|
|
311
|
+
* signed extensions (e.g. `AsPgas` on Paseo Next) are forwarded to the host
|
|
312
|
+
* as opaque bytes for metadata-driven decoding.
|
|
317
313
|
*/
|
|
318
314
|
getProductAccountSigner(account) {
|
|
319
315
|
if (!this.accountsProvider) {
|
|
320
316
|
throw new Error("Host provider is not connected");
|
|
321
317
|
}
|
|
322
|
-
return this.accountsProvider.getProductAccountSigner(account
|
|
318
|
+
return this.accountsProvider.getProductAccountSigner(account);
|
|
323
319
|
}
|
|
324
320
|
/**
|
|
325
321
|
* Get a contextual alias for a product account via Ring VRF.
|
|
@@ -417,18 +413,18 @@ var HostProvider = class {
|
|
|
417
413
|
}
|
|
418
414
|
// ── Private ──────────────────────────────────────────────────────
|
|
419
415
|
async tryConnect() {
|
|
420
|
-
let
|
|
416
|
+
let provider;
|
|
421
417
|
try {
|
|
422
|
-
|
|
418
|
+
provider = await this.loadAccountsProvider();
|
|
423
419
|
} catch (cause) {
|
|
424
|
-
log2.warn("
|
|
420
|
+
log2.warn("host accounts provider unavailable", { cause });
|
|
425
421
|
return err(
|
|
426
422
|
new HostUnavailableError(
|
|
427
|
-
cause instanceof Error ? `
|
|
423
|
+
cause instanceof Error ? `host accounts provider failed: ${cause.message}` : "host accounts provider is unavailable"
|
|
428
424
|
)
|
|
429
425
|
);
|
|
430
426
|
}
|
|
431
|
-
if (
|
|
427
|
+
if (!provider) {
|
|
432
428
|
log2.warn("not inside a host container \u2014 Host API unavailable");
|
|
433
429
|
return err(
|
|
434
430
|
new HostUnavailableError(
|
|
@@ -436,7 +432,6 @@ var HostProvider = class {
|
|
|
436
432
|
)
|
|
437
433
|
);
|
|
438
434
|
}
|
|
439
|
-
const provider = sdk.createAccountsProvider();
|
|
440
435
|
this.accountsProvider = provider;
|
|
441
436
|
let signerAccounts;
|
|
442
437
|
if (this.productAccount) {
|
|
@@ -474,30 +469,20 @@ var HostProvider = class {
|
|
|
474
469
|
);
|
|
475
470
|
signerAccounts = [];
|
|
476
471
|
}
|
|
477
|
-
if (this.requestChainSubmitPermission
|
|
472
|
+
if (this.requestChainSubmitPermission) {
|
|
478
473
|
try {
|
|
479
|
-
const
|
|
480
|
-
const request = hostApiEnum.enumValue("v1", {
|
|
474
|
+
const granted = await this.requestChainSubmitPermissionFn({
|
|
481
475
|
tag: "ChainSubmit",
|
|
482
476
|
value: void 0
|
|
483
477
|
});
|
|
484
|
-
|
|
485
|
-
() => {
|
|
486
|
-
log2.debug("ChainSubmit permission granted");
|
|
487
|
-
},
|
|
488
|
-
(error) => {
|
|
489
|
-
log2.warn("ChainSubmit permission rejected by host", {
|
|
490
|
-
error: formatError(error)
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
);
|
|
478
|
+
log2.debug("ChainSubmit permission result", { granted });
|
|
494
479
|
} catch (cause) {
|
|
495
480
|
log2.warn("failed to request ChainSubmit permission", { cause });
|
|
496
481
|
}
|
|
497
482
|
}
|
|
498
483
|
log2.info("host connected", { accounts: signerAccounts.length });
|
|
499
484
|
const sub = provider.subscribeAccountConnectionStatus((status) => {
|
|
500
|
-
const mapped = status === "connected" ? "connected" : "disconnected";
|
|
485
|
+
const mapped = String(status).toLowerCase() === "connected" ? "connected" : "disconnected";
|
|
501
486
|
log2.debug("host status changed", { status: mapped });
|
|
502
487
|
for (const listener of this.statusListeners) {
|
|
503
488
|
listener(mapped);
|
|
@@ -536,7 +521,11 @@ var HostProvider = class {
|
|
|
536
521
|
function formatError(error) {
|
|
537
522
|
if (!error || typeof error !== "object") return String(error);
|
|
538
523
|
const e = error;
|
|
539
|
-
if (!("tag" in e))
|
|
524
|
+
if (!("tag" in e)) {
|
|
525
|
+
if (typeof e.reason === "string") return e.reason;
|
|
526
|
+
if (typeof e.message === "string") return e.message;
|
|
527
|
+
return String(error);
|
|
528
|
+
}
|
|
540
529
|
const outerTag = String(e.tag);
|
|
541
530
|
const inner = e.value;
|
|
542
531
|
if (inner && typeof inner === "object") {
|
|
@@ -1015,7 +1004,14 @@ var SignerManager = class {
|
|
|
1015
1004
|
this.onConnectController = controller;
|
|
1016
1005
|
const ctx = {
|
|
1017
1006
|
signal: controller.signal,
|
|
1018
|
-
requestResourceAllocation
|
|
1007
|
+
// Bridge host's `Result`-returning `requestResourceAllocation` back to
|
|
1008
|
+
// the `ConnectContext` contract (`Promise<AllocationOutcome[]>`, throws
|
|
1009
|
+
// on failure) by unwrapping the typed error on the `err` channel.
|
|
1010
|
+
requestResourceAllocation: async (resources) => {
|
|
1011
|
+
const result = await requestResourceAllocation(resources);
|
|
1012
|
+
if (!result.ok) throw result.error;
|
|
1013
|
+
return result.value;
|
|
1014
|
+
}
|
|
1019
1015
|
};
|
|
1020
1016
|
queueMicrotask(async () => {
|
|
1021
1017
|
try {
|