diff --git a/CHANGELOG.md b/CHANGELOG.md
index b59aa5e3c1..0f03888002 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# CHANGELOG
+## 1.32.0-beta.x
+
+Changes:
+
+- Added `derive.chain.getBlock(hash)` to retrieve a `SignedBlock` extended with an `.author` (same as `derive.chain.getHeader(...)`)
+
+
## 1.31.1 Aug 31, 2020
Upgrade priority: Medium, especially for staking users on Polkadot, Kusama & Westend (type updates applied for v23 onwards) or if transparent `.at(hash)` queries are required/used
diff --git a/packages/api-derive/src/chain/getBlock.ts b/packages/api-derive/src/chain/getBlock.ts
new file mode 100644
index 0000000000..156124d4e0
--- /dev/null
+++ b/packages/api-derive/src/chain/getBlock.ts
@@ -0,0 +1,44 @@
+// Copyright 2017-2020 @polkadot/api-derive authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+import { ApiInterfaceRx } from '@polkadot/api/types';
+
+import { Observable, combineLatest, of } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+import { SignedBlockExtended } from '../type';
+import { memo } from '../util';
+
+/**
+ * @name getBlock
+ * @param {( Uint8Array | string )} hash - A block hash as U8 array or string.
+ * @description Get a specific block (e.g. rpc.chain.getBlock) and extend it with the author
+ * @example
+ *
+ *
+ * ```javascript
+ * const { author, number } = await api.derive.chain.getHeader('0x123...456');
+ *
+ * console.log(`block #${number} was authored by ${author}`);
+ * ```
+ */
+export function getHeader (api: ApiInterfaceRx): (hash: Uint8Array | string) => Observable {
+ return memo((hash: Uint8Array | string): Observable =>
+ combineLatest([
+ api.rpc.chain.getBlock(hash),
+ api.query.session
+ ? api.query.session.validators.at(hash)
+ : of([])
+ ]).pipe(
+ map(([signedBlock, validators]): SignedBlockExtended =>
+ new SignedBlockExtended(api.registry, signedBlock, validators)
+ ),
+ catchError((): Observable =>
+ // where rpc.chain.getHeader throws, we will land here - it can happen that
+ // we supplied an invalid hash. (Due to defaults, storeage will have an
+ // empty value, so only the RPC is affected). So return undefined
+ of()
+ )
+ ));
+}
diff --git a/packages/api-derive/src/type/HeaderExtended.ts b/packages/api-derive/src/type/HeaderExtended.ts
index 7b0b8869cc..06c7980986 100644
--- a/packages/api-derive/src/type/HeaderExtended.ts
+++ b/packages/api-derive/src/type/HeaderExtended.ts
@@ -8,6 +8,8 @@ import { AnyJson, Constructor, Registry } from '@polkadot/types/types';
import runtimeTypes from '@polkadot/types/interfaces/runtime/definitions';
import { Struct } from '@polkadot/types';
+import { extractAuthor } from './util';
+
// We can ignore the properties, added via Struct.with
const _Header = Struct.with(runtimeTypes.types.Header as any) as Constructor;
@@ -22,29 +24,7 @@ export default class HeaderExtended extends _Header {
constructor (registry: Registry, header?: Header, sessionValidators?: AccountId[]) {
super(registry, header);
- this.#author = this._extractAuthor(sessionValidators);
- }
-
- private _extractAuthor (sessionValidators: AccountId[] = []): AccountId | undefined {
- const [pitem] = this.digest.logs.filter(({ type }) => type === 'PreRuntime');
-
- // extract from the substrate 2.0 PreRuntime digest
- if (pitem) {
- const [engine, data] = pitem.asPreRuntime;
-
- return engine.extractAuthor(data, sessionValidators);
- } else {
- const [citem] = this.digest.logs.filter(({ type }) => type === 'Consensus');
-
- // extract author from the consensus (substrate 1.0, digest)
- if (citem) {
- const [engine, data] = citem.asConsensus;
-
- return engine.extractAuthor(data, sessionValidators);
- }
- }
-
- return undefined;
+ this.#author = extractAuthor(this.digest, sessionValidators);
}
/**
diff --git a/packages/api-derive/src/type/SignedBlockExtended.ts b/packages/api-derive/src/type/SignedBlockExtended.ts
new file mode 100644
index 0000000000..42bb850837
--- /dev/null
+++ b/packages/api-derive/src/type/SignedBlockExtended.ts
@@ -0,0 +1,60 @@
+// Copyright 2017-2020 @polkadot/api-derive authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+import { AccountId, SignedBlock } from '@polkadot/types/interfaces';
+import { AnyJson, Constructor, Registry } from '@polkadot/types/types';
+
+import runtimeTypes from '@polkadot/types/interfaces/runtime/definitions';
+import { Struct } from '@polkadot/types';
+
+import { extractAuthor } from './util';
+
+// We can ignore the properties, added via Struct.with
+const _SignedBlock = Struct.with(runtimeTypes.types.SignedBlock as any) as Constructor;
+
+/**
+ * @name SignedBlockExtended
+ * @description
+ * A [[Block]] header with an additional `author` field that indicates the block author
+ */
+export default class BlockExtended extends _SignedBlock {
+ readonly #author?: AccountId;
+
+ constructor (registry: Registry, block?: SignedBlock, sessionValidators?: AccountId[]) {
+ super(registry, block);
+
+ this.#author = extractAuthor(this.block.header.digest, sessionValidators);
+ }
+
+ /**
+ * @description Convenience method, returns the author for the block
+ */
+ public get author (): AccountId | undefined {
+ return this.#author;
+ }
+
+ /**
+ * @description Creates a human-friendly JSON representation
+ */
+ public toHuman (isExtended?: boolean): AnyJson {
+ return {
+ ...super.toHuman(isExtended) as { [index: string]: AnyJson },
+ author: this.author
+ ? this.author.toHuman()
+ : undefined
+ };
+ }
+
+ /**
+ * @description Creates the JSON representation
+ */
+ public toJSON (): AnyJson {
+ return {
+ ...super.toJSON() as { [index: string]: AnyJson },
+ author: this.author
+ ? this.author.toJSON()
+ : undefined
+ };
+ }
+}
diff --git a/packages/api-derive/src/type/index.ts b/packages/api-derive/src/type/index.ts
index 0040bfc0e3..bc4c675114 100644
--- a/packages/api-derive/src/type/index.ts
+++ b/packages/api-derive/src/type/index.ts
@@ -3,3 +3,4 @@
// of the Apache-2.0 license. See the LICENSE file for details.
export { default as HeaderExtended } from './HeaderExtended';
+export { default as SignedBlockExtended } from './SignedBlockExtended';
diff --git a/packages/api-derive/src/type/util.ts b/packages/api-derive/src/type/util.ts
new file mode 100644
index 0000000000..68db9e19e0
--- /dev/null
+++ b/packages/api-derive/src/type/util.ts
@@ -0,0 +1,27 @@
+// Copyright 2017-2020 @polkadot/api-derive authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+import { AccountId, Digest } from '@polkadot/types/interfaces';
+
+export function extractAuthor (digest: Digest, sessionValidators: AccountId[] = []): AccountId | undefined {
+ const [pitem] = digest.logs.filter(({ type }) => type === 'PreRuntime');
+
+ // extract from the substrate 2.0 PreRuntime digest
+ if (pitem) {
+ const [engine, data] = pitem.asPreRuntime;
+
+ return engine.extractAuthor(data, sessionValidators);
+ } else {
+ const [citem] = digest.logs.filter(({ type }) => type === 'Consensus');
+
+ // extract author from the consensus (substrate 1.0, digest)
+ if (citem) {
+ const [engine, data] = citem.asConsensus;
+
+ return engine.extractAuthor(data, sessionValidators);
+ }
+ }
+
+ return undefined;
+}