@parity/product-sdk-tx
Submit Polkadot transactions and follow them to finality.
submitAndWatch signs, broadcasts, and tracks a single extrinsic through its
lifecycle; batchSubmitAndWatch does the same for a list of calls. The package
also bundles the things you almost always reach for next to a real submission:
dry-run helpers, Asset Hub account mapping, retry primitives, dev signers, and
a typed error hierarchy with formatters that turn dispatch errors into readable
messages.
npm install @parity/product-sdk-txExports
Classes
| Name | Summary |
|---|---|
TxAccountMappingError | Error raised when account mapping fails. Extends TxError so it flows on |
TxBatchError | Error specific to batch transaction construction (e.g., empty calls array). |
TxDispatchError | The transaction was included on-chain but the dispatch failed. |
TxDryRunError | A dry-run simulation failed before the transaction was submitted on-chain. |
TxError | Base class for all transaction errors. Use instanceof TxError to catch any |
TxSigningRejectedError | The user rejected the signing request in their wallet. |
TxTimeoutError | The transaction did not finalize within the configured timeout. It may still be processing on-chain. |
TxValidityError | The transaction failed before inclusion — a validity or submission failure |
Functions
| Name | Summary |
|---|---|
applyWeightBuffer() | Apply a safety buffer to weight estimates from a dry-run result. |
batchSubmitAndWatch() | Batch multiple transactions into a single Substrate Utility batch and submit. |
calculateDelay() | Calculate delay with exponential backoff and jitter. |
createDevSigner() | Create a PolkadotSigner for a standard Substrate dev account. |
ensureAccountMapped() | Ensure an account’s SS58 address is mapped to its H160 EVM address on-chain. |
extractTransaction() | Validate an Ink SDK dry-run result and extract the submittable transaction. |
formatDispatchError() | Extract a human-readable error from a transaction result’s dispatch error. |
formatDryRunError() | Extract a human-readable error from a failed dry-run result. |
formatValidityError() | Extract a human-readable error from a transaction validity failure payload — |
getDevPublicKey() | Get the public key bytes for a dev account. |
isAccountMapped() | Check if an address is mapped on-chain. |
isSigningRejection() | Check if an error looks like a user-rejected signing request. |
submitAndWatch() | Submit a transaction and watch its lifecycle through signing, broadcasting, |
withRetry() | Wrap an async function with retry logic and exponential backoff. |
Interfaces
| Name | Summary |
|---|---|
BatchApi | Minimal structural type for a PAPI typed API with the Utility pallet. |
BatchSubmitOptions | Options for batchSubmitAndWatch. Extends SubmitOptions with batch mode. |
EnsureAccountMappedOptions | Options for ensureAccountMapped. |
MappingChecker | Minimal interface for checking if an address is mapped on-chain. |
RetryOptions | Options for withRetry. |
ReviveApi | Minimal typed API shape for Revive.map_account(). |
SubmitOptions | Options for submitAndWatch. |
SubmittableTransaction | Structural type for any transaction object that supports Observable-based |
TxResult | Successful transaction result. |
Weight | Substrate weight representing computational and storage resources. |
Type Aliases
| Name | Summary |
|---|---|
BatchableCall | A transaction or decoded call that can be included in a batch. |
BatchMode | Batch execution mode corresponding to Substrate’s Utility pallet. |
DevAccountName | Standard Substrate dev account names. |
TxEvent | PAPI transaction event (discriminated union). |
TxStatus | Transaction lifecycle status for UI callbacks. |
WaitFor | When to resolve the submission promise. |
Classes
class TxAccountMappingError
Error raised when account mapping fails. Extends TxError so it flows on
the err channel of the mapping functions alongside the other tx errors and
carries the shared SdkError marker.
Extends: TxError
Constructors
constructor
new TxAccountMappingError(message: string, options?: ErrorOptions): TxAccountMappingErrorclass TxBatchError
Error specific to batch transaction construction (e.g., empty calls array).
Extends: TxError
Constructors
constructor
new TxBatchError(message: string, options?: ErrorOptions): TxBatchErrorclass TxDispatchError
The transaction was included on-chain but the dispatch failed.
Extends: TxError
Constructors
constructor
new TxDispatchError(dispatchError: unknown, formatted: string): TxDispatchErrorProperties
dispatchError
unknownRaw dispatch error from polkadot-api.
formatted
stringHuman-readable error string (e.g., “Revive.ContractReverted”).
class TxDryRunError
A dry-run simulation failed before the transaction was submitted on-chain.
Thrown by extractTransaction when the dry-run result indicates failure.
Carries structured error information so callers can distinguish revert reasons
from dispatch errors programmatically.
Extends: TxError
Examples
try {
const tx = extractTransaction(await contract.query("mint", { origin, data }));
} catch (e) {
if (e instanceof TxDryRunError) {
console.log(e.revertReason); // "InsufficientBalance" (if contract provided one)
console.log(e.formatted); // "Revive.StorageDepositNotEnoughFunds"
}
}Constructors
constructor
new TxDryRunError(raw: unknown, formatted: string, revertReason?: string): TxDryRunErrorProperties
formatted
stringHuman-readable error string derived from the dry-run result.
raw
unknownThe raw dry-run result for programmatic inspection.
revertReason
stringSolidity revert reason, if the contract provided one.
class TxError
Base class for all transaction errors. Use instanceof TxError to catch any
tx-related error. Implements the cross-package SdkError marker so
isSdkError(e) also recognizes it.
Extends: Error
Implements: SdkError
Constructors
constructor
new TxError(message: string, options?: ErrorOptions): TxErrorProperties
isSdkError
trueDiscriminant present on all SDK errors.
source
"tx"The package that raised the error, e.g. "host", "signer", "contracts".
class TxSigningRejectedError
The user rejected the signing request in their wallet.
Extends: TxError
Constructors
constructor
new TxSigningRejectedError(): TxSigningRejectedErrorclass TxTimeoutError
The transaction did not finalize within the configured timeout. It may still be processing on-chain.
Extends: TxError
Constructors
constructor
new TxTimeoutError(timeoutMs: number): TxTimeoutErrorProperties
timeoutMs
numberclass TxValidityError
The transaction failed before inclusion — a validity or submission failure
such as InvalidTransaction::Payment (the submitter cannot pay fees or is
not authorized on a feeless-but-authorized chain).
These failures carry no dispatchError (that field only exists for a
transaction that was included and then failed to dispatch), so they are
reported as a distinct type instead of a TxDispatchError with a
placeholder string. The raw failure payload is preserved on
TxValidityError.reason for programmatic inspection.
Extends: TxError
Constructors
constructor
new TxValidityError(reason: unknown, formatted: string): TxValidityErrorProperties
formatted
stringHuman-readable error string (e.g., “Invalid.Payment”).
reason
unknownRaw underlying failure payload: the decoded validity error from
polkadot-api’s InvalidTxError
(e.g. { type: "Invalid", value: { type: "Payment" } }), or the failed
tx event when the node reported failure without any payload.
Functions
applyWeightBuffer()
Apply a safety buffer to weight estimates from a dry-run result.
Dry-run weight estimates reflect the exact execution cost at the time of
simulation. On-chain conditions can change between dry-run and actual
submission (storage growth, state changes by other transactions), so a
buffer prevents unexpected OutOfGas failures.
The default 25% buffer matches the convention used across Polkadot ecosystem tooling.
applyWeightBuffer(weight: Weight, options?: { percent?: number }): WeightParameters
weight: Theweight_requiredfrom aReviveApi.callorReviveApi.eth_transactdry-run.options: Override the buffer percentage (default: 25%).
Returns
A new weight with both components scaled up.
Examples
const dryRun = await api.apis.ReviveApi.call(origin, dest, value, undefined, undefined, data);
const tx = api.tx.Revive.call({
dest, value, data,
weight_limit: applyWeightBuffer(dryRun.weight_required),
storage_deposit_limit: dryRun.storage_deposit.value,
});applyWeightBuffer(dryRun.weight_required, { percent: 50 });batchSubmitAndWatch()
Batch multiple transactions into a single Substrate Utility batch and submit.
Extracts .decodedCall from each transaction (handling Ink SDK AsyncTransaction
wrappers), wraps them in Utility.batch_all (or batch/force_batch via the
mode option), and submits via submitAndWatch with full lifecycle tracking.
batchSubmitAndWatch(calls: BatchableCall[], api: BatchApi, signer: PolkadotSigner, options?: BatchSubmitOptions): Promise<Result<TxResult, TxError>>Parameters
calls: Array of transactions, AsyncTransactions, or raw decoded calls to batch.api: A typed API withtx.Utility.batch_all/batch/force_batch. Works with any chain that has the Utility pallet — no chain-specific imports required. All calls must target the same chain as this API. Do not mix decoded calls from different chains (e.g., Asset Hub and Bulletin) in a single batch.signer: The signer to use. Can come from the Host API (getProductAccountSigner) orcreateDevSigner.options: OptionalBatchSubmitOptions(extendsSubmitOptionswithmode).
Returns
A Result: ok(TxResult) from the batch submission, or err(TxError) on
failure. The err channel carries a typed TxError — a TxBatchError (empty calls, an
invalid call, or an AsyncTransaction that resolves without .decodedCall), or any error
surfaced by the underlying submitAndWatch (TxTimeoutError, TxDispatchError,
TxSigningRejectedError).
Examples
import { batchSubmitAndWatch } from "@parity/product-sdk-tx";
const tx1 = api.tx.Balances.transfer_keep_alive({ dest: addr1, value: 1_000n });
const tx2 = api.tx.Balances.transfer_keep_alive({ dest: addr2, value: 2_000n });
const result = await batchSubmitAndWatch([tx1, tx2], api, signer, {
onStatus: (status) => console.log(status),
});
if (!result.ok) handle(result.error);calculateDelay()
Calculate delay with exponential backoff and jitter.
Jitter prevents thundering-herd when multiple clients retry simultaneously.
The delay is min(baseDelay * 2^attempt, maxDelay) * random(0.5, 1.0).
calculateDelay(attempt: number, baseDelayMs: number, maxDelayMs: number): numbercreateDevSigner()
Create a PolkadotSigner for a standard Substrate dev account.
Dev accounts use the well-known Substrate dev mnemonic (DEV_PHRASE) with
Sr25519 key derivation at the path //{Name}. These accounts have known
private keys and are pre-funded on dev/test chains.
Only for local development, scripts, and testing. Never use in production.
createDevSigner(name: DevAccountName): PolkadotSignerParameters
name: Dev account name (“Alice”, “Bob”, “Charlie”, “Dave”, “Eve”, or “Ferdie”).
Returns
A PolkadotSigner that can sign transactions.
Examples
import { createDevSigner } from "@parity/product-sdk-tx";
const alice = createDevSigner("Alice");
const result = await submitAndWatch(tx, alice);ensureAccountMapped()
Ensure an account’s SS58 address is mapped to its H160 EVM address on-chain.
Account mapping is a prerequisite for any EVM contract interaction on Asset Hub.
This function checks the on-chain mapping status and, if unmapped, submits a
Revive.map_account() transaction and waits for inclusion.
Idempotent — safe to call multiple times. Returns immediately if already mapped.
ensureAccountMapped(address: string, signer: PolkadotSigner, checker: MappingChecker, api: ReviveApi, options?: EnsureAccountMappedOptions): Promise<Result<TxResult | null, TxError>>Parameters
address: The SS58 address to check/map.signer: The signer for the account (must match the address).checker: An object withaddressIsMapped(). Easiest path: build one from a pallet-revive-capable typed API by queryingRevive.OriginalAccount.api: A typed API withtx.Revive.map_account().options: Optional timeout and status callback.
Returns
A Result: ok(TxResult) if mapping was performed, ok(null) if the account
was already mapped (an expected, non-failure state), or err(TxError) if the mapping check
or map_account transaction fails (a TxAccountMappingError for a failed check, or a
TxDispatchError / TxTimeoutError from the submission).
Examples
import { ensureAccountMapped } from "@parity/product-sdk-tx";
import { ss58ToH160 } from "@parity/product-sdk-address";
const api = client.getTypedApi(paseo_asset_hub);
const checker = {
addressIsMapped: async (addr: string) =>
(await api.query.Revive.OriginalAccount.getValue(ss58ToH160(addr))) !== undefined,
};
const result = await ensureAccountMapped(address, signer, checker, api);
if (!result.ok) handle(result.error);
// Account is now mapped — safe to call PolkaVM/Solidity contractsextractTransaction()
Validate an Ink SDK dry-run result and extract the submittable transaction.
Replaces the 5-10 line boilerplate that every contract interaction repeats:
check success, parse the error, verify send() exists, and call it.
Works with any object whose shape matches the Ink SDK contract query result (typed structurally — no Ink SDK import required):
contract.query("method", { origin, data })(Ink SDK)contract.write("method", args, origin)(patched SDK wrappers)- Any object with
{ success: boolean; value?: { send?(): ... } }
extractTransaction(result: { error?: unknown; success: boolean; value?: unknown }): Result<SubmittableTransaction, TxDryRunError>Parameters
result: The dry-run result from a contract query or write simulation.
Returns
A Result: ok(SubmittableTransaction) ready to pass to submitAndWatch,
or err(TxDryRunError) if the dry run failed, returned no value, or has no send().
Examples
import { extractTransaction, submitAndWatch, createDevSigner } from "@parity/product-sdk-tx";
const dryRun = await contract.query("createItem", { origin, data: { name, price } });
const extracted = extractTransaction(dryRun);
if (!extracted.ok) return handle(extracted.error);
const result = await submitAndWatch(extracted.value, createDevSigner("Alice"));formatDispatchError()
Extract a human-readable error from a transaction result’s dispatch error.
PAPI dispatch errors for pallet modules are nested:
{ type: "Module", value: { type: "Revive", value: { type: "ContractReverted" } } }
This walks the chain to build a string like "Revive.ContractReverted".
A failed result with a missing dispatchError is a pre-inclusion validity
or submission failure (e.g. InvalidTransaction::Payment) — dispatchError
only exists for a transaction that was included and then failed — and is
described as such rather than collapsed to "unknown error".
formatDispatchError(result: { dispatchError?: unknown; ok: boolean }): stringParameters
result: A transaction result withokand optionaldispatchError.
Returns
A human-readable error string, or "" if the result is ok, or "unknown error" if
the dispatch error cannot be decoded.
formatDryRunError()
Extract a human-readable error from a failed dry-run result.
Handles every error shape found across the Polkadot contract ecosystem:
-
Revert reason (Ink SDK patched results / EVM contracts):
{ value: { revertReason: "InsufficientBalance" } } -
Nested dispatch errors (raw Ink SDK / pallet errors):
{ value: { type: "Module", value: { type: "Revive", value: { type: "StorageDepositNotEnoughFunds" } } } }Delegates toformatDispatchErrorfor the Module.Pallet.Error chain. -
ReviveApi runtime messages (
eth_transact/ReviveApi.call):{ value: { type: "Message", value: "Insufficient balance for gas * price + value" } } -
ReviveApi contract revert data:
{ value: { type: "Data", value: "0x08c379a0..." } } -
Wrapped raw errors (patched SDK wrappers):
{ value: { raw: { type: "Message", value: "..." } } } -
Generic error field:
{ error: { type: "ContractTrapped" } }or{ error: { name: "..." } }
formatDryRunError(result: { error?: unknown; success?: boolean; value?: unknown }): stringParameters
result: A dry-run result with at leastsuccess, and optionallyvalue/error.
Returns
A human-readable error string, or "" if the result succeeded.
formatValidityError()
Extract a human-readable error from a transaction validity failure payload —
the decoded TransactionValidityError that polkadot-api attaches to an
InvalidTxError (its .error property) when a transaction is rejected
before inclusion.
Validity payloads are nested tagged enums:
{ type: "Invalid", value: { type: "Payment" } } → "Invalid.Payment".
Kept separate from formatDispatchError on purpose: dispatch errors
only nest under Module, and walking every non-Module type/value chain
there would change established formats (e.g. "Token").
formatValidityError(reason: unknown): stringParameters
reason: The raw validity payload (any shape is tolerated).
Returns
The joined type chain, or "unknown validity error" when the
payload carries no recognizable tags.
getDevPublicKey()
Get the public key bytes for a dev account.
Useful for address derivation or identity checks in tests without needing the full signer.
getDevPublicKey(name: DevAccountName): Uint8ArrayParameters
name: Dev account name.
Returns
32-byte Sr25519 public key.
isAccountMapped()
Check if an address is mapped on-chain.
Convenience wrapper around checker.addressIsMapped() with error handling.
isAccountMapped(address: string, checker: MappingChecker): Promise<boolean>isSigningRejection()
Check if an error looks like a user-rejected signing request.
Different wallets use different error messages when the user rejects signing: “Cancelled”, “Rejected”, “User rejected”, “denied”. This checks for common patterns as a best-effort heuristic. Non-Error values always return false.
isSigningRejection(error: unknown): booleanParameters
error: The error to check.
Returns
true if the error message matches a known rejection pattern.
submitAndWatch()
Submit a transaction and watch its lifecycle through signing, broadcasting, block inclusion, and (optionally) finalization.
submitAndWatch(tx: SubmittableTransaction, signer: PolkadotSigner, options?: SubmitOptions): Promise<Result<TxResult, TxError>>Parameters
tx: A transaction object withsignSubmitAndWatch. Works with raw PAPI transactions and Ink SDKAsyncTransactionwrappers (resolved automatically).signer: The signer to use. Can come from the Host API (getProductAccountSigner) orcreateDevSigner.options: Submission options (waitFor, timeout, mortality, status callback).
Returns
A Result: ok(TxResult) once included/finalized, or err(TxError) on failure.
The err channel carries a typed TxError — a TxTimeoutError (target state not reached
within timeoutMs), TxDispatchError (on-chain dispatch failed, e.g. insufficient balance or
contract revert), TxValidityError (pre-inclusion validity/submission failure, e.g.
InvalidTransaction::Payment — no dispatch error exists for these), TxSigningRejectedError
(user rejected signing), or a base TxError wrapping any other failure.
withRetry()
Wrap an async function with retry logic and exponential backoff.
Only retries transient errors (network disconnects, temporary RPC failures).
Deterministic errors (TxDispatchError, TxValidityError,
TxBatchError), user rejections (TxSigningRejectedError), and
timeouts (TxTimeoutError) are rethrown immediately without retry.
withRetry(fn: () => Promise<T>, options?: RetryOptions): Promise<T>Parameters
fn: The async function to retry.options: Retry configuration.
Returns
The result of the first successful call.
Examples
const result = await withRetry(
() => submitAndWatch(tx, signer),
{ maxAttempts: 3, baseDelayMs: 1_000 },
);Interfaces
interface BatchApi
Minimal structural type for a PAPI typed API with the Utility pallet.
Structural so it works with any chain that has the Utility pallet, without importing chain-specific descriptors.
Properties
tx
{ Utility: { batch: unknown; batch_all: unknown; force_batch: unknown } }interface BatchSubmitOptions
Options for batchSubmitAndWatch. Extends SubmitOptions with batch mode.
Extends: SubmitOptions
Properties
mode
BatchModeBatch execution mode. Default: "batch_all" (atomic, all-or-nothing).
"batch_all"— Atomic. Reverts all calls if any single call fails."batch"— Best-effort. Stops at first failure but earlier successful calls are not reverted."force_batch"— Likebatchbut continues executing remaining calls after failures (never aborts early).
interface EnsureAccountMappedOptions
Options for ensureAccountMapped.
Properties
onStatus
(status: "checking" | "mapping" | "mapped" | "already-mapped") => voidCalled on mapping transaction status changes.
timeoutMs
numberTimeout in ms for the map_account transaction. Default: 60_000 (1 minute).
interface MappingChecker
Minimal interface for checking if an address is mapped on-chain.
Accepted structurally so this module stays runtime-agnostic. Build one
directly from the typed API of any pallet-revive-capable chain — see the
example on ensureAccountMapped.
Methods
addressIsMapped
addressIsMapped(address: string): Promise<boolean>interface RetryOptions
Options for withRetry.
Properties
baseDelayMs
numberBase delay in ms for exponential backoff. Default: 1_000.
maxAttempts
numberTotal attempts including the first. Default: 3.
maxDelayMs
numberMaximum delay in ms. Default: 15_000.
interface ReviveApi
Minimal typed API shape for Revive.map_account().
Accepted structurally so this module works with any PAPI typed API that has the Revive pallet, without importing chain-specific descriptors.
Properties
tx
{ Revive: { map_account: unknown } }interface SubmitOptions
Options for submitAndWatch.
Properties
mortalityPeriod
numberMortality period in blocks. Default: 256 (~43 minutes on Polkadot).
onStatus
(status: TxStatus) => voidCalled on each lifecycle transition for UI progress indicators.
timeoutMs
numberTimeout in milliseconds. Default: 300_000 (5 minutes).
waitFor
WaitForWhen to resolve the promise. Default: "best-block".
interface SubmittableTransaction
Structural type for any transaction object that supports Observable-based sign-submit-and-watch. Works with raw PAPI transactions and Ink SDK resolved transactions.
Properties
decodedCall
unknownThe decoded call data. Present on PAPI transactions.
signSubmitAndWatch
(signer: PolkadotSigner, options?: { mortality?: { mortal: boolean; period: number } }) => { subscribe: (handlers: { error: (error: Error) => void; next: (event: TxEvent) => void }) => { unsubscribe: () => void } }waited
Promise<SubmittableTransaction>Present on Ink SDK AsyncTransaction wrappers.
interface TxResult
Successful transaction result.
Properties
block
{ hash: string; index: number; number: number }Block where the transaction was included.
dispatchError
unknownDispatch error details when ok is false.
events
unknown[]Raw events emitted by the transaction.
ok
booleanWhether the on-chain dispatch succeeded.
txHash
stringTransaction hash.
interface Weight
Substrate weight representing computational and storage resources.
Matches the shape returned by ReviveApi.call and ReviveApi.eth_transact
dry-run results in the weight_required field.
Properties
proof_size
bigintProof size component in bytes.
ref_time
bigintReference time component in picoseconds.
Type Aliases
type BatchableCall
A transaction or decoded call that can be included in a batch.
Accepts:
- A
SubmittableTransaction(has.decodedCall) - An Ink SDK AsyncTransaction (has
.waitedthat resolves to one with.decodedCall) - A raw decoded call object (passed through as
Record<string, unknown>)
The Record<string, unknown> variant is intentionally broad because PAPI decoded
calls are chain-specific enum types that cannot be imported without chain descriptors.
Runtime validation in resolveDecodedCall rejects null, undefined, and primitives.
type BatchableCall = SubmittableTransaction | { decodedCall: unknown } | Record<string, unknown>type BatchMode
Batch execution mode corresponding to Substrate’s Utility pallet.
type BatchMode = "batch_all" | "batch" | "force_batch"type DevAccountName
Standard Substrate dev account names.
type DevAccountName = "Alice" | "Bob" | "Charlie" | "Dave" | "Eve" | "Ferdie"type TxEvent
PAPI transaction event (discriminated union).
type TxEvent = { txHash: string; type: "signed" } | { txHash: string; type: "broadcasted" } | { block?: { hash: string; index: number; number: number }; dispatchError?: unknown; events?: unknown[]; found: boolean; ok?: boolean; txHash: string; type: "txBestBlocksState" } | { block: { hash: string; index: number; number: number }; dispatchError?: unknown; events: unknown[]; ok: boolean; txHash: string; type: "finalized" }type TxStatus
Transaction lifecycle status for UI callbacks.
type TxStatus = "signing" | "broadcasting" | "in-block" | "finalized" | "error"type WaitFor
When to resolve the submission promise.
type WaitFor = "best-block" | "finalized"