@dedot/api 1.1.1 → 1.3.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.
@@ -257,15 +257,26 @@ class BaseSubstrateClient extends index_js_2.JsonRpcClient {
257
257
  this.on('connected', this.onConnected);
258
258
  // @ts-ignore
259
259
  this.on('disconnected', this.onDisconnected);
260
- return new Promise((resolve) => {
260
+ return new Promise((resolve, reject) => {
261
+ // @ts-ignore
262
+ const offError = this.on('error', (err) => {
263
+ reject(err instanceof Error ? err : new Error(String(err)));
264
+ });
261
265
  // @ts-ignore
262
266
  this.once('ready', () => {
267
+ offError();
263
268
  resolve(this);
264
269
  });
265
270
  });
266
271
  }
267
272
  onConnected = async () => {
268
- await this.initialize();
273
+ try {
274
+ await this.initialize();
275
+ }
276
+ catch (e) {
277
+ // @ts-ignore — surface init failure so the pending connect() promise can reject
278
+ this.emit('error', e);
279
+ }
269
280
  };
270
281
  onDisconnected = async () => { };
271
282
  async initialize() {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DedotClient = void 0;
4
+ const utils_1 = require("@dedot/utils");
4
5
  const index_js_1 = require("../json-rpc/index.js");
5
6
  const LegacyClient_js_1 = require("./LegacyClient.js");
6
7
  const V2Client_js_1 = require("./V2Client.js");
@@ -51,7 +52,12 @@ const V2Client_js_1 = require("./V2Client.js");
51
52
  */
52
53
  class DedotClient {
53
54
  #client;
54
- /** The JSON-RPC version being used ('v2' or 'legacy') */
55
+ #pendingOptions;
56
+ /**
57
+ * The JSON-RPC version being used ('v2' or 'legacy').
58
+ *
59
+ * In auto-detect mode (no `rpcVersion` option), this is resolved during `connect()`.
60
+ */
55
61
  rpcVersion;
56
62
  /**
57
63
  * Creates a new DedotClient instance.
@@ -61,18 +67,14 @@ class DedotClient {
61
67
  * @param options - Client configuration options or a JsonRpcProvider instance
62
68
  */
63
69
  constructor(options) {
64
- let rpcVersion = 'v2';
65
- if (!(0, index_js_1.isJsonRpcProvider)(options)) {
66
- if (options['rpcVersion'] === 'legacy') {
67
- rpcVersion = 'legacy';
68
- }
69
- }
70
- this.rpcVersion = rpcVersion;
71
- if (this.rpcVersion === 'legacy') {
72
- this.#client = new LegacyClient_js_1.LegacyClient(options);
70
+ const explicitVersion = (0, index_js_1.isJsonRpcProvider)(options) ? undefined : options.rpcVersion;
71
+ if (explicitVersion) {
72
+ this.rpcVersion = explicitVersion;
73
+ this.#client =
74
+ explicitVersion === 'legacy' ? new LegacyClient_js_1.LegacyClient(options) : new V2Client_js_1.V2Client(options);
73
75
  }
74
76
  else {
75
- this.#client = new V2Client_js_1.V2Client(options);
77
+ this.#pendingOptions = options;
76
78
  }
77
79
  }
78
80
  /**
@@ -284,10 +286,34 @@ class DedotClient {
284
286
  /**
285
287
  * Establishes connection to the blockchain network.
286
288
  *
289
+ * When `rpcVersion` was not specified, this tries JSON-RPC v2 first and
290
+ * transparently falls back to legacy if the node does not support v2.
291
+ *
287
292
  * @returns This client instance for method chaining
288
293
  */
289
294
  async connect() {
290
- await this.#client.connect();
295
+ if (this.#client) {
296
+ await this.#client.connect();
297
+ return this;
298
+ }
299
+ const options = this.#pendingOptions;
300
+ this.#pendingOptions = undefined;
301
+ const v2 = new V2Client_js_1.V2Client(options);
302
+ try {
303
+ await v2.connect();
304
+ this.#client = v2;
305
+ this.rpcVersion = 'v2';
306
+ }
307
+ catch (e) {
308
+ if (!(e instanceof utils_1.JsonRpcV2NotSupportedError))
309
+ throw e;
310
+ console.warn('JSON-RPC v2 is not supported by the connected node, falling back to legacy JSON-RPC.');
311
+ await v2.disconnect().catch(utils_1.noop);
312
+ const legacy = new LegacyClient_js_1.LegacyClient(options);
313
+ await legacy.connect();
314
+ this.#client = legacy;
315
+ this.rpcVersion = 'legacy';
316
+ }
291
317
  return this;
292
318
  }
293
319
  /**
@@ -395,25 +421,27 @@ class DedotClient {
395
421
  return this.#client.sendTx(tx, callback);
396
422
  }
397
423
  /**
398
- * Convert a raw hex-encoded transaction or an Extrinsic instance into a submittable extrinsic
424
+ * Convert a transaction input into a submittable extrinsic
399
425
  * with `sign`, `signAndSend`, `send`, and `paymentInfo` methods.
400
426
  *
401
- * @param tx - A hex-encoded transaction string or an Extrinsic instance
427
+ * @param tx - A hex-encoded extrinsic or runtime call, a Uint8Array of encoded bytes,
428
+ * an Extrinsic instance, or an IRuntimeTxCall object.
429
+ * For HexString/Uint8Array, it first tries to decode as a full extrinsic;
430
+ * if that fails, it falls back to decoding as a raw runtime call.
402
431
  * @returns A submittable extrinsic instance
403
432
  *
404
433
  * @example
405
434
  * ```typescript
406
- * // Convert a raw hex transaction to a submittable extrinsic
435
+ * // From a raw hex extrinsic
407
436
  * const submittable = client.toTx(rawTxHex);
408
437
  *
438
+ * // From a runtime call object
439
+ * const submittable = client.toTx({ pallet: 'Balances', palletCall: { name: 'TransferKeepAlive', params: { dest, value } } });
440
+ *
409
441
  * // Sign and send
410
442
  * const unsub = await submittable.signAndSend(alice, (result) => {
411
443
  * console.log('Status:', result.status);
412
444
  * });
413
- *
414
- * // Or query payment info
415
- * const paymentInfo = await submittable.paymentInfo(alice);
416
- * console.log('Estimated fee:', paymentInfo.partialFee);
417
445
  * ```
418
446
  */
419
447
  toTx(tx) {
@@ -78,6 +78,10 @@ class V2Client// prettier-end-here
78
78
  const shouldInitialize = !this._genesisHash;
79
79
  if (shouldInitialize) {
80
80
  const rpcMethods = (await this.rpc.rpc_methods()).methods;
81
+ if (!rpcMethods.some((m) => m.startsWith('chainHead_'))) {
82
+ throw new utils_1.JsonRpcV2NotSupportedError('The connected node does not support JSON-RPC v2 (no chainHead_* methods). ' +
83
+ 'Omit `rpcVersion` to auto-detect, or pass `rpcVersion: "legacy"`.');
84
+ }
81
85
  this._chainHead = new index_js_2.ChainHead(this, { rpcMethods });
82
86
  this._chainSpec = new index_js_2.ChainSpec(this, { rpcMethods });
83
87
  // Always initialize Archive, but only set up fallback if supported
@@ -13,9 +13,17 @@ exports.MORTAL_PERIOD = 12 * 60 * 1000;
13
13
  class CheckMortality extends SignedExtension_js_1.SignedExtension {
14
14
  #signingHeader;
15
15
  async init() {
16
- this.#signingHeader = await this.#getSigningHeader();
17
- this.data = { period: this.#calculateMortalLength(), current: BigInt(this.#signingHeader.number) };
18
- this.additionalSigned = this.#signingHeader.hash;
16
+ const mortality = this.payloadOptions.mortality;
17
+ if (mortality?.type === 'Immortal') {
18
+ this.data = { type: 'Immortal' };
19
+ this.additionalSigned = this.client.genesisHash;
20
+ }
21
+ else {
22
+ this.#signingHeader = await this.#getSigningHeader();
23
+ const period = mortality?.type === 'Mortal' ? BigInt(mortality.period) : this.#calculateMortalLength();
24
+ this.data = { period, current: BigInt(this.#signingHeader.number) };
25
+ this.additionalSigned = this.#signingHeader.hash;
26
+ }
19
27
  }
20
28
  async fromPayload(payload) {
21
29
  const { era, blockHash, blockNumber } = payload;
@@ -88,7 +96,7 @@ class CheckMortality extends SignedExtension_js_1.SignedExtension {
88
96
  return {
89
97
  era: (0, utils_1.u8aToHex)(this.$Data.tryEncode(this.data)),
90
98
  blockHash: this.additionalSigned,
91
- blockNumber: (0, utils_1.numberToHex)(this.#signingHeader.number),
99
+ blockNumber: (0, utils_1.numberToHex)(this.#signingHeader?.number ?? 0),
92
100
  };
93
101
  }
94
102
  }
@@ -11,14 +11,8 @@ const utils_js_1 = require("./utils.js");
11
11
  */
12
12
  class SubmittableExtrinsic extends BaseSubmittableExtrinsic_js_1.BaseSubmittableExtrinsic {
13
13
  static fromTx(client, tx) {
14
- let extrinsic;
15
- if ((0, utils_1.isHex)(tx)) {
16
- extrinsic = client.registry.$Extrinsic.tryDecode(tx);
17
- }
18
- else {
19
- extrinsic = tx;
20
- }
21
- return new SubmittableExtrinsic(client, extrinsic.call, extrinsic.preamble);
14
+ const { call, preamble } = (0, utils_js_1.resolveCallAndPreamble)(client.registry, tx);
15
+ return new SubmittableExtrinsic(client, call, preamble);
22
16
  }
23
17
  async dryRun(account, optionsOrHash) {
24
18
  const dryRunFn = this.client.rpc.system_dryRun;
@@ -17,14 +17,8 @@ class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic_js_1.BaseSubmittab
17
17
  this.client = client;
18
18
  }
19
19
  static fromTx(client, tx) {
20
- let extrinsic;
21
- if ((0, utils_1.isHex)(tx)) {
22
- extrinsic = client.registry.$Extrinsic.tryDecode(tx);
23
- }
24
- else {
25
- extrinsic = tx;
26
- }
27
- return new SubmittableExtrinsicV2(client, extrinsic.call, extrinsic.preamble);
20
+ const { call, preamble } = (0, utils_js_1.resolveCallAndPreamble)(client.registry, tx);
21
+ return new SubmittableExtrinsicV2(client, call, preamble);
28
22
  }
29
23
  async #send(callback) {
30
24
  const api = this.client;
@@ -1,8 +1,43 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.txDefer = exports.toTxStatus = exports.signRawMessage = exports.isKeyringPair = void 0;
3
+ exports.txDefer = exports.toTxStatus = exports.signRawMessage = exports.isKeyringPair = exports.resolveCallAndPreamble = void 0;
4
+ const codecs_1 = require("@dedot/codecs");
4
5
  const utils_1 = require("@dedot/utils");
5
6
  const errors_js_1 = require("./errors.js");
7
+ /**
8
+ * Check if a value is an IRuntimeTxCall object (has a 'pallet' property).
9
+ */
10
+ function isRuntimeTxCall(tx) {
11
+ return typeof tx === 'object' && tx !== null && 'pallet' in tx;
12
+ }
13
+ /**
14
+ * Resolve a transaction input into a call and optional preamble.
15
+ *
16
+ * Supports:
17
+ * - `Extrinsic` instance: extract call + preamble
18
+ * - `IRuntimeTxCall` object: use directly as call, no preamble
19
+ * - `HexString` or `Uint8Array`: try decode as extrinsic first, fallback to runtime call
20
+ */
21
+ function resolveCallAndPreamble(registry, tx) {
22
+ if (tx instanceof codecs_1.Extrinsic) {
23
+ return { call: tx.call, preamble: tx.preamble };
24
+ }
25
+ if (isRuntimeTxCall(tx)) {
26
+ return { call: tx };
27
+ }
28
+ // HexString or Uint8Array: try extrinsic decode first, fallback to runtime call
29
+ try {
30
+ const extrinsic = registry.$Extrinsic.tryDecode(tx);
31
+ return { call: extrinsic.call, preamble: extrinsic.preamble };
32
+ }
33
+ catch {
34
+ const { callTypeId } = registry.metadata.extrinsic;
35
+ const $RuntimeCall = registry.findCodec(callTypeId);
36
+ const call = $RuntimeCall.tryDecode(tx);
37
+ return { call };
38
+ }
39
+ }
40
+ exports.resolveCallAndPreamble = resolveCallAndPreamble;
6
41
  function isKeyringPair(account) {
7
42
  return (0, utils_1.isFunction)(account.sign);
8
43
  }
@@ -1,7 +1,7 @@
1
1
  import { BlockHash, Extrinsic, Hash, Metadata, PortableRegistry, RuntimeVersion } from '@dedot/codecs';
2
2
  import { type JsonRpcProvider } from '@dedot/providers';
3
3
  import { type IStorage } from '@dedot/storage';
4
- import { Callback, ChainSubmittableExtrinsic, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, ISubmittableResult, Query, QueryFnResult, RpcVersion, TxUnsub, Unsub } from '@dedot/types';
4
+ import { Callback, ChainSubmittableExtrinsic, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, IRuntimeTxCall, ISubmittableResult, Query, QueryFnResult, RpcVersion, TxUnsub, Unsub } from '@dedot/types';
5
5
  import { Deferred, HexString, LRUCache } from '@dedot/utils';
6
6
  import type { SubstrateApi } from '../chaintypes/index.js';
7
7
  import { JsonRpcClient } from '../json-rpc/index.js';
@@ -133,7 +133,7 @@ export declare abstract class BaseSubstrateClient<ChainApi extends GenericSubstr
133
133
  }>): Promise<Unsub>;
134
134
  protected getStorageQuery(): BaseStorageQuery;
135
135
  sendTx(tx: HexString | Extrinsic, callback?: Callback<ISubmittableResult<ChainApi['types']['EventRecord']>>): TxUnsub;
136
- toTx(tx: HexString | Extrinsic): ChainSubmittableExtrinsic<ChainApi>;
136
+ toTx(tx: HexString | Uint8Array | Extrinsic | IRuntimeTxCall): ChainSubmittableExtrinsic<ChainApi>;
137
137
  get chainSpec(): IChainSpec;
138
138
  on<Event extends Events = Events>(event: Event, handler: EventHandlerFn<Event>): () => void;
139
139
  }
@@ -253,15 +253,26 @@ export class BaseSubstrateClient extends JsonRpcClient {
253
253
  this.on('connected', this.onConnected);
254
254
  // @ts-ignore
255
255
  this.on('disconnected', this.onDisconnected);
256
- return new Promise((resolve) => {
256
+ return new Promise((resolve, reject) => {
257
+ // @ts-ignore
258
+ const offError = this.on('error', (err) => {
259
+ reject(err instanceof Error ? err : new Error(String(err)));
260
+ });
257
261
  // @ts-ignore
258
262
  this.once('ready', () => {
263
+ offError();
259
264
  resolve(this);
260
265
  });
261
266
  });
262
267
  }
263
268
  onConnected = async () => {
264
- await this.initialize();
269
+ try {
270
+ await this.initialize();
271
+ }
272
+ catch (e) {
273
+ // @ts-ignore — surface init failure so the pending connect() promise can reject
274
+ this.emit('error', e);
275
+ }
265
276
  };
266
277
  onDisconnected = async () => { };
267
278
  async initialize() {
@@ -1,6 +1,6 @@
1
1
  import { type Extrinsic, Metadata, PortableRegistry } from '@dedot/codecs';
2
2
  import { ConnectionStatus, JsonRpcProvider } from '@dedot/providers';
3
- import { Callback, ChainSubmittableExtrinsic, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, ISubmittableResult, Query, QueryFnResult, RpcVersion, TxUnsub, Unsub } from '@dedot/types';
3
+ import { Callback, ChainSubmittableExtrinsic, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, IRuntimeTxCall, ISubmittableResult, Query, QueryFnResult, RpcVersion, TxUnsub, Unsub } from '@dedot/types';
4
4
  import { HexString } from '@dedot/utils';
5
5
  import { SubstrateApi } from '../chaintypes/index.js';
6
6
  import { ApiEvent, ApiOptions, BlockExplorer, ISubstrateClient, ISubstrateClientAt, SubstrateRuntimeVersion, IChainSpec, type EventHandlerFn } from '../types.js';
@@ -9,9 +9,14 @@ import { ApiEvent, ApiOptions, BlockExplorer, ISubstrateClient, ISubstrateClient
9
9
  */
10
10
  export type ClientOptions = ApiOptions & {
11
11
  /**
12
- * The JSON-RPC version to use
13
- * - 'v2' (default): Uses the new JSON-RPC v2 specification
14
- * - 'legacy': Uses the legacy JSON-RPC specification for older nodes
12
+ * The JSON-RPC version to use.
13
+ *
14
+ * - _unset_ (default): auto-detect. Try JSON-RPC v2 first and transparently fall back
15
+ * to legacy if the connected node does not expose v2 methods (no `chainHead_*`).
16
+ * After `connect()` resolves, `client.rpcVersion` reflects the version that was picked.
17
+ * - `'v2'`: force JSON-RPC v2. Throws `JsonRpcV2NotSupportedError` during `connect()`
18
+ * if the node does not support v2.
19
+ * - `'legacy'`: force legacy JSON-RPC.
15
20
  */
16
21
  rpcVersion?: RpcVersion;
17
22
  };
@@ -62,7 +67,11 @@ export type ClientOptions = ApiOptions & {
62
67
  */
63
68
  export declare class DedotClient<ChainApi extends GenericSubstrateApi = SubstrateApi> implements ISubstrateClient<ChainApi, ApiEvent> {
64
69
  #private;
65
- /** The JSON-RPC version being used ('v2' or 'legacy') */
70
+ /**
71
+ * The JSON-RPC version being used ('v2' or 'legacy').
72
+ *
73
+ * In auto-detect mode (no `rpcVersion` option), this is resolved during `connect()`.
74
+ */
66
75
  rpcVersion: RpcVersion;
67
76
  /**
68
77
  * Creates a new DedotClient instance.
@@ -239,6 +248,9 @@ export declare class DedotClient<ChainApi extends GenericSubstrateApi = Substrat
239
248
  /**
240
249
  * Establishes connection to the blockchain network.
241
250
  *
251
+ * When `rpcVersion` was not specified, this tries JSON-RPC v2 first and
252
+ * transparently falls back to legacy if the node does not support v2.
253
+ *
242
254
  * @returns This client instance for method chaining
243
255
  */
244
256
  connect(): Promise<this>;
@@ -360,28 +372,30 @@ export declare class DedotClient<ChainApi extends GenericSubstrateApi = Substrat
360
372
  */
361
373
  sendTx(tx: HexString | Extrinsic, callback?: Callback<ISubmittableResult<ChainApi['types']['EventRecord']>>): TxUnsub;
362
374
  /**
363
- * Convert a raw hex-encoded transaction or an Extrinsic instance into a submittable extrinsic
375
+ * Convert a transaction input into a submittable extrinsic
364
376
  * with `sign`, `signAndSend`, `send`, and `paymentInfo` methods.
365
377
  *
366
- * @param tx - A hex-encoded transaction string or an Extrinsic instance
378
+ * @param tx - A hex-encoded extrinsic or runtime call, a Uint8Array of encoded bytes,
379
+ * an Extrinsic instance, or an IRuntimeTxCall object.
380
+ * For HexString/Uint8Array, it first tries to decode as a full extrinsic;
381
+ * if that fails, it falls back to decoding as a raw runtime call.
367
382
  * @returns A submittable extrinsic instance
368
383
  *
369
384
  * @example
370
385
  * ```typescript
371
- * // Convert a raw hex transaction to a submittable extrinsic
386
+ * // From a raw hex extrinsic
372
387
  * const submittable = client.toTx(rawTxHex);
373
388
  *
389
+ * // From a runtime call object
390
+ * const submittable = client.toTx({ pallet: 'Balances', palletCall: { name: 'TransferKeepAlive', params: { dest, value } } });
391
+ *
374
392
  * // Sign and send
375
393
  * const unsub = await submittable.signAndSend(alice, (result) => {
376
394
  * console.log('Status:', result.status);
377
395
  * });
378
- *
379
- * // Or query payment info
380
- * const paymentInfo = await submittable.paymentInfo(alice);
381
- * console.log('Estimated fee:', paymentInfo.partialFee);
382
396
  * ```
383
397
  */
384
- toTx(tx: HexString | Extrinsic): ChainSubmittableExtrinsic<ChainApi>;
398
+ toTx(tx: HexString | Uint8Array | Extrinsic | IRuntimeTxCall): ChainSubmittableExtrinsic<ChainApi>;
385
399
  /**
386
400
  * Clear internal caches.
387
401
  *
@@ -1,3 +1,4 @@
1
+ import { JsonRpcV2NotSupportedError, noop } from '@dedot/utils';
1
2
  import { isJsonRpcProvider } from '../json-rpc/index.js';
2
3
  import { LegacyClient } from './LegacyClient.js';
3
4
  import { V2Client } from './V2Client.js';
@@ -48,7 +49,12 @@ import { V2Client } from './V2Client.js';
48
49
  */
49
50
  export class DedotClient {
50
51
  #client;
51
- /** The JSON-RPC version being used ('v2' or 'legacy') */
52
+ #pendingOptions;
53
+ /**
54
+ * The JSON-RPC version being used ('v2' or 'legacy').
55
+ *
56
+ * In auto-detect mode (no `rpcVersion` option), this is resolved during `connect()`.
57
+ */
52
58
  rpcVersion;
53
59
  /**
54
60
  * Creates a new DedotClient instance.
@@ -58,18 +64,14 @@ export class DedotClient {
58
64
  * @param options - Client configuration options or a JsonRpcProvider instance
59
65
  */
60
66
  constructor(options) {
61
- let rpcVersion = 'v2';
62
- if (!isJsonRpcProvider(options)) {
63
- if (options['rpcVersion'] === 'legacy') {
64
- rpcVersion = 'legacy';
65
- }
66
- }
67
- this.rpcVersion = rpcVersion;
68
- if (this.rpcVersion === 'legacy') {
69
- this.#client = new LegacyClient(options);
67
+ const explicitVersion = isJsonRpcProvider(options) ? undefined : options.rpcVersion;
68
+ if (explicitVersion) {
69
+ this.rpcVersion = explicitVersion;
70
+ this.#client =
71
+ explicitVersion === 'legacy' ? new LegacyClient(options) : new V2Client(options);
70
72
  }
71
73
  else {
72
- this.#client = new V2Client(options);
74
+ this.#pendingOptions = options;
73
75
  }
74
76
  }
75
77
  /**
@@ -281,10 +283,34 @@ export class DedotClient {
281
283
  /**
282
284
  * Establishes connection to the blockchain network.
283
285
  *
286
+ * When `rpcVersion` was not specified, this tries JSON-RPC v2 first and
287
+ * transparently falls back to legacy if the node does not support v2.
288
+ *
284
289
  * @returns This client instance for method chaining
285
290
  */
286
291
  async connect() {
287
- await this.#client.connect();
292
+ if (this.#client) {
293
+ await this.#client.connect();
294
+ return this;
295
+ }
296
+ const options = this.#pendingOptions;
297
+ this.#pendingOptions = undefined;
298
+ const v2 = new V2Client(options);
299
+ try {
300
+ await v2.connect();
301
+ this.#client = v2;
302
+ this.rpcVersion = 'v2';
303
+ }
304
+ catch (e) {
305
+ if (!(e instanceof JsonRpcV2NotSupportedError))
306
+ throw e;
307
+ console.warn('JSON-RPC v2 is not supported by the connected node, falling back to legacy JSON-RPC.');
308
+ await v2.disconnect().catch(noop);
309
+ const legacy = new LegacyClient(options);
310
+ await legacy.connect();
311
+ this.#client = legacy;
312
+ this.rpcVersion = 'legacy';
313
+ }
288
314
  return this;
289
315
  }
290
316
  /**
@@ -392,25 +418,27 @@ export class DedotClient {
392
418
  return this.#client.sendTx(tx, callback);
393
419
  }
394
420
  /**
395
- * Convert a raw hex-encoded transaction or an Extrinsic instance into a submittable extrinsic
421
+ * Convert a transaction input into a submittable extrinsic
396
422
  * with `sign`, `signAndSend`, `send`, and `paymentInfo` methods.
397
423
  *
398
- * @param tx - A hex-encoded transaction string or an Extrinsic instance
424
+ * @param tx - A hex-encoded extrinsic or runtime call, a Uint8Array of encoded bytes,
425
+ * an Extrinsic instance, or an IRuntimeTxCall object.
426
+ * For HexString/Uint8Array, it first tries to decode as a full extrinsic;
427
+ * if that fails, it falls back to decoding as a raw runtime call.
399
428
  * @returns A submittable extrinsic instance
400
429
  *
401
430
  * @example
402
431
  * ```typescript
403
- * // Convert a raw hex transaction to a submittable extrinsic
432
+ * // From a raw hex extrinsic
404
433
  * const submittable = client.toTx(rawTxHex);
405
434
  *
435
+ * // From a runtime call object
436
+ * const submittable = client.toTx({ pallet: 'Balances', palletCall: { name: 'TransferKeepAlive', params: { dest, value } } });
437
+ *
406
438
  * // Sign and send
407
439
  * const unsub = await submittable.signAndSend(alice, (result) => {
408
440
  * console.log('Status:', result.status);
409
441
  * });
410
- *
411
- * // Or query payment info
412
- * const paymentInfo = await submittable.paymentInfo(alice);
413
- * console.log('Estimated fee:', paymentInfo.partialFee);
414
442
  * ```
415
443
  */
416
444
  toTx(tx) {
@@ -1,6 +1,6 @@
1
1
  import { BlockHash, type Extrinsic, Metadata } from '@dedot/codecs';
2
2
  import type { JsonRpcProvider } from '@dedot/providers';
3
- import { Callback, ChainSubmittableExtrinsic, GenericSubstrateApi, ISubmittableResult, TxUnsub } from '@dedot/types';
3
+ import { Callback, ChainSubmittableExtrinsic, GenericSubstrateApi, IRuntimeTxCall, ISubmittableResult, TxUnsub } from '@dedot/types';
4
4
  import { HexString } from '@dedot/utils';
5
5
  import type { SubstrateApi } from '../chaintypes/index.js';
6
6
  import { BaseStorageQuery } from '../storage/index.js';
@@ -137,5 +137,5 @@ export declare class LegacyClient<ChainApi extends GenericSubstrateApi = Substra
137
137
  at<ChainApiAt extends GenericSubstrateApi = ChainApi>(hash: BlockHash): Promise<ISubstrateClientAt<ChainApiAt>>;
138
138
  protected getStorageQuery(): BaseStorageQuery;
139
139
  sendTx(tx: HexString | Extrinsic, callback?: Callback<ISubmittableResult<ChainApi['types']['EventRecord']>>): TxUnsub;
140
- toTx(tx: HexString | Extrinsic): ChainSubmittableExtrinsic<ChainApi>;
140
+ toTx(tx: HexString | Uint8Array | Extrinsic | IRuntimeTxCall): ChainSubmittableExtrinsic<ChainApi>;
141
141
  }
@@ -1,6 +1,6 @@
1
1
  import { BlockHash, type Extrinsic, Metadata } from '@dedot/codecs';
2
2
  import type { JsonRpcProvider } from '@dedot/providers';
3
- import { Callback, ChainSubmittableExtrinsic, GenericSubstrateApi, ISubmittableResult, TxUnsub } from '@dedot/types';
3
+ import { Callback, ChainSubmittableExtrinsic, GenericSubstrateApi, IRuntimeTxCall, ISubmittableResult, TxUnsub } from '@dedot/types';
4
4
  import { HexString } from '@dedot/utils';
5
5
  import type { SubstrateApi } from '../chaintypes/index.js';
6
6
  import { Archive, ChainHead, ChainSpec, PinnedBlock } from '../json-rpc/index.js';
@@ -72,5 +72,5 @@ export declare class V2Client<ChainApi extends GenericSubstrateApi = SubstrateAp
72
72
  at<ChainApiAt extends GenericSubstrateApi = ChainApi>(hash: BlockHash): Promise<ISubstrateClientAt<ChainApiAt>>;
73
73
  protected getStorageQuery(): BaseStorageQuery;
74
74
  sendTx(tx: HexString | Extrinsic, callback?: Callback<ISubmittableResult<ChainApi['types']['EventRecord']>>): TxUnsub;
75
- toTx(tx: HexString | Extrinsic): ChainSubmittableExtrinsic<ChainApi>;
75
+ toTx(tx: HexString | Uint8Array | Extrinsic | IRuntimeTxCall): ChainSubmittableExtrinsic<ChainApi>;
76
76
  }
@@ -1,6 +1,6 @@
1
1
  import { $H256, $Header, $RuntimeVersion, PortableRegistry, } from '@dedot/codecs';
2
2
  import { u32 } from '@dedot/shape';
3
- import { assert, concatU8a, DedotError, twox64Concat, u8aToHex, xxhashAsU8a } from '@dedot/utils';
3
+ import { assert, concatU8a, DedotError, JsonRpcV2NotSupportedError, twox64Concat, u8aToHex, xxhashAsU8a, } from '@dedot/utils';
4
4
  import { ConstantExecutor, ErrorExecutor, EventExecutor, RuntimeApiExecutorV2, StorageQueryExecutorV2, TxExecutorV2, ViewFunctionExecutorV2, } from '../executor/index.js';
5
5
  import { SubmittableExtrinsicV2 } from '../extrinsic/submittable/SubmittableExtrinsicV2.js';
6
6
  import { Archive, ChainHead, ChainSpec, Transaction, TransactionWatch } from '../json-rpc/index.js';
@@ -75,6 +75,10 @@ export class V2Client// prettier-end-here
75
75
  const shouldInitialize = !this._genesisHash;
76
76
  if (shouldInitialize) {
77
77
  const rpcMethods = (await this.rpc.rpc_methods()).methods;
78
+ if (!rpcMethods.some((m) => m.startsWith('chainHead_'))) {
79
+ throw new JsonRpcV2NotSupportedError('The connected node does not support JSON-RPC v2 (no chainHead_* methods). ' +
80
+ 'Omit `rpcVersion` to auto-detect, or pass `rpcVersion: "legacy"`.');
81
+ }
78
82
  this._chainHead = new ChainHead(this, { rpcMethods });
79
83
  this._chainSpec = new ChainSpec(this, { rpcMethods });
80
84
  // Always initialize Archive, but only set up fallback if supported
@@ -10,9 +10,17 @@ export const MORTAL_PERIOD = 12 * 60 * 1000;
10
10
  export class CheckMortality extends SignedExtension {
11
11
  #signingHeader;
12
12
  async init() {
13
- this.#signingHeader = await this.#getSigningHeader();
14
- this.data = { period: this.#calculateMortalLength(), current: BigInt(this.#signingHeader.number) };
15
- this.additionalSigned = this.#signingHeader.hash;
13
+ const mortality = this.payloadOptions.mortality;
14
+ if (mortality?.type === 'Immortal') {
15
+ this.data = { type: 'Immortal' };
16
+ this.additionalSigned = this.client.genesisHash;
17
+ }
18
+ else {
19
+ this.#signingHeader = await this.#getSigningHeader();
20
+ const period = mortality?.type === 'Mortal' ? BigInt(mortality.period) : this.#calculateMortalLength();
21
+ this.data = { period, current: BigInt(this.#signingHeader.number) };
22
+ this.additionalSigned = this.#signingHeader.hash;
23
+ }
16
24
  }
17
25
  async fromPayload(payload) {
18
26
  const { era, blockHash, blockNumber } = payload;
@@ -85,7 +93,7 @@ export class CheckMortality extends SignedExtension {
85
93
  return {
86
94
  era: u8aToHex(this.$Data.tryEncode(this.data)),
87
95
  blockHash: this.additionalSigned,
88
- blockNumber: numberToHex(this.#signingHeader.number),
96
+ blockNumber: numberToHex(this.#signingHeader?.number ?? 0),
89
97
  };
90
98
  }
91
99
  }
@@ -1,5 +1,5 @@
1
1
  import { BlockHash, Extrinsic } from '@dedot/codecs';
2
- import { AddressOrPair, Callback, DryRunResult, ISubmittableExtrinsicLegacy, ISubmittableResult, SignerOptions, TxHash, TxUnsub } from '@dedot/types';
2
+ import { AddressOrPair, Callback, DryRunResult, IRuntimeTxCall, ISubmittableExtrinsicLegacy, ISubmittableResult, SignerOptions, TxHash, TxUnsub } from '@dedot/types';
3
3
  import { HexString } from '@dedot/utils';
4
4
  import { LegacyClient } from '../../client/LegacyClient.js';
5
5
  import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
@@ -8,7 +8,7 @@ import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
8
8
  * @description A wrapper around an Extrinsic that exposes methods to sign, send, and other utility around Extrinsic.
9
9
  */
10
10
  export declare class SubmittableExtrinsic extends BaseSubmittableExtrinsic implements ISubmittableExtrinsicLegacy {
11
- static fromTx(client: LegacyClient<any>, tx: HexString | Extrinsic): SubmittableExtrinsic;
11
+ static fromTx(client: LegacyClient<any>, tx: HexString | Uint8Array | Extrinsic | IRuntimeTxCall): SubmittableExtrinsic;
12
12
  dryRun(account: AddressOrPair, optionsOrHash?: Partial<SignerOptions> | BlockHash): Promise<DryRunResult>;
13
13
  send(): TxHash;
14
14
  send(callback: Callback<ISubmittableResult>): TxUnsub;
@@ -1,21 +1,15 @@
1
1
  import { assert, isHex, noop } from '@dedot/utils';
2
2
  import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
3
3
  import { SubmittableResult } from './SubmittableResult.js';
4
- import { toTxStatus, txDefer } from './utils.js';
4
+ import { resolveCallAndPreamble, toTxStatus, txDefer } from './utils.js';
5
5
  /**
6
6
  * @name SubmittableExtrinsic
7
7
  * @description A wrapper around an Extrinsic that exposes methods to sign, send, and other utility around Extrinsic.
8
8
  */
9
9
  export class SubmittableExtrinsic extends BaseSubmittableExtrinsic {
10
10
  static fromTx(client, tx) {
11
- let extrinsic;
12
- if (isHex(tx)) {
13
- extrinsic = client.registry.$Extrinsic.tryDecode(tx);
14
- }
15
- else {
16
- extrinsic = tx;
17
- }
18
- return new SubmittableExtrinsic(client, extrinsic.call, extrinsic.preamble);
11
+ const { call, preamble } = resolveCallAndPreamble(client.registry, tx);
12
+ return new SubmittableExtrinsic(client, call, preamble);
19
13
  }
20
14
  async dryRun(account, optionsOrHash) {
21
15
  const dryRunFn = this.client.rpc.system_dryRun;
@@ -11,7 +11,7 @@ export declare class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
11
11
  #private;
12
12
  client: V2Client<any>;
13
13
  constructor(client: V2Client<any>, call: IRuntimeTxCall, preamble?: Preamble);
14
- static fromTx(client: V2Client<any>, tx: HexString | Extrinsic): SubmittableExtrinsicV2;
14
+ static fromTx(client: V2Client<any>, tx: HexString | Uint8Array | Extrinsic | IRuntimeTxCall): SubmittableExtrinsicV2;
15
15
  send(): TxHash;
16
16
  send(callback: Callback<ISubmittableResult>): TxUnsub;
17
17
  }
@@ -1,8 +1,8 @@
1
- import { AsyncQueue, isHex, noop, waitFor } from '@dedot/utils';
1
+ import { AsyncQueue, noop, waitFor } from '@dedot/utils';
2
2
  import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
3
3
  import { SubmittableResult } from './SubmittableResult.js';
4
4
  import { InvalidTxError } from './errors.js';
5
- import { txDefer } from './utils.js';
5
+ import { resolveCallAndPreamble, txDefer } from './utils.js';
6
6
  /**
7
7
  * @name SubmittableExtrinsicV2
8
8
  * @description Submittable extrinsic based on JSON-RPC v2
@@ -14,14 +14,8 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
14
14
  this.client = client;
15
15
  }
16
16
  static fromTx(client, tx) {
17
- let extrinsic;
18
- if (isHex(tx)) {
19
- extrinsic = client.registry.$Extrinsic.tryDecode(tx);
20
- }
21
- else {
22
- extrinsic = tx;
23
- }
24
- return new SubmittableExtrinsicV2(client, extrinsic.call, extrinsic.preamble);
17
+ const { call, preamble } = resolveCallAndPreamble(client.registry, tx);
18
+ return new SubmittableExtrinsicV2(client, call, preamble);
25
19
  }
26
20
  async #send(callback) {
27
21
  const api = this.client;
@@ -1,6 +1,18 @@
1
- import { TransactionStatus } from '@dedot/codecs';
2
- import { AddressOrPair, IKeyringPair, ISubmittableResult, TxStatus, Unsub } from '@dedot/types';
1
+ import { Extrinsic, Preamble, PortableRegistry, TransactionStatus } from '@dedot/codecs';
2
+ import { AddressOrPair, IKeyringPair, IRuntimeTxCall, ISubmittableResult, TxStatus, Unsub } from '@dedot/types';
3
3
  import { Deferred, HexString } from '@dedot/utils';
4
+ /**
5
+ * Resolve a transaction input into a call and optional preamble.
6
+ *
7
+ * Supports:
8
+ * - `Extrinsic` instance: extract call + preamble
9
+ * - `IRuntimeTxCall` object: use directly as call, no preamble
10
+ * - `HexString` or `Uint8Array`: try decode as extrinsic first, fallback to runtime call
11
+ */
12
+ export declare function resolveCallAndPreamble(registry: PortableRegistry, tx: HexString | Uint8Array | Extrinsic | IRuntimeTxCall): {
13
+ call: IRuntimeTxCall;
14
+ preamble?: Preamble;
15
+ };
4
16
  export declare function isKeyringPair(account: AddressOrPair): account is IKeyringPair;
5
17
  /**
6
18
  * Sign a raw message
@@ -1,5 +1,39 @@
1
+ import { Extrinsic } from '@dedot/codecs';
1
2
  import { assert, blake2AsU8a, deferred, hexToU8a, isFunction } from '@dedot/utils';
2
3
  import { RejectedTxError } from './errors.js';
4
+ /**
5
+ * Check if a value is an IRuntimeTxCall object (has a 'pallet' property).
6
+ */
7
+ function isRuntimeTxCall(tx) {
8
+ return typeof tx === 'object' && tx !== null && 'pallet' in tx;
9
+ }
10
+ /**
11
+ * Resolve a transaction input into a call and optional preamble.
12
+ *
13
+ * Supports:
14
+ * - `Extrinsic` instance: extract call + preamble
15
+ * - `IRuntimeTxCall` object: use directly as call, no preamble
16
+ * - `HexString` or `Uint8Array`: try decode as extrinsic first, fallback to runtime call
17
+ */
18
+ export function resolveCallAndPreamble(registry, tx) {
19
+ if (tx instanceof Extrinsic) {
20
+ return { call: tx.call, preamble: tx.preamble };
21
+ }
22
+ if (isRuntimeTxCall(tx)) {
23
+ return { call: tx };
24
+ }
25
+ // HexString or Uint8Array: try extrinsic decode first, fallback to runtime call
26
+ try {
27
+ const extrinsic = registry.$Extrinsic.tryDecode(tx);
28
+ return { call: extrinsic.call, preamble: extrinsic.preamble };
29
+ }
30
+ catch {
31
+ const { callTypeId } = registry.metadata.extrinsic;
32
+ const $RuntimeCall = registry.findCodec(callTypeId);
33
+ const call = $RuntimeCall.tryDecode(tx);
34
+ return { call };
35
+ }
36
+ }
3
37
  export function isKeyringPair(account) {
4
38
  return isFunction(account.sign);
5
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dedot/api",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "A delightful JavaScript/TypeScript client for Polkadot & Substrate",
5
5
  "author": "Thang X. Vu <thang@dedot.dev>",
6
6
  "homepage": "https://dedot.dev",
@@ -13,13 +13,13 @@
13
13
  "type": "module",
14
14
  "sideEffects": false,
15
15
  "dependencies": {
16
- "@dedot/codecs": "^1.1.1",
17
- "@dedot/providers": "^1.1.1",
18
- "@dedot/runtime-specs": "^1.1.1",
19
- "@dedot/shape": "^1.1.1",
20
- "@dedot/storage": "^1.1.1",
21
- "@dedot/types": "^1.1.1",
22
- "@dedot/utils": "^1.1.1"
16
+ "@dedot/codecs": "^1.3.0",
17
+ "@dedot/providers": "^1.3.0",
18
+ "@dedot/runtime-specs": "^1.3.0",
19
+ "@dedot/shape": "^1.3.0",
20
+ "@dedot/storage": "^1.3.0",
21
+ "@dedot/types": "^1.3.0",
22
+ "@dedot/utils": "^1.3.0"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json",
@@ -48,7 +48,7 @@
48
48
  "node": ">=18"
49
49
  },
50
50
  "license": "Apache-2.0",
51
- "gitHead": "d64c5da3433f21b17fcf1e431235b8694ed11fa2",
51
+ "gitHead": "1179ba75b371517a21eb1162881e6e4c655514a0",
52
52
  "module": "./index.js",
53
53
  "types": "./index.d.ts"
54
54
  }
package/types.d.ts CHANGED
@@ -2,7 +2,7 @@ import { BlockHash, Extrinsic, Hash, Header, Metadata, PortableRegistry } from '
2
2
  import type { ConnectionStatus, JsonRpcProvider, ProviderEvent } from '@dedot/providers';
3
3
  import type { AnyShape } from '@dedot/shape';
4
4
  import type { IStorage } from '@dedot/storage';
5
- import { Callback, ChainSubmittableExtrinsic, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, ISubmittableResult, Query, QueryFnResult, RpcVersion, RuntimeApiName, RuntimeApiSpec, TxUnsub, Unsub } from '@dedot/types';
5
+ import { Callback, ChainSubmittableExtrinsic, GenericStorageQuery, GenericSubstrateApi, InjectedSigner, IRuntimeTxCall, ISubmittableResult, Query, QueryFnResult, RpcVersion, RuntimeApiName, RuntimeApiSpec, TxUnsub, Unsub } from '@dedot/types';
6
6
  import { Properties } from '@dedot/types/json-rpc';
7
7
  import type { HashFn, HexString, IEventEmitter } from '@dedot/utils';
8
8
  import type { SubstrateApi } from './chaintypes/index.js';
@@ -226,28 +226,36 @@ Events extends string = ApiEvent> extends IJsonRpcClient<ChainApi, Events>, IGen
226
226
  */
227
227
  sendTx(tx: HexString | Extrinsic, callback?: Callback<ISubmittableResult<ChainApi['types']['EventRecord']>>): TxUnsub;
228
228
  /**
229
- * Convert a raw hex-encoded transaction or an Extrinsic instance into a submittable extrinsic
229
+ * Convert a transaction input into a submittable extrinsic
230
230
  * with `sign`, `signAndSend`, `send`, and `paymentInfo` methods.
231
231
  *
232
- * @param tx - A hex-encoded transaction string or an Extrinsic instance
232
+ * @param tx - A hex-encoded extrinsic or runtime call, a Uint8Array of encoded bytes,
233
+ * an Extrinsic instance, or an IRuntimeTxCall object.
234
+ * For HexString/Uint8Array, it first tries to decode as a full extrinsic;
235
+ * if that fails, it falls back to decoding as a raw runtime call.
233
236
  * @returns A submittable extrinsic instance
234
237
  *
235
238
  * @example
236
239
  * ```typescript
237
- * // Convert a raw hex transaction to a submittable extrinsic
240
+ * // From a raw hex extrinsic
238
241
  * const submittable = client.toTx(rawTxHex);
239
242
  *
243
+ * // From a runtime call hex (encoded call data)
244
+ * const submittable = client.toTx(runtimeCallHex);
245
+ *
246
+ * // From a Uint8Array
247
+ * const submittable = client.toTx(callBytes);
248
+ *
249
+ * // From a runtime call object
250
+ * const submittable = client.toTx({ pallet: 'Balances', palletCall: { name: 'TransferKeepAlive', params: { dest, value } } });
251
+ *
240
252
  * // Sign and send
241
253
  * const unsub = await submittable.signAndSend(alice, (result) => {
242
254
  * console.log('Status:', result.status);
243
255
  * });
244
- *
245
- * // Or query payment info
246
- * const paymentInfo = await submittable.paymentInfo(alice);
247
- * console.log('Estimated fee:', paymentInfo.partialFee);
248
256
  * ```
249
257
  */
250
- toTx(tx: HexString | Extrinsic): ChainSubmittableExtrinsic<ChainApi>;
258
+ toTx(tx: HexString | Uint8Array | Extrinsic | IRuntimeTxCall): ChainSubmittableExtrinsic<ChainApi>;
251
259
  /**
252
260
  * Query multiple storage items in a single call or subscribe to multiple storage items
253
261
  *