@talismn/sapi 0.0.0-pr2277-20251211070508 → 0.0.0-pr2295-20260110031023
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/dist/index.d.mts +111 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +658 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +619 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +21 -7
- package/dist/declarations/src/fetchBestMetadata.d.ts +0 -10
- package/dist/declarations/src/helpers/errors.d.ts +0 -2
- package/dist/declarations/src/helpers/getCallDocs.d.ts +0 -2
- package/dist/declarations/src/helpers/getChainInfo.d.ts +0 -7
- package/dist/declarations/src/helpers/getConstantValue.d.ts +0 -2
- package/dist/declarations/src/helpers/getDecodedCall.d.ts +0 -13
- package/dist/declarations/src/helpers/getDryRunCall.d.ts +0 -13
- package/dist/declarations/src/helpers/getExtrinsicDispatchInfo.d.ts +0 -7
- package/dist/declarations/src/helpers/getFeeEstimate.d.ts +0 -3
- package/dist/declarations/src/helpers/getPayloadWithMetadataHash.d.ts +0 -6
- package/dist/declarations/src/helpers/getRuntimeCallResult.d.ts +0 -2
- package/dist/declarations/src/helpers/getSapiConnector.d.ts +0 -3
- package/dist/declarations/src/helpers/getSendRequestResult.d.ts +0 -2
- package/dist/declarations/src/helpers/getSignerPayloadJSON.d.ts +0 -8
- package/dist/declarations/src/helpers/getStorageValue.d.ts +0 -2
- package/dist/declarations/src/helpers/getTypeRegistry.d.ts +0 -4
- package/dist/declarations/src/helpers/isApiAvailable.d.ts +0 -2
- package/dist/declarations/src/helpers/papi.d.ts +0 -5
- package/dist/declarations/src/helpers/submit.d.ts +0 -6
- package/dist/declarations/src/helpers/types.d.ts +0 -24
- package/dist/declarations/src/index.d.ts +0 -3
- package/dist/declarations/src/log.d.ts +0 -2
- package/dist/declarations/src/sapi.d.ts +0 -58
- package/dist/declarations/src/types.d.ts +0 -21
- package/dist/talismn-sapi.cjs.d.ts +0 -1
- package/dist/talismn-sapi.cjs.dev.js +0 -601
- package/dist/talismn-sapi.cjs.js +0 -7
- package/dist/talismn-sapi.cjs.prod.js +0 -601
- package/dist/talismn-sapi.esm.js +0 -593
|
@@ -1,601 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var scale = require('@talismn/scale');
|
|
4
|
-
var anylogger = require('anylogger');
|
|
5
|
-
var polkadotApi = require('polkadot-api');
|
|
6
|
-
var metadataBuilders = require('@polkadot-api/metadata-builders');
|
|
7
|
-
var utils = require('@polkadot-api/utils');
|
|
8
|
-
var util = require('@polkadot/util');
|
|
9
|
-
var types = require('@polkadot/types');
|
|
10
|
-
var merkleizeMetadata = require('@polkadot-api/merkleize-metadata');
|
|
11
|
-
var substrateBindings = require('@polkadot-api/substrate-bindings');
|
|
12
|
-
var scaleTs = require('scale-ts');
|
|
13
|
-
|
|
14
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
-
|
|
16
|
-
var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
|
|
17
|
-
|
|
18
|
-
var packageJson = {
|
|
19
|
-
name: "@talismn/sapi"};
|
|
20
|
-
|
|
21
|
-
var log = anylogger__default.default(packageJson.name);
|
|
22
|
-
|
|
23
|
-
const getCallDocs = (chain, pallet, method) => {
|
|
24
|
-
try {
|
|
25
|
-
const typeIdCalls = chain.metadata.pallets.find(({
|
|
26
|
-
name
|
|
27
|
-
}) => name === pallet)?.calls?.type;
|
|
28
|
-
if (!typeIdCalls) return null;
|
|
29
|
-
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
-
let palletCalls = chain.metadata.lookup[typeIdCalls];
|
|
32
|
-
if (!palletCalls || palletCalls.id !== typeIdCalls) palletCalls = chain.metadata.lookup.find(v => v.id === typeIdCalls);
|
|
33
|
-
if (!palletCalls) return null;
|
|
34
|
-
const call = palletCalls.def.value.find(c => c.name === method);
|
|
35
|
-
return call?.docs?.join("\n") ?? null;
|
|
36
|
-
} catch (err) {
|
|
37
|
-
log.error("Failed to find call docs", {
|
|
38
|
-
pallet,
|
|
39
|
-
method,
|
|
40
|
-
chain
|
|
41
|
-
});
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const getConstantValue = (chain, pallet, constant) => {
|
|
47
|
-
return scale.getConstantValueFromMetadata({
|
|
48
|
-
builder: chain.builder,
|
|
49
|
-
unifiedMetadata: chain.metadata
|
|
50
|
-
}, pallet, constant);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const getChainInfo = chain => {
|
|
54
|
-
const {
|
|
55
|
-
spec_name: specName,
|
|
56
|
-
spec_version: specVersion,
|
|
57
|
-
transaction_version: transactionVersion
|
|
58
|
-
} = getConstantValue(chain, "System", "Version");
|
|
59
|
-
const base58Prefix = getConstantValue(chain, "System", "SS58Prefix");
|
|
60
|
-
return {
|
|
61
|
-
specName,
|
|
62
|
-
specVersion,
|
|
63
|
-
transactionVersion,
|
|
64
|
-
base58Prefix
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const getDecodedCall = (palletName, methodName, args) => ({
|
|
69
|
-
type: palletName,
|
|
70
|
-
value: {
|
|
71
|
-
type: methodName,
|
|
72
|
-
value: args
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
const getDecodedCallFromPayload = (chain, payload) => {
|
|
76
|
-
const def = chain.builder.buildDefinition(chain.lookup.call);
|
|
77
|
-
const decoded = def.dec(payload.method);
|
|
78
|
-
return {
|
|
79
|
-
pallet: decoded.type,
|
|
80
|
-
method: decoded.value.type,
|
|
81
|
-
args: decoded.value.value
|
|
82
|
-
};
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const getDispatchErrorMessage = (chain, err) => {
|
|
86
|
-
try {
|
|
87
|
-
if (!err) return null;
|
|
88
|
-
const error = err;
|
|
89
|
-
if (!error.type) throw new Error("Unknown dispatch error");
|
|
90
|
-
const lv1 = DISPATCH_ERROR[error.type];
|
|
91
|
-
if (!lv1) throw new Error("Unknown dispatch error");
|
|
92
|
-
if (lv1 === ERROR_METADATA_LOOKUP) return getModuleErrorMessage(chain, error.value);
|
|
93
|
-
if (typeof lv1 === "string") return lv1;
|
|
94
|
-
const lv2 = lv1[error.value?.type];
|
|
95
|
-
if (!lv2) throw new Error("Unknown dispatch error");
|
|
96
|
-
if (typeof lv2 === "string") return lv2;
|
|
97
|
-
throw new Error("Unknown dispatch error");
|
|
98
|
-
} catch (cause) {
|
|
99
|
-
log.error("Failed to parse runtime error", {
|
|
100
|
-
chainId: chain.connector.chainId,
|
|
101
|
-
cause,
|
|
102
|
-
err
|
|
103
|
-
});
|
|
104
|
-
return tryFormatError(err);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
const ERROR_METADATA_LOOKUP = "METADATA_LOOKUP";
|
|
108
|
-
|
|
109
|
-
// only `Module` errors are part of the metadata
|
|
110
|
-
// errors below are defined as part of the runtime but their docs aren't included in the metadata
|
|
111
|
-
// so those are copy/pasted from the polkadot-sdk repo
|
|
112
|
-
|
|
113
|
-
// https://github.com/paritytech/polkadot-sdk/blob/56d97c3ad8c86e602bc7ac368751210517c4309f/substrate/primitives/runtime/src/lib.rs#L543
|
|
114
|
-
const ERRORS_TRANSACTIONAL = {
|
|
115
|
-
LimitReached: "Too many transactional layers have been spawned",
|
|
116
|
-
NoLayer: "A transactional layer was expected, but does not exist"
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// https://github.com/paritytech/polkadot-sdk/blob/56d97c3ad8c86e602bc7ac368751210517c4309f/substrate/primitives/runtime/src/lib.rs#L672
|
|
120
|
-
const ERRORS_TOKEN = {
|
|
121
|
-
FundsUnavailable: "Funds are unavailable",
|
|
122
|
-
OnlyProvider: "Account that must exist would die",
|
|
123
|
-
BelowMinimum: "Account cannot exist with the funds that would be given",
|
|
124
|
-
CannotCreate: "Account cannot be created",
|
|
125
|
-
UnknownAsset: "The asset in question is unknown",
|
|
126
|
-
Frozen: "Funds exist but are frozen",
|
|
127
|
-
Unsupported: "Operation is not supported by the asset",
|
|
128
|
-
CannotCreateHold: "Account cannot be created for recording amount on hold",
|
|
129
|
-
NotExpendable: "Account that is desired to remain would die",
|
|
130
|
-
Blocked: "Account cannot receive the assets"
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// https://github.com/paritytech/polkadot-sdk/blob/56d97c3ad8c86e602bc7ac368751210517c4309f/substrate/primitives/arithmetic/src/lib.rs#L76
|
|
134
|
-
const ERRORS_ARITHMETIC = {
|
|
135
|
-
Overflow: "An underflow would occur",
|
|
136
|
-
Underflow: "An overflow would occur",
|
|
137
|
-
DivisionByZero: "Division by zero"
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// https://github.com/paritytech/polkadot-sdk/blob/56d97c3ad8c86e602bc7ac368751210517c4309f/substrate/primitives/runtime/src/lib.rs#L714
|
|
141
|
-
const DISPATCH_ERROR = {
|
|
142
|
-
CannotLookup: "Cannot lookup",
|
|
143
|
-
BadOrigin: "Bad origin",
|
|
144
|
-
Module: ERROR_METADATA_LOOKUP,
|
|
145
|
-
ConsumerRemaining: "Consumer remaining",
|
|
146
|
-
NoProviders: "No providers",
|
|
147
|
-
TooManyConsumers: "Too many consumers",
|
|
148
|
-
Token: ERRORS_TOKEN,
|
|
149
|
-
Arithmetic: ERRORS_ARITHMETIC,
|
|
150
|
-
Transactional: ERRORS_TRANSACTIONAL,
|
|
151
|
-
Exhausted: "Resources exhausted",
|
|
152
|
-
Corruption: "State corrupt",
|
|
153
|
-
Unavailable: "Resource unavailable",
|
|
154
|
-
RootNotAllowed: "Root not allowed",
|
|
155
|
-
Trie: "Unknown error",
|
|
156
|
-
// unsupported,
|
|
157
|
-
Other: "Unknown error" // unsupported,
|
|
158
|
-
};
|
|
159
|
-
const getModuleErrorMessage = (chain, error) => {
|
|
160
|
-
try {
|
|
161
|
-
if (!chain.metadata) throw new Error("Could not fetch metadata");
|
|
162
|
-
const pallet = chain.metadata.pallets.find(p => p.name === error.type);
|
|
163
|
-
if (typeof pallet?.errors !== "number") throw new Error("Unknown pallet");
|
|
164
|
-
const lookup = metadataBuilders.getLookupFn(chain.metadata);
|
|
165
|
-
const palletErrors = lookup(pallet.errors);
|
|
166
|
-
if (palletErrors.type !== "enum" || !palletErrors.innerDocs[error.value.type]?.length) throw new Error("Unknown error type");
|
|
167
|
-
|
|
168
|
-
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
|
169
|
-
return palletErrors.innerDocs[error.value.type].join(" ");
|
|
170
|
-
} catch (err) {
|
|
171
|
-
log.error("Failed to parse module error", {
|
|
172
|
-
chainId: chain.connector.chainId,
|
|
173
|
-
error,
|
|
174
|
-
err
|
|
175
|
-
});
|
|
176
|
-
return [error.type, error.value.type].join(": ");
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
const tryFormatError = err => {
|
|
180
|
-
try {
|
|
181
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
-
const unsafeErr = err;
|
|
183
|
-
if (unsafeErr.type && unsafeErr.value?.type) return [unsafeErr.type, unsafeErr.value.type].join(": ");
|
|
184
|
-
} catch (err) {
|
|
185
|
-
// ignore
|
|
186
|
-
}
|
|
187
|
-
return "Unknown error";
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const getSendRequestResult = (chain, method, params, isCacheable) => {
|
|
191
|
-
return chain.connector.send(method, params, isCacheable);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const getRuntimeCallResult = async (chain, apiName, method, args) => {
|
|
195
|
-
const call = chain.builder.buildRuntimeCall(apiName, method);
|
|
196
|
-
const hex = await getSendRequestResult(chain, "state_call", [`${apiName}_${method}`, utils.toHex(call.args.enc(args))]);
|
|
197
|
-
return call.value.dec(hex);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const isApiAvailable = (chain, name, method) => {
|
|
201
|
-
return chain.metadata.apis.some(a => a.name === name && a.methods.some(m => m.name === method));
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const getDryRunCall = async (chain, from, decodedCall) => {
|
|
205
|
-
try {
|
|
206
|
-
if (!isApiAvailable(chain, "DryRunApi", "dry_run_call")) return {
|
|
207
|
-
available: false,
|
|
208
|
-
data: null
|
|
209
|
-
};
|
|
210
|
-
const origin = polkadotApi.Enum("system", polkadotApi.Enum("Signed", from));
|
|
211
|
-
const {
|
|
212
|
-
pallet,
|
|
213
|
-
method,
|
|
214
|
-
args
|
|
215
|
-
} = decodedCall;
|
|
216
|
-
const call = {
|
|
217
|
-
type: pallet,
|
|
218
|
-
value: {
|
|
219
|
-
type: method,
|
|
220
|
-
value: args
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// This will throw an error if the api is not available on that chain
|
|
225
|
-
const data = await getRuntimeCallResult(chain, "DryRunApi", "dry_run_call", [origin, call]);
|
|
226
|
-
const ok = data.success && data.value.execution_result.success;
|
|
227
|
-
const errorMessage = data.success && !data.value.execution_result.success ? getDispatchErrorMessage(chain, data.value.execution_result.value.error) : null;
|
|
228
|
-
return {
|
|
229
|
-
available: true,
|
|
230
|
-
// NOTE: we can't re-export `@polkadot-api/descriptors` from this package.
|
|
231
|
-
// So, the caller of this function must pass in their own instance of `type DryRunResult` as the generic argument `T`.
|
|
232
|
-
data: data,
|
|
233
|
-
ok,
|
|
234
|
-
errorMessage
|
|
235
|
-
};
|
|
236
|
-
} catch (err) {
|
|
237
|
-
// Note : err is null if chain doesnt have the api
|
|
238
|
-
log.error("Failed to dry run", {
|
|
239
|
-
chainId: chain.connector.chainId,
|
|
240
|
-
err
|
|
241
|
-
});
|
|
242
|
-
return {
|
|
243
|
-
available: false,
|
|
244
|
-
data: null
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
// used for chains that dont have metadata v15 yet
|
|
250
|
-
const getExtrinsicDispatchInfo = async (chain, signedExtrinsic) => {
|
|
251
|
-
util.assert(signedExtrinsic.isSigned, "Extrinsic must be signed (or fakeSigned) in order to query fee");
|
|
252
|
-
const len = signedExtrinsic.registry.createType("u32", signedExtrinsic.encodedLength);
|
|
253
|
-
const dispatchInfo = await stateCall(chain.connector.send, "TransactionPaymentApi_query_info", "RuntimeDispatchInfo", [signedExtrinsic, len], undefined, true);
|
|
254
|
-
return {
|
|
255
|
-
partialFee: dispatchInfo.partialFee.toString()
|
|
256
|
-
};
|
|
257
|
-
};
|
|
258
|
-
const stateCall = async (request, method, resultType, args, blockHash, isCacheable) => {
|
|
259
|
-
// on a state call there are always arguments
|
|
260
|
-
const registry = args[0].registry;
|
|
261
|
-
const bytes = registry.createType("Raw", util.u8aConcatStrict(args.map(arg => arg.toU8a())));
|
|
262
|
-
const result = await request("state_call", [method, bytes.toHex(), blockHash], isCacheable);
|
|
263
|
-
return registry.createType(resultType, result);
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
const getTypeRegistry = (chain, payload) => {
|
|
267
|
-
log.log(`[sapi] getTypeRegistry begin: ${Date.now()}`);
|
|
268
|
-
const registry = new types.TypeRegistry();
|
|
269
|
-
if (chain.registryTypes) registry.register(chain.registryTypes);
|
|
270
|
-
const meta = new types.Metadata(registry, chain.hexMetadata);
|
|
271
|
-
registry.setMetadata(meta, payload.signedExtensions, chain.signedExtensions); // ~30ms
|
|
272
|
-
|
|
273
|
-
log.log(`[sapi] getTypeRegistry end: ${Date.now()}`);
|
|
274
|
-
return registry;
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
const getFeeEstimate = async (chain, payload, chainInfo) => {
|
|
278
|
-
// TODO do this without PJS / registry => waiting for @polkadot-api/tx-utils
|
|
279
|
-
const registry = getTypeRegistry(chain, payload);
|
|
280
|
-
const extrinsic = registry.createType("Extrinsic", payload);
|
|
281
|
-
extrinsic.signFake(payload.address, {
|
|
282
|
-
appId: 0,
|
|
283
|
-
nonce: payload.nonce,
|
|
284
|
-
blockHash: payload.blockHash,
|
|
285
|
-
genesisHash: payload.genesisHash,
|
|
286
|
-
runtimeVersion: {
|
|
287
|
-
specVersion: chainInfo.specVersion,
|
|
288
|
-
transactionVersion: chainInfo.transactionVersion
|
|
289
|
-
// other fields aren't necessary for signing
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
const bytes = extrinsic.toU8a(true);
|
|
293
|
-
const binary = polkadotApi.Binary.fromBytes(bytes);
|
|
294
|
-
try {
|
|
295
|
-
const result = await getRuntimeCallResult(chain, "TransactionPaymentApi", "query_info", [binary, bytes.length]);
|
|
296
|
-
// Do not throw if partialFee is 0n. This is a valid response, eg: Bittensor remove_stake fee estimation is 0n.
|
|
297
|
-
if (!result?.partial_fee && result.partial_fee !== 0n) {
|
|
298
|
-
throw new Error("partialFee is not found");
|
|
299
|
-
}
|
|
300
|
-
return result.partial_fee;
|
|
301
|
-
} catch (err) {
|
|
302
|
-
log.error("Failed to get fee estimate using getRuntimeCallValue", {
|
|
303
|
-
err
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// fallback to pjs encoded state call, in case the above fails (extracting runtime calls codecs might require metadata V15)
|
|
308
|
-
// Note: PAPI will consider TransactionPaymentApi as first class api so it should work even without V15, but this is not the case yet.
|
|
309
|
-
const {
|
|
310
|
-
partialFee
|
|
311
|
-
} = await getExtrinsicDispatchInfo(chain, extrinsic);
|
|
312
|
-
return BigInt(partialFee);
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const getSapiConnector = ({
|
|
316
|
-
chainId,
|
|
317
|
-
send,
|
|
318
|
-
submit,
|
|
319
|
-
submitWithBittensorMevShield
|
|
320
|
-
}) => ({
|
|
321
|
-
chainId,
|
|
322
|
-
send,
|
|
323
|
-
submit: (...args) => {
|
|
324
|
-
if (submit) return submit(...args);
|
|
325
|
-
throw new Error("submit handler not provided");
|
|
326
|
-
},
|
|
327
|
-
submitWithBittensorMevShield: (...args) => {
|
|
328
|
-
if (submitWithBittensorMevShield) return submitWithBittensorMevShield(...args);
|
|
329
|
-
throw new Error("submitWithBittensorMevShield handler not provided");
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
const getPayloadWithMetadataHash = (chain, chainInfo, payload) => {
|
|
334
|
-
if (!chain.hasCheckMetadataHash || !payload.signedExtensions.includes("CheckMetadataHash")) return {
|
|
335
|
-
payload,
|
|
336
|
-
txMetadata: undefined
|
|
337
|
-
};
|
|
338
|
-
try {
|
|
339
|
-
const {
|
|
340
|
-
decimals,
|
|
341
|
-
symbol: tokenSymbol
|
|
342
|
-
} = chain.token;
|
|
343
|
-
const {
|
|
344
|
-
base58Prefix,
|
|
345
|
-
specName,
|
|
346
|
-
specVersion
|
|
347
|
-
} = chainInfo;
|
|
348
|
-
const metadataHashInputs = {
|
|
349
|
-
tokenSymbol,
|
|
350
|
-
decimals,
|
|
351
|
-
base58Prefix,
|
|
352
|
-
specName,
|
|
353
|
-
specVersion
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
// since ultimately this needs a V15 object, would be nice if this accepted one directly as input
|
|
357
|
-
const merkleizedMetadata = merkleizeMetadata.merkleizeMetadata(chain.hexMetadata, metadataHashInputs);
|
|
358
|
-
const metadataHash = utils.toHex(merkleizedMetadata.digest());
|
|
359
|
-
log.log("metadataHash", metadataHash, metadataHashInputs);
|
|
360
|
-
const payloadWithMetadataHash = {
|
|
361
|
-
...payload,
|
|
362
|
-
mode: 1,
|
|
363
|
-
metadataHash,
|
|
364
|
-
withSignedTransaction: true
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
// TODO do this without PJS / registry => waiting for @polkadot-api/tx-utils
|
|
368
|
-
// const { extra, additionalSigned } = getSignedExtensionValues(payload, metadata)
|
|
369
|
-
// const badExtPayload = mergeUint8([fromHex(payload.method), ...extra, ...additionalSigned])
|
|
370
|
-
|
|
371
|
-
const registry = getTypeRegistry(chain, payload);
|
|
372
|
-
const extPayload = registry.createType("ExtrinsicPayload", payloadWithMetadataHash);
|
|
373
|
-
const barePayload = extPayload.toU8a(true);
|
|
374
|
-
const txMetadata = merkleizedMetadata.getProofForExtrinsicPayload(barePayload);
|
|
375
|
-
return {
|
|
376
|
-
payload: payloadWithMetadataHash,
|
|
377
|
-
txMetadata
|
|
378
|
-
};
|
|
379
|
-
} catch (err) {
|
|
380
|
-
log.error("Failed to get shortened metadata", {
|
|
381
|
-
error: err
|
|
382
|
-
});
|
|
383
|
-
return {
|
|
384
|
-
payload,
|
|
385
|
-
txMetadata: undefined
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
const getStorageValue = async (chain, pallet, entry, keys, at) => {
|
|
391
|
-
const storageCodec = chain.builder.buildStorage(pallet, entry);
|
|
392
|
-
const stateKey = storageCodec.keys.enc(...keys);
|
|
393
|
-
const hexValue = await getSendRequestResult(chain, "state_getStorage", [stateKey, at]);
|
|
394
|
-
if (!hexValue) return null; // caller will need to expect null when applicable
|
|
395
|
-
|
|
396
|
-
return storageCodec.value.dec(hexValue);
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
400
|
-
//////////////////////////// Utilities from PAPI /////////////////////////////
|
|
401
|
-
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
402
|
-
|
|
403
|
-
const toPjsHex = (value, minByteLen) => {
|
|
404
|
-
let inner = value.toString(16);
|
|
405
|
-
inner = (inner.length % 2 ? "0" : "") + inner;
|
|
406
|
-
const nPaddedBytes = Math.max(0, (minByteLen || 0) - inner.length / 2);
|
|
407
|
-
return "0x" + "00".repeat(nPaddedBytes) + inner;
|
|
408
|
-
};
|
|
409
|
-
const mortal = substrateBindings.enhanceEncoder(substrateBindings.Bytes(2).enc, value => {
|
|
410
|
-
const factor = Math.max(value.period >> 12, 1);
|
|
411
|
-
const left = Math.min(Math.max(trailingZeroes(value.period) - 1, 1), 15);
|
|
412
|
-
const right = value.phase / factor << 4;
|
|
413
|
-
return substrateBindings.u16.enc(left | right);
|
|
414
|
-
});
|
|
415
|
-
function trailingZeroes(n) {
|
|
416
|
-
let i = 0;
|
|
417
|
-
while (!(n & 1)) {
|
|
418
|
-
i++;
|
|
419
|
-
n >>= 1;
|
|
420
|
-
}
|
|
421
|
-
return i;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const ERA_PERIOD = 64; // validity period in blocks, used for mortal era
|
|
425
|
-
|
|
426
|
-
const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerConfig, chainInfo) => {
|
|
427
|
-
const {
|
|
428
|
-
codec,
|
|
429
|
-
location
|
|
430
|
-
} = chain.builder.buildCall(palletName, methodName);
|
|
431
|
-
const method = polkadotApi.Binary.fromBytes(utils.mergeUint8([new Uint8Array(location), codec.enc(args)]));
|
|
432
|
-
|
|
433
|
-
// on unstable networks with lots of forks (ex: westend asset hub as of june 2025),
|
|
434
|
-
// using a finalized block as reference for mortality is necessary for txs to get through
|
|
435
|
-
let blockHash = await getSendRequestResult(chain, "chain_getFinalizedHead", [], false);
|
|
436
|
-
const [nonce, genesisHash, blockNumberFinalized, blockNumberCurrent] = await Promise.all([getSendRequestResult(chain, "system_accountNextIndex", [signerConfig.address], false), getStorageValue(chain, "System", "BlockHash", [0]), getStorageValue(chain, "System", "Number", [], blockHash), getStorageValue(chain, "System", "Number", [])]);
|
|
437
|
-
if (!genesisHash) throw new Error("Genesis hash not found");
|
|
438
|
-
if (!blockHash) throw new Error("Block hash not found");
|
|
439
|
-
let blockNumber = blockNumberFinalized;
|
|
440
|
-
|
|
441
|
-
// on Autonomys the finalized block hash is wrong (7000 blocks behind),
|
|
442
|
-
// if we use it to craft a tx it will be invalid
|
|
443
|
-
// => if finalized block number is more than 32 blocks behind, use current - 16
|
|
444
|
-
if (blockNumberCurrent - blockNumberFinalized > 32) {
|
|
445
|
-
blockNumber = blockNumberCurrent - 16;
|
|
446
|
-
const binBlockHash = await getStorageValue(chain, "System", "BlockHash", [blockNumber]);
|
|
447
|
-
blockHash = binBlockHash.asHex();
|
|
448
|
-
}
|
|
449
|
-
const era = mortal({
|
|
450
|
-
period: ERA_PERIOD,
|
|
451
|
-
phase: blockNumber % ERA_PERIOD
|
|
452
|
-
});
|
|
453
|
-
const signedExtensions = chain.metadata.extrinsic.signedExtensions.map(ext => ext.identifier);
|
|
454
|
-
const basePayload = {
|
|
455
|
-
address: signerConfig.address,
|
|
456
|
-
genesisHash: genesisHash.asHex(),
|
|
457
|
-
blockHash,
|
|
458
|
-
method: method.asHex(),
|
|
459
|
-
signedExtensions,
|
|
460
|
-
nonce: toPjsHex(nonce, 4),
|
|
461
|
-
specVersion: toPjsHex(chainInfo.specVersion, 4),
|
|
462
|
-
transactionVersion: toPjsHex(chainInfo.transactionVersion, 4),
|
|
463
|
-
blockNumber: toPjsHex(blockNumber, 4),
|
|
464
|
-
era: utils.toHex(era),
|
|
465
|
-
tip: toPjsHex(0, 16),
|
|
466
|
-
// TODO gas station (required for Astar)
|
|
467
|
-
assetId: undefined,
|
|
468
|
-
version: 4
|
|
469
|
-
};
|
|
470
|
-
const {
|
|
471
|
-
payload,
|
|
472
|
-
txMetadata
|
|
473
|
-
} = getPayloadWithMetadataHash(chain, chainInfo, basePayload);
|
|
474
|
-
const shortMetadata = txMetadata ? util.u8aToHex(txMetadata) : undefined;
|
|
475
|
-
|
|
476
|
-
// Avail support
|
|
477
|
-
if (payload.signedExtensions.includes("CheckAppId")) payload.appId = 0;
|
|
478
|
-
log.log("[sapi] payload", {
|
|
479
|
-
newPayload: payload,
|
|
480
|
-
txMetadata
|
|
481
|
-
});
|
|
482
|
-
return {
|
|
483
|
-
payload,
|
|
484
|
-
txMetadata,
|
|
485
|
-
// TODO remove
|
|
486
|
-
shortMetadata
|
|
487
|
-
};
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
const submit = async (chain, payload, signature, txInfo, mode) => {
|
|
491
|
-
switch (mode) {
|
|
492
|
-
case "bittensor-mev-shield":
|
|
493
|
-
if (signature) throw new Error("Signature should not be provided when using bittensor-mev-shield mode");
|
|
494
|
-
return chain.connector.submitWithBittensorMevShield(payload, txInfo);
|
|
495
|
-
default:
|
|
496
|
-
return chain.connector.submit(payload, signature, txInfo);
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signedExtensions, registryTypes) => {
|
|
501
|
-
const {
|
|
502
|
-
unifiedMetadata: metadata,
|
|
503
|
-
lookupFn: lookup,
|
|
504
|
-
builder
|
|
505
|
-
} = scale.parseMetadataRpc(hexMetadata);
|
|
506
|
-
const chain = {
|
|
507
|
-
connector: getSapiConnector(connector),
|
|
508
|
-
hexMetadata,
|
|
509
|
-
token,
|
|
510
|
-
hasCheckMetadataHash,
|
|
511
|
-
signedExtensions,
|
|
512
|
-
registryTypes,
|
|
513
|
-
metadata,
|
|
514
|
-
lookup,
|
|
515
|
-
builder,
|
|
516
|
-
metadataRpc: hexMetadata
|
|
517
|
-
};
|
|
518
|
-
const chainInfo = getChainInfo(chain);
|
|
519
|
-
const {
|
|
520
|
-
specName,
|
|
521
|
-
specVersion,
|
|
522
|
-
base58Prefix
|
|
523
|
-
} = chainInfo;
|
|
524
|
-
return {
|
|
525
|
-
id: `${connector.chainId}::${specName}::${specVersion}`,
|
|
526
|
-
chainId: connector.chainId,
|
|
527
|
-
specName,
|
|
528
|
-
specVersion,
|
|
529
|
-
hasCheckMetadataHash,
|
|
530
|
-
base58Prefix,
|
|
531
|
-
token: chain.token,
|
|
532
|
-
chain,
|
|
533
|
-
getConstant: (pallet, constant) => getConstantValue(chain, pallet, constant),
|
|
534
|
-
getStorage: (pallet, entry, keys, at) => getStorageValue(chain, pallet, entry, keys, at),
|
|
535
|
-
getDecodedCall: (pallet, method, args) => getDecodedCall(pallet, method, args),
|
|
536
|
-
getDecodedCallFromPayload: payload => getDecodedCallFromPayload(chain, payload),
|
|
537
|
-
getExtrinsicPayload: (pallet, method, args, config) => getSignerPayloadJSON(chain, pallet, method, args, config, chainInfo),
|
|
538
|
-
getFeeEstimate: payload => getFeeEstimate(chain, payload, chainInfo),
|
|
539
|
-
getRuntimeCallValue: (apiName, method, args) => getRuntimeCallResult(chain, apiName, method, args),
|
|
540
|
-
getTypeRegistry: payload => getTypeRegistry(chain, payload),
|
|
541
|
-
submit: (payload, signature, txInfo, mode) => submit(chain, payload, signature, txInfo, mode),
|
|
542
|
-
getCallDocs: (pallet, method) => getCallDocs(chain, pallet, method),
|
|
543
|
-
getDryRunCall: (from, decodedCall) => getDryRunCall(chain, from, decodedCall),
|
|
544
|
-
isApiAvailable: (name, method) => isApiAvailable(chain, name, method)
|
|
545
|
-
};
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
const MAGIC_NUMBER = 1635018093;
|
|
549
|
-
|
|
550
|
-
// it's important to set a max because some chains also return high invalid version numbers in the metadata_versions list (ex on Polkadot, related to JAM?)
|
|
551
|
-
const MAX_SUPPORTED_METADATA_VERSION = 15; // v16 sometimes outputs different metadata hashes, ignore v16 until that is fixed in PAPI
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Fetches the highest supported version of metadata from the chain.
|
|
555
|
-
*
|
|
556
|
-
* @param rpcSend
|
|
557
|
-
* @returns hex-encoded metadata starting with the magic number
|
|
558
|
-
*/
|
|
559
|
-
const fetchBestMetadata = async (rpcSend, allowLegacyFallback) => {
|
|
560
|
-
try {
|
|
561
|
-
// fetch available versions of metadata
|
|
562
|
-
const metadataVersions = await rpcSend("state_call", ["Metadata_metadata_versions", "0x"], true);
|
|
563
|
-
const availableVersions = scaleTs.Vector(scaleTs.u32).dec(metadataVersions);
|
|
564
|
-
const bestVersion = Math.max(...availableVersions.filter(v => v <= MAX_SUPPORTED_METADATA_VERSION));
|
|
565
|
-
const metadata = await rpcSend("state_call", ["Metadata_metadata_at_version", utils.toHex(scaleTs.u32.enc(bestVersion))], true);
|
|
566
|
-
return normalizeMetadata(metadata);
|
|
567
|
-
} catch (cause) {
|
|
568
|
-
// if the chain doesnt support the Metadata pallet, fallback to legacy rpc provided metadata (V14)
|
|
569
|
-
const message = cause?.message;
|
|
570
|
-
if (allowLegacyFallback || message?.includes("is not found") ||
|
|
571
|
-
// ex: crust standalone
|
|
572
|
-
message?.includes("Module doesn't have export Metadata_metadata_versions") ||
|
|
573
|
-
// ex: 3DPass
|
|
574
|
-
message?.includes("Exported method Metadata_metadata_versions is not found") ||
|
|
575
|
-
// ex: sora-polkadot & sora-standalone
|
|
576
|
-
message?.includes("Execution, MethodNotFound, Metadata_metadata_versions") // ex: stafi
|
|
577
|
-
) {
|
|
578
|
-
return await rpcSend("state_getMetadata", [], true);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// otherwise throw so it can be handled by the caller
|
|
582
|
-
throw new Error("Failed to fetch metadata", {
|
|
583
|
-
cause
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* Removes everything before the magic number in the metadata.
|
|
590
|
-
* This ensures Opaque metadata is usable by pjs
|
|
591
|
-
*/
|
|
592
|
-
const normalizeMetadata = metadata => {
|
|
593
|
-
const hexMagicNumber = utils.toHex(scaleTs.u32.enc(MAGIC_NUMBER)).slice(2);
|
|
594
|
-
const magicNumberIndex = metadata.indexOf(hexMagicNumber);
|
|
595
|
-
if (magicNumberIndex === -1) throw new Error("Invalid metadata format: magic number not found");
|
|
596
|
-
return `0x${metadata.slice(magicNumberIndex)}`;
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
exports.MAX_SUPPORTED_METADATA_VERSION = MAX_SUPPORTED_METADATA_VERSION;
|
|
600
|
-
exports.fetchBestMetadata = fetchBestMetadata;
|
|
601
|
-
exports.getScaleApi = getScaleApi;
|