@dedot/api 0.0.1-next.cfee875e.21 → 0.1.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.
- package/chaintypes/substrate/tx.d.ts +2 -2
- package/chaintypes/substrate/types.d.ts +377 -377
- package/cjs/client/BaseSubstrateClient.js +21 -0
- package/cjs/client/DedotClient.js +5 -4
- package/cjs/client/LegacyClient.js +6 -2
- package/cjs/executor/ErrorExecutor.js +3 -3
- package/cjs/executor/EventExecutor.js +2 -2
- package/cjs/executor/StorageQueryExecutor.js +1 -1
- package/cjs/executor/TxExecutor.js +3 -3
- package/cjs/executor/v2/StorageQueryExecutorV2.js +4 -4
- package/cjs/extrinsic/extensions/known/ChargeAssetTxPayment.js +2 -2
- package/cjs/extrinsic/extensions/known/StorageWeightReclaim.js +10 -0
- package/cjs/extrinsic/extensions/known/index.js +2 -0
- package/cjs/extrinsic/submittable/BaseSubmittableExtrinsic.js +31 -1
- package/cjs/extrinsic/submittable/SubmittableExtrinsic.js +7 -4
- package/cjs/extrinsic/submittable/SubmittableExtrinsicV2.js +11 -11
- package/cjs/extrinsic/submittable/utils.js +66 -1
- package/cjs/storage/QueryableStorage.js +17 -17
- package/client/BaseSubstrateClient.d.ts +15 -9
- package/client/BaseSubstrateClient.js +22 -1
- package/client/DedotClient.d.ts +2 -2
- package/client/DedotClient.js +5 -4
- package/client/LegacyClient.d.ts +3 -2
- package/client/LegacyClient.js +6 -2
- package/executor/ErrorExecutor.js +3 -3
- package/executor/EventExecutor.js +2 -2
- package/executor/Executor.d.ts +12 -12
- package/executor/StorageQueryExecutor.js +1 -1
- package/executor/TxExecutor.js +3 -3
- package/executor/v2/StorageQueryExecutorV2.js +4 -4
- package/extrinsic/extensions/known/ChargeAssetTxPayment.js +2 -2
- package/extrinsic/extensions/known/StorageWeightReclaim.d.ts +6 -0
- package/extrinsic/extensions/known/StorageWeightReclaim.js +6 -0
- package/extrinsic/extensions/known/index.js +2 -0
- package/extrinsic/submittable/BaseSubmittableExtrinsic.d.ts +2 -0
- package/extrinsic/submittable/BaseSubmittableExtrinsic.js +32 -2
- package/extrinsic/submittable/SubmittableExtrinsic.js +7 -4
- package/extrinsic/submittable/SubmittableExtrinsicV2.js +11 -11
- package/extrinsic/submittable/SubmittableResult.d.ts +4 -4
- package/extrinsic/submittable/utils.d.ts +10 -1
- package/extrinsic/submittable/utils.js +65 -1
- package/json-rpc/JsonRpcClient.d.ts +6 -5
- package/package.json +11 -11
- package/storage/QueryableStorage.js +17 -17
- package/types.d.ts +9 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $Metadata, PortableRegistry } from '@dedot/codecs';
|
|
2
2
|
import { LocalStorage } from '@dedot/storage';
|
|
3
|
-
import { calcRuntimeApiHash, ensurePresence as _ensurePresence, u8aToHex } from '@dedot/utils';
|
|
3
|
+
import { calcRuntimeApiHash, deferred, ensurePresence as _ensurePresence, u8aToHex } from '@dedot/utils';
|
|
4
4
|
import { ConstantExecutor, ErrorExecutor, EventExecutor } from '../executor/index.js';
|
|
5
5
|
import { isJsonRpcProvider, JsonRpcClient } from '../json-rpc/index.js';
|
|
6
6
|
import { newProxyChain } from '../proxychain.js';
|
|
@@ -22,6 +22,7 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
22
22
|
_genesisHash;
|
|
23
23
|
_runtimeVersion;
|
|
24
24
|
_localCache;
|
|
25
|
+
_runtimeUpgrading;
|
|
25
26
|
constructor(rpcVersion, options) {
|
|
26
27
|
super(options);
|
|
27
28
|
this.rpcVersion = rpcVersion;
|
|
@@ -193,6 +194,22 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
193
194
|
}, {}),
|
|
194
195
|
};
|
|
195
196
|
}
|
|
197
|
+
startRuntimeUpgrade() {
|
|
198
|
+
this._runtimeUpgrading = deferred();
|
|
199
|
+
}
|
|
200
|
+
doneRuntimeUpgrade() {
|
|
201
|
+
if (!this._runtimeUpgrading)
|
|
202
|
+
return;
|
|
203
|
+
this._runtimeUpgrading.resolve();
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
this._runtimeUpgrading = undefined;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async ensureRuntimeUpgraded() {
|
|
209
|
+
if (!this._runtimeUpgrading)
|
|
210
|
+
return;
|
|
211
|
+
await this._runtimeUpgrading.promise;
|
|
212
|
+
}
|
|
196
213
|
/// --- Public APIs ---
|
|
197
214
|
/**
|
|
198
215
|
* @description Connect to blockchain node
|
|
@@ -224,6 +241,10 @@ export class BaseSubstrateClient extends JsonRpcClient {
|
|
|
224
241
|
get runtimeVersion() {
|
|
225
242
|
return ensurePresence(this._runtimeVersion);
|
|
226
243
|
}
|
|
244
|
+
async getRuntimeVersion() {
|
|
245
|
+
await this.ensureRuntimeUpgraded();
|
|
246
|
+
return this.runtimeVersion;
|
|
247
|
+
}
|
|
227
248
|
get consts() {
|
|
228
249
|
return newProxyChain({ executor: new ConstantExecutor(this) });
|
|
229
250
|
}
|
package/client/DedotClient.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ import { BaseSubstrateClient } from './BaseSubstrateClient.js';
|
|
|
11
11
|
*
|
|
12
12
|
* __Unstable, use with caution.__
|
|
13
13
|
*/
|
|
14
|
-
export declare class DedotClient<ChainApi extends VersionedGenericSubstrateApi = SubstrateApi
|
|
14
|
+
export declare class DedotClient<ChainApi extends VersionedGenericSubstrateApi = SubstrateApi>// prettier-end-here
|
|
15
|
+
extends BaseSubstrateClient<RpcV2, ChainApi> {
|
|
15
16
|
#private;
|
|
16
17
|
protected _chainHead?: ChainHead;
|
|
17
18
|
protected _chainSpec?: ChainSpec;
|
|
@@ -42,7 +43,6 @@ export declare class DedotClient<ChainApi extends VersionedGenericSubstrateApi =
|
|
|
42
43
|
*/
|
|
43
44
|
protected doInitialize(): Promise<void>;
|
|
44
45
|
protected subscribeRuntimeUpgrades(): void;
|
|
45
|
-
protected unsubscribeRuntimeUpgrades(): void;
|
|
46
46
|
protected onRuntimeUpgrade: (block: PinnedBlock) => Promise<void>;
|
|
47
47
|
protected beforeDisconnect(): Promise<void>;
|
|
48
48
|
protected onDisconnected: () => Promise<void>;
|
package/client/DedotClient.js
CHANGED
|
@@ -11,7 +11,8 @@ import { BaseSubstrateClient, ensurePresence } from './BaseSubstrateClient.js';
|
|
|
11
11
|
*
|
|
12
12
|
* __Unstable, use with caution.__
|
|
13
13
|
*/
|
|
14
|
-
export class DedotClient
|
|
14
|
+
export class DedotClient// prettier-end-here
|
|
15
|
+
extends BaseSubstrateClient {
|
|
15
16
|
_chainHead;
|
|
16
17
|
_chainSpec;
|
|
17
18
|
_txBroadcaster;
|
|
@@ -99,16 +100,16 @@ export class DedotClient extends BaseSubstrateClient {
|
|
|
99
100
|
subscribeRuntimeUpgrades() {
|
|
100
101
|
this.chainHead.on('bestBlock', this.onRuntimeUpgrade);
|
|
101
102
|
}
|
|
102
|
-
unsubscribeRuntimeUpgrades() {
|
|
103
|
-
this.chainHead.off('bestBlock', this.onRuntimeUpgrade);
|
|
104
|
-
}
|
|
105
103
|
onRuntimeUpgrade = async (block) => {
|
|
106
104
|
const runtimeUpgraded = block.runtime && block.runtime.specVersion !== this._runtimeVersion?.specVersion;
|
|
107
105
|
if (!runtimeUpgraded)
|
|
108
106
|
return;
|
|
107
|
+
this.startRuntimeUpgrade();
|
|
109
108
|
this._runtimeVersion = block.runtime;
|
|
110
109
|
const newMetadata = await this.fetchMetadata(undefined, this._runtimeVersion);
|
|
111
110
|
await this.setupMetadata(newMetadata);
|
|
111
|
+
this.emit('runtimeUpgraded', this._runtimeVersion);
|
|
112
|
+
this.doneRuntimeUpgrade();
|
|
112
113
|
};
|
|
113
114
|
async beforeDisconnect() {
|
|
114
115
|
await this.chainHead.unfollow();
|
package/client/LegacyClient.d.ts
CHANGED
|
@@ -42,7 +42,8 @@ import { BaseSubstrateClient } from './BaseSubstrateClient.js';
|
|
|
42
42
|
* run().catch(console.error);
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
|
-
export declare class LegacyClient<ChainApi extends VersionedGenericSubstrateApi = SubstrateApi
|
|
45
|
+
export declare class LegacyClient<ChainApi extends VersionedGenericSubstrateApi = SubstrateApi>// prettier-end-here
|
|
46
|
+
extends BaseSubstrateClient<RpcLegacy, ChainApi> {
|
|
46
47
|
#private;
|
|
47
48
|
/**
|
|
48
49
|
* Use factory methods (`create`, `new`) to create `Dedot` instances.
|
|
@@ -116,7 +117,7 @@ export declare class LegacyClient<ChainApi extends VersionedGenericSubstrateApi
|
|
|
116
117
|
* // Make a transfer balance transaction
|
|
117
118
|
* api.tx.balances.transferKeepAlive(<address>, <amount>)
|
|
118
119
|
* .signAndSend(<keyPair|address>, { signer }, ({ status }) => {
|
|
119
|
-
* console.log('Transaction status', status.
|
|
120
|
+
* console.log('Transaction status', status.type);
|
|
120
121
|
* });
|
|
121
122
|
* ```
|
|
122
123
|
*/
|
package/client/LegacyClient.js
CHANGED
|
@@ -41,7 +41,8 @@ const KEEP_ALIVE_INTERVAL = 10_000; // in ms
|
|
|
41
41
|
* run().catch(console.error);
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
|
-
export class LegacyClient
|
|
44
|
+
export class LegacyClient// prettier-end-here
|
|
45
|
+
extends BaseSubstrateClient {
|
|
45
46
|
#runtimeSubscriptionUnsub;
|
|
46
47
|
#healthTimer;
|
|
47
48
|
#apiAtCache = {};
|
|
@@ -101,9 +102,12 @@ export class LegacyClient extends BaseSubstrateClient {
|
|
|
101
102
|
this.rpc
|
|
102
103
|
.state_subscribeRuntimeVersion(async (runtimeVersion) => {
|
|
103
104
|
if (runtimeVersion.specVersion !== this.runtimeVersion?.specVersion) {
|
|
105
|
+
this.startRuntimeUpgrade();
|
|
104
106
|
this._runtimeVersion = this.toSubstrateRuntimeVersion(runtimeVersion);
|
|
105
107
|
const newMetadata = await this.fetchMetadata(undefined, this._runtimeVersion);
|
|
106
108
|
await this.setupMetadata(newMetadata);
|
|
109
|
+
this.emit('runtimeUpgraded', this._runtimeVersion);
|
|
110
|
+
this.doneRuntimeUpgrade();
|
|
107
111
|
}
|
|
108
112
|
})
|
|
109
113
|
.then((unsub) => {
|
|
@@ -202,7 +206,7 @@ export class LegacyClient extends BaseSubstrateClient {
|
|
|
202
206
|
* // Make a transfer balance transaction
|
|
203
207
|
* api.tx.balances.transferKeepAlive(<address>, <amount>)
|
|
204
208
|
* .signAndSend(<keyPair|address>, { signer }, ({ status }) => {
|
|
205
|
-
* console.log('Transaction status', status.
|
|
209
|
+
* console.log('Transaction status', status.type);
|
|
206
210
|
* });
|
|
207
211
|
* ```
|
|
208
212
|
*/
|
|
@@ -17,7 +17,7 @@ export class ErrorExecutor extends Executor {
|
|
|
17
17
|
palletIndex: targetPallet.index,
|
|
18
18
|
},
|
|
19
19
|
is: (errorInfo) => {
|
|
20
|
-
if (isObject(errorInfo) && errorInfo.
|
|
20
|
+
if (isObject(errorInfo) && errorInfo.type === 'Module') {
|
|
21
21
|
errorInfo = errorInfo.value;
|
|
22
22
|
}
|
|
23
23
|
if (isObject(errorInfo) && isNumber(errorInfo.index) && isHex(errorInfo.error)) {
|
|
@@ -30,8 +30,8 @@ export class ErrorExecutor extends Executor {
|
|
|
30
30
|
#getErrorDef(errorTypeId, errorName) {
|
|
31
31
|
const def = this.metadata.types[errorTypeId];
|
|
32
32
|
assert(def, new UnknownApiError(`Error def not found for id ${errorTypeId}`));
|
|
33
|
-
const {
|
|
34
|
-
assert(
|
|
33
|
+
const { type, value } = def.typeDef;
|
|
34
|
+
assert(type === 'Enum', new UnknownApiError(`Error type should be an enum, found: ${type}`));
|
|
35
35
|
const errorDef = value.members.find(({ name }) => stringPascalCase(name) === errorName);
|
|
36
36
|
assert(errorDef, new UnknownApiError(`Error def not found for ${errorName}`));
|
|
37
37
|
return {
|
|
@@ -36,8 +36,8 @@ export class EventExecutor extends Executor {
|
|
|
36
36
|
#getEventDef(eventTypeId, errorName) {
|
|
37
37
|
const def = this.metadata.types[eventTypeId];
|
|
38
38
|
assert(def, new UnknownApiError(`Event def not found for id ${eventTypeId}`));
|
|
39
|
-
const {
|
|
40
|
-
assert(
|
|
39
|
+
const { type, value } = def.typeDef;
|
|
40
|
+
assert(type === 'Enum', new UnknownApiError(`Event type should be an enum, found: ${type}`));
|
|
41
41
|
const eventDef = value.members.find(({ name }) => stringPascalCase(name) === errorName);
|
|
42
42
|
assert(eventDef, new UnknownApiError(`Event def not found for ${errorName}`));
|
|
43
43
|
return {
|
package/executor/Executor.d.ts
CHANGED
|
@@ -19,13 +19,13 @@ export declare abstract class Executor<ChainApi extends GenericSubstrateApi = Ge
|
|
|
19
19
|
name: string;
|
|
20
20
|
typeId: number | undefined;
|
|
21
21
|
}[];
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
typeDef: {
|
|
23
|
+
type: "Struct";
|
|
24
24
|
value: {
|
|
25
25
|
fields: import("@dedot/codecs").Field[];
|
|
26
26
|
};
|
|
27
27
|
} | {
|
|
28
|
-
|
|
28
|
+
type: "Enum";
|
|
29
29
|
value: {
|
|
30
30
|
members: {
|
|
31
31
|
name: string;
|
|
@@ -35,33 +35,33 @@ export declare abstract class Executor<ChainApi extends GenericSubstrateApi = Ge
|
|
|
35
35
|
}[];
|
|
36
36
|
};
|
|
37
37
|
} | {
|
|
38
|
-
|
|
38
|
+
type: "Sequence";
|
|
39
39
|
value: {
|
|
40
40
|
typeParam: number;
|
|
41
41
|
};
|
|
42
42
|
} | {
|
|
43
|
-
|
|
43
|
+
type: "SizedVec";
|
|
44
44
|
value: {
|
|
45
45
|
len: number;
|
|
46
46
|
typeParam: number;
|
|
47
47
|
};
|
|
48
48
|
} | {
|
|
49
|
-
|
|
49
|
+
type: "Tuple";
|
|
50
50
|
value: {
|
|
51
51
|
fields: number[];
|
|
52
52
|
};
|
|
53
53
|
} | {
|
|
54
|
-
|
|
54
|
+
type: "Primitive";
|
|
55
55
|
value: {
|
|
56
56
|
kind: "bool" | "char" | "str" | "u8" | "u16" | "u32" | "u64" | "u128" | "u256" | "i8" | "i16" | "i32" | "i64" | "i128" | "i256";
|
|
57
57
|
};
|
|
58
58
|
} | {
|
|
59
|
-
|
|
59
|
+
type: "Compact";
|
|
60
60
|
value: {
|
|
61
61
|
typeParam: number;
|
|
62
62
|
};
|
|
63
63
|
} | {
|
|
64
|
-
|
|
64
|
+
type: "BitSequence";
|
|
65
65
|
value: {
|
|
66
66
|
bitOrderType: number;
|
|
67
67
|
bitStoreType: number;
|
|
@@ -76,13 +76,13 @@ export declare abstract class Executor<ChainApi extends GenericSubstrateApi = Ge
|
|
|
76
76
|
entries: {
|
|
77
77
|
name: string;
|
|
78
78
|
modifier: string;
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
storageType: {
|
|
80
|
+
type: "Plain";
|
|
81
81
|
value: {
|
|
82
82
|
valueTypeId: number;
|
|
83
83
|
};
|
|
84
84
|
} | {
|
|
85
|
-
|
|
85
|
+
type: "Map";
|
|
86
86
|
value: {
|
|
87
87
|
hashers: ("identity" | "blake2_128" | "blake2_256" | "blake2_128Concat" | "twox128" | "twox256" | "twox64Concat")[];
|
|
88
88
|
keyTypeId: number;
|
|
@@ -49,7 +49,7 @@ export class StorageQueryExecutor extends Executor {
|
|
|
49
49
|
palletIndex: entry.pallet.index,
|
|
50
50
|
...entry.storageEntry,
|
|
51
51
|
};
|
|
52
|
-
const isMap = entry.storageEntry.type
|
|
52
|
+
const isMap = entry.storageEntry.storageType.type === 'Map';
|
|
53
53
|
if (isMap) {
|
|
54
54
|
const queryMultiFn = async (...args) => {
|
|
55
55
|
const [inArgs, callback] = extractArgs(args);
|
package/executor/TxExecutor.js
CHANGED
|
@@ -10,9 +10,9 @@ export class TxExecutor extends Executor {
|
|
|
10
10
|
const targetPallet = this.getPallet(pallet);
|
|
11
11
|
assert(targetPallet.calls, new UnknownApiError(`Tx calls are not available for pallet ${targetPallet.name}`));
|
|
12
12
|
const txType = this.metadata.types[targetPallet.calls];
|
|
13
|
-
assert(txType.type
|
|
14
|
-
const isFlatEnum = txType.
|
|
15
|
-
const txCallDef = txType.
|
|
13
|
+
assert(txType.typeDef.type === 'Enum', new UnknownApiError('Tx type defs should be enum'));
|
|
14
|
+
const isFlatEnum = txType.typeDef.value.members.every((m) => m.fields.length === 0);
|
|
15
|
+
const txCallDef = txType.typeDef.value.members.find((m) => stringCamelCase(m.name) === functionName);
|
|
16
16
|
assert(txCallDef, new UnknownApiError(`Tx call spec not found for ${pallet}.${functionName}`));
|
|
17
17
|
const txCallFn = (...args) => {
|
|
18
18
|
let call;
|
|
@@ -37,20 +37,20 @@ export class StorageQueryExecutorV2 extends StorageQueryExecutor {
|
|
|
37
37
|
// TODO subscribe to finalized data source
|
|
38
38
|
// initialHash = this.chainHead.finalizedHash;
|
|
39
39
|
// eventToListen = 'finalizedBlock';
|
|
40
|
-
const latestChanges =
|
|
40
|
+
const latestChanges = new Map();
|
|
41
41
|
const pull = async ({ hash }) => {
|
|
42
42
|
const results = await this.queryStorage(keys, hash);
|
|
43
43
|
let changed = false;
|
|
44
44
|
keys.forEach((key) => {
|
|
45
45
|
const newValue = results[key];
|
|
46
|
-
if (latestChanges
|
|
46
|
+
if (latestChanges.size > 0 && latestChanges.get(key) === newValue)
|
|
47
47
|
return;
|
|
48
48
|
changed = true;
|
|
49
|
-
latestChanges
|
|
49
|
+
latestChanges.set(key, newValue);
|
|
50
50
|
});
|
|
51
51
|
if (!changed)
|
|
52
52
|
return;
|
|
53
|
-
callback(keys.map((key) => latestChanges
|
|
53
|
+
callback(keys.map((key) => latestChanges.get(key)));
|
|
54
54
|
};
|
|
55
55
|
await pull(best);
|
|
56
56
|
const unsub = this.chainHead.on(eventToListen, pull);
|
|
@@ -27,8 +27,8 @@ export class ChargeAssetTxPayment extends SignedExtension {
|
|
|
27
27
|
}
|
|
28
28
|
$AssetId() {
|
|
29
29
|
const extensionTypeDef = this.registry.findType(this.signedExtensionDef.typeId);
|
|
30
|
-
assert(extensionTypeDef.type
|
|
31
|
-
const assetIdTypeDef = extensionTypeDef.
|
|
30
|
+
assert(extensionTypeDef.typeDef.type === 'Struct');
|
|
31
|
+
const assetIdTypeDef = extensionTypeDef.typeDef.value.fields.find((f) => f.name === 'asset_id');
|
|
32
32
|
const $codec = this.registry.findCodec(assetIdTypeDef.typeId);
|
|
33
33
|
const codecMetadata = $codec.metadata[0];
|
|
34
34
|
if (codecMetadata.name === '$.option') {
|
|
@@ -9,6 +9,7 @@ import { CheckSpecVersion } from './CheckSpecVersion.js';
|
|
|
9
9
|
import { CheckTxVersion } from './CheckTxVersion.js';
|
|
10
10
|
import { CheckWeight } from './CheckWeight.js';
|
|
11
11
|
import { PrevalidateAttests } from './PrevalidateAttests.js';
|
|
12
|
+
import { StorageWeightReclaim } from './StorageWeightReclaim.js';
|
|
12
13
|
export const knownSignedExtensions = {
|
|
13
14
|
CheckNonZeroSender,
|
|
14
15
|
CheckSpecVersion,
|
|
@@ -21,4 +22,5 @@ export const knownSignedExtensions = {
|
|
|
21
22
|
PrevalidateAttests,
|
|
22
23
|
ChargeAssetTxPayment,
|
|
23
24
|
CheckMetadataHash,
|
|
25
|
+
StorageWeightReclaim,
|
|
24
26
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BlockHash, Extrinsic, Hash } from '@dedot/codecs';
|
|
2
2
|
import { AddressOrPair, Callback, IRuntimeTxCall, ISubmittableExtrinsic, ISubmittableResult, PayloadOptions, SignerOptions, TxPaymentInfo, Unsub } from '@dedot/types';
|
|
3
|
+
import { HexString } from '@dedot/utils';
|
|
3
4
|
import type { FrameSystemEventRecord } from '../../chaintypes/index.js';
|
|
4
5
|
import type { ISubstrateClient } from '../../types.js';
|
|
5
6
|
export declare abstract class BaseSubmittableExtrinsic extends Extrinsic implements ISubmittableExtrinsic {
|
|
@@ -14,4 +15,5 @@ export declare abstract class BaseSubmittableExtrinsic extends Extrinsic impleme
|
|
|
14
15
|
send(): Promise<Hash>;
|
|
15
16
|
send(callback: Callback): Promise<Unsub>;
|
|
16
17
|
protected getSystemEventsAt(hash: BlockHash): Promise<FrameSystemEventRecord[]>;
|
|
18
|
+
toHex(): HexString;
|
|
17
19
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Extrinsic } from '@dedot/codecs';
|
|
2
|
-
import { isFunction, u8aToHex } from '@dedot/utils';
|
|
2
|
+
import { DedotError, isFunction, toHex, u8aToHex } from '@dedot/utils';
|
|
3
3
|
import { ExtraSignedExtension } from '../extensions/index.js';
|
|
4
4
|
import { fakeSigner } from './fakeSigner.js';
|
|
5
5
|
import { isKeyringPair, signRaw } from './utils.js';
|
|
6
6
|
export class BaseSubmittableExtrinsic extends Extrinsic {
|
|
7
7
|
api;
|
|
8
|
+
#alterTx;
|
|
8
9
|
constructor(api, call) {
|
|
9
10
|
super(api.registry, call);
|
|
10
11
|
this.api = api;
|
|
@@ -23,13 +24,14 @@ export class BaseSubmittableExtrinsic extends Extrinsic {
|
|
|
23
24
|
});
|
|
24
25
|
await extra.init();
|
|
25
26
|
const { signer } = options || {};
|
|
26
|
-
let signature;
|
|
27
|
+
let signature, alteredTx;
|
|
27
28
|
if (isKeyringPair(fromAccount)) {
|
|
28
29
|
signature = u8aToHex(signRaw(fromAccount, extra.toRawPayload(this.callHex).data));
|
|
29
30
|
}
|
|
30
31
|
else if (signer?.signPayload) {
|
|
31
32
|
const result = await signer.signPayload(extra.toPayload(this.callHex));
|
|
32
33
|
signature = result.signature;
|
|
34
|
+
alteredTx = result.signedTransaction;
|
|
33
35
|
}
|
|
34
36
|
else {
|
|
35
37
|
throw new Error('Signer not found. Cannot sign the extrinsic!');
|
|
@@ -41,6 +43,13 @@ export class BaseSubmittableExtrinsic extends Extrinsic {
|
|
|
41
43
|
signature: $Signature.tryDecode(signature),
|
|
42
44
|
extra: extra.data,
|
|
43
45
|
});
|
|
46
|
+
// If the tx payload are altered from signer
|
|
47
|
+
// We'll need to validate the altered tx
|
|
48
|
+
// and broadcast it instead of the original tx
|
|
49
|
+
if (alteredTx) {
|
|
50
|
+
this.#validateSignedTx(alteredTx);
|
|
51
|
+
this.#alterTx = toHex(alteredTx);
|
|
52
|
+
}
|
|
44
53
|
return this;
|
|
45
54
|
}
|
|
46
55
|
async signAndSend(fromAccount, partialOptions, maybeCallback) {
|
|
@@ -63,4 +72,25 @@ export class BaseSubmittableExtrinsic extends Extrinsic {
|
|
|
63
72
|
const atApi = (await this.api.at(hash));
|
|
64
73
|
return await atApi.query.system.events();
|
|
65
74
|
}
|
|
75
|
+
toHex() {
|
|
76
|
+
return this.#alterTx || super.toHex();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validate a raw signed transaction coming from signer
|
|
80
|
+
* We need to make sure the tx is signed and call-data is intact/not-changing
|
|
81
|
+
*
|
|
82
|
+
* @param tx
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
#validateSignedTx(tx) {
|
|
86
|
+
const alteredTx = this.$Codec.tryDecode(tx);
|
|
87
|
+
// The alter tx should be signed
|
|
88
|
+
if (!alteredTx.signed) {
|
|
89
|
+
throw new DedotError('Altered transaction from signer is not signed');
|
|
90
|
+
}
|
|
91
|
+
// Signer's not allow the change the call data
|
|
92
|
+
if (alteredTx.callHex !== this.callHex) {
|
|
93
|
+
throw new DedotError('Call data does not match, signer is not allowed to change tx call data.');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
66
96
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assert, isHex } from '@dedot/utils';
|
|
2
2
|
import { BaseSubmittableExtrinsic } from './BaseSubmittableExtrinsic.js';
|
|
3
3
|
import { SubmittableResult } from './SubmittableResult.js';
|
|
4
|
+
import { toTxStatus } from './utils.js';
|
|
4
5
|
/**
|
|
5
6
|
* @name SubmittableExtrinsic
|
|
6
7
|
* @description A wrapper around an Extrinsic that exposes methods to sign, send, and other utility around Extrinsic.
|
|
@@ -19,19 +20,21 @@ export class SubmittableExtrinsic extends BaseSubmittableExtrinsic {
|
|
|
19
20
|
const txHex = this.toHex();
|
|
20
21
|
const txHash = this.hash;
|
|
21
22
|
if (isSubscription) {
|
|
22
|
-
return this.api.rpc.author_submitAndWatchExtrinsic(txHex, async (
|
|
23
|
-
if (
|
|
24
|
-
const blockHash =
|
|
23
|
+
return this.api.rpc.author_submitAndWatchExtrinsic(txHex, async (txStatus) => {
|
|
24
|
+
if (txStatus.type === 'InBlock' || txStatus.type === 'Finalized') {
|
|
25
|
+
const blockHash = txStatus.value;
|
|
25
26
|
const [signedBlock, blockEvents] = await Promise.all([
|
|
26
27
|
this.api.rpc.chain_getBlock(blockHash),
|
|
27
28
|
this.getSystemEventsAt(blockHash),
|
|
28
29
|
]);
|
|
29
30
|
const txIndex = signedBlock.block.extrinsics.indexOf(txHex);
|
|
30
31
|
assert(txIndex >= 0, 'Extrinsic not found!');
|
|
31
|
-
const events = blockEvents.filter(({ phase }) => phase.
|
|
32
|
+
const events = blockEvents.filter(({ phase }) => phase.type === 'ApplyExtrinsic' && phase.value === txIndex);
|
|
33
|
+
const status = toTxStatus(txStatus, txIndex);
|
|
32
34
|
return callback(new SubmittableResult({ status, txHash, events, txIndex }));
|
|
33
35
|
}
|
|
34
36
|
else {
|
|
37
|
+
const status = toTxStatus(txStatus);
|
|
35
38
|
return callback(new SubmittableResult({ status, txHash }));
|
|
36
39
|
}
|
|
37
40
|
});
|
|
@@ -25,10 +25,10 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
25
25
|
};
|
|
26
26
|
const validation = await validateTx(finalizedHash);
|
|
27
27
|
if (validation.isOk) {
|
|
28
|
-
callback(new SubmittableResult({ status: {
|
|
28
|
+
callback(new SubmittableResult({ status: { type: 'Validated' }, txHash }));
|
|
29
29
|
}
|
|
30
30
|
else if (validation.isErr) {
|
|
31
|
-
throw new InvalidTxError(`Invalid Tx: ${validation.err.
|
|
31
|
+
throw new InvalidTxError(`Invalid Tx: ${validation.err.type} - ${validation.err.value.type}`, validation);
|
|
32
32
|
}
|
|
33
33
|
const checkTxIsOnChain = async (blockHash) => {
|
|
34
34
|
if (blockHash === finalizedHash)
|
|
@@ -39,7 +39,7 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
39
39
|
return checkTxIsOnChain(api.chainHead.findBlock(blockHash).parent);
|
|
40
40
|
}
|
|
41
41
|
const events = await this.getSystemEventsAt(blockHash);
|
|
42
|
-
const txEvents = events.filter(({ phase }) => phase.
|
|
42
|
+
const txEvents = events.filter(({ phase }) => phase.type == 'ApplyExtrinsic' && phase.value === txIndex);
|
|
43
43
|
return {
|
|
44
44
|
blockHash,
|
|
45
45
|
index: txIndex,
|
|
@@ -85,7 +85,7 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
85
85
|
if (txFound && bestChainChanged) {
|
|
86
86
|
txFound = undefined;
|
|
87
87
|
callback(new SubmittableResult({
|
|
88
|
-
status: {
|
|
88
|
+
status: { type: 'NoLongerInBestChain' },
|
|
89
89
|
txHash,
|
|
90
90
|
}));
|
|
91
91
|
}
|
|
@@ -98,7 +98,7 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
98
98
|
txFound = inBlock;
|
|
99
99
|
const { index: txIndex, events, blockHash } = inBlock;
|
|
100
100
|
callback(new SubmittableResult({
|
|
101
|
-
status: {
|
|
101
|
+
status: { type: 'BestChainBlockIncluded', value: { blockHash, txIndex } },
|
|
102
102
|
txHash,
|
|
103
103
|
events,
|
|
104
104
|
txIndex,
|
|
@@ -131,7 +131,7 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
131
131
|
if (inBlock) {
|
|
132
132
|
const { index: txIndex, events, blockHash } = inBlock;
|
|
133
133
|
callback(new SubmittableResult({
|
|
134
|
-
status: {
|
|
134
|
+
status: { type: 'Finalized', value: { blockHash, txIndex } },
|
|
135
135
|
txHash,
|
|
136
136
|
events,
|
|
137
137
|
txIndex,
|
|
@@ -145,8 +145,8 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
145
145
|
return;
|
|
146
146
|
callback(new SubmittableResult({
|
|
147
147
|
status: {
|
|
148
|
-
|
|
149
|
-
value: { error: `Invalid Tx: ${validation.err.
|
|
148
|
+
type: 'Invalid',
|
|
149
|
+
value: { error: `Invalid Tx: ${validation.err.type} - ${validation.err.value.type}` },
|
|
150
150
|
},
|
|
151
151
|
txHash,
|
|
152
152
|
}));
|
|
@@ -155,7 +155,7 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
155
155
|
};
|
|
156
156
|
stopBroadcastFn = await api.txBroadcaster.broadcastTx(txHex);
|
|
157
157
|
callback(new SubmittableResult({
|
|
158
|
-
status: {
|
|
158
|
+
status: { type: 'Broadcasting' },
|
|
159
159
|
txHash,
|
|
160
160
|
}));
|
|
161
161
|
const stopBestBlockTrackingFn = api.chainHead.on('bestBlock', checkBestBlockIncluded);
|
|
@@ -181,11 +181,11 @@ export class SubmittableExtrinsicV2 extends BaseSubmittableExtrinsic {
|
|
|
181
181
|
try {
|
|
182
182
|
// TODO handle timeout for this with the Drop status, just in-case we somehow can't find the tx in any block
|
|
183
183
|
const unsub = await this.#send(({ status, txHash }) => {
|
|
184
|
-
if (status.
|
|
184
|
+
if (status.type === 'BestChainBlockIncluded' || status.type === 'Finalized') {
|
|
185
185
|
defer.resolve(txHash);
|
|
186
186
|
unsub().catch(noop);
|
|
187
187
|
}
|
|
188
|
-
else if (status.
|
|
188
|
+
else if (status.type === 'Invalid' || status.type === 'Drop') {
|
|
189
189
|
defer.reject(new Error(status.value.error));
|
|
190
190
|
unsub().catch(noop);
|
|
191
191
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import type { DispatchError, DispatchInfo, Hash } from '@dedot/codecs';
|
|
2
|
-
import type { IEventRecord, ISubmittableResult } from '@dedot/types';
|
|
2
|
+
import type { IEventRecord, ISubmittableResult, TxStatus } from '@dedot/types';
|
|
3
3
|
import type { FrameSystemEventRecord } from '../../chaintypes/index.js';
|
|
4
|
-
export interface SubmittableResultInputs<E extends IEventRecord = FrameSystemEventRecord
|
|
4
|
+
export interface SubmittableResultInputs<E extends IEventRecord = FrameSystemEventRecord> {
|
|
5
5
|
events?: E[];
|
|
6
6
|
status: TxStatus;
|
|
7
7
|
txHash: Hash;
|
|
8
8
|
txIndex?: number;
|
|
9
9
|
}
|
|
10
|
-
export declare class SubmittableResult<E extends IEventRecord = FrameSystemEventRecord
|
|
10
|
+
export declare class SubmittableResult<E extends IEventRecord = FrameSystemEventRecord> implements ISubmittableResult<E> {
|
|
11
11
|
status: TxStatus;
|
|
12
12
|
events: E[];
|
|
13
13
|
dispatchInfo?: DispatchInfo;
|
|
14
14
|
dispatchError?: DispatchError;
|
|
15
15
|
txHash: Hash;
|
|
16
16
|
txIndex?: number;
|
|
17
|
-
constructor({ events, status, txHash, txIndex }: SubmittableResultInputs<E
|
|
17
|
+
constructor({ events, status, txHash, txIndex }: SubmittableResultInputs<E>);
|
|
18
18
|
_extractDispatchInfo(): [DispatchInfo | undefined, DispatchError | undefined];
|
|
19
19
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IKeyringPair } from '@polkadot/types/types';
|
|
2
|
-
import
|
|
2
|
+
import { TransactionStatus } from '@dedot/codecs';
|
|
3
|
+
import type { AddressOrPair, TxStatus } from '@dedot/types';
|
|
3
4
|
import { HexString } from '@dedot/utils';
|
|
4
5
|
export declare function isKeyringPair(account: AddressOrPair): account is IKeyringPair;
|
|
5
6
|
/**
|
|
@@ -8,3 +9,11 @@ export declare function isKeyringPair(account: AddressOrPair): account is IKeyri
|
|
|
8
9
|
* @param raw
|
|
9
10
|
*/
|
|
10
11
|
export declare function signRaw(signerPair: IKeyringPair, raw: HexString): Uint8Array;
|
|
12
|
+
/**
|
|
13
|
+
* Convert transaction status to transaction event
|
|
14
|
+
*
|
|
15
|
+
* Ref: https://github.com/paritytech/polkadot-sdk/blob/98a364fe6e7abf10819f5fddd3de0588f7c38700/substrate/client/rpc-spec-v2/src/transaction/transaction.rs#L132-L159
|
|
16
|
+
* @param txStatus
|
|
17
|
+
* @param txIndex
|
|
18
|
+
*/
|
|
19
|
+
export declare function toTxStatus(txStatus: TransactionStatus, txIndex?: number): TxStatus;
|