mirror of
https://github.com/pezkuwichain/pezkuwi-api.git
synced 2026-06-13 09:51:00 +00:00
Rebrand: polkadot → pezkuwi, substrate → bizinikiwi, kusama → dicle
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# @pezkuwi/types
|
||||
|
||||
Implementation of the types and their (de-)serialisation via SCALE codec. On the Rust side, the codec types and primitive types are implemented via the [parity-codec](https://github.com/pezkuwichain/parity-codec).
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-api/issues",
|
||||
"description": "Implementation of the Parity codec",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/types#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/types",
|
||||
"repository": {
|
||||
"directory": "packages/types",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-api.git"
|
||||
},
|
||||
"sideEffects": [
|
||||
"./packageDetect.js",
|
||||
"./packageDetect.cjs"
|
||||
],
|
||||
"type": "module",
|
||||
"version": "16.5.6",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@pezkuwi/keyring": "^14.0.1",
|
||||
"@pezkuwi/types-augment": "16.5.4",
|
||||
"@pezkuwi/types-codec": "16.5.4",
|
||||
"@pezkuwi/types-create": "16.5.4",
|
||||
"@pezkuwi/util": "^14.0.1",
|
||||
"@pezkuwi/util-crypto": "^14.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pezkuwi/keyring": "^14.0.1",
|
||||
"@pezkuwi/types-support": "16.5.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as typeDefinitions from './interfaces/definitions.js';
|
||||
import rpcDefinitions from './interfaces/jsonrpc.js';
|
||||
|
||||
// all external
|
||||
export { TypeDefInfo } from '@pezkuwi/types-create';
|
||||
|
||||
// all named
|
||||
export { convertSiV0toV1 } from './metadata/PortableRegistry/index.js';
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
export { unwrapStorageType } from './util/index.js';
|
||||
|
||||
// all starred
|
||||
export * from './codec/index.js';
|
||||
export * from './create/index.js';
|
||||
export * from './index.types.js';
|
||||
export * from './metadata/index.js';
|
||||
|
||||
// local
|
||||
export { rpcDefinitions, typeDefinitions };
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import '@pezkuwi/types-augment';
|
||||
|
||||
import type { Bytes, Compact, Option, u32 } from '@pezkuwi/types-codec';
|
||||
import type { IOption, ITuple } from '@pezkuwi/types-codec/types';
|
||||
import type { AccountId, BlockAttestations, SessionKeys7 } from './interfaces/index.js';
|
||||
|
||||
import { assert } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from './create/index.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
// something that uses overrides
|
||||
const oo0 = registry.createType('Something' as 'u32');
|
||||
const oo1 = registry.createType<BlockAttestations>('u32');
|
||||
const oo2 = registry.createType<SessionKeys7>('u32');
|
||||
const oo3 = registry.createType<u32>('Something');
|
||||
const oo4 = registry.createType<AccountId>('Option<u32>');
|
||||
const oo5 = registry.createType<IOption<u32>>('u32');
|
||||
const oo6 = registry.createType<Option<Compact<u32>>>('u32');
|
||||
const oo7 = registry.createType<Bytes>('u64');
|
||||
const oo8 = registry.createType<ITuple<[Bytes, u32]>>('(u32)');
|
||||
|
||||
assert(oo0.divn(123) && [...oo1.values()] && oo2[6].isAscii && oo3.divn(3) && oo4.isAscii && oo5.unwrap().divn(1) && oo6.unwrap().unwrap().divn(1) && oo7.isAscii && oo8[1].toNumber(), 'All ok');
|
||||
|
||||
// There are in the interface registry
|
||||
const aa0 = registry.createType(' AccountId');
|
||||
const aa1 = registry.createType('BlockAttestations');
|
||||
const aa2 = registry.createType('ExtrinsicEra');
|
||||
const aa3 = registry.createType('VestingInfo');
|
||||
const aa4 = registry.createType('(Vec<ValidatorIndex>, CompactAssignmentsTo257, PhragmenScore, EraIndex)');
|
||||
|
||||
assert(aa0.isAscii && aa1.receipt && aa2.isMortalEra && aa3.toHuman() && aa4[3].toNumber(), 'All ok');
|
||||
|
||||
// Should be Codec, we don't know this one
|
||||
const bb = registry.createType('Something');
|
||||
|
||||
assert(bb.toHuman(), 'All ok');
|
||||
|
||||
// Should be Vec<Option<Compact<ReferendumIndex>>>
|
||||
const ee = registry.createType('Vec<Option<Compact<ReferendumIndex>>>');
|
||||
// Option<Bytes>
|
||||
const vb = registry.createType('Option< Vec< u8 > >');
|
||||
// nested vecs
|
||||
const vv = registry.createType('Vec< Vec< Vec< Vec<u8>> > >');
|
||||
// vec with tuple
|
||||
const vt = registry.createType('Vec<(u8, u16)>');
|
||||
// nested stuff from all-over
|
||||
const vn = registry.createType('Vec<(u32, (u32, u64), Vec<u8>, Vec<u32>, Vec<(u32, u64)>, [u8;32], [u128;32])>');
|
||||
// nested fixed
|
||||
const nf = registry.createType('[[[u8;32];5];3]');
|
||||
// with linkage
|
||||
const tl = registry.createType('(ValidatorPrefsWithCommission, Linkage<AccountId>)');
|
||||
|
||||
assert(ee[0].unwrap().unwrap().divn(123) && vb.unwrap().bitLength() && vv.toHuman() && vn[0][3][0].bitLength() && vt.toHuman() && nf.toHuman() && tl[1].next, 'All ok');
|
||||
|
||||
// tuple & struct
|
||||
const vs = registry.createType('(u8, {"a":"u32","b":"(u32,u64)"},(u8,u16),{"foo":"Bar"},u16)');
|
||||
// set
|
||||
const st = registry.createType('{"_set": { "A": 1, "B": 2, "C": 4 } }');
|
||||
// enum
|
||||
const en = registry.createType('{"_enum": { "A": 1, "B": 2, "C": 4 } }');
|
||||
|
||||
assert(vs.toHuman() && st.strings && en.index, 'All ok');
|
||||
|
||||
// Should end up as Raw
|
||||
const gg = registry.createType('[ u8 ;678]');
|
||||
|
||||
assert(gg.subarray(1), 'All ok');
|
||||
|
||||
// Should end up as VecFixed<u128>
|
||||
const hh = registry.createType('[u128; 32]');
|
||||
// maps and sets
|
||||
const ms = registry.createType('(BTreeSet<u8>, BTreeMap<u16, u32>, HashMap<u64, u128>)');
|
||||
|
||||
assert(hh[0].bitLength() && ms[0].strings && ms[1].values() && ms[2].keys, 'All ok');
|
||||
|
||||
// tuple! ITuple<[u32, Compact<u64>, u128, Codec]>
|
||||
const tt1 = registry.createType('(u32, Compact<u64>, u128 , Something)');
|
||||
// unwraps into a u32
|
||||
const tt2 = registry.createType('(((u32)))');
|
||||
// TEST: Adding a single param makes this go over the recursion limit in 4.4.4
|
||||
// lots and lots of params (indicates recursion limit)
|
||||
const tt4 = registry.createType('(u8,u16,u32,u64,u128,u256,u8,u16,u32,u64,u128,u256,u8,u16,u32,u64,u128,u256,u8)');
|
||||
// empty
|
||||
const tt5 = registry.createType('()');
|
||||
// nested tuples
|
||||
const tt6 = registry.createType('(u8, (u16, (u32, u64, u128)), (u64, u128))');
|
||||
// more nested tuples
|
||||
const tt7 = registry.createType('(((u8, u16, u32), (u32, u16, u8)), u128, u256)');
|
||||
// nested tuples with a wrapper
|
||||
const tt8 = registry.createType('(u8, Vec<(u16, u32)>, Option<(u128, u128)>)');
|
||||
// same example as above
|
||||
const tt9 = registry.createType('(u32, (u32, u64), Vec<u8>, Vec<(u32, u64)>, [u8;32], [u128;32])');
|
||||
// tuple with nested fixed
|
||||
const tta = registry.createType('([u8;32], [u16;5])');
|
||||
|
||||
assert(tt1[2].bitLength() && tt2.bitLength() && tt4[3].bitLength() && tt5.isEmpty && tt6[1].toHuman() && tt7.toHuman() && tt8.toHuman() && tt9.toHuman() && tta[1][1].bitLength(), 'All ok');
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// NOTE We are not exporting everything here. These _should_ be enough to use the
|
||||
// actual interfaces from a "create-a-working-coder" perspective. If not, we should
|
||||
// expand with slight care (for instance, Length is really only used internally to
|
||||
// others, so there _should_ not be need for direct use)
|
||||
|
||||
export { BTreeMap, BTreeSet, CodecMap, CodecSet, Compact, DoNotConstruct, Enum, HashMap, Int, Json, Linkage, Map, Option, Range, RangeInclusive, Raw, Result, Set, Struct, Tuple, U8aFixed, UInt, Vec, VecFixed, WrapperKeepOpaque, WrapperOpaque } from '@pezkuwi/types-codec';
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export type { U8aBitLength, UIntBitLength } from '@pezkuwi/types-codec/types';
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { TypeDefInfo } from '@pezkuwi/types-create';
|
||||
|
||||
import { createClass, getTypeClass, TypeRegistry } from './index.js';
|
||||
|
||||
describe('createClass', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('should memoize from strings', (): void => {
|
||||
const a = createClass(registry, 'BabeWeight');
|
||||
const b = createClass(registry, 'BabeWeight');
|
||||
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
|
||||
it('should return equivalents for Bytes & Vec<u8>', (): void => {
|
||||
const A = createClass(registry, 'Vec<u8>');
|
||||
const B = createClass(registry, 'Bytes');
|
||||
|
||||
expect(new A(registry) instanceof B).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTypeClass', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('warns on invalid types', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
const typeDef = { info: TypeDefInfo.Plain, type: 'ABC' };
|
||||
|
||||
try {
|
||||
getTypeClass(registry, typeDef);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'Unable to resolve type ABC, it will fail on construction'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Codec, CodecClass, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { DetectCodec } from '../types/index.js';
|
||||
|
||||
import { createClassUnsafe } from '@pezkuwi/types-create';
|
||||
|
||||
export function createClass<T extends Codec = Codec, K extends string = string> (registry: Registry, type: K): CodecClass<DetectCodec<T, K>> {
|
||||
return createClassUnsafe(registry, type);
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import type { CodecSet } from '@pezkuwi/types-codec';
|
||||
|
||||
import { Int } from '@pezkuwi/types-codec';
|
||||
|
||||
import { createClass, TypeRegistry } from './index.js';
|
||||
|
||||
describe('createType', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('allows creation of a H256 (with proper toRawType)', (): void => {
|
||||
expect(
|
||||
registry.createType('H256').toRawType()
|
||||
).toEqual('H256');
|
||||
expect(
|
||||
registry.createType('Hash').toRawType()
|
||||
).toEqual('H256');
|
||||
});
|
||||
|
||||
it('allows creation of a Fixed64 (with proper toRawType & instance)', (): void => {
|
||||
const f64 = registry.createType('Fixed64');
|
||||
|
||||
expect(f64.toRawType()).toEqual('Fixed64');
|
||||
expect(f64.bitLength()).toEqual(64);
|
||||
expect(f64.isUnsigned).toBe(false);
|
||||
expect(f64 instanceof Int).toBe(true);
|
||||
});
|
||||
|
||||
it('allows creation of a Struct', (): void => {
|
||||
const raw = '{"balance":"Balance","index":"u32"}';
|
||||
const struct = registry.createTypeUnsafe(raw, [{
|
||||
balance: 1234,
|
||||
index: '0x10'
|
||||
}]);
|
||||
|
||||
expect(struct.toJSON()).toEqual({
|
||||
balance: 1234, // '0x000000000000000000000000000004d2',
|
||||
index: 16
|
||||
});
|
||||
expect(struct.toRawType()).toEqual(raw);
|
||||
});
|
||||
|
||||
it('allows creation of a BTreeMap', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('BTreeMap<Text,u32>', ['0x041c62617a7a696e6745000000']).toString()
|
||||
).toEqual('{"bazzing":69}');
|
||||
});
|
||||
|
||||
it('allows creation of a BTreeSet', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('BTreeSet<u32>', ['0x1002000000180000001e00000050000000']).toString()
|
||||
).toEqual('[2,24,30,80]');
|
||||
});
|
||||
|
||||
it('allows creation of a Enum (simple)', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('{"_enum": ["A", "B", "C"]}', [1]).toJSON()
|
||||
).toEqual('B');
|
||||
});
|
||||
|
||||
it('allows creation of a Enum (with types)', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('{"_enum": {"A": null, "B": "u32", "C": null} }', [1]).toJSON()
|
||||
).toEqual({ b: 0 });
|
||||
});
|
||||
|
||||
it('allows creation of a Enum (with indexes)', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('{"_enum": {"A": 42, "B": 69, "C": 255} }', [69]).toJSON()
|
||||
).toEqual('B');
|
||||
});
|
||||
|
||||
it('allows creation of a Result', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('Result<u32,Text>', ['0x011064656667']).toJSON()
|
||||
).toEqual({ err: 'defg' });
|
||||
});
|
||||
|
||||
it('allows creation of a Set', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe<CodecSet>('{"_set": { "A": 1, "B": 2, "C": 4, "D": 8, "E": 16, "G": 32, "H": 64, "I": 128 } }', [1 + 4 + 16 + 64]).strings
|
||||
).toEqual(['A', 'C', 'E', 'H']);
|
||||
});
|
||||
|
||||
it('allows creation of a Tuple', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('(Balance,u32)', [[1234, 5678]]).toJSON()
|
||||
).toEqual([
|
||||
1234, // '0x000000000000000000000000000004d2',
|
||||
5678
|
||||
]);
|
||||
});
|
||||
|
||||
it('allows creation for a UInt<bitLength>', (): void => {
|
||||
expect(
|
||||
registry.createType('UInt<2048>').toRawType()
|
||||
).toEqual('u2048');
|
||||
});
|
||||
|
||||
it('fails creation for a UInt<bitLength> where bitLength is not power of 8', (): void => {
|
||||
expect(
|
||||
() => registry.createType('UInt<20>').toRawType()
|
||||
).toThrow(/UInt<20>: Only support for UInt<bitLength>, where length <= 8192 and a power of 8/);
|
||||
});
|
||||
|
||||
it('fails on creation of DoNotConstruct', (): void => {
|
||||
const Clazz = createClass(registry, 'DoNotConstruct<UnknownSomething>');
|
||||
|
||||
expect(
|
||||
() => new Clazz(registry)
|
||||
).toThrow(/Cannot construct unknown type UnknownSomething/);
|
||||
});
|
||||
|
||||
it('allows creation of a [u8; 8]', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('[u8; 8]', [[0x12, 0x00, 0x23, 0x00, 0x45, 0x00, 0x67, 0x00]]).toHex()
|
||||
).toEqual('0x1200230045006700');
|
||||
});
|
||||
|
||||
it('allows creation of a [u16; 4]', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('[u16; 4]', [[0x1200, 0x2300, 0x4500, 0x6700]]).toU8a()
|
||||
).toEqual(new Uint8Array([0x00, 0x12, 0x00, 0x23, 0x00, 0x45, 0x00, 0x67]));
|
||||
});
|
||||
|
||||
describe('isPedantic', (): void => {
|
||||
it('correctly decodes Bytes', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('Bytes', ['0x12345678'], { isPedantic: true }).toHex()
|
||||
).toEqual('0x12345678');
|
||||
});
|
||||
|
||||
it('correctly decodes Bytes (prefixed)', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('Bytes', [new Uint8Array([4 << 2, 0x12, 0x34, 0x56, 0x78])], { isPedantic: true }).toHex()
|
||||
).toEqual('0x12345678');
|
||||
});
|
||||
|
||||
it('correctly decodes Text', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('Text', ['0x70726f7669646572'], { isPedantic: true }).toString()
|
||||
).toEqual('provider');
|
||||
});
|
||||
|
||||
it('correctly decodes Type', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('Type', ['0x5665633c75383e'], { isPedantic: true }).toString()
|
||||
).toEqual('Bytes'); // Vec<u8> -> Bytes
|
||||
});
|
||||
|
||||
it('correctly decodes [u16; 4]', (): void => {
|
||||
expect(
|
||||
registry.createTypeUnsafe('[u16; 4]', ['0x0012002300450067'], { isPedantic: true }).toHex()
|
||||
).toEqual('0x0012002300450067');
|
||||
});
|
||||
});
|
||||
|
||||
describe('instanceof', (): void => {
|
||||
it('instanceof should work (primitive type)', (): void => {
|
||||
const value = registry.createType('Balance', 1234);
|
||||
|
||||
expect(value instanceof registry.createClass('Balance')).toBe(true);
|
||||
});
|
||||
|
||||
it('instanceof should work (srml type)', (): void => {
|
||||
const value = registry.createType('Gas', 1234);
|
||||
const Gas = registry.createClass('Gas');
|
||||
|
||||
expect(value instanceof Gas).toBe(true);
|
||||
});
|
||||
|
||||
it('instanceof should work (complex type)', (): void => {
|
||||
registry.register({
|
||||
TestComplex: {
|
||||
balance: 'Balance',
|
||||
// eslint-disable-next-line sort-keys
|
||||
accountId: 'AccountId',
|
||||
log: '(u64, u32)',
|
||||
// eslint-disable-next-line sort-keys
|
||||
fromSrml: 'Gas'
|
||||
}
|
||||
});
|
||||
|
||||
const value = registry.createType('TestComplex', {
|
||||
accountId: '0x1234567812345678123456781234567812345678123456781234567812345678',
|
||||
balance: 123,
|
||||
fromSrml: 0,
|
||||
log: [456, 789]
|
||||
});
|
||||
|
||||
expect(value instanceof createClass(registry, 'TestComplex')).toBe(true);
|
||||
});
|
||||
|
||||
it('allows for re-registration of a type', (): void => {
|
||||
const balDef = registry.createType('Balance');
|
||||
|
||||
expect(balDef instanceof registry.createClass('Balance')).toBe(true);
|
||||
expect(balDef.bitLength()).toEqual(128);
|
||||
|
||||
registry.register({ Balance: 'u32' });
|
||||
|
||||
const balu32 = registry.createType('Balance');
|
||||
|
||||
expect(balu32 instanceof registry.createClass('Balance')).toBe(true);
|
||||
expect(balu32.bitLength()).toEqual(32);
|
||||
});
|
||||
|
||||
it('allows for re-registration of a type (derived types)', (): void => {
|
||||
registry.clearCache();
|
||||
registry.register({
|
||||
Balance: 'u128',
|
||||
TestComplex: {
|
||||
balance: 'Balance',
|
||||
// eslint-disable-next-line sort-keys
|
||||
accountId: 'AccountId',
|
||||
log: '(u64, u32)',
|
||||
// eslint-disable-next-line sort-keys
|
||||
fromSrml: 'Gas'
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const cmpDef: any = registry.createType('TestComplex');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
|
||||
expect(cmpDef.balance.bitLength()).toEqual(128);
|
||||
|
||||
registry.clearCache();
|
||||
registry.register({
|
||||
Balance: 'u32',
|
||||
TestComplex: {
|
||||
balance: 'Balance',
|
||||
// eslint-disable-next-line sort-keys
|
||||
accountId: 'AccountId',
|
||||
log: '(u64, u32)',
|
||||
// eslint-disable-next-line sort-keys
|
||||
fromSrml: 'Gas'
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const cmpu32: any = registry.createType('TestComplex');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
|
||||
expect(cmpu32.balance.bitLength()).toEqual(32);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Codec, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { DetectCodec } from '../types/index.js';
|
||||
|
||||
import { createTypeUnsafe } from '@pezkuwi/types-create';
|
||||
|
||||
/**
|
||||
* Create an instance of a `type` with a given `params`.
|
||||
* @param type - A recognizable string representing the type to create an
|
||||
* instance from
|
||||
* @param params - The value to instantiate the type with
|
||||
*/
|
||||
export function createType<T extends Codec = Codec, K extends string = string> (registry: Registry, type: K, ...params: unknown[]): DetectCodec<T, K> {
|
||||
return createTypeUnsafe(registry, type, params);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// all external
|
||||
export * from '@pezkuwi/types-create/exports';
|
||||
|
||||
// all local
|
||||
export * from './createClass.js';
|
||||
export * from './createType.js';
|
||||
export * from './lazy.js';
|
||||
export * from './registry.js';
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SiLookupTypeId, SiVariant } from '../interfaces/index.js';
|
||||
import type { PortableRegistry } from '../metadata/index.js';
|
||||
|
||||
import { lazyMethod } from '@pezkuwi/util';
|
||||
|
||||
interface TypeHolder {
|
||||
type: SiLookupTypeId
|
||||
}
|
||||
|
||||
export function lazyVariants <T> (lookup: PortableRegistry, { type }: TypeHolder, getName: (v: SiVariant) => string, creator: (v: SiVariant) => T): Record<string, T> {
|
||||
const result: Record<string, T> = {};
|
||||
const variants = lookup.getSiType(type).def.asVariant.variants;
|
||||
|
||||
for (let i = 0, count = variants.length; i < count; i++) {
|
||||
lazyMethod(result, variants[i], creator, getName, i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
import type { Codec, CodecClass } from '@pezkuwi/types-codec/types';
|
||||
|
||||
import { DoNotConstruct, Struct, Text, U32 } from '@pezkuwi/types-codec';
|
||||
import { isChildClass, u8aToU8a } from '@pezkuwi/util';
|
||||
import { keccakAsU8a } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { TypeRegistry } from './index.js';
|
||||
|
||||
describe('TypeRegistry', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('handles non exist type', (): void => {
|
||||
expect(registry.get('non-exist')).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('throws on non-existent via getOrThrow', (): void => {
|
||||
expect(
|
||||
(): CodecClass<Codec> => registry.getOrThrow('non-exist')
|
||||
).toThrow('type non-exist not found');
|
||||
});
|
||||
|
||||
it('handles non exist type as Unknown (via getOrUnknown)', (): void => {
|
||||
const Type = registry.getOrUnknown('non-exist');
|
||||
|
||||
expect(Type).toBeDefined();
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
expect(isChildClass(DoNotConstruct, Type)).toBe(true);
|
||||
});
|
||||
|
||||
it('can register single type', (): void => {
|
||||
registry.register(Text);
|
||||
expect(registry.get('Text')).toBe(Text);
|
||||
});
|
||||
|
||||
it('can register type with a different name', (): void => {
|
||||
registry.register('TextRenamed', Text);
|
||||
expect(isChildClass(Text, registry.get('TextRenamed'))).toBe(true);
|
||||
});
|
||||
|
||||
describe('object registration', (): void => {
|
||||
it('can register multiple types', (): void => {
|
||||
registry.register({
|
||||
Text,
|
||||
U32Renamed: U32
|
||||
});
|
||||
expect(isChildClass(Text, registry.get('Text'))).toBe(true);
|
||||
expect(isChildClass(U32, registry.get('U32Renamed'))).toBe(true);
|
||||
});
|
||||
|
||||
it('can register recursive types', (): void => {
|
||||
registry.register({
|
||||
Recursive: {
|
||||
next: 'Option<Recursive>'
|
||||
}
|
||||
});
|
||||
|
||||
expect(registry.hasDef('Recursive')).toBe(true);
|
||||
expect(registry.hasClass('Recursive')).toBe(false);
|
||||
|
||||
const Recursive = registry.getOrThrow('Recursive');
|
||||
|
||||
expect(registry.hasClass('Recursive')).toBe(true);
|
||||
|
||||
const last = new Recursive(registry, { next: null });
|
||||
const first = new Recursive(registry, { next: last });
|
||||
|
||||
expect((first as any).next.isSome).toBe(true);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
expect((first as any).next.unwrap().next.isSome).toBe(false);
|
||||
});
|
||||
|
||||
it('can register non-embedded recursive types', (): void => {
|
||||
registry.register({
|
||||
Operation: {
|
||||
data: 'OperationData'
|
||||
},
|
||||
OperationData: {
|
||||
ops: 'Vec<Operation>'
|
||||
},
|
||||
Rule: {
|
||||
data: 'RuleData'
|
||||
},
|
||||
RuleData: {
|
||||
ops: 'Vec<Operation>'
|
||||
}
|
||||
});
|
||||
|
||||
expect(registry.hasDef('Rule')).toBe(true);
|
||||
expect(registry.hasClass('Rule')).toBe(false);
|
||||
|
||||
const Rule = registry.getOrThrow('Rule');
|
||||
|
||||
expect(registry.hasClass('Rule')).toBe(true);
|
||||
|
||||
const instance = new Rule(registry);
|
||||
|
||||
expect(instance.toRawType()).toEqual('{"data":"RuleData"}');
|
||||
});
|
||||
|
||||
it('can register cross-referencing types', (): void => {
|
||||
registry.register({
|
||||
A: {
|
||||
next: 'B'
|
||||
},
|
||||
B: {
|
||||
_enum: {
|
||||
End: null,
|
||||
Other: 'A'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const A = registry.getOrThrow('A');
|
||||
const B = registry.getOrThrow('B');
|
||||
|
||||
expect(registry.hasClass('Recursive')).toBe(true);
|
||||
|
||||
const last = new B(registry, { End: null });
|
||||
const first = new B(registry, { Other: new A(registry, { next: last }) });
|
||||
|
||||
expect((first as any).isOther).toBe(true);
|
||||
});
|
||||
|
||||
it('can create types from string', (): void => {
|
||||
registry.register({
|
||||
U32Renamed: 'u32'
|
||||
});
|
||||
|
||||
const Type = registry.getOrThrow('U32Renamed');
|
||||
|
||||
expect(new Type(registry) instanceof U32).toBe(true);
|
||||
});
|
||||
|
||||
it('can create structs via definition', (): void => {
|
||||
registry.register({
|
||||
SomeStruct: {
|
||||
bar: 'Text',
|
||||
foo: 'u32'
|
||||
}
|
||||
});
|
||||
|
||||
const SomeStruct = registry.getOrThrow('SomeStruct');
|
||||
const struct: any = new SomeStruct(registry, {
|
||||
bar: 'testing',
|
||||
foo: 42
|
||||
});
|
||||
|
||||
expect(struct instanceof Struct).toBe(true);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
expect(struct.foo.toNumber()).toEqual(42);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
expect(struct.bar.toString()).toEqual('testing');
|
||||
});
|
||||
});
|
||||
|
||||
it('hashes via blake2 by default', (): void => {
|
||||
expect(
|
||||
registry.hash(u8aToU8a('abc')).toU8a()
|
||||
).toEqual(
|
||||
new Uint8Array([189, 221, 129, 60, 99, 66, 57, 114, 49, 113, 239, 63, 238, 152, 87, 155, 148, 150, 78, 59, 177, 203, 62, 66, 114, 98, 200, 192, 104, 213, 35, 25])
|
||||
);
|
||||
});
|
||||
|
||||
it('hashes via override hasher', (): void => {
|
||||
registry.setHasher(keccakAsU8a);
|
||||
|
||||
expect(
|
||||
registry.hash(u8aToU8a('test value')).toHex()
|
||||
).toEqual('0x2d07364b5c231c56ce63d49430e085ea3033c750688ba532b24029124c26ca5e');
|
||||
|
||||
registry.setHasher();
|
||||
|
||||
expect(
|
||||
registry.hash(u8aToU8a('abc')).toU8a()
|
||||
).toEqual(
|
||||
new Uint8Array([189, 221, 129, 60, 99, 66, 57, 114, 49, 113, 239, 63, 238, 152, 87, 155, 148, 150, 78, 59, 177, 203, 62, 66, 114, 98, 200, 192, 104, 213, 35, 25])
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,639 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyString, Codec, CodecClass, IU8a, LookupString } from '@pezkuwi/types-codec/types';
|
||||
import type { CreateOptions, TypeDef } from '@pezkuwi/types-create/types';
|
||||
import type { ExtDef } from '../extrinsic/signedExtensions/types.js';
|
||||
import type { ChainProperties, DispatchErrorModule, DispatchErrorModuleU8, DispatchErrorModuleU8a, EventMetadataLatest, Hash, MetadataLatest, SiField, SiLookupTypeId, SiVariant, WeightV1, WeightV2 } from '../interfaces/types.js';
|
||||
import type { CallFunction, CodecHasher, Definitions, DetectCodec, RegisteredTypes, Registry, RegistryError, RegistryTypes } from '../types/index.js';
|
||||
|
||||
import { DoNotConstruct, Json, Raw } from '@pezkuwi/types-codec';
|
||||
import { constructTypeClass, createClassUnsafe, createTypeUnsafe } from '@pezkuwi/types-create';
|
||||
import { assertReturn, formatBalance, isBn, isFunction, isNumber, isString, isU8a, lazyMethod, logger, objectSpread, stringCamelCase, stringify } from '@pezkuwi/util';
|
||||
import { blake2AsU8a } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { expandExtensionTypes, fallbackExtensions, findUnknownExtensions } from '../extrinsic/signedExtensions/index.js';
|
||||
import { GenericEventData } from '../generic/Event.js';
|
||||
import * as baseTypes from '../index.types.js';
|
||||
import * as definitions from '../interfaces/definitions.js';
|
||||
import { createCallFunction } from '../metadata/decorate/extrinsics/index.js';
|
||||
import { decorateConstants, filterCallsSome, filterEventsSome } from '../metadata/decorate/index.js';
|
||||
import { Metadata } from '../metadata/Metadata.js';
|
||||
import { PortableRegistry } from '../metadata/PortableRegistry/index.js';
|
||||
import { lazyVariants } from './lazy.js';
|
||||
|
||||
const DEFAULT_FIRST_CALL_IDX = new Uint8Array(2);
|
||||
|
||||
const l = logger('registry');
|
||||
|
||||
function sortDecimalStrings (a: string, b: string): number {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
}
|
||||
|
||||
function valueToString (v: { toString: () => string }): string {
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
function getFieldArgs (lookup: PortableRegistry, fields: SiField[]): string[] {
|
||||
const count = fields.length;
|
||||
const args = new Array<string>(count);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
args[i] = lookup.getTypeDef(fields[i].type).type;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function clearRecord (record: Record<string, unknown>): void {
|
||||
const keys = Object.keys(record);
|
||||
|
||||
for (let i = 0, count = keys.length; i < count; i++) {
|
||||
delete record[keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
function getVariantStringIdx ({ index }: SiVariant): string {
|
||||
return index.toString();
|
||||
}
|
||||
|
||||
// create error mapping from metadata
|
||||
function injectErrors (_: TypeRegistry, { lookup, pallets }: MetadataLatest, version: number, result: Record<string, Record<string, RegistryError>>): void {
|
||||
clearRecord(result);
|
||||
|
||||
for (let i = 0, count = pallets.length; i < count; i++) {
|
||||
const { errors, index, name } = pallets[i];
|
||||
|
||||
if (errors.isSome) {
|
||||
const sectionName = stringCamelCase(name);
|
||||
|
||||
lazyMethod(result, version >= 12 ? index.toNumber() : i, () =>
|
||||
lazyVariants(lookup, errors.unwrap(), getVariantStringIdx, ({ docs, fields, index, name }: SiVariant): RegistryError => ({
|
||||
args: getFieldArgs(lookup, fields),
|
||||
docs: docs.map(valueToString),
|
||||
fields,
|
||||
index: index.toNumber(),
|
||||
method: name.toString(),
|
||||
name: name.toString(),
|
||||
section: sectionName
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create event classes from metadata
|
||||
function injectEvents (registry: TypeRegistry, { lookup, pallets }: MetadataLatest, version: number, result: Record<string, Record<string, CodecClass<GenericEventData>>>): void {
|
||||
const filtered = pallets.filter(filterEventsSome);
|
||||
|
||||
clearRecord(result);
|
||||
|
||||
for (let i = 0, count = filtered.length; i < count; i++) {
|
||||
const { events, index, name } = filtered[i];
|
||||
|
||||
lazyMethod(result, version >= 12 ? index.toNumber() : i, () =>
|
||||
lazyVariants(lookup, events.unwrap(), getVariantStringIdx, (variant: SiVariant): CodecClass<GenericEventData> => {
|
||||
const meta = registry.createType<EventMetadataLatest>('EventMetadataLatest', objectSpread({}, variant, { args: getFieldArgs(lookup, variant.fields) }));
|
||||
|
||||
return class extends GenericEventData {
|
||||
constructor (registry: Registry, value: Uint8Array) {
|
||||
super(registry, value, meta, stringCamelCase(name), variant.name.toString());
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// create extrinsic mapping from metadata
|
||||
function injectExtrinsics (registry: TypeRegistry, { lookup, pallets }: MetadataLatest, version: number, result: Record<string, Record<string, CallFunction>>, mapping: Record<string, string[]>): void {
|
||||
const filtered = pallets.filter(filterCallsSome);
|
||||
|
||||
clearRecord(result);
|
||||
clearRecord(mapping);
|
||||
|
||||
for (let i = 0, count = filtered.length; i < count; i++) {
|
||||
const { calls, index, name } = filtered[i];
|
||||
const sectionIndex = version >= 12 ? index.toNumber() : i;
|
||||
const sectionName = stringCamelCase(name);
|
||||
const allCalls = calls.unwrap();
|
||||
|
||||
lazyMethod(result, sectionIndex, () =>
|
||||
lazyVariants(lookup, allCalls, getVariantStringIdx, (variant: SiVariant) =>
|
||||
createCallFunction(registry, lookup, variant, sectionName, sectionIndex)
|
||||
)
|
||||
);
|
||||
|
||||
const { path } = registry.lookup.getSiType(allCalls.type);
|
||||
|
||||
// frame_system::pallet::Call / pallet_balances::pallet::Call / pezkuwi_runtime_teyrchains::configuration::pallet::Call /
|
||||
const palletIdx = path.findIndex((v) => v.eq('pallet'));
|
||||
|
||||
if (palletIdx !== -1) {
|
||||
const name = stringCamelCase(
|
||||
path
|
||||
.slice(0, palletIdx)
|
||||
.map((p, i) =>
|
||||
i === 0
|
||||
// frame_system || pallet_balances
|
||||
? p.replace(/^(frame|pallet)_/, '')
|
||||
: p
|
||||
)
|
||||
.join(' ')
|
||||
);
|
||||
|
||||
if (!mapping[name]) {
|
||||
mapping[name] = [sectionName];
|
||||
} else {
|
||||
mapping[name].push(sectionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extract additional properties from the metadata
|
||||
function extractProperties (registry: TypeRegistry, metadata: Metadata): ChainProperties | undefined {
|
||||
const original = registry.getChainProperties();
|
||||
const constants = decorateConstants(registry, metadata.asLatest, metadata.version);
|
||||
const ss58Format = constants['system'] && (constants['system']['sS58Prefix'] || constants['system']['ss58Prefix']);
|
||||
|
||||
if (!ss58Format) {
|
||||
return original;
|
||||
}
|
||||
|
||||
const { isEthereum, tokenDecimals, tokenSymbol } = original || {};
|
||||
|
||||
return registry.createTypeUnsafe<ChainProperties>('ChainProperties', [{ isEthereum, ss58Format, tokenDecimals, tokenSymbol }]);
|
||||
}
|
||||
|
||||
export class TypeRegistry implements Registry {
|
||||
#chainProperties?: ChainProperties;
|
||||
#classes = new Map<string, CodecClass>();
|
||||
#definitions = new Map<string, string>();
|
||||
#firstCallIndex: Uint8Array | null = null;
|
||||
#hasher: (data: Uint8Array) => Uint8Array = blake2AsU8a;
|
||||
#knownTypes: RegisteredTypes = {};
|
||||
#lookup?: PortableRegistry;
|
||||
#metadata?: MetadataLatest;
|
||||
#metadataVersion = 0;
|
||||
#signedExtensions: string[] = fallbackExtensions;
|
||||
#unknownTypes = new Map<string, boolean>();
|
||||
#userExtensions?: ExtDef | undefined;
|
||||
|
||||
readonly #knownDefaults: Map<string, CodecClass>;
|
||||
readonly #knownDefaultsEntries: [string, CodecClass][];
|
||||
readonly #knownDefinitions: Record<string, Definitions>;
|
||||
readonly #metadataCalls: Record<string, Record<string, CallFunction>> = {};
|
||||
readonly #metadataErrors: Record<string, Record<string, RegistryError>> = {};
|
||||
readonly #metadataEvents: Record<string, Record<string, CodecClass<GenericEventData>>> = {};
|
||||
readonly #moduleMap: Record<string, string[]> = {};
|
||||
|
||||
public createdAtHash?: Hash;
|
||||
|
||||
constructor (createdAtHash?: Hash | Uint8Array | string) {
|
||||
this.#knownDefaults = new Map(Object.entries({ Json, Metadata, PortableRegistry, Raw, ...baseTypes }));
|
||||
this.#knownDefaultsEntries = Array.from(this.#knownDefaults.entries());
|
||||
this.#knownDefinitions = definitions;
|
||||
|
||||
const allKnown = Object.values(this.#knownDefinitions);
|
||||
|
||||
for (let i = 0, count = allKnown.length; i < count; i++) {
|
||||
this.register(allKnown[i].types as unknown as RegistryTypes);
|
||||
}
|
||||
|
||||
if (createdAtHash) {
|
||||
this.createdAtHash = this.createType('BlockHash', createdAtHash);
|
||||
}
|
||||
}
|
||||
|
||||
public get chainDecimals (): number[] {
|
||||
if (this.#chainProperties?.tokenDecimals.isSome) {
|
||||
const allDecimals = this.#chainProperties.tokenDecimals.unwrap();
|
||||
|
||||
if (allDecimals.length) {
|
||||
return allDecimals.map((b) => b.toNumber());
|
||||
}
|
||||
}
|
||||
|
||||
return [12];
|
||||
}
|
||||
|
||||
public get chainIsEthereum (): boolean {
|
||||
return this.#chainProperties?.isEthereum.isTrue || false;
|
||||
}
|
||||
|
||||
public get chainSS58 (): number | undefined {
|
||||
return this.#chainProperties?.ss58Format.isSome
|
||||
? this.#chainProperties.ss58Format.unwrap().toNumber()
|
||||
: undefined;
|
||||
}
|
||||
|
||||
public get chainTokens (): string[] {
|
||||
if (this.#chainProperties?.tokenSymbol.isSome) {
|
||||
const allTokens = this.#chainProperties.tokenSymbol.unwrap();
|
||||
|
||||
if (allTokens.length) {
|
||||
return allTokens.map(valueToString);
|
||||
}
|
||||
}
|
||||
|
||||
return [formatBalance.getDefaults().unit];
|
||||
}
|
||||
|
||||
public get firstCallIndex (): Uint8Array {
|
||||
return this.#firstCallIndex || DEFAULT_FIRST_CALL_IDX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns true if the type is in a Compat format
|
||||
*/
|
||||
public isLookupType (value: string): value is LookupString {
|
||||
return /Lookup\d+$/.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates a lookup string from the supplied id
|
||||
*/
|
||||
public createLookupType (lookupId: SiLookupTypeId | number): LookupString {
|
||||
return `Lookup${typeof lookupId === 'number' ? lookupId : lookupId.toNumber()}`;
|
||||
}
|
||||
|
||||
public get knownTypes (): RegisteredTypes {
|
||||
return this.#knownTypes;
|
||||
}
|
||||
|
||||
public get lookup (): PortableRegistry {
|
||||
return assertReturn(this.#lookup, 'PortableRegistry has not been set on this registry');
|
||||
}
|
||||
|
||||
public get metadata (): MetadataLatest {
|
||||
return assertReturn(this.#metadata, 'Metadata has not been set on this registry');
|
||||
}
|
||||
|
||||
public get unknownTypes (): string[] {
|
||||
return [...this.#unknownTypes.keys()];
|
||||
}
|
||||
|
||||
public get signedExtensions (): string[] {
|
||||
return this.#signedExtensions;
|
||||
}
|
||||
|
||||
public clearCache (): void {
|
||||
this.#classes = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* @describe Creates an instance of the class
|
||||
*/
|
||||
public createClass <T extends Codec = Codec, K extends string = string> (type: K): CodecClass<DetectCodec<T, K>> {
|
||||
return createClassUnsafe<DetectCodec<T, K>>(this, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @describe Creates an instance of the class
|
||||
*/
|
||||
public createClassUnsafe <T extends Codec = Codec, K extends string = string> (type: K): CodecClass<T> {
|
||||
return createClassUnsafe(this, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates an instance of a type as registered
|
||||
*/
|
||||
public createType <T extends Codec = Codec, K extends string = string> (type: K, ...params: unknown[]): DetectCodec<T, K> {
|
||||
return createTypeUnsafe(this, type, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates an instance of a type as registered
|
||||
*/
|
||||
public createTypeUnsafe <T extends Codec = Codec, K extends string = string> (type: K, params: unknown[], options?: CreateOptions): T {
|
||||
return createTypeUnsafe(this, type, params, options);
|
||||
}
|
||||
|
||||
// find a specific call
|
||||
public findMetaCall (callIndex: Uint8Array): CallFunction {
|
||||
const [section, method] = [callIndex[0], callIndex[1]];
|
||||
|
||||
return assertReturn(
|
||||
this.#metadataCalls[`${section}`] && this.#metadataCalls[`${section}`][`${method}`],
|
||||
() => `findMetaCall: Unable to find Call with index [${section}, ${method}]/[${callIndex.toString()}]`
|
||||
);
|
||||
}
|
||||
|
||||
// finds an error
|
||||
public findMetaError (errorIndex: Uint8Array | DispatchErrorModule | DispatchErrorModuleU8 | DispatchErrorModuleU8a): RegistryError {
|
||||
const [section, method] = isU8a(errorIndex)
|
||||
? [errorIndex[0], errorIndex[1]]
|
||||
: [
|
||||
errorIndex.index.toNumber(),
|
||||
isU8a(errorIndex.error)
|
||||
? errorIndex.error[0]
|
||||
: errorIndex.error.toNumber()
|
||||
];
|
||||
|
||||
return assertReturn(
|
||||
this.#metadataErrors[`${section}`] && this.#metadataErrors[`${section}`][`${method}`],
|
||||
() => `findMetaError: Unable to find Error with index [${section}, ${method}]/[${errorIndex.toString()}]`
|
||||
);
|
||||
}
|
||||
|
||||
public findMetaEvent (eventIndex: Uint8Array): CodecClass<GenericEventData> {
|
||||
const [section, method] = [eventIndex[0], eventIndex[1]];
|
||||
|
||||
return assertReturn(
|
||||
this.#metadataEvents[`${section}`] && this.#metadataEvents[`${section}`][`${method}`],
|
||||
() => `findMetaEvent: Unable to find Event with index [${section}, ${method}]/[${eventIndex.toString()}]`
|
||||
);
|
||||
}
|
||||
|
||||
public get <T extends Codec = Codec, K extends string = string> (name: K, withUnknown?: boolean, knownTypeDef?: TypeDef): CodecClass<T> | undefined {
|
||||
return this.getUnsafe(name, withUnknown, knownTypeDef);
|
||||
}
|
||||
|
||||
public getUnsafe <T extends Codec = Codec, K extends string = string> (name: K, withUnknown?: boolean, knownTypeDef?: TypeDef): CodecClass<T> | undefined {
|
||||
let Type = this.#classes.get(name) || this.#knownDefaults.get(name);
|
||||
|
||||
// we have not already created the type, attempt it
|
||||
if (!Type) {
|
||||
const definition = this.#definitions.get(name);
|
||||
let BaseType: CodecClass | undefined;
|
||||
|
||||
// we have a definition, so create the class now (lazily)
|
||||
if (definition) {
|
||||
BaseType = createClassUnsafe(this, definition);
|
||||
} else if (knownTypeDef) {
|
||||
BaseType = constructTypeClass(this, knownTypeDef);
|
||||
} else if (withUnknown) {
|
||||
l.warn(`Unable to resolve type ${name}, it will fail on construction`);
|
||||
|
||||
this.#unknownTypes.set(name, true);
|
||||
|
||||
BaseType = DoNotConstruct.with(name);
|
||||
}
|
||||
|
||||
if (BaseType) {
|
||||
// NOTE If we didn't extend here, we would have strange artifacts. An example is
|
||||
// Balance, with this, new Balance() instanceof u128 is true, but Balance !== u128
|
||||
// Additionally, we now pass through the registry, which is a link to ourselves
|
||||
Type = class extends BaseType {};
|
||||
|
||||
this.#classes.set(name, Type);
|
||||
|
||||
// In the case of lookups, we also want to store the actual class against
|
||||
// the lookup name, instad of having to traverse again
|
||||
if (knownTypeDef && isNumber(knownTypeDef.lookupIndex)) {
|
||||
this.#classes.set(this.createLookupType(knownTypeDef.lookupIndex), Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Type as unknown as CodecClass<T>;
|
||||
}
|
||||
|
||||
public getChainProperties (): ChainProperties | undefined {
|
||||
return this.#chainProperties;
|
||||
}
|
||||
|
||||
public getClassName (Type: CodecClass): string | undefined {
|
||||
// we cannot rely on export order (anymore, since babel/core 7.15.8), so in the case of
|
||||
// items such as u32 & U32, we get the lowercase versions here... not quite as optimal
|
||||
// (previously this used to be a simple find & return)
|
||||
const names: string[] = [];
|
||||
|
||||
for (const [name, Clazz] of this.#knownDefaultsEntries) {
|
||||
if (Type === Clazz) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, Clazz] of this.#classes.entries()) {
|
||||
if (Type === Clazz) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
return names.length
|
||||
// both sort and reverse are done in-place
|
||||
// ['U32', 'u32'] -> ['u32', 'U32']
|
||||
? names.sort().reverse()[0]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
public getDefinition (typeName: string): string | undefined {
|
||||
return this.#definitions.get(typeName);
|
||||
}
|
||||
|
||||
public getModuleInstances (specName: AnyString, moduleName: string): string[] | undefined {
|
||||
return this.#knownTypes?.typesBundle?.spec?.[specName.toString()]?.instances?.[moduleName] || this.#moduleMap[moduleName];
|
||||
}
|
||||
|
||||
public getOrThrow <T extends Codec = Codec, K extends string = string, R = DetectCodec<T, K>> (name: K): CodecClass<R> {
|
||||
const Clazz = this.get<T, K>(name);
|
||||
|
||||
if (!Clazz) {
|
||||
throw new Error(`type ${name} not found`);
|
||||
}
|
||||
|
||||
return Clazz as unknown as CodecClass<R>;
|
||||
}
|
||||
|
||||
public getOrUnknown <T extends Codec = Codec, K extends string = string, R = DetectCodec<T, K>> (name: K): CodecClass<R> {
|
||||
return this.get<T, K>(name, true) as unknown as CodecClass<R>;
|
||||
}
|
||||
|
||||
// Only used in extrinsic version 5
|
||||
public getTransactionExtensionVersion (): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public getSignedExtensionExtra (): Record<string, string> {
|
||||
return expandExtensionTypes(this.#signedExtensions, 'payload', this.#userExtensions);
|
||||
}
|
||||
|
||||
public getSignedExtensionTypes (): Record<string, string> {
|
||||
return expandExtensionTypes(this.#signedExtensions, 'extrinsic', this.#userExtensions);
|
||||
}
|
||||
|
||||
public hasClass (name: string): boolean {
|
||||
return this.#classes.has(name) || !!this.#knownDefaults.has(name);
|
||||
}
|
||||
|
||||
public hasDef (name: string): boolean {
|
||||
return this.#definitions.has(name);
|
||||
}
|
||||
|
||||
public hasType (name: string): boolean {
|
||||
return !this.#unknownTypes.get(name) && (this.hasClass(name) || this.hasDef(name));
|
||||
}
|
||||
|
||||
public hash (data: Uint8Array): IU8a {
|
||||
return this.createType('CodecHash', this.#hasher(data));
|
||||
}
|
||||
|
||||
public register (type: CodecClass | RegistryTypes): void;
|
||||
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
public register (name: string, type: CodecClass): void;
|
||||
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
public register (arg1: string | CodecClass | RegistryTypes, arg2?: CodecClass): void {
|
||||
// NOTE Constructors appear as functions here
|
||||
if (isFunction(arg1)) {
|
||||
this.#classes.set(arg1.name, arg1);
|
||||
} else if (isString(arg1)) {
|
||||
if (!isFunction(arg2)) {
|
||||
throw new Error(`Expected class definition passed to '${arg1}' registration`);
|
||||
} else if (arg1 === arg2.toString()) {
|
||||
throw new Error(`Unable to register circular ${arg1} === ${arg1}`);
|
||||
}
|
||||
|
||||
this.#classes.set(arg1, arg2);
|
||||
} else {
|
||||
this.#registerObject(arg1);
|
||||
}
|
||||
}
|
||||
|
||||
#registerObject = (obj: RegistryTypes): void => {
|
||||
const entries = Object.entries(obj);
|
||||
|
||||
for (let e = 0, count = entries.length; e < count; e++) {
|
||||
const [name, type] = entries[e];
|
||||
|
||||
if (isFunction(type)) {
|
||||
// This _looks_ a bit funny, but `typeof Clazz === 'function'
|
||||
this.#classes.set(name, type);
|
||||
} else {
|
||||
const def = isString(type)
|
||||
? type
|
||||
: stringify(type);
|
||||
|
||||
if (name === def) {
|
||||
throw new Error(`Unable to register circular ${name} === ${def}`);
|
||||
}
|
||||
|
||||
// we already have this type, remove the classes registered for it
|
||||
if (this.#classes.has(name)) {
|
||||
this.#classes.delete(name);
|
||||
}
|
||||
|
||||
this.#definitions.set(name, def);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// sets the chain properties
|
||||
public setChainProperties (properties?: ChainProperties): void {
|
||||
if (properties) {
|
||||
this.#chainProperties = properties;
|
||||
}
|
||||
}
|
||||
|
||||
setHasher (hasher?: CodecHasher | null): void {
|
||||
this.#hasher = hasher || blake2AsU8a;
|
||||
}
|
||||
|
||||
setKnownTypes (knownTypes: RegisteredTypes): void {
|
||||
this.#knownTypes = knownTypes;
|
||||
}
|
||||
|
||||
setLookup (lookup: PortableRegistry): void {
|
||||
this.#lookup = lookup;
|
||||
|
||||
// register all applicable types found
|
||||
lookup.register();
|
||||
}
|
||||
|
||||
// register alias types alongside the portable/lookup setup
|
||||
// (we don't combine this into setLookup since that would/could
|
||||
// affect stand-along lookups, such as ABIs which don't have
|
||||
// actual on-chain metadata)
|
||||
#registerLookup = (lookup: PortableRegistry): void => {
|
||||
// attach the lookup before we register any types
|
||||
this.setLookup(lookup);
|
||||
|
||||
// we detect based on runtime configuration
|
||||
let Weight: string | null = null;
|
||||
|
||||
if (this.hasType('PezspWeightsWeightV2Weight')) {
|
||||
// detection for WeightV2 type based on latest naming
|
||||
const weightv2 = this.createType<WeightV2>('PezspWeightsWeightV2Weight');
|
||||
|
||||
Weight = weightv2.refTime && weightv2.proofSize
|
||||
// with both refTime & proofSize we use as-is (WeightV2)
|
||||
? 'PezspWeightsWeightV2Weight'
|
||||
// fallback to WeightV1 (WeightV1.5 is a struct, single field)
|
||||
: 'WeightV1';
|
||||
} else if (!isBn(this.createType<WeightV1>('Weight'))) {
|
||||
// where we have an already-supplied BN override, we don't clobber
|
||||
// it with our detected value (This protects against pre-defines
|
||||
// where Weight may be aliassed to WeightV0, e.g. in early Zagros chains)
|
||||
Weight = 'WeightV1';
|
||||
}
|
||||
|
||||
if (Weight) {
|
||||
// we have detected a version, adjust the definition
|
||||
this.register({ Weight });
|
||||
}
|
||||
};
|
||||
|
||||
// sets the metadata
|
||||
public setMetadata (metadata: Metadata, signedExtensions?: string[], userExtensions?: ExtDef, noInitWarn?: boolean): void {
|
||||
this.#metadata = metadata.asLatest;
|
||||
this.#metadataVersion = metadata.version;
|
||||
this.#firstCallIndex = null;
|
||||
|
||||
// attach the lookup at this point and register relevant types (before injecting)
|
||||
this.#registerLookup(this.#metadata.lookup);
|
||||
|
||||
injectExtrinsics(this, this.#metadata, this.#metadataVersion, this.#metadataCalls, this.#moduleMap);
|
||||
injectErrors(this, this.#metadata, this.#metadataVersion, this.#metadataErrors);
|
||||
injectEvents(this, this.#metadata, this.#metadataVersion, this.#metadataEvents);
|
||||
|
||||
// set the default call index (the lowest section, the lowest method)
|
||||
// in most chains this should be 0,0
|
||||
const [defSection] = Object
|
||||
.keys(this.#metadataCalls)
|
||||
.sort(sortDecimalStrings);
|
||||
|
||||
if (defSection) {
|
||||
const [defMethod] = Object
|
||||
.keys(this.#metadataCalls[defSection])
|
||||
.sort(sortDecimalStrings);
|
||||
|
||||
if (defMethod) {
|
||||
this.#firstCallIndex = new Uint8Array([parseInt(defSection, 10), parseInt(defMethod, 10)]);
|
||||
}
|
||||
}
|
||||
|
||||
// setup the available extensions
|
||||
this.setSignedExtensions(
|
||||
signedExtensions || (
|
||||
this.#metadata.extrinsic.versions.length > 0 && this.#metadata.extrinsic.versions.every((value) => value > 0)
|
||||
// FIXME Use the extension and their injected types
|
||||
? this.#metadata.extrinsic.transactionExtensions.map(({ identifier }) => identifier.toString())
|
||||
: fallbackExtensions
|
||||
),
|
||||
userExtensions,
|
||||
noInitWarn
|
||||
);
|
||||
|
||||
// setup the chain properties with format overrides
|
||||
this.setChainProperties(
|
||||
extractProperties(this, metadata)
|
||||
);
|
||||
}
|
||||
|
||||
// sets the available signed extensions
|
||||
setSignedExtensions (signedExtensions: string[] = fallbackExtensions, userExtensions?: ExtDef, noInitWarn?: boolean): void {
|
||||
this.#signedExtensions = signedExtensions;
|
||||
this.#userExtensions = userExtensions;
|
||||
|
||||
if (!noInitWarn) {
|
||||
const unknown = findUnknownExtensions(this.#signedExtensions, this.#userExtensions);
|
||||
|
||||
if (unknown.length) {
|
||||
l.warn(`Unknown signed extensions ${unknown.join(', ')} found, treating them as no-effect`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// all types
|
||||
export type { CodecCreateOptions as CreateOptions } from '@pezkuwi/types-codec/types';
|
||||
export type { TypeDef } from '@pezkuwi/types-create/types';
|
||||
|
||||
// all enums
|
||||
export { TypeDefInfo } from '@pezkuwi/types-create';
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
import type { GenericEthereumAccountId as AccountId } from './AccountId.js';
|
||||
|
||||
import { Raw } from '@pezkuwi/types-codec';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
|
||||
describe('EthereumAccountId', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
describe('defaults', (): void => {
|
||||
const id = registry.createType('EthereumAccountId');
|
||||
|
||||
it('has a 20-byte length', (): void => {
|
||||
expect(id).toHaveLength(20);
|
||||
});
|
||||
|
||||
it('is empty by default', (): void => {
|
||||
expect(id.isEmpty).toBe(true);
|
||||
});
|
||||
|
||||
it('equals the empty address', (): void => {
|
||||
expect(id.eq('0x0000000000000000000000000000000000000000')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decoding', (): void => {
|
||||
const testDecode = (type: string, input: Uint8Array | string | AccountId, expected: string): void =>
|
||||
it(`can decode from ${type}`, (): void => {
|
||||
const a = registry.createType('EthereumAccountId', input);
|
||||
|
||||
expect(a.toString()).toBe(expected);
|
||||
});
|
||||
|
||||
testDecode(
|
||||
'AccountId',
|
||||
registry.createType('EthereumAccountId', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'),
|
||||
'0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'
|
||||
);
|
||||
testDecode('hex', '0x4119b2e6c3cb618f4f0B93ac77f9Beec7ff02887', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
|
||||
testDecode(
|
||||
'Raw',
|
||||
new Raw(registry, [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2
|
||||
]),
|
||||
'0x0102030405060708010201020304050607080102'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array',
|
||||
Uint8Array.from([
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2
|
||||
]),
|
||||
'0x0102030405060708010201020304050607080102'
|
||||
);
|
||||
});
|
||||
|
||||
describe('encoding', (): void => {
|
||||
const testEncode = (to: 'toHex' | 'toJSON' | 'toString' | 'toU8a', expected: Uint8Array | string, input = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'): void =>
|
||||
it(`can encode ${to}`, (): void => {
|
||||
const a = registry.createType('EthereumAccountId', input);
|
||||
|
||||
expect(a[to]()).toEqual(expected);
|
||||
});
|
||||
|
||||
testEncode('toHex', '0x4119b2e6c3cb618f4f0b93ac77f9beec7ff02887');
|
||||
testEncode('toJSON', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
|
||||
testEncode('toString', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
|
||||
testEncode('toString', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000');
|
||||
testEncode('toU8a', Uint8Array.from([
|
||||
0x41, 0x19, 0xb2, 0xe6, 0xc3, 0xcb, 0x61, 0x8f, 0x4f, 0x0b,
|
||||
0x93, 0xac, 0x77, 0xf9, 0xbe, 0xec, 0x7f, 0xf0, 0x28, 0x87
|
||||
]));
|
||||
|
||||
it('decodes to a non-empty value', (): void => {
|
||||
expect(registry.createType('EthereumAccountId', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887').isEmpty).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyString, AnyU8a, Registry } from '@pezkuwi/types-codec/types';
|
||||
|
||||
import { U8aFixed } from '@pezkuwi/types-codec';
|
||||
import { hexToU8a, isHex, isString, isU8a, u8aToU8a } from '@pezkuwi/util';
|
||||
import { ethereumEncode, isEthereumAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
/** @internal */
|
||||
function decodeAccountId (value: AnyU8a | AnyString): AnyU8a {
|
||||
if (isU8a(value) || Array.isArray(value)) {
|
||||
return u8aToU8a(value);
|
||||
} else if (isHex(value) || isEthereumAddress(value.toString())) {
|
||||
return hexToU8a(value.toString());
|
||||
} else if (isString(value)) {
|
||||
return u8aToU8a(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericEthereumAccountId
|
||||
* @description
|
||||
* A wrapper around an Ethereum-compatible AccountId. Since we are dealing with
|
||||
* underlying addresses (20 bytes in length), we extend from U8aFixed which is
|
||||
* just a Uint8Array wrapper with a fixed length.
|
||||
*/
|
||||
export class GenericEthereumAccountId extends U8aFixed {
|
||||
constructor (registry: Registry, value: AnyU8a = new Uint8Array()) {
|
||||
super(registry, decodeAccountId(value), 160);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Compares the value of the input to see if there is a match
|
||||
*/
|
||||
public override eq (other?: unknown): boolean {
|
||||
return !!other && super.eq(decodeAccountId(other as AnyU8a));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (): string {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to JSON, typically used for RPC transfers
|
||||
*/
|
||||
public override toJSON (): string {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the value in a best-fit primitive form
|
||||
*/
|
||||
public override toPrimitive (): string {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the string representation of the value
|
||||
*/
|
||||
public override toString (): string {
|
||||
return ethereumEncode(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'AccountId';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import type { GenericAccountIndex as AccountIndex } from '../generic/index.js';
|
||||
import type { GenericEthereumAccountId as AccountId, GenericEthereumLookupSource as Address } from './index.js';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
|
||||
describe('EthereumLookupSource', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
const testDecode = (type: string, input: Address | AccountId | AccountIndex | number[] | Uint8Array, expected: string): void =>
|
||||
it(`can decode from ${type}`, (): void => {
|
||||
const a = registry.createType('EthereumLookupSource', input);
|
||||
|
||||
expect(a.toString()).toBe(expected);
|
||||
});
|
||||
|
||||
describe('decoding', (): void => {
|
||||
testDecode(
|
||||
'Address',
|
||||
registry.createType('EthereumLookupSource', '0x00a329c0648769a73afac7f9381e08fb43dbea72'),
|
||||
'0x00a329c0648769A73afAc7F9381E08FB43dBEA72'
|
||||
);
|
||||
testDecode(
|
||||
'AccountId',
|
||||
registry.createType('EthereumAccountId', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'),
|
||||
'0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'
|
||||
);
|
||||
testDecode(
|
||||
'AccountIndex (mixed prefixes)',
|
||||
registry.createType('EthereumLookupSource', '118r'),
|
||||
// NOTE Expected address here is encoded with prefix 42, input above with 1
|
||||
'25GUyv'
|
||||
);
|
||||
testDecode(
|
||||
'AccountIndex (hex)',
|
||||
registry.createType('AccountIndex', '0x0100'),
|
||||
'25GUyv'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array (with prefix 255)',
|
||||
Uint8Array.from([
|
||||
255,
|
||||
0x41, 0x19, 0xb2, 0xe6, 0xc3, 0xcb, 0x61, 0x8f, 0x4f, 0x0b,
|
||||
0x93, 0xac, 0x77, 0xf9, 0xbe, 0xec, 0x7f, 0xf0, 0x28, 0x87
|
||||
]),
|
||||
'0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array (with prefix 1 byte)',
|
||||
Uint8Array.from([1]),
|
||||
'F7NZ'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array (with prefix 2 bytes)',
|
||||
Uint8Array.from([0xfc, 0, 1]),
|
||||
'25GUyv'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array (with prefix 4 bytes)',
|
||||
Uint8Array.from([0xfd, 17, 18, 19, 20]),
|
||||
'Mwz15xP2'
|
||||
);
|
||||
});
|
||||
|
||||
describe('encoding', (): void => {
|
||||
const testEncode = (to: 'toHex' | 'toString' | 'toU8a', expected: string | Uint8Array): void =>
|
||||
it(`can encode ${to}`, (): void => {
|
||||
const a = registry.createType('EthereumLookupSource', '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
|
||||
|
||||
expect(a[to]()).toEqual(expected);
|
||||
});
|
||||
|
||||
testEncode(
|
||||
'toHex',
|
||||
'0xff4119b2e6c3cb618f4f0b93ac77f9beec7ff02887'
|
||||
);
|
||||
testEncode(
|
||||
'toString',
|
||||
'0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887'
|
||||
);
|
||||
testEncode(
|
||||
'toU8a',
|
||||
Uint8Array.from([
|
||||
255,
|
||||
0x41, 0x19, 0xb2, 0xe6, 0xc3, 0xcb, 0x61, 0x8f, 0x4f, 0x0b,
|
||||
0x93, 0xac, 0x77, 0xf9, 0xbe, 0xec, 0x7f, 0xf0, 0x28, 0x87
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
describe('utility', (): void => {
|
||||
it('equals on AccountId', (): void => {
|
||||
const addr = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';
|
||||
|
||||
expect(registry.createType('EthereumLookupSource', addr).eq(addr)).toBe(true);
|
||||
});
|
||||
|
||||
it('equals on AccountIndex', (): void => {
|
||||
// see the test below - these are equivalent (with different prefix encoding)
|
||||
expect(registry.createType('EthereumLookupSource', '118r').eq('25GUyv')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
import { AbstractBase } from '@pezkuwi/types-codec';
|
||||
import { isBigInt, isBn, isHex, isNumber, isU8a, u8aConcat, u8aToBn, u8aToHex, u8aToU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { GenericAccountIndex } from '../generic/AccountIndex.js';
|
||||
import { GenericEthereumAccountId } from './AccountId.js';
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
type AnyAddress = bigint | BN | GenericEthereumLookupSource | GenericEthereumAccountId | GenericAccountIndex | number[] | Uint8Array | number | string;
|
||||
|
||||
export const ACCOUNT_ID_PREFIX = new Uint8Array([0xff]);
|
||||
|
||||
/** @internal */
|
||||
function decodeString (registry: Registry, value: string): GenericEthereumAccountId | GenericAccountIndex {
|
||||
const decoded = decodeAddress(value);
|
||||
|
||||
return decoded.length === 20
|
||||
? registry.createTypeUnsafe('EthereumAccountId', [decoded])
|
||||
: registry.createTypeUnsafe('AccountIndex', [u8aToBn(decoded)]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeU8a (registry: Registry, value: Uint8Array): GenericEthereumAccountId | GenericAccountIndex {
|
||||
// This allows us to instantiate an address with a raw publicKey. Do this first before
|
||||
// we checking the first byte, otherwise we may split an already-existent valid address
|
||||
if (value.length === 20) {
|
||||
return registry.createTypeUnsafe('EthereumAccountId', [value]);
|
||||
} else if (value[0] === 0xff) {
|
||||
return registry.createTypeUnsafe('EthereumAccountId', [value.subarray(1)]);
|
||||
}
|
||||
|
||||
const [offset, length] = GenericAccountIndex.readLength(value);
|
||||
|
||||
return registry.createTypeUnsafe('AccountIndex', [u8aToBn(value.subarray(offset, offset + length))]);
|
||||
}
|
||||
|
||||
function decodeAddressOrIndex (registry: Registry, value: AnyAddress): GenericEthereumAccountId | GenericAccountIndex {
|
||||
return value instanceof GenericEthereumLookupSource
|
||||
? value.inner
|
||||
: value instanceof GenericEthereumAccountId || value instanceof GenericAccountIndex
|
||||
? value
|
||||
: isU8a(value) || Array.isArray(value) || isHex(value)
|
||||
? decodeU8a(registry, u8aToU8a(value))
|
||||
: isBn(value) || isNumber(value) || isBigInt(value)
|
||||
? registry.createTypeUnsafe('AccountIndex', [value])
|
||||
: decodeString(registry, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericEthereumLookupSource
|
||||
* @description
|
||||
* A wrapper around an EthereumAccountId and/or AccountIndex that is encoded with a prefix.
|
||||
* Since we are dealing with underlying publicKeys (or shorter encoded addresses),
|
||||
* we extend from Base with an AccountId/AccountIndex wrapper. Basically the Address
|
||||
* is encoded as `[ <prefix-byte>, ...publicKey/...bytes ]` as per spec
|
||||
*/
|
||||
export class GenericEthereumLookupSource extends AbstractBase<GenericEthereumAccountId | GenericAccountIndex> {
|
||||
constructor (registry: Registry, value: AnyAddress = new Uint8Array()) {
|
||||
super(registry, decodeAddressOrIndex(registry, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the value when encoded as a Uint8Array
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
const rawLength = this._rawLength;
|
||||
|
||||
return rawLength + (
|
||||
// for 1 byte AccountIndexes, we are not adding a specific prefix
|
||||
rawLength > 1
|
||||
? 1
|
||||
: 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the raw value, either AccountIndex or AccountId
|
||||
*/
|
||||
protected get _rawLength (): number {
|
||||
return this.inner instanceof GenericAccountIndex
|
||||
? GenericAccountIndex.calcLength(this.inner)
|
||||
: this.inner.encodedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a hex string representation of the value
|
||||
*/
|
||||
public override toHex (): HexString {
|
||||
return u8aToHex(this.toU8a());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'Address';
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encodes the value as a Uint8Array as per the SCALE specifications
|
||||
* @param isBare true when the value has none of the type-specific prefixes (internal)
|
||||
*/
|
||||
public override toU8a (isBare?: boolean): Uint8Array {
|
||||
const encoded = this.inner.toU8a().subarray(0, this._rawLength);
|
||||
|
||||
return isBare
|
||||
? encoded
|
||||
: u8aConcat(
|
||||
this.inner instanceof GenericAccountIndex
|
||||
? GenericAccountIndex.writeLength(encoded)
|
||||
: ACCOUNT_ID_PREFIX,
|
||||
encoded
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { GenericEthereumAccountId } from './AccountId.js';
|
||||
export { GenericEthereumLookupSource } from './LookupSource.js';
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import rpcMetadata from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { Metadata } from '../metadata/index.js';
|
||||
import { fallbackExtensions } from './signedExtensions/index.js';
|
||||
import { GenericExtrinsic as Extrinsic } from './index.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
describe('Extrinsic', (): void => {
|
||||
describe('V4', (): void => {
|
||||
it('decodes an actual transaction', (): void => {
|
||||
registry.setSignedExtensions(fallbackExtensions);
|
||||
|
||||
const extrinsic = new Extrinsic(
|
||||
registry,
|
||||
'0x' +
|
||||
'5d02' + // length
|
||||
'84' + // V4, signing bit set
|
||||
'00' + // MultiAddress, AccountId of sender follows
|
||||
'fcc4910cb536b4333db4bccb40e2cf6427b4766518e754b91e70c97e4a87dbb3' + // sender
|
||||
'00' + // multisig, type ed25519
|
||||
'd99ffe3e610ad234e1414bda5831395a6df9098bf80b01561ce89a5065ae89d5' + // sig first 32
|
||||
'c10e1619c6c99131b0bea4fb73ef04d07c07770e2ae9df5c325c331769ccb300' + // sig last 32
|
||||
'a90b' + // mortal era
|
||||
'1101' + // nonce, compact 68
|
||||
'0700ac23fc06' + // tip, 0.03 KSM
|
||||
'0600' + // balances.transferAllowDeath (on Zagros this was 0400, changed here to match metadata)
|
||||
'00' + // MultiAddress, AccountId of recipient follows
|
||||
'495e1e506f266418af07fa0c5c108dd436f2faa59fe7d9e54403779f5bbd7718' + // recipient
|
||||
'0bc01eb1fc185f' // value, 104.560 KSM
|
||||
);
|
||||
|
||||
expect(extrinsic.era.toHuman()).toEqual({ MortalEra: { period: '1,024', phase: '186' } });
|
||||
expect(extrinsic.nonce.toNumber()).toEqual(68);
|
||||
expect(extrinsic.tip.toHuman()).toEqual('30.0000 mUnit');
|
||||
expect(extrinsic.callIndex).toEqual(new Uint8Array([6, 0]));
|
||||
expect(extrinsic.args[0].toHex()).toEqual('0x00495e1e506f266418af07fa0c5c108dd436f2faa59fe7d9e54403779f5bbd7718');
|
||||
expect(extrinsic.args[1].toHuman()).toEqual('104,560,923,320,000'); // ('104.5609 Unit');
|
||||
expect(extrinsic.toPrimitive()).toEqual({ method: { args: { dest: { id: '5DiuK2zR4asj2CEh77SKtUgTswTLkD8eiAKrByg5G3wL5w9b' }, value: 104560923320000 }, callIndex: '0x0600' }, signature: { era: { mortalEra: [1024, 186] }, nonce: 68, signature: { ed25519: '0xd99ffe3e610ad234e1414bda5831395a6df9098bf80b01561ce89a5065ae89d5c10e1619c6c99131b0bea4fb73ef04d07c07770e2ae9df5c325c331769ccb300' }, signer: { id: '5Hn8KKEp8qruCGWaN9MEsjTs4FXB4wv9xn7g1RWkNeKKNXCr' }, tip: 30000000000 } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('V5', () => {
|
||||
// Ensure it does not have its registry modified by the fallback extensions.
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
describe('SignedExtrinsic', () => {
|
||||
it('Should error with a signed extrinsic, when the version is passed in', () => {
|
||||
expect(() => new Extrinsic(
|
||||
registry,
|
||||
'0x51028500d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d011e0b7d9438899333c50121f8e10144952d51c3bb8d0ea11dd1f24940d8ff615ad351d95ed9f41f078748ed7cf182864a20b38eebfaef6629433365eb90c0148c007502000000000603008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00a0724e1809',
|
||||
{ version: 5 }
|
||||
)).toThrow('Signed Extrinsics are currently only available for ExtrinsicV4');
|
||||
});
|
||||
|
||||
it('Should error when the version and preamble is not passed in, and its a signed extrinsic', () => {
|
||||
expect(() => new Extrinsic(
|
||||
registry,
|
||||
'0x51028500d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d011e0b7d9438899333c50121f8e10144952d51c3bb8d0ea11dd1f24940d8ff615ad351d95ed9f41f078748ed7cf182864a20b38eebfaef6629433365eb90c0148c007502000000000603008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00a0724e1809'
|
||||
)).toThrow('Signed Extrinsics are currently only available for ExtrinsicV4');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GeneralExtrinsic', () => {
|
||||
it('Should work when the version and preamble is passed in', () => {
|
||||
const extrinsic = new Extrinsic(
|
||||
registry,
|
||||
'0xc44500650000000000060000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d0700e40b5402',
|
||||
{ preamble: 'general', version: 5 }
|
||||
);
|
||||
|
||||
expect(extrinsic.version).toEqual(69);
|
||||
// expect(extrinsic.transactionExtensionVersion.toNumber()).toEqual(0);
|
||||
expect(extrinsic.method.toHuman()).toEqual({ args: { dest: { Id: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY' }, value: '10,000,000,000' }, method: 'transferAllowDeath', section: 'balances' });
|
||||
expect(extrinsic.era.toHuman()).toEqual({ MortalEra: { period: '64', phase: '6' } });
|
||||
expect(extrinsic.tip.toNumber()).toEqual(0);
|
||||
expect(extrinsic.mode.toNumber()).toEqual(0);
|
||||
expect(extrinsic.assetId.toHuman()).toEqual(null);
|
||||
expect(extrinsic.nonce.toNumber()).toEqual(0);
|
||||
});
|
||||
|
||||
it('Should work when there is no version and preamble is passed in', () => {
|
||||
const extrinsic = new Extrinsic(
|
||||
registry,
|
||||
'0xc44500650000000000060000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d0700e40b5402'
|
||||
);
|
||||
|
||||
expect(extrinsic.version).toEqual(69);
|
||||
// expect(extrinsic.transactionExtensionVersion.toNumber()).toEqual(0);
|
||||
expect(extrinsic.method.toHuman()).toEqual({ args: { dest: { Id: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY' }, value: '10,000,000,000' }, method: 'transferAllowDeath', section: 'balances' });
|
||||
expect(extrinsic.era.toHuman()).toEqual({ MortalEra: { period: '64', phase: '6' } });
|
||||
expect(extrinsic.tip.toNumber()).toEqual(0);
|
||||
expect(extrinsic.mode.toNumber()).toEqual(0);
|
||||
expect(extrinsic.assetId.toHuman()).toEqual(null);
|
||||
expect(extrinsic.nonce.toNumber()).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,461 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyJson, AnyTuple, AnyU8a, ArgsDef, IMethod, Inspect, IOption } from '@pezkuwi/types-codec/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { GeneralExtrinsic } from '../index.types.js';
|
||||
import type { EcdsaSignature, Ed25519Signature, ExtrinsicSignatureV5, ExtrinsicUnknown, ExtrinsicV5, Sr25519Signature } from '../interfaces/extrinsics/index.js';
|
||||
import type { FunctionMetadataLatest } from '../interfaces/metadata/index.js';
|
||||
import type { Address, Call, CodecHash, Hash } from '../interfaces/runtime/index.js';
|
||||
import type { MultiLocation } from '../interfaces/types.js';
|
||||
import type { CallBase, ExtrinsicPayloadValue, ICompact, IExtrinsic, IKeyringPair, INumber, Registry, SignatureOptions } from '../types/index.js';
|
||||
import type { GenericExtrinsicEra } from './ExtrinsicEra.js';
|
||||
import type { Preamble } from './types.js';
|
||||
import type { ExtrinsicValueV5 } from './v5/Extrinsic.js';
|
||||
|
||||
import { AbstractBase } from '@pezkuwi/types-codec';
|
||||
import { compactAddLength, compactFromU8a, compactToU8a, isHex, isU8a, objectProperty, objectSpread, u8aConcat, u8aToHex, u8aToU8a } from '@pezkuwi/util';
|
||||
|
||||
import { BARE_EXTRINSIC, BIT_SIGNED, BIT_UNSIGNED, DEFAULT_PREAMBLE, GENERAL_EXTRINSIC, LATEST_EXTRINSIC_VERSION, LOWEST_SUPPORTED_EXTRINSIC_FORMAT_VERSION, TYPE_MASK, VERSION_MASK } from './constants.js';
|
||||
|
||||
interface CreateOptions {
|
||||
version?: number;
|
||||
preamble?: Preamble;
|
||||
}
|
||||
|
||||
// NOTE The following 2 types, as well as the VERSION structure and the latest export
|
||||
// is to be changed with the addition of a new extrinsic version
|
||||
|
||||
type ExtrinsicVx = ExtrinsicV5 | GeneralExtrinsic;
|
||||
type ExtrinsicValue = ExtrinsicValueV5;
|
||||
|
||||
const VERSIONS = [
|
||||
'ExtrinsicUnknown', // v0 is unknown
|
||||
'ExtrinsicUnknown',
|
||||
'ExtrinsicUnknown',
|
||||
'ExtrinsicUnknown',
|
||||
'ExtrinsicV4',
|
||||
'ExtrinsicV5'
|
||||
];
|
||||
|
||||
const PREAMBLE = {
|
||||
bare: 'ExtrinsicV5',
|
||||
general: 'GeneralExtrinsic'
|
||||
};
|
||||
|
||||
const PreambleMask = {
|
||||
bare: BARE_EXTRINSIC,
|
||||
general: GENERAL_EXTRINSIC
|
||||
};
|
||||
|
||||
const preambleUnMask: Record<string, Preamble> = {
|
||||
0: 'bare',
|
||||
// eslint-disable-next-line sort-keys
|
||||
64: 'general'
|
||||
};
|
||||
|
||||
export { LATEST_EXTRINSIC_VERSION };
|
||||
|
||||
/** @internal */
|
||||
function newFromValue (registry: Registry, value: any, version: number, preamble: Preamble): ExtrinsicVx | ExtrinsicUnknown {
|
||||
if (value instanceof GenericExtrinsic) {
|
||||
return value.unwrap();
|
||||
}
|
||||
|
||||
const isSigned = (version & BIT_SIGNED) === BIT_SIGNED;
|
||||
const type = (version & VERSION_MASK) === 5 ? PREAMBLE[preamble] : VERSIONS[version & VERSION_MASK] || VERSIONS[0];
|
||||
|
||||
// we cast here since the VERSION definition is incredibly broad - we don't have a
|
||||
// slice for "only add extrinsic types", and more string definitions become unwieldy
|
||||
return registry.createTypeUnsafe(type, [value, { isSigned, version }]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeExtrinsic (registry: Registry, value?: GenericExtrinsic | ExtrinsicValue | AnyU8a | Call, version: number = LOWEST_SUPPORTED_EXTRINSIC_FORMAT_VERSION, preamble: Preamble = DEFAULT_PREAMBLE): ExtrinsicVx | ExtrinsicUnknown {
|
||||
if (isU8a(value) || Array.isArray(value) || isHex(value)) {
|
||||
return decodeU8a(registry, u8aToU8a(value), version, preamble);
|
||||
} else if (value instanceof registry.createClassUnsafe('Call')) {
|
||||
return newFromValue(registry, { method: value }, version, preamble);
|
||||
}
|
||||
|
||||
return newFromValue(registry, value, version, preamble);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeU8a (registry: Registry, value: Uint8Array, version: number, preamble: Preamble): ExtrinsicVx | ExtrinsicUnknown {
|
||||
if (!value.length) {
|
||||
return newFromValue(registry, new Uint8Array(), version, preamble);
|
||||
}
|
||||
|
||||
const [offset, length] = compactFromU8a(value);
|
||||
const total = offset + length.toNumber();
|
||||
|
||||
if (total > value.length) {
|
||||
throw new Error(`Extrinsic: length less than remainder, expected at least ${total}, found ${value.length}`);
|
||||
}
|
||||
|
||||
const data = value.subarray(offset, total);
|
||||
const unmaskedPreamble = data[0] & TYPE_MASK;
|
||||
|
||||
if (preambleUnMask[`${unmaskedPreamble}`] === 'general') {
|
||||
// NOTE: GeneralExtrinsic needs to have the full data to validate the preamble and version
|
||||
return newFromValue(registry, value, data[0], preambleUnMask[`${unmaskedPreamble}`] || preamble);
|
||||
} else {
|
||||
return newFromValue(registry, data.subarray(1), data[0], preambleUnMask[`${unmaskedPreamble}`] || preamble);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ExtrinsicBase<A extends AnyTuple> extends AbstractBase<ExtrinsicVx | ExtrinsicUnknown> {
|
||||
readonly #preamble: Preamble;
|
||||
|
||||
constructor (registry: Registry, value: ExtrinsicVx | ExtrinsicUnknown, initialU8aLength?: number, preamble?: Preamble) {
|
||||
super(registry, value, initialU8aLength);
|
||||
|
||||
const signKeys = Object.keys(registry.getSignedExtensionTypes());
|
||||
|
||||
if (this.version === 5 && preamble !== 'general') {
|
||||
const getter = (key: string) => (this.inner.signature as unknown as ExtrinsicSignatureV5)[key as 'signer'];
|
||||
|
||||
// This is on the abstract class, ensuring that hasOwnProperty operates
|
||||
// correctly, i.e. it needs to be on the base class exposing it
|
||||
for (let i = 0, count = signKeys.length; i < count; i++) {
|
||||
objectProperty(this, signKeys[i], getter);
|
||||
}
|
||||
}
|
||||
|
||||
const unmaskedPreamble = this.type & TYPE_MASK;
|
||||
|
||||
this.#preamble = preamble || preambleUnMask[`${unmaskedPreamble}`];
|
||||
}
|
||||
|
||||
public isGeneral () {
|
||||
return this.#preamble === 'general';
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The arguments passed to for the call, exposes args so it is compatible with [[Call]]
|
||||
*/
|
||||
public get args (): A {
|
||||
return this.method.args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The argument definitions, compatible with [[Call]]
|
||||
*/
|
||||
public get argsDef (): ArgsDef {
|
||||
return this.method.argsDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The actual `[sectionIndex, methodIndex]` as used in the Call
|
||||
*/
|
||||
public get callIndex (): Uint8Array {
|
||||
return this.method.callIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The actual data for the Call
|
||||
*/
|
||||
public get data (): Uint8Array {
|
||||
return this.method.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The era for this extrinsic
|
||||
*/
|
||||
public get era (): GenericExtrinsicEra {
|
||||
return this.isGeneral()
|
||||
? (this.inner as unknown as GeneralExtrinsic).era
|
||||
: (this.inner.signature as unknown as ExtrinsicSignatureV5).era;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the value when encoded as a Uint8Array
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
return this.toU8a().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` id the extrinsic is signed
|
||||
*/
|
||||
public get isSigned (): boolean {
|
||||
return this.isGeneral()
|
||||
? false
|
||||
: (this.inner.signature as unknown as ExtrinsicSignatureV5).isSigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the actual data, excluding prefix
|
||||
*/
|
||||
public get length (): number {
|
||||
return this.toU8a(true).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[FunctionMetadataLatest]] that describes the extrinsic
|
||||
*/
|
||||
public get meta (): FunctionMetadataLatest {
|
||||
return this.method.meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Call]] this extrinsic wraps
|
||||
*/
|
||||
public get method (): CallBase<A> {
|
||||
return this.inner.method as unknown as CallBase<A>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The nonce for this extrinsic
|
||||
*/
|
||||
public get nonce (): ICompact<INumber> {
|
||||
return this.isGeneral()
|
||||
? (this.inner as unknown as GeneralExtrinsic).nonce
|
||||
: (this.inner.signature as unknown as ExtrinsicSignatureV5).nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The actual [[EcdsaSignature]], [[Ed25519Signature]] or [[Sr25519Signature]]
|
||||
*/
|
||||
public get signature (): EcdsaSignature | Ed25519Signature | Sr25519Signature {
|
||||
if (this.isGeneral()) {
|
||||
throw new Error('Extrinsic: GeneralExtrinsic does not have signature implemented');
|
||||
}
|
||||
|
||||
return (this.inner.signature as unknown as ExtrinsicSignatureV5).signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Address]] that signed
|
||||
*/
|
||||
public get signer (): Address {
|
||||
if (this.isGeneral()) {
|
||||
throw new Error('Extrinsic: GeneralExtrinsic does not have signer implemented');
|
||||
}
|
||||
|
||||
return (this.inner.signature as unknown as ExtrinsicSignatureV5).signer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Forwards compat
|
||||
*/
|
||||
public get tip (): ICompact<INumber> {
|
||||
return this.isGeneral()
|
||||
? (this.inner as unknown as GeneralExtrinsic).tip
|
||||
: (this.inner.signature as unknown as ExtrinsicSignatureV5).tip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Forward compat
|
||||
*/
|
||||
public get assetId (): IOption<INumber | MultiLocation> {
|
||||
return this.isGeneral()
|
||||
? (this.inner as unknown as GeneralExtrinsic).assetId
|
||||
: (this.inner.signature as unknown as ExtrinsicSignatureV5).assetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Forward compat
|
||||
*/
|
||||
public get metadataHash (): IOption<Hash> {
|
||||
return this.isGeneral()
|
||||
? (this.inner as unknown as GeneralExtrinsic).metadataHash
|
||||
: (this.inner.signature as unknown as ExtrinsicSignatureV5).metadataHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Forward compat
|
||||
*/
|
||||
public get mode (): INumber {
|
||||
return this.isGeneral()
|
||||
? (this.inner as unknown as GeneralExtrinsic).mode
|
||||
: (this.inner.signature as unknown as ExtrinsicSignatureV5).mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the raw transaction version (not flagged with signing information)
|
||||
*/
|
||||
public get type (): number {
|
||||
return this.inner.version;
|
||||
}
|
||||
|
||||
public override get inner (): ExtrinsicVx {
|
||||
return this.unwrap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the encoded version flag
|
||||
*/
|
||||
public get version (): number {
|
||||
if (this.type <= LOWEST_SUPPORTED_EXTRINSIC_FORMAT_VERSION) {
|
||||
return this.type | (this.isSigned ? BIT_SIGNED : BIT_UNSIGNED);
|
||||
} else {
|
||||
if (this.isSigned) {
|
||||
throw new Error('Signed Extrinsics are currently only available for ExtrinsicV4');
|
||||
}
|
||||
|
||||
return this.type | (this.isGeneral() ? PreambleMask.general : PreambleMask.bare);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Checks if the source matches this in type
|
||||
*/
|
||||
public is (other: IMethod<AnyTuple>): other is IMethod<A> {
|
||||
return this.method.is(other);
|
||||
}
|
||||
|
||||
public override unwrap (): ExtrinsicVx {
|
||||
return super.unwrap() as ExtrinsicVx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsic
|
||||
* @description
|
||||
* Representation of an Extrinsic in the system. It contains the actual call,
|
||||
* (optional) signature and encodes with an actual length prefix
|
||||
*
|
||||
* {@link https://github.com/pezkuwichain/wiki/blob/master/Extrinsic.md#the-extrinsic-format-for-node}.
|
||||
*
|
||||
* Can be:
|
||||
* - signed, to create a transaction
|
||||
* - left as is, to create an inherent
|
||||
*/
|
||||
export class GenericExtrinsic<A extends AnyTuple = AnyTuple> extends ExtrinsicBase<A> implements IExtrinsic<A> {
|
||||
#hashCache?: CodecHash | undefined;
|
||||
|
||||
static LATEST_EXTRINSIC_VERSION = LATEST_EXTRINSIC_VERSION;
|
||||
|
||||
constructor (registry: Registry, value?: GenericExtrinsic | ExtrinsicValue | AnyU8a | Call, { preamble, version }: CreateOptions = {}) {
|
||||
const versionsLength = registry.metadata.extrinsic.versions.length;
|
||||
|
||||
// TODO: Once ExtrinsicV5 is fully supported update this to use the highest supported verion which is the last item of the array
|
||||
const supportedVersion = versionsLength ? registry.metadata.extrinsic.versions[0] : undefined;
|
||||
|
||||
super(registry, decodeExtrinsic(registry, value, version || supportedVersion, preamble), undefined, preamble);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description returns a hash of the contents
|
||||
*/
|
||||
public override get hash (): CodecHash {
|
||||
if (!this.#hashCache) {
|
||||
this.#hashCache = super.hash;
|
||||
}
|
||||
|
||||
return this.#hashCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Injects an already-generated signature into the extrinsic
|
||||
*/
|
||||
public addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | HexString, payload: ExtrinsicPayloadValue | Uint8Array | HexString): GenericExtrinsic<A> {
|
||||
this.inner.addSignature(signer, signature, payload);
|
||||
this.#hashCache = undefined;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a breakdown of the hex encoding for this Codec
|
||||
*/
|
||||
override inspect (): Inspect {
|
||||
const encoded = u8aConcat(...this.toU8aInner());
|
||||
|
||||
return {
|
||||
inner: this.isSigned
|
||||
? this.inner.inspect().inner
|
||||
: this.inner.method.inspect().inner,
|
||||
outer: [compactToU8a(encoded.length), new Uint8Array([this.version])]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sign the extrinsic with a specific keypair
|
||||
*/
|
||||
public sign (account: IKeyringPair, options: SignatureOptions): GenericExtrinsic<A> {
|
||||
this.inner.sign(account, options);
|
||||
this.#hashCache = undefined;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @describe Adds a fake signature to the extrinsic
|
||||
*/
|
||||
public signFake (signer: Address | Uint8Array | string, options: SignatureOptions): GenericExtrinsic<A> {
|
||||
this.inner.signFake(signer, options);
|
||||
this.#hashCache = undefined;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a hex string representation of the value
|
||||
*/
|
||||
public override toHex (isBare?: boolean): HexString {
|
||||
return u8aToHex(this.toU8a(isBare));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (isExpanded?: boolean, disableAscii?: boolean): AnyJson {
|
||||
return objectSpread<Record<string, AnyJson>>(
|
||||
{},
|
||||
{
|
||||
isSigned: this.isSigned,
|
||||
method: this.method.toHuman(isExpanded, disableAscii)
|
||||
},
|
||||
this.isSigned
|
||||
? {
|
||||
assetId: this.assetId ? this.assetId.toHuman(isExpanded, disableAscii) : null,
|
||||
era: this.era.toHuman(isExpanded, disableAscii),
|
||||
metadataHash: this.metadataHash ? this.metadataHash.toHex() : null,
|
||||
mode: this.mode ? this.mode.toHuman() : null,
|
||||
nonce: this.nonce.toHuman(isExpanded, disableAscii),
|
||||
signature: this.signature.toHex(),
|
||||
signer: this.signer.toHuman(isExpanded, disableAscii),
|
||||
tip: this.tip.toHuman(isExpanded, disableAscii)
|
||||
}
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to JSON, typically used for RPC transfers
|
||||
*/
|
||||
public override toJSON (): string {
|
||||
return this.toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'Extrinsic';
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encodes the value as a Uint8Array as per the SCALE specifications
|
||||
* @param isBare true when the value is not length-prefixed
|
||||
*/
|
||||
public override toU8a (isBare?: boolean): Uint8Array {
|
||||
const encoded = u8aConcat(...this.toU8aInner());
|
||||
|
||||
return isBare
|
||||
? encoded
|
||||
: compactAddLength(encoded);
|
||||
}
|
||||
|
||||
public toU8aInner (): Uint8Array[] {
|
||||
// we do not apply bare to the internal values, rather this only determines out length addition,
|
||||
// where we strip all lengths this creates an extrinsic that cannot be decoded
|
||||
return [
|
||||
new Uint8Array([this.version]),
|
||||
this.inner.toU8a()
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { GenericExtrinsicEra as ExtrinsicEra } from './index.js';
|
||||
|
||||
describe('ExtrinsicEra', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('decodes an Extrinsic Era with immortal', (): void => {
|
||||
const extrinsicEra = new ExtrinsicEra(registry, new Uint8Array([0]));
|
||||
|
||||
expect(extrinsicEra.asImmortalEra).toBeDefined();
|
||||
expect(extrinsicEra.toJSON()).toEqual({ immortalEra: '0x00' });
|
||||
});
|
||||
|
||||
it('decodes an Extrinsic Era from u8 as mortal', (): void => {
|
||||
const extrinsicEra = new ExtrinsicEra(registry, new Uint8Array([78, 156]));
|
||||
|
||||
expect(extrinsicEra.asMortalEra.period.toNumber()).toEqual(32768);
|
||||
expect(extrinsicEra.asMortalEra.phase.toNumber()).toEqual(20000);
|
||||
});
|
||||
|
||||
it('decoded from an existing ExtrinsicEra', (): void => {
|
||||
const extrinsicEra = new ExtrinsicEra(registry, new ExtrinsicEra(registry, new Uint8Array([78, 156])));
|
||||
|
||||
expect(extrinsicEra.asMortalEra.period.toNumber()).toEqual(32768);
|
||||
expect(extrinsicEra.asMortalEra.phase.toNumber()).toEqual(20000);
|
||||
});
|
||||
|
||||
it('encode an Extrinsic Era from Object with blocknumber & period as mortal instance', (): void => {
|
||||
const extrinsicEra = new ExtrinsicEra(registry, { current: 1400, period: 200 });
|
||||
|
||||
expect(extrinsicEra.asMortalEra.period.toNumber()).toEqual(256);
|
||||
expect(extrinsicEra.asMortalEra.phase.toNumber()).toEqual(120);
|
||||
});
|
||||
|
||||
it('serializes and de-serializes from JSON', (): void => {
|
||||
const extrinsicEra = new ExtrinsicEra(registry, new Uint8Array([78, 156]));
|
||||
const u8a = extrinsicEra.toU8a();
|
||||
const json = extrinsicEra.toJSON();
|
||||
|
||||
expect(u8a).toEqual(new Uint8Array([78, 156]));
|
||||
expect(json).toEqual({ mortalEra: '0x4e9c' });
|
||||
expect(new ExtrinsicEra(registry, json).toU8a()).toEqual(u8a);
|
||||
});
|
||||
|
||||
it('creates from an actual valid era', (): void => {
|
||||
const currBlock = 2251519;
|
||||
const mortalEra = new ExtrinsicEra(registry, '0xc503').asMortalEra;
|
||||
|
||||
expect(mortalEra.period.toNumber()).toEqual(64);
|
||||
expect(mortalEra.phase.toNumber()).toEqual(60);
|
||||
expect(mortalEra.birth(currBlock)).toEqual(2251516);
|
||||
expect(mortalEra.death(currBlock)).toEqual(2251580);
|
||||
});
|
||||
|
||||
it('creates for an actual era (2)', (): void => {
|
||||
const mortalEra = new ExtrinsicEra(registry, '0x8502').asMortalEra;
|
||||
|
||||
expect(mortalEra.period.toNumber()).toEqual(64);
|
||||
expect(mortalEra.phase.toNumber()).toEqual(40);
|
||||
});
|
||||
|
||||
it('creates form an actual era (3)', (): void => {
|
||||
const mortalEra = new ExtrinsicEra(registry, '0x6502').asMortalEra;
|
||||
|
||||
expect(mortalEra.period.toNumber()).toEqual(64);
|
||||
expect(mortalEra.phase.toNumber()).toEqual(38);
|
||||
});
|
||||
|
||||
it('creates from an actual era, 100 block hash count', (): void => {
|
||||
const mortalEra = new ExtrinsicEra(registry, '0xd607').asMortalEra;
|
||||
|
||||
expect(mortalEra.period.toNumber()).toEqual(128);
|
||||
expect(mortalEra.phase.toNumber()).toEqual(125);
|
||||
});
|
||||
|
||||
it('creates from a actual 2400 block hash count', (): void => {
|
||||
const mortalEra = new ExtrinsicEra(registry, '0x9be3').asMortalEra;
|
||||
|
||||
expect(mortalEra.period.toNumber()).toEqual(4096);
|
||||
expect(mortalEra.phase.toNumber()).toEqual(3641);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyU8a, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { IExtrinsicEra, INumber } from '../types/index.js';
|
||||
|
||||
import { Enum, Raw, Tuple, U64 } from '@pezkuwi/types-codec';
|
||||
import { bnToBn, formatNumber, hexToU8a, isHex, isObject, isU8a, u8aToBn, u8aToU8a } from '@pezkuwi/util';
|
||||
|
||||
import { IMMORTAL_ERA } from './constants.js';
|
||||
|
||||
type MortalEraValue = [INumber, INumber];
|
||||
|
||||
interface MortalMethod {
|
||||
current: number;
|
||||
period: number;
|
||||
}
|
||||
|
||||
interface MortalEnumDef {
|
||||
MortalEra: string;
|
||||
}
|
||||
|
||||
interface ImmortalEnumDef {
|
||||
ImmortalEra: string;
|
||||
}
|
||||
|
||||
function getTrailingZeros (period: number): number {
|
||||
const binary = period.toString(2);
|
||||
let index = 0;
|
||||
|
||||
while (binary[binary.length - 1 - index] === '0') {
|
||||
index++;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeMortalEra (registry: Registry, value?: MortalMethod | Uint8Array | number[] | string): MortalEraValue {
|
||||
if (isU8a(value) || isHex(value) || Array.isArray(value)) {
|
||||
return decodeMortalU8a(registry, u8aToU8a(value));
|
||||
} else if (!value) {
|
||||
return [new U64(registry), new U64(registry)];
|
||||
} else if (isObject(value)) {
|
||||
return decodeMortalObject(registry, value);
|
||||
}
|
||||
|
||||
throw new Error('Invalid data passed to Mortal era');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeMortalObject (registry: Registry, value: MortalMethod): MortalEraValue {
|
||||
const { current, period } = value;
|
||||
let calPeriod = Math.pow(2, Math.ceil(Math.log2(period)));
|
||||
|
||||
calPeriod = Math.min(Math.max(calPeriod, 4), 1 << 16);
|
||||
|
||||
const phase = current % calPeriod;
|
||||
const quantizeFactor = Math.max(calPeriod >> 12, 1);
|
||||
const quantizedPhase = phase / quantizeFactor * quantizeFactor;
|
||||
|
||||
return [new U64(registry, calPeriod), new U64(registry, quantizedPhase)];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeMortalU8a (registry: Registry, value: Uint8Array): MortalEraValue {
|
||||
if (value.length === 0) {
|
||||
return [new U64(registry), new U64(registry)];
|
||||
}
|
||||
|
||||
const first = u8aToBn(value.subarray(0, 1)).toNumber();
|
||||
const second = u8aToBn(value.subarray(1, 2)).toNumber();
|
||||
const encoded: number = first + (second << 8);
|
||||
const period = 2 << (encoded % (1 << 4));
|
||||
const quantizeFactor = Math.max(period >> 12, 1);
|
||||
const phase = (encoded >> 4) * quantizeFactor;
|
||||
|
||||
if (period < 4 || phase >= period) {
|
||||
throw new Error('Invalid data passed to Mortal era');
|
||||
}
|
||||
|
||||
return [new U64(registry, period), new U64(registry, phase)];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
function decodeExtrinsicEra (value: IExtrinsicEra | MortalMethod | MortalEnumDef | ImmortalEnumDef | Uint8Array | string = new Uint8Array()): Uint8Array | Object | undefined {
|
||||
if (isU8a(value)) {
|
||||
return (!value.length || value[0] === 0)
|
||||
? new Uint8Array([0])
|
||||
: new Uint8Array([1, value[0], value[1]]);
|
||||
} else if (!value) {
|
||||
return new Uint8Array([0]);
|
||||
} else if (value instanceof GenericExtrinsicEra) {
|
||||
return decodeExtrinsicEra(value.toU8a());
|
||||
} else if (isHex(value)) {
|
||||
return decodeExtrinsicEra(hexToU8a(value));
|
||||
} else if (isObject(value)) {
|
||||
const entries = Object.entries(value as MortalEnumDef).map(([k, v]): [string, any] => [k.toLowerCase(), v]);
|
||||
const mortal = entries.find(([k]) => k.toLowerCase() === 'mortalera');
|
||||
const immortal = entries.find(([k]) => k.toLowerCase() === 'immortalera');
|
||||
|
||||
// this is to de-serialize from JSON
|
||||
return mortal
|
||||
? { MortalEra: mortal[1] as string }
|
||||
: immortal
|
||||
? { ImmortalEra: immortal[1] as string }
|
||||
: { MortalEra: value };
|
||||
}
|
||||
|
||||
throw new Error('Invalid data passed to Era');
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ImmortalEra
|
||||
* @description
|
||||
* The ImmortalEra for an extrinsic
|
||||
*/
|
||||
export class ImmortalEra extends Raw {
|
||||
constructor (registry: Registry, _value?: AnyU8a) {
|
||||
// For immortals, we always provide the known value (i.e. treated as a
|
||||
// constant no matter how it is constructed - it is a fixed structure)
|
||||
super(registry, IMMORTAL_ERA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name MortalEra
|
||||
* @description
|
||||
* The MortalEra for an extrinsic, indicating period and phase
|
||||
*/
|
||||
export class MortalEra extends Tuple {
|
||||
constructor (registry: Registry, value?: MortalMethod | Uint8Array | number[] | string) {
|
||||
super(registry, {
|
||||
period: U64,
|
||||
phase: U64
|
||||
}, decodeMortalEra(registry, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encoded length for mortals occupy 2 bytes, different from the actual Tuple since it is encoded. This is a shortcut fro `toU8a().length`
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
return 2 | 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The period of this Mortal wraps as a [[U64]]
|
||||
*/
|
||||
public get period (): INumber {
|
||||
return this[0] as INumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The phase of this Mortal wraps as a [[U64]]
|
||||
*/
|
||||
public get phase (): INumber {
|
||||
return this[1] as INumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (): any {
|
||||
return {
|
||||
period: formatNumber(this.period),
|
||||
phase: formatNumber(this.phase)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a JSON representation of the actual value
|
||||
*/
|
||||
public override toJSON (): any {
|
||||
return this.toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encodes the value as a Uint8Array as per the parity-codec specifications
|
||||
* @param isBare true when the value has none of the type-specific prefixes (internal)
|
||||
* Period and phase are encoded:
|
||||
* - The period of validity from the block hash found in the signing material.
|
||||
* - The phase in the period that this transaction's lifetime begins (and, importantly,
|
||||
* implies which block hash is included in the signature material). If the `period` is
|
||||
* greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
|
||||
* `period` is.
|
||||
*/
|
||||
public override toU8a (_isBare?: boolean): Uint8Array {
|
||||
const period = this.period.toNumber();
|
||||
const encoded = Math.min(
|
||||
15,
|
||||
Math.max(1, getTrailingZeros(period) - 1)
|
||||
) + (
|
||||
(
|
||||
this.phase.toNumber() / Math.max(period >> 12, 1)
|
||||
) << 4
|
||||
);
|
||||
|
||||
return new Uint8Array([
|
||||
encoded & 0xff,
|
||||
encoded >> 8
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the block number of the start of the era whose properties this object describes that `current` belongs to.
|
||||
*/
|
||||
public birth (current: BN | bigint | number | string): number {
|
||||
const phase = this.phase.toNumber();
|
||||
const period = this.period.toNumber();
|
||||
|
||||
// FIXME No toNumber() here
|
||||
return (
|
||||
~~(
|
||||
(
|
||||
Math.max(bnToBn(current).toNumber(), phase) - phase
|
||||
) / period
|
||||
) * period
|
||||
) + phase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the block number of the first block at which the era has ended.
|
||||
*/
|
||||
public death (current: BN | bigint | number | string): number {
|
||||
// FIXME No toNumber() here
|
||||
return this.birth(current) + this.period.toNumber();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicEra
|
||||
* @description
|
||||
* The era for an extrinsic, indicating either a mortal or immortal extrinsic
|
||||
*/
|
||||
export class GenericExtrinsicEra extends Enum implements IExtrinsicEra {
|
||||
constructor (registry: Registry, value?: unknown) {
|
||||
super(registry, {
|
||||
ImmortalEra,
|
||||
MortalEra
|
||||
}, decodeExtrinsicEra(value as string));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Override the encoded length method
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
return this.isImmortalEra
|
||||
? this.asImmortalEra.encodedLength
|
||||
: this.asMortalEra.encodedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the item as a [[ImmortalEra]]
|
||||
*/
|
||||
public get asImmortalEra (): ImmortalEra {
|
||||
if (!this.isImmortalEra) {
|
||||
throw new Error(`Cannot convert '${this.type}' via asImmortalEra`);
|
||||
}
|
||||
|
||||
return this.inner as ImmortalEra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the item as a [[MortalEra]]
|
||||
*/
|
||||
public get asMortalEra (): MortalEra {
|
||||
if (!this.isMortalEra) {
|
||||
throw new Error(`Cannot convert '${this.type}' via asMortalEra`);
|
||||
}
|
||||
|
||||
return this.inner as MortalEra;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` if Immortal
|
||||
*/
|
||||
public get isImmortalEra (): boolean {
|
||||
return this.index === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` if Mortal
|
||||
*/
|
||||
public get isMortalEra (): boolean {
|
||||
return this.index > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encodes the value as a Uint8Array as per the parity-codec specifications
|
||||
* @param isBare true when the value has none of the type-specific prefixes (internal)
|
||||
*/
|
||||
public override toU8a (isBare?: boolean): Uint8Array {
|
||||
return this.isMortalEra
|
||||
? this.asMortalEra.toU8a(isBare)
|
||||
: this.asImmortalEra.toU8a(isBare);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { fallbackExtensions } from './signedExtensions/index.js';
|
||||
import { GenericExtrinsicPayload as ExtrinsicPayload } from './index.js';
|
||||
|
||||
describe('ExtrinsicPayload', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const TEST = {
|
||||
address: '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE',
|
||||
blockHash: '0xde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7',
|
||||
era: '0x0703',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
method: '0x0600ffd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c',
|
||||
nonce: '0x00001234',
|
||||
specVersion: 123,
|
||||
tip: '0x00000000000000000000000000005678'
|
||||
};
|
||||
|
||||
const TEST_WITH_ASSET = {
|
||||
address: '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE',
|
||||
assetId: '0x010002043205011f' as `0x${string}`,
|
||||
blockHash: '0xde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7',
|
||||
era: '0x0703',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
method: '0x0600ffd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c',
|
||||
nonce: '0x00001234',
|
||||
specVersion: 123,
|
||||
tip: '0x00000000000000000000000000005678'
|
||||
};
|
||||
|
||||
it('creates and can re-create from itself (U8a)', (): void => {
|
||||
const a = new ExtrinsicPayload(registry, TEST, { version: 4 });
|
||||
const b = new ExtrinsicPayload(registry, a.toU8a(), { version: 4 });
|
||||
|
||||
expect(a.inspect()).toEqual(b.inspect());
|
||||
expect(a.toJSON()).toEqual(b.toJSON());
|
||||
});
|
||||
|
||||
it('creates and can re-create from itself (hex)', (): void => {
|
||||
const a = new ExtrinsicPayload(registry, TEST, { version: 4 });
|
||||
const b = new ExtrinsicPayload(registry, a.toHex(), { version: 4 });
|
||||
|
||||
expect(a.inspect()).toEqual(b.inspect());
|
||||
expect(a.toJSON()).toEqual(b.toJSON());
|
||||
});
|
||||
|
||||
it('handles assetId correctly', () => {
|
||||
const reg = new TypeRegistry();
|
||||
|
||||
reg.setSignedExtensions(fallbackExtensions.concat(['ChargeAssetTxPayment']));
|
||||
const ext = new ExtrinsicPayload(reg, TEST_WITH_ASSET, { version: 4 });
|
||||
// remove option byte
|
||||
const ext2 = new ExtrinsicPayload(reg, { ...TEST_WITH_ASSET, assetId: `0x${TEST_WITH_ASSET.assetId.slice(4)}` }, { version: 4 });
|
||||
|
||||
expect(ext.assetId.toJSON()).toEqual({
|
||||
interior: {
|
||||
x2: [
|
||||
{
|
||||
palletInstance: 50
|
||||
},
|
||||
{
|
||||
generalIndex: 1984
|
||||
}
|
||||
]
|
||||
},
|
||||
parents: 0
|
||||
});
|
||||
expect(ext.assetId.toJSON()).toEqual(ext2.assetId.toJSON());
|
||||
});
|
||||
|
||||
it('handles toU8a(true) correctly', (): void => {
|
||||
expect(
|
||||
u8aToHex(
|
||||
new ExtrinsicPayload(registry, TEST, { version: 4 }).toU8a(true)
|
||||
)
|
||||
).toEqual(
|
||||
// no method length prefix
|
||||
'0x0600ffd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c0703d148e25901007b000000dcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025bde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles toU8a(false) correctly', (): void => {
|
||||
expect(
|
||||
u8aToHex(
|
||||
new ExtrinsicPayload(registry, TEST, { version: 4 }).toU8a()
|
||||
)
|
||||
).toEqual(
|
||||
// with method length prefix
|
||||
'0x940600ffd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c0703d148e25901007b000000dcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025bde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7'
|
||||
);
|
||||
});
|
||||
|
||||
it('has a sane inspect of an empty value', (): void => {
|
||||
const reg = new TypeRegistry();
|
||||
|
||||
reg.setSignedExtensions(fallbackExtensions.concat(['ChargeAssetTxPayment']));
|
||||
expect(new ExtrinsicPayload(reg, undefined).inspect()).toEqual({
|
||||
inner: [
|
||||
{ name: 'method', outer: [new Uint8Array()] },
|
||||
{ inner: undefined, name: 'era', outer: [new Uint8Array([0]), new Uint8Array([0])] },
|
||||
{ name: 'nonce', outer: [new Uint8Array([0])] },
|
||||
{ name: 'tip', outer: [new Uint8Array([0])] },
|
||||
{ name: 'assetId', outer: [new Uint8Array([0])] },
|
||||
{ name: 'specVersion', outer: [new Uint8Array([0, 0, 0, 0])] },
|
||||
{ name: 'genesisHash', outer: [new Uint8Array(32)] },
|
||||
{ name: 'blockHash', outer: [new Uint8Array(32)] }
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Bytes } from '@pezkuwi/types-codec';
|
||||
import type { AnyJson, BareOpts, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { BlockHash } from '../interfaces/chain/index.js';
|
||||
import type { ExtrinsicPayloadV5 } from '../interfaces/extrinsics/index.js';
|
||||
import type { Hash, MultiLocation } from '../interfaces/types.js';
|
||||
import type { ExtrinsicPayloadValue, ICompact, IKeyringPair, INumber, IOption } from '../types/index.js';
|
||||
import type { GenericExtrinsicEra } from './ExtrinsicEra.js';
|
||||
import type { Preamble } from './types.js';
|
||||
|
||||
import { AbstractBase } from '@pezkuwi/types-codec';
|
||||
import { hexToU8a, isHex, u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
import { DEFAULT_PREAMBLE, LATEST_EXTRINSIC_VERSION } from './constants.js';
|
||||
|
||||
interface ExtrinsicPayloadOptions {
|
||||
version?: number;
|
||||
preamble?: Preamble;
|
||||
}
|
||||
|
||||
// all our known types that can be returned
|
||||
type ExtrinsicPayloadVx = ExtrinsicPayloadV5;
|
||||
|
||||
const VERSIONS = [
|
||||
'ExtrinsicPayloadUnknown', // v0 is unknown
|
||||
'ExtrinsicPayloadUnknown',
|
||||
'ExtrinsicPayloadUnknown',
|
||||
'ExtrinsicPayloadUnknown',
|
||||
'ExtrinsicPayloadV4',
|
||||
'ExtrinsicPayloadV5'
|
||||
];
|
||||
|
||||
const PREAMBLES = {
|
||||
bare: 'ExtrinsicPayloadV5',
|
||||
// Not supported yet
|
||||
general: 'ExtrinsicPayloadV5'
|
||||
};
|
||||
|
||||
/**
|
||||
* HACK: In order to change the assetId from `number | object` to HexString (While maintaining the true type ie Option<TAssetConversion>),
|
||||
* to allow for easier generalization of the SignerPayloadJSON interface the below check is necessary. The ExtrinsicPayloadV4 class does not like
|
||||
* a value passed in as an Option, and can't decode it properly. Therefore, we ensure to convert the following below, and then pass the option as a unwrapped
|
||||
* JSON value.
|
||||
*
|
||||
* ref: https://github.com/pezkuwichain/pezkuwi-api/pull/5968
|
||||
* ref: https://github.com/pezkuwichain/pezkuwi-api/pull/5967
|
||||
*/
|
||||
export function decodeAssetId (registry: Registry, payload?: ExtrinsicPayloadValue | Uint8Array | HexString) {
|
||||
const maybeAsset = (payload as ExtrinsicPayloadValue)?.assetId;
|
||||
|
||||
if (maybeAsset && isHex(maybeAsset)) {
|
||||
const assetId = registry.createType('TAssetConversion', hexToU8a(maybeAsset));
|
||||
|
||||
// we only want to adjust the payload if the hex passed has the option
|
||||
if (maybeAsset === '0x00' || maybeAsset === '0x01' + assetId.toHex().slice(2)) {
|
||||
return {
|
||||
...(payload as ExtrinsicPayloadValue),
|
||||
assetId: assetId.toJSON()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeExtrinsicPayload (registry: Registry, value?: GenericExtrinsicPayload | ExtrinsicPayloadValue | Uint8Array | string, version = LATEST_EXTRINSIC_VERSION, preamble: Preamble = DEFAULT_PREAMBLE): ExtrinsicPayloadVx {
|
||||
if (value instanceof GenericExtrinsicPayload) {
|
||||
return value.unwrap();
|
||||
}
|
||||
|
||||
const extVersion = version === 5 ? PREAMBLES[preamble] : VERSIONS[version] || VERSIONS[0];
|
||||
const payload = decodeAssetId(registry, value as ExtrinsicPayloadValue);
|
||||
|
||||
return registry.createTypeUnsafe(extVersion, [payload, { version }]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicPayload
|
||||
* @description
|
||||
* A signing payload for an [[Extrinsic]]. For the final encoding, it is variable length based
|
||||
* on the contents included
|
||||
*/
|
||||
export class GenericExtrinsicPayload extends AbstractBase<ExtrinsicPayloadVx> {
|
||||
constructor (registry: Registry, value?: Partial<ExtrinsicPayloadValue> | Uint8Array | string, { preamble, version }: ExtrinsicPayloadOptions = {}) {
|
||||
super(registry, decodeExtrinsicPayload(registry, value as ExtrinsicPayloadValue, version, preamble));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The block [[BlockHash]] the signature applies to (mortal/immortal)
|
||||
*/
|
||||
public get blockHash (): BlockHash {
|
||||
return this.inner.blockHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[ExtrinsicEra]]
|
||||
*/
|
||||
public get era (): GenericExtrinsicEra {
|
||||
return this.inner.era;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The genesis block [[BlockHash]] the signature applies to
|
||||
*/
|
||||
public get genesisHash (): BlockHash {
|
||||
// NOTE only v3+
|
||||
return this.inner.genesisHash || this.registry.createTypeUnsafe('Hash', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Bytes]] contained in the payload
|
||||
*/
|
||||
public get method (): Bytes {
|
||||
return this.inner.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Index]]
|
||||
*/
|
||||
public get nonce (): ICompact<INumber> {
|
||||
return this.inner.nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The specVersion as a [[u32]] for this payload
|
||||
*/
|
||||
public get specVersion (): INumber {
|
||||
// NOTE only v3+
|
||||
return this.inner.specVersion || this.registry.createTypeUnsafe('u32', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Balance]]
|
||||
*/
|
||||
public get tip (): ICompact<INumber> {
|
||||
// NOTE from v2+
|
||||
return this.inner.tip || this.registry.createTypeUnsafe('Compact<Balance>', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The transaction version as a [[u32]] for this payload
|
||||
*/
|
||||
public get transactionVersion (): INumber {
|
||||
// NOTE only v4+
|
||||
return this.inner.transactionVersion || this.registry.createTypeUnsafe('u32', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) asset id as a [[u32]] or [[MultiLocation]] for this payload
|
||||
*/
|
||||
public get assetId (): IOption<INumber | MultiLocation> {
|
||||
return this.inner.assetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) [[Hash]] of the genesis metadata for this payload
|
||||
*/
|
||||
public get metadataHash (): IOption<Hash> {
|
||||
return this.inner.metadataHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Compares the value of the input to see if there is a match
|
||||
*/
|
||||
public override eq (other?: unknown): boolean {
|
||||
return this.inner.eq(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sign the payload with the keypair
|
||||
*/
|
||||
public sign (signerPair: IKeyringPair): { signature: HexString } {
|
||||
const signature = this.inner.sign(signerPair);
|
||||
|
||||
// This is extensible, so we could quite readily extend to send back extra
|
||||
// information, such as for instance the payload, i.e. `payload: this.toHex()`
|
||||
// For the case here we sign via the extrinsic, we ignore the return, so generally
|
||||
// this is applicable for external signing
|
||||
return {
|
||||
signature: u8aToHex(signature)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (isExtended?: boolean, disableAscii?: boolean): AnyJson {
|
||||
return this.inner.toHuman(isExtended, disableAscii);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to JSON, typically used for RPC transfers
|
||||
*/
|
||||
public override toJSON (): any {
|
||||
return this.toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public toRawType (): string {
|
||||
return 'ExtrinsicPayload';
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the string representation of the value
|
||||
*/
|
||||
public override toString (): string {
|
||||
return this.toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a serialized u8a form
|
||||
*/
|
||||
public override toU8a (isBare?: BareOpts): Uint8Array {
|
||||
// call our parent, with only the method stripped
|
||||
return super.toU8a(isBare ? { method: true } : false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { ExtrinsicPayloadOptions } from './types.js';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicPayloadUnknown
|
||||
* @description
|
||||
* A default handler for payloads where the version is not known (default throw)
|
||||
*/
|
||||
export class GenericExtrinsicPayloadUnknown extends Struct {
|
||||
constructor (registry: Registry, _value?: unknown, { version = 0 }: Partial<ExtrinsicPayloadOptions> = {}) {
|
||||
super(registry, {});
|
||||
|
||||
throw new Error(`Unsupported extrinsic payload version ${version}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { ExtrinsicOptions } from './types.js';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
|
||||
import { UNMASK_VERSION } from './constants.js';
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicUnknown
|
||||
* @description
|
||||
* A default handler for extrinsics where the version is not known (default throw)
|
||||
*/
|
||||
export class GenericExtrinsicUnknown extends Struct {
|
||||
constructor (registry: Registry, _value?: unknown, { isSigned = false, version = 0 }: Partial<ExtrinsicOptions> = {}) {
|
||||
super(registry, {});
|
||||
|
||||
throw new Error(`Unsupported ${isSigned ? '' : 'un'}signed extrinsic version ${version & UNMASK_VERSION}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import rpcMetadata from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
import { u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { Metadata } from '../metadata/index.js';
|
||||
import { GenericSignerPayload as SignerPayload } from './index.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
describe('SignerPayload', (): void => {
|
||||
const TEST = {
|
||||
address: '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE',
|
||||
// eslint-disable-next-line sort-keys
|
||||
assetId: '0x0002043205ed01',
|
||||
blockHash: '0xde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7',
|
||||
blockNumber: '0x00231d30',
|
||||
era: '0x0703',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
metadataHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
method: '0x060000d7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c',
|
||||
mode: 1,
|
||||
nonce: '0x00001234',
|
||||
signedExtensions: ['CheckNonce', 'CheckWeight'],
|
||||
specVersion: '0x00000006',
|
||||
tip: '0x00000000000000000000000000005678',
|
||||
transactionVersion: '0x00000007',
|
||||
version: 4,
|
||||
withSignedTransaction: false
|
||||
};
|
||||
|
||||
const TEST_WITH_ASSETID_HEX = {
|
||||
...TEST,
|
||||
assetId: '0x010002043205ed01'
|
||||
};
|
||||
|
||||
it('creates a valid JSON output', (): void => {
|
||||
expect(
|
||||
new SignerPayload(registry, {
|
||||
address: '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE',
|
||||
// eslint-disable-next-line sort-keys
|
||||
assetId: { parents: 0, interior: { x2: [{ palletInstance: 50 }, { generalIndex: 123 }] } },
|
||||
blockHash: '0xde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7',
|
||||
blockNumber: '0x231d30',
|
||||
era: registry.createType('ExtrinsicEra', { current: 2301232, period: 200 }),
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
metadataHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
method: registry.createType('Call', '0x060000d7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c'),
|
||||
mode: 1,
|
||||
nonce: 0x1234,
|
||||
signedExtensions: ['CheckNonce'],
|
||||
tip: 0x5678,
|
||||
version: 4,
|
||||
withSignedTransaction: true
|
||||
}).toPayload()
|
||||
).toEqual({
|
||||
address: '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE',
|
||||
// eslint-disable-next-line sort-keys
|
||||
assetId: '0x010002043205ed01',
|
||||
blockHash: '0xde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7',
|
||||
blockNumber: '0x00231d30',
|
||||
era: '0x0703',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
metadataHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
method: '0x060000d7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c',
|
||||
mode: 1,
|
||||
nonce: '0x00001234',
|
||||
signedExtensions: ['CheckNonce'],
|
||||
specVersion: '0x00000000',
|
||||
tip: '0x00000000000000000000000000005678',
|
||||
transactionVersion: '0x00000000',
|
||||
version: 4,
|
||||
withSignedTransaction: true
|
||||
});
|
||||
});
|
||||
|
||||
it('handles Option<AssetId> correctly', (): void => {
|
||||
const test = new SignerPayload(registry, {
|
||||
// eslint-disable-next-line sort-keys
|
||||
assetId: { parents: 0, interior: { x2: [{ palletInstance: 50 }, { generalIndex: 123 }] } }
|
||||
});
|
||||
|
||||
expect(
|
||||
[...test.keys()].includes('assetId')
|
||||
).toEqual(true);
|
||||
|
||||
expect(
|
||||
test.toPayload().assetId
|
||||
// eslint-disable-next-line sort-keys
|
||||
).toEqual('0x010002043205ed01');
|
||||
|
||||
expect(
|
||||
new SignerPayload(registry, { assetId: 0 }).toPayload().assetId
|
||||
// eslint-disable-next-line sort-keys
|
||||
).toEqual('0x010000');
|
||||
});
|
||||
|
||||
it('re-constructs from JSON', (): void => {
|
||||
expect(
|
||||
new SignerPayload(registry, {
|
||||
...TEST,
|
||||
runtimeVersion: { specVersion: 0x06, transactionVersion: 0x07 }
|
||||
}).toPayload()
|
||||
).toEqual(TEST_WITH_ASSETID_HEX);
|
||||
});
|
||||
|
||||
it('re-constructs from itself', (): void => {
|
||||
expect(
|
||||
new SignerPayload(
|
||||
registry,
|
||||
new SignerPayload(registry, {
|
||||
...TEST,
|
||||
runtimeVersion: { specVersion: 0x06, transactionVersion: 0x07 }
|
||||
})
|
||||
).toPayload()
|
||||
).toEqual(TEST_WITH_ASSETID_HEX);
|
||||
});
|
||||
|
||||
it('can be used as a feed to ExtrinsicPayload', (): void => {
|
||||
const signer = new SignerPayload(registry, {
|
||||
...TEST
|
||||
}).toPayload();
|
||||
const payload = registry.createType('ExtrinsicPayload', signer, { version: signer.version });
|
||||
|
||||
expect(payload.era.toHex()).toEqual(TEST.era);
|
||||
expect(payload.method.toHex()).toEqual(TEST.method);
|
||||
expect(payload.blockHash.toHex()).toEqual(TEST.blockHash);
|
||||
expect(payload.nonce.eq(TEST.nonce)).toBe(true);
|
||||
expect(payload.tip.eq(TEST.tip)).toBe(true);
|
||||
expect(u8aToHex(payload.assetId?.toU8a()))
|
||||
.toEqual(u8aToHex(registry.createType('Option<MultiLocation>', {
|
||||
// eslint-disable-next-line sort-keys
|
||||
parents: 0, interior: { X2: [{ palletInstance: 50 }, { generalIndex: 123 }] }
|
||||
}).toU8a()));
|
||||
});
|
||||
|
||||
const TEST_WITHOUT_CHECK = {
|
||||
address: '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE',
|
||||
blockHash: '0xde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7',
|
||||
blockNumber: '0x00231d30',
|
||||
era: '0x0703',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
method: '0x060000d7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c',
|
||||
nonce: '0x00001234',
|
||||
signedExtensions: [],
|
||||
specVersion: '0x00000006',
|
||||
tip: '0x00000000000000000000000000005678',
|
||||
transactionVersion: '0x00000007',
|
||||
version: 4
|
||||
};
|
||||
|
||||
const PAYLOAD_WITHOUT_CHECK = {
|
||||
address: '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE',
|
||||
assetId: null,
|
||||
blockHash: '0xde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7',
|
||||
blockNumber: '0x00231d30',
|
||||
era: '0x0703',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
metadataHash: null,
|
||||
method: '0x060000d7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c',
|
||||
mode: 0,
|
||||
nonce: '0x00001234',
|
||||
signedExtensions: [],
|
||||
specVersion: '0x00000006',
|
||||
tip: '0x00000000000000000000000000005678',
|
||||
transactionVersion: '0x00000007',
|
||||
version: 4,
|
||||
withSignedTransaction: false
|
||||
};
|
||||
|
||||
it('can build SignerPayload without additional SignedExtensions', (): void => {
|
||||
expect(
|
||||
new SignerPayload(
|
||||
registry,
|
||||
{
|
||||
...TEST_WITHOUT_CHECK,
|
||||
runtimeVersion: { specVersion: 0x06, transactionVersion: 0x07 }
|
||||
}
|
||||
).toPayload()
|
||||
).toEqual(PAYLOAD_WITHOUT_CHECK);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { bool, Text, Vec } from '@pezkuwi/types-codec';
|
||||
import type { AnyJson, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { Address, BlockHash, Call, ExtrinsicEra, Hash, MultiLocation } from '../interfaces/index.js';
|
||||
import type { Codec, ICompact, INumber, IOption, IRuntimeVersion, ISignerPayload, SignerPayloadJSON, SignerPayloadRaw } from '../types/index.js';
|
||||
|
||||
import { Option, Struct } from '@pezkuwi/types-codec';
|
||||
import { objectProperty, objectSpread, u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
export interface SignerPayloadType extends Codec {
|
||||
address: Address;
|
||||
assetId: IOption<INumber | MultiLocation>;
|
||||
blockHash: Hash;
|
||||
blockNumber: INumber;
|
||||
era: ExtrinsicEra;
|
||||
genesisHash: Hash;
|
||||
metadataHash: IOption<Hash>;
|
||||
method: Call;
|
||||
mode: INumber;
|
||||
nonce: ICompact<INumber>;
|
||||
runtimeVersion: IRuntimeVersion;
|
||||
signedExtensions: Vec<Text>;
|
||||
tip: ICompact<INumber>;
|
||||
version: INumber;
|
||||
}
|
||||
|
||||
const knownTypes: Record<string, string> = {
|
||||
address: 'Address',
|
||||
assetId: 'Option<TAssetConversion>',
|
||||
blockHash: 'Hash',
|
||||
blockNumber: 'BlockNumber',
|
||||
era: 'ExtrinsicEra',
|
||||
genesisHash: 'Hash',
|
||||
metadataHash: 'Option<[u8;32]>',
|
||||
method: 'Call',
|
||||
mode: 'u8',
|
||||
nonce: 'Compact<Index>',
|
||||
runtimeVersion: 'RuntimeVersion',
|
||||
signedExtensions: 'Vec<Text>',
|
||||
tip: 'Compact<Balance>',
|
||||
version: 'u8'
|
||||
};
|
||||
|
||||
/**
|
||||
* @name GenericSignerPayload
|
||||
* @description
|
||||
* A generic signer payload that can be used for serialization between API and signer
|
||||
*/
|
||||
export class GenericSignerPayload extends Struct implements ISignerPayload, SignerPayloadType {
|
||||
readonly #extraTypes: Record<string, string>;
|
||||
|
||||
constructor (registry: Registry, value?: HexString | Record<string, unknown> | Map<unknown, unknown> | unknown[]) {
|
||||
const extensionTypes = objectSpread<Record<string, string>>({}, registry.getSignedExtensionTypes(), registry.getSignedExtensionExtra());
|
||||
|
||||
super(registry, objectSpread<Record<string, string>>({}, extensionTypes, knownTypes, { withSignedTransaction: 'bool' }), value);
|
||||
|
||||
this.#extraTypes = {};
|
||||
const getter = (key: string) => this.get(key);
|
||||
|
||||
// add all extras that are not in the base types
|
||||
for (const [key, type] of Object.entries(extensionTypes)) {
|
||||
if (!knownTypes[key]) {
|
||||
this.#extraTypes[key] = type;
|
||||
}
|
||||
|
||||
objectProperty(this, key, getter);
|
||||
}
|
||||
}
|
||||
|
||||
get address (): Address {
|
||||
return this.getT('address');
|
||||
}
|
||||
|
||||
get blockHash (): BlockHash {
|
||||
return this.getT('blockHash');
|
||||
}
|
||||
|
||||
get blockNumber (): INumber {
|
||||
return this.getT('blockNumber');
|
||||
}
|
||||
|
||||
get era (): ExtrinsicEra {
|
||||
return this.getT('era');
|
||||
}
|
||||
|
||||
get genesisHash (): BlockHash {
|
||||
return this.getT('genesisHash');
|
||||
}
|
||||
|
||||
get method (): Call {
|
||||
return this.getT('method');
|
||||
}
|
||||
|
||||
get nonce (): ICompact<INumber> {
|
||||
return this.getT('nonce');
|
||||
}
|
||||
|
||||
get runtimeVersion (): IRuntimeVersion {
|
||||
return this.getT('runtimeVersion');
|
||||
}
|
||||
|
||||
get signedExtensions (): Vec<Text> {
|
||||
return this.getT('signedExtensions');
|
||||
}
|
||||
|
||||
get tip (): ICompact<INumber> {
|
||||
return this.getT('tip');
|
||||
}
|
||||
|
||||
get assetId (): IOption<INumber | MultiLocation> {
|
||||
return this.getT('assetId');
|
||||
}
|
||||
|
||||
get version (): INumber {
|
||||
return this.getT('version');
|
||||
}
|
||||
|
||||
get mode (): INumber {
|
||||
return this.getT('mode');
|
||||
}
|
||||
|
||||
get metadataHash (): IOption<Hash> {
|
||||
return this.getT('metadataHash');
|
||||
}
|
||||
|
||||
get withSignedTransaction (): boolean {
|
||||
const val: bool = this.getT('withSignedTransaction');
|
||||
|
||||
return val.isTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates an representation of the structure as an ISignerPayload JSON
|
||||
*/
|
||||
public toPayload (): SignerPayloadJSON {
|
||||
const result: Record<string, AnyJson> = {};
|
||||
const keys = Object.keys(this.#extraTypes);
|
||||
|
||||
// add any explicit overrides we may have
|
||||
for (let i = 0, count = keys.length; i < count; i++) {
|
||||
const key = keys[i];
|
||||
const value = this.getT(key);
|
||||
|
||||
// Don't include Option.isNone
|
||||
if (!(value instanceof Option) || value.isSome) {
|
||||
// NOTE In the spread below we convert (mostly) to Hex to align
|
||||
// with the typings. In the case of "unknown" fields, we use the
|
||||
// primitive toJSON conversion (which is serializable). Technically
|
||||
// we can include isNone in here as well ("null" is allowed), however
|
||||
// for empty fields we just skip it completely (historical compat)
|
||||
result[key] = value.toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
return objectSpread(result, {
|
||||
// the known defaults as managed explicitly and has different
|
||||
// formatting in cases, e.g. we mostly expose a hex format here
|
||||
address: this.address.toString(),
|
||||
assetId: this.assetId && this.assetId.isSome ? this.assetId.toHex() : null,
|
||||
blockHash: this.blockHash.toHex(),
|
||||
blockNumber: this.blockNumber.toHex(),
|
||||
era: this.era.toHex(),
|
||||
genesisHash: this.genesisHash.toHex(),
|
||||
metadataHash: this.metadataHash.isSome ? this.metadataHash.toHex() : null,
|
||||
method: this.method.toHex(),
|
||||
mode: this.mode.toNumber(),
|
||||
nonce: this.nonce.toHex(),
|
||||
signedExtensions: this.signedExtensions.map((e) => e.toString()),
|
||||
specVersion: this.runtimeVersion.specVersion.toHex(),
|
||||
tip: this.tip.toHex(),
|
||||
transactionVersion: this.runtimeVersion.transactionVersion.toHex(),
|
||||
version: this.version.toNumber(),
|
||||
withSignedTransaction: this.withSignedTransaction
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates a representation of the payload in raw Exrinsic form
|
||||
*/
|
||||
public toRaw (): SignerPayloadRaw {
|
||||
const payload = this.toPayload();
|
||||
const data = u8aToHex(
|
||||
this.registry
|
||||
.createTypeUnsafe('ExtrinsicPayload', [payload, { version: payload.version }])
|
||||
// NOTE Explicitly pass the bare flag so the method is encoded un-prefixed (non-decodable, for signing only)
|
||||
.toU8a({ method: true })
|
||||
);
|
||||
|
||||
return {
|
||||
address: payload.address,
|
||||
data,
|
||||
type: 'payload'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export const BIT_SIGNED = 0b10000000;
|
||||
|
||||
export const BIT_UNSIGNED = 0;
|
||||
|
||||
export const EMPTY_U8A = new Uint8Array();
|
||||
|
||||
export const IMMORTAL_ERA = new Uint8Array([0]);
|
||||
|
||||
export const UNMASK_VERSION = 0b01111111;
|
||||
|
||||
export const DEFAULT_PREAMBLE = 'bare';
|
||||
|
||||
// Latest extrinsic version is v5, which has backwards compatibility for v4 signed extrinsics
|
||||
// However is not fully supported so LATEST_EXTRINSIC_VERSION is configured as 4 in the meantime.
|
||||
export const LATEST_EXTRINSIC_VERSION = 4;
|
||||
|
||||
export const VERSION_MASK = 0b00111111;
|
||||
|
||||
export const TYPE_MASK = 0b11000000;
|
||||
|
||||
export const BARE_EXTRINSIC = 0b00000000;
|
||||
|
||||
export const GENERAL_EXTRINSIC = 0b01000000;
|
||||
|
||||
export const LOWEST_SUPPORTED_EXTRINSIC_FORMAT_VERSION = 4;
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// all named
|
||||
export { GenericExtrinsic } from './Extrinsic.js';
|
||||
export { GenericExtrinsicEra, ImmortalEra as GenericImmortalEra, MortalEra as GenericMortalEra } from './ExtrinsicEra.js';
|
||||
export { GenericExtrinsicPayload } from './ExtrinsicPayload.js';
|
||||
export { GenericExtrinsicPayloadUnknown } from './ExtrinsicPayloadUnknown.js';
|
||||
export { GenericExtrinsicUnknown } from './ExtrinsicUnknown.js';
|
||||
export { GenericSignerPayload } from './SignerPayload.js';
|
||||
|
||||
// all starred
|
||||
export * from './v4/index.js';
|
||||
export * from './v5/index.js';
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ExtDef, ExtInfo } from './types.js';
|
||||
|
||||
import { emptyCheck } from './emptyCheck.js';
|
||||
|
||||
const CheckMetadataHash: ExtInfo = {
|
||||
extrinsic: {
|
||||
mode: 'u8'
|
||||
},
|
||||
payload: {
|
||||
metadataHash: 'Option<[u8;32]>'
|
||||
}
|
||||
};
|
||||
|
||||
const CheckMortality: ExtInfo = {
|
||||
extrinsic: {
|
||||
era: 'ExtrinsicEra'
|
||||
},
|
||||
payload: {
|
||||
blockHash: 'Hash'
|
||||
}
|
||||
};
|
||||
|
||||
const ChargeTransactionPayment: ExtInfo = {
|
||||
extrinsic: {
|
||||
tip: 'Compact<Balance>'
|
||||
},
|
||||
payload: {}
|
||||
};
|
||||
|
||||
export const bizinikiwi: ExtDef = {
|
||||
ChargeTransactionPayment,
|
||||
CheckBlockGasLimit: emptyCheck,
|
||||
CheckEra: CheckMortality,
|
||||
CheckGenesis: {
|
||||
extrinsic: {},
|
||||
payload: {
|
||||
genesisHash: 'Hash'
|
||||
}
|
||||
},
|
||||
CheckMetadataHash,
|
||||
CheckMortality,
|
||||
CheckNonZeroSender: emptyCheck,
|
||||
CheckNonce: {
|
||||
extrinsic: {
|
||||
nonce: 'Compact<Index>'
|
||||
},
|
||||
payload: {}
|
||||
},
|
||||
CheckSpecVersion: {
|
||||
extrinsic: {},
|
||||
payload: {
|
||||
specVersion: 'u32'
|
||||
}
|
||||
},
|
||||
CheckTxVersion: {
|
||||
extrinsic: {},
|
||||
payload: {
|
||||
transactionVersion: 'u32'
|
||||
}
|
||||
},
|
||||
CheckVersion: {
|
||||
extrinsic: {},
|
||||
payload: {
|
||||
specVersion: 'u32'
|
||||
}
|
||||
},
|
||||
CheckWeight: emptyCheck,
|
||||
LockStakingStatus: emptyCheck,
|
||||
SkipCheckIfFeeless: ChargeTransactionPayment,
|
||||
ValidateEquivocationReport: emptyCheck,
|
||||
WeightReclaim: emptyCheck
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ExtInfo } from './types.js';
|
||||
|
||||
export const emptyCheck: ExtInfo = {
|
||||
extrinsic: {},
|
||||
payload: {}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ExtDef, ExtInfo, ExtTypes } from './types.js';
|
||||
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { bizinikiwi } from './bizinikiwi.js';
|
||||
import { pezkuwi } from './pezkuwi.js';
|
||||
import { shell } from './shell.js';
|
||||
import { statemint } from './statemint.js';
|
||||
|
||||
// A mapping of the known signed extensions to the extra fields that they
|
||||
// contain. Unlike in the actual extensions, we define the extra fields not
|
||||
// as a Tuple, but rather as a struct so they can be named. These will be
|
||||
// expanded into the various fields when added to the payload (we only
|
||||
// support V4 onwards with these, V3 and earlier are deemed fixed))
|
||||
export const allExtensions: ExtDef = objectSpread({}, bizinikiwi, pezkuwi, shell, statemint);
|
||||
|
||||
// the v4 signed extensions prior to the point of exposing these to the
|
||||
// metadata. This may not match 100% with the current defaults and are used
|
||||
// when not specified in the metadata (which is for very old chains). The
|
||||
// order is important here, as applied by default
|
||||
export const fallbackExtensions = [
|
||||
'CheckVersion',
|
||||
'CheckGenesis',
|
||||
'CheckEra',
|
||||
'CheckNonce',
|
||||
'CheckWeight',
|
||||
'ChargeTransactionPayment',
|
||||
'CheckBlockGasLimit'
|
||||
];
|
||||
|
||||
export function findUnknownExtensions (extensions: string[], userExtensions: ExtDef = {}): string[] {
|
||||
const names = [...Object.keys(allExtensions), ...Object.keys(userExtensions)];
|
||||
|
||||
return extensions.filter((k) => !names.includes(k));
|
||||
}
|
||||
|
||||
export function expandExtensionTypes (extensions: string[], type: keyof ExtInfo, userExtensions: ExtDef = {}): ExtTypes {
|
||||
return extensions
|
||||
// Always allow user extensions first - these should provide overrides
|
||||
.map((k) => userExtensions[k] || allExtensions[k])
|
||||
.filter((info): info is ExtInfo => !!info)
|
||||
.reduce((result, info): ExtTypes => objectSpread(result, info[type]), {});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ExtDef } from './types.js';
|
||||
|
||||
import { emptyCheck } from './emptyCheck.js';
|
||||
|
||||
export const pezkuwi: ExtDef = {
|
||||
LimitParathreadCommits: emptyCheck,
|
||||
OnlyStakingAndClaims: emptyCheck,
|
||||
PrevalidateAttests: emptyCheck,
|
||||
RestrictFunctionality: emptyCheck,
|
||||
TransactionCallFilter: emptyCheck,
|
||||
ValidateDoubleVoteReports: emptyCheck
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ExtDef } from './types.js';
|
||||
|
||||
import { emptyCheck } from './emptyCheck.js';
|
||||
|
||||
export const shell: ExtDef = {
|
||||
DisallowSigned: emptyCheck
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ExtDef } from './types.js';
|
||||
|
||||
export const statemint: ExtDef = {
|
||||
ChargeAssetTxPayment: {
|
||||
extrinsic: {
|
||||
tip: 'Compact<Balance>',
|
||||
// eslint-disable-next-line sort-keys
|
||||
assetId: 'TAssetConversion'
|
||||
},
|
||||
payload: {}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export type ExtTypes = Record<string, string>;
|
||||
|
||||
export interface ExtInfo {
|
||||
extrinsic: ExtTypes;
|
||||
payload: ExtTypes;
|
||||
}
|
||||
|
||||
export type ExtDef = Record<string, ExtInfo>;
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyNumber } from '@pezkuwi/types-codec/types';
|
||||
|
||||
export interface ExtrinsicOptions {
|
||||
isSigned: boolean;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface ExtrinsicPayloadOptions {
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface ExtrinsicSignatureOptions {
|
||||
isSigned?: boolean;
|
||||
}
|
||||
|
||||
export interface ExtrinsicExtraValue {
|
||||
era?: Uint8Array;
|
||||
nonce?: AnyNumber;
|
||||
tip?: AnyNumber;
|
||||
}
|
||||
|
||||
export type Preamble = 'bare' | 'general';
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SignOptions } from '@pezkuwi/keyring/types';
|
||||
import type { Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { IKeyringPair } from '../types/index.js';
|
||||
|
||||
import { blake2AsU8a } from '@pezkuwi/util-crypto';
|
||||
|
||||
// a helper function for both types of payloads, Raw and metadata-known
|
||||
export function sign (_registry: Registry, signerPair: IKeyringPair, u8a: Uint8Array, options?: SignOptions): Uint8Array {
|
||||
const encoded = u8a.length > 256
|
||||
? blake2AsU8a(u8a)
|
||||
: u8a;
|
||||
|
||||
return signerPair.sign(encoded, options);
|
||||
}
|
||||
|
||||
export function signGeneral (registry: Registry, u8a: Uint8Array): Uint8Array {
|
||||
const encoded = registry.hash(u8a);
|
||||
|
||||
return encoded;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { createTestPairs } from '@pezkuwi/keyring/testingPairs';
|
||||
import rpcMetadata from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from '../../create/index.js';
|
||||
import { decorateExtrinsics, Metadata } from '../../metadata/index.js';
|
||||
import { GenericExtrinsicV4 as Extrinsic } from './index.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
const tx = decorateExtrinsics(registry, metadata.asLatest, metadata.version);
|
||||
|
||||
describe('ExtrinsicV4', (): void => {
|
||||
it('constructs a sane Uint8Array (default)', (): void => {
|
||||
const xt = new Extrinsic(registry);
|
||||
|
||||
// expect(`${xt.method.section}${xt.method.method}`).toEqual('system.fillBlock');
|
||||
expect(`${xt.method.section}.${xt.method.method}`).toEqual('system.remark');
|
||||
|
||||
expect(xt.toU8a()).toEqual(new Uint8Array([
|
||||
0, 0, // index
|
||||
// 0, 0, 0, 0 // fillBlock, Perbill
|
||||
0 // remark, Vec<u8>
|
||||
]));
|
||||
});
|
||||
|
||||
it('creates a unsigned extrinsic', (): void => {
|
||||
expect(
|
||||
new Extrinsic(
|
||||
registry,
|
||||
tx['balances']['transferAllowDeath'](keyring.bob.publicKey, 6969n)
|
||||
).toHex()
|
||||
).toEqual(
|
||||
'0x' +
|
||||
'0600' + // balance.transferAllowDeath
|
||||
'00' +
|
||||
'd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9' +
|
||||
'e56c'
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a signed extrinsic', (): void => {
|
||||
expect(
|
||||
new Extrinsic(
|
||||
registry,
|
||||
tx['balances']['transferAllowDeath'](keyring.bob.publicKey, 6969n)
|
||||
).sign(keyring.alice, {
|
||||
blockHash: '0xec7afaf1cca720ce88c1d1b689d81f0583cc15a97d621cf046dd9abf605ef22f',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
mode: 0,
|
||||
nonce: 1,
|
||||
runtimeVersion: {
|
||||
apis: [],
|
||||
authoringVersion: new BN(123),
|
||||
implName: 'test',
|
||||
implVersion: new BN(123),
|
||||
specName: 'test',
|
||||
specVersion: new BN(123),
|
||||
transactionVersion: new BN(123)
|
||||
},
|
||||
tip: 2
|
||||
}).toHex()
|
||||
).toEqual(
|
||||
'0x' +
|
||||
'00' +
|
||||
'd172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f' +
|
||||
'00' + // ed25519
|
||||
'bac8e442c7d6e6827fe3b72a2dee6bd036e059e1b0a2a5cfb00ec39184d60536' +
|
||||
'a4f3b5f3bfcd31e6e5b5cf6482914dc989fbe9483043a13c90376dfe28c5e00f' +
|
||||
'0004080000' + // era. nonce, tip, mode
|
||||
'0600' +
|
||||
'00' +
|
||||
'd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9' +
|
||||
'e56c'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { ExtrinsicSignatureV4 } from '../../interfaces/extrinsics/index.js';
|
||||
import type { Address, Call } from '../../interfaces/runtime/index.js';
|
||||
import type { ExtrinsicPayloadValue, IExtrinsicImpl, IKeyringPair, Registry, SignatureOptions } from '../../types/index.js';
|
||||
import type { ExtrinsicOptions } from '../types.js';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
import { isU8a } from '@pezkuwi/util';
|
||||
|
||||
export const EXTRINSIC_VERSION = 4;
|
||||
|
||||
export interface ExtrinsicValueV4 {
|
||||
method?: Call;
|
||||
signature?: ExtrinsicSignatureV4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicV4
|
||||
* @description
|
||||
* The third generation of compact extrinsics
|
||||
*/
|
||||
export class GenericExtrinsicV4 extends Struct implements IExtrinsicImpl {
|
||||
constructor (registry: Registry, value?: Uint8Array | ExtrinsicValueV4 | Call, { isSigned }: Partial<ExtrinsicOptions> = {}) {
|
||||
super(registry, {
|
||||
signature: 'ExtrinsicSignatureV4',
|
||||
// eslint-disable-next-line sort-keys
|
||||
method: 'Call'
|
||||
}, GenericExtrinsicV4.decodeExtrinsic(registry, value, isSigned));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public static decodeExtrinsic (registry: Registry, value?: Call | Uint8Array | ExtrinsicValueV4, isSigned = false): ExtrinsicValueV4 {
|
||||
if (value instanceof GenericExtrinsicV4) {
|
||||
return value;
|
||||
} else if (value instanceof registry.createClassUnsafe<Call>('Call')) {
|
||||
return { method: value };
|
||||
} else if (isU8a(value)) {
|
||||
// here we decode manually since we need to pull through the version information
|
||||
const signature = registry.createTypeUnsafe<ExtrinsicSignatureV4>('ExtrinsicSignatureV4', [value, { isSigned }]);
|
||||
const method = registry.createTypeUnsafe<Call>('Call', [value.subarray(signature.encodedLength)]);
|
||||
|
||||
return {
|
||||
method,
|
||||
signature
|
||||
};
|
||||
}
|
||||
|
||||
return value || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the value when encoded as a Uint8Array
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
return this.toU8a().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Call]] this extrinsic wraps
|
||||
*/
|
||||
public get method (): Call {
|
||||
return this.getT('method');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[ExtrinsicSignatureV4]]
|
||||
*/
|
||||
public get signature (): ExtrinsicSignatureV4 {
|
||||
return this.getT('signature');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The version for the signature
|
||||
*/
|
||||
public get version (): number {
|
||||
return EXTRINSIC_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Add an [[ExtrinsicSignatureV4]] to the extrinsic (already generated)
|
||||
*/
|
||||
public addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | HexString, payload: ExtrinsicPayloadValue | Uint8Array | HexString): GenericExtrinsicV4 {
|
||||
this.signature.addSignature(signer, signature, payload);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sign the extrinsic with a specific keypair
|
||||
*/
|
||||
public sign (account: IKeyringPair, options: SignatureOptions): GenericExtrinsicV4 {
|
||||
this.signature.sign(this.method, account, options);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @describe Adds a fake signature to the extrinsic
|
||||
*/
|
||||
public signFake (signer: Address | Uint8Array | string, options: SignatureOptions): GenericExtrinsicV4 {
|
||||
this.signature.signFake(this.method, signer, options);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import rpcMetadata from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
|
||||
import { TypeRegistry } from '../../create/index.js';
|
||||
import { decorateExtrinsics, Metadata } from '../../metadata/index.js';
|
||||
import { fallbackExtensions } from '../signedExtensions/index.js';
|
||||
import { GenericExtrinsicPayloadV4 as ExtrinsicPayload } from './index.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
const tx = decorateExtrinsics(registry, metadata.asLatest, metadata.version);
|
||||
|
||||
describe('ExtrinsicPayload', (): void => {
|
||||
it('has a sane inspect', (): void => {
|
||||
// we don't expect this to fail, however it is actually a good
|
||||
// reference for the ordering in base Bizinikiwi
|
||||
expect(new ExtrinsicPayload(registry, { method: tx['timestamp']['set'](0).toHex() } as never).inspect()).toEqual({
|
||||
inner: [
|
||||
{ name: 'method', outer: [new Uint8Array([3, 0, 0])] },
|
||||
{ inner: undefined, name: 'era', outer: [new Uint8Array([0]), new Uint8Array([0])] },
|
||||
{ name: 'nonce', outer: [new Uint8Array([0])] },
|
||||
{ name: 'tip', outer: [new Uint8Array([0])] },
|
||||
{ name: 'assetId', outer: [new Uint8Array([0])] },
|
||||
{ name: 'mode', outer: [new Uint8Array([0])] },
|
||||
{ name: 'specVersion', outer: [new Uint8Array([0, 0, 0, 0])] },
|
||||
{ name: 'transactionVersion', outer: [new Uint8Array([0, 0, 0, 0])] },
|
||||
{ name: 'genesisHash', outer: [new Uint8Array(32)] },
|
||||
{ name: 'blockHash', outer: [new Uint8Array(32)] },
|
||||
{ name: 'metadataHash', outer: [new Uint8Array([0])] }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('Correctly decodes assetId', () => {
|
||||
const TEST_VALUE = {
|
||||
address: 'J97drEQy6sYPXf2D1uj1hJfeHsxjvwr4tVGKs9o8VDSht8r',
|
||||
assetId: '0x010002043205011f' as `0x${string}`,
|
||||
blockHash: '0x28a464e6b40fccec3b9e7989db97d2627d3653c644a3c801f8239910eaaa58a8',
|
||||
era: '0x4401',
|
||||
genesisHash: '0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a',
|
||||
method: '0x0a0300d27001b334c34489c67b81dfbbdc86eba5b433163bd08226d89b081914e9aa490284d717',
|
||||
nonce: '0x0000000a',
|
||||
specVersion: '0x000f4dfc',
|
||||
tip: '0x00000000000000000000000000000000',
|
||||
transactionVersion: '0x0000000f'
|
||||
};
|
||||
|
||||
const reg = new TypeRegistry();
|
||||
|
||||
reg.setSignedExtensions(fallbackExtensions.concat(['ChargeAssetTxPayment']));
|
||||
const ext = new ExtrinsicPayload(reg, TEST_VALUE);
|
||||
|
||||
expect(ext.assetId.toJSON()).toEqual({
|
||||
interior: {
|
||||
x2: [
|
||||
{
|
||||
palletInstance: 50
|
||||
},
|
||||
{
|
||||
generalIndex: 1984
|
||||
}
|
||||
]
|
||||
},
|
||||
parents: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SignOptions } from '@pezkuwi/keyring/types';
|
||||
import type { Hash, MultiLocation } from '@pezkuwi/types/interfaces';
|
||||
import type { Bytes } from '@pezkuwi/types-codec';
|
||||
import type { Inspect, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { BlockHash } from '../../interfaces/chain/index.js';
|
||||
import type { ExtrinsicEra } from '../../interfaces/extrinsics/index.js';
|
||||
import type { ExtrinsicPayloadValue, ICompact, IKeyringPair, INumber, IOption } from '../../types/index.js';
|
||||
|
||||
import { Enum, Struct } from '@pezkuwi/types-codec';
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { decodeAssetId } from '../ExtrinsicPayload.js';
|
||||
import { sign } from '../util.js';
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicPayloadV4
|
||||
* @description
|
||||
* A signing payload for an [[Extrinsic]]. For the final encoding, it is
|
||||
* variable length based on the contents included
|
||||
*/
|
||||
export class GenericExtrinsicPayloadV4 extends Struct {
|
||||
#signOptions: SignOptions;
|
||||
|
||||
constructor (registry: Registry, value?: ExtrinsicPayloadValue | Uint8Array | HexString) {
|
||||
super(registry, objectSpread(
|
||||
{ method: 'Bytes' },
|
||||
registry.getSignedExtensionTypes(),
|
||||
registry.getSignedExtensionExtra()
|
||||
), decodeAssetId(registry, value));
|
||||
|
||||
// Do detection for the type of extrinsic, in the case of MultiSignature
|
||||
// this is an enum, in the case of AnySignature, this is a Hash only
|
||||
// (which may be 64 or 65 bytes)
|
||||
this.#signOptions = {
|
||||
withType: registry.createTypeUnsafe('ExtrinsicSignature', []) instanceof Enum
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a breakdown of the hex encoding for this Codec
|
||||
*/
|
||||
public override inspect (): Inspect {
|
||||
return super.inspect({ method: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The block [[BlockHash]] the signature applies to (mortal/immortal)
|
||||
*/
|
||||
public get blockHash (): BlockHash {
|
||||
return this.getT('blockHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[ExtrinsicEra]]
|
||||
*/
|
||||
public get era (): ExtrinsicEra {
|
||||
return this.getT('era');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The genesis [[BlockHash]] the signature applies to (mortal/immortal)
|
||||
*/
|
||||
public get genesisHash (): BlockHash {
|
||||
return this.getT('genesisHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Bytes]] contained in the payload
|
||||
*/
|
||||
public get method (): Bytes {
|
||||
return this.getT('method');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Index]]
|
||||
*/
|
||||
public get nonce (): ICompact<INumber> {
|
||||
return this.getT('nonce');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The specVersion for this signature
|
||||
*/
|
||||
public get specVersion (): INumber {
|
||||
return this.getT('specVersion');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The tip [[Balance]]
|
||||
*/
|
||||
public get tip (): ICompact<INumber> {
|
||||
return this.getT('tip');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The transactionVersion for this signature
|
||||
*/
|
||||
public get transactionVersion (): INumber {
|
||||
return this.getT('transactionVersion');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) asset id for this signature for chains that support transaction fees in assets
|
||||
*/
|
||||
public get assetId (): IOption<INumber | MultiLocation> {
|
||||
return this.getT('assetId');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) asset id for this signature for chains that support transaction fees in assets
|
||||
*/
|
||||
public get metadataHash (): IOption<Hash> {
|
||||
return this.getT('metadataHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sign the payload with the keypair
|
||||
*/
|
||||
public sign (signerPair: IKeyringPair): Uint8Array {
|
||||
// NOTE The `toU8a({ method: true })` argument is absolutely critical, we
|
||||
// don't want the method (Bytes) to have the length prefix included. This
|
||||
// means that the data-as-signed is un-decodable, but is also doesn't need
|
||||
// the extra information, only the pure data (and is not decoded) ...
|
||||
// The same applies to V1..V3, if we have a V5, carry this comment
|
||||
return sign(this.registry, signerPair, this.toU8a({ method: true }), this.#signOptions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { createTestPairs } from '@pezkuwi/keyring/testingPairs';
|
||||
import metadataStatic from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
import { BN_ZERO } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from '../../create/index.js';
|
||||
import { Metadata } from '../../metadata/index.js';
|
||||
import { GenericExtrinsicSignatureV4 as ExtrinsicSignature } from './index.js';
|
||||
|
||||
const signOptions = {
|
||||
blockHash: '0x1234567890123456789012345678901234567890123456789012345678901234',
|
||||
genesisHash: '0x1234567890123456789012345678901234567890123456789012345678901234',
|
||||
nonce: '0x69',
|
||||
runtimeVersion: {
|
||||
apis: [],
|
||||
authoringVersion: BN_ZERO,
|
||||
implName: String('test'),
|
||||
implVersion: BN_ZERO,
|
||||
specName: String('test'),
|
||||
specVersion: BN_ZERO,
|
||||
transactionVersion: BN_ZERO
|
||||
}
|
||||
};
|
||||
|
||||
describe('ExtrinsicSignatureV4', (): void => {
|
||||
const pairs = createTestPairs({ type: 'ed25519' });
|
||||
|
||||
it('encodes to a sane Uint8Array (default)', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
const u8a = new Uint8Array([
|
||||
// signer as an AccountIndex
|
||||
0x01, 0x08, // 4 << 2
|
||||
// signature type
|
||||
0x01,
|
||||
// signature
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
// extra stuff
|
||||
0x00, // immortal,
|
||||
0x04, // nonce, compact
|
||||
0x08 // tip, compact
|
||||
]);
|
||||
|
||||
expect(
|
||||
new ExtrinsicSignature(registry, u8a, { isSigned: true }).toU8a()
|
||||
).toEqual(u8a);
|
||||
});
|
||||
|
||||
it('fake signs default', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
expect(
|
||||
new ExtrinsicSignature(registry).signFake(
|
||||
registry.createType('Call'),
|
||||
pairs.alice.publicKey,
|
||||
signOptions
|
||||
).toHex()
|
||||
).toEqual(
|
||||
'0x' +
|
||||
'00' + // MultiAddress
|
||||
'd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +
|
||||
'01' +
|
||||
'0101010101010101010101010101010101010101010101010101010101010101' +
|
||||
'0101010101010101010101010101010101010101010101010101010101010101' +
|
||||
'00a5010000' +
|
||||
'00' // Mode
|
||||
);
|
||||
});
|
||||
|
||||
it('fake signs default (AccountId address)', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
registry.register({
|
||||
Address: 'AccountId',
|
||||
ExtrinsicSignature: 'AnySignature'
|
||||
});
|
||||
|
||||
expect(
|
||||
new ExtrinsicSignature(registry).signFake(
|
||||
registry.createType('Call'),
|
||||
pairs.alice.address,
|
||||
signOptions
|
||||
).toHex()
|
||||
).toEqual(
|
||||
'0x' +
|
||||
// Address = AccountId, no prefix
|
||||
'd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +
|
||||
// This is a prefix-less signature, anySignture as opposed to Multi above
|
||||
'0101010101010101010101010101010101010101010101010101010101010101' +
|
||||
'0101010101010101010101010101010101010101010101010101010101010101' +
|
||||
'00a5010000' +
|
||||
'00' // mode
|
||||
);
|
||||
});
|
||||
|
||||
it('fake signs with non-enum signature', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
registry.register({
|
||||
Address: 'AccountId',
|
||||
ExtrinsicSignature: '[u8;65]'
|
||||
});
|
||||
|
||||
expect(
|
||||
new ExtrinsicSignature(registry).signFake(
|
||||
registry.createType('Call'),
|
||||
pairs.alice.address,
|
||||
signOptions
|
||||
).toHex()
|
||||
).toEqual(
|
||||
'0x' +
|
||||
// Address = AccountId, no prefix
|
||||
'd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +
|
||||
// 65 bytes here
|
||||
'01' +
|
||||
'0101010101010101010101010101010101010101010101010101010101010101' +
|
||||
'0101010101010101010101010101010101010101010101010101010101010101' +
|
||||
'00a5010000' +
|
||||
'00' // mode
|
||||
);
|
||||
});
|
||||
|
||||
it('injects a signature', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
expect(
|
||||
new ExtrinsicSignature(registry).addSignature(
|
||||
pairs.alice.publicKey,
|
||||
new Uint8Array(65).fill(1),
|
||||
new Uint8Array(0)
|
||||
).toHex()
|
||||
).toEqual(
|
||||
'0x' +
|
||||
'00' + // MultiAddress
|
||||
'd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +
|
||||
'01' +
|
||||
'0101010101010101010101010101010101010101010101010101010101010101' +
|
||||
'0101010101010101010101010101010101010101010101010101010101010101' +
|
||||
'00000000' +
|
||||
'00' // mode
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,229 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { MultiLocation } from '@pezkuwi/types/interfaces';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { EcdsaSignature, Ed25519Signature, ExtrinsicEra, ExtrinsicSignature, Sr25519Signature } from '../../interfaces/extrinsics/index.js';
|
||||
import type { Address, Call, Hash } from '../../interfaces/runtime/index.js';
|
||||
import type { ExtrinsicPayloadValue, ICompact, IExtrinsicSignature, IKeyringPair, INumber, IOption, Registry, SignatureOptions } from '../../types/index.js';
|
||||
import type { ExtrinsicSignatureOptions } from '../types.js';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
import { isU8a, isUndefined, objectProperties, objectSpread, stringify, u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
import { EMPTY_U8A, IMMORTAL_ERA } from '../constants.js';
|
||||
import { GenericExtrinsicPayloadV4 } from './ExtrinsicPayload.js';
|
||||
|
||||
// Ensure we have enough data for all types of signatures
|
||||
const FAKE_SIGNATURE = new Uint8Array(256).fill(1);
|
||||
|
||||
function toAddress (registry: Registry, address: Address | Uint8Array | string): Address {
|
||||
return registry.createTypeUnsafe('Address', [isU8a(address) ? u8aToHex(address) : address]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicSignatureV4
|
||||
* @description
|
||||
* A container for the [[Signature]] associated with a specific [[Extrinsic]]
|
||||
*/
|
||||
export class GenericExtrinsicSignatureV4 extends Struct implements IExtrinsicSignature {
|
||||
#signKeys: string[];
|
||||
|
||||
constructor (registry: Registry, value?: GenericExtrinsicSignatureV4 | Uint8Array, { isSigned }: ExtrinsicSignatureOptions = {}) {
|
||||
const signTypes = registry.getSignedExtensionTypes();
|
||||
|
||||
super(
|
||||
registry,
|
||||
objectSpread(
|
||||
// eslint-disable-next-line sort-keys
|
||||
{ signer: 'Address', signature: 'ExtrinsicSignature' },
|
||||
signTypes
|
||||
),
|
||||
GenericExtrinsicSignatureV4.decodeExtrinsicSignature(value, isSigned)
|
||||
);
|
||||
|
||||
this.#signKeys = Object.keys(signTypes);
|
||||
|
||||
objectProperties(this, this.#signKeys, (k) => this.get(k));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public static decodeExtrinsicSignature (value?: GenericExtrinsicSignatureV4 | Uint8Array, isSigned = false): GenericExtrinsicSignatureV4 | Uint8Array {
|
||||
if (!value) {
|
||||
return EMPTY_U8A;
|
||||
} else if (value instanceof GenericExtrinsicSignatureV4) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return isSigned
|
||||
? value
|
||||
: EMPTY_U8A;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the value when encoded as a Uint8Array
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
return this.isSigned
|
||||
? super.encodedLength
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` if the signature is valid
|
||||
*/
|
||||
public get isSigned (): boolean {
|
||||
return !this.signature.isEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[ExtrinsicEra]] (mortal or immortal) this signature applies to
|
||||
*/
|
||||
public get era (): ExtrinsicEra {
|
||||
return this.getT('era');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Index]] for the signature
|
||||
*/
|
||||
public get nonce (): ICompact<INumber> {
|
||||
return this.getT('nonce');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The actual [[EcdsaSignature]], [[Ed25519Signature]] or [[Sr25519Signature]]
|
||||
*/
|
||||
public get signature (): EcdsaSignature | Ed25519Signature | Sr25519Signature {
|
||||
// the second case here is when we don't have an enum signature, treat as raw
|
||||
return (this.multiSignature.value || this.multiSignature) as Sr25519Signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The raw [[ExtrinsicSignature]]
|
||||
*/
|
||||
public get multiSignature (): ExtrinsicSignature {
|
||||
return this.getT('signature');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Address]] that signed
|
||||
*/
|
||||
public get signer (): Address {
|
||||
return this.getT('signer');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Balance]] tip
|
||||
*/
|
||||
public get tip (): ICompact<INumber> {
|
||||
return this.getT('tip');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[u32]] or [[MultiLocation]] assetId
|
||||
*/
|
||||
public get assetId (): IOption<INumber | MultiLocation> {
|
||||
return this.getT('assetId');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the [[u32]] mode
|
||||
*/
|
||||
public get mode (): INumber {
|
||||
return this.getT('mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Hash]] for the metadata
|
||||
*/
|
||||
public get metadataHash (): IOption<Hash> {
|
||||
return this.getT('metadataHash');
|
||||
}
|
||||
|
||||
protected _injectSignature (signer: Address, signature: ExtrinsicSignature, payload: GenericExtrinsicPayloadV4): IExtrinsicSignature {
|
||||
// use the fields exposed to guide the getters
|
||||
for (let i = 0, count = this.#signKeys.length; i < count; i++) {
|
||||
const k = this.#signKeys[i];
|
||||
const v = payload.get(k);
|
||||
|
||||
if (!isUndefined(v)) {
|
||||
this.set(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
// additional fields (exposed in struct itself)
|
||||
this.set('signer', signer);
|
||||
this.set('signature', signature);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Adds a raw signature
|
||||
*/
|
||||
public addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | HexString, payload: ExtrinsicPayloadValue | Uint8Array | HexString): IExtrinsicSignature {
|
||||
return this._injectSignature(
|
||||
toAddress(this.registry, signer),
|
||||
this.registry.createTypeUnsafe('ExtrinsicSignature', [signature]),
|
||||
new GenericExtrinsicPayloadV4(this.registry, payload)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates a payload from the supplied options
|
||||
*/
|
||||
public createPayload (method: Call, options: SignatureOptions): GenericExtrinsicPayloadV4 {
|
||||
const { era, runtimeVersion: { specVersion, transactionVersion } } = options;
|
||||
|
||||
return new GenericExtrinsicPayloadV4(this.registry, objectSpread<ExtrinsicPayloadValue>({}, options, {
|
||||
era: era || IMMORTAL_ERA,
|
||||
method: method.toHex(),
|
||||
specVersion,
|
||||
transactionVersion
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Generate a payload and applies the signature from a keypair
|
||||
*/
|
||||
public sign (method: Call, account: IKeyringPair, options: SignatureOptions): IExtrinsicSignature {
|
||||
if (!account?.addressRaw) {
|
||||
throw new Error(`Expected a valid keypair for signing, found ${stringify(account)}`);
|
||||
}
|
||||
|
||||
const payload = this.createPayload(method, options);
|
||||
|
||||
return this._injectSignature(
|
||||
toAddress(this.registry, account.addressRaw),
|
||||
this.registry.createTypeUnsafe('ExtrinsicSignature', [payload.sign(account)]),
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Generate a payload and applies a fake signature
|
||||
*/
|
||||
public signFake (method: Call, address: Address | Uint8Array | string, options: SignatureOptions): IExtrinsicSignature {
|
||||
if (!address) {
|
||||
throw new Error(`Expected a valid address for signing, found ${stringify(address)}`);
|
||||
}
|
||||
|
||||
const payload = this.createPayload(method, options);
|
||||
|
||||
return this._injectSignature(
|
||||
toAddress(this.registry, address),
|
||||
this.registry.createTypeUnsafe('ExtrinsicSignature', [FAKE_SIGNATURE]),
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encodes the value as a Uint8Array as per the SCALE specifications
|
||||
* @param isBare true when the value has none of the type-specific prefixes (internal)
|
||||
*/
|
||||
public override toU8a (isBare?: boolean): Uint8Array {
|
||||
return this.isSigned
|
||||
? super.toU8a(isBare)
|
||||
: EMPTY_U8A;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { GenericExtrinsicV4 } from './Extrinsic.js';
|
||||
export { GenericExtrinsicPayloadV4 } from './ExtrinsicPayload.js';
|
||||
export { GenericExtrinsicSignatureV4 } from './ExtrinsicSignature.js';
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { createTestPairs } from '@pezkuwi/keyring/testingPairs';
|
||||
import rpcMetadata from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from '../../create/index.js';
|
||||
import { decorateExtrinsics, Metadata } from '../../metadata/index.js';
|
||||
import { GenericExtrinsicV5 as Extrinsic } from './index.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
const tx = decorateExtrinsics(registry, metadata.asLatest, metadata.version);
|
||||
|
||||
describe('ExtrinsicV5', (): void => {
|
||||
it('constructs a sane Uint8Array (default)', (): void => {
|
||||
const xt = new Extrinsic(registry);
|
||||
|
||||
// expect(`${xt.method.section}${xt.method.method}`).toEqual('system.fillBlock');
|
||||
expect(`${xt.method.section}.${xt.method.method}`).toEqual('system.remark');
|
||||
|
||||
expect(xt.toU8a()).toEqual(new Uint8Array([
|
||||
0, 0, // index
|
||||
// 0, 0, 0, 0 // fillBlock, Perbill
|
||||
0 // remark, Vec<u8>
|
||||
]));
|
||||
});
|
||||
|
||||
it('creates a unsigned extrinsic', (): void => {
|
||||
expect(
|
||||
new Extrinsic(
|
||||
registry,
|
||||
tx['balances']['transferAllowDeath'](keyring.bob.publicKey, 6969n)
|
||||
).toHex()
|
||||
).toEqual(
|
||||
'0x' +
|
||||
'0600' + // balance.transferAllowDeath
|
||||
'00' +
|
||||
'd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9' +
|
||||
'e56c'
|
||||
);
|
||||
});
|
||||
|
||||
it('Errors when creating a signed Extrinsic', (): void => {
|
||||
expect(
|
||||
() => new Extrinsic(
|
||||
registry,
|
||||
tx['balances']['transferAllowDeath'](keyring.bob.publicKey, 6969n)
|
||||
).sign(keyring.alice, {
|
||||
blockHash: '0xec7afaf1cca720ce88c1d1b689d81f0583cc15a97d621cf046dd9abf605ef22f',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
mode: 0,
|
||||
nonce: 1,
|
||||
runtimeVersion: {
|
||||
apis: [],
|
||||
authoringVersion: new BN(123),
|
||||
implName: 'test',
|
||||
implVersion: new BN(123),
|
||||
specName: 'test',
|
||||
specVersion: new BN(123),
|
||||
transactionVersion: new BN(123)
|
||||
},
|
||||
tip: 2
|
||||
}).toHex()
|
||||
).toThrow('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { ExtrinsicSignatureV5 } from '../../interfaces/extrinsics/index.js';
|
||||
import type { Address, Call } from '../../interfaces/runtime/index.js';
|
||||
import type { ExtrinsicPayloadValue, IExtrinsicV5Impl, IKeyringPair, Registry, SignatureOptions } from '../../types/index.js';
|
||||
import type { ExtrinsicOptions, Preamble } from '../types.js';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
import { isU8a } from '@pezkuwi/util';
|
||||
|
||||
export const EXTRINSIC_VERSION = 5;
|
||||
|
||||
export interface ExtrinsicValueV5 {
|
||||
method?: Call;
|
||||
signature?: ExtrinsicSignatureV5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicV5
|
||||
* @description
|
||||
* The fourth generation of compact extrinsics
|
||||
*/
|
||||
export class GenericExtrinsicV5 extends Struct implements IExtrinsicV5Impl {
|
||||
constructor (registry: Registry, value?: Uint8Array | ExtrinsicValueV5 | Call, { isSigned }: Partial<ExtrinsicOptions> = {}) {
|
||||
super(registry, {
|
||||
signature: 'ExtrinsicSignatureV5',
|
||||
// eslint-disable-next-line sort-keys
|
||||
method: 'Call'
|
||||
}, GenericExtrinsicV5.decodeExtrinsic(registry, value, isSigned));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public static decodeExtrinsic (registry: Registry, value?: Call | Uint8Array | ExtrinsicValueV5, isSigned = false): ExtrinsicValueV5 {
|
||||
if (value instanceof GenericExtrinsicV5) {
|
||||
return value;
|
||||
} else if (value instanceof registry.createClassUnsafe<Call>('Call')) {
|
||||
return { method: value };
|
||||
} else if (isU8a(value)) {
|
||||
// here we decode manually since we need to pull through the version information
|
||||
const signature = registry.createTypeUnsafe<ExtrinsicSignatureV5>('ExtrinsicSignatureV5', [value, { isSigned }]);
|
||||
// We add 2 here since the length of the TransactionExtension Version needs to be accounted for
|
||||
const method = registry.createTypeUnsafe<Call>('Call', [value.subarray(signature.encodedLength)]);
|
||||
|
||||
return {
|
||||
method,
|
||||
signature
|
||||
};
|
||||
}
|
||||
|
||||
return value || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the value when encoded as a Uint8Array
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
return this.toU8a().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Call]] this extrinsic wraps
|
||||
*/
|
||||
public get method (): Call {
|
||||
return this.getT('method');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[ExtrinsicSignatureV5]]
|
||||
*/
|
||||
public get signature (): ExtrinsicSignatureV5 {
|
||||
return this.getT('signature');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The version for the signature
|
||||
*/
|
||||
public get version (): number {
|
||||
return EXTRINSIC_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Preamble]] for the extrinsic
|
||||
*/
|
||||
public get preamble (): Preamble {
|
||||
return this.getT('preamble');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Add an [[ExtrinsicSignatureV5]] to the extrinsic (already generated)
|
||||
*
|
||||
* [Disabled for ExtrinsicV5]
|
||||
*/
|
||||
public addSignature (_signer: Address | Uint8Array | string, _signature: Uint8Array | HexString, _payload: ExtrinsicPayloadValue | Uint8Array | HexString): GenericExtrinsicV5 {
|
||||
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sign the extrinsic with a specific keypair
|
||||
*
|
||||
* [Disabled for ExtrinsicV5]
|
||||
*/
|
||||
public sign (_account: IKeyringPair, _options: SignatureOptions): GenericExtrinsicV5 {
|
||||
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
}
|
||||
|
||||
/**
|
||||
* @describe Adds a fake signature to the extrinsic
|
||||
*
|
||||
* [Disabled for ExtrinsicV5]
|
||||
*/
|
||||
public signFake (_signer: Address | Uint8Array | string, _options: SignatureOptions): GenericExtrinsicV5 {
|
||||
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import rpcMetadata from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
|
||||
import { TypeRegistry } from '../../create/index.js';
|
||||
import { decorateExtrinsics, Metadata } from '../../metadata/index.js';
|
||||
import { GenericExtrinsicPayloadV5 as ExtrinsicPayload } from './index.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
const tx = decorateExtrinsics(registry, metadata.asLatest, metadata.version);
|
||||
|
||||
describe('ExtrinsicPayload', (): void => {
|
||||
it('has a sane inspect', (): void => {
|
||||
// we don't expect this to fail, however it is actually a good
|
||||
// reference for the ordering in base Bizinikiwi
|
||||
expect(new ExtrinsicPayload(registry, { method: tx['timestamp']['set'](0).toHex() } as never).inspect()).toEqual({
|
||||
inner: [
|
||||
{ name: 'method', outer: [new Uint8Array([3, 0, 0])] },
|
||||
{ inner: undefined, name: 'era', outer: [new Uint8Array([0]), new Uint8Array([0])] },
|
||||
{ name: 'nonce', outer: [new Uint8Array([0])] },
|
||||
{ name: 'tip', outer: [new Uint8Array([0])] },
|
||||
{ name: 'assetId', outer: [new Uint8Array([0])] },
|
||||
{ name: 'mode', outer: [new Uint8Array([0])] },
|
||||
{ name: 'specVersion', outer: [new Uint8Array([0, 0, 0, 0])] },
|
||||
{ name: 'transactionVersion', outer: [new Uint8Array([0, 0, 0, 0])] },
|
||||
{ name: 'genesisHash', outer: [new Uint8Array(32)] },
|
||||
{ name: 'blockHash', outer: [new Uint8Array(32)] },
|
||||
{ name: 'metadataHash', outer: [new Uint8Array([0])] }
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Hash, MultiLocation } from '@pezkuwi/types/interfaces';
|
||||
import type { Bytes } from '@pezkuwi/types-codec';
|
||||
import type { Inspect, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { BlockHash } from '../../interfaces/chain/index.js';
|
||||
import type { ExtrinsicEra } from '../../interfaces/extrinsics/index.js';
|
||||
import type { ExtrinsicPayloadValue, ICompact, IKeyringPair, INumber, IOption } from '../../types/index.js';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicPayloadV5
|
||||
* @description
|
||||
* A signing payload for an [[Extrinsic]]. For the final encoding, it is
|
||||
* variable length based on the contents included
|
||||
*/
|
||||
export class GenericExtrinsicPayloadV5 extends Struct {
|
||||
constructor (registry: Registry, value?: ExtrinsicPayloadValue | Uint8Array | HexString) {
|
||||
super(registry, objectSpread(
|
||||
{ method: 'Bytes' },
|
||||
registry.getSignedExtensionTypes(),
|
||||
registry.getSignedExtensionExtra()
|
||||
), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a breakdown of the hex encoding for this Codec
|
||||
*/
|
||||
public override inspect (): Inspect {
|
||||
return super.inspect({ method: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The block [[BlockHash]] the signature applies to (mortal/immortal)
|
||||
*/
|
||||
public get blockHash (): BlockHash {
|
||||
return this.getT('blockHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[ExtrinsicEra]]
|
||||
*/
|
||||
public get era (): ExtrinsicEra {
|
||||
return this.getT('era');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The genesis [[BlockHash]] the signature applies to (mortal/immortal)
|
||||
*/
|
||||
public get genesisHash (): BlockHash {
|
||||
return this.getT('genesisHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Bytes]] contained in the payload
|
||||
*/
|
||||
public get method (): Bytes {
|
||||
return this.getT('method');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Index]]
|
||||
*/
|
||||
public get nonce (): ICompact<INumber> {
|
||||
return this.getT('nonce');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The specVersion for this signature
|
||||
*/
|
||||
public get specVersion (): INumber {
|
||||
return this.getT('specVersion');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The tip [[Balance]]
|
||||
*/
|
||||
public get tip (): ICompact<INumber> {
|
||||
return this.getT('tip');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The transactionVersion for this signature
|
||||
*/
|
||||
public get transactionVersion (): INumber {
|
||||
return this.getT('transactionVersion');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) asset id for this signature for chains that support transaction fees in assets
|
||||
*/
|
||||
public get assetId (): IOption<INumber | MultiLocation> {
|
||||
return this.getT('assetId');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) metadataHash proof for the CheckMetadataHash TransactionExtension
|
||||
*/
|
||||
public get metadataHash (): IOption<Hash> {
|
||||
return this.getT('metadataHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sign the payload with the keypair
|
||||
*
|
||||
* [Disabled for ExtrinsicV5]
|
||||
*/
|
||||
public sign (_signerPair: IKeyringPair): Uint8Array {
|
||||
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { createTestPairs } from '@pezkuwi/keyring/testingPairs';
|
||||
import metadataStatic from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
import { BN_ZERO } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from '../../create/index.js';
|
||||
import { Metadata } from '../../metadata/index.js';
|
||||
import { GenericExtrinsicSignatureV5 as ExtrinsicSignature } from './index.js';
|
||||
|
||||
const signOptions = {
|
||||
blockHash: '0x1234567890123456789012345678901234567890123456789012345678901234',
|
||||
genesisHash: '0x1234567890123456789012345678901234567890123456789012345678901234',
|
||||
nonce: '0x69',
|
||||
runtimeVersion: {
|
||||
apis: [],
|
||||
authoringVersion: BN_ZERO,
|
||||
implName: String('test'),
|
||||
implVersion: BN_ZERO,
|
||||
specName: String('test'),
|
||||
specVersion: BN_ZERO,
|
||||
transactionVersion: BN_ZERO
|
||||
}
|
||||
};
|
||||
|
||||
describe('ExtrinsicSignatureV4', (): void => {
|
||||
const pairs = createTestPairs({ type: 'ed25519' });
|
||||
|
||||
it('encodes to a sane Uint8Array (default)', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
const u8a = new Uint8Array([
|
||||
// signer as an AccountIndex
|
||||
0x01, 0x08, // 4 << 2
|
||||
// signature type
|
||||
0x01,
|
||||
// signature
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x00, // TransactionExtension version
|
||||
// extra stuff
|
||||
0x00, // immortal,
|
||||
0x04, // nonce, compact
|
||||
0x08 // tip, compact
|
||||
]);
|
||||
|
||||
expect(
|
||||
new ExtrinsicSignature(registry, u8a, { isSigned: true }).toU8a()
|
||||
).toEqual(u8a);
|
||||
});
|
||||
|
||||
it('fake signs default', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
expect(
|
||||
() => new ExtrinsicSignature(registry).signFake(
|
||||
registry.createType('Call'),
|
||||
pairs.alice.publicKey,
|
||||
signOptions
|
||||
).toHex()
|
||||
).toThrow('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
});
|
||||
|
||||
it('Errors on fake sign', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
registry.register({
|
||||
Address: 'AccountId',
|
||||
ExtrinsicSignature: 'AnySignature'
|
||||
});
|
||||
|
||||
expect(
|
||||
() => new ExtrinsicSignature(registry).signFake(
|
||||
registry.createType('Call'),
|
||||
pairs.alice.address,
|
||||
signOptions
|
||||
).toHex()
|
||||
).toThrow('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
});
|
||||
|
||||
it('Errors on injecting a signature', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
expect(
|
||||
() => new ExtrinsicSignature(registry).addSignature(
|
||||
pairs.alice.publicKey,
|
||||
new Uint8Array(65).fill(1),
|
||||
new Uint8Array(0)
|
||||
).toHex()
|
||||
).toThrow('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { MultiLocation } from '@pezkuwi/types/interfaces';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { EcdsaSignature, Ed25519Signature, ExtrinsicEra, ExtrinsicSignature, Sr25519Signature } from '../../interfaces/extrinsics/index.js';
|
||||
import type { Address, Call, Hash } from '../../interfaces/runtime/index.js';
|
||||
import type { ExtrinsicPayloadValue, ICompact, IExtrinsicSignature, IKeyringPair, INumber, IOption, Registry, SignatureOptions } from '../../types/index.js';
|
||||
import type { ExtrinsicSignatureOptions } from '../types.js';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
import { objectProperties, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import { EMPTY_U8A, IMMORTAL_ERA } from '../constants.js';
|
||||
import { GenericExtrinsicPayloadV5 } from './ExtrinsicPayload.js';
|
||||
|
||||
/**
|
||||
* @name GenericExtrinsicSignatureV5
|
||||
* @description
|
||||
* A container for the [[Signature]] associated with a specific [[Extrinsic]]
|
||||
*/
|
||||
export class GenericExtrinsicSignatureV5 extends Struct implements IExtrinsicSignature {
|
||||
#signKeys: string[];
|
||||
|
||||
constructor (registry: Registry, value?: GenericExtrinsicSignatureV5 | Uint8Array, { isSigned }: ExtrinsicSignatureOptions = {}) {
|
||||
const signTypes = registry.getSignedExtensionTypes();
|
||||
|
||||
super(
|
||||
registry,
|
||||
objectSpread(
|
||||
// eslint-disable-next-line sort-keys
|
||||
{ signer: 'Address', signature: 'ExtrinsicSignature', transactionExtensionVersion: 'u8' },
|
||||
signTypes
|
||||
),
|
||||
GenericExtrinsicSignatureV5.decodeExtrinsicSignature(value, isSigned)
|
||||
);
|
||||
|
||||
this.#signKeys = Object.keys(signTypes);
|
||||
|
||||
objectProperties(this, this.#signKeys, (k) => this.get(k));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public static decodeExtrinsicSignature (value?: GenericExtrinsicSignatureV5 | Uint8Array, isSigned = false): GenericExtrinsicSignatureV5 | Uint8Array {
|
||||
if (!value) {
|
||||
return EMPTY_U8A;
|
||||
} else if (value instanceof GenericExtrinsicSignatureV5) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return isSigned
|
||||
? value
|
||||
: EMPTY_U8A;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the value when encoded as a Uint8Array
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
return this.isSigned
|
||||
? super.encodedLength
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` if the signature is valid
|
||||
*/
|
||||
public get isSigned (): boolean {
|
||||
return !this.signature.isEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[ExtrinsicEra]] (mortal or immortal) this signature applies to
|
||||
*/
|
||||
public get era (): ExtrinsicEra {
|
||||
return this.getT('era');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Index]] for the signature
|
||||
*/
|
||||
public get nonce (): ICompact<INumber> {
|
||||
return this.getT('nonce');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The actual [[EcdsaSignature]], [[Ed25519Signature]] or [[Sr25519Signature]]
|
||||
*/
|
||||
public get signature (): EcdsaSignature | Ed25519Signature | Sr25519Signature {
|
||||
// the second case here is when we don't have an enum signature, treat as raw
|
||||
return (this.multiSignature.value || this.multiSignature) as Sr25519Signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The raw [[ExtrinsicSignature]]
|
||||
*/
|
||||
public get multiSignature (): ExtrinsicSignature {
|
||||
return this.getT('signature');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Address]] that signed
|
||||
*/
|
||||
public get signer (): Address {
|
||||
return this.getT('signer');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Balance]] tip
|
||||
*/
|
||||
public get tip (): ICompact<INumber> {
|
||||
return this.getT('tip');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[u32]] or [[MultiLocation]] assetId
|
||||
*/
|
||||
public get assetId (): IOption<INumber | MultiLocation> {
|
||||
return this.getT('assetId');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the [[u32]] mode
|
||||
*/
|
||||
public get mode (): INumber {
|
||||
return this.getT('mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) [[Hash]] for the metadata proof
|
||||
*/
|
||||
public get metadataHash (): IOption<Hash> {
|
||||
return this.getT('metadataHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[u8]] for the TransactionExtension version
|
||||
*/
|
||||
public get transactionExtensionVersion (): INumber {
|
||||
return this.getT('transactionExtensionVersion');
|
||||
}
|
||||
|
||||
/**
|
||||
* [Disabled for ExtrinsicV5]
|
||||
*/
|
||||
protected _injectSignature (_signer: Address, _signature: ExtrinsicSignature, _payload: GenericExtrinsicPayloadV5): IExtrinsicSignature {
|
||||
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Adds a raw signature
|
||||
*
|
||||
* [Disabled for ExtrinsicV5]
|
||||
*/
|
||||
public addSignature (_signer: Address | Uint8Array | string, _signature: Uint8Array | HexString, _payload: ExtrinsicPayloadValue | Uint8Array | HexString): IExtrinsicSignature {
|
||||
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates a payload from the supplied options
|
||||
*/
|
||||
public createPayload (method: Call, options: SignatureOptions): GenericExtrinsicPayloadV5 {
|
||||
const { era, runtimeVersion: { specVersion, transactionVersion } } = options;
|
||||
|
||||
return new GenericExtrinsicPayloadV5(this.registry, objectSpread<ExtrinsicPayloadValue>({}, options, {
|
||||
era: era || IMMORTAL_ERA,
|
||||
method: method.toHex(),
|
||||
specVersion,
|
||||
transactionVersion
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Generate a payload and applies the signature from a keypair
|
||||
*
|
||||
* [Disabled for ExtrinsicV5]
|
||||
*/
|
||||
public sign (_method: Call, _account: IKeyringPair, _options: SignatureOptions): IExtrinsicSignature {
|
||||
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Generate a payload and applies a fake signature
|
||||
*
|
||||
* [Disabled for ExtrinsicV5]
|
||||
*/
|
||||
public signFake (_method: Call, _address: Address | Uint8Array | string, _options: SignatureOptions): IExtrinsicSignature {
|
||||
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encodes the value as a Uint8Array as per the SCALE specifications
|
||||
* @param isBare true when the value has none of the type-specific prefixes (internal)
|
||||
*/
|
||||
public override toU8a (isBare?: boolean): Uint8Array {
|
||||
return this.isSigned
|
||||
? super.toU8a(isBare)
|
||||
: EMPTY_U8A;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import rpcMetadata from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
|
||||
import { TypeRegistry } from '../../create/index.js';
|
||||
import { Metadata } from '../../metadata/index.js';
|
||||
import { GeneralExtrinsic } from './GeneralExtrinsic.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, rpcMetadata);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
describe('GeneralExt', (): void => {
|
||||
const extrinsic = '0xc44500650000000000060000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d0700e40b5402';
|
||||
|
||||
it('Can decode a general extrinsic', (): void => {
|
||||
const genExt = new GeneralExtrinsic(registry, extrinsic);
|
||||
|
||||
expect(genExt.version).toEqual(5);
|
||||
expect(genExt.transactionExtensionVersion.toNumber()).toEqual(0);
|
||||
expect(genExt.method.toHuman()).toEqual({ args: { dest: { Id: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY' }, value: '10,000,000,000' }, method: 'transferAllowDeath', section: 'balances' });
|
||||
expect(genExt.era.toHuman()).toEqual({ MortalEra: { period: '64', phase: '6' } });
|
||||
expect(genExt.tip.toNumber()).toEqual(0);
|
||||
expect(genExt.mode.toNumber()).toEqual(0);
|
||||
expect(genExt.assetId.toHuman()).toEqual(null);
|
||||
expect(genExt.nonce.toNumber()).toEqual(0);
|
||||
});
|
||||
|
||||
it('Can encode a general extrinsic', (): void => {
|
||||
const payload = {
|
||||
era: '0x6500',
|
||||
method: '0x060000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d0700e40b5402',
|
||||
nonce: '0x00000000',
|
||||
tip: '0x00000000000000000000000000000000',
|
||||
transactionVersion: '0x00000002'
|
||||
};
|
||||
const genExt = new GeneralExtrinsic(registry, {
|
||||
payload
|
||||
});
|
||||
|
||||
expect(genExt.toHex()).toEqual(extrinsic);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Call, ExtrinsicEra, Hash, MultiLocation } from '@pezkuwi/types/interfaces';
|
||||
import type { AnyNumber, AnyU8a, ICompact, IExtrinsicEra, INumber, IOption, Registry } from '@pezkuwi/types/types';
|
||||
import type { AnyTuple, IMethod } from '@pezkuwi/types-codec/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
import { compactAddLength, compactFromU8a, isHex, isObject, isU8a, objectSpread, u8aConcat, u8aToHex, u8aToU8a } from '@pezkuwi/util';
|
||||
|
||||
import { EMPTY_U8A, UNMASK_VERSION } from '../constants.js';
|
||||
|
||||
interface TransactionExtensionValues {
|
||||
era: AnyU8a | IExtrinsicEra;
|
||||
nonce: AnyNumber;
|
||||
tip: AnyNumber;
|
||||
transactionVersion: AnyNumber;
|
||||
assetId?: HexString;
|
||||
mode?: AnyNumber;
|
||||
metadataHash?: AnyU8a;
|
||||
}
|
||||
|
||||
interface GeneralExtrinsicPayloadValues extends TransactionExtensionValues {
|
||||
method: AnyU8a | IMethod<AnyTuple>;
|
||||
}
|
||||
|
||||
interface GeneralExtrinsicValue {
|
||||
payload?: GeneralExtrinsicPayloadValues;
|
||||
transactionExtensionVersion?: number;
|
||||
}
|
||||
|
||||
function decodeU8a (u8a: Uint8Array) {
|
||||
if (!u8a.length) {
|
||||
return new Uint8Array();
|
||||
}
|
||||
|
||||
const [offset, length] = compactFromU8a(u8a);
|
||||
const total = offset + length.toNumber();
|
||||
|
||||
if (total > u8a.length) {
|
||||
throw new Error(`Extrinsic: length less than remainder, expected at least ${total}, found ${u8a.length}`);
|
||||
}
|
||||
|
||||
const data = u8a.subarray(offset, total);
|
||||
|
||||
// 69 denotes 0b01000101 which is the version and preamble for this Extrinsic
|
||||
if (data[0] !== 69) {
|
||||
throw new Error(`Extrinsic: incorrect version for General Transactions, expected 5, found ${data[0] & UNMASK_VERSION}`);
|
||||
}
|
||||
|
||||
return data.subarray(1);
|
||||
}
|
||||
|
||||
export class GeneralExtrinsic extends Struct {
|
||||
#version: number;
|
||||
#preamble: number;
|
||||
|
||||
constructor (registry: Registry, value?: GeneralExtrinsicValue | Uint8Array | HexString, opt?: { version: number }) {
|
||||
const extTypes = registry.getSignedExtensionTypes();
|
||||
|
||||
super(registry, objectSpread(
|
||||
{
|
||||
transactionExtensionVersion: 'u8'
|
||||
},
|
||||
extTypes,
|
||||
{
|
||||
method: 'Call'
|
||||
}
|
||||
), GeneralExtrinsic.decodeExtrinsic(registry, value));
|
||||
|
||||
this.#version = opt?.version || 0b00000101;
|
||||
this.#preamble = 0b01000000;
|
||||
}
|
||||
|
||||
public static decodeExtrinsic (registry: Registry, value?: GeneralExtrinsicValue | Uint8Array | HexString) {
|
||||
if (!value) {
|
||||
return EMPTY_U8A;
|
||||
} else if (value instanceof GeneralExtrinsic) {
|
||||
return value;
|
||||
} else if (isU8a(value) || Array.isArray(value) || isHex(value)) {
|
||||
return decodeU8a(u8aToU8a(value));
|
||||
} else if (isObject(value)) {
|
||||
const { payload, transactionExtensionVersion } = value;
|
||||
|
||||
return objectSpread(payload || {}, {
|
||||
transactionExtensionVersion: transactionExtensionVersion || registry.getTransactionExtensionVersion()
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the value when encoded as a Uint8Array
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
return super.encodedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[ExtrinsicEra]]
|
||||
*/
|
||||
public get era (): ExtrinsicEra {
|
||||
return this.getT('era');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Index]]
|
||||
*/
|
||||
public get nonce (): ICompact<INumber> {
|
||||
return this.getT('nonce');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The tip [[Balance]]
|
||||
*/
|
||||
public get tip (): ICompact<INumber> {
|
||||
return this.getT('tip');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) asset id for this signature for chains that support transaction fees in assets
|
||||
*/
|
||||
public get assetId (): IOption<INumber | MultiLocation> {
|
||||
return this.getT('assetId');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The mode used for the CheckMetadataHash TransactionExtension
|
||||
*/
|
||||
public get mode (): INumber {
|
||||
return this.getT('mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The (optional) [[Hash]] for the metadata proof
|
||||
*/
|
||||
public get metadataHash (): IOption<Hash> {
|
||||
return this.getT('metadataHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The version of the TransactionExtensions used in this extrinsic
|
||||
*/
|
||||
public get transactionExtensionVersion (): INumber {
|
||||
return this.getT('transactionExtensionVersion');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Call]] this extrinsic wraps
|
||||
*/
|
||||
public get method (): Call {
|
||||
return this.getT('method');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The extrinsic's version
|
||||
*/
|
||||
public get version () {
|
||||
return this.#version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Preamble]] for the extrinsic
|
||||
*/
|
||||
public get preamble () {
|
||||
return this.#preamble;
|
||||
}
|
||||
|
||||
public override toHex (isBare?: boolean): HexString {
|
||||
return u8aToHex(this.toU8a(isBare));
|
||||
}
|
||||
|
||||
public override toU8a (isBare?: boolean): Uint8Array {
|
||||
return isBare
|
||||
? this.encode()
|
||||
: compactAddLength(this.encode());
|
||||
}
|
||||
|
||||
public override toRawType () {
|
||||
return 'GeneralExt';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Returns an encoded GeneralExtrinsic
|
||||
*/
|
||||
public encode () {
|
||||
return u8aConcat(new Uint8Array([this.version | this.preamble]), super.toU8a());
|
||||
}
|
||||
|
||||
public signFake () {
|
||||
throw new Error('Extrinsic: Type GeneralExtrinsic does not have signFake implemented');
|
||||
}
|
||||
|
||||
public addSignature () {
|
||||
throw new Error('Extrinsic: Type GeneralExtrinsic does not have addSignature implemented');
|
||||
}
|
||||
|
||||
public sign () {
|
||||
throw new Error('Extrinsic: Type GeneralExtrinsic does not have sign implemented');
|
||||
}
|
||||
|
||||
public signature () {
|
||||
throw new Error('Extrinsic: Type GeneralExtrinsic does not have the signature getter');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { GenericExtrinsicV5 } from './Extrinsic.js';
|
||||
export { GenericExtrinsicPayloadV5 } from './ExtrinsicPayload.js';
|
||||
export { GenericExtrinsicSignatureV5 } from './ExtrinsicSignature.js';
|
||||
export { GeneralExtrinsic } from './GeneralExtrinsic.js';
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
import { Raw } from '@pezkuwi/types-codec';
|
||||
import jsonVec from '@pezkuwi/types-support/json/AccountIdVec.001.json' assert { type: 'json' };
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
|
||||
describe('AccountId', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
describe('defaults', (): void => {
|
||||
const id = registry.createType('AccountId');
|
||||
|
||||
it('has a 32-byte length', (): void => {
|
||||
expect(id).toHaveLength(32);
|
||||
});
|
||||
|
||||
it('is empty by default', (): void => {
|
||||
expect(id.isEmpty).toBe(true);
|
||||
});
|
||||
|
||||
it('equals the empty address', (): void => {
|
||||
expect(id.eq('5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM')).toBe(true);
|
||||
});
|
||||
|
||||
it('allows decoding from null', (): void => {
|
||||
expect(
|
||||
registry
|
||||
.createType('AccountId', null)
|
||||
.eq('5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM')
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decoding', (): void => {
|
||||
const testDecode = (type: string, input: Uint8Array | string, expected: string, createType = 'AccountId'): void =>
|
||||
it(`can decode from ${type}`, (): void => {
|
||||
expect(
|
||||
registry
|
||||
.createType(createType, input)
|
||||
.toString()
|
||||
).toBe(expected);
|
||||
});
|
||||
|
||||
it('fails with non-32-byte lengths', (): void => {
|
||||
expect(
|
||||
() => registry.createType('AccountId', '0x1234')
|
||||
).toThrow(/Invalid AccountId provided, expected 32 bytes/);
|
||||
});
|
||||
|
||||
it('correctly encodes for AccountId32', (): void => {
|
||||
const s = '5CHCWUYMmDGeJjiuaQ1LnrsAWacDhiTAV6vCfytSxoqBdCCb';
|
||||
const h = '0x0987654309876543098765430987654309876543098765430987654309876543';
|
||||
const a = registry.createType('AccountId32', s);
|
||||
|
||||
expect(a).toHaveLength(32);
|
||||
expect(a.toHex()).toEqual(h);
|
||||
expect(a.toString()).toEqual(s);
|
||||
expect(registry.createType('AccountId32', h).toString()).toEqual(s);
|
||||
});
|
||||
|
||||
it('correctly encodes for AccountId33', (): void => {
|
||||
const s = 'KWnVo6ZQe9A3fHZy4QYWMR6QyZLT4hxUs37pX465bKhfsMobh';
|
||||
const h = '0x098765430987654309876543098765430987654309876543098765430987654309';
|
||||
const a = registry.createType('AccountId33', s);
|
||||
|
||||
expect(a).toHaveLength(33);
|
||||
expect(a.toHex()).toEqual(h);
|
||||
expect(a.toString()).toEqual(s);
|
||||
expect(registry.createType('AccountId33', h).toString()).toEqual(s);
|
||||
});
|
||||
|
||||
testDecode(
|
||||
'AccountId',
|
||||
registry.createType('AccountId', '0x0102030405060708010203040506070801020304050607080102030405060708'),
|
||||
'5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'
|
||||
);
|
||||
testDecode(
|
||||
'AccountId33',
|
||||
registry.createType('AccountId33', '0x098765430987654309876543098765430987654309876543098765430987654309'),
|
||||
'KWnVo6ZQe9A3fHZy4QYWMR6QyZLT4hxUs37pX465bKhfsMobh',
|
||||
'AccountId33'
|
||||
);
|
||||
testDecode('hex', '0x0102030405060708010203040506070801020304050607080102030405060708', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF');
|
||||
testDecode('string', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF');
|
||||
testDecode(
|
||||
'Raw',
|
||||
new Raw(registry, [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
]),
|
||||
'5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array',
|
||||
Uint8Array.from([
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
]),
|
||||
'5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'
|
||||
);
|
||||
});
|
||||
|
||||
describe('encoding', (): void => {
|
||||
const testEncode = (to: 'toHex' | 'toJSON' | 'toString' | 'toU8a', expected: Uint8Array | string, input = '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'): void =>
|
||||
it(`can encode ${to}`, (): void => {
|
||||
expect(
|
||||
registry.createType('AccountId', input)[to]()
|
||||
).toEqual(expected);
|
||||
});
|
||||
|
||||
testEncode('toHex', '0x0102030405060708010203040506070801020304050607080102030405060708');
|
||||
testEncode('toJSON', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF');
|
||||
testEncode('toString', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF');
|
||||
testEncode('toString', '5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM', '0x0000000000000000000000000000000000000000000000000000000000000000');
|
||||
testEncode('toU8a', Uint8Array.from([
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
]));
|
||||
|
||||
it('decodes to a non-empty value', (): void => {
|
||||
expect(
|
||||
registry
|
||||
.createType('AccountId', 'FVU8T5yaAssHAUwo3PnKso7fqNthEqJZGCZgJZSR9cKjWAf')
|
||||
.isEmpty
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('encodes a 2-byte key correctly', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
registry.setChainProperties(
|
||||
registry.createType('ChainProperties', {
|
||||
ss58Format: 252
|
||||
})
|
||||
);
|
||||
|
||||
expect(
|
||||
registry
|
||||
.createType('AccountId', '0x5c64f1151f0ce4358c27238fb20c88e7c899825436f565410724c8c2c5add869')
|
||||
.toString()
|
||||
).toEqual('xw5g1Eec8LT99pZLZMaTWwrwvNtfM6vrSuZeVbtEszCDUwByg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('storage decoding', (): void => {
|
||||
it('has the correct entries', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
registry.setChainProperties(
|
||||
registry.createType('ChainProperties', {
|
||||
ss58Format: 2
|
||||
})
|
||||
);
|
||||
|
||||
const data = registry.createType('StorageData', jsonVec.params.result.changes[0][1]);
|
||||
|
||||
expect(
|
||||
registry
|
||||
.createType('Vec<AccountId>', data)
|
||||
.map((accountId) => accountId.toString())
|
||||
).toEqual([
|
||||
'FXmrFbdg2VdG1NttGTx1S6Czbv2SQ7PaD2PfryGBWWgt52i',
|
||||
'ELSnAoxwXaRP5i4RPjs6m1K3AGVCfXpt8GjTcrgtxvrAuZv',
|
||||
'EaoT1Mn6H3N9Ui3WdiLGTdKgjGChbrpLfnNV5zSLJLhZvgj',
|
||||
'FVU8T5yaAssHAUwo3PnKso7fqNthEqJZGCZgJZSR9cKjWAf',
|
||||
'GCgYfZti2kHwWoixFJ9toWZPZrcesc8CZDAoBGhiUNKvTQM',
|
||||
'DMYFwYVNgLqpiD73eoLEMLnzszXyEnvhxL1bPGhSGXKV8kS',
|
||||
'CfdSPZKSb529oMZ9HeFw6p9fhiGS6i45FEgFaJazhmBzsxZ',
|
||||
'DNaSBo4xxQMjwbhAZwSzaK8taaBTKVXtnNygqYUpXS4FMwN'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyString, AnyU8a, Registry, U8aBitLength } from '@pezkuwi/types-codec/types';
|
||||
|
||||
import { U8aFixed } from '@pezkuwi/types-codec';
|
||||
import { hexToU8a, isHex, isString, isU8a, u8aToU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress, encodeAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
/** @internal */
|
||||
function decodeAccountId (value?: AnyU8a | AnyString): Uint8Array {
|
||||
if (isU8a(value) || Array.isArray(value)) {
|
||||
return u8aToU8a(value);
|
||||
} else if (!value) {
|
||||
return new Uint8Array();
|
||||
} else if (isHex(value)) {
|
||||
return hexToU8a(value);
|
||||
} else if (isString(value)) {
|
||||
return decodeAddress(value.toString());
|
||||
}
|
||||
|
||||
throw new Error(`Unknown type passed to AccountId constructor, found typeof ${typeof value}`);
|
||||
}
|
||||
|
||||
class BaseAccountId extends U8aFixed {
|
||||
constructor (registry: Registry, allowedBits = 256 | 264, value?: AnyU8a) {
|
||||
const decoded = decodeAccountId(value);
|
||||
const decodedBits = decoded.length * 8;
|
||||
|
||||
// Part of stream containing >= 32 bytes or a all empty (defaults)
|
||||
if (decodedBits < allowedBits && decoded.some((b) => b)) {
|
||||
throw new Error(`Invalid AccountId provided, expected ${allowedBits >> 3} bytes, found ${decoded.length}`);
|
||||
}
|
||||
|
||||
super(registry, decoded, allowedBits as U8aBitLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Compares the value of the input to see if there is a match
|
||||
*/
|
||||
public override eq (other?: unknown): boolean {
|
||||
return super.eq(decodeAccountId(other as AnyU8a));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (): string {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to JSON, typically used for RPC transfers
|
||||
*/
|
||||
public override toJSON (): string {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the value in a best-fit primitive form
|
||||
*/
|
||||
public override toPrimitive (): string {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the string representation of the value
|
||||
*/
|
||||
public override toString (): string {
|
||||
return encodeAddress(this, this.registry.chainSS58);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'AccountId';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericAccountId
|
||||
* @description
|
||||
* A wrapper around an AccountId/PublicKey representation. Since we are dealing with
|
||||
* underlying PublicKeys (32 bytes in length), we extend from U8aFixed which is
|
||||
* just a Uint8Array wrapper with a fixed length.
|
||||
* If constructed with an empty value ([], "", undefined) it will result in
|
||||
* the zero account 0x000...000.
|
||||
*/
|
||||
export class GenericAccountId extends BaseAccountId {
|
||||
constructor (registry: Registry, value?: AnyU8a) {
|
||||
super(registry, 256, value);
|
||||
}
|
||||
}
|
||||
|
||||
export class GenericAccountId33 extends BaseAccountId {
|
||||
constructor (registry: Registry, value?: AnyU8a) {
|
||||
super(registry, 264, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* eslint-disable jest/expect-expect */
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { GenericAccountIndex as AccountIndex } from './index.js';
|
||||
|
||||
describe('AccountIndex', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('creates a BN representation', (): void => {
|
||||
expect(
|
||||
registry.createType('AccountIndex', new Uint8Array([17, 18, 19, 20])).toNumber()
|
||||
).toEqual(336794129);
|
||||
});
|
||||
|
||||
it('creates from BigInt', (): void => {
|
||||
expect(
|
||||
registry.createType('AccountIndex', 336794129n).toNumber()
|
||||
).toEqual(336794129);
|
||||
});
|
||||
|
||||
it('creates a BN representation (from ss-58)', (): void => {
|
||||
expect(
|
||||
registry.createType('AccountIndex', 'Mwz15xP2').toNumber()
|
||||
).toEqual(336794129);
|
||||
});
|
||||
|
||||
it('constructs 2-byte from number', (): void => {
|
||||
expect(
|
||||
registry.createType('AccountIndex', 256 * 1).toString()
|
||||
).toEqual('25GUyv');
|
||||
});
|
||||
|
||||
it('constructs from number', (): void => {
|
||||
expect(
|
||||
registry.createType('AccountIndex', new BN(336794129)).toString()
|
||||
).toEqual('Mwz15xP2');
|
||||
});
|
||||
|
||||
it('compares ss-58 values', (): void => {
|
||||
expect(registry.createType('AccountIndex', 256 * 1).eq('25GUyv')).toBe(true);
|
||||
});
|
||||
|
||||
it('compares numbers', (): void => {
|
||||
expect(registry.createType('AccountIndex', '118r').eq(256 * 1)).toBe(true);
|
||||
});
|
||||
|
||||
describe('calcLength', (): void => {
|
||||
const testLength = (value: number, length: number): void => {
|
||||
expect(AccountIndex.calcLength(value)).toEqual(length);
|
||||
};
|
||||
|
||||
it('returns 1 for <= 0xef', (): void => {
|
||||
testLength(0xef, 1);
|
||||
});
|
||||
|
||||
it('returns 2 for > 0xef', (): void => {
|
||||
testLength(0xf0, 2);
|
||||
});
|
||||
|
||||
it('returns 4 bytes for 32-bit inputs', (): void => {
|
||||
testLength(0xffeeddcc, 4);
|
||||
});
|
||||
|
||||
it('returns 8 bytes for larger inputs', (): void => {
|
||||
testLength(0x122334455, 8);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyNumber, Registry } from '@pezkuwi/types-codec/types';
|
||||
|
||||
import { u32 } from '@pezkuwi/types-codec';
|
||||
import { BN, bnToBn, isBigInt, isBn, isHex, isNumber, isU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress, encodeAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
const PREFIX_1BYTE = 0xef;
|
||||
const PREFIX_2BYTE = 0xfc;
|
||||
const PREFIX_4BYTE = 0xfd;
|
||||
const PREFIX_8BYTE = 0xfe;
|
||||
const MAX_1BYTE = new BN(PREFIX_1BYTE);
|
||||
const MAX_2BYTE = new BN(1).shln(16);
|
||||
const MAX_4BYTE = new BN(1).shln(32);
|
||||
|
||||
/** @internal */
|
||||
function decodeAccountIndex (value: AnyNumber): BN | bigint | Uint8Array | number | string {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
if (value instanceof GenericAccountIndex) {
|
||||
// `value.toBn()` on AccountIndex returns a pure BN (i.e. not an
|
||||
// AccountIndex), which has the initial `toString()` implementation.
|
||||
return value.toBn();
|
||||
} else if (isBn(value) || isNumber(value) || isHex(value) || isU8a(value) || isBigInt(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return decodeAccountIndex(decodeAddress(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericAccountIndex
|
||||
* @description
|
||||
* A wrapper around an AccountIndex, which is a shortened, variable-length encoding
|
||||
* for an Account. We extends from [[U32]] to provide the number-like properties.
|
||||
*/
|
||||
export class GenericAccountIndex extends u32 {
|
||||
constructor (registry: Registry, value: AnyNumber = new BN(0)) {
|
||||
super(registry, decodeAccountIndex(value));
|
||||
}
|
||||
|
||||
public static calcLength (_value: BN | number): number {
|
||||
const value = bnToBn(_value);
|
||||
|
||||
if (value.lte(MAX_1BYTE)) {
|
||||
return 1;
|
||||
} else if (value.lt(MAX_2BYTE)) {
|
||||
return 2;
|
||||
} else if (value.lt(MAX_4BYTE)) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
public static readLength (input: Uint8Array): [number, number] {
|
||||
const first = input[0];
|
||||
|
||||
if (first === PREFIX_2BYTE) {
|
||||
return [1, 2];
|
||||
} else if (first === PREFIX_4BYTE) {
|
||||
return [1, 4];
|
||||
} else if (first === PREFIX_8BYTE) {
|
||||
return [1, 8];
|
||||
}
|
||||
|
||||
return [0, 1];
|
||||
}
|
||||
|
||||
public static writeLength (input: Uint8Array): Uint8Array {
|
||||
switch (input.length) {
|
||||
case 2: return new Uint8Array([PREFIX_2BYTE]);
|
||||
case 4: return new Uint8Array([PREFIX_4BYTE]);
|
||||
case 8: return new Uint8Array([PREFIX_8BYTE]);
|
||||
default: return new Uint8Array([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Compares the value of the input to see if there is a match
|
||||
*/
|
||||
public override eq (other?: unknown): boolean {
|
||||
// shortcut for BN or Number, don't create an object
|
||||
if (isBn(other as string) || isNumber(other)) {
|
||||
return super.eq(other);
|
||||
}
|
||||
|
||||
// convert and compare
|
||||
return super.eq(this.registry.createTypeUnsafe('AccountIndex', [other]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (): string {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to JSON, typically used for RPC transfers
|
||||
*/
|
||||
public override toJSON (): string {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the value in a best-fit primitive form
|
||||
*/
|
||||
public override toPrimitive (): string {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the string representation of the value
|
||||
*/
|
||||
public override toString (): string {
|
||||
const length = GenericAccountIndex.calcLength(this);
|
||||
|
||||
return encodeAddress(this.toU8a().subarray(0, length), this.registry.chainSS58);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'AccountIndex';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
/* eslint-disable sort-keys */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
import type { BlockValue } from './Block.js';
|
||||
|
||||
import block00300 from '@pezkuwi/types-support/json/SignedBlock.003.00.json' assert { type: 'json' };
|
||||
import metadataStatic from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
import { stringify } from '@pezkuwi/util';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { Metadata } from '../metadata/index.js';
|
||||
import { GenericBlock as Block } from './Block.js';
|
||||
|
||||
interface BlockJson {
|
||||
result: {
|
||||
block: BlockValue;
|
||||
};
|
||||
}
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
describe('Block', (): void => {
|
||||
it('has a valid toRawType', (): void => {
|
||||
expect(
|
||||
new Block(registry).toRawType()
|
||||
).toEqual(
|
||||
// each of the containing structures have been stringified on their own
|
||||
stringify({
|
||||
header: 'Header',
|
||||
extrinsics: 'Vec<Extrinsic>'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('re-encodes digest items correctly', (): void => {
|
||||
const digest = new Block(registry, (block00300 as BlockJson).result.block).header.digest;
|
||||
|
||||
expect(digest.logs[0].toHex()).toEqual((block00300 as BlockJson).result.block.header?.digest?.logs[0]);
|
||||
expect(digest.logs[1].toHex()).toEqual((block00300 as BlockJson).result.block.header?.digest?.logs[1]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Vec } from '@pezkuwi/types-codec';
|
||||
import type { AnyNumber, AnyU8a, IU8a, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { GenericExtrinsic } from '../extrinsic/Extrinsic.js';
|
||||
import type { Digest, DigestItem, Header } from '../interfaces/runtime/index.js';
|
||||
|
||||
import { Struct } from '@pezkuwi/types-codec';
|
||||
|
||||
export interface HeaderValue {
|
||||
digest?: Digest | { logs: DigestItem[] | string[] };
|
||||
extrinsicsRoot?: AnyU8a;
|
||||
number?: AnyNumber;
|
||||
parentHash?: AnyU8a;
|
||||
stateRoot?: AnyU8a;
|
||||
}
|
||||
|
||||
export interface BlockValue {
|
||||
extrinsics?: AnyU8a[];
|
||||
header?: HeaderValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericBlock
|
||||
* @description
|
||||
* A block encoded with header and extrinsics
|
||||
*/
|
||||
export class GenericBlock extends Struct {
|
||||
constructor (registry: Registry, value?: BlockValue | Uint8Array) {
|
||||
super(registry, {
|
||||
header: 'Header',
|
||||
// eslint-disable-next-line sort-keys
|
||||
extrinsics: 'Vec<Extrinsic>'
|
||||
}, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encodes a content [[Hash]] for the block
|
||||
*/
|
||||
public get contentHash (): IU8a {
|
||||
return this.registry.hash(this.toU8a());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Extrinsic]] contained in the block
|
||||
*/
|
||||
public get extrinsics (): Vec<GenericExtrinsic> {
|
||||
return this.getT('extrinsics');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Block/header [[Hash]]
|
||||
*/
|
||||
public override get hash (): IU8a {
|
||||
return this.header.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[Header]] of the block
|
||||
*/
|
||||
public get header (): Header {
|
||||
return this.getT('header');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import metadataStatic from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { Metadata } from '../metadata/index.js';
|
||||
import { GenericCall as Call } from './index.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
describe('Call', (): void => {
|
||||
// balances.forceSetBalance(0x, 0)
|
||||
const FSBU8 = new Uint8Array([
|
||||
// index
|
||||
6, 8,
|
||||
// id lookup
|
||||
0,
|
||||
// public
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// value
|
||||
0
|
||||
]);
|
||||
|
||||
it('handles decoding correctly (bare)', (): void => {
|
||||
expect(
|
||||
new Call(registry, {
|
||||
args: ['0x0000000000000000000000000000000000000000000000000000000000000000', 0],
|
||||
callIndex: [6, 8]
|
||||
}).toU8a()
|
||||
).toEqual(FSBU8);
|
||||
});
|
||||
|
||||
it('handles creation from a hex value properly', (): void => {
|
||||
expect(
|
||||
new Call(registry, '0x0608000000000000000000000000000000000000000000000000000000000000000000').toU8a()
|
||||
).toEqual(FSBU8);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,244 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyJson, AnyTuple, AnyU8a, ArgsDef, Codec, IMethod, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { FunctionMetadataLatest } from '../interfaces/metadata/index.js';
|
||||
import type { CallBase, CallFunction, InterfaceTypes } from '../types/index.js';
|
||||
|
||||
import { Struct, U8aFixed } from '@pezkuwi/types-codec';
|
||||
import { isHex, isObject, isU8a, objectSpread, u8aToU8a } from '@pezkuwi/util';
|
||||
|
||||
interface DecodeMethodInput {
|
||||
args: unknown;
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
callIndex: GenericCallIndex | Uint8Array;
|
||||
}
|
||||
|
||||
interface DecodedMethod extends DecodeMethodInput {
|
||||
argsDef: ArgsDef;
|
||||
meta: FunctionMetadataLatest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of `argument name -> argument type` for the function, from
|
||||
* its metadata.
|
||||
*
|
||||
* @param meta - The function metadata used to get the definition.
|
||||
* @internal
|
||||
*/
|
||||
function getArgsDef (registry: Registry, meta: FunctionMetadataLatest): ArgsDef {
|
||||
return meta.fields.reduce((result, { name, type }, index): ArgsDef => {
|
||||
result[name.unwrapOr(`param${index}`).toString()] = registry.createLookupType(type) as keyof InterfaceTypes;
|
||||
|
||||
return result;
|
||||
}, {} as ArgsDef);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeCallViaObject (registry: Registry, value: DecodedMethod, _meta?: FunctionMetadataLatest): DecodedMethod {
|
||||
// we only pass args/methodsIndex out
|
||||
const { args, callIndex } = value;
|
||||
|
||||
// Get the correct lookupIndex
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const lookupIndex = callIndex instanceof GenericCallIndex
|
||||
? callIndex.toU8a()
|
||||
: callIndex;
|
||||
|
||||
// Find metadata with callIndex
|
||||
const meta = _meta || registry.findMetaCall(lookupIndex).meta;
|
||||
|
||||
return {
|
||||
args,
|
||||
argsDef: getArgsDef(registry, meta),
|
||||
callIndex,
|
||||
meta
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeCallViaU8a (registry: Registry, value: Uint8Array, _meta?: FunctionMetadataLatest): DecodedMethod {
|
||||
// We need 2 bytes for the callIndex
|
||||
const callIndex = registry.firstCallIndex.slice();
|
||||
|
||||
callIndex.set(value.subarray(0, 2), 0);
|
||||
|
||||
// Find metadata with callIndex
|
||||
const meta = _meta || registry.findMetaCall(callIndex).meta;
|
||||
|
||||
return {
|
||||
args: value.subarray(2),
|
||||
argsDef: getArgsDef(registry, meta),
|
||||
callIndex,
|
||||
meta
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode input to pass into constructor.
|
||||
*
|
||||
* @param value - Value to decode, one of:
|
||||
* - hex
|
||||
* - Uint8Array
|
||||
* - {@see DecodeMethodInput}
|
||||
* @param _meta - Metadata to use, so that `injectMethods` lookup is not
|
||||
* necessary.
|
||||
* @internal
|
||||
*/
|
||||
function decodeCall (registry: Registry, value: unknown = new Uint8Array(), _meta?: FunctionMetadataLatest): DecodedMethod {
|
||||
if (isU8a(value) || isHex(value)) {
|
||||
return decodeCallViaU8a(registry, u8aToU8a(value), _meta);
|
||||
} else if (isObject<DecodedMethod>(value) && value.callIndex && value.args) {
|
||||
return decodeCallViaObject(registry, value, _meta);
|
||||
}
|
||||
|
||||
throw new Error(`Call: Cannot decode value '${value as string}' of type ${typeof value}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericCallIndex
|
||||
* @description
|
||||
* A wrapper around the `[sectionIndex, methodIndex]` value that uniquely identifies a method
|
||||
*/
|
||||
export class GenericCallIndex extends U8aFixed {
|
||||
constructor (registry: Registry, value?: AnyU8a) {
|
||||
super(registry, value, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the value in a best-fit primitive form
|
||||
*/
|
||||
public override toPrimitive (): string {
|
||||
return this.toHex();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericCall
|
||||
* @description
|
||||
* Extrinsic function descriptor
|
||||
*/
|
||||
export class GenericCall<A extends AnyTuple = AnyTuple> extends Struct implements CallBase<A> {
|
||||
protected _meta: FunctionMetadataLatest;
|
||||
|
||||
constructor (registry: Registry, value: unknown, meta?: FunctionMetadataLatest) {
|
||||
const decoded = decodeCall(registry, value, meta);
|
||||
|
||||
try {
|
||||
super(registry, {
|
||||
callIndex: GenericCallIndex,
|
||||
// eslint-disable-next-line sort-keys
|
||||
args: Struct.with(decoded.argsDef)
|
||||
}, decoded);
|
||||
} catch (error) {
|
||||
let method = 'unknown.unknown';
|
||||
|
||||
try {
|
||||
const c = registry.findMetaCall(decoded.callIndex);
|
||||
|
||||
method = `${c.section}.${c.method}`;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
throw new Error(`Call: failed decoding ${method}:: ${(error as Error).message}`);
|
||||
}
|
||||
|
||||
this._meta = decoded.meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The arguments for the function call
|
||||
*/
|
||||
public get args (): A {
|
||||
return [...this.getT<Struct>('args').values()] as A;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The argument definitions
|
||||
*/
|
||||
public get argsDef (): ArgsDef {
|
||||
return getArgsDef(this.registry, this.meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The argument entries
|
||||
*/
|
||||
public get argsEntries (): [string, Codec][] {
|
||||
return [...this.getT<Struct>('args').entries()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The encoded `[sectionIndex, methodIndex]` identifier
|
||||
*/
|
||||
public get callIndex (): Uint8Array {
|
||||
return this.getT<GenericCallIndex>('callIndex').toU8a();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The encoded data
|
||||
*/
|
||||
public get data (): Uint8Array {
|
||||
return this.getT<Struct>('args').toU8a();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[FunctionMetadata]]
|
||||
*/
|
||||
public get meta (): FunctionMetadataLatest {
|
||||
return this._meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the name of the method
|
||||
*/
|
||||
public get method (): string {
|
||||
return this.registry.findMetaCall(this.callIndex).method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the module containing the method
|
||||
*/
|
||||
public get section (): string {
|
||||
return this.registry.findMetaCall(this.callIndex).section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Checks if the source matches this in type
|
||||
*/
|
||||
public is (other: IMethod<AnyTuple>): other is IMethod<A> {
|
||||
return other.callIndex[0] === this.callIndex[0] && other.callIndex[1] === this.callIndex[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (isExpanded?: boolean, disableAscii?: boolean): Record<string, AnyJson> {
|
||||
let call: CallFunction | undefined;
|
||||
|
||||
try {
|
||||
call = this.registry.findMetaCall(this.callIndex);
|
||||
} catch {
|
||||
// swallow
|
||||
}
|
||||
|
||||
return objectSpread(
|
||||
{
|
||||
args: this.argsEntries.reduce<Record<string, AnyJson>>((args, [n, a]) =>
|
||||
objectSpread(args, { [n]: a.toHuman(isExpanded, disableAscii) }), {}),
|
||||
method: call?.method,
|
||||
section: call?.section
|
||||
},
|
||||
isExpanded && call
|
||||
? { docs: call.meta.docs.map((d) => d.toString()) }
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'Call';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
|
||||
describe('ChainProperties', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('decodes from a null value (setting defaults)', (): void => {
|
||||
expect(
|
||||
[...registry.createType('ChainProperties', null).keys()]
|
||||
).toEqual(['isEthereum', 'ss58Format', 'tokenDecimals', 'tokenSymbol']);
|
||||
});
|
||||
|
||||
it('decodes from an actual JSON', (): void => {
|
||||
const { isEthereum, ss58Format, tokenDecimals, tokenSymbol } = registry.createType('ChainProperties', JSON.parse('{"isEthereum":false,"ss58Format":2,"tokenDecimals":12,"tokenSymbol":"KSM"}'));
|
||||
|
||||
expect(ss58Format.unwrap().eq(2)).toBe(true);
|
||||
expect(tokenDecimals.unwrap().eq([12])).toBe(true);
|
||||
expect(tokenSymbol.unwrap().eq(['KSM'])).toBe(true);
|
||||
expect(isEthereum.isFalse).toBe(true);
|
||||
});
|
||||
|
||||
it('decodes from an actual object (multiple tokens)', (): void => {
|
||||
const { isEthereum, ss58Format, tokenDecimals, tokenSymbol } = registry.createType('ChainProperties', {
|
||||
isEthereum: undefined,
|
||||
ss58Format: undefined,
|
||||
tokenDecimals: [10, 12],
|
||||
tokenSymbol: ['pDOT', 'pKSM']
|
||||
});
|
||||
|
||||
expect(isEthereum.isFalse).toBe(true);
|
||||
expect(ss58Format.isNone).toBe(true);
|
||||
expect(tokenDecimals.unwrap().eq([10, 12])).toBe(true);
|
||||
expect(tokenSymbol.unwrap().eq(['pDOT', 'pKSM'])).toBe(true);
|
||||
});
|
||||
|
||||
it('decodes from an object, flagged for non-existent ss58Format', (): void => {
|
||||
const { isEthereum, ss58Format, tokenDecimals, tokenSymbol } = registry.createType('ChainProperties', { tokenSymbol: 'DEV' });
|
||||
|
||||
expect(isEthereum.isFalse).toBe(true);
|
||||
expect(ss58Format.isNone).toBe(true);
|
||||
expect(tokenDecimals.isNone).toBe(true);
|
||||
expect(tokenSymbol.isSome).toBe(true);
|
||||
});
|
||||
|
||||
it('decodes from a ChainProperties object', (): void => {
|
||||
const original = registry.createType('ChainProperties', {
|
||||
isEthereum: true,
|
||||
ss58Format: 2,
|
||||
tokenDecimals: 15,
|
||||
tokenSymbol: 'KSM'
|
||||
});
|
||||
const { isEthereum, ss58Format, tokenDecimals, tokenSymbol } = registry.createType('ChainProperties', original);
|
||||
|
||||
expect(isEthereum.isTrue).toBe(true);
|
||||
expect(ss58Format.unwrap().eq(2)).toBe(true);
|
||||
expect(tokenDecimals.unwrap().eq([15])).toBe(true);
|
||||
expect(tokenSymbol.unwrap().eq(['KSM'])).toBe(true);
|
||||
});
|
||||
|
||||
it('has a sane toHuman (single tokenDecimals)', (): void => {
|
||||
expect(
|
||||
registry.createType('ChainProperties', {
|
||||
isEthereum: false,
|
||||
ss58Format: 42,
|
||||
tokenDecimals: registry.createType('u32', 9),
|
||||
tokenSymbol: ['Unit', 'Aux1']
|
||||
}).toHuman()
|
||||
).toEqual({
|
||||
isEthereum: false,
|
||||
ss58Format: '42',
|
||||
tokenDecimals: ['9'],
|
||||
tokenSymbol: ['Unit', 'Aux1']
|
||||
});
|
||||
});
|
||||
|
||||
it('has a sane toHuman (multiple tokenDecimals)', (): void => {
|
||||
expect(
|
||||
registry.createType('ChainProperties', {
|
||||
isEthereum: false,
|
||||
ss58Format: 2,
|
||||
tokenDecimals: [registry.createType('u32', 12), 8],
|
||||
tokenSymbol: ['KSM', 'BTC']
|
||||
}).toHuman()
|
||||
).toEqual({
|
||||
isEthereum: false,
|
||||
ss58Format: '2',
|
||||
tokenDecimals: ['12', '8'],
|
||||
tokenSymbol: ['KSM', 'BTC']
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { bool as Bool, Option, Text, u32, Vec } from '@pezkuwi/types-codec';
|
||||
import type { Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { Codec } from '../types/index.js';
|
||||
|
||||
import { Json } from '@pezkuwi/types-codec';
|
||||
import { isFunction, isNull, isUndefined } from '@pezkuwi/util';
|
||||
|
||||
function createValue (registry: Registry, type: string, value: unknown, asArray = true): Option<Codec> {
|
||||
// We detect codec here as well - when found, generally this is constructed from itself
|
||||
if (value && isFunction((value as Option<Codec>).unwrapOrDefault)) {
|
||||
return value as Option<Codec>;
|
||||
}
|
||||
|
||||
return registry.createTypeUnsafe<Option<u32>>(
|
||||
type,
|
||||
[
|
||||
asArray
|
||||
? isNull(value) || isUndefined(value)
|
||||
? null
|
||||
: Array.isArray(value)
|
||||
? value
|
||||
: [value]
|
||||
: value
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
function decodeValue (registry: Registry, key: string, value: unknown): unknown {
|
||||
return key === 'ss58Format'
|
||||
? createValue(registry, 'Option<u32>', value, false)
|
||||
: key === 'tokenDecimals'
|
||||
? createValue(registry, 'Option<Vec<u32>>' as 'Vec<u32>', value)
|
||||
: key === 'tokenSymbol'
|
||||
? createValue(registry, 'Option<Vec<Text>>' as 'Vec<Text>', value)
|
||||
: key === 'isEthereum'
|
||||
? createValue(registry, 'Bool', value, false)
|
||||
: value;
|
||||
}
|
||||
|
||||
function decode (registry: Registry, value?: Map<string, unknown> | Record<string, unknown> | null): Record<string, unknown> {
|
||||
return (
|
||||
// allow decoding from a map as well (ourselves)
|
||||
value && isFunction((value as Map<string, unknown>).entries)
|
||||
? [...(value as Map<string, unknown>).entries()]
|
||||
: Object.entries(value || {})
|
||||
).reduce((all: Record<string, unknown>, [key, value]) => {
|
||||
all[key] = decodeValue(registry, key, value);
|
||||
|
||||
return all;
|
||||
}, {
|
||||
isEthereum: registry.createTypeUnsafe('Bool', []),
|
||||
ss58Format: registry.createTypeUnsafe('Option<u32>', []),
|
||||
tokenDecimals: registry.createTypeUnsafe('Option<Vec<u32>>', []),
|
||||
tokenSymbol: registry.createTypeUnsafe('Option<Vec<Text>>', [])
|
||||
});
|
||||
}
|
||||
|
||||
export class GenericChainProperties extends Json {
|
||||
constructor (registry: Registry, value?: Map<string, unknown> | Record<string, unknown> | null) {
|
||||
super(registry, decode(registry, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The chain uses Ethereum addresses
|
||||
*/
|
||||
public get isEthereum (): Bool {
|
||||
return this.getT('isEthereum');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The chain ss58Format
|
||||
*/
|
||||
public get ss58Format (): Option<u32> {
|
||||
return this.getT('ss58Format');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The decimals for each of the tokens
|
||||
*/
|
||||
public get tokenDecimals (): Option<Vec<u32>> {
|
||||
return this.getT('tokenDecimals');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The symbols for the tokens
|
||||
*/
|
||||
public get tokenSymbol (): Option<Vec<Text>> {
|
||||
return this.getT('tokenSymbol');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { CID_AURA, CID_NMBS, GenericConsensusEngineId as ConsensusEngineId } from './ConsensusEngineId.js';
|
||||
|
||||
describe('ConsensusEngineId', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('creates a valid id for aura', (): void => {
|
||||
expect(new ConsensusEngineId(registry, 'aura').toU8a()).toEqual(CID_AURA);
|
||||
});
|
||||
|
||||
it('reverses an id to string for babe', (): void => {
|
||||
expect(new ConsensusEngineId(registry, 'BABE').toString()).toEqual('BABE');
|
||||
});
|
||||
|
||||
it('creates a valid id for nimbus', (): void => {
|
||||
expect(new ConsensusEngineId(registry, 'nmbs').toU8a()).toEqual(CID_NMBS);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Bytes, u32 } from '@pezkuwi/types-codec';
|
||||
import type { AnyU8a, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { AccountId, RawAuraPreDigest, RawBabePreDigestCompat } from '../interfaces/index.js';
|
||||
|
||||
import { U8aFixed } from '@pezkuwi/types-codec';
|
||||
import { BN, bnToU8a, isNumber, stringToU8a, u8aToHex, u8aToString } from '@pezkuwi/util';
|
||||
|
||||
export const CID_AURA = /*#__PURE__*/ stringToU8a('aura');
|
||||
export const CID_BABE = /*#__PURE__*/ stringToU8a('BABE');
|
||||
export const CID_GRPA = /*#__PURE__*/ stringToU8a('FRNK');
|
||||
export const CID_POW = /*#__PURE__*/ stringToU8a('pow_');
|
||||
export const CID_NMBS = /*#__PURE__*/ stringToU8a('nmbs');
|
||||
|
||||
function getAuraAuthor (registry: Registry, bytes: Bytes, sessionValidators: AccountId[]): AccountId {
|
||||
return sessionValidators[
|
||||
registry.createTypeUnsafe<RawAuraPreDigest>('RawAuraPreDigest', [bytes.toU8a(true)])
|
||||
.slotNumber
|
||||
.mod(new BN(sessionValidators.length))
|
||||
.toNumber()
|
||||
];
|
||||
}
|
||||
|
||||
function getBabeAuthor (registry: Registry, bytes: Bytes, sessionValidators: AccountId[]): AccountId {
|
||||
const digest = registry.createTypeUnsafe<RawBabePreDigestCompat>('RawBabePreDigestCompat', [bytes.toU8a(true)]);
|
||||
|
||||
return sessionValidators[
|
||||
(digest.value as u32).toNumber()
|
||||
];
|
||||
}
|
||||
|
||||
function getBytesAsAuthor (registry: Registry, bytes: Bytes): AccountId {
|
||||
return registry.createTypeUnsafe('AccountId', [bytes]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericConsensusEngineId
|
||||
* @description
|
||||
* A 4-byte identifier identifying the engine
|
||||
*/
|
||||
export class GenericConsensusEngineId extends U8aFixed {
|
||||
constructor (registry: Registry, value?: AnyU8a) {
|
||||
super(
|
||||
registry,
|
||||
isNumber(value)
|
||||
? bnToU8a(value, { isLe: false })
|
||||
: value,
|
||||
32
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` if the engine matches aura
|
||||
*/
|
||||
public get isAura (): boolean {
|
||||
return this.eq(CID_AURA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` is the engine matches babe
|
||||
*/
|
||||
public get isBabe (): boolean {
|
||||
return this.eq(CID_BABE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` is the engine matches grandpa
|
||||
*/
|
||||
public get isGrandpa (): boolean {
|
||||
return this.eq(CID_GRPA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` is the engine matches pow
|
||||
*/
|
||||
public get isPow (): boolean {
|
||||
return this.eq(CID_POW);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description `true` is the engine matches nimbus
|
||||
*/
|
||||
public get isNimbus (): boolean {
|
||||
return this.eq(CID_NMBS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description From the input bytes, decode into an author
|
||||
*/
|
||||
public extractAuthor (bytes: Bytes, sessionValidators: AccountId[]): AccountId | undefined {
|
||||
if (sessionValidators?.length) {
|
||||
if (this.isAura) {
|
||||
return getAuraAuthor(this.registry, bytes, sessionValidators);
|
||||
} else if (this.isBabe) {
|
||||
return getBabeAuthor(this.registry, bytes, sessionValidators);
|
||||
}
|
||||
}
|
||||
|
||||
// For pow & Nimbus, the bytes are the actual author
|
||||
if (this.isPow || this.isNimbus) {
|
||||
return getBytesAsAuthor(this.registry, bytes);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (): string {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'ConsensusEngineId';
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Override the default toString to return a 4-byte string
|
||||
*/
|
||||
public override toString (): string {
|
||||
return this.isAscii
|
||||
? u8aToString(this)
|
||||
: u8aToHex(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyJson, Codec, CodecClass } from '@pezkuwi/types-codec/types';
|
||||
import type { TypeDef } from '@pezkuwi/types-create/types';
|
||||
import type { EventMetadataLatest } from '../interfaces/metadata/index.js';
|
||||
import type { EventId } from '../interfaces/system/index.js';
|
||||
import type { IEvent, IEventData, InterfaceTypes, Registry } from '../types/index.js';
|
||||
|
||||
import { Null, Struct, Tuple } from '@pezkuwi/types-codec';
|
||||
import { objectProperties, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
interface Decoded {
|
||||
DataType: CodecClass<Null> | CodecClass<GenericEventData>;
|
||||
value?: {
|
||||
index: Uint8Array;
|
||||
data: Uint8Array;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeEvent (registry: Registry, value?: Uint8Array): Decoded {
|
||||
if (!value?.length) {
|
||||
return { DataType: Null };
|
||||
}
|
||||
|
||||
const index = value.subarray(0, 2);
|
||||
|
||||
return {
|
||||
DataType: registry.findMetaEvent(index),
|
||||
value: {
|
||||
data: value.subarray(2),
|
||||
index
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericEventData
|
||||
* @description
|
||||
* Wrapper for the actual data that forms part of an [[Event]]
|
||||
*/
|
||||
export class GenericEventData extends Tuple implements IEventData {
|
||||
readonly #meta: EventMetadataLatest;
|
||||
readonly #method: string;
|
||||
readonly #names: string[] | null = null;
|
||||
readonly #section: string;
|
||||
readonly #typeDef: TypeDef[];
|
||||
|
||||
constructor (registry: Registry, value: Uint8Array, meta: EventMetadataLatest, section = '<unknown>', method = '<unknown>') {
|
||||
const fields = meta?.fields || [];
|
||||
|
||||
super(registry, fields.map(({ type }) => registry.createLookupType(type) as keyof InterfaceTypes), value);
|
||||
|
||||
this.#meta = meta;
|
||||
this.#method = method;
|
||||
this.#section = section;
|
||||
this.#typeDef = fields.map(({ type }) => registry.lookup.getTypeDef(type));
|
||||
|
||||
const names = fields
|
||||
.map(({ name }) => registry.lookup.sanitizeField(name)[0])
|
||||
.filter((n): n is string => !!n);
|
||||
|
||||
if (names.length === fields.length) {
|
||||
this.#names = names;
|
||||
|
||||
objectProperties(this, names, (_, i) => this[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The wrapped [[EventMetadata]]
|
||||
*/
|
||||
public get meta (): EventMetadataLatest {
|
||||
return this.#meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The method as a string
|
||||
*/
|
||||
public get method (): string {
|
||||
return this.#method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The field names (as available)
|
||||
*/
|
||||
public get names (): string[] | null {
|
||||
return this.#names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The section as a string
|
||||
*/
|
||||
public get section (): string {
|
||||
return this.#section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[TypeDef]] for this event
|
||||
*/
|
||||
public get typeDef (): TypeDef[] {
|
||||
return this.#typeDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (isExtended?: boolean, disableAscii?: boolean): AnyJson {
|
||||
if (this.#names !== null) {
|
||||
const json: Record<string, AnyJson> = {};
|
||||
|
||||
for (let i = 0, count = this.#names.length; i < count; i++) {
|
||||
json[this.#names[i]] = this[i].toHuman(isExtended, disableAscii);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
return super.toHuman(isExtended);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericEvent
|
||||
* @description
|
||||
* A representation of a system event. These are generated via the [[Metadata]] interfaces and
|
||||
* specific to a specific Bizinikiwi runtime
|
||||
*/
|
||||
export class GenericEvent extends Struct implements IEvent<Codec[]> {
|
||||
// Currently we _only_ decode from Uint8Array, since we expect it to
|
||||
// be used via EventRecord
|
||||
constructor (registry: Registry, _value?: Uint8Array) {
|
||||
const { DataType, value } = decodeEvent(registry, _value);
|
||||
|
||||
super(registry, {
|
||||
index: 'EventId',
|
||||
// eslint-disable-next-line sort-keys
|
||||
data: DataType
|
||||
}, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The wrapped [[EventData]]
|
||||
*/
|
||||
public get data (): IEvent<Codec[]>['data'] {
|
||||
return this.getT('data');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[EventId]], identifying the raw event
|
||||
*/
|
||||
public get index (): EventId {
|
||||
return this.getT('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[EventMetadata]] with the documentation
|
||||
*/
|
||||
public get meta (): EventMetadataLatest {
|
||||
return this.data.meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The method string identifying the event
|
||||
*/
|
||||
public get method (): string {
|
||||
return this.data.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The section string identifying the event
|
||||
*/
|
||||
public get section (): string {
|
||||
return this.data.section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The [[TypeDef]] for the event
|
||||
*/
|
||||
public get typeDef (): TypeDef[] {
|
||||
return this.data.typeDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (isExpanded?: boolean, disableAscii?: boolean): Record<string, AnyJson> {
|
||||
return objectSpread(
|
||||
{
|
||||
method: this.method,
|
||||
section: this.section
|
||||
},
|
||||
isExpanded
|
||||
? { docs: this.meta.docs.map((d) => d.toString()) }
|
||||
: null,
|
||||
super.toHuman(isExpanded, disableAscii)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import type { GenericAccountId as AccountId, GenericAccountIndex as AccountIndex, GenericLookupSource as LookupSource } from './index.js';
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
|
||||
describe('LookupSource', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
const testDecode = (type: string, input: LookupSource | AccountId | AccountIndex | number[] | Uint8Array, expected: string): void =>
|
||||
it(`can decode from ${type}`, (): void => {
|
||||
const a = registry.createType('IndicesLookupSource', input);
|
||||
|
||||
expect(a.toString()).toBe(expected);
|
||||
});
|
||||
|
||||
describe('utility', (): void => {
|
||||
it('equals on AccountId', (): void => {
|
||||
const addr = '5DkQbYAExs3M2sZgT1Ec3mKfZnAQCL4Dt9beTCknkCUn5jzo';
|
||||
|
||||
expect(registry.createType('IndicesLookupSource', addr).eq(addr)).toBe(true);
|
||||
});
|
||||
|
||||
it('equals on AccountIndex', (): void => {
|
||||
// see the test below - these are equivalent (with different prefix encoding)
|
||||
expect(registry.createType('IndicesLookupSource', '118r').eq('25GUyv')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decoding', (): void => {
|
||||
testDecode(
|
||||
'Address',
|
||||
registry.createType('IndicesLookupSource', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'),
|
||||
'5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'
|
||||
);
|
||||
testDecode(
|
||||
'AccountId',
|
||||
registry.createType('AccountId', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'),
|
||||
'5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'
|
||||
);
|
||||
testDecode(
|
||||
'AccountIndex (mixed prefixes)',
|
||||
registry.createType('IndicesLookupSource', '118r'),
|
||||
// NOTE Expected address here is encoded with prefix 42, input above with 1
|
||||
'25GUyv'
|
||||
);
|
||||
testDecode(
|
||||
'AccountIndex (hex)',
|
||||
registry.createType('AccountIndex', '0x0100'),
|
||||
'25GUyv'
|
||||
);
|
||||
testDecode(
|
||||
'Array',
|
||||
[
|
||||
255,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
],
|
||||
'5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array (with prefix 255)',
|
||||
Uint8Array.from([
|
||||
255,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
]),
|
||||
'5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array (with prefix 1 byte)',
|
||||
Uint8Array.from([1]),
|
||||
'F7NZ'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array (with prefix 2 bytes)',
|
||||
Uint8Array.from([0xfc, 0, 1]),
|
||||
'25GUyv'
|
||||
);
|
||||
testDecode(
|
||||
'Uint8Array (with prefix 4 bytes)',
|
||||
Uint8Array.from([0xfd, 17, 18, 19, 20]),
|
||||
'Mwz15xP2'
|
||||
);
|
||||
// FIXME The specification allows for 8 byte addresses, however since AccountIndex is u32 internally
|
||||
// (and defined that way in the default Bizinikiwi),this does not actually work since it is 8 bytes,
|
||||
// instead of 4 bytes max u32 length
|
||||
// testDecode(
|
||||
// 'Uint8Array (with prefix 8 bytes)',
|
||||
// Uint8Array.from([0xfe, 17, 18, 19, 20, 21, 22, 23, 24]),
|
||||
// '3N5RJXxM5fLd4h'
|
||||
// );
|
||||
});
|
||||
|
||||
describe('encoding', (): void => {
|
||||
const testEncode = (to: 'toHex' | 'toString' | 'toU8a', expected: string | Uint8Array): void =>
|
||||
it(`can encode ${to}`, (): void => {
|
||||
const a = registry.createType('IndicesLookupSource', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF');
|
||||
|
||||
expect(a[to]()).toEqual(expected);
|
||||
});
|
||||
|
||||
testEncode(
|
||||
'toHex',
|
||||
'0xff0102030405060708010203040506070801020304050607080102030405060708'
|
||||
);
|
||||
testEncode(
|
||||
'toString',
|
||||
'5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'
|
||||
);
|
||||
testEncode(
|
||||
'toU8a',
|
||||
Uint8Array.from([
|
||||
255,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Inspect, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
import { AbstractBase } from '@pezkuwi/types-codec';
|
||||
import { isBigInt, isBn, isHex, isNumber, isU8a, u8aConcat, u8aToBn, u8aToHex, u8aToU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { GenericAccountId } from './AccountId.js';
|
||||
import { GenericAccountIndex } from './AccountIndex.js';
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
type AnyAddress = bigint | BN | GenericLookupSource | GenericAccountId | GenericAccountIndex | number[] | Uint8Array | number | string;
|
||||
|
||||
export const ACCOUNT_ID_PREFIX = new Uint8Array([0xff]);
|
||||
|
||||
/** @internal */
|
||||
function decodeString (registry: Registry, value: string): GenericAccountId | GenericAccountIndex {
|
||||
const decoded = decodeAddress(value);
|
||||
|
||||
return decoded.length === 32
|
||||
? registry.createTypeUnsafe('AccountId', [decoded])
|
||||
: registry.createTypeUnsafe('AccountIndex', [u8aToBn(decoded)]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeU8a (registry: Registry, value: Uint8Array): GenericAccountId | GenericAccountIndex {
|
||||
// This allows us to instantiate an address with a raw publicKey. Do this first before
|
||||
// we checking the first byte, otherwise we may split an already-existent valid address
|
||||
if (value.length === 32) {
|
||||
return registry.createTypeUnsafe('AccountId', [value]);
|
||||
} else if (value[0] === 0xff) {
|
||||
return registry.createTypeUnsafe('AccountId', [value.subarray(1)]);
|
||||
}
|
||||
|
||||
const [offset, length] = GenericAccountIndex.readLength(value);
|
||||
|
||||
return registry.createTypeUnsafe('AccountIndex', [u8aToBn(value.subarray(offset, offset + length))]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeAddressOrIndex (registry: Registry, value: AnyAddress): GenericAccountId | GenericAccountIndex {
|
||||
return value instanceof GenericLookupSource
|
||||
? value.inner
|
||||
: value instanceof GenericAccountId || value instanceof GenericAccountIndex
|
||||
? value
|
||||
: isBn(value) || isNumber(value) || isBigInt(value)
|
||||
? registry.createTypeUnsafe('AccountIndex', [value])
|
||||
: Array.isArray(value) || isHex(value) || isU8a(value)
|
||||
? decodeU8a(registry, u8aToU8a(value))
|
||||
: decodeString(registry, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name LookupSource
|
||||
* @description
|
||||
* A wrapper around an AccountId and/or AccountIndex that is encoded with a prefix.
|
||||
* Since we are dealing with underlying publicKeys (or shorter encoded addresses),
|
||||
* we extend from Base with an AccountId/AccountIndex wrapper. Basically the Address
|
||||
* is encoded as `[ <prefix-byte>, ...publicKey/...bytes ]` as per spec
|
||||
*/
|
||||
export class GenericLookupSource extends AbstractBase<GenericAccountId | GenericAccountIndex> {
|
||||
constructor (registry: Registry, value: AnyAddress = new Uint8Array()) {
|
||||
super(registry, decodeAddressOrIndex(registry, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the value when encoded as a Uint8Array
|
||||
*/
|
||||
public override get encodedLength (): number {
|
||||
const rawLength = this._rawLength;
|
||||
|
||||
return rawLength + (
|
||||
// for 1 byte AccountIndexes, we are not adding a specific prefix
|
||||
rawLength > 1
|
||||
? 1
|
||||
: 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The length of the raw value, either AccountIndex or AccountId
|
||||
*/
|
||||
protected get _rawLength (): number {
|
||||
return this.inner instanceof GenericAccountIndex
|
||||
? GenericAccountIndex.calcLength(this.inner)
|
||||
: this.inner.encodedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a breakdown of the hex encoding for this Codec
|
||||
*/
|
||||
override inspect (): Inspect {
|
||||
const value = this.inner.toU8a().subarray(0, this._rawLength);
|
||||
|
||||
return {
|
||||
outer: [
|
||||
new Uint8Array(
|
||||
this.inner instanceof GenericAccountIndex
|
||||
? GenericAccountIndex.writeLength(value)
|
||||
: ACCOUNT_ID_PREFIX
|
||||
),
|
||||
value
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a hex string representation of the value
|
||||
*/
|
||||
public override toHex (): HexString {
|
||||
return u8aToHex(this.toU8a());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'Address';
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Encodes the value as a Uint8Array as per the SCALE specifications
|
||||
* @param isBare true when the value has none of the type-specific prefixes (internal)
|
||||
*/
|
||||
public override toU8a (isBare?: boolean): Uint8Array {
|
||||
const encoded = this.inner.toU8a().subarray(0, this._rawLength);
|
||||
|
||||
return isBare
|
||||
? encoded
|
||||
: u8aConcat(
|
||||
this.inner instanceof GenericAccountIndex
|
||||
? GenericAccountIndex.writeLength(encoded)
|
||||
: ACCOUNT_ID_PREFIX,
|
||||
encoded
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
|
||||
describe('MultiAddress', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('decodes from an empty value', (): void => {
|
||||
const a = registry.createType('MultiAddress');
|
||||
|
||||
expect(a.index).toEqual(0);
|
||||
expect(a.toHex()).toEqual('0x000000000000000000000000000000000000000000000000000000000000000000');
|
||||
});
|
||||
|
||||
it('correctly decodes a stream with Address20', (): void => {
|
||||
const a = registry.createType('MultiAddress', '0x0467f89207abe6e1b093befd84a48f03313765929207009e292608');
|
||||
|
||||
expect(a.index).toEqual(4);
|
||||
expect(a.toString()).toEqual('0x67f89207abe6e1b093befd84a48f033137659292');
|
||||
});
|
||||
|
||||
it('correctly decodes an AccountId', (): void => {
|
||||
const a = registry.createType('MultiAddress', '0x0102030405060708010203040506070801020304050607080102030405060708');
|
||||
|
||||
expect(a.index).toEqual(0);
|
||||
expect(a.toString()).toEqual('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF');
|
||||
});
|
||||
|
||||
it('correctly decodes an AccountIndex', (): void => {
|
||||
const a = registry.createType('MultiAddress', '25GUyv');
|
||||
|
||||
expect(a.index).toEqual(1);
|
||||
expect(a.toString()).toEqual('25GUyv');
|
||||
});
|
||||
|
||||
it('correctly decodes an AccountIndex (AccountIndex input)', (): void => {
|
||||
const a = registry.createType('MultiAddress', registry.createType('AccountIndex', '25GUyv'));
|
||||
|
||||
expect(a.index).toEqual(1);
|
||||
expect(a.toString()).toEqual('25GUyv');
|
||||
});
|
||||
|
||||
it('correctly decodes an Address20', (): void => {
|
||||
const a = registry.createType('MultiAddress', '0x67f89207abe6e1b093befd84a48f033137659292');
|
||||
|
||||
expect(a.index).toEqual(4);
|
||||
expect(a.toString()).toEqual('0x67f89207abe6e1b093befd84a48f033137659292');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Inspect, Registry } from '@pezkuwi/types-codec/types';
|
||||
|
||||
import { Enum } from '@pezkuwi/types-codec';
|
||||
import { isBn, isNumber, isString, isU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { GenericAccountId } from './AccountId.js';
|
||||
import { GenericAccountIndex } from './AccountIndex.js';
|
||||
|
||||
function decodeU8a (registry: Registry, u8a: Uint8Array): unknown {
|
||||
if ([0, 32].includes(u8a.length)) {
|
||||
return { Id: u8a };
|
||||
} else if (u8a.length === 20) {
|
||||
return { Address20: u8a };
|
||||
} else if (u8a.length <= 8) {
|
||||
return { Index: registry.createTypeUnsafe<GenericAccountIndex>('AccountIndex', [u8a]).toNumber() };
|
||||
}
|
||||
|
||||
return u8a;
|
||||
}
|
||||
|
||||
function decodeMultiAny (registry: Registry, value?: unknown): unknown {
|
||||
if (value instanceof GenericAccountId) {
|
||||
return { Id: value };
|
||||
} else if (isU8a(value)) {
|
||||
// NOTE This is after the AccountId check (which is U8a)
|
||||
return decodeU8a(registry, value);
|
||||
} else if (value instanceof GenericMultiAddress) {
|
||||
return value;
|
||||
} else if (value instanceof GenericAccountIndex || isBn(value) || isNumber(value)) {
|
||||
return { Index: isNumber(value) ? value : value.toNumber() };
|
||||
} else if (isString(value)) {
|
||||
return decodeU8a(registry, decodeAddress(value.toString()));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export class GenericMultiAddress extends Enum {
|
||||
constructor (registry: Registry, value?: unknown) {
|
||||
super(registry, {
|
||||
Id: 'AccountId',
|
||||
Index: 'Compact<AccountIndex>',
|
||||
Raw: 'Bytes',
|
||||
// eslint-disable-next-line sort-keys
|
||||
Address32: 'H256',
|
||||
// eslint-disable-next-line sort-keys
|
||||
Address20: 'H160'
|
||||
}, decodeMultiAny(registry, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a breakdown of the hex encoding for this Codec
|
||||
*/
|
||||
override inspect (): Inspect {
|
||||
const { inner, outer = [] } = this.inner.inspect();
|
||||
|
||||
return {
|
||||
inner,
|
||||
outer: [new Uint8Array([this.index]), ...outer]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the string representation of the value
|
||||
*/
|
||||
public override toString (): string {
|
||||
return this.value.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { TypeRegistry } from '../create/index.js';
|
||||
import { GenericVote } from './index.js';
|
||||
|
||||
describe('GenericVote', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
describe('construction', (): void => {
|
||||
it('constructs via boolean true', (): void => {
|
||||
expect(new GenericVote(registry, true).toU8a()).toEqual(new Uint8Array([128]));
|
||||
expect(new GenericVote(registry, true).isAye).toBe(true);
|
||||
expect(new GenericVote(registry, true).isNay).toBe(false);
|
||||
});
|
||||
|
||||
it('constructs via boolean false', (): void => {
|
||||
expect(new GenericVote(registry, false).toU8a()).toEqual(new Uint8Array([0]));
|
||||
expect(new GenericVote(registry, false).isNay).toBe(true);
|
||||
expect(new GenericVote(registry, false).isAye).toBe(false);
|
||||
});
|
||||
|
||||
it('constructs via undefined', (): void => {
|
||||
expect(new GenericVote(registry).isNay).toBe(true);
|
||||
});
|
||||
|
||||
it('has isYay for positive', (): void => {
|
||||
// eslint-disable-next-line no-new-wrappers
|
||||
expect(new GenericVote(registry, true).isAye).toBe(true);
|
||||
});
|
||||
|
||||
it('has isNay for negative', (): void => {
|
||||
// eslint-disable-next-line no-new-wrappers
|
||||
expect(new GenericVote(registry, false).isNay).toBe(true);
|
||||
});
|
||||
|
||||
it('is Aye for negative numbers', (): void => {
|
||||
expect(new GenericVote(registry, -128).isAye).toBe(true);
|
||||
});
|
||||
|
||||
it('is Nay for positive numbers', (): void => {
|
||||
expect(new GenericVote(registry, 127).isNay).toBe(true);
|
||||
});
|
||||
|
||||
it('is Nay for 0', (): void => {
|
||||
expect(new GenericVote(registry, 0).isNay).toBe(true);
|
||||
});
|
||||
|
||||
it('constructs via empty', (): void => {
|
||||
expect(new GenericVote(registry).isNay).toBe(true);
|
||||
});
|
||||
|
||||
it('constructs via Uint8Array (empty)', (): void => {
|
||||
expect(new GenericVote(registry, new Uint8Array()).isNay).toBe(true);
|
||||
});
|
||||
|
||||
it('constructs via Uint8Array (nay)', (): void => {
|
||||
expect(new GenericVote(registry, new Uint8Array([1])).isNay).toBe(true);
|
||||
});
|
||||
|
||||
it('constructs via Uint8Array (aye)', (): void => {
|
||||
const test = new GenericVote(registry, new Uint8Array([0b10000010]));
|
||||
|
||||
expect(test.isNay).toBe(false);
|
||||
expect(test.conviction.toString()).toEqual('Locked2x');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vote with conviction', (): void => {
|
||||
it('constructs Vote with raw boolean', (): void => {
|
||||
const vote = new GenericVote(registry, {
|
||||
aye: true,
|
||||
conviction: 'Locked1x'
|
||||
});
|
||||
|
||||
expect(
|
||||
vote.toU8a()
|
||||
).toEqual(new Uint8Array([0b10000001]));
|
||||
expect(
|
||||
vote.toPrimitive()
|
||||
).toEqual({
|
||||
aye: true,
|
||||
conviction: 'Locked1x'
|
||||
});
|
||||
});
|
||||
|
||||
it('constructs with Vote aye is false, conviction is None', (): void => {
|
||||
expect(
|
||||
new GenericVote(registry, {
|
||||
aye: false,
|
||||
conviction: 'None'
|
||||
}).toU8a()
|
||||
).toEqual(new Uint8Array([0b00000000]));
|
||||
});
|
||||
|
||||
it('constructs with Vote aye is true, conviction is Locked4x', (): void => {
|
||||
expect(
|
||||
new GenericVote(registry, {
|
||||
aye: true,
|
||||
conviction: 'Locked4x'
|
||||
}).toU8a()
|
||||
).toEqual(new Uint8Array([0b10000100]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getters', (): void => {
|
||||
it('Conviction getter works', (): void => {
|
||||
expect(
|
||||
new GenericVote(registry, {
|
||||
aye: true,
|
||||
conviction: 'Locked2x'
|
||||
}).conviction.toString()
|
||||
).toEqual('Locked2x');
|
||||
});
|
||||
|
||||
it('Conviction getter works with raw boolean and string conviction', (): void => {
|
||||
expect(
|
||||
new GenericVote(registry, {
|
||||
aye: true,
|
||||
conviction: 'Locked2x'
|
||||
}).conviction.toString()
|
||||
).toEqual('Locked2x');
|
||||
});
|
||||
|
||||
it('Conviction getter works with raw boolean and conviction index', (): void => {
|
||||
expect(
|
||||
new GenericVote(registry, {
|
||||
aye: true,
|
||||
conviction: 2
|
||||
}).conviction.toString()
|
||||
).toEqual('Locked2x');
|
||||
});
|
||||
|
||||
it('Conviction getter works with raw boolean and no conviction', (): void => {
|
||||
const test = new GenericVote(registry, { aye: true });
|
||||
|
||||
expect(test.isAye).toEqual(true);
|
||||
expect(test.conviction.toString()).toEqual('None');
|
||||
});
|
||||
|
||||
it('isAye getter works', (): void => {
|
||||
expect(
|
||||
new GenericVote(registry, {
|
||||
aye: true,
|
||||
conviction: 'None'
|
||||
}).isAye)
|
||||
.toEqual(true);
|
||||
});
|
||||
|
||||
it('isNay getter works', (): void => {
|
||||
expect(
|
||||
new GenericVote(registry, {
|
||||
aye: true,
|
||||
conviction: 'None'
|
||||
}).isNay)
|
||||
.toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('utils', (): void => {
|
||||
it('has a sane toRawType', (): void => {
|
||||
expect(new GenericVote(registry).toRawType()).toEqual('Vote');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AnyJson, Registry } from '@pezkuwi/types-codec/types';
|
||||
import type { AllConvictions } from '../interfaces/democracy/definitions.js';
|
||||
import type { Conviction } from '../interfaces/democracy/index.js';
|
||||
import type { ArrayElementType } from '../types/index.js';
|
||||
|
||||
import { Bool, U8aFixed } from '@pezkuwi/types-codec';
|
||||
import { isBoolean, isNumber, isU8a, isUndefined } from '@pezkuwi/util';
|
||||
|
||||
interface VoteType {
|
||||
aye: boolean;
|
||||
conviction?: number | ArrayElementType<typeof AllConvictions>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
type InputTypes = boolean | number | Boolean | Uint8Array | VoteType;
|
||||
|
||||
// For votes, the topmost bit indicated aye/nay, the lower bits indicate the conviction
|
||||
const AYE_BITS = 0b10000000;
|
||||
const NAY_BITS = 0b00000000;
|
||||
const CON_MASK = 0b01111111;
|
||||
const DEF_CONV = 0b00000000; // the default conviction, None
|
||||
|
||||
/** @internal */
|
||||
function decodeVoteBool (value: boolean): Uint8Array {
|
||||
return value
|
||||
? new Uint8Array([AYE_BITS | DEF_CONV])
|
||||
: new Uint8Array([NAY_BITS]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeVoteU8a (value: Uint8Array): Uint8Array {
|
||||
return value.length
|
||||
? value.subarray(0, 1)
|
||||
: new Uint8Array([NAY_BITS]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeVoteType (registry: Registry, value: VoteType): Uint8Array {
|
||||
return new Uint8Array([
|
||||
(
|
||||
new Bool(registry, value.aye).isTrue
|
||||
? AYE_BITS
|
||||
: NAY_BITS
|
||||
) |
|
||||
registry.createTypeUnsafe<Conviction>('Conviction', [value.conviction || DEF_CONV]).index
|
||||
]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function decodeVote (registry: Registry, value?: InputTypes): Uint8Array {
|
||||
if (isU8a(value)) {
|
||||
return decodeVoteU8a(value);
|
||||
} else if (isUndefined(value) || value instanceof Boolean || isBoolean(value)) {
|
||||
return decodeVoteBool(new Bool(registry, value).isTrue);
|
||||
} else if (isNumber(value)) {
|
||||
return decodeVoteBool(value < 0);
|
||||
}
|
||||
|
||||
return decodeVoteType(registry, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name GenericVote
|
||||
* @description
|
||||
* A number of lock periods, plus a vote, one way or the other.
|
||||
*/
|
||||
export class GenericVote extends U8aFixed {
|
||||
#aye: boolean;
|
||||
#conviction: Conviction;
|
||||
|
||||
constructor (registry: Registry, value?: InputTypes) {
|
||||
// decoded is just 1 byte
|
||||
// Aye: Most Significant Bit
|
||||
// Conviction: 0000 - 0101
|
||||
const decoded = decodeVote(registry, value);
|
||||
|
||||
super(registry, decoded, 8);
|
||||
|
||||
this.#aye = (decoded[0] & AYE_BITS) === AYE_BITS;
|
||||
this.#conviction = this.registry.createTypeUnsafe('Conviction', [decoded[0] & CON_MASK]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description returns a V2 conviction
|
||||
*/
|
||||
public get conviction (): Conviction {
|
||||
return this.#conviction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description true if the wrapped value is a positive vote
|
||||
*/
|
||||
public get isAye (): boolean {
|
||||
return this.#aye;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description true if the wrapped value is a negative vote
|
||||
*/
|
||||
public get isNay (): boolean {
|
||||
return !this.isAye;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
|
||||
*/
|
||||
public override toHuman (isExpanded?: boolean): AnyJson {
|
||||
return {
|
||||
conviction: this.conviction.toHuman(isExpanded),
|
||||
vote: this.isAye ? 'Aye' : 'Nay'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Converts the value in a best-fit primitive form
|
||||
*/
|
||||
public override toPrimitive (): any {
|
||||
return {
|
||||
aye: this.isAye,
|
||||
conviction: this.conviction.toPrimitive()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public override toRawType (): string {
|
||||
return 'Vote';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// all named
|
||||
export { GenericAccountId, GenericAccountId as GenericAccountId32, GenericAccountId33 } from './AccountId.js';
|
||||
export { GenericAccountIndex } from './AccountIndex.js';
|
||||
export { GenericBlock } from './Block.js';
|
||||
export { GenericCall } from './Call.js';
|
||||
export { GenericChainProperties } from './ChainProperties.js';
|
||||
export { GenericConsensusEngineId } from './ConsensusEngineId.js';
|
||||
export { GenericEvent, GenericEventData } from './Event.js';
|
||||
export { GenericLookupSource } from './LookupSource.js';
|
||||
export { GenericMultiAddress as GenericAddress, GenericMultiAddress } from './MultiAddress.js';
|
||||
export { GenericVote } from './Vote.js';
|
||||
|
||||
// all starred
|
||||
export * from '../ethereum/index.js';
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Codec } from './types/index.js';
|
||||
|
||||
import metadataStatic from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
|
||||
import { TypeRegistry } from './create/index.js';
|
||||
import * as definitions from './interfaces/definitions.js';
|
||||
import { Metadata } from './metadata/index.js';
|
||||
import * as exported from './index.types.js';
|
||||
|
||||
// NOTE This is not a shortcut to implementing types incorrectly. This is here
|
||||
// specifically for the types that _should_ throw in the constrtuctor, i.e
|
||||
// `usize` is not allowed (runtime incompat) and `origin` is not passed through
|
||||
// to any calls. All other types _must_ pass and allow for empty defaults
|
||||
const UNCONSTRUCTABLE = [
|
||||
'ExtrinsicPayloadUnknown', 'GenericExtrinsicPayloadUnknown',
|
||||
'ExtrinsicUnknown', 'GenericExtrinsicUnknown',
|
||||
'DoNotConstruct',
|
||||
'MetadataAll',
|
||||
'Origin',
|
||||
'isize',
|
||||
'usize'
|
||||
].map((v): string => v.toLowerCase());
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metadataStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
function testTypes (type: string, typeNames: string[]): void {
|
||||
describe(`${type}`, (): void => {
|
||||
describe(`${type}:: default creation`, (): void => {
|
||||
typeNames.forEach((name): void => {
|
||||
it(`creates an empty ${name}`, (): void => {
|
||||
const constructFn = (): Codec =>
|
||||
registry.createType(name);
|
||||
|
||||
if (UNCONSTRUCTABLE.includes(name.toLowerCase())) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(constructFn).toThrow();
|
||||
} else {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(constructFn).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${type}:: default creation (empty bytes)`, (): void => {
|
||||
typeNames.forEach((name): void => {
|
||||
it(`creates an empty ${name} (from empty bytes)`, (): void => {
|
||||
const constructFn = (): Codec =>
|
||||
registry.createType(name, registry.createType('Bytes'));
|
||||
|
||||
if (UNCONSTRUCTABLE.includes(name.toLowerCase())) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(constructFn).toThrow();
|
||||
} else {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(constructFn).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('type creation', (): void => {
|
||||
testTypes('exported', Object.keys(exported));
|
||||
|
||||
Object
|
||||
.entries(definitions)
|
||||
.forEach(([name, { types }]): void =>
|
||||
testTypes(`${name} (injected)`, Object.keys(types))
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import './packageDetect.js';
|
||||
|
||||
export * from './bundle.js';
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './extrinsic/index.js';
|
||||
export * from './generic/index.js';
|
||||
export * from './primitive/index.js';
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types-known authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { TypeRegistry } from '../create/registry.js';
|
||||
import { getAliasTypes } from './alias.js';
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
registry.setKnownTypes({
|
||||
typesAlias: {
|
||||
identity: {
|
||||
Id: 'IdentityId'
|
||||
},
|
||||
testModule: {
|
||||
Proposal: 'TestProposal'
|
||||
},
|
||||
treasury: {
|
||||
Proposal: 'TreasuryProposals2'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('getModuleTypes', (): void => {
|
||||
it('collects the pre-defined types for contracts', (): void => {
|
||||
expect(getAliasTypes(registry, 'contracts')).toEqual({
|
||||
StorageKey: 'ContractStorageKey'
|
||||
});
|
||||
});
|
||||
|
||||
it('collects the user-defined types for testModule', (): void => {
|
||||
expect(getAliasTypes(registry, 'testModule')).toEqual({
|
||||
Proposal: 'TestProposal'
|
||||
});
|
||||
});
|
||||
|
||||
it('overrides pre-defined with user-defined for treasury', (): void => {
|
||||
expect(getAliasTypes(registry, 'treasury')).toEqual({
|
||||
Proposal: 'TreasuryProposals2'
|
||||
});
|
||||
});
|
||||
|
||||
it('merges pre-defined and user-defined for identity', (): void => {
|
||||
expect(getAliasTypes(registry, 'identity')).toEqual({
|
||||
Id: 'IdentityId',
|
||||
Judgement: 'IdentityJudgement'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types-known authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { OverrideModuleType, Registry } from '../types/index.js';
|
||||
|
||||
// type overrides for modules (where duplication between modules exist)
|
||||
const typesAlias: Record<string, OverrideModuleType> = {
|
||||
assets: {
|
||||
Approval: 'AssetApproval',
|
||||
ApprovalKey: 'AssetApprovalKey',
|
||||
Balance: 'TAssetBalance',
|
||||
DestroyWitness: 'AssetDestroyWitness'
|
||||
},
|
||||
babe: {
|
||||
EquivocationProof: 'BabeEquivocationProof'
|
||||
},
|
||||
balances: {
|
||||
Status: 'BalanceStatus'
|
||||
},
|
||||
beefy: {
|
||||
AuthorityId: 'BeefyId'
|
||||
},
|
||||
contracts: {
|
||||
StorageKey: 'ContractStorageKey'
|
||||
},
|
||||
electionProviderMultiPhase: {
|
||||
Phase: 'ElectionPhase'
|
||||
},
|
||||
ethereum: {
|
||||
Block: 'EthBlock',
|
||||
Header: 'EthHeader',
|
||||
Receipt: 'EthReceipt',
|
||||
Transaction: 'EthTransaction',
|
||||
TransactionStatus: 'EthTransactionStatus'
|
||||
},
|
||||
evm: {
|
||||
Account: 'EvmAccount',
|
||||
Log: 'EvmLog',
|
||||
Vicinity: 'EvmVicinity'
|
||||
},
|
||||
grandpa: {
|
||||
Equivocation: 'GrandpaEquivocation',
|
||||
EquivocationProof: 'GrandpaEquivocationProof'
|
||||
},
|
||||
identity: {
|
||||
Judgement: 'IdentityJudgement'
|
||||
},
|
||||
inclusion: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
paraDisputes: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
paraInclusion: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
paraScheduler: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
paraShared: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
teyrchains: {
|
||||
Id: 'ParaId'
|
||||
},
|
||||
parasDisputes: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
parasInclusion: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
parasScheduler: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
parasShared: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
proposeTeyrchain: {
|
||||
Proposal: 'TeyrchainProposal'
|
||||
},
|
||||
proxy: {
|
||||
Announcement: 'ProxyAnnouncement'
|
||||
},
|
||||
scheduler: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
shared: {
|
||||
ValidatorIndex: 'ParaValidatorIndex'
|
||||
},
|
||||
society: {
|
||||
Judgement: 'SocietyJudgement',
|
||||
Vote: 'SocietyVote'
|
||||
},
|
||||
staking: {
|
||||
Compact: 'CompactAssignments'
|
||||
},
|
||||
treasury: {
|
||||
Proposal: 'TreasuryProposal'
|
||||
},
|
||||
xcm: {
|
||||
AssetId: 'XcmAssetId'
|
||||
},
|
||||
xcmPezpallet: {
|
||||
AssetId: 'XcmAssetId'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get types for specific modules (metadata override)
|
||||
*/
|
||||
export function getAliasTypes ({ knownTypes }: Registry, section: string): OverrideModuleType {
|
||||
return {
|
||||
...(typesAlias[section] ?? {}),
|
||||
...(knownTypes.typesAlias?.[section] ?? {})
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// order important in structs... :)
|
||||
/* eslint-disable sort-keys */
|
||||
|
||||
import type { Definitions } from '../../types/index.js';
|
||||
|
||||
import { runtime } from './runtime.js';
|
||||
|
||||
export default {
|
||||
rpc: {},
|
||||
runtime,
|
||||
types: {
|
||||
TAssetConversion: 'Option<MultiLocation>'
|
||||
}
|
||||
} as Definitions;
|
||||
@@ -0,0 +1,4 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
export * from './types.js';
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DefinitionsCall } from '../../types/index.js';
|
||||
|
||||
export const runtime: DefinitionsCall = {
|
||||
AssetConversionApi: [
|
||||
{
|
||||
methods: {
|
||||
get_reserves: {
|
||||
description: 'Get pool reserves',
|
||||
params: [
|
||||
{
|
||||
name: 'asset1',
|
||||
type: 'StagingXcmV3MultiLocation'
|
||||
},
|
||||
{
|
||||
name: 'asset2',
|
||||
type: 'StagingXcmV3MultiLocation'
|
||||
}
|
||||
],
|
||||
type: 'Option<(Balance,Balance)>'
|
||||
},
|
||||
quote_price_exact_tokens_for_tokens: {
|
||||
description: 'Quote price: exact tokens for tokens',
|
||||
params: [
|
||||
{
|
||||
name: 'asset1',
|
||||
type: 'StagingXcmV3MultiLocation'
|
||||
},
|
||||
{
|
||||
name: 'asset2',
|
||||
type: 'StagingXcmV3MultiLocation'
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
type: 'u128'
|
||||
},
|
||||
{
|
||||
name: 'include_fee',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
type: 'Option<(Balance)>'
|
||||
},
|
||||
quote_price_tokens_for_exact_tokens: {
|
||||
description: 'Quote price: tokens for exact tokens',
|
||||
params: [
|
||||
{
|
||||
name: 'asset1',
|
||||
type: 'StagingXcmV3MultiLocation'
|
||||
},
|
||||
{
|
||||
name: 'asset2',
|
||||
type: 'StagingXcmV3MultiLocation'
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
type: 'u128'
|
||||
},
|
||||
{
|
||||
name: 'include_fee',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
type: 'Option<(Balance)>'
|
||||
}
|
||||
},
|
||||
version: 1
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
import type { Option } from '@pezkuwi/types-codec';
|
||||
import type { MultiLocation } from '@pezkuwi/types/interfaces/xcm';
|
||||
|
||||
/** @name TAssetConversion */
|
||||
export interface TAssetConversion extends Option<MultiLocation> {}
|
||||
|
||||
export type PHANTOM_ASSETCONVERSION = 'assetConversion';
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// order important in structs... :)
|
||||
/* eslint-disable sort-keys */
|
||||
|
||||
import type { Definitions } from '../../types/index.js';
|
||||
|
||||
import { runtime } from './runtime.js';
|
||||
|
||||
export default {
|
||||
rpc: {},
|
||||
runtime,
|
||||
types: {
|
||||
AssetApprovalKey: {
|
||||
owner: 'AccountId',
|
||||
delegate: 'AccountId'
|
||||
},
|
||||
AssetApproval: {
|
||||
amount: 'TAssetBalance',
|
||||
deposit: 'TAssetDepositBalance'
|
||||
},
|
||||
AssetBalance: {
|
||||
balance: 'TAssetBalance',
|
||||
isFrozen: 'bool',
|
||||
isSufficient: 'bool'
|
||||
},
|
||||
AssetDestroyWitness: {
|
||||
accounts: 'Compact<u32>',
|
||||
sufficients: 'Compact<u32>',
|
||||
approvals: 'Compact<u32>'
|
||||
},
|
||||
AssetDetails: {
|
||||
owner: 'AccountId',
|
||||
issuer: 'AccountId',
|
||||
admin: 'AccountId',
|
||||
freezer: 'AccountId',
|
||||
supply: 'TAssetBalance',
|
||||
deposit: 'TAssetDepositBalance',
|
||||
minBalance: 'TAssetBalance',
|
||||
isSufficient: 'bool',
|
||||
accounts: 'u32',
|
||||
sufficients: 'u32',
|
||||
approvals: 'u32',
|
||||
isFrozen: 'bool'
|
||||
},
|
||||
AssetMetadata: {
|
||||
deposit: 'TAssetDepositBalance',
|
||||
name: 'Vec<u8>',
|
||||
symbol: 'Vec<u8>',
|
||||
decimals: 'u8',
|
||||
isFrozen: 'bool'
|
||||
},
|
||||
TAssetBalance: 'u64',
|
||||
TAssetDepositBalance: 'BalanceOf'
|
||||
}
|
||||
} as Definitions;
|
||||
@@ -0,0 +1,4 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
export * from './types.js';
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DefinitionsCall } from '../../types/index.js';
|
||||
|
||||
export const runtime: DefinitionsCall = {
|
||||
AssetsApi: [
|
||||
{
|
||||
methods: {
|
||||
account_balances: {
|
||||
description: 'Return the current set of authorities.',
|
||||
params: [
|
||||
{
|
||||
name: 'account',
|
||||
type: 'AccountId'
|
||||
}
|
||||
],
|
||||
type: 'Vec<(u32, TAssetBalance)>'
|
||||
}
|
||||
},
|
||||
version: 1
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
import type { Bytes, Compact, Struct, bool, u32, u64, u8 } from '@pezkuwi/types-codec';
|
||||
import type { AccountId, BalanceOf } from '@pezkuwi/types/interfaces/runtime';
|
||||
|
||||
/** @name AssetApproval */
|
||||
export interface AssetApproval extends Struct {
|
||||
readonly amount: TAssetBalance;
|
||||
readonly deposit: TAssetDepositBalance;
|
||||
}
|
||||
|
||||
/** @name AssetApprovalKey */
|
||||
export interface AssetApprovalKey extends Struct {
|
||||
readonly owner: AccountId;
|
||||
readonly delegate: AccountId;
|
||||
}
|
||||
|
||||
/** @name AssetBalance */
|
||||
export interface AssetBalance extends Struct {
|
||||
readonly balance: TAssetBalance;
|
||||
readonly isFrozen: bool;
|
||||
readonly isSufficient: bool;
|
||||
}
|
||||
|
||||
/** @name AssetDestroyWitness */
|
||||
export interface AssetDestroyWitness extends Struct {
|
||||
readonly accounts: Compact<u32>;
|
||||
readonly sufficients: Compact<u32>;
|
||||
readonly approvals: Compact<u32>;
|
||||
}
|
||||
|
||||
/** @name AssetDetails */
|
||||
export interface AssetDetails extends Struct {
|
||||
readonly owner: AccountId;
|
||||
readonly issuer: AccountId;
|
||||
readonly admin: AccountId;
|
||||
readonly freezer: AccountId;
|
||||
readonly supply: TAssetBalance;
|
||||
readonly deposit: TAssetDepositBalance;
|
||||
readonly minBalance: TAssetBalance;
|
||||
readonly isSufficient: bool;
|
||||
readonly accounts: u32;
|
||||
readonly sufficients: u32;
|
||||
readonly approvals: u32;
|
||||
readonly isFrozen: bool;
|
||||
}
|
||||
|
||||
/** @name AssetMetadata */
|
||||
export interface AssetMetadata extends Struct {
|
||||
readonly deposit: TAssetDepositBalance;
|
||||
readonly name: Bytes;
|
||||
readonly symbol: Bytes;
|
||||
readonly decimals: u8;
|
||||
readonly isFrozen: bool;
|
||||
}
|
||||
|
||||
/** @name TAssetBalance */
|
||||
export interface TAssetBalance extends u64 {}
|
||||
|
||||
/** @name TAssetDepositBalance */
|
||||
export interface TAssetDepositBalance extends BalanceOf {}
|
||||
|
||||
export type PHANTOM_ASSETS = 'assets';
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// order important in structs... :)
|
||||
/* eslint-disable sort-keys */
|
||||
|
||||
import type { Definitions } from '../../types/index.js';
|
||||
|
||||
export default {
|
||||
rpc: {},
|
||||
types: {
|
||||
BlockAttestations: {
|
||||
receipt: 'CandidateReceipt',
|
||||
valid: 'Vec<AccountId>',
|
||||
invalid: 'Vec<AccountId>'
|
||||
},
|
||||
IncludedBlocks: {
|
||||
actualNumber: 'BlockNumber',
|
||||
session: 'SessionIndex',
|
||||
randomSeed: 'H256',
|
||||
activeTeyrchains: 'Vec<ParaId>',
|
||||
paraBlocks: 'Vec<Hash>'
|
||||
},
|
||||
MoreAttestations: {}
|
||||
}
|
||||
} as Definitions;
|
||||
@@ -0,0 +1,4 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
export * from './types.js';
|
||||
@@ -0,0 +1,28 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
import type { Struct, Vec } from '@pezkuwi/types-codec';
|
||||
import type { AccountId, BlockNumber, H256, Hash } from '@pezkuwi/types/interfaces/runtime';
|
||||
import type { SessionIndex } from '@pezkuwi/types/interfaces/session';
|
||||
import type { CandidateReceipt, ParaId } from '@pezkuwi/types/interfaces/teyrchains';
|
||||
|
||||
/** @name BlockAttestations */
|
||||
export interface BlockAttestations extends Struct {
|
||||
readonly receipt: CandidateReceipt;
|
||||
readonly valid: Vec<AccountId>;
|
||||
readonly invalid: Vec<AccountId>;
|
||||
}
|
||||
|
||||
/** @name IncludedBlocks */
|
||||
export interface IncludedBlocks extends Struct {
|
||||
readonly actualNumber: BlockNumber;
|
||||
readonly session: SessionIndex;
|
||||
readonly randomSeed: H256;
|
||||
readonly activeTeyrchains: Vec<ParaId>;
|
||||
readonly paraBlocks: Vec<Hash>;
|
||||
}
|
||||
|
||||
/** @name MoreAttestations */
|
||||
export interface MoreAttestations extends Struct {}
|
||||
|
||||
export type PHANTOM_ATTESTATIONS = 'attestations';
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// order important in structs... :)
|
||||
/* eslint-disable sort-keys */
|
||||
|
||||
import type { Definitions } from '../../types/index.js';
|
||||
|
||||
import { runtime } from './runtime.js';
|
||||
|
||||
export default {
|
||||
rpc: {},
|
||||
runtime,
|
||||
types: {
|
||||
RawAuraPreDigest: {
|
||||
slotNumber: 'u64'
|
||||
}
|
||||
}
|
||||
} as Definitions;
|
||||
@@ -0,0 +1,4 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
export * from './types.js';
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DefinitionsCall } from '../../types/index.js';
|
||||
|
||||
export const runtime: DefinitionsCall = {
|
||||
AuraApi: [
|
||||
{
|
||||
methods: {
|
||||
authorities: {
|
||||
description: 'Return the current set of authorities.',
|
||||
params: [],
|
||||
type: 'Vec<AuthorityId>'
|
||||
},
|
||||
slot_duration: {
|
||||
description: 'Returns the slot duration for Aura.',
|
||||
params: [],
|
||||
type: 'SlotDuration'
|
||||
}
|
||||
},
|
||||
version: 1
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
import type { Struct, u64 } from '@pezkuwi/types-codec';
|
||||
|
||||
/** @name RawAuraPreDigest */
|
||||
export interface RawAuraPreDigest extends Struct {
|
||||
readonly slotNumber: u64;
|
||||
}
|
||||
|
||||
export type PHANTOM_AURA = 'aura';
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
import type { ExtrinsicStatus } from './types.js';
|
||||
|
||||
import rpc from '@pezkuwi/types-support/json/ExtrinsicStatus.001.json' assert { type: 'json' };
|
||||
|
||||
import { TypeRegistry } from '../../create/index.js';
|
||||
|
||||
describe('ExtrinsicStatus', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
let status: ExtrinsicStatus;
|
||||
|
||||
beforeEach((): void => {
|
||||
status = registry.createType('ExtrinsicStatus', rpc.params.result);
|
||||
});
|
||||
|
||||
it('has the correct type', (): void => {
|
||||
expect(
|
||||
status.type
|
||||
).toEqual('Finalized');
|
||||
});
|
||||
|
||||
it('has the correct hash', (): void => {
|
||||
expect(
|
||||
status.value.toString()
|
||||
).toEqual('0xc465b92a72b1d20918d64cd4effa70c2bb58b53a3f8c24c3ac8fd8f465f059b4');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2025 @pezkuwi/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// order important in structs... :)
|
||||
/* eslint-disable sort-keys */
|
||||
|
||||
import type { Definitions } from '../../types/index.js';
|
||||
|
||||
import { rpc } from './rpc.js';
|
||||
|
||||
export default {
|
||||
rpc,
|
||||
types: {
|
||||
ExtrinsicOrHash: {
|
||||
_enum: {
|
||||
Hash: 'Hash',
|
||||
Extrinsic: 'Bytes'
|
||||
}
|
||||
},
|
||||
ExtrinsicStatus: {
|
||||
_enum: {
|
||||
Future: 'Null',
|
||||
Ready: 'Null',
|
||||
Broadcast: 'Vec<Text>',
|
||||
InBlock: 'Hash',
|
||||
Retracted: 'Hash',
|
||||
FinalityTimeout: 'Hash',
|
||||
Finalized: 'Hash',
|
||||
Usurped: 'Hash',
|
||||
Dropped: 'Null',
|
||||
Invalid: 'Null'
|
||||
}
|
||||
}
|
||||
}
|
||||
} as Definitions;
|
||||
@@ -0,0 +1,4 @@
|
||||
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
|
||||
/* eslint-disable */
|
||||
|
||||
export * from './types.js';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user