Rebrand: polkadot → pezkuwi, substrate → bizinikiwi, kusama → dicle

This commit is contained in:
2026-01-07 02:29:40 +03:00
commit d5f038faea
1383 changed files with 1088018 additions and 0 deletions
+3
View File
@@ -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).
+37
View File
@@ -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"
}
}
+22
View File
@@ -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 };
+101
View File
@@ -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');
+9
View File
@@ -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';
+4
View File
@@ -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'
);
});
});
+11
View File
@@ -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);
});
});
});
+17
View File
@@ -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);
}
+11
View File
@@ -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';
+22
View File
@@ -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;
}
+186
View File
@@ -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])
);
});
});
+639
View File
@@ -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`);
}
}
}
}
+9
View File
@@ -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);
});
});
});
+76
View File
@@ -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);
});
});
});
+122
View File
@@ -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
);
}
}
+5
View File
@@ -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);
});
});
});
});
+461
View File
@@ -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'
};
}
}
+28
View File
@@ -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;
+14
View File
@@ -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>;
+25
View File
@@ -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';
+23
View File
@@ -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;
}
}
+6
View File
@@ -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');
}
}
+7
View File
@@ -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'
]);
});
});
});
+100
View File
@@ -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);
});
});
});
+129
View File
@@ -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';
}
}
+49
View File
@@ -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]);
});
});
+65
View File
@@ -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');
}
}
+47
View File
@@ -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);
});
});
+244
View File
@@ -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);
}
}
+200
View File
@@ -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
])
);
});
});
+141
View File
@@ -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();
}
}
+167
View File
@@ -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');
});
});
});
+133
View File
@@ -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';
}
}
+17
View File
@@ -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';
+78
View File
@@ -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))
);
});
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2025 @pezkuwi/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './packageDetect.js';
export * from './bundle.js';
+6
View File
@@ -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'
});
});
});
+116
View File
@@ -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