@polkadot-apps/signer
v0.2.1
Published
Multi-provider signer manager for Polkadot — Host API, browser extensions, and dev accounts
Readme
@polkadot-apps/signer
Multi-provider signer manager for Polkadot -- Host API, browser extensions, and dev accounts.
Install
pnpm add @polkadot-apps/signerOptional peer dependency: @novasamatech/product-sdk is required only when using the Host API provider (Polkadot Desktop/Mobile containers).
pnpm add @novasamatech/product-sdkQuick start
import { SignerManager } from "@polkadot-apps/signer";
const manager = new SignerManager();
const result = await manager.connect();
if (!result.ok) throw result.error;
const accounts = result.value;
manager.selectAccount(accounts[0].address);
const signer = manager.getSigner();
// Pass `signer` to polkadot-api transaction callsAuto-detection
connect() detects the runtime environment and picks the best available provider:
- Inside a container (Polkadot Desktop/Mobile): tries Host API first, falls back to Extension.
- Outside a container (standalone browser): tries Extension first, falls back to Dev.
Override auto-detection by passing a provider type explicitly:
await manager.connect("dev"); // force dev accounts
await manager.connect("extension"); // force browser extension
await manager.connect("host"); // force Host APICheck the environment programmatically:
import { isInsideContainer } from "@polkadot-apps/signer";
if (isInsideContainer()) {
console.log("Running inside Polkadot Desktop/Mobile");
}State management
Subscribe to state changes to drive your UI. The callback fires on every status transition, account list change, or account selection.
const unsubscribe = manager.subscribe((state) => {
console.log(state.status); // "disconnected" | "connecting" | "connected"
console.log(state.accounts); // readonly SignerAccount[]
console.log(state.selectedAccount); // SignerAccount | null
console.log(state.activeProvider); // "host" | "extension" | "dev" | null
console.log(state.error); // SignerError | null
});
// Get a snapshot without subscribing
const snapshot = manager.getState();
// Clean up
unsubscribe();Account selection and signing
const selectResult = manager.selectAccount("5GrwvaEF...");
if (!selectResult.ok) {
console.error(selectResult.error);
}
const signer = manager.getSigner(); // PolkadotSigner | null
// Sign raw bytes (for non-transaction signing)
const signResult = await manager.signRaw(new Uint8Array([1, 2, 3]));
if (signResult.ok) {
console.log(signResult.value); // Uint8Array signature
}Host API features
When connected via the Host API provider, additional methods are available for product accounts, aliases, and ring VRF proofs.
const productAccount = manager.getProductAccount("myDapp", 0);
const alias = manager.getProductAccountAlias("myDapp", 0);
const proof = await manager.createRingVRFProof("myDapp", 0, message);Providers
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.
DevProvider
Local dev accounts from the well-known Substrate mnemonic.
import { DevProvider } from "@polkadot-apps/signer";
const provider = new DevProvider({
names: ["Alice", "Bob"],
ss58Prefix: 42,
keyType: "sr25519", // or "ed25519"
});ExtensionProvider
Browser wallet extensions discovered via window.injectedWeb3.
import { ExtensionProvider } from "@polkadot-apps/signer";
const provider = new ExtensionProvider({
extensionName: "polkadot-js",
dappName: "My App",
injectionWait: 1_000,
});
// List available extensions
const extensions = await manager.getAvailableExtensions();HostProvider
Polkadot Desktop/Mobile via product-sdk. Requires @novasamatech/product-sdk.
import { HostProvider } from "@polkadot-apps/signer";
const provider = new HostProvider({
hostTimeout: 10_000,
dappName: "My App",
});
// Inject Spektr compatibility layer
HostProvider.injectSpektr();Cleanup
Always destroy the manager when your application unmounts to release resources and close connections.
manager.disconnect();
manager.destroy();Error handling
All errors extend SignerError. Use type guards to narrow errors by provider.
import {
isHostError,
isExtensionError,
HostUnavailableError,
ExtensionNotFoundError,
NoAccountsError,
} from "@polkadot-apps/signer";
const result = await manager.connect();
if (!result.ok) {
if (isHostError(result.error)) {
console.error("Host API problem:", result.error.message);
} else if (isExtensionError(result.error)) {
console.error("Extension problem:", result.error.message);
}
}API
SignerManager
constructor(options?: SignerManagerOptions)
Create a new manager instance.
getState(): SignerState
Return a snapshot of the current status, accounts, selected account, active provider, and error.
subscribe(callback): () => void
Subscribe to state changes. Returns an unsubscribe function.
| Parameter | Type | Description |
|-----------|------|-------------|
| callback | (state: SignerState) => void | Called on every state transition. |
connect(providerType?): Promise<Result<SignerAccount[], SignerError>>
Connect to accounts. Auto-detects the provider when no type is given.
| Parameter | Type | Description |
|-----------|------|-------------|
| providerType | ProviderType | Optional. "host", "extension", or "dev". |
disconnect(): void
Disconnect the active provider and clear accounts.
selectAccount(address): Result<SignerAccount, SignerError>
Select an account by SS58 address.
getSigner(): PolkadotSigner | null
Return the signer for the currently selected account, or null.
signRaw(data): Promise<Result<Uint8Array, SignerError>>
Sign raw bytes with the selected account.
getProductAccount(dappName, index): ProductAccount
Host API only. Get a product account by dapp name and index.
getProductAccountAlias(dappName, index): ContextualAlias
Host API only. Get the contextual alias for a product account.
createRingVRFProof(dappName, index, message): Promise<RingLocation>
Host API only. Create a ring VRF proof.
getAvailableExtensions(): Promise<string[]>
List browser extension names found in window.injectedWeb3.
destroy(): void
Release all resources. The manager must not be used after this call.
Standalone functions
| Function | Signature | Description |
|----------|-----------|-------------|
| isInsideContainer | () => boolean | Synchronous container environment check. |
| ok | <T>(value: T) => Result<T, never> | Create a successful Result. |
| err | <E>(error: E) => Result<never, E> | Create a failed Result. |
| isHostError | (e: SignerError) => boolean | Type guard for Host API errors. |
| isExtensionError | (e: SignerError) => boolean | Type guard for extension errors. |
Types
type ConnectionStatus = "disconnected" | "connecting" | "connected";
type ProviderType = "host" | "extension" | "dev";
interface SignerAccount {
address: SS58String;
h160Address: `0x${string}`;
publicKey: Uint8Array;
name: string | null;
source: ProviderType;
getSigner(): PolkadotSigner;
}
interface SignerState {
status: ConnectionStatus;
accounts: readonly SignerAccount[];
selectedAccount: SignerAccount | null;
activeProvider: ProviderType | null;
error: SignerError | null;
}
interface SignerManagerOptions {
ss58Prefix?: number; // default: 42
hostTimeout?: number; // default: 10_000
extensionTimeout?: number; // default: 1_000
maxRetries?: number; // default: 3
createProvider?: ProviderFactory;
dappName?: string; // default: "polkadot-app"
persistence?: AccountPersistence | null;
}
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };Error classes
| Class | Extends | When thrown |
|-------|---------|------------|
| SignerError | Error | Base class for all signer errors. |
| HostUnavailableError | SignerError | Host API not found or not responding. |
| HostRejectedError | SignerError | Host API rejected the request. |
| HostDisconnectedError | SignerError | Host API connection lost. |
| ExtensionNotFoundError | SignerError | Browser extension not installed or not injected. |
| ExtensionRejectedError | SignerError | User rejected the extension prompt. |
| SigningFailedError | SignerError | Signing operation failed. |
| NoAccountsError | SignerError | Provider returned zero accounts. |
| TimeoutError | SignerError | Operation timed out. |
| AccountNotFoundError | SignerError | Selected address not in the account list. |
| DestroyedError | SignerError | Method called after destroy(). |
License
Apache-2.0
