@parity/product-sdk-terminal
npm install @parity/product-sdk-terminalExports
Classes
| Name | Summary |
|---|---|
AllowanceError | — |
Functions
| Name | Summary |
|---|---|
createNodeStorageAdapter() | Create a file-based StorageAdapter for use with the host-papp SDK in Node.js. |
createSessionSigner() | Create a PAPI 3 transaction creator backed by a QR-paired mobile wallet session, |
createSessionSignerForAccount() | Create a PAPI 3 transaction creator for a specific sub-account of a paired session. |
createTerminalAdapter() | — |
deriveEntropy() | Derive 32 bytes of deterministic entropy for a terminal session, scoped to the |
deriveProductPublicKey() | The product account’s sr25519 public key for ref. |
getBulletinSigner() | Get a PAPI 3 transaction creator for a Bulletin allowance slot. |
getProductSubtreePublicKey() | Fetch the subtree public key for productId, reaching the wallet only on a |
getStatementStoreProver() | Get a StatementProver for a statement-store allowance slot. |
hasBulletinAllowance() | Cache-only probe for a Bulletin allowance slot. Resolves true when a |
hasStatementStoreAllowance() | Cache-only probe for a Statement Store allowance slot. Resolves true |
isBenignTeardownError() | Whether error is the benign teardown noise @novasamatech/statement-store |
renderQrCode() | Encode a string as a QR code rendered in Unicode half-block characters. |
requestResourceAllocation() | Send the AP request_resource_allocation message over the paired |
sendResourceAllocation() | Send an AP request_resource_allocation message over the paired session and |
sessionRootPublicKey() | The wallet’s own root account key. Product accounts do not derive from it: |
waitForSessions() | Wait for the adapter to load at least one persisted session, or resolve |
Interfaces
| Name | Summary |
|---|---|
ProductAccountRef | Identifies which sub-account of a paired session should sign. |
ProductSubtreeOptions | — |
QrRenderOptions | Options for QR code rendering. |
RequestResourceAllocationOptions | — |
SessionSignerOptions | — |
TerminalAdapterOptions | Options for creating a terminal adapter. |
Type Aliases
| Name | Summary |
|---|---|
AllocatableResource | One resource a Host can request from the Account Holder. AutoSigning |
AllowanceErrorReason | — |
AllowanceService | — |
ApAllocationOutcome | Per-resource outcome. Allocated.value carries the materialized payload |
HostMetadata | — |
OnExistingAllowancePolicy | "Ignore": return existing keys if any, else allocate one slot. |
PairingStatus | — |
PappAdapter | — |
SigningPayloadRequest | — |
SigningPayloadResponse | — |
SigningRawRequest | — |
StatementStoreEnvironment | Network keys with built-in endpoints in StatementStoreNetworks. |
StoredUserSession | — |
TerminalAdapter | A PappAdapter with the appId it was created with and a destroy method for cleanup. |
UserSession | — |
Variables
| Name | Summary |
|---|---|
INCOMPLETE_SESSION_MESSAGE | — |
SS_STABLE_STAGE_ENDPOINTS | — |
StatementStoreNetworks | — |
Re-exports
Convenience re-exports from leaf packages. Click through for the canonical documentation.
| Name | Kind | Source package |
|---|---|---|
AllowanceExpiredError | class | @parity/product-sdk-signer |
SignerError | class | @parity/product-sdk-signer |
Classes
class AllowanceError
Extends: Error
Constructors
constructor
new AllowanceError(reason: AllowanceErrorReason, message?: string): AllowanceErrorProperties
reason
AllowanceErrorReasonFunctions
createNodeStorageAdapter()
Create a file-based StorageAdapter for use with the host-papp SDK in Node.js.
Data is stored as individual JSON files in the given directory
(defaults to ~/.polkadot-apps/).
createNodeStorageAdapter(appId: string, storageDir?: string): StorageAdaptercreateSessionSigner()
Create a PAPI 3 transaction creator backed by a QR-paired mobile wallet session,
using the session’s default account (derivationIndex: 0).
For non-default sub-accounts, use createSessionSignerForAccount.
createSessionSigner(session: UserSession, adapter: TerminalAdapter, publicKey?: Uint8Array<ArrayBufferLike>, options?: SessionSignerOptions): Promise<CommonSignerTxCreator>Parameters
session: The paired user session.adapter: TheTerminalAdapterthat loaded the session. ItsappIdis used as theproductIdin the wire request.publicKey: The product account’s sr25519 public key for[adapter.appId, 0]. Optional — when omitted it’s fetched and derived. SeeProductAccountRef.publicKey.
createSessionSignerForAccount()
Create a PAPI 3 transaction creator for a specific sub-account of a paired session.
Use this when you need a derivation index other than 0, or a productId
different from the adapter’s appId. For the common default-account case,
prefer createSessionSigner.
createSessionSignerForAccount(session: UserSession, ref: ProductAccountRef, options?: SessionSignerOptions): Promise<CommonSignerTxCreator>Parameters
session: The paired user session.ref: The product account to sign as:\{ productId, derivationIndex \}.
createTerminalAdapter()
createTerminalAdapter(options: TerminalAdapterOptions): TerminalAdapterderiveEntropy()
Derive 32 bytes of deterministic entropy for a terminal session, scoped to the calling product and a caller key (RFC-0007 layers 2 + 3).
Same wallet + product + key ⇒ same bytes; any difference ⇒ uncorrelated entropy. Because it derives from the wallet (not the device), the entropy is recreatable after device loss as long as the wallet is recoverable.
deriveEntropy(session: UserSession, productId: string, key: Uint8Array): Uint8ArrayRemarks
This reads nothing account-identifying, and a terminal may hold several paired
sessions (see waitForSessions). Passing the wrong one yields valid entropy
for a different wallet: the intended user cannot decrypt, and the other
wallet’s holder can. Pin the identity with sessionRootPublicKey before
deriving long-lived keys.
The return value is raw key material — do not log it or persist it unwrapped.
Parameters
session: A QR-pairedUserSession. Must carryrootEntropySource(present since host-papp 0.8.6 / RFC-0007).productId: The calling product’s identifier, used verbatim as the layer-2 scope. Per host-spec §C.7 that is\<label\>.dotfor a dotNS product (e.g."my-app.dot"), orlocalhost:\<port\>— no.dotsuffix — for local dev. It is case-sensitive and is not normalized here:"My-App.dot","my-app.dot "and"my-app.dot"derive three uncorrelated entropies. This must be the identifier the in-container deployment is served under — the same value you pass asproductIdtogetBulletinSignerand asoptions.productIdtorequestResourceAllocation— because that is the scope the host derives against. It is notadapter.appId: the adapter id is a local namespace (cache keys, storage dir) and the two are routinely different. The package’s own example pairscreateTerminalAdapter(\{ appId: "my-cli" \})withgetBulletinSigner(adapter, "my-cli.dot"). Note thatrequestResourceAllocationfalls back toadapter.appIdwhenoptions.productIdis omitted, so a call relying on that default is not a reliable reference point for this argument. The host takes the scope from the iframe’s deployment label rather than from the caller, so it differs per deployment: production, each PR preview and local dev all scope to different entropy. A CLI that hardcodes the production id derives valid-but-different bytes against a preview, with no error anywhere. Required and positional by design — do not give this a default. Unlike an allocation request, a wrong product scope here fails silently: it yields well-formed entropy that simply cannot decrypt anything.key: Caller key, 1..32 bytes (the layer-3 BLAKE2b key). Both bounds are normative: the spec requires zero-length and >32-byte keys to be rejected, because some BLAKE2b implementations silently degrade an empty MAC key to unkeyed mode.
Returns
32 bytes of derived entropy.
Throws
- if the session lacks
rootEntropySourceor carries one of the wrong width, ifproductIdis not a non-empty string, or ifkeyis not 1..32 bytes.
deriveProductPublicKey()
The product account’s sr25519 public key for ref.
The one place product-account math happens, so a displayed address cannot desync from what the signer signs with.
deriveProductPublicKey(session: UserSession, ref: ProductAccountRef, options?: ProductSubtreeOptions): Promise<Uint8Array<ArrayBufferLike>>Throws
- Error when the wallet is unreachable on a cold cache.
getBulletinSigner()
Get a PAPI 3 transaction creator for a Bulletin allowance slot.
Allocates an allowance slot via the paired wallet (or returns the cached
one), derives the slot-account keypair, and returns a PAPI 3 transaction creator
that signs Bulletin extrinsics with it. Replaces the manual
requestResourceAllocation + createSlotAccountSigner two-step for the
common case.
getBulletinSigner(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<CommonSignerTxCreator>Parameters
adapter: Terminal adapter.productId: The product id the slot is allocated under. Passed to the host as the calling product id in the allowance request.sessionId: Paired session to allocate against. Defaults to the only paired session; throwsAllowanceError('NoSession')when zero or more than one sessions are paired and no explicit id is supplied.
Throws
- On rejection, missing session, host-side failure, or unexpected response shape.
Examples
import { createTerminalAdapter, getBulletinSigner } from "@parity/product-sdk-terminal";
const adapter = createTerminalAdapter({ appId: "my-cli" });
// ... QR pair, wait for session ...
const signer = await getBulletinSigner(adapter, "my-cli.dot");
await client.bulletin.tx.TransactionStorage.store({ data }).createAndSubmit(signer);getProductSubtreePublicKey()
Fetch the subtree public key for productId, reaching the wallet only on a
cold cache.
getProductSubtreePublicKey(session: UserSession, productId: string, options: ProductSubtreeOptions = {}): Promise<Uint8Array<ArrayBufferLike>>Throws
- Error when the wallet rejects the request or returns a malformed key.
getStatementStoreProver()
Get a StatementProver for a statement-store allowance slot.
Allocates an allowance slot via the paired wallet (or returns the cached
one) and returns the upstream StatementProver for the slot. Use when
publishing statements through @novasamatech/statement-store without
holding a long-lived key yourself.
getStatementStoreProver(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<StatementProver>Parameters
adapter: Terminal adapter.productId: The product id the slot is allocated under.sessionId: Paired session to allocate against. Defaults to the only paired session; throwsAllowanceError('NoSession')when zero or more than one sessions are paired and no explicit id is supplied.
Throws
- On rejection, missing session, host-side failure, or unexpected response shape.
hasBulletinAllowance()
Cache-only probe for a Bulletin allowance slot. Resolves true when a
slot key for (sessionId, productId, bulletin) is already cached on
disk; false when it is not. Never prompts the paired wallet.
Pair with getBulletinSigner for the “check first, fetch only
if needed” flow:
hasBulletinAllowance(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<boolean>Examples
if (await hasBulletinAllowance(adapter, "my-cli.dot")) {
// happy path — fetch the signer without risking a wallet prompt
const signer = await getBulletinSigner(adapter, "my-cli.dot");
} else {
// tell the user a wallet prompt will fire, then call getBulletinSigner
}hasStatementStoreAllowance()
Cache-only probe for a Statement Store allowance slot. Resolves true
when a slot key for (sessionId, productId, statementStore) is already
cached on disk; false when it is not. Never prompts the paired wallet.
Pair with getStatementStoreProver for the “check first, fetch
only if needed” flow.
hasStatementStoreAllowance(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<boolean>isBenignTeardownError()
Whether error is the benign teardown noise @novasamatech/statement-store
emits when a statement subscription’s observable errors because the client
was destroyed underneath it — the raw-client’s DestroyedError: Client destroyed.
Matches on the message text only. Matching the DestroyedError name would
also swallow @parity/product-sdk-signer’s own DestroyedError (a direct
dependency, message “SignerManager has been destroyed”), so a real signer
failure during teardown would vanish — and this predicate is exported, so it
would carry that to consumers. Client destroyed is unique to the upstream
line.
Exported so a consumer’s own console.error guard can drop this line without
reinventing the match. createTerminalAdapter’s own destroy() already
suppresses it (see suppressBenignTeardownErrors); this is for code
paths outside that window.
isBenignTeardownError(error: unknown): booleanrenderQrCode()
Encode a string as a QR code rendered in Unicode half-block characters.
Returns a multi-line string suitable for console.log.
renderQrCode(data: string, options?: QrRenderOptions): Promise<string>requestResourceAllocation()
Send the AP request_resource_allocation message over the paired
session; block on the user’s mobile dialog; return outcomes in request
order. Granted key material is cached on disk so subsequent calls skip
the wallet prompt.
requestResourceAllocation(session: UserSession, adapter: TerminalAdapter, resources: { tag: "StatementStoreAllowance"; value: undefined } | { tag: "SmartContractAllowance"; value: { tag: "Index"; value: number } | { tag: "Raw"; value: Uint8Array<ArrayBufferLike> } } | { tag: "AutoSigning"; value: undefined } | { tag: "BulletInAllowance"; value: undefined }[], options: RequestResourceAllocationOptions = {}): Promise<{ tag: "Rejected"; value: undefined } | { tag: "Allocated"; value: { tag: "StatementStoreAllowance"; value: { slotAccountKey: Uint8Array<ArrayBufferLike> } } | { tag: "SmartContractAllowance"; value: undefined } | { tag: "AutoSigning"; value: { productRootPrivateKey: Uint8Array<ArrayBufferLike>; ringVrfDomainEntropy: Uint8Array<ArrayBufferLike> } } | { tag: "BulletInAllowance"; value: { slotAccountKey: Uint8Array<ArrayBufferLike> } } } | { tag: "NotAvailable"; value: undefined }[]>Throws
- If the session call fails (transport, timeout, AH protocol error).
Examples
const [session] = adapter.sessions.sessions.read();
const outcomes = await requestResourceAllocation(session, adapter, [
{ tag: "BulletInAllowance", value: undefined },
]);sendResourceAllocation()
Send an AP request_resource_allocation message over the paired session and
return the outcomes in request order. This is the raw wire call: no disk
cache, no onExisting auto-pick, no adapter. Callers that want the caching /
slot-reuse behaviour should use requestResourceAllocation instead;
this primitive exists for consumers (e.g. @parity/product-sdk-auth) that
only hold a productId and manage their own policy.
sendResourceAllocation(session: UserSession, productId: string, resources: { tag: "StatementStoreAllowance"; value: undefined } | { tag: "SmartContractAllowance"; value: { tag: "Index"; value: number } | { tag: "Raw"; value: Uint8Array<ArrayBufferLike> } } | { tag: "AutoSigning"; value: undefined } | { tag: "BulletInAllowance"; value: undefined }[], onExisting: "Ignore" | "Increase"): Promise<{ tag: "Rejected"; value: undefined } | { tag: "Allocated"; value: { tag: "StatementStoreAllowance"; value: { slotAccountKey: Uint8Array<ArrayBufferLike> } } | { tag: "SmartContractAllowance"; value: undefined } | { tag: "AutoSigning"; value: { productRootPrivateKey: Uint8Array<ArrayBufferLike>; ringVrfDomainEntropy: Uint8Array<ArrayBufferLike> } } | { tag: "BulletInAllowance"; value: { slotAccountKey: Uint8Array<ArrayBufferLike> } } } | { tag: "NotAvailable"; value: undefined }[]>Throws
- If the session call fails (transport, timeout, AH protocol error).
sessionRootPublicKey()
The wallet’s own root account key. Product accounts do not derive from it: RFC-0022 puts two hard junctions in between.
sessionRootPublicKey(session: UserSession): Uint8ArrayThrows
INCOMPLETE_SESSION_MESSAGEif the session predates therootAccountIdfield.
waitForSessions()
Wait for the adapter to load at least one persisted session, or resolve
with an empty array after timeoutMs.
The session manager loads sessions from storage asynchronously, so a
synchronous adapter.sessions.sessions.read() immediately after
createTerminalAdapter() may return [] even when sessions exist on
disk. Use this helper to give the loader a chance to populate before
deciding whether the user is logged in.
waitForSessions(adapter: TerminalAdapter, timeoutMs: number = 3000): Promise<UserSession[]>Interfaces
interface ProductAccountRef
Identifies which sub-account of a paired session should sign.
Mirrors the host-papp wire format productAccountId: [productId, index]:
productId is the dotNS-style identifier for the requesting product (matches
the adapter’s appId in normal usage); derivationIndex is the BIP32-style
child-key index, where 0 is the session’s default account.
Properties
derivationIndex
numberChild-key derivation index. 0 is the default account.
productId
stringThe product identifier. Usually equal to the adapter’s appId.
publicKey
Uint8Array<ArrayBufferLike>The product account’s sr25519 public key (32 bytes), as derived by the
host for [productId, derivationIndex].
PAPI stamps this into the extrinsic’s signer address and verifies against it, so a mismatch produces an invalid signature.
When omitted, the signer fetches the subtree key and derives it. Supply it to skip that round trip.
interface ProductSubtreeOptions
Properties
appId
stringNames the cache file. Defaults to the productId.
storageDir
stringinterface QrRenderOptions
Options for QR code rendering.
Properties
errorCorrectionLevel
"L" | "M" | "Q" | "H"Error correction level. Default: “M”.
margin
numberQuiet zone size in modules. Default: 2.
interface RequestResourceAllocationOptions
Properties
onExisting
"Ignore" | "Increase"Override the auto-picked onExisting. Default: Ignore unless every
requested resource is a cached slot-table variant, then Increase.
productId
stringProduct id the allocation is scoped to. Sent on the wire as
callingProductId and used as the slot-cache namespace. Defaults to
adapter.appId.
Pass this when the product id the app signs as differs from the
terminal’s storage appId — the wallet derives every per-product
artifact (including the on-chain account PGAS is minted to and
auto-mapped for) from this id, so allocating under the wrong id lands
the allowance on the wrong account. Mirrors the explicit productId
that getBulletinSigner already takes.
interface SessionSignerOptions
Extends: ProductSubtreeOptions
Properties
txExtVersion
numberPipeline version to prefer when the transaction names none. Defaults to 0.
When that pipeline does not declare every extension the caller supplied,
for example in customSignedExtensions, the signer uses the lowest version
that does, and refuses a transaction no pipeline can carry. A wrapper that
writes its own extensions, such as withAsPerson, sets the version on the
transaction itself, and that always wins.
interface TerminalAdapterOptions
Options for creating a terminal adapter.
Properties
appId
stringUnique app identifier. Used as the storage namespace.
endpoints
string[]Statement store WebSocket endpoints. Defaults to StatementStoreNetworks.paseo.
hostMetadata
HandshakeMetadataOptional host metadata for the Sign-In screen.
storageDir
stringDirectory where session files are persisted. Defaults to
~/.polkadot-apps/. Override in tests to point at a temporary
directory populated with createTestSession from
@parity/product-sdk-terminal/testing.
Type Aliases
type AllocatableResource
One resource a Host can request from the Account Holder. AutoSigning
currently returns NotAvailable on both Android and iOS wallets.
type AllocatableResource = ResourceAllocationRequest["resources"][number]type AllowanceErrorReason
type AllowanceErrorReason = "NoSession" | "Rejected" | "NotAvailable" | "UnexpectedResponse"type AllowanceService
type AllowanceService = unknowntype ApAllocationOutcome
Per-resource outcome. Allocated.value carries the materialized payload
(slot account key for Bulletin/SSS; subtree key + secret for AutoSigning;
undefined for SC). Each entry is independent — no rollback on partial
success.
type ApAllocationOutcome = ExtractOk<ReturnType<UserSession["requestResourceAllocation"]>>[number]type HostMetadata
type HostMetadata = HandshakeMetadatatype OnExistingAllowancePolicy
"Ignore": return existing keys if any, else allocate one slot.
"Increase": add one slot to an existing allowance account.
type OnExistingAllowancePolicy = ResourceAllocationRequest["onExisting"]type PairingStatus
type PairingStatus = { step: "none" } | { step: "initial" } | { payload: string; step: "pairing" } | { stage: string; step: "pending" } | { message: string; step: "pairingError" } | { session: StoredUserSession; step: "finished" }type PappAdapter
type PappAdapter = unknowntype SigningPayloadRequest
type SigningPayloadRequest = CodecType<typeof SigningPayloadRequestCodec>type SigningPayloadResponse
type SigningPayloadResponse = CodecType<typeof SigningResponseCodec>type SigningRawRequest
type SigningRawRequest = CodecType<typeof SigningRawRequestCodec>type StatementStoreEnvironment
Network keys with built-in endpoints in StatementStoreNetworks.
type StatementStoreEnvironment = keyof typeof StatementStoreNetworkstype StoredUserSession
type StoredUserSession = CodecType<typeof storedUserSessionCodec>type TerminalAdapter
A PappAdapter with the appId it was created with and a destroy method for cleanup.
type TerminalAdapter = PappAdapter & { readonly appId: string; readonly storageDir?: string; destroy: unknown }type UserSession
type UserSession = StoredUserSession & { abortPendingRequests: unknown; createRingVrfProof: unknown; createTransaction: unknown; createTransactionLegacy: unknown; dispose: unknown; getIdentity: unknown; getProductSubtree: unknown; getRingVrfAlias: unknown; listRingVrfKeys: unknown; readAllowance: unknown; registerRingVrfKey: unknown; requestResourceAllocation: unknown; ringVrfSign: unknown; sendDisconnectMessage: unknown; signPayload: unknown; signRaw: unknown; signRawLegacy: unknown; signVrf: unknown; subscribe: unknown }Variables
INCOMPLETE_SESSION_MESSAGE
let INCOMPLETE_SESSION_MESSAGE: "Stored login session is missing the root account public key. Run \"logout\" and then \"login\" to pair again." = 'Stored login session is missing the root account public key. Run "logout" and then "login" to pair again.'SS_STABLE_STAGE_ENDPOINTS
let SS_STABLE_STAGE_ENDPOINTS: string[]StatementStoreNetworks
let StatementStoreNetworks: { paseo: string[]; previewnet: string[] } = ...