@polkadot-apps/tx 0.2.5 → 0.2.6
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/README.md +259 -0
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# @polkadot-apps/tx
|
|
2
|
+
|
|
3
|
+
Transaction submission, lifecycle watching, and dev signers for Polkadot chains.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @polkadot-apps/tx
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Peer dependency**: `polkadot-api` must be installed in your project.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add polkadot-api
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { submitAndWatch, createDevSigner } from "@polkadot-apps/tx";
|
|
21
|
+
|
|
22
|
+
const signer = createDevSigner("Alice");
|
|
23
|
+
|
|
24
|
+
const result = await submitAndWatch(tx, signer, {
|
|
25
|
+
waitFor: "finalized",
|
|
26
|
+
onStatus: (status) => console.log(status),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log(result.txHash, result.ok);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Transaction lifecycle
|
|
33
|
+
|
|
34
|
+
`submitAndWatch` drives a transaction through its full lifecycle: signing, broadcasting, block inclusion, and optional finalization. You choose when to resolve the returned promise with the `waitFor` option.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { submitAndWatch } from "@polkadot-apps/tx";
|
|
38
|
+
|
|
39
|
+
const result = await submitAndWatch(tx, signer, {
|
|
40
|
+
waitFor: "best-block", // resolve at best-block inclusion (default)
|
|
41
|
+
timeoutMs: 300_000, // 5-minute timeout (default)
|
|
42
|
+
mortalityPeriod: 256, // ~43 minutes on Polkadot (default)
|
|
43
|
+
onStatus: (status) => {
|
|
44
|
+
// "signing" -> "broadcasting" -> "in-block" -> "finalized"
|
|
45
|
+
updateUI(status);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!result.ok) {
|
|
50
|
+
console.error("Dispatch failed:", result.dispatchError);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The function accepts both raw PAPI transactions and Ink SDK `AsyncTransaction` wrappers. Ink SDK wrappers are resolved automatically via the `.waited` promise.
|
|
55
|
+
|
|
56
|
+
## Dev signers
|
|
57
|
+
|
|
58
|
+
Create signers from the well-known Substrate dev mnemonic for local testing. All keys derive at `//Name` using sr25519.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { createDevSigner, getDevPublicKey } from "@polkadot-apps/tx";
|
|
62
|
+
|
|
63
|
+
const alice = createDevSigner("Alice");
|
|
64
|
+
const bobPubKey = getDevPublicKey("Bob"); // Uint8Array (32 bytes)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Available names: `"Alice"`, `"Bob"`, `"Charlie"`, `"Dave"`, `"Eve"`, `"Ferdie"`.
|
|
68
|
+
|
|
69
|
+
## Dry-run and weight buffers
|
|
70
|
+
|
|
71
|
+
Extract a submittable transaction from an Ink SDK dry-run result and apply a safety buffer to weight estimates before submission.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { extractTransaction, applyWeightBuffer } from "@polkadot-apps/tx";
|
|
75
|
+
|
|
76
|
+
const dryRunResult = await contract.query.myMethod(args);
|
|
77
|
+
const tx = extractTransaction(dryRunResult);
|
|
78
|
+
|
|
79
|
+
const buffered = applyWeightBuffer(dryRunResult.weight_required, {
|
|
80
|
+
bufferPercent: 25, // default: 25%
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Account mapping
|
|
85
|
+
|
|
86
|
+
Map an SS58 address to an H160 address on Asset Hub. The operation is idempotent -- it returns `null` when the account is already mapped.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { ensureAccountMapped, isAccountMapped } from "@polkadot-apps/tx";
|
|
90
|
+
|
|
91
|
+
const mapped = await isAccountMapped(address, checker);
|
|
92
|
+
|
|
93
|
+
if (!mapped) {
|
|
94
|
+
const result = await ensureAccountMapped(address, signer, checker, api);
|
|
95
|
+
// result is TxResult or null (if already mapped)
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Retry logic
|
|
100
|
+
|
|
101
|
+
`withRetry` wraps any async function with exponential backoff and jitter. It does **not** retry `TxDispatchError`, `TxSigningRejectedError`, or `TxTimeoutError` -- these represent terminal conditions that retrying cannot fix.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { withRetry, calculateDelay } from "@polkadot-apps/tx";
|
|
105
|
+
|
|
106
|
+
const result = await withRetry(() => submitAndWatch(tx, signer), {
|
|
107
|
+
maxAttempts: 3, // total attempts including the first (default)
|
|
108
|
+
baseDelayMs: 1_000, // initial backoff (default)
|
|
109
|
+
maxDelayMs: 15_000, // backoff cap (default)
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Calculate delay directly for custom retry strategies
|
|
113
|
+
const delay = calculateDelay(2, 1_000, 15_000);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Error handling
|
|
117
|
+
|
|
118
|
+
All errors extend a common `TxError` base class. Use the specific error types and utility functions to handle failures precisely.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import {
|
|
122
|
+
TxTimeoutError,
|
|
123
|
+
TxDispatchError,
|
|
124
|
+
TxSigningRejectedError,
|
|
125
|
+
TxDryRunError,
|
|
126
|
+
TxAccountMappingError,
|
|
127
|
+
formatDispatchError,
|
|
128
|
+
formatDryRunError,
|
|
129
|
+
isSigningRejection,
|
|
130
|
+
} from "@polkadot-apps/tx";
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
await submitAndWatch(tx, signer);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (isSigningRejection(error)) {
|
|
136
|
+
console.log("User cancelled signing");
|
|
137
|
+
} else if (error instanceof TxDispatchError) {
|
|
138
|
+
console.error(error.formatted, error.dispatchError);
|
|
139
|
+
} else if (error instanceof TxTimeoutError) {
|
|
140
|
+
console.error(`Timed out after ${error.timeoutMs}ms`);
|
|
141
|
+
} else if (error instanceof TxDryRunError) {
|
|
142
|
+
console.error(error.formatted, error.revertReason);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## API
|
|
148
|
+
|
|
149
|
+
### `submitAndWatch(tx, signer, options?): Promise<TxResult>`
|
|
150
|
+
|
|
151
|
+
Submit a transaction and watch its lifecycle through to inclusion or finalization.
|
|
152
|
+
|
|
153
|
+
| Parameter | Type | Description |
|
|
154
|
+
|-----------|------|-------------|
|
|
155
|
+
| `tx` | `SubmittableTransaction` | Transaction with `signSubmitAndWatch`. Raw PAPI or Ink SDK. |
|
|
156
|
+
| `signer` | `PolkadotSigner` | Signer from a wallet, Host API, or `createDevSigner`. |
|
|
157
|
+
| `options` | `SubmitOptions` | Optional. See below. |
|
|
158
|
+
|
|
159
|
+
**Throws**: `TxTimeoutError`, `TxDispatchError`, `TxSigningRejectedError`.
|
|
160
|
+
|
|
161
|
+
### `createDevSigner(name): PolkadotSigner`
|
|
162
|
+
|
|
163
|
+
Create a signer from the well-known dev mnemonic at `//Name` (sr25519).
|
|
164
|
+
|
|
165
|
+
| Parameter | Type | Description |
|
|
166
|
+
|-----------|------|-------------|
|
|
167
|
+
| `name` | `DevAccountName` | One of `"Alice"`, `"Bob"`, `"Charlie"`, `"Dave"`, `"Eve"`, `"Ferdie"`. |
|
|
168
|
+
|
|
169
|
+
### `getDevPublicKey(name): Uint8Array`
|
|
170
|
+
|
|
171
|
+
Return the 32-byte public key for a dev account.
|
|
172
|
+
|
|
173
|
+
### `withRetry<T>(fn, options?): Promise<T>`
|
|
174
|
+
|
|
175
|
+
Retry an async function with exponential backoff and jitter. Does not retry `TxDispatchError`, `TxSigningRejectedError`, or `TxTimeoutError`.
|
|
176
|
+
|
|
177
|
+
| Parameter | Type | Description |
|
|
178
|
+
|-----------|------|-------------|
|
|
179
|
+
| `fn` | `() => Promise<T>` | Async function to retry. |
|
|
180
|
+
| `options` | `RetryOptions` | Optional retry configuration. |
|
|
181
|
+
|
|
182
|
+
### `calculateDelay(attempt, baseDelayMs, maxDelayMs): number`
|
|
183
|
+
|
|
184
|
+
Compute the backoff delay for a given attempt number, with jitter.
|
|
185
|
+
|
|
186
|
+
### `extractTransaction(result): SubmittableTransaction`
|
|
187
|
+
|
|
188
|
+
Extract a submittable transaction from an Ink SDK dry-run result.
|
|
189
|
+
|
|
190
|
+
### `applyWeightBuffer(weight, options?): Weight`
|
|
191
|
+
|
|
192
|
+
Apply a percentage safety buffer to a weight estimate. Default buffer is 25%.
|
|
193
|
+
|
|
194
|
+
### `ensureAccountMapped(address, signer, checker, api, options?): Promise<TxResult | null>`
|
|
195
|
+
|
|
196
|
+
Map an SS58 address to H160 on Asset Hub. Returns `null` if the account is already mapped.
|
|
197
|
+
|
|
198
|
+
### `isAccountMapped(address, checker): Promise<boolean>`
|
|
199
|
+
|
|
200
|
+
Check whether an SS58 address is already mapped to an H160 address.
|
|
201
|
+
|
|
202
|
+
### Error utilities
|
|
203
|
+
|
|
204
|
+
| Function | Signature | Description |
|
|
205
|
+
|----------|-----------|-------------|
|
|
206
|
+
| `formatDispatchError` | `(result) => string` | Format a dispatch error into a readable string. |
|
|
207
|
+
| `formatDryRunError` | `(result) => string` | Format a dry-run error into a readable string. |
|
|
208
|
+
| `isSigningRejection` | `(error) => boolean` | Check if an error is a signing rejection. |
|
|
209
|
+
|
|
210
|
+
## Types
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
type TxStatus = "signing" | "broadcasting" | "in-block" | "finalized" | "error";
|
|
214
|
+
|
|
215
|
+
type WaitFor = "best-block" | "finalized";
|
|
216
|
+
|
|
217
|
+
interface TxResult {
|
|
218
|
+
txHash: string;
|
|
219
|
+
ok: boolean;
|
|
220
|
+
block: { hash: string; number: number; index: number };
|
|
221
|
+
events: unknown[];
|
|
222
|
+
dispatchError?: unknown;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface SubmitOptions {
|
|
226
|
+
waitFor?: WaitFor; // default: "best-block"
|
|
227
|
+
timeoutMs?: number; // default: 300_000
|
|
228
|
+
mortalityPeriod?: number; // default: 256
|
|
229
|
+
onStatus?: (status: TxStatus) => void;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
interface RetryOptions {
|
|
233
|
+
maxAttempts?: number; // default: 3
|
|
234
|
+
baseDelayMs?: number; // default: 1_000
|
|
235
|
+
maxDelayMs?: number; // default: 15_000
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
type DevAccountName = "Alice" | "Bob" | "Charlie" | "Dave" | "Eve" | "Ferdie";
|
|
239
|
+
|
|
240
|
+
interface Weight {
|
|
241
|
+
ref_time: bigint;
|
|
242
|
+
proof_size: bigint;
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Error classes
|
|
247
|
+
|
|
248
|
+
| Class | Extends | Key properties |
|
|
249
|
+
|-------|---------|---------------|
|
|
250
|
+
| `TxError` | `Error` | Base class for all tx errors. |
|
|
251
|
+
| `TxTimeoutError` | `TxError` | `timeoutMs: number` |
|
|
252
|
+
| `TxDispatchError` | `TxError` | `dispatchError: unknown`, `formatted: string` |
|
|
253
|
+
| `TxSigningRejectedError` | `TxError` | User rejected signing. |
|
|
254
|
+
| `TxDryRunError` | `TxError` | `raw: unknown`, `formatted: string`, `revertReason?: string` |
|
|
255
|
+
| `TxAccountMappingError` | `TxError` | Account mapping failed. |
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polkadot-apps/tx",
|
|
3
|
-
"
|
|
3
|
+
"description": "Transaction submission, lifecycle watching, and dev signers for Polkadot chains",
|
|
4
|
+
"version": "0.2.6",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"types": "./dist/index.d.ts",
|
|
@@ -19,8 +20,8 @@
|
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"polkadot-api": "^1.23.3",
|
|
21
22
|
"@polkadot-labs/hdkd-helpers": "^0.0.27",
|
|
22
|
-
"@polkadot-apps/keys": "0.3.
|
|
23
|
-
"@polkadot-apps/logger": "0.1.
|
|
23
|
+
"@polkadot-apps/keys": "0.3.3",
|
|
24
|
+
"@polkadot-apps/logger": "0.1.4"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"typescript": "^5.9.3"
|