@dedot/api 0.18.6 → 1.0.0-next.a0f8d41e.25
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/chaintypes/substrate/consts.d.ts +2 -2
- package/chaintypes/substrate/consts.js +1 -0
- package/chaintypes/substrate/errors.d.ts +909 -909
- package/chaintypes/substrate/errors.js +1 -0
- package/chaintypes/substrate/events.d.ts +664 -664
- package/chaintypes/substrate/events.js +1 -0
- package/chaintypes/substrate/index.d.ts +13 -16
- package/chaintypes/substrate/index.js +1 -0
- package/chaintypes/substrate/json-rpc.d.ts +2 -2
- package/chaintypes/substrate/json-rpc.js +1 -0
- package/chaintypes/substrate/query.d.ts +509 -527
- package/chaintypes/substrate/query.js +1 -0
- package/chaintypes/substrate/runtime.d.ts +123 -123
- package/chaintypes/substrate/runtime.js +1 -0
- package/chaintypes/substrate/tx.d.ts +1311 -1311
- package/chaintypes/substrate/tx.js +1 -0
- package/chaintypes/substrate/types.js +1 -0
- package/chaintypes/substrate/view-functions.d.ts +2 -2
- package/chaintypes/substrate/view-functions.js +1 -0
- package/cjs/chaintypes/substrate/consts.js +1 -0
- package/cjs/chaintypes/substrate/errors.js +1 -0
- package/cjs/chaintypes/substrate/events.js +1 -0
- package/cjs/chaintypes/substrate/index.js +1 -0
- package/cjs/chaintypes/substrate/json-rpc.js +1 -0
- package/cjs/chaintypes/substrate/query.js +1 -0
- package/cjs/chaintypes/substrate/runtime.js +1 -0
- package/cjs/chaintypes/substrate/tx.js +1 -0
- package/cjs/chaintypes/substrate/types.js +1 -0
- package/cjs/chaintypes/substrate/view-functions.js +1 -0
- package/cjs/client/BaseSubstrateClient.js +45 -2
- package/cjs/client/DedotClient.js +356 -224
- package/cjs/client/LegacyClient.js +99 -23
- package/cjs/client/V2Client.js +316 -0
- package/cjs/client/explorer/LegacyBlockExplorer.js +263 -0
- package/cjs/client/explorer/V2BlockExplorer.js +74 -0
- package/cjs/client/explorer/index.js +18 -0
- package/cjs/client/utils.js +57 -0
- package/cjs/executor/EventExecutor.js +1 -0
- package/cjs/executor/RuntimeApiExecutor.js +49 -2
- package/cjs/executor/StorageQueryExecutor.js +114 -2
- package/cjs/executor/ViewFunctionExecutor.js +21 -2
- package/cjs/executor/v2/StorageQueryExecutorV2.js +7 -1
- package/cjs/executor/validation-helpers.js +181 -0
- package/cjs/extrinsic/submittable/BaseSubmittableExtrinsic.js +86 -2
- package/cjs/extrinsic/submittable/SubmittableExtrinsic.js +38 -16
- package/cjs/extrinsic/submittable/SubmittableExtrinsicV2.js +65 -35
- package/cjs/json-rpc/group/ChainHead/ChainHead.js +233 -21
- package/cjs/storage/QueryableStorage.js +1 -0
- package/client/BaseSubstrateClient.d.ts +34 -18
- package/client/BaseSubstrateClient.js +47 -4
- package/client/DedotClient.d.ts +346 -49
- package/client/DedotClient.js +356 -224
- package/client/LegacyClient.d.ts +22 -17
- package/client/LegacyClient.js +101 -25
- package/client/V2Client.d.ts +75 -0
- package/client/V2Client.js +312 -0
- package/client/explorer/LegacyBlockExplorer.d.ts +68 -0
- package/client/explorer/LegacyBlockExplorer.js +259 -0
- package/client/explorer/V2BlockExplorer.d.ts +40 -0
- package/client/explorer/V2BlockExplorer.js +70 -0
- package/client/explorer/index.d.ts +2 -0
- package/client/explorer/index.js +2 -0
- package/client/utils.d.ts +18 -0
- package/client/utils.js +52 -0
- package/executor/ConstantExecutor.d.ts +1 -2
- package/executor/ErrorExecutor.d.ts +2 -2
- package/executor/EventExecutor.d.ts +2 -2
- package/executor/EventExecutor.js +1 -0
- package/executor/Executor.d.ts +4 -5
- package/executor/RuntimeApiExecutor.d.ts +2 -2
- package/executor/RuntimeApiExecutor.js +27 -3
- package/executor/StorageQueryExecutor.d.ts +3 -2
- package/executor/StorageQueryExecutor.js +92 -3
- package/executor/TxExecutor.d.ts +2 -2
- package/executor/ViewFunctionExecutor.d.ts +2 -2
- package/executor/ViewFunctionExecutor.js +22 -3
- package/executor/v2/RuntimeApiExecutorV2.d.ts +2 -3
- package/executor/v2/StorageQueryExecutorV2.d.ts +3 -3
- package/executor/v2/StorageQueryExecutorV2.js +8 -2
- package/executor/v2/TxExecutorV2.d.ts +5 -5
- package/executor/v2/ViewFunctionExecutorV2.d.ts +2 -3
- package/executor/validation-helpers.d.ts +36 -0
- package/executor/validation-helpers.js +174 -0
- package/extrinsic/extensions/SignedExtension.d.ts +1 -1
- package/extrinsic/submittable/BaseSubmittableExtrinsic.d.ts +7 -3
- package/extrinsic/submittable/BaseSubmittableExtrinsic.js +64 -3
- package/extrinsic/submittable/SubmittableExtrinsic.d.ts +4 -1
- package/extrinsic/submittable/SubmittableExtrinsic.js +39 -17
- package/extrinsic/submittable/SubmittableExtrinsicV2.d.ts +6 -3
- package/extrinsic/submittable/SubmittableExtrinsicV2.js +66 -36
- package/json-rpc/JsonRpcClient.d.ts +6 -6
- package/json-rpc/group/Archive.d.ts +1 -1
- package/json-rpc/group/ChainHead/ChainHead.d.ts +20 -2
- package/json-rpc/group/ChainHead/ChainHead.js +233 -21
- package/json-rpc/group/ChainSpec.d.ts +3 -3
- package/json-rpc/group/Transaction.d.ts +1 -1
- package/json-rpc/group/TransactionWatch.d.ts +1 -1
- package/package.json +9 -9
- package/proxychain.d.ts +3 -4
- package/storage/NewStorageQuery.d.ts +3 -3
- package/storage/QueryableStorage.js +1 -0
- package/types.d.ts +94 -6
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { ApiCompatibilityError, isNumber } from '@dedot/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Check if a codec is an Option type
|
|
4
|
+
*/
|
|
5
|
+
export function isOptionCodec(codec) {
|
|
6
|
+
try {
|
|
7
|
+
const metadata = codec?.metadata?.[0];
|
|
8
|
+
return metadata?.name === '$.option';
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if a parameter is optional (wrapped in Option)
|
|
16
|
+
*/
|
|
17
|
+
export function isOptionalParam(param, registry) {
|
|
18
|
+
// Check direct codec first
|
|
19
|
+
if (param.codec) {
|
|
20
|
+
return isOptionCodec(param.codec);
|
|
21
|
+
}
|
|
22
|
+
// Check via typeId using registry
|
|
23
|
+
if (isNumber(param.typeId)) {
|
|
24
|
+
try {
|
|
25
|
+
const type = registry.findType(param.typeId);
|
|
26
|
+
return type.typeDef.type === 'Enum' && type.path.join('::') === 'Option';
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// ignore
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pad args array with undefined for missing trailing optional parameters.
|
|
36
|
+
* This allows users to omit optional parameters at the end of the parameter list.
|
|
37
|
+
*/
|
|
38
|
+
export function padArgsForOptionalParams(args, params, registry) {
|
|
39
|
+
// If args already match params length, no padding needed
|
|
40
|
+
if (args.length === params.length) {
|
|
41
|
+
return args;
|
|
42
|
+
}
|
|
43
|
+
// If more args than params, validation will fail later
|
|
44
|
+
if (args.length > params.length) {
|
|
45
|
+
return args;
|
|
46
|
+
}
|
|
47
|
+
// Check that all missing parameters are optional
|
|
48
|
+
for (let i = args.length; i < params.length; i++) {
|
|
49
|
+
const param = params[i];
|
|
50
|
+
if (!isOptionalParam(param, registry)) {
|
|
51
|
+
// Required parameter is missing, let the validation fail naturally
|
|
52
|
+
return args;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// All missing parameters are optional, pad with undefined
|
|
56
|
+
return [...args, ...Array(params.length - args.length).fill(undefined)];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Format a value for display in error messages
|
|
60
|
+
* Stringifies and truncates to 20 characters max
|
|
61
|
+
*/
|
|
62
|
+
function formatValue(value) {
|
|
63
|
+
try {
|
|
64
|
+
let str;
|
|
65
|
+
if (typeof value === 'string') {
|
|
66
|
+
str = JSON.stringify(value);
|
|
67
|
+
}
|
|
68
|
+
else if (typeof value === 'undefined') {
|
|
69
|
+
str = 'undefined';
|
|
70
|
+
}
|
|
71
|
+
else if (value === null) {
|
|
72
|
+
str = 'null';
|
|
73
|
+
}
|
|
74
|
+
else if (typeof value === 'bigint') {
|
|
75
|
+
str = `${value}n`;
|
|
76
|
+
}
|
|
77
|
+
else if (typeof value === 'object') {
|
|
78
|
+
// For objects/arrays, use JSON.stringify
|
|
79
|
+
str = JSON.stringify(value);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
str = String(value);
|
|
83
|
+
}
|
|
84
|
+
// Truncate to 20 characters
|
|
85
|
+
if (str.length > 20) {
|
|
86
|
+
return str.slice(0, 20) + '...';
|
|
87
|
+
}
|
|
88
|
+
return str;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return '[complex value]';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build a comprehensive API compatibility error with detailed information
|
|
96
|
+
*/
|
|
97
|
+
export function buildCompatibilityError(originalError, params, args, context) {
|
|
98
|
+
const lines = [];
|
|
99
|
+
// Header
|
|
100
|
+
if (context.itemIndex !== undefined) {
|
|
101
|
+
lines.push(`API Compatibility Error: ${context.apiName} (item ${context.itemIndex})`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
lines.push(`API Compatibility Error: ${context.apiName}`);
|
|
105
|
+
}
|
|
106
|
+
lines.push('');
|
|
107
|
+
// Check parameter count mismatch
|
|
108
|
+
if (args.length !== params.length) {
|
|
109
|
+
lines.push(`Expected ${params.length} parameter${params.length !== 1 ? 's' : ''} but received ${args.length}`);
|
|
110
|
+
lines.push('');
|
|
111
|
+
}
|
|
112
|
+
// Validate each parameter and report errors
|
|
113
|
+
const paramErrors = [];
|
|
114
|
+
const maxParams = Math.max(args.length, params.length);
|
|
115
|
+
for (let i = 0; i < maxParams; i++) {
|
|
116
|
+
const param = params[i];
|
|
117
|
+
const value = args[i];
|
|
118
|
+
if (!param) {
|
|
119
|
+
// Extra argument provided
|
|
120
|
+
paramErrors.push(` [${i}] (unexpected) - value: ${formatValue(value)}`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (value === undefined && i >= args.length) {
|
|
124
|
+
// Missing argument - check if it's actually optional
|
|
125
|
+
const isOptional = isOptionalParam(param, context.registry);
|
|
126
|
+
if (isOptional) {
|
|
127
|
+
paramErrors.push(` [${i}] ${param.name}: omitted (optional)`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
paramErrors.push(` [${i}] ${param.name}: ✗ required parameter missing - value: undefined`);
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// Try to validate the parameter
|
|
135
|
+
try {
|
|
136
|
+
const $codec = findCodec(param, context.registry);
|
|
137
|
+
if ($codec) {
|
|
138
|
+
$codec.assert?.(value);
|
|
139
|
+
// Valid parameter
|
|
140
|
+
paramErrors.push(` [${i}] ${param.name}: ✓ valid`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
// Invalid parameter
|
|
145
|
+
paramErrors.push(` [${i}] ${param.name}: ✗ invalid input type - value: ${formatValue(value)}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (paramErrors.length > 0) {
|
|
149
|
+
lines.push('Parameters:');
|
|
150
|
+
lines.push(...paramErrors);
|
|
151
|
+
lines.push('');
|
|
152
|
+
}
|
|
153
|
+
// Add suggestion
|
|
154
|
+
lines.push('This may indicate your chaintypes definitions are outdated.');
|
|
155
|
+
lines.push('Consider regenerating chaintypes with:');
|
|
156
|
+
lines.push(' npx dedot chaintypes -w <your-chain-endpoint>');
|
|
157
|
+
return new ApiCompatibilityError(lines.join('\n'));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Find codec for a parameter (internal helper)
|
|
161
|
+
*/
|
|
162
|
+
function findCodec(param, registry) {
|
|
163
|
+
if (param.codec)
|
|
164
|
+
return param.codec;
|
|
165
|
+
if (isNumber(param.typeId)) {
|
|
166
|
+
try {
|
|
167
|
+
return registry.findCodec(param.typeId);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// ignore
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
@@ -35,7 +35,7 @@ export declare abstract class SignedExtension<Data extends any = {}, AdditionalS
|
|
|
35
35
|
typeId: number;
|
|
36
36
|
additionalSigned: number;
|
|
37
37
|
};
|
|
38
|
-
get payloadOptions(): Partial<PayloadOptions
|
|
38
|
+
get payloadOptions(): Partial<PayloadOptions<any>>;
|
|
39
39
|
toPayload(...args: any[]): Partial<SignerPayloadJSON>;
|
|
40
40
|
}
|
|
41
41
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BlockHash, Extrinsic } from '@dedot/codecs';
|
|
1
|
+
import { BlockHash, Extrinsic, Preamble } from '@dedot/codecs';
|
|
2
2
|
import { AddressOrPair, Callback, IRuntimeTxCall, ISubmittableExtrinsic, ISubmittableResult, PayloadOptions, SignerOptions, TxHash, TxPaymentInfo, TxUnsub } from '@dedot/types';
|
|
3
3
|
import { HexString } from '@dedot/utils';
|
|
4
4
|
import type { FrameSystemEventRecord } from '../../chaintypes/index.js';
|
|
@@ -9,8 +9,8 @@ interface TxHooks {
|
|
|
9
9
|
}
|
|
10
10
|
export declare abstract class BaseSubmittableExtrinsic extends Extrinsic implements ISubmittableExtrinsic {
|
|
11
11
|
#private;
|
|
12
|
-
readonly client: ISubstrateClient<any>;
|
|
13
|
-
constructor(client: ISubstrateClient<any>, call: IRuntimeTxCall);
|
|
12
|
+
readonly client: ISubstrateClient<any, any>;
|
|
13
|
+
constructor(client: ISubstrateClient<any, any>, call: IRuntimeTxCall, preamble?: Preamble);
|
|
14
14
|
withHooks(hooks: TxHooks): void;
|
|
15
15
|
protected transformTxResult<R extends ISubmittableResult = ISubmittableResult>(result: ISubmittableResult): R;
|
|
16
16
|
paymentInfo(account: AddressOrPair, options?: Partial<PayloadOptions>): Promise<TxPaymentInfo>;
|
|
@@ -21,6 +21,10 @@ export declare abstract class BaseSubmittableExtrinsic extends Extrinsic impleme
|
|
|
21
21
|
send(): TxHash;
|
|
22
22
|
send(callback: Callback): TxUnsub;
|
|
23
23
|
protected getSystemEventsAt(hash: BlockHash): Promise<FrameSystemEventRecord[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Override callU8a to add transaction parameter validation before encoding
|
|
26
|
+
*/
|
|
27
|
+
get callU8a(): Uint8Array;
|
|
24
28
|
toU8a(): Uint8Array;
|
|
25
29
|
toHex(): HexString;
|
|
26
30
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Extrinsic } from '@dedot/codecs';
|
|
2
|
-
import
|
|
2
|
+
import * as $ from '@dedot/shape';
|
|
3
|
+
import { DedotError, hexToU8a, isFunction, stringCamelCase, toHex, u8aToHex } from '@dedot/utils';
|
|
4
|
+
import { buildCompatibilityError } from '../../executor/validation-helpers.js';
|
|
3
5
|
import { ExtraSignedExtension } from '../extensions/index.js';
|
|
4
6
|
import { fakeSigner } from './fakeSigner.js';
|
|
5
7
|
import { isKeyringPair, signRawMessage, txDefer } from './utils.js';
|
|
@@ -7,8 +9,8 @@ export class BaseSubmittableExtrinsic extends Extrinsic {
|
|
|
7
9
|
client;
|
|
8
10
|
#alterTx;
|
|
9
11
|
#hooks;
|
|
10
|
-
constructor(client, call) {
|
|
11
|
-
super(client.registry, call);
|
|
12
|
+
constructor(client, call, preamble) {
|
|
13
|
+
super(client.registry, call, preamble);
|
|
12
14
|
this.client = client;
|
|
13
15
|
}
|
|
14
16
|
withHooks(hooks) {
|
|
@@ -115,10 +117,69 @@ export class BaseSubmittableExtrinsic extends Extrinsic {
|
|
|
115
117
|
const atApi = (await this.client.at(hash));
|
|
116
118
|
return await atApi.query.system.events();
|
|
117
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Override callU8a to add transaction parameter validation before encoding
|
|
122
|
+
*/
|
|
123
|
+
get callU8a() {
|
|
124
|
+
// Validate transaction parameters first
|
|
125
|
+
this.#validateTxParams();
|
|
126
|
+
// Then call parent implementation which does $RuntimeCall.assert and encoding
|
|
127
|
+
return super.callU8a;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Validate transaction parameters against their type definitions
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
#validateTxParams() {
|
|
134
|
+
// Skip if palletCall is a string (flat enum, no params)
|
|
135
|
+
if (typeof this.call.palletCall === 'string')
|
|
136
|
+
return;
|
|
137
|
+
if (!this.call.palletCall?.name)
|
|
138
|
+
return;
|
|
139
|
+
// 1. Find pallet metadata
|
|
140
|
+
const palletName = this.call.pallet;
|
|
141
|
+
const pallet = this.registry.metadata.pallets.find((p) => stringCamelCase(p.name) === stringCamelCase(palletName));
|
|
142
|
+
if (!pallet?.calls)
|
|
143
|
+
return;
|
|
144
|
+
// 2. Get calls enum type definition
|
|
145
|
+
const callsType = this.registry.metadata.types[pallet.calls.typeId];
|
|
146
|
+
if (callsType.typeDef.type !== 'Enum')
|
|
147
|
+
return;
|
|
148
|
+
// 3. Find specific call definition
|
|
149
|
+
const callName = this.call.palletCall.name;
|
|
150
|
+
const callDef = callsType.typeDef.value.members.find((m) => stringCamelCase(m.name) === stringCamelCase(callName));
|
|
151
|
+
if (!callDef)
|
|
152
|
+
return;
|
|
153
|
+
// 4. Build param specs from field definitions
|
|
154
|
+
const paramSpecs = callDef.fields.map((f) => ({
|
|
155
|
+
name: stringCamelCase(f.name || ''),
|
|
156
|
+
typeId: f.typeId,
|
|
157
|
+
}));
|
|
158
|
+
// 5. Extract args in field order from params object
|
|
159
|
+
const params = this.call.palletCall.params || {};
|
|
160
|
+
const args = paramSpecs.map((spec) => params[spec.name]);
|
|
161
|
+
// 6. Validate with tuple codec
|
|
162
|
+
const $ParamsTuple = $.Tuple(...paramSpecs.map((spec) => this.registry.findCodec(spec.typeId)));
|
|
163
|
+
try {
|
|
164
|
+
$ParamsTuple.assert?.(args);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (error instanceof $.ShapeAssertError) {
|
|
168
|
+
throw buildCompatibilityError(error, // --
|
|
169
|
+
paramSpecs, args, {
|
|
170
|
+
apiName: `${stringCamelCase(pallet.name)}.${stringCamelCase(callDef.name)}`,
|
|
171
|
+
registry: this.registry,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
118
177
|
toU8a() {
|
|
119
178
|
if (this.#alterTx) {
|
|
120
179
|
return hexToU8a(this.#alterTx);
|
|
121
180
|
}
|
|
181
|
+
// Validate transaction parameters before encoding
|
|
182
|
+
this.#validateTxParams();
|
|
122
183
|
return super.toU8a();
|
|
123
184
|
}
|
|
124
185
|
toHex() {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { BlockHash } from '@dedot/codecs';
|
|
1
|
+
import { BlockHash, Extrinsic } from '@dedot/codecs';
|
|
2
2
|
import { AddressOrPair, Callback, DryRunResult, ISubmittableExtrinsicLegacy, ISubmittableResult, SignerOptions, TxHash, TxUnsub } from '@dedot/types';
|
|
3
|
+
import { HexString } from '@dedot/utils';
|
|
4
|
+
import { LegacyClient } from '../../client/LegacyClient.js';
|
|
3
5
|
import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
|
|
4
6
|
/**
|
|
5
7
|
* @name SubmittableExtrinsic
|
|
6
8
|
* @description A wrapper around an Extrinsic that exposes methods to sign, send, and other utility around Extrinsic.
|
|
7
9
|
*/
|
|
8
10
|
export declare class SubmittableExtrinsic extends BaseSubmittableExtrinsic implements ISubmittableExtrinsicLegacy {
|
|
11
|
+
static fromTx(client: LegacyClient<any>, tx: HexString | Extrinsic): SubmittableExtrinsic;
|
|
9
12
|
dryRun(account: AddressOrPair, optionsOrHash?: Partial<SignerOptions> | BlockHash): Promise<DryRunResult>;
|
|
10
13
|
send(): TxHash;
|
|
11
14
|
send(callback: Callback<ISubmittableResult>): TxUnsub;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assert, isHex } from '@dedot/utils';
|
|
1
|
+
import { assert, isHex, noop } from '@dedot/utils';
|
|
2
2
|
import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
|
|
3
3
|
import { SubmittableResult } from './SubmittableResult.js';
|
|
4
4
|
import { toTxStatus, txDefer } from './utils.js';
|
|
@@ -7,6 +7,16 @@ import { toTxStatus, txDefer } from './utils.js';
|
|
|
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
|
+
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);
|
|
19
|
+
}
|
|
10
20
|
async dryRun(account, optionsOrHash) {
|
|
11
21
|
const dryRunFn = this.client.rpc.system_dryRun;
|
|
12
22
|
if (isHex(optionsOrHash)) {
|
|
@@ -20,7 +30,11 @@ export class SubmittableExtrinsic extends BaseSubmittableExtrinsic {
|
|
|
20
30
|
const txHex = this.toHex();
|
|
21
31
|
const txHash = this.hash;
|
|
22
32
|
const { deferTx, onTxProgress } = txDefer();
|
|
23
|
-
|
|
33
|
+
let unsub;
|
|
34
|
+
this.client.rpc
|
|
35
|
+
.author_submitAndWatchExtrinsic(txHex, async (txStatus) => {
|
|
36
|
+
let status;
|
|
37
|
+
let result;
|
|
24
38
|
if (txStatus.type === 'InBlock' || txStatus.type === 'Finalized') {
|
|
25
39
|
const blockHash = txStatus.value;
|
|
26
40
|
const [signedBlock, blockEvents] = await Promise.all([
|
|
@@ -31,25 +45,33 @@ export class SubmittableExtrinsic extends BaseSubmittableExtrinsic {
|
|
|
31
45
|
assert(txIndex >= 0, 'Extrinsic not found!');
|
|
32
46
|
const events = blockEvents.filter(({ phase }) => phase.type === 'ApplyExtrinsic' && phase.value === txIndex);
|
|
33
47
|
const blockNumber = signedBlock.block.header.number;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
onTxProgress(result);
|
|
37
|
-
!isSubscription && deferTx.resolve(txHash);
|
|
38
|
-
return callback?.(result);
|
|
48
|
+
status = toTxStatus(txStatus, { txIndex, blockNumber });
|
|
49
|
+
result = this.transformTxResult(new SubmittableResult({ status, txHash, events, txIndex }));
|
|
39
50
|
}
|
|
40
51
|
else {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
onTxProgress(result);
|
|
44
|
-
!isSubscription && deferTx.resolve(txHash);
|
|
45
|
-
return callback?.(result);
|
|
52
|
+
status = toTxStatus(txStatus);
|
|
53
|
+
result = this.transformTxResult(new SubmittableResult({ status, txHash }));
|
|
46
54
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
onTxProgress(result);
|
|
56
|
+
if (isSubscription) {
|
|
57
|
+
callback?.(result);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
deferTx.resolve(txHash);
|
|
61
|
+
}
|
|
62
|
+
if (status.type === 'Finalized' || // --
|
|
63
|
+
status.type === 'Invalid' ||
|
|
64
|
+
status.type === 'Drop') {
|
|
65
|
+
unsub?.().catch(noop);
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
.then((x) => {
|
|
69
|
+
unsub = x;
|
|
70
|
+
if (isSubscription) {
|
|
50
71
|
deferTx.resolve(x);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.catch(deferTx.reject);
|
|
53
75
|
return deferTx.promise;
|
|
54
76
|
}
|
|
55
77
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { Extrinsic, Preamble } from '@dedot/codecs';
|
|
1
2
|
import { Callback, IRuntimeTxCall, TxHash, TxUnsub } from '@dedot/types';
|
|
2
|
-
import {
|
|
3
|
+
import { HexString } from '@dedot/utils';
|
|
4
|
+
import { V2Client } from '../../client/V2Client.js';
|
|
3
5
|
import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
|
|
4
6
|
/**
|
|
5
7
|
* @name SubmittableExtrinsicV2
|
|
@@ -7,8 +9,9 @@ import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
|
|
|
7
9
|
*/
|
|
8
10
|
export declare class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
9
11
|
#private;
|
|
10
|
-
client:
|
|
11
|
-
constructor(client:
|
|
12
|
+
client: V2Client<any>;
|
|
13
|
+
constructor(client: V2Client<any>, call: IRuntimeTxCall, preamble?: Preamble);
|
|
14
|
+
static fromTx(client: V2Client<any>, tx: HexString | Extrinsic): SubmittableExtrinsicV2;
|
|
12
15
|
send(): TxHash;
|
|
13
16
|
send(callback: Callback): TxUnsub;
|
|
14
17
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AsyncQueue, noop, waitFor } from '@dedot/utils';
|
|
1
|
+
import { AsyncQueue, isHex, 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';
|
|
@@ -9,10 +9,20 @@ import { txDefer } from './utils.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
11
11
|
client;
|
|
12
|
-
constructor(client, call) {
|
|
13
|
-
super(client, call);
|
|
12
|
+
constructor(client, call, preamble) {
|
|
13
|
+
super(client, call, preamble);
|
|
14
14
|
this.client = client;
|
|
15
15
|
}
|
|
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);
|
|
25
|
+
}
|
|
16
26
|
async #send(callback) {
|
|
17
27
|
const api = this.client;
|
|
18
28
|
const txHex = this.toHex();
|
|
@@ -60,23 +70,30 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
60
70
|
};
|
|
61
71
|
let txFound;
|
|
62
72
|
let isSearching = false;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
const searchQueue = new AsyncQueue();
|
|
74
|
+
const finalizedQueue = new AsyncQueue();
|
|
75
|
+
const trackedHashes = [];
|
|
66
76
|
const cancelPendingSearch = () => {
|
|
67
77
|
searchQueue.clear();
|
|
68
78
|
};
|
|
69
79
|
const cancelBodySearch = () => {
|
|
70
80
|
searchQueue.cancel();
|
|
71
81
|
};
|
|
72
|
-
const startSearching = (block) => {
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
const startSearching = async (block) => {
|
|
83
|
+
const hash = block.hash;
|
|
84
|
+
if (!trackedHashes.includes(hash)) {
|
|
85
|
+
api.chainHead.holdBlock(hash);
|
|
86
|
+
trackedHashes.push(hash);
|
|
87
|
+
}
|
|
88
|
+
return searchQueue
|
|
89
|
+
.enqueue(async () => {
|
|
90
|
+
const found = await checkTxIsOnChain(hash);
|
|
75
91
|
if (found) {
|
|
76
92
|
cancelPendingSearch();
|
|
77
93
|
}
|
|
78
94
|
return found;
|
|
79
|
-
})
|
|
95
|
+
})
|
|
96
|
+
.catch(() => undefined);
|
|
80
97
|
};
|
|
81
98
|
const checkBestBlockIncluded = async (block, bestChainChanged) => {
|
|
82
99
|
if (bestChainChanged) {
|
|
@@ -139,43 +156,56 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
139
156
|
}
|
|
140
157
|
};
|
|
141
158
|
const checkFinalizedBlockIncluded = async (block) => {
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
status: { type: 'Finalized', value: { blockHash, blockNumber, txIndex } },
|
|
147
|
-
txHash,
|
|
148
|
-
events,
|
|
149
|
-
txIndex,
|
|
150
|
-
}));
|
|
159
|
+
const hash = block.hash;
|
|
160
|
+
if (!trackedHashes.includes(hash)) {
|
|
161
|
+
api.chainHead.holdBlock(hash);
|
|
162
|
+
trackedHashes.push(hash);
|
|
151
163
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
finalizedQueue
|
|
165
|
+
.enqueue(async () => {
|
|
166
|
+
const inBlock = await checkTxIsOnChain(hash);
|
|
167
|
+
if (inBlock) {
|
|
168
|
+
const { index: txIndex, events, blockHash, blockNumber } = inBlock;
|
|
169
|
+
callback(new SubmittableResult({
|
|
170
|
+
status: { type: 'Finalized', value: { blockHash, blockNumber, txIndex } },
|
|
171
|
+
txHash,
|
|
172
|
+
events,
|
|
173
|
+
txIndex,
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// Revalidate the tx, just in-case it becomes invalid along the way
|
|
178
|
+
// Context: https://github.com/paritytech/json-rpc-interface-spec/pull/107#issuecomment-1906008814
|
|
179
|
+
const validation = await validateTx(hash);
|
|
180
|
+
if (validation.isOk)
|
|
181
|
+
return;
|
|
182
|
+
callback(new SubmittableResult({
|
|
183
|
+
status: {
|
|
184
|
+
type: 'Invalid',
|
|
185
|
+
value: { error: `Invalid Tx: ${validation.err.type} - ${validation.err.value.type}` },
|
|
186
|
+
},
|
|
187
|
+
txHash,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
txUnsub().catch(noop);
|
|
191
|
+
})
|
|
192
|
+
.catch(noop);
|
|
167
193
|
};
|
|
194
|
+
const stopBestBlockTrackingFn = api.on('bestBlock', checkBestBlockIncluded);
|
|
195
|
+
const stopFinalizedBlockTrackingFn = api.on('finalizedBlock', checkFinalizedBlockIncluded);
|
|
168
196
|
stopBroadcastFn = await api.txBroadcaster.broadcastTx(txHex);
|
|
169
197
|
callback(new SubmittableResult({
|
|
170
198
|
status: { type: 'Broadcasting' },
|
|
171
199
|
txHash,
|
|
172
200
|
}));
|
|
173
|
-
const stopBestBlockTrackingFn = api.on('bestBlock', checkBestBlockIncluded);
|
|
174
|
-
const stopFinalizedBlockTrackingFn = api.on('finalizedBlock', checkFinalizedBlockIncluded);
|
|
175
201
|
const stopTracking = () => {
|
|
176
202
|
stopBestBlockTrackingFn();
|
|
177
203
|
stopFinalizedBlockTrackingFn();
|
|
178
204
|
cancelBodySearch();
|
|
205
|
+
finalizedQueue.cancel();
|
|
206
|
+
trackedHashes.forEach((h) => {
|
|
207
|
+
api.chainHead.releaseBlock(h);
|
|
208
|
+
});
|
|
179
209
|
};
|
|
180
210
|
txUnsub = async () => {
|
|
181
211
|
stopTracking();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ConnectionStatus, JsonRpcProvider, ProviderEvent } from '@dedot/providers';
|
|
2
|
-
import type {
|
|
2
|
+
import type { GenericSubstrateApi } from '@dedot/types';
|
|
3
3
|
import { EventEmitter } from '@dedot/utils';
|
|
4
4
|
import type { SubstrateApi } from '../chaintypes/index.js';
|
|
5
5
|
import type { IJsonRpcClient, JsonRpcClientOptions } from '../types.js';
|
|
6
6
|
export declare const isJsonRpcProvider: (provider: any) => provider is JsonRpcProvider;
|
|
7
|
-
export declare class JsonRpcClient<ChainApi extends
|
|
8
|
-
Events extends string = ProviderEvent> extends EventEmitter<Events> implements IJsonRpcClient<ChainApi
|
|
7
|
+
export declare class JsonRpcClient<ChainApi extends GenericSubstrateApi = SubstrateApi, // prettier-end-here
|
|
8
|
+
Events extends string = ProviderEvent> extends EventEmitter<Events> implements IJsonRpcClient<ChainApi, Events> {
|
|
9
9
|
#private;
|
|
10
10
|
constructor(options: JsonRpcClientOptions | JsonRpcProvider);
|
|
11
11
|
/**
|
|
@@ -13,13 +13,13 @@ Events extends string = ProviderEvent> extends EventEmitter<Events> implements I
|
|
|
13
13
|
*
|
|
14
14
|
* @param options
|
|
15
15
|
*/
|
|
16
|
-
static create<ChainApi extends
|
|
16
|
+
static create<ChainApi extends GenericSubstrateApi = SubstrateApi>(options: JsonRpcClientOptions | JsonRpcProvider): Promise<JsonRpcClient<ChainApi>>;
|
|
17
17
|
/**
|
|
18
18
|
* Alias for __JsonRpcClient.create__
|
|
19
19
|
*
|
|
20
20
|
* @param options
|
|
21
21
|
*/
|
|
22
|
-
static new<ChainApi extends
|
|
22
|
+
static new<ChainApi extends GenericSubstrateApi = SubstrateApi>(options: JsonRpcClientOptions | JsonRpcProvider): Promise<JsonRpcClient<ChainApi>>;
|
|
23
23
|
get options(): JsonRpcClientOptions;
|
|
24
24
|
/**
|
|
25
25
|
* @description Check connection status of the api instance
|
|
@@ -47,5 +47,5 @@ Events extends string = ProviderEvent> extends EventEmitter<Events> implements I
|
|
|
47
47
|
* const result = await client.rpc.module_rpc_name();
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
|
-
get rpc(): ChainApi[
|
|
50
|
+
get rpc(): ChainApi['rpc'];
|
|
51
51
|
}
|
|
@@ -13,7 +13,7 @@ import { JsonRpcGroup, JsonRpcGroupOptions } from './JsonRpcGroup.js';
|
|
|
13
13
|
*/
|
|
14
14
|
export declare class Archive extends JsonRpcGroup {
|
|
15
15
|
#private;
|
|
16
|
-
constructor(client: IJsonRpcClient
|
|
16
|
+
constructor(client: IJsonRpcClient<any>, options?: Partial<JsonRpcGroupOptions>);
|
|
17
17
|
/**
|
|
18
18
|
* Retrieves the body (list of transactions) of a given block.
|
|
19
19
|
* Returns an array of strings containing the hexadecimal-encoded SCALE-codec-encoded
|