Procházet zdrojové kódy

Merge branch 'post_giza_content_dir' into giza_feature_NewContentFeaturesAndNFT

ignazio před 3 roky
rodič
revize
faf8906143
33 změnil soubory, kde provedl 14713 přidání a 35 odebrání
  1. 15 0
      node/src/chain_spec/mod.rs
  2. 279 0
      query-node/mappings/sumer/content/channel.ts
  3. 773 0
      query-node/mappings/sumer/content/utils.ts
  4. 498 0
      query-node/mappings/sumer/content/video.ts
  5. 136 3
      runtime-modules/content/src/errors.rs
  6. 710 12
      runtime-modules/content/src/lib.rs
  7. 385 0
      runtime-modules/content/src/nft/mod.rs
  8. 499 0
      runtime-modules/content/src/nft/types.rs
  9. 47 1
      runtime-modules/content/src/permissions/mod.rs
  10. 1 1
      runtime-modules/content/src/tests/curators.rs
  11. 191 13
      runtime-modules/content/src/tests/mock.rs
  12. 1 0
      runtime-modules/content/src/tests/mod.rs
  13. 14 0
      runtime-modules/content/src/tests/nft.rs
  14. 287 0
      runtime-modules/content/src/tests/nft/accept_incoming_offer.rs
  15. 344 0
      runtime-modules/content/src/tests/nft/buy_nft.rs
  16. 226 0
      runtime-modules/content/src/tests/nft/cancel_buy_now.rs
  17. 288 0
      runtime-modules/content/src/tests/nft/cancel_nft_auction.rs
  18. 223 0
      runtime-modules/content/src/tests/nft/cancel_offer.rs
  19. 509 0
      runtime-modules/content/src/tests/nft/cancel_open_auction_bid.rs
  20. 444 0
      runtime-modules/content/src/tests/nft/claim_won_english_auction.rs
  21. 198 0
      runtime-modules/content/src/tests/nft/issue_nft.rs
  22. 660 0
      runtime-modules/content/src/tests/nft/make_bid.rs
  23. 222 0
      runtime-modules/content/src/tests/nft/offer_nft.rs
  24. 441 0
      runtime-modules/content/src/tests/nft/pick_open_auction_winner.rs
  25. 226 0
      runtime-modules/content/src/tests/nft/sell_nft.rs
  26. 212 0
      runtime-modules/content/src/tests/nft/sling_nft_back.rs
  27. 676 0
      runtime-modules/content/src/tests/nft/start_nft_auction.rs
  28. 42 5
      runtime-modules/content/src/tests/videos.rs
  29. 431 0
      runtime-modules/content/src/types.rs
  30. 5 0
      runtime-modules/governance/src/mock.rs
  31. 16 0
      types/augment-codec/all.ts
  32. 2857 0
      types/augment-codec/augment-api-errors.ts
  33. 2857 0
      types/augment/augment-api-errors.ts

+ 15 - 0
node/src/chain_spec/mod.rs

@@ -377,6 +377,21 @@ pub fn testnet_genesis(
                 },
                 max_reward_allowed: 1000,
                 min_cashout_allowed: 1,
+                min_auction_duration: 3,
+                max_auction_duration: 20,
+                min_auction_extension_period: 5,
+                max_auction_extension_period: 30,
+                min_bid_lock_duration: 2,
+                max_bid_lock_duration: 10,
+                min_starting_price: 10,
+                max_starting_price: 1000,
+                min_creator_royalty: Perbill::from_percent(1),
+                max_creator_royalty: Perbill::from_percent(5),
+                min_bid_step: 10,
+                max_bid_step: 100,
+                platform_fee_percentage: Perbill::from_percent(1),
+                auction_starts_at_max_delta: 90_000,
+                max_auction_whitelist_length: 100,
             }
         }),
         proposals_codex: Some(ProposalsCodexConfig {

+ 279 - 0
query-node/mappings/sumer/content/channel.ts

@@ -0,0 +1,279 @@
+import { fixBlockTimestamp } from '../eventFix'
+import { SubstrateEvent } from '@dzlzv/hydra-common'
+import { DatabaseManager } from '@dzlzv/hydra-db-utils'
+import ISO6391 from 'iso-639-1'
+import { FindConditions, In } from 'typeorm'
+
+import { AccountId } from '@polkadot/types/interfaces'
+import { Option } from '@polkadot/types/codec'
+import { Content } from '../../../generated/types'
+import {
+  readProtobuf,
+  readProtobufWithAssets,
+  convertContentActorToChannelOwner,
+  convertContentActorToDataObjectOwner,
+} from './utils'
+
+import { Channel, ChannelCategory, DataObject, AssetAvailability } from 'query-node'
+import { inconsistentState, logger } from '../common'
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_ChannelCreated(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const { channelId, channelCreationParameters, contentActor } = new Content.ChannelCreatedEvent(event).data
+
+  // read metadata
+  const protobufContent = await readProtobufWithAssets(new Channel(), {
+    metadata: channelCreationParameters.meta,
+    db,
+    event,
+    assets: channelCreationParameters.assets,
+    ChannelOwner: convertContentActorToDataObjectOwner(contentActor, channelId.toNumber()),
+  })
+
+  // create entity
+  const channel = new Channel({
+    // main data
+    id: channelId.toString(),
+    isCensored: false,
+    videos: [],
+    createdInBlock: event.blockNumber,
+
+    // default values for properties that might or might not be filled by metadata
+    coverPhotoUrls: [],
+    coverPhotoAvailability: AssetAvailability.INVALID,
+    avatarPhotoUrls: [],
+    avatarPhotoAvailability: AssetAvailability.INVALID,
+
+    // fill in auto-generated fields
+    createdAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+    updatedAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+
+    // prepare channel owner (handles fields `ownerMember` and `ownerCuratorGroup`)
+    ...(await convertContentActorToChannelOwner(db, contentActor)),
+
+    // integrate metadata
+    ...protobufContent,
+  })
+
+  // save entity
+  await db.save<Channel>(channel)
+
+  // emit log event
+  logger.info('Channel has been created', { id: channel.id })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_ChannelUpdated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { channelId, channelUpdateParameters, contentActor } = new Content.ChannelUpdatedEvent(event).data
+
+  // load channel
+  const channel = await db.get(Channel, { where: { id: channelId.toString() } as FindConditions<Channel> })
+
+  // ensure channel exists
+  if (!channel) {
+    return inconsistentState('Non-existing channel update requested', channelId)
+  }
+
+  // prepare changed metadata
+  const newMetadata = channelUpdateParameters.new_meta.unwrapOr(null)
+
+  //  update metadata if it was changed
+  if (newMetadata) {
+    const protobufContent = await readProtobufWithAssets(new Channel(), {
+      metadata: newMetadata,
+      db,
+      event,
+      assets: channelUpdateParameters.assets.unwrapOr([]),
+      ChannelOwner: convertContentActorToDataObjectOwner(contentActor, channelId.toNumber()),
+    })
+
+    // update all fields read from protobuf
+    for (const [key, value] of Object.entries(protobufContent)) {
+      channel[key] = value
+    }
+  }
+
+  // prepare changed reward account
+  const newRewardAccount = channelUpdateParameters.reward_account.unwrapOr(null)
+
+  // reward account change happened?
+  if (newRewardAccount) {
+    // this will change the `channel`!
+    handleChannelRewardAccountChange(channel, newRewardAccount)
+  }
+
+  // set last update time
+  channel.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+  // save channel
+  await db.save<Channel>(channel)
+
+  // emit log event
+  logger.info('Channel has been updated', { id: channel.id })
+}
+
+export async function content_ChannelAssetsRemoved(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { contentId: contentIds } = new Content.ChannelAssetsRemovedEvent(event).data
+
+  // load channel
+  const assets = await db.getMany(DataObject, {
+    where: {
+      id: In(contentIds.toArray().map((item) => item.toString())),
+    } as FindConditions<DataObject>,
+  })
+
+  // delete assets
+  for (const asset of assets) {
+    await db.remove<DataObject>(asset)
+  }
+
+  // emit log event
+  logger.info('Channel assets have been removed', { ids: contentIds })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_ChannelCensorshipStatusUpdated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { channelId, isCensored } = new Content.ChannelCensorshipStatusUpdatedEvent(event).data
+
+  // load event
+  const channel = await db.get(Channel, { where: { id: channelId.toString() } as FindConditions<Channel> })
+
+  // ensure channel exists
+  if (!channel) {
+    return inconsistentState('Non-existing channel censoring requested', channelId)
+  }
+
+  // update channel
+  channel.isCensored = isCensored.isTrue
+
+  // set last update time
+  channel.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+  // save channel
+  await db.save<Channel>(channel)
+
+  // emit log event
+  logger.info('Channel censorship status has been updated', { id: channelId, isCensored: isCensored.isTrue })
+}
+
+/// ///////////////// ChannelCategory ////////////////////////////////////////////
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_ChannelCategoryCreated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { channelCategoryCreationParameters, channelCategoryId } = new Content.ChannelCategoryCreatedEvent(event).data
+
+  // read metadata
+  const protobufContent = await readProtobuf(new ChannelCategory(), {
+    metadata: channelCategoryCreationParameters.meta,
+    db,
+    event,
+  })
+
+  // create new channel category
+  const channelCategory = new ChannelCategory({
+    // main data
+    id: channelCategoryId.toString(),
+    channels: [],
+    createdInBlock: event.blockNumber,
+
+    // fill in auto-generated fields
+    createdAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+    updatedAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+
+    // integrate metadata
+    ...protobufContent,
+  })
+
+  // save channel
+  await db.save<ChannelCategory>(channelCategory)
+
+  // emit log event
+  logger.info('Channel category has been created', { id: channelCategory.id })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_ChannelCategoryUpdated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { channelCategoryId, channelCategoryUpdateParameters } = new Content.ChannelCategoryUpdatedEvent(event).data
+
+  // load channel category
+  const channelCategory = await db.get(ChannelCategory, {
+    where: {
+      id: channelCategoryId.toString(),
+    } as FindConditions<ChannelCategory>,
+  })
+
+  // ensure channel exists
+  if (!channelCategory) {
+    return inconsistentState('Non-existing channel category update requested', channelCategoryId)
+  }
+
+  // read metadata
+  const protobufContent = await readProtobuf(new ChannelCategory(), {
+    metadata: channelCategoryUpdateParameters.new_meta,
+    db,
+    event,
+  })
+
+  // update all fields read from protobuf
+  for (const [key, value] of Object.entries(protobufContent)) {
+    channelCategory[key] = value
+  }
+
+  // set last update time
+  channelCategory.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+  // save channel category
+  await db.save<ChannelCategory>(channelCategory)
+
+  // emit log event
+  logger.info('Channel category has been updated', { id: channelCategory.id })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_ChannelCategoryDeleted(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { channelCategoryId } = new Content.ChannelCategoryDeletedEvent(event).data
+
+  // load channel category
+  const channelCategory = await db.get(ChannelCategory, {
+    where: {
+      id: channelCategoryId.toString(),
+    } as FindConditions<ChannelCategory>,
+  })
+
+  // ensure channel category exists
+  if (!channelCategory) {
+    return inconsistentState('Non-existing channel category deletion requested', channelCategoryId)
+  }
+
+  // delete channel category
+  await db.remove<ChannelCategory>(channelCategory)
+
+  // emit log event
+  logger.info('Channel category has been deleted', { id: channelCategory.id })
+}
+
+/// ///////////////// Helpers ////////////////////////////////////////////////////
+
+function handleChannelRewardAccountChange(
+  channel: Channel, // will be modified inside of the function!
+  reward_account: Option<AccountId>
+) {
+  const rewardAccount = reward_account.unwrapOr(null)
+
+  // new different reward account set?
+  if (rewardAccount) {
+    channel.rewardAccount = rewardAccount.toString()
+    return
+  }
+
+  // reward account removed
+
+  channel.rewardAccount = undefined // plan deletion (will have effect when saved to db)
+}

+ 773 - 0
query-node/mappings/sumer/content/utils.ts

@@ -0,0 +1,773 @@
+// TODO: finish db cascade on save/remove; right now there is manually added `cascade: ["insert", "update"]` directive
+//       to all relations in `query-node/generated/graphql-server/src/modules/**/*.model.ts`. That should ensure all records
+//       are saved on one `db.save(...)` call. Missing features
+//       - find a proper way to cascade on remove or implement custom removals for every entity
+//       - convert manual changes done to `*model.ts` file into some patch or bash commands that can be executed
+//         every time query node codegen is run (that will overwrite said manual changes)
+//       - verify in integration tests that the records are trully created/updated/removed as expected
+
+import { SubstrateEvent } from '@dzlzv/hydra-common'
+import { DatabaseManager } from '@dzlzv/hydra-db-utils'
+import { Bytes } from '@polkadot/types'
+import ISO6391 from 'iso-639-1'
+import { u64 } from '@polkadot/types/primitive'
+import { FindConditions } from 'typeorm'
+import * as jspb from 'google-protobuf'
+import { fixBlockTimestamp } from '../eventFix'
+
+// protobuf definitions
+import {
+  ChannelMetadata,
+  ChannelCategoryMetadata,
+  PublishedBeforeJoystream as PublishedBeforeJoystreamMetadata,
+  License as LicenseMetadata,
+  MediaType as MediaTypeMetadata,
+  VideoMetadata,
+  VideoCategoryMetadata,
+} from '@joystream/content-metadata-protobuf'
+
+import { Content } from '../../../generated/types'
+
+import { invalidMetadata, inconsistentState, logger, prepareDataObject, getNextId } from '../common'
+
+import {
+  // primary entities
+  CuratorGroup,
+  Channel,
+  ChannelCategory,
+  Video,
+  VideoCategory,
+
+  // secondary entities
+  Language,
+  License,
+  VideoMediaEncoding,
+  VideoMediaMetadata,
+
+  // asset
+  DataObjectOwner,
+  DataObjectOwnerMember,
+  DataObjectOwnerChannel,
+  DataObject,
+  LiaisonJudgement,
+  AssetAvailability,
+  Membership,
+} from 'query-node'
+
+// Joystream types
+import { ChannelId, ContentParameters, NewAsset, ContentActor } from '@joystream/types/augment'
+
+import { ContentParameters as Custom_ContentParameters } from '@joystream/types/storage'
+import { registry } from '@joystream/types'
+
+/*
+  Asset either stored in storage or describing list of URLs.
+*/
+type AssetStorageOrUrls = DataObject | string[]
+
+/*
+  Type guard differentiating asset stored in storage from asset describing a list of URLs.
+*/
+function isAssetInStorage(dataObject: AssetStorageOrUrls): dataObject is DataObject {
+  if (Array.isArray(dataObject)) {
+    return false
+  }
+
+  return true
+}
+
+export interface IReadProtobufArguments {
+  metadata: Bytes
+  db: DatabaseManager
+  event: SubstrateEvent
+}
+
+export interface IReadProtobufArgumentsWithAssets extends IReadProtobufArguments {
+  assets: NewAsset[] // assets provided in event
+  ChannelOwner: typeof DataObjectOwner
+}
+
+/*
+  This class represents one of 3 possible states when changing property read from metadata.
+  NoChange - don't change anything (used when invalid metadata are encountered)
+  Unset - unset the value (used when the unset is requested in runtime)
+  Change - set the new value
+*/
+export class PropertyChange<T> {
+  static newUnset<T>(): PropertyChange<T> {
+    return new PropertyChange<T>('unset')
+  }
+
+  static newNoChange<T>(): PropertyChange<T> {
+    return new PropertyChange<T>('nochange')
+  }
+
+  static newChange<T>(value: T): PropertyChange<T> {
+    return new PropertyChange<T>('change', value)
+  }
+
+  /*
+    Determines property change from the given object property.
+  */
+  static fromObjectProperty<T, Key extends string, ChangedObject extends { [key in Key]?: T }>(
+    object: ChangedObject,
+    key: Key
+  ): PropertyChange<T> {
+    if (!(key in object)) {
+      return PropertyChange.newNoChange<T>()
+    }
+
+    if (object[key] === undefined) {
+      return PropertyChange.newUnset<T>()
+    }
+
+    return PropertyChange.newChange<T>(object[key] as T)
+  }
+
+  private type: string
+  private value?: T
+
+  private constructor(type: 'change' | 'nochange' | 'unset', value?: T) {
+    this.type = type
+    this.value = value
+  }
+
+  public isUnset(): boolean {
+    return this.type === 'unset'
+  }
+
+  public isNoChange(): boolean {
+    return this.type === 'nochange'
+  }
+
+  public isValue(): boolean {
+    return this.type === 'change'
+  }
+
+  public getValue(): T | undefined {
+    return this.type === 'change' ? this.value : undefined
+  }
+
+  /*
+    Integrates the value into the given dictionary.
+  */
+  public integrateInto(object: Object, key: string): void {
+    if (this.isNoChange()) {
+      return
+    }
+
+    if (this.isUnset()) {
+      delete object[key]
+      return
+    }
+
+    object[key] = this.value
+  }
+}
+
+export interface RawVideoMetadata {
+  encoding: {
+    codecName: PropertyChange<string>
+    container: PropertyChange<string>
+    mimeMediaType: PropertyChange<string>
+  }
+  pixelWidth: PropertyChange<number>
+  pixelHeight: PropertyChange<number>
+  size: PropertyChange<number>
+}
+
+/*
+  Reads information from the event and protobuf metadata and constructs changeset that's fit to be used when saving to db.
+*/
+export async function readProtobuf<T extends ChannelCategory | VideoCategory>(
+  type: T,
+  parameters: IReadProtobufArguments
+): Promise<Partial<T>> {
+  // true option here is crucial, it indicates that we want just the underlying bytes (by default it will also include bytes encoding the length)
+  const metaU8a = parameters.metadata.toU8a(true)
+
+  // process channel category
+  if (type instanceof ChannelCategory) {
+    const meta = ChannelCategoryMetadata.deserializeBinary(metaU8a)
+    const result = convertMetadataToObject<ChannelCategoryMetadata.AsObject>(meta) as Partial<T>
+
+    return result
+  }
+
+  // process video category
+  if (type instanceof VideoCategory) {
+    const meta = VideoCategoryMetadata.deserializeBinary(metaU8a)
+    const result = convertMetadataToObject<VideoCategoryMetadata.AsObject>(meta) as Partial<T>
+
+    return result
+  }
+
+  // this should never happen
+  logger.error('Not implemented metadata type', { type })
+  throw new Error(`Not implemented metadata type`)
+}
+
+/*
+  Reads information from the event and protobuf metadata and constructs changeset that's fit to be used when saving to db.
+  In addition it handles any assets associated with the metadata.
+*/
+
+export async function readProtobufWithAssets<T extends Channel | Video>(
+  type: T,
+  parameters: IReadProtobufArgumentsWithAssets
+): Promise<Partial<T>> {
+  // true option here is crucial, it indicates that we want just the underlying bytes (by default it will also include bytes encoding the length)
+  const metaU8a = parameters.metadata.toU8a(true)
+
+  // process channel
+  if (type instanceof Channel) {
+    const meta = ChannelMetadata.deserializeBinary(metaU8a)
+    const metaAsObject = convertMetadataToObject<ChannelMetadata.AsObject>(meta)
+    const result = (metaAsObject as any) as Partial<Channel>
+
+    // prepare cover photo asset if needed
+    if ('coverPhoto' in metaAsObject) {
+      const asset = await extractAsset({
+        assetIndex: metaAsObject.coverPhoto,
+        assets: parameters.assets,
+        db: parameters.db,
+        event: parameters.event,
+        ChannelOwner: parameters.ChannelOwner,
+      })
+      integrateAsset('coverPhoto', result, asset) // changes `result` inline!
+      delete metaAsObject.coverPhoto
+    }
+
+    // prepare avatar photo asset if needed
+    if ('avatarPhoto' in metaAsObject) {
+      const asset = await extractAsset({
+        assetIndex: metaAsObject.avatarPhoto,
+        assets: parameters.assets,
+        db: parameters.db,
+        event: parameters.event,
+        ChannelOwner: parameters.ChannelOwner,
+      })
+      integrateAsset('avatarPhoto', result, asset) // changes `result` inline!
+      delete metaAsObject.avatarPhoto
+    }
+
+    // prepare language if needed
+    if ('language' in metaAsObject) {
+      const language = await prepareLanguage(metaAsObject.language, parameters.db, parameters.event)
+      delete metaAsObject.language // make sure temporary value will not interfere
+      language.integrateInto(result, 'language')
+    }
+
+    return result as Partial<T>
+  }
+
+  // process video
+  if (type instanceof Video) {
+    const meta = VideoMetadata.deserializeBinary(metaU8a)
+    const metaAsObject = convertMetadataToObject<VideoMetadata.AsObject>(meta)
+    const result = (metaAsObject as any) as Partial<Video>
+
+    // prepare video category if needed
+    if ('category' in metaAsObject) {
+      const category = await prepareVideoCategory(metaAsObject.category, parameters.db)
+      delete metaAsObject.category // make sure temporary value will not interfere
+      category.integrateInto(result, 'category')
+    }
+
+    // prepare media meta information if needed
+    if ('mediaType' in metaAsObject || 'mediaPixelWidth' in metaAsObject || 'mediaPixelHeight' in metaAsObject) {
+      // prepare video file size if poosible
+      const videoSize = extractVideoSize(parameters.assets, metaAsObject.video)
+
+      // NOTE: type hack - `RawVideoMetadata` is inserted instead of VideoMediaMetadata - it should be edited in `video.ts`
+      //       see `integrateVideoMetadata()` in `video.ts` for more info
+      result.mediaMetadata = (prepareVideoMetadata(
+        metaAsObject,
+        videoSize,
+        parameters.event.blockNumber
+      ) as unknown) as VideoMediaMetadata
+
+      // remove extra values
+      delete metaAsObject.mediaType
+      delete metaAsObject.mediaPixelWidth
+      delete metaAsObject.mediaPixelHeight
+    }
+
+    // prepare license if needed
+    if ('license' in metaAsObject) {
+      result.license = await prepareLicense(parameters.db, metaAsObject.license, parameters.event)
+    }
+
+    // prepare thumbnail photo asset if needed
+    if ('thumbnailPhoto' in metaAsObject) {
+      const asset = await extractAsset({
+        assetIndex: metaAsObject.thumbnailPhoto,
+        assets: parameters.assets,
+        db: parameters.db,
+        event: parameters.event,
+        ChannelOwner: parameters.ChannelOwner,
+      })
+      integrateAsset('thumbnailPhoto', result, asset) // changes `result` inline!
+      delete metaAsObject.thumbnailPhoto
+    }
+
+    // prepare video asset if needed
+    if ('video' in metaAsObject) {
+      const asset = await extractAsset({
+        assetIndex: metaAsObject.video,
+        assets: parameters.assets,
+        db: parameters.db,
+        event: parameters.event,
+        ChannelOwner: parameters.ChannelOwner,
+      })
+      integrateAsset('media', result, asset) // changes `result` inline!
+      delete metaAsObject.video
+    }
+
+    // prepare language if needed
+    if ('language' in metaAsObject) {
+      const language = await prepareLanguage(metaAsObject.language, parameters.db, parameters.event)
+      delete metaAsObject.language // make sure temporary value will not interfere
+      language.integrateInto(result, 'language')
+    }
+
+    if (metaAsObject.publishedBeforeJoystream) {
+      const publishedBeforeJoystream = handlePublishedBeforeJoystream(result, metaAsObject.publishedBeforeJoystream)
+      delete metaAsObject.publishedBeforeJoystream // make sure temporary value will not interfere
+      publishedBeforeJoystream.integrateInto(result, 'publishedBeforeJoystream')
+    }
+
+    return result as Partial<T>
+  }
+
+  // this should never happen
+  logger.error('Not implemented metadata type', { type })
+  throw new Error(`Not implemented metadata type`)
+}
+
+export async function convertContentActorToChannelOwner(
+  db: DatabaseManager,
+  contentActor: ContentActor
+): Promise<{
+  ownerMember?: Membership
+  ownerCuratorGroup?: CuratorGroup
+}> {
+  if (contentActor.isMember) {
+    const memberId = contentActor.asMember.toNumber()
+    const member = await db.get(Membership, { where: { id: memberId.toString() } as FindConditions<Membership> })
+
+    // ensure member exists
+    if (!member) {
+      return inconsistentState(`Actor is non-existing member`, memberId)
+    }
+
+    return {
+      ownerMember: member,
+      ownerCuratorGroup: undefined, // this will clear the field
+    }
+  }
+
+  if (contentActor.isCurator) {
+    const curatorGroupId = contentActor.asCurator[0].toNumber()
+    const curatorGroup = await db.get(CuratorGroup, {
+      where: { id: curatorGroupId.toString() } as FindConditions<CuratorGroup>,
+    })
+
+    // ensure curator group exists
+    if (!curatorGroup) {
+      return inconsistentState('Actor is non-existing curator group', curatorGroupId)
+    }
+
+    return {
+      ownerMember: undefined, // this will clear the field
+      ownerCuratorGroup: curatorGroup,
+    }
+  }
+
+  // TODO: contentActor.isLead
+
+  logger.error('Not implemented ContentActor type', { contentActor: contentActor.toString() })
+  throw new Error('Not-implemented ContentActor type used')
+}
+
+export function convertContentActorToDataObjectOwner(
+  contentActor: ContentActor,
+  channelId: number
+): typeof DataObjectOwner {
+  const owner = new DataObjectOwnerChannel()
+  owner.channel = channelId
+
+  return owner
+
+  /* contentActor is irrelevant now -> all video/channel content belongs to the channel
+  if (contentActor.isMember) {
+    const owner = new DataObjectOwnerMember()
+    owner.member = contentActor.asMember.toBn()
+
+    return owner
+  }
+
+  if (contentActor.isLead || contentActor.isCurator) {
+    const owner = new DataObjectOwnerChannel()
+    owner.channel = channelId
+
+    return owner
+  }
+
+  logger.error('Not implemented ContentActor type', {contentActor: contentActor.toString()})
+  throw new Error('Not-implemented ContentActor type used')
+  */
+}
+
+function handlePublishedBeforeJoystream(
+  video: Partial<Video>,
+  metadata: PublishedBeforeJoystreamMetadata.AsObject
+): PropertyChange<Date> {
+  // is publish being unset
+  if ('isPublished' in metadata && !metadata.isPublished) {
+    return PropertyChange.newUnset()
+  }
+
+  // try to parse timestamp from publish date
+  const timestamp = metadata.date ? Date.parse(metadata.date) : NaN
+
+  // ensure date is valid
+  if (isNaN(timestamp)) {
+    invalidMetadata(`Invalid date used for publishedBeforeJoystream`, {
+      timestamp,
+    })
+    return PropertyChange.newNoChange()
+  }
+
+  // set new date
+  return PropertyChange.newChange(new Date(timestamp))
+}
+
+interface IConvertAssetParameters {
+  rawAsset: NewAsset
+  db: DatabaseManager
+  event: SubstrateEvent
+  ChannelOwner: typeof DataObjectOwner
+}
+
+/*
+  Converts event asset into data object or list of URLs fit to be saved to db.
+*/
+async function convertAsset(parameters: IConvertAssetParameters): Promise<AssetStorageOrUrls> {
+  // is asset describing list of URLs?
+  if (parameters.rawAsset.isUrls) {
+    const urls = parameters.rawAsset.asUrls.toArray().map((item) => item.toString())
+
+    return urls
+  }
+
+  // !parameters.rawAsset.isUrls && parameters.rawAsset.isUpload // asset is in storage
+
+  // prepare data object
+  const contentParameters: ContentParameters = parameters.rawAsset.asUpload
+  const dataObject = await prepareDataObject(
+    parameters.db,
+    contentParameters,
+    parameters.event,
+    parameters.ChannelOwner
+  )
+
+  return dataObject
+}
+
+interface IExtractAssetParameters {
+  assetIndex: number | undefined
+  assets: NewAsset[]
+  db: DatabaseManager
+  event: SubstrateEvent
+  ChannelOwner: typeof DataObjectOwner
+}
+
+/*
+  Selects asset from provided set of assets and prepares asset data fit to be saved to db.
+*/
+async function extractAsset(parameters: IExtractAssetParameters): Promise<PropertyChange<AssetStorageOrUrls>> {
+  // is asset being unset?
+  if (parameters.assetIndex === undefined) {
+    return PropertyChange.newUnset()
+  }
+
+  // ensure asset index is valid
+  if (parameters.assetIndex >= parameters.assets.length) {
+    invalidMetadata(`Non-existing asset extraction requested`, {
+      assetsProvided: parameters.assets.length,
+      assetIndex: parameters.assetIndex,
+    })
+    return PropertyChange.newNoChange()
+  }
+
+  // convert asset to data object record
+  const asset = await convertAsset({
+    rawAsset: parameters.assets[parameters.assetIndex],
+    db: parameters.db,
+    event: parameters.event,
+    ChannelOwner: parameters.ChannelOwner,
+  })
+
+  return PropertyChange.newChange(asset)
+}
+
+/*
+  As a temporary messure to overcome yet-to-be-implemented features in Hydra, we are using redudant information
+  to describe asset state. This function introduces all redudant data needed to be saved to db.
+
+  Changes `result` argument!
+*/
+function integrateAsset<T>(propertyName: string, result: Object, asset: PropertyChange<AssetStorageOrUrls>): void {
+  // helpers - property names
+  const nameUrl = propertyName + 'Urls'
+  const nameDataObject = propertyName + 'DataObject'
+  const nameAvailability = propertyName + 'Availability'
+
+  if (asset.isNoChange()) {
+    return
+  }
+
+  if (asset.isUnset()) {
+    result[nameUrl] = []
+    result[nameAvailability] = AssetAvailability.INVALID
+    result[nameDataObject] = undefined // plan deletion (will have effect when saved to db)
+
+    return
+  }
+
+  const newValue = asset.getValue() as AssetStorageOrUrls
+
+  // is asset available on external URL(s)
+  if (!isAssetInStorage(newValue)) {
+    // (un)set asset's properties
+    result[nameUrl] = newValue
+    result[nameAvailability] = AssetAvailability.ACCEPTED
+    result[nameDataObject] = undefined // plan deletion (will have effect when saved to db)
+
+    return
+  }
+
+  // asset saved in storage
+
+  // prepare conversion table between liaison judgment and asset availability
+  const conversionTable = {
+    [LiaisonJudgement.ACCEPTED]: AssetAvailability.ACCEPTED,
+    [LiaisonJudgement.PENDING]: AssetAvailability.PENDING,
+  }
+
+  // (un)set asset's properties
+  result[nameUrl] = [] // plan deletion (will have effect when saved to db)
+  result[nameAvailability] = conversionTable[newValue.liaisonJudgement]
+  result[nameDataObject] = newValue
+}
+
+function extractVideoSize(assets: NewAsset[], assetIndex: number | undefined): number | undefined {
+  // escape if no asset is required
+  if (assetIndex === undefined) {
+    return undefined
+  }
+
+  // ensure asset index is valid
+  if (assetIndex > assets.length) {
+    invalidMetadata(`Non-existing asset video size extraction requested`, { assetsProvided: assets.length, assetIndex })
+    return undefined
+  }
+
+  const rawAsset = assets[assetIndex]
+
+  // escape if asset is describing URLs (can't get size)
+  if (rawAsset.isUrls) {
+    return undefined
+  }
+
+  // !rawAsset.isUrls && rawAsset.isUpload // asset is in storage
+
+  // convert generic content parameters coming from processor to custom Joystream data type
+  const customContentParameters = new Custom_ContentParameters(registry, rawAsset.asUpload.toJSON() as any)
+  // extract video size
+  const videoSize = customContentParameters.size_in_bytes.toNumber()
+
+  return videoSize
+}
+
+async function prepareLanguage(
+  languageIso: string | undefined,
+  db: DatabaseManager,
+  event: SubstrateEvent
+): Promise<PropertyChange<Language>> {
+  // is language being unset?
+  if (languageIso === undefined) {
+    return PropertyChange.newUnset()
+  }
+
+  // validate language string
+  const isValidIso = ISO6391.validate(languageIso)
+
+  // ensure language string is valid
+  if (!isValidIso) {
+    invalidMetadata(`Invalid language ISO-639-1 provided`, languageIso)
+    return PropertyChange.newNoChange()
+  }
+
+  // load language
+  const language = await db.get(Language, { where: { iso: languageIso } as FindConditions<Language> })
+
+  // return existing language if any
+  if (language) {
+    return PropertyChange.newChange(language)
+  }
+
+  // create new language
+  const newLanguage = new Language({
+    // set id as iso to overcome current graphql filtering limitations (so we can use query `videos(where: {languageId_eq: 'en'})`)
+    // id: await getNextId(db),
+    id: languageIso,
+    iso: languageIso,
+    createdInBlock: event.blockNumber,
+
+    createdAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+    updatedAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+
+    // TODO: remove these lines after Hydra auto-fills the values when cascading save (remove them on all places)
+    createdById: '1',
+    updatedById: '1',
+  })
+
+  await db.save<Language>(newLanguage)
+
+  return PropertyChange.newChange(newLanguage)
+}
+
+async function prepareLicense(
+  db: DatabaseManager,
+  licenseProtobuf: LicenseMetadata.AsObject | undefined,
+  event: SubstrateEvent
+): Promise<License | undefined> {
+  // NOTE: Deletion of any previous license should take place in appropriate event handling function
+  //       and not here even it might appear so.
+
+  // is license being unset?
+  if (licenseProtobuf === undefined) {
+    return undefined
+  }
+
+  // license is meant to be deleted
+  if (isLicenseEmpty(licenseProtobuf)) {
+    return new License({})
+  }
+
+  // crete new license
+  const license = new License({
+    ...licenseProtobuf,
+    id: await getNextId(db),
+
+    createdAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+    updatedAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+
+    createdById: '1',
+    updatedById: '1',
+  })
+
+  return license
+}
+
+/*
+  Checks if protobof contains license with some fields filled or is empty object (`{}` or `{someKey: undefined, ...}`).
+  Empty object means deletion is requested.
+*/
+function isLicenseEmpty(licenseObject: LicenseMetadata.AsObject): boolean {
+  const somePropertySet = Object.entries(licenseObject).reduce((acc, [key, value]) => {
+    return acc || value !== undefined
+  }, false)
+
+  return !somePropertySet
+}
+
+function prepareVideoMetadata(
+  videoProtobuf: VideoMetadata.AsObject,
+  videoSize: number | undefined,
+  blockNumber: number
+): RawVideoMetadata {
+  const rawMeta = {
+    encoding: {
+      codecName: PropertyChange.fromObjectProperty<string, 'codecName', MediaTypeMetadata.AsObject>(
+        videoProtobuf.mediaType || {},
+        'codecName'
+      ),
+      container: PropertyChange.fromObjectProperty<string, 'container', MediaTypeMetadata.AsObject>(
+        videoProtobuf.mediaType || {},
+        'container'
+      ),
+      mimeMediaType: PropertyChange.fromObjectProperty<string, 'mimeMediaType', MediaTypeMetadata.AsObject>(
+        videoProtobuf.mediaType || {},
+        'mimeMediaType'
+      ),
+    },
+    pixelWidth: PropertyChange.fromObjectProperty<number, 'mediaPixelWidth', VideoMetadata.AsObject>(
+      videoProtobuf,
+      'mediaPixelWidth'
+    ),
+    pixelHeight: PropertyChange.fromObjectProperty<number, 'mediaPixelHeight', VideoMetadata.AsObject>(
+      videoProtobuf,
+      'mediaPixelHeight'
+    ),
+    size: videoSize === undefined ? PropertyChange.newNoChange() : PropertyChange.newChange(videoSize),
+  } as RawVideoMetadata
+
+  return rawMeta
+}
+
+async function prepareVideoCategory(
+  categoryId: number | undefined,
+  db: DatabaseManager
+): Promise<PropertyChange<VideoCategory>> {
+  // is category being unset?
+  if (categoryId === undefined) {
+    return PropertyChange.newUnset()
+  }
+
+  // load video category
+  const category = await db.get(VideoCategory, {
+    where: { id: categoryId.toString() } as FindConditions<VideoCategory>,
+  })
+
+  // ensure video category exists
+  if (!category) {
+    invalidMetadata('Non-existing video category association with video requested', categoryId)
+    return PropertyChange.newNoChange()
+  }
+
+  return PropertyChange.newChange(category)
+}
+
+function convertMetadataToObject<T extends Object>(metadata: jspb.Message): T {
+  const metaAsObject = metadata.toObject()
+  const result = {} as T
+
+  for (const key in metaAsObject) {
+    const funcNameBase = key.charAt(0).toUpperCase() + key.slice(1)
+    const hasFuncName = 'has' + funcNameBase
+    const isSet =
+      funcNameBase === 'PersonsList' // there is no `VideoMetadata.hasPersonsList` method from unkown reason -> create exception
+        ? true
+        : metadata[hasFuncName]()
+
+    if (!isSet) {
+      continue
+    }
+
+    const getFuncName = 'get' + funcNameBase
+    const value = metadata[getFuncName]()
+
+    // TODO: check that recursion trully works
+    if (value instanceof jspb.Message) {
+      result[key] = convertMetadataToObject(value)
+      continue
+    }
+
+    result[key] = metaAsObject[key]
+  }
+
+  return result
+}

+ 498 - 0
query-node/mappings/sumer/content/video.ts

@@ -0,0 +1,498 @@
+import BN from 'bn.js'
+import { fixBlockTimestamp } from '../eventFix'
+import { SubstrateEvent } from '@dzlzv/hydra-common'
+import { DatabaseManager } from '@dzlzv/hydra-db-utils'
+import { FindConditions, In } from 'typeorm'
+
+import { Content } from '../../../generated/types'
+
+import { inconsistentState, logger, getNextId } from '../common'
+
+import { convertContentActorToDataObjectOwner, readProtobuf, readProtobufWithAssets, RawVideoMetadata } from './utils'
+
+import {
+  AssetAvailability,
+  Channel,
+  License,
+  Video,
+  VideoCategory,
+  VideoMediaEncoding,
+  VideoMediaMetadata,
+} from 'query-node'
+
+// Joystream types
+import { ChannelId } from '@joystream/types/augment'
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_VideoCategoryCreated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { videoCategoryId, videoCategoryCreationParameters, contentActor } = new Content.VideoCategoryCreatedEvent(
+    event
+  ).data
+
+  // read metadata
+  const protobufContent = await readProtobuf(new VideoCategory(), {
+    metadata: videoCategoryCreationParameters.meta,
+    db,
+    event,
+  })
+
+  // create new video category
+  const videoCategory = new VideoCategory({
+    // main data
+    id: videoCategoryId.toString(),
+    videos: [],
+    createdInBlock: event.blockNumber,
+
+    // fill in auto-generated fields
+    createdAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+    updatedAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+
+    // integrate metadata
+    ...protobufContent,
+  })
+
+  // save video category
+  await db.save<VideoCategory>(videoCategory)
+
+  // emit log event
+  logger.info('Video category has been created', { id: videoCategoryId })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_VideoCategoryUpdated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { videoCategoryId, videoCategoryUpdateParameters, contentActor } = new Content.VideoCategoryUpdatedEvent(
+    event
+  ).data
+
+  // load video category
+  const videoCategory = await db.get(VideoCategory, {
+    where: { id: videoCategoryId.toString() } as FindConditions<VideoCategory>,
+  })
+
+  // ensure video category exists
+  if (!videoCategory) {
+    return inconsistentState('Non-existing video category update requested', videoCategoryId)
+  }
+
+  // read metadata
+  const protobufContent = await readProtobuf(new VideoCategory(), {
+    metadata: videoCategoryUpdateParameters.new_meta,
+    db,
+    event,
+  })
+
+  // update all fields read from protobuf
+  for (const [key, value] of Object.entries(protobufContent)) {
+    videoCategory[key] = value
+  }
+
+  // set last update time
+  videoCategory.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+  // save video category
+  await db.save<VideoCategory>(videoCategory)
+
+  // emit log event
+  logger.info('Video category has been updated', { id: videoCategoryId })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_VideoCategoryDeleted(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { videoCategoryId } = new Content.VideoCategoryDeletedEvent(event).data
+
+  // load video category
+  const videoCategory = await db.get(VideoCategory, {
+    where: { id: videoCategoryId.toString() } as FindConditions<VideoCategory>,
+  })
+
+  // ensure video category exists
+  if (!videoCategory) {
+    return inconsistentState('Non-existing video category deletion requested', videoCategoryId)
+  }
+
+  // remove video category
+  await db.remove<VideoCategory>(videoCategory)
+
+  // emit log event
+  logger.info('Video category has been deleted', { id: videoCategoryId })
+}
+
+/// ///////////////// Video //////////////////////////////////////////////////////
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_VideoCreated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { channelId, videoId, videoCreationParameters, contentActor } = new Content.VideoCreatedEvent(event).data
+
+  // read metadata
+  const protobufContent = await readProtobufWithAssets(new Video(), {
+    metadata: videoCreationParameters.meta,
+    db,
+    event,
+    assets: videoCreationParameters.assets,
+    ChannelOwner: convertContentActorToDataObjectOwner(contentActor, channelId.toNumber()),
+  })
+
+  // load channel
+  const channel = await db.get(Channel, { where: { id: channelId.toString() } as FindConditions<Channel> })
+
+  // ensure channel exists
+  if (!channel) {
+    return inconsistentState('Trying to add video to non-existing channel', channelId)
+  }
+
+  // prepare video media metadata (if any)
+  const fixedProtobuf = await integrateVideoMediaMetadata(db, null, protobufContent, event)
+
+  const licenseIsEmpty = fixedProtobuf.license && !Object.keys(fixedProtobuf.license).length
+  if (licenseIsEmpty) {
+    // license deletion was requested - ignore it and consider it empty
+    delete fixedProtobuf.license
+  }
+
+  // create new video
+  const video = new Video({
+    // main data
+    id: videoId.toString(),
+    isCensored: false,
+    channel,
+    createdInBlock: event.blockNumber,
+    isFeatured: false,
+
+    // default values for properties that might or might not be filled by metadata
+    thumbnailPhotoUrls: [],
+    thumbnailPhotoAvailability: AssetAvailability.INVALID,
+    mediaUrls: [],
+    mediaAvailability: AssetAvailability.INVALID,
+
+    // fill in auto-generated fields
+    createdAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+    updatedAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
+
+    // integrate metadata
+    ...fixedProtobuf,
+  })
+
+  // save video
+  await db.save<Video>(video)
+
+  // emit log event
+  logger.info('Video has been created', { id: videoId })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_VideoUpdated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { videoId, videoUpdateParameters, contentActor } = new Content.VideoUpdatedEvent(event).data
+
+  // load video
+  const video = await db.get(Video, {
+    where: { id: videoId.toString() } as FindConditions<Video>,
+    relations: ['channel', 'license'],
+  })
+
+  // ensure video exists
+  if (!video) {
+    return inconsistentState('Non-existing video update requested', videoId)
+  }
+
+  // prepare changed metadata
+  const newMetadata = videoUpdateParameters.new_meta.unwrapOr(null)
+
+  // license must be deleted AFTER video is saved - plan a license deletion by assigning it to this variable
+  let licenseToDelete: License | null = null
+
+  // update metadata if it was changed
+  if (newMetadata) {
+    const protobufContent = await readProtobufWithAssets(new Video(), {
+      metadata: newMetadata,
+      db,
+      event,
+      assets: videoUpdateParameters.assets.unwrapOr([]),
+      ChannelOwner: convertContentActorToDataObjectOwner(contentActor, new BN(video.channel.id).toNumber()),
+    })
+
+    // prepare video media metadata (if any)
+    const fixedProtobuf = await integrateVideoMediaMetadata(db, video, protobufContent, event)
+
+    // remember original license
+    const originalLicense = video.license
+
+    // update all fields read from protobuf
+    for (const [key, value] of Object.entries(fixedProtobuf)) {
+      video[key] = value
+    }
+
+    // license has changed - plan old license delete
+    if (originalLicense && video.license !== originalLicense) {
+      ;[video.license, licenseToDelete] = handleLicenseUpdate(originalLicense, video.license)
+    } else if (!Object.keys(video.license || {}).length) {
+      // license deletion was requested event no license exists?
+      delete video.license // ensure license is empty
+    }
+  }
+
+  // set last update time
+  video.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+  // save video
+  await db.save<Video>(video)
+
+  // delete old license if it's planned
+  if (licenseToDelete) {
+    await db.remove<License>(licenseToDelete)
+  }
+
+  // emit log event
+  logger.info('Video has been updated', { id: videoId })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_VideoDeleted(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { videoId } = new Content.VideoDeletedEvent(event).data
+
+  // load video
+  const video = await db.get(Video, { where: { id: videoId.toString() } as FindConditions<Video> })
+
+  // ensure video exists
+  if (!video) {
+    return inconsistentState('Non-existing video deletion requested', videoId)
+  }
+
+  // remove video
+  await db.remove<Video>(video)
+
+  // emit log event
+  logger.info('Video has been deleted', { id: videoId })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_VideoCensorshipStatusUpdated(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { videoId, isCensored } = new Content.VideoCensorshipStatusUpdatedEvent(event).data
+
+  // load video
+  const video = await db.get(Video, { where: { id: videoId.toString() } as FindConditions<Video> })
+
+  // ensure video exists
+  if (!video) {
+    return inconsistentState('Non-existing video censoring requested', videoId)
+  }
+
+  // update video
+  video.isCensored = isCensored.isTrue
+
+  // set last update time
+  video.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+  // save video
+  await db.save<Video>(video)
+
+  // emit log event
+  logger.info('Video censorship status has been updated', { id: videoId, isCensored: isCensored.isTrue })
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function content_FeaturedVideosSet(db: DatabaseManager, event: SubstrateEvent) {
+  // read event data
+  const { videoId: videoIds } = new Content.FeaturedVideosSetEvent(event).data
+
+  // load old featured videos
+  const existingFeaturedVideos = await db.getMany(Video, { where: { isFeatured: true } as FindConditions<Video> })
+
+  // comparsion utility
+  const isSame = (videoIdA: string) => (videoIdB: string) => videoIdA === videoIdB
+
+  // calculate diff sets
+  const toRemove = existingFeaturedVideos.filter(
+    (existingFV) => !videoIds.map((item) => item.toString()).some(isSame(existingFV.id))
+  )
+  const toAdd = videoIds.filter(
+    (video) => !existingFeaturedVideos.map((item) => item.id).some(isSame(video.toString()))
+  )
+
+  // escape if no featured video needs to be added or removed
+  if (!toRemove.length && !toAdd.length) {
+    // emit log event
+    logger.info('Featured videos unchanged')
+
+    return
+  }
+
+  // mark previously featured videos as not-featured
+  await Promise.all(
+    toRemove.map(async (video) => {
+      video.isFeatured = false
+
+      // set last update time
+      video.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+      await db.save<Video>(video)
+    })
+  )
+
+  // escape if no featured video needs to be added
+  if (!toAdd.length) {
+    // emit log event
+    logger.info('Some featured videos have been unset.', { videoIds: toRemove.map((item) => item.id.toString()) })
+
+    return
+  }
+
+  // read videos previously not-featured videos that are meant to be featured
+  const videosToAdd = await db.getMany(Video, {
+    where: {
+      id: In(toAdd.map((item) => item.toString())),
+    } as FindConditions<Video>,
+  })
+
+  if (videosToAdd.length !== toAdd.length) {
+    return inconsistentState('At least one non-existing video featuring requested', toAdd)
+  }
+
+  // mark previously not-featured videos as featured
+  await Promise.all(
+    videosToAdd.map(async (video) => {
+      video.isFeatured = true
+
+      // set last update time
+      video.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+      await db.save<Video>(video)
+    })
+  )
+
+  // emit log event
+  logger.info('New featured videos have been set', { videoIds })
+}
+
+/// ///////////////// Helpers ////////////////////////////////////////////////////
+
+/*
+  Integrates video metadata-related data into existing data (if any) or creates a new record.
+
+  NOTE: type hack - `RawVideoMetadata` is accepted for `metadata` instead of `Partial<Video>`
+        see `prepareVideoMetadata()` in `utils.ts` for more info
+*/
+async function integrateVideoMediaMetadata(
+  db: DatabaseManager,
+  existingRecord: Video | null,
+  metadata: Partial<Video>,
+  event: SubstrateEvent
+): Promise<Partial<Video>> {
+  if (!metadata.mediaMetadata) {
+    return metadata
+  }
+
+  const now = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
+
+  // fix TS type
+  const rawMediaMetadata = (metadata.mediaMetadata as unknown) as RawVideoMetadata
+
+  // ensure encoding object
+  const encoding =
+    (existingRecord && existingRecord.mediaMetadata && existingRecord.mediaMetadata.encoding) ||
+    new VideoMediaEncoding({
+      createdAt: now,
+      updatedAt: now,
+
+      createdById: '1',
+      updatedById: '1',
+    })
+
+  // integrate media encoding-related data
+  rawMediaMetadata.encoding.codecName.integrateInto(encoding, 'codecName')
+  rawMediaMetadata.encoding.container.integrateInto(encoding, 'container')
+  rawMediaMetadata.encoding.mimeMediaType.integrateInto(encoding, 'mimeMediaType')
+
+  // ensure media metadata object
+  const mediaMetadata =
+    (existingRecord && existingRecord.mediaMetadata) ||
+    new VideoMediaMetadata({
+      createdInBlock: event.blockNumber,
+
+      createdAt: now,
+      updatedAt: now,
+
+      createdById: '1',
+      updatedById: '1',
+    })
+
+  // integrate media-related data
+  rawMediaMetadata.pixelWidth.integrateInto(mediaMetadata, 'pixelWidth')
+  rawMediaMetadata.pixelHeight.integrateInto(mediaMetadata, 'pixelHeight')
+  rawMediaMetadata.size.integrateInto(mediaMetadata, 'size')
+
+  // connect encoding to media metadata object
+  mediaMetadata.encoding = encoding
+
+  // ensure predictable ids
+  if (!mediaMetadata.encoding.id) {
+    mediaMetadata.encoding.id = await getNextId(db)
+  }
+  if (!mediaMetadata.id) {
+    mediaMetadata.id = await getNextId(db)
+  }
+
+  /// ///////////////// update updatedAt if needed ///////////////////////////////
+
+  const encodingNoChange =
+    true &&
+    rawMediaMetadata.encoding.codecName.isNoChange() &&
+    rawMediaMetadata.encoding.container.isNoChange() &&
+    rawMediaMetadata.encoding.mimeMediaType.isNoChange()
+  const mediaMetadataNoChange =
+    encodingNoChange &&
+    rawMediaMetadata.encoding.codecName.isNoChange() &&
+    rawMediaMetadata.encoding.container.isNoChange() &&
+    rawMediaMetadata.encoding.mimeMediaType.isNoChange()
+
+  if (!encodingNoChange) {
+    // encoding changed?
+    mediaMetadata.encoding.updatedAt = now
+  }
+  if (!mediaMetadataNoChange) {
+    // metadata changed?
+    mediaMetadata.updatedAt = now
+  }
+
+  /// ////////////////////////////////////////////////////////////////////////////
+
+  return {
+    ...metadata,
+    mediaMetadata,
+  }
+}
+
+// returns tuple `[newLicenseForVideo, oldLicenseToBeDeleted]`
+function handleLicenseUpdate(originalLicense, newLicense): [License | undefined, License | null] {
+  const isNewEmpty = !Object.keys(newLicense).length
+
+  if (!originalLicense && isNewEmpty) {
+    return [undefined, null]
+  }
+
+  if (!originalLicense) {
+    // && !isNewEmpty
+    return [newLicense, null]
+  }
+
+  if (!isNewEmpty) {
+    // && originalLicense
+    return [
+      new License({
+        ...originalLicense,
+        ...newLicense,
+      }),
+      null,
+    ]
+  }
+
+  // originalLicense && isNewEmpty
+
+  return [originalLicense, null]
+}

+ 136 - 3
runtime-modules/content/src/errors.rs

@@ -46,9 +46,6 @@ decl_error! {
         /// Operation cannot be perfomed with this Actor
         ActorNotAuthorized,
 
-        /// This content actor cannot own a channel
-        ActorCannotOwnChannel,
-
         /// A Channel or Video Category does not exist.
         CategoryDoesNotExist,
 
@@ -64,6 +61,142 @@ decl_error! {
         /// Curators can only censor non-curator group owned channels
         CannotCensoreCuratorGroupOwnedChannels,
 
+        /// Actor cannot authorize as lead for given extrinsic
+        ActorCannotBeLead,
+
+        /// Channel censorship status did not change
+        ChannelCensorshipStatusDidNotChange,
+
+        /// Video censorship status did not change
+        VideoCensorshipStatusDidNotChange,
+
+
+        // Auction Errors
+        // ---------------------
+
+        /// Auction for given video did not start
+        AuctionDidNotStart,
+
+        /// NFT for given video id already exists
+        NFTAlreadyExists,
+
+        /// NFT for given video id does not exist
+        NFTDoesNotExist,
+
+        /// Overflow or underflow error happened
+        OverflowOrUnderflowHappened,
+
+        /// Given origin does not own nft
+        DoesNotOwnNFT,
+
+        /// Royalty Upper Bound Exceeded
+        RoyaltyUpperBoundExceeded,
+
+        /// Royalty Lower Bound Exceeded
+        RoyaltyLowerBoundExceeded,
+
+        /// Auction duration upper bound exceeded
+        AuctionDurationUpperBoundExceeded,
+
+        /// Auction duration lower bound exceeded
+        AuctionDurationLowerBoundExceeded,
+
+        /// Auction extension period upper bound exceeded
+        ExtensionPeriodUpperBoundExceeded,
+
+        /// Auction extension period lower bound exceeded
+        ExtensionPeriodLowerBoundExceeded,
+
+        /// Bid lock duration upper bound exceeded
+        BidLockDurationUpperBoundExceeded,
+
+        /// Bid lock duration lower bound exceeded
+        BidLockDurationLowerBoundExceeded,
+
+        /// Starting price upper bound exceeded
+        StartingPriceUpperBoundExceeded,
+
+        /// Starting price lower bound exceeded
+        StartingPriceLowerBoundExceeded,
+
+        /// Auction bid step upper bound exceeded
+        AuctionBidStepUpperBoundExceeded,
+
+        /// Auction bid step lower bound exceeded
+        AuctionBidStepLowerBoundExceeded,
+
+        /// Insufficient balance
+        InsufficientBalance,
+
+        /// Minimal auction bid step constraint violated.
+        BidStepConstraintViolated,
+
+        /// Auction starting price constraint violated.
+        StartingPriceConstraintViolated,
+
+        /// Already active auction cannot be cancelled
+        ActionHasBidsAlready,
+
+        /// Can not create auction for NFT, if auction have been already started or nft is locked for the transfer
+        NftIsNotIdle,
+
+        /// No pending offers for given NFT
+        PendingOfferDoesNotExist,
+
+        /// Creator royalty requires reward account to be set.
+        RewardAccountIsNotSet,
+
+        /// Actor is not a last bidder
+        ActorIsNotALastBidder,
+
+        /// Auction cannot be completed
+        AuctionCannotBeCompleted,
+
+        /// Auction does not have bids
+        LastBidDoesNotExist,
+
+        /// Auction starts at lower bound exceeded
+        StartsAtLowerBoundExceeded,
+
+        /// Auction starts at upper bound exceeded
+        StartsAtUpperBoundExceeded,
+
+        /// Nft is not in auction state
+        NotInAuctionState,
+
+        /// Member is not allowed to participate in auction
+        MemberIsNotAllowedToParticipate,
+
+        /// Member profile not found
+        MemberProfileNotFound,
+
+        /// Given video nft is not in buy now state
+        NFTNotInBuyNowState,
+
+        /// Auction type is not `Open`
+        IsNotOpenAuctionType,
+
+        /// Auction type is not `English`
+        IsNotEnglishAuctionType,
+
+        /// Bid lock duration is not expired
+        BidLockDurationIsNotExpired,
+
+        /// NFT auction is already expired
+        NFTAuctionIsAlreadyExpired,
+
+        /// Auction buy now is less then starting price
+        BuyNowIsLessThenStartingPrice,
+
+        /// Max auction whitelist length upper bound exceeded
+        MaxAuctionWhiteListLengthUpperBoundExceeded,
+
+        /// Auction whitelist has only one member
+        WhitelistHasOnlyOneMember,
+
+        /// Extension period is greater then auction duration
+        ExtensionPeriodIsGreaterThenAuctionDuration,
+
         /// No assets to be removed have been specified
         NoAssetsSpecified,
 

+ 710 - 12
runtime-modules/content/src/lib.rs

@@ -24,13 +24,17 @@
 mod tests;
 use core::marker::PhantomData;
 mod errors;
+mod nft;
 mod permissions;
+mod types;
 
 use sp_std::cmp::max;
 use sp_std::mem::size_of;
 
 pub use errors::*;
+pub use nft::*;
 pub use permissions::*;
+pub use types::*;
 
 use codec::Codec;
 use codec::{Decode, Encode};
@@ -41,7 +45,7 @@ pub use storage::{
 };
 
 pub use common::{working_group::WorkingGroup, MembershipTypes, StorageOwnership, Url};
-use frame_support::traits::{Currency, ExistenceRequirement, Get};
+use frame_support::traits::{Currency, ExistenceRequirement, Get, ReservableCurrency};
 use frame_support::{
     decl_event, decl_module, decl_storage,
     dispatch::{DispatchError, DispatchResult},
@@ -148,10 +152,9 @@ pub trait Trait:
     frame_system::Trait
     + ContentActorAuthenticator
     + Clone
-    + MembershipTypes
+    + membership::Trait
     + balances::Trait
     + storage::Trait
-    + minting::Trait
 {
     /// The overarching event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
@@ -806,6 +809,53 @@ decl_storage! {
 
         pub MinCashoutAllowed get(fn min_cashout_allowed) config(): BalanceOf<T>;
 
+        /// Map, representing  CuratorGroupId -> CuratorGroup relation
+        pub CuratorGroupById get(fn curator_group_by_id): map hasher(blake2_128_concat) T::CuratorGroupId => CuratorGroup<T>;
+
+        /// Min auction duration
+        pub MinAuctionDuration get(fn min_auction_duration) config(): T::BlockNumber;
+
+        /// Max auction duration
+        pub MaxAuctionDuration get(fn max_auction_duration) config(): T::BlockNumber;
+
+        /// Min auction extension period
+        pub MinAuctionExtensionPeriod get(fn min_auction_extension_period) config(): T::BlockNumber;
+
+        /// Max auction extension period
+        pub MaxAuctionExtensionPeriod get(fn max_auction_extension_period) config(): T::BlockNumber;
+
+        /// Min bid lock duration
+        pub MinBidLockDuration get(fn min_bid_lock_duration) config(): T::BlockNumber;
+
+        /// Max bid lock duration
+        pub MaxBidLockDuration get(fn max_bid_lock_duration) config(): T::BlockNumber;
+
+        /// Min auction staring price
+        pub MinStartingPrice get(fn min_starting_price) config(): BalanceOf<T>;
+
+        /// Max auction staring price
+        pub MaxStartingPrice get(fn max_starting_price) config(): BalanceOf<T>;
+
+        /// Min creator royalty percentage
+        pub MinCreatorRoyalty get(fn min_creator_royalty) config(): Perbill;
+
+        /// Max creator royalty percentage
+        pub MaxCreatorRoyalty get(fn max_creator_royalty) config(): Perbill;
+
+        /// Min auction bid step
+        pub MinBidStep get(fn min_bid_step) config(): BalanceOf<T>;
+
+        /// Max auction bid step
+        pub MaxBidStep get(fn max_bid_step) config(): BalanceOf<T>;
+
+        /// Platform fee percentage
+        pub PlatfromFeePercentage get(fn platform_fee_percentage) config(): Perbill;
+
+        /// Max delta between current block and starts at
+        pub AuctionStartsAtMaxDelta get(fn auction_starts_at_max_delta) config(): T::BlockNumber;
+
+        /// Max nft auction whitelist length
+        pub MaxAuctionWhiteListLength get(fn max_auction_whitelist_length) config(): MaxNumber;
     }
 }
 
@@ -1222,16 +1272,15 @@ decl_module! {
             // check that channel exists
             let channel = Self::ensure_channel_validity(&channel_id)?;
 
-            if channel.is_censored == is_censored {
-                return Ok(())
-            }
-
             ensure_actor_authorized_to_censor::<T>(
                 origin,
                 &actor,
                 &channel.owner,
             )?;
 
+            // Ensure censorship status have been changed
+            channel.ensure_censorship_status_changed::<T>(is_censored)?;
+
             //
             // == MUTATION SAFE ==
             //
@@ -1344,6 +1393,10 @@ decl_module! {
                 is_censored: false,
                 enable_comments: params.enable_comments,
                 video_post_id:  None,
+                /// Newly created video has no nft
+                nft_status: None,
+                /// storage parameters for later storage deletion
+                maybe_data_objects_id_set: maybe_data_objects_ids,
             };
 
             // add it to the onchain state
@@ -1452,9 +1505,21 @@ decl_module! {
                 Storage::<T>::can_delete_data_objects(
                     &Self::bag_id_for_channel(&channel_id),
                     &assets_to_remove,
-                )?;
+                    // Ensure nft for this video have not been issued
+                    video.ensure_nft_is_not_issued::<T>()?;
+                )
             }
 
+            // TODO: Solve #now
+            // If video is on storage, remove it
+            // if let Some(data_objects_id_set) = video.maybe_data_objects_id_set {
+            //     Storage::<T>::delete_data_objects(
+            //         channel.deletion_prize_source_account_id,
+            //         Self::bag_id_for_channel(&channel_id),
+            //         data_objects_id_set,
+            //     )?;
+            // }
+
             // bloat bond logic: channel owner is refunded
             video.video_post_id.as_ref().map(
                 |video_post_id| Self::video_deletion_refund_logic(&sender, &video_id, &video_post_id)
@@ -1660,6 +1725,9 @@ decl_module! {
                 &Self::channel_by_id(video.in_channel).owner,
             )?;
 
+            // Ensure censorship status have been changed
+            video.ensure_censorship_status_changed::<T>(is_censored)?;
+
             //
             // == MUTATION SAFE ==
             //
@@ -1991,10 +2059,6 @@ decl_module! {
             ensure!(<MinCashoutAllowed<T>>::get() < cashout, Error::<T>::UnsufficientCashoutAmount);
             Self::verify_proof(&proof, &item)?;
 
-            //
-            // == MUTATION SAFE ==
-            //
-
             ContentTreasury::<T>::transfer_reward( &channel.reward_account.unwrap(), cashout);
             ChannelById::<T>::mutate(
                 &item.channel_id,
@@ -2022,6 +2086,597 @@ decl_module! {
             <MinCashoutAllowed<T>>::put(amount);
             Self::deposit_event(RawEvent::MinCashoutUpdated(amount));
         }
+
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn issue_nft(
+            origin,
+            actor: ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+            video_id: T::VideoId,
+            royalty: Option<Royalty>,
+            metadata: Metadata,
+            to: Option<T::MemberId>,
+        ) {
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure have not been issued yet
+            video.ensure_nft_is_not_issued::<T>()?;
+
+            let channel_id = video.in_channel;
+
+            // Ensure channel exists, retrieve channel owner
+            let channel_owner = Self::ensure_channel_exists(&channel_id)?.owner;
+
+            ensure_actor_authorized_to_update_channel::<T>(origin, &actor, &channel_owner)?;
+
+            // The content owner will be..
+            let nft_owner = if let Some(to) = to {
+                NFTOwner::Member(to)
+            } else {
+                // if `to` set to None, actor issues to ChannelOwner
+                NFTOwner::ChannelOwner
+            };
+
+            // Enure royalty bounds satisfied, if provided
+            if let Some(royalty) = royalty {
+                Self::ensure_royalty_bounds_satisfied(royalty)?;
+            }
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Issue NFT
+            let video = video.set_nft_status(OwnedNFT::new(nft_owner, royalty));
+
+            // Update the video
+            VideoById::<T>::insert(video_id, video);
+
+            Self::deposit_event(RawEvent::NftIssued(
+                actor,
+                video_id,
+                royalty,
+                metadata,
+                to,
+            ));
+        }
+
+        /// Start video nft auction
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn start_nft_auction(
+            origin,
+            owner_id: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+            video_id: T::VideoId,
+            auction_params: AuctionParams<T::BlockNumber, BalanceOf<T>, T::MemberId>,
+        ) {
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Authorize nft owner
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure there nft transactional status is set to idle.
+            nft.ensure_nft_transactional_status_is_idle::<T>()?;
+
+            // Validate round_duration & starting_price
+            Self::validate_auction_params(&auction_params)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Create new auction
+            let mut auction = AuctionRecord::new(auction_params.clone());
+
+            // If starts_at is not set, set it to now
+            if auction_params.starts_at.is_none() {
+                auction.starts_at = <frame_system::Module<T>>::block_number();
+            }
+
+            let nft = nft.set_auction_transactional_status(auction);
+            let video = video.set_nft_status(nft);
+
+            // Update the video
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::AuctionStarted(owner_id, video_id, auction_params));
+        }
+
+        // Cancel video nft auction
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn cancel_nft_auction(
+            origin,
+            owner_id: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+            video_id: T::VideoId,
+        ) {
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Authorize nft owner
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure auction for given video id exists
+            let auction = nft.ensure_auction_state::<T>()?;
+
+            // Ensure given auction can be canceled
+            auction.ensure_auction_can_be_canceled::<T>()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Cancel auction
+            let nft = Self::cancel_transaction(nft);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::AuctionCanceled(owner_id, video_id));
+        }
+
+        /// Cancel NFT offer
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn cancel_offer(
+            origin,
+            owner_id: ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+            video_id: T::VideoId,
+        ) {
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Authorize nft owner
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure nft in pending offer state
+            nft.ensure_pending_offer_state::<T>()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Cancel pending offer
+            let nft = Self::cancel_transaction(nft);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::OfferCanceled(video_id, owner_id));
+        }
+
+        /// Cancel NFT sell order
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn cancel_buy_now(
+            origin,
+            owner_id: ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+            video_id: T::VideoId,
+        ) {
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Authorize nft owner
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure nft in buy now state
+            nft.ensure_buy_now_state::<T>()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Cancel sell order
+            let nft = Self::cancel_transaction(nft);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::BuyNowCanceled(video_id, owner_id));
+        }
+
+        /// Make auction bid
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn make_bid(
+            origin,
+            participant_id: T::MemberId,
+            video_id: T::VideoId,
+            bid: BalanceOf<T>,
+        ) {
+
+            // Authorize participant under given member id
+            let participant_account_id = ensure_signed(origin)?;
+            ensure_member_auth_success::<T>(&participant_id, &participant_account_id)?;
+
+            // Ensure bidder have sufficient balance amount to reserve for bid
+            Self::ensure_has_sufficient_balance(&participant_account_id, bid)?;
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Ensure auction for given video id exists
+            let auction = nft.ensure_auction_state::<T>()?;
+
+            let current_block = <frame_system::Module<T>>::block_number();
+
+            // Ensure nft auction not expired
+            auction.ensure_nft_auction_not_expired::<T>(current_block)?;
+
+            // Ensure auction have been already started
+            auction.ensure_auction_started::<T>(current_block)?;
+
+            // Ensure participant have been already added to whitelist if set
+            auction.ensure_whitelisted_participant::<T>(participant_id)?;
+
+            // Ensure new bid is greater then last bid + minimal bid step
+            auction.ensure_is_valid_bid::<T>(bid)?;
+
+            // Used only for immediate auction completion
+            let funds_destination_account_id = Self::ensure_owner_account_id(&video, &nft).ok();
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Unreserve previous bidder balance
+            if let Some(last_bid) = &auction.last_bid {
+                T::Currency::unreserve(&last_bid.bidder_account_id, last_bid.amount);
+            }
+
+            match auction.buy_now_price {
+                Some(buy_now_price) if bid >= buy_now_price => {
+                    // Do not charge more then buy now
+                    let (_, _, bid) = auction.make_bid(participant_id, participant_account_id, buy_now_price, current_block);
+
+                    let nft = Self::complete_auction(video.in_channel, nft, bid, funds_destination_account_id);
+                    let video = video.set_nft_status(nft);
+
+                    // Update the video
+                    VideoById::<T>::insert(video_id, video);
+
+                    // Trigger event
+                    Self::deposit_event(RawEvent::BidMadeCompletingAuction(participant_id, video_id));
+                }
+                _ => {
+                    // Make auction bid & update auction data
+
+                    // Reseve balance for current bid
+                    // Can not fail, needed check made
+                    T::Currency::reserve(&participant_account_id, bid)?;
+
+                    let (auction, is_extended, _) = auction.make_bid(participant_id, participant_account_id, bid, current_block);
+                    let nft = nft.set_auction_transactional_status(auction);
+                    let video = video.set_nft_status(nft);
+
+                    VideoById::<T>::insert(video_id, video);
+
+                    // Trigger event
+                    Self::deposit_event(RawEvent::AuctionBidMade(participant_id, video_id, bid, is_extended));
+                }
+            }
+        }
+
+        /// Cancel open auction bid
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn cancel_open_auction_bid(
+            origin,
+            participant_id: T::MemberId,
+            video_id: T::VideoId,
+        ) {
+
+            // Authorize participant under given member id
+            let participant_account_id = ensure_signed(origin)?;
+            ensure_member_auth_success::<T>(&participant_id, &participant_account_id)?;
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Ensure auction for given video id exists
+            let auction = nft.ensure_auction_state::<T>()?;
+
+            let current_block = <frame_system::Module<T>>::block_number();
+
+            // Ensure participant can cancel last bid
+            auction.ensure_bid_can_be_canceled::<T>(participant_id, current_block)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Cancel last auction bid & update auction data
+            let auction = auction.cancel_bid();
+            let nft = nft.set_auction_transactional_status(auction);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::AuctionBidCanceled(participant_id, video_id));
+        }
+
+        /// Claim won english auction
+        /// Can be called by anyone
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn claim_won_english_auction(
+            origin,
+            member_id: T::MemberId,
+            video_id: T::VideoId,
+        ) {
+            // Authorize member under given member id
+            let account_id = ensure_signed(origin)?;
+            ensure_member_auth_success::<T>(&member_id, &account_id)?;
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Ensure auction for given video id exists, retrieve corresponding one
+            let auction = nft.ensure_auction_state::<T>()?;
+
+            // Ensure auction has at least one bid
+            let bid = auction.ensure_last_bid_exists::<T>()?;
+
+            // Ensure auction type is set to `English`
+            auction.ensure_is_english_auction::<T>()?;
+
+            // Ensure auction can be completed
+            Self::ensure_auction_can_be_completed(&auction)?;
+
+            let owner_account_id = Self::ensure_owner_account_id(&video, &nft).ok();
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let nft = Self::complete_auction(video.in_channel, nft, bid, owner_account_id);
+            let video = video.set_nft_status(nft);
+
+            // Update the video
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::EnglishAuctionCompleted(member_id, video_id));
+        }
+
+        /// Accept open auction bid
+        /// Should only be called by auctioneer
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn pick_open_auction_winner(
+            origin,
+            owner_id: ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+            video_id: T::VideoId,
+        ) {
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Ensure actor is authorized to accept open auction bid
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure auction for given video id exists, retrieve corresponding one
+            let auction = nft.ensure_auction_state::<T>()?;
+
+            // Ensure open type auction
+            auction.ensure_is_open_auction::<T>()?;
+
+            // Ensure there is a bid to accept
+            let bid = auction.ensure_last_bid_exists::<T>()?;
+
+            let owner_account_id = Self::ensure_owner_account_id(&video, &nft).ok();
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let nft = Self::complete_auction(video.in_channel, nft, bid, owner_account_id);
+            let video = video.set_nft_status(nft);
+
+            // Update the video
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::OpenAuctionBidAccepted(owner_id, video_id));
+        }
+
+        /// Offer NFT
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn offer_nft(
+            origin,
+            video_id: T::VideoId,
+            owner_id: ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+            to: MemberId<T>,
+            price: Option<BalanceOf<T>>,
+        ) {
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Authorize nft owner
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure there is no pending offer or existing auction for given nft.
+            nft.ensure_nft_transactional_status_is_idle::<T>()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Set nft transactional status to InitiatedOfferToMember
+            let nft = nft.set_pending_offer_transactional_status(to, price);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::OfferStarted(video_id, owner_id, to, price));
+        }
+
+        /// Return NFT back to the original artist at no cost
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn sling_nft_back(
+            origin,
+            video_id: T::VideoId,
+            owner_id: ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+        ) {
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Authorize nft owner
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure there is no pending offer or existing auction for given nft.
+            nft.ensure_nft_transactional_status_is_idle::<T>()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Set nft owner to ChannelOwner
+            let nft = nft.set_owner(NFTOwner::ChannelOwner);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::NftSlingedBackToTheOriginalArtist(video_id, owner_id));
+        }
+
+        /// Accept incoming NFT offer
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn accept_incoming_offer(
+            origin,
+            video_id: T::VideoId,
+        ) {
+            let receiver_account_id = ensure_signed(origin)?;
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Ensure new pending offer is available to proceed
+            Self::ensure_new_pending_offer_available_to_proceed(&nft, &receiver_account_id)?;
+
+            let owner_account_id = Self::ensure_owner_account_id(&video, &nft)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Complete nft offer
+            let nft = Self::complete_nft_offer(video.in_channel, nft, owner_account_id, receiver_account_id);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::OfferAccepted(video_id));
+        }
+
+        /// Sell NFT
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn sell_nft(
+            origin,
+            video_id: T::VideoId,
+            owner_id: ContentActor<CuratorGroupId<T>, CuratorId<T>, MemberId<T>>,
+            price: BalanceOf<T>,
+        ) {
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Authorize nft owner
+            ensure_actor_authorized_to_manage_nft::<T>(origin, &owner_id, &nft.owner, video.in_channel)?;
+
+            // Ensure there is no pending transfer or existing auction for given nft.
+            nft.ensure_nft_transactional_status_is_idle::<T>()?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Place nft sell order
+            let nft = nft.set_buy_now_transactionl_status(price);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::NFTSellOrderMade(video_id, owner_id, price));
+        }
+
+        /// Buy NFT
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn buy_nft(
+            origin,
+            video_id: T::VideoId,
+            participant_id: MemberId<T>,
+        ) {
+
+            // Authorize participant under given member id
+            let participant_account_id = ensure_signed(origin)?;
+            ensure_member_auth_success::<T>(&participant_id, &participant_account_id)?;
+
+            // Ensure given video exists
+            let video = Self::ensure_video_exists(&video_id)?;
+
+            // Ensure nft is already issued
+            let nft = video.ensure_nft_is_issued::<T>()?;
+
+            // Ensure given participant can buy nft now
+            Self::ensure_can_buy_now(&nft, &participant_account_id)?;
+
+            let owner_account_id = Self::ensure_owner_account_id(&video, &nft)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Buy nft
+            let nft = Self::buy_now(video.in_channel, nft, owner_account_id, participant_account_id, participant_id);
+            let video = video.set_nft_status(nft);
+
+            VideoById::<T>::insert(video_id, video);
+
+            // Trigger event
+            Self::deposit_event(RawEvent::NFTBought(video_id, participant_id));
+        }
     }
 }
 
@@ -2211,6 +2866,22 @@ impl<T: Trait> Module<T> {
         Ok(())
     }
 
+    /// Ensure owner account id exists, retreive corresponding one.
+    pub fn ensure_owner_account_id(
+        video: &Video<T>,
+        owned_nft: &Nft<T>,
+    ) -> Result<T::AccountId, Error<T>> {
+        if let NFTOwner::Member(member_id) = owned_nft.owner {
+            let membership = <membership::Module<T>>::ensure_membership(member_id)
+                .map_err(|_| Error::<T>::MemberProfileNotFound)?;
+            Ok(membership.controller_account)
+        } else if let Some(reward_account) = Self::channel_by_id(video.in_channel).reward_account {
+            Ok(reward_account)
+        } else {
+            Err(Error::<T>::RewardAccountIsNotSet)
+        }
+    }
+
     fn bag_id_for_channel(channel_id: &T::ChannelId) -> storage::BagId<T> {
         // retrieve bag id from channel id
         let dyn_bag = DynamicBagIdType::<T::MemberId, T::ChannelId>::Channel(*channel_id);
@@ -2304,6 +2975,7 @@ decl_event!(
             <T as ContentActorAuthenticator>::CuratorId,
             <T as common::MembershipTypes>::MemberId,
         >,
+        MemberId = <T as common::MembershipTypes>::MemberId,
         CuratorGroupId = <T as ContentActorAuthenticator>::CuratorGroupId,
         CuratorId = <T as ContentActorAuthenticator>::CuratorId,
         VideoId = <T as Trait>::VideoId,
@@ -2319,6 +2991,9 @@ decl_event!(
         Channel = Channel<T>,
         DataObjectId = DataObjectId<T>,
         IsCensored = bool,
+        AuctionParams =
+            AuctionParams<<T as frame_system::Trait>::BlockNumber, BalanceOf<T>, MemberId<T>>,
+        Balance = BalanceOf<T>,
         ChannelCreationParameters = ChannelCreationParameters<T>,
         ChannelUpdateParameters = ChannelUpdateParameters<T>,
         VideoCreationParameters = VideoCreationParameters<T>,
@@ -2331,6 +3006,7 @@ decl_event!(
         ModeratorSet = BTreeSet<<T as MembershipTypes>::MemberId>,
         Hash = <T as frame_system::Trait>::Hash,
         Balance = BalanceOf<T>,
+        IsExtended = bool,
     {
         // Curators
         CuratorGroupCreated(CuratorGroupId),
@@ -2446,5 +3122,27 @@ decl_event!(
         ChannelRewardUpdated(Balance, ChannelId),
         MaxRewardUpdated(Balance),
         MinCashoutUpdated(Balance),
+        // NFT auction
+        AuctionStarted(ContentActor, VideoId, AuctionParams),
+        NftIssued(
+            ContentActor,
+            VideoId,
+            Option<Royalty>,
+            Metadata,
+            Option<MemberId>,
+        ),
+        AuctionBidMade(MemberId, VideoId, Balance, IsExtended),
+        AuctionBidCanceled(MemberId, VideoId),
+        AuctionCanceled(ContentActor, VideoId),
+        EnglishAuctionCompleted(MemberId, VideoId),
+        BidMadeCompletingAuction(MemberId, VideoId),
+        OpenAuctionBidAccepted(ContentActor, VideoId),
+        OfferStarted(VideoId, ContentActor, MemberId, Option<Balance>),
+        OfferAccepted(VideoId),
+        OfferCanceled(VideoId, ContentActor),
+        NFTSellOrderMade(VideoId, ContentActor, Balance),
+        NFTBought(VideoId, MemberId),
+        BuyNowCanceled(VideoId, ContentActor),
+        NftSlingedBackToTheOriginalArtist(VideoId, ContentActor),
     }
 );

+ 385 - 0
runtime-modules/content/src/nft/mod.rs

@@ -0,0 +1,385 @@
+mod types;
+use sp_std::borrow::ToOwned;
+pub use types::*;
+
+use crate::*;
+
+impl<T: Trait> Module<T> {
+    /// Ensure nft auction can be completed
+    pub(crate) fn ensure_auction_can_be_completed(auction: &Auction<T>) -> DispatchResult {
+        let can_be_completed = if let AuctionType::English(EnglishAuctionDetails {
+            auction_duration,
+            ..
+        }) = auction.auction_type
+        {
+            let now = <frame_system::Module<T>>::block_number();
+
+            // Check whether auction time expired.
+            (now - auction.starts_at) >= auction_duration
+        } else {
+            // Open auction can be completed at any time
+            true
+        };
+
+        ensure!(can_be_completed, Error::<T>::AuctionCannotBeCompleted);
+
+        Ok(())
+    }
+
+    /// Ensure auction participant has sufficient balance to make bid
+    pub(crate) fn ensure_has_sufficient_balance(
+        participant: &T::AccountId,
+        bid: BalanceOf<T>,
+    ) -> DispatchResult {
+        ensure!(
+            T::Currency::can_reserve(participant, bid),
+            Error::<T>::InsufficientBalance
+        );
+        Ok(())
+    }
+
+    /// Safety/bound checks for auction parameters
+    pub(crate) fn validate_auction_params(
+        auction_params: &AuctionParams<T::BlockNumber, BalanceOf<T>, MemberId<T>>,
+    ) -> DispatchResult {
+        match auction_params.auction_type {
+            AuctionType::English(EnglishAuctionDetails {
+                extension_period,
+                auction_duration,
+            }) => {
+                Self::ensure_auction_duration_bounds_satisfied(auction_duration)?;
+                Self::ensure_extension_period_bounds_satisfied(extension_period)?;
+
+                // Ensure auction_duration of English auction is >= extension_period
+                ensure!(
+                    auction_duration >= extension_period,
+                    Error::<T>::ExtensionPeriodIsGreaterThenAuctionDuration
+                );
+            }
+            AuctionType::Open(OpenAuctionDetails { bid_lock_duration }) => {
+                Self::ensure_bid_lock_duration_bounds_satisfied(bid_lock_duration)?;
+            }
+        }
+
+        Self::ensure_starting_price_bounds_satisfied(auction_params.starting_price)?;
+        Self::ensure_bid_step_bounds_satisfied(auction_params.minimal_bid_step)?;
+        Self::ensure_whitelist_bounds_satisfied(&auction_params.whitelist)?;
+
+        if let Some(starts_at) = auction_params.starts_at {
+            Self::ensure_starts_at_delta_bounds_satisfied(starts_at)?;
+        }
+
+        if let Some(buy_now_price) = auction_params.buy_now_price {
+            ensure!(
+                buy_now_price > auction_params.starting_price,
+                Error::<T>::BuyNowIsLessThenStartingPrice
+            );
+        }
+
+        Ok(())
+    }
+
+    /// Ensure starts at bounds satisfied
+    pub(crate) fn ensure_starts_at_delta_bounds_satisfied(
+        starts_at: T::BlockNumber,
+    ) -> DispatchResult {
+        ensure!(
+            starts_at >= <frame_system::Module<T>>::block_number(),
+            Error::<T>::StartsAtLowerBoundExceeded
+        );
+
+        ensure!(
+            starts_at
+                <= <frame_system::Module<T>>::block_number() + Self::auction_starts_at_max_delta(),
+            Error::<T>::StartsAtUpperBoundExceeded
+        );
+
+        Ok(())
+    }
+
+    /// Ensure royalty bounds satisfied
+    pub(crate) fn ensure_royalty_bounds_satisfied(royalty: Perbill) -> DispatchResult {
+        ensure!(
+            royalty <= Self::max_creator_royalty(),
+            Error::<T>::RoyaltyUpperBoundExceeded
+        );
+        ensure!(
+            royalty >= Self::min_creator_royalty(),
+            Error::<T>::RoyaltyLowerBoundExceeded
+        );
+        Ok(())
+    }
+
+    /// Ensure bid step bounds satisfied
+    pub(crate) fn ensure_bid_step_bounds_satisfied(bid_step: BalanceOf<T>) -> DispatchResult {
+        ensure!(
+            bid_step <= Self::max_bid_step(),
+            Error::<T>::AuctionBidStepUpperBoundExceeded
+        );
+        ensure!(
+            bid_step >= Self::min_bid_step(),
+            Error::<T>::AuctionBidStepLowerBoundExceeded
+        );
+        Ok(())
+    }
+
+    /// Ensure whitelist bounds satisfied
+    pub(crate) fn ensure_whitelist_bounds_satisfied(
+        whitelist: &BTreeSet<T::MemberId>,
+    ) -> DispatchResult {
+        match whitelist.len() {
+            // whitelist is empty <==> feature is not active.
+            0 => Ok(()),
+            // auctions with one paticipant does not makes sense
+            1 => Err(Error::<T>::WhitelistHasOnlyOneMember.into()),
+            length => {
+                ensure!(
+                    length <= Self::max_auction_whitelist_length() as usize,
+                    Error::<T>::MaxAuctionWhiteListLengthUpperBoundExceeded
+                );
+                Ok(())
+            }
+        }
+    }
+
+    /// Ensure auction duration bounds satisfied
+    pub(crate) fn ensure_auction_duration_bounds_satisfied(
+        duration: T::BlockNumber,
+    ) -> DispatchResult {
+        ensure!(
+            duration <= Self::max_auction_duration(),
+            Error::<T>::AuctionDurationUpperBoundExceeded
+        );
+        ensure!(
+            duration >= Self::min_auction_duration(),
+            Error::<T>::AuctionDurationLowerBoundExceeded
+        );
+
+        Ok(())
+    }
+
+    /// Ensure auction extension period bounds satisfied
+    pub(crate) fn ensure_extension_period_bounds_satisfied(
+        extension_period: T::BlockNumber,
+    ) -> DispatchResult {
+        ensure!(
+            extension_period <= Self::max_auction_extension_period(),
+            Error::<T>::ExtensionPeriodUpperBoundExceeded
+        );
+        ensure!(
+            extension_period >= Self::min_auction_extension_period(),
+            Error::<T>::ExtensionPeriodLowerBoundExceeded
+        );
+
+        Ok(())
+    }
+
+    /// Ensure bid lock duration bounds satisfied
+    pub(crate) fn ensure_bid_lock_duration_bounds_satisfied(
+        bid_lock_duration: T::BlockNumber,
+    ) -> DispatchResult {
+        ensure!(
+            bid_lock_duration <= Self::max_bid_lock_duration(),
+            Error::<T>::BidLockDurationUpperBoundExceeded
+        );
+        ensure!(
+            bid_lock_duration >= Self::min_bid_lock_duration(),
+            Error::<T>::BidLockDurationLowerBoundExceeded
+        );
+        Ok(())
+    }
+
+    /// Ensure royalty bounds satisfied
+    pub(crate) fn ensure_starting_price_bounds_satisfied(
+        starting_price: BalanceOf<T>,
+    ) -> DispatchResult {
+        ensure!(
+            starting_price >= Self::min_starting_price(),
+            Error::<T>::StartingPriceLowerBoundExceeded
+        );
+        ensure!(
+            starting_price <= Self::max_starting_price(),
+            Error::<T>::StartingPriceUpperBoundExceeded
+        );
+        Ok(())
+    }
+
+    /// Ensure given participant have sufficient free balance
+    pub(crate) fn ensure_sufficient_free_balance(
+        participant_account_id: &T::AccountId,
+        balance: BalanceOf<T>,
+    ) -> DispatchResult {
+        ensure!(
+            T::Currency::can_slash(participant_account_id, balance),
+            Error::<T>::InsufficientBalance
+        );
+        Ok(())
+    }
+
+    /// Ensure given participant can buy nft now
+    pub(crate) fn ensure_can_buy_now(
+        nft: &Nft<T>,
+        participant_account_id: &T::AccountId,
+    ) -> DispatchResult {
+        if let TransactionalStatus::BuyNow(price) = &nft.transactional_status {
+            Self::ensure_sufficient_free_balance(participant_account_id, *price)
+        } else {
+            Err(Error::<T>::NFTNotInBuyNowState.into())
+        }
+    }
+
+    /// Ensure new pending offer for given participant is available to proceed
+    pub(crate) fn ensure_new_pending_offer_available_to_proceed(
+        nft: &Nft<T>,
+        participant_account_id: &T::AccountId,
+    ) -> DispatchResult {
+        if let TransactionalStatus::InitiatedOfferToMember(member_id, price) =
+            &nft.transactional_status
+        {
+            // Authorize participant under given member id
+            ensure_member_auth_success::<T>(&member_id, &participant_account_id)?;
+
+            if let Some(price) = price {
+                Self::ensure_sufficient_free_balance(participant_account_id, *price)?;
+            }
+            Ok(())
+        } else {
+            Err(Error::<T>::PendingOfferDoesNotExist.into())
+        }
+    }
+
+    /// Cancel NFT transaction
+    pub fn cancel_transaction(nft: Nft<T>) -> Nft<T> {
+        if let TransactionalStatus::Auction(ref auction) = nft.transactional_status {
+            if let Some(ref last_bid) = auction.last_bid {
+                // Unreserve previous bidder balance
+                T::Currency::unreserve(&last_bid.bidder_account_id, last_bid.amount);
+            }
+        }
+
+        nft.set_idle_transactional_status()
+    }
+
+    /// Buy nft
+    pub(crate) fn buy_now(
+        in_channel: T::ChannelId,
+        mut nft: Nft<T>,
+        owner_account_id: T::AccountId,
+        new_owner_account_id: T::AccountId,
+        new_owner: T::MemberId,
+    ) -> Nft<T> {
+        if let TransactionalStatus::BuyNow(price) = &nft.transactional_status {
+            Self::complete_payment(
+                in_channel,
+                nft.creator_royalty,
+                *price,
+                new_owner_account_id,
+                Some(owner_account_id),
+                false,
+            );
+
+            nft.owner = NFTOwner::Member(new_owner);
+        }
+
+        nft.set_idle_transactional_status()
+    }
+
+    /// Completes nft offer
+    pub(crate) fn complete_nft_offer(
+        in_channel: T::ChannelId,
+        mut nft: Nft<T>,
+        owner_account_id: T::AccountId,
+        new_owner_account_id: T::AccountId,
+    ) -> Nft<T> {
+        if let TransactionalStatus::InitiatedOfferToMember(to, price) = &nft.transactional_status {
+            if let Some(price) = price {
+                Self::complete_payment(
+                    in_channel,
+                    nft.creator_royalty,
+                    *price,
+                    new_owner_account_id,
+                    Some(owner_account_id),
+                    false,
+                );
+            }
+
+            nft.owner = NFTOwner::Member(*to);
+        }
+
+        nft.set_idle_transactional_status()
+    }
+
+    /// Complete payment, either auction related or buy now/offer
+    pub(crate) fn complete_payment(
+        in_channel: T::ChannelId,
+        creator_royalty: Option<Royalty>,
+        amount: BalanceOf<T>,
+        sender_account_id: T::AccountId,
+        receiver_account_id: Option<T::AccountId>,
+        // for auction related payments
+        is_auction: bool,
+    ) {
+        let auction_fee = Self::platform_fee_percentage() * amount;
+
+        // Slash amount from sender
+        if is_auction {
+            T::Currency::slash_reserved(&sender_account_id, amount);
+        } else {
+            T::Currency::slash(&sender_account_id, amount);
+        }
+
+        if let Some(creator_royalty) = creator_royalty {
+            let royalty = creator_royalty * amount;
+
+            // Deposit amount, exluding royalty and platform fee into receiver account
+            match receiver_account_id {
+                Some(receiver_account_id) if amount > royalty + auction_fee => {
+                    T::Currency::deposit_creating(
+                        &receiver_account_id,
+                        amount - royalty - auction_fee,
+                    );
+                }
+                Some(receiver_account_id) => {
+                    T::Currency::deposit_creating(&receiver_account_id, amount - auction_fee);
+                }
+                _ => (),
+            };
+
+            // Should always be Some(_) at this stage, because of previously made check.
+            if let Some(creator_account_id) = Self::channel_by_id(in_channel).reward_account {
+                // Deposit royalty into creator account
+                T::Currency::deposit_creating(&creator_account_id, royalty);
+            }
+        } else {
+            if let Some(receiver_account_id) = receiver_account_id {
+                // Deposit amount, exluding auction fee into receiver account
+                T::Currency::deposit_creating(&receiver_account_id, amount - auction_fee);
+            }
+        }
+    }
+
+    /// Complete auction
+    pub(crate) fn complete_auction(
+        in_channel: T::ChannelId,
+        mut nft: Nft<T>,
+        last_bid: Bid<T::MemberId, T::AccountId, T::BlockNumber, BalanceOf<T>>,
+        owner_account_id: Option<T::AccountId>,
+    ) -> Nft<T> {
+        let last_bid_amount = last_bid.amount;
+        let last_bidder = last_bid.bidder;
+        let bidder_account_id = last_bid.bidder_account_id;
+
+        Self::complete_payment(
+            in_channel,
+            nft.creator_royalty,
+            last_bid_amount,
+            bidder_account_id,
+            owner_account_id,
+            true,
+        );
+
+        nft.owner = NFTOwner::Member(last_bidder);
+        nft.transactional_status = TransactionalStatus::Idle;
+        nft
+    }
+}

+ 499 - 0
runtime-modules/content/src/nft/types.rs

@@ -0,0 +1,499 @@
+use super::*;
+
+/// Metadata for NFT issuance
+pub type Metadata = Vec<u8>;
+
+pub type CuratorGroupId<T> = <T as ContentActorAuthenticator>::CuratorGroupId;
+pub type CuratorId<T> = <T as ContentActorAuthenticator>::CuratorId;
+pub type MemberId<T> = <T as membership::Trait>::MemberId;
+
+/// Owner royalty
+pub type Royalty = Perbill;
+
+/// NFT transactional status
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum TransactionalStatus<
+    BlockNumber: BaseArithmetic + Copy + Default,
+    MemberId: Default + Copy + Ord,
+    AccountId: Default + Clone + Ord,
+    Balance: Default + Clone + BaseArithmetic,
+> {
+    Idle,
+    InitiatedOfferToMember(MemberId, Option<Balance>),
+    Auction(AuctionRecord<BlockNumber, Balance, MemberId, AccountId>),
+    BuyNow(Balance),
+}
+
+impl<
+        BlockNumber: BaseArithmetic + Copy + Default,
+        MemberId: Default + Copy + Ord,
+        AccountId: Default + Clone + Ord,
+        Balance: Default + Clone + BaseArithmetic,
+    > Default for TransactionalStatus<BlockNumber, MemberId, AccountId, Balance>
+{
+    fn default() -> Self {
+        Self::Idle
+    }
+}
+
+/// Owned NFT representation
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct OwnedNFT<
+    BlockNumber: BaseArithmetic + Copy + Default,
+    MemberId: Default + Copy + Ord,
+    AccountId: Default + Clone + Ord,
+    Balance: Default + Clone + BaseArithmetic,
+> {
+    pub owner: NFTOwner<MemberId>,
+    pub transactional_status: TransactionalStatus<BlockNumber, MemberId, AccountId, Balance>,
+    pub creator_royalty: Option<Royalty>,
+}
+
+impl<
+        BlockNumber: BaseArithmetic + Copy + Default,
+        MemberId: Default + Copy + PartialEq + Ord,
+        AccountId: Default + Clone + PartialEq + Ord,
+        Balance: Default + Clone + BaseArithmetic,
+    > OwnedNFT<BlockNumber, MemberId, AccountId, Balance>
+{
+    /// Create new NFT
+    pub fn new(owner: NFTOwner<MemberId>, creator_royalty: Option<Royalty>) -> Self {
+        Self {
+            owner,
+            transactional_status: TransactionalStatus::Idle,
+            creator_royalty,
+        }
+    }
+
+    /// Set nft owner
+    pub fn set_owner(mut self, owner: NFTOwner<MemberId>) -> Self {
+        self.owner = owner;
+        self
+    }
+
+    /// Get nft auction record
+    pub fn ensure_auction_state<T: Trait>(
+        &self,
+    ) -> Result<AuctionRecord<BlockNumber, Balance, MemberId, AccountId>, Error<T>> {
+        if let TransactionalStatus::Auction(auction) = &self.transactional_status {
+            Ok(auction.to_owned())
+        } else {
+            Err(Error::<T>::NotInAuctionState)
+        }
+    }
+
+    ///  Ensure nft transactional status is set to `Idle`
+    pub fn ensure_nft_transactional_status_is_idle<T: Trait>(&self) -> DispatchResult {
+        if let TransactionalStatus::Idle = self.transactional_status {
+            Ok(())
+        } else {
+            Err(Error::<T>::NftIsNotIdle.into())
+        }
+    }
+
+    /// Sets nft transactional status to `BuyNow`
+    pub fn set_buy_now_transactionl_status(mut self, buy_now_price: Balance) -> Self {
+        self.transactional_status = TransactionalStatus::BuyNow(buy_now_price);
+        self
+    }
+
+    /// Sets nft transactional status to provided `Auction`
+    pub fn set_auction_transactional_status(
+        mut self,
+        auction: AuctionRecord<BlockNumber, Balance, MemberId, AccountId>,
+    ) -> Self {
+        self.transactional_status = TransactionalStatus::Auction(auction);
+        self
+    }
+
+    /// Set nft transactional status to `Idle`
+    pub fn set_idle_transactional_status(mut self) -> Self {
+        self.transactional_status = TransactionalStatus::Idle;
+        self
+    }
+
+    /// Set nft transactional status to `InitiatedOfferToMember`
+    pub fn set_pending_offer_transactional_status(
+        mut self,
+        to: MemberId,
+        balance: Option<Balance>,
+    ) -> Self {
+        self.transactional_status = TransactionalStatus::InitiatedOfferToMember(to, balance);
+        self
+    }
+
+    /// Ensure NFT has pending offer
+    pub fn ensure_pending_offer_state<T: Trait>(&self) -> DispatchResult {
+        ensure!(
+            matches!(
+                self.transactional_status,
+                TransactionalStatus::InitiatedOfferToMember(..),
+            ),
+            Error::<T>::PendingOfferDoesNotExist
+        );
+        Ok(())
+    }
+
+    /// Ensure NFT is in BuyNow state
+    pub fn ensure_buy_now_state<T: Trait>(&self) -> DispatchResult {
+        ensure!(
+            matches!(self.transactional_status, TransactionalStatus::BuyNow(..),),
+            Error::<T>::NFTNotInBuyNowState
+        );
+        Ok(())
+    }
+}
+
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum NFTOwner<MemberId> {
+    ChannelOwner,
+    Member(MemberId),
+}
+
+impl<MemberId> Default for NFTOwner<MemberId> {
+    fn default() -> Self {
+        Self::ChannelOwner
+    }
+}
+
+/// Information on the auction being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Bid<MemberId, AccountId, BlockNumber: BaseArithmetic + Copy, Balance> {
+    pub bidder: MemberId,
+    pub bidder_account_id: AccountId,
+    pub amount: Balance,
+    pub made_at_block: BlockNumber,
+}
+
+impl<MemberId, AccountId, BlockNumber: BaseArithmetic + Copy, Balance>
+    Bid<MemberId, AccountId, BlockNumber, Balance>
+{
+    fn new(
+        bidder: MemberId,
+        bidder_account_id: AccountId,
+        amount: Balance,
+        made_at_block: BlockNumber,
+    ) -> Self {
+        Self {
+            bidder,
+            bidder_account_id,
+            amount,
+            made_at_block,
+        }
+    }
+}
+
+/// Information on the auction being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct AuctionRecord<
+    BlockNumber: BaseArithmetic + Copy,
+    Balance: Clone,
+    MemberId: Ord + Clone,
+    AccountId: Ord + Clone,
+> {
+    pub starting_price: Balance,
+    pub buy_now_price: Option<Balance>,
+    /// Auction type (either english or open)
+    pub auction_type: AuctionType<BlockNumber>,
+    pub minimal_bid_step: Balance,
+    pub last_bid: Option<Bid<MemberId, AccountId, BlockNumber, Balance>>,
+    pub starts_at: BlockNumber,
+    pub whitelist: BTreeSet<MemberId>,
+}
+
+impl<
+        BlockNumber: BaseArithmetic + Copy + Default + Clone,
+        Balance: Default + BaseArithmetic + Clone,
+        MemberId: Default + PartialEq + Ord + Clone,
+        AccountId: Default + PartialEq + Ord + Clone,
+    > AuctionRecord<BlockNumber, Balance, MemberId, AccountId>
+{
+    /// Create a new auction record with provided parameters
+    pub fn new(auction_params: AuctionParams<BlockNumber, Balance, MemberId>) -> Self {
+        if let Some(starts_at) = auction_params.starts_at {
+            Self {
+                starting_price: auction_params.starting_price,
+                buy_now_price: auction_params.buy_now_price,
+                auction_type: auction_params.auction_type,
+                minimal_bid_step: auction_params.minimal_bid_step,
+                last_bid: None,
+                starts_at,
+                whitelist: auction_params.whitelist,
+            }
+        } else {
+            Self {
+                starting_price: auction_params.starting_price,
+                buy_now_price: auction_params.buy_now_price,
+                auction_type: auction_params.auction_type,
+                minimal_bid_step: auction_params.minimal_bid_step,
+                last_bid: None,
+                starts_at: BlockNumber::default(),
+                whitelist: auction_params.whitelist,
+            }
+        }
+    }
+
+    /// Ensure new bid is greater then last bid + minimal bid step
+    pub fn ensure_is_valid_bid<T: Trait>(&self, new_bid: Balance) -> DispatchResult {
+        // Always allow to buy now
+        match &self.buy_now_price {
+            Some(buy_now_price) if new_bid >= *buy_now_price => (),
+
+            // Ensure new bid is greater then last bid + minimal bid step
+            _ => {
+                if let Some(last_bid) = &self.last_bid {
+                    ensure!(
+                        last_bid
+                            .amount
+                            .checked_add(&self.minimal_bid_step)
+                            .ok_or(Error::<T>::OverflowOrUnderflowHappened)?
+                            <= new_bid,
+                        Error::<T>::BidStepConstraintViolated
+                    );
+                } else {
+                    ensure!(
+                        self.starting_price <= new_bid,
+                        Error::<T>::StartingPriceConstraintViolated
+                    );
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Make auction bid
+    pub fn make_bid(
+        mut self,
+        bidder: MemberId,
+        bidder_account_id: AccountId,
+        bid: Balance,
+        last_bid_block: BlockNumber,
+    ) -> (Self, bool, Bid<MemberId, AccountId, BlockNumber, Balance>) {
+        let bid = Bid::new(bidder, bidder_account_id, bid, last_bid_block);
+        let is_extended = match &mut self.auction_type {
+            AuctionType::English(EnglishAuctionDetails {
+                extension_period,
+                auction_duration,
+            }) if last_bid_block - self.starts_at >= *auction_duration - *extension_period => {
+                // bump auction duration when bid is made during extension period.
+                *auction_duration += *extension_period;
+                true
+            }
+            _ => false,
+        };
+
+        self.last_bid = Some(bid.clone());
+        (self, is_extended, bid)
+    }
+
+    /// Cnacel auction bid
+    pub fn cancel_bid(mut self) -> Self {
+        self.last_bid = None;
+        self
+    }
+
+    // Ensure auction has no bids
+    fn ensure_has_no_bids<T: Trait>(&self) -> DispatchResult {
+        ensure!(self.last_bid.is_none(), Error::<T>::ActionHasBidsAlready);
+        Ok(())
+    }
+
+    /// Ensure given auction can be canceled
+    pub fn ensure_auction_can_be_canceled<T: Trait>(&self) -> DispatchResult {
+        if let AuctionType::English(_) = self.auction_type {
+            self.ensure_has_no_bids::<T>()
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Ensure auction have been already started
+    pub fn ensure_auction_started<T: Trait>(&self, current_block: BlockNumber) -> DispatchResult {
+        ensure!(
+            self.starts_at <= current_block,
+            Error::<T>::AuctionDidNotStart
+        );
+        Ok(())
+    }
+
+    /// Check whether nft auction expired
+    pub fn is_nft_auction_expired(&self, current_block: BlockNumber) -> bool {
+        if let AuctionType::English(EnglishAuctionDetails {
+            auction_duration, ..
+        }) = self.auction_type
+        {
+            // Check whether auction time expired.
+            (current_block - self.starts_at) >= auction_duration
+        } else {
+            // Open auction never expires
+            false
+        }
+    }
+
+    /// Ensure nft auction not expired
+    pub fn ensure_nft_auction_not_expired<T: Trait>(
+        &self,
+        current_block: BlockNumber,
+    ) -> DispatchResult {
+        ensure!(
+            !self.is_nft_auction_expired(current_block),
+            Error::<T>::NFTAuctionIsAlreadyExpired
+        );
+        Ok(())
+    }
+
+    /// Whether caller is last bidder
+    pub fn is_last_bidder(&self, who: MemberId) -> bool {
+        matches!(&self.last_bid, Some(last_bid) if last_bid.bidder == who)
+    }
+
+    /// Ensure caller is last bidder.
+    pub fn ensure_caller_is_last_bidder<T: Trait>(&self, who: MemberId) -> DispatchResult {
+        ensure!(self.is_last_bidder(who), Error::<T>::ActorIsNotALastBidder);
+        Ok(())
+    }
+
+    /// Ensure auction type is `Open`
+    pub fn ensure_is_open_auction<T: Trait>(&self) -> DispatchResult {
+        ensure!(
+            matches!(&self.auction_type, AuctionType::Open(_)),
+            Error::<T>::IsNotOpenAuctionType
+        );
+        Ok(())
+    }
+
+    /// Ensure auction type is `English`
+    pub fn ensure_is_english_auction<T: Trait>(&self) -> DispatchResult {
+        ensure!(
+            matches!(&self.auction_type, AuctionType::English(_)),
+            Error::<T>::IsNotEnglishAuctionType
+        );
+        Ok(())
+    }
+
+    /// Ensure bid lock duration expired
+    pub fn ensure_bid_lock_duration_expired<T: Trait>(
+        &self,
+        current_block: BlockNumber,
+        bid: Bid<MemberId, AccountId, BlockNumber, Balance>,
+    ) -> DispatchResult {
+        if let AuctionType::Open(OpenAuctionDetails { bid_lock_duration }) = &self.auction_type {
+            ensure!(
+                current_block - bid.made_at_block >= *bid_lock_duration,
+                Error::<T>::BidLockDurationIsNotExpired
+            );
+        }
+        Ok(())
+    }
+
+    /// Ensure bid can be cancelled
+    pub fn ensure_bid_can_be_canceled<T: Trait>(
+        &self,
+        who: MemberId,
+        current_block: BlockNumber,
+    ) -> DispatchResult {
+        // ensure is open auction
+        self.ensure_is_open_auction::<T>()?;
+
+        // ensure last bid exists
+        let last_bid = self.ensure_last_bid_exists::<T>()?;
+
+        // ensure caller is last bidder.
+        self.ensure_caller_is_last_bidder::<T>(who)?;
+
+        // ensure bid lock duration expired
+        self.ensure_bid_lock_duration_expired::<T>(current_block, last_bid)
+    }
+
+    /// If whitelist set, ensure provided member is authorized to make bids
+    pub fn ensure_whitelisted_participant<T: Trait>(&self, who: MemberId) -> DispatchResult {
+        if !self.whitelist.is_empty() {
+            ensure!(
+                self.whitelist.contains(&who),
+                Error::<T>::MemberIsNotAllowedToParticipate
+            );
+        }
+        Ok(())
+    }
+
+    /// Ensure auction has last bid, return corresponding reference
+    pub fn ensure_last_bid_exists<T: Trait>(
+        &self,
+    ) -> Result<Bid<MemberId, AccountId, BlockNumber, Balance>, Error<T>> {
+        if let Some(bid) = &self.last_bid {
+            Ok(bid.clone())
+        } else {
+            Err(Error::<T>::LastBidDoesNotExist)
+        }
+    }
+}
+
+/// Auction alias type for simplification.
+pub type Auction<T> = AuctionRecord<
+    <T as frame_system::Trait>::BlockNumber,
+    BalanceOf<T>,
+    MemberId<T>,
+    <T as frame_system::Trait>::AccountId,
+>;
+
+/// OwnedNFT alias type for simplification.
+pub type Nft<T> = OwnedNFT<
+    <T as frame_system::Trait>::BlockNumber,
+    MemberId<T>,
+    <T as frame_system::Trait>::AccountId,
+    BalanceOf<T>,
+>;
+
+/// Parameters, needed for auction start
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct AuctionParams<BlockNumber, Balance, MemberId: Ord> {
+    // Auction type (either english or open)
+    pub auction_type: AuctionType<BlockNumber>,
+    pub starting_price: Balance,
+    pub minimal_bid_step: Balance,
+    pub buy_now_price: Option<Balance>,
+    pub starts_at: Option<BlockNumber>,
+    pub whitelist: BTreeSet<MemberId>,
+}
+
+/// Auction type
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum AuctionType<BlockNumber> {
+    // English auction details
+    English(EnglishAuctionDetails<BlockNumber>),
+    // Open auction details
+    Open(OpenAuctionDetails<BlockNumber>),
+}
+
+impl<BlockNumber: Default> Default for AuctionType<BlockNumber> {
+    fn default() -> Self {
+        Self::English(EnglishAuctionDetails::default())
+    }
+}
+
+/// English auction details
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct EnglishAuctionDetails<BlockNumber> {
+    // the remaining time on a lot will automatically reset to to the preset extension time
+    // if a new bid is placed within that period
+    pub extension_period: BlockNumber,
+    // auction duration
+    pub auction_duration: BlockNumber,
+}
+
+/// Open auction details
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub struct OpenAuctionDetails<BlockNumber> {
+    // bid lock duration
+    pub bid_lock_duration: BlockNumber,
+}

+ 47 - 1
runtime-modules/content/src/permissions/mod.rs

@@ -253,7 +253,53 @@ pub fn ensure_actor_is_channel_owner<T: Trait>(
 
 /// SET FEATURED VIDEOS & CATEGORIES MANAGEMENT PERMISSION
 
-// Ensure actor can update or delete channels and videos
+/// Ensure actor can manage nft
+pub fn ensure_actor_authorized_to_manage_nft<T: Trait>(
+    origin: T::Origin,
+    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+    nft_owner: &NFTOwner<T::MemberId>,
+    in_channel: T::ChannelId,
+) -> DispatchResult {
+    let sender = ensure_signed(origin)?;
+    ensure_actor_auth_success::<T>(&sender, actor)?;
+
+    if let NFTOwner::Member(member_id) = nft_owner {
+        ensure!(
+            *actor == ContentActor::Member(*member_id),
+            Error::<T>::ActorNotAuthorized
+        );
+    } else {
+        // Ensure curator group is the channel owner.
+        let channel_owner = Module::<T>::ensure_channel_exists(&in_channel)?.owner;
+
+        match actor {
+            ContentActor::Lead => {
+                if let ChannelOwner::CuratorGroup(_) = channel_owner {
+                    return Ok(());
+                } else {
+                    return Err(Error::<T>::ActorNotAuthorized.into());
+                }
+            }
+            ContentActor::Curator(curator_group_id, curator_id) => {
+                // Ensure curator group is the channel owner.
+                ensure!(
+                    channel_owner == ChannelOwner::CuratorGroup(*curator_group_id),
+                    Error::<T>::ActorNotAuthorized
+                );
+            }
+            ContentActor::Member(member_id) => {
+                // Ensure the member is the channel owner.
+                ensure!(
+                    channel_owner == ChannelOwner::Member(*member_id),
+                    Error::<T>::ActorNotAuthorized
+                );
+            }
+        }
+    }
+    Ok(())
+}
+
+// Enure actor can update or delete channels and videos
 pub fn ensure_actor_authorized_to_set_featured_videos<T: Trait>(
     origin: T::Origin,
     actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,

+ 1 - 1
runtime-modules/content/src/tests/curators.rs

@@ -1,6 +1,6 @@
 #![cfg(test)]
 
-use super::mock::*;
+use super::mock::{CuratorGroupId, CuratorId, *};
 use crate::*;
 use frame_support::{assert_err, assert_ok};
 

+ 191 - 13
runtime-modules/content/src/tests/mock.rs

@@ -14,6 +14,7 @@ use sp_runtime::{
 use crate::ContentActorAuthenticator;
 use crate::Trait;
 use common::currency::GovernanceCurrency;
+use frame_support::assert_ok;
 
 /// Module Aliases
 pub type System = frame_system::Module<Test>;
@@ -28,8 +29,12 @@ pub type VideoId = <Test as Trait>::VideoId;
 pub type VideoPostId = <Test as Trait>::VideoPostId;
 pub type CuratorId = <Test as ContentActorAuthenticator>::CuratorId;
 pub type CuratorGroupId = <Test as ContentActorAuthenticator>::CuratorGroupId;
-pub type MemberId = <Test as MembershipTypes>::MemberId;
+pub type MemberId = <Test as membership::Trait>::MemberId;
 pub type ChannelId = <Test as StorageOwnership>::ChannelId;
+pub type VideoId = <Test as Trait>::VideoId;
+pub type VideoCategoryId = <Test as Trait>::VideoCategoryId;
+pub type ChannelCategoryId = <Test as Trait>::ChannelCategoryId;
+type ChannelOwnershipTransferRequestId = <Test as Trait>::ChannelOwnershipTransferRequestId;
 
 /// Account Ids
 pub const DEFAULT_MEMBER_ACCOUNT_ID: u64 = 101;
@@ -82,6 +87,7 @@ pub const MEMBERS_COUNT: u64 = 10;
 pub const PAYMENTS_NUMBER: u64 = 10;
 pub const DEFAULT_PAYOUT_CLAIMED: u64 = 10;
 pub const DEFAULT_PAYOUT_EARNED: u64 = 10;
+pub const REWARD_ACCOUNT_ID: u64 = 25;
 
 impl_outer_origin! {
     pub enum Origin for Test {}
@@ -148,18 +154,6 @@ impl frame_system::Trait for Test {
     type SystemWeightInfo = ();
 }
 
-impl pallet_timestamp::Trait for Test {
-    type Moment = u64;
-    type OnTimestampSet = ();
-    type MinimumPeriod = MinimumPeriod;
-    type WeightInfo = ();
-}
-
-impl common::MembershipTypes for Test {
-    type MemberId = u64;
-    type ActorId = u64;
-}
-
 impl common::StorageOwnership for Test {
     type ChannelId = u64;
     type ContentId = u64;
@@ -180,6 +174,13 @@ impl balances::Trait for Test {
     type MaxLocks = ();
 }
 
+impl pallet_timestamp::Trait for Test {
+    type Moment = u64;
+    type OnTimestampSet = ();
+    type MinimumPeriod = MinimumPeriod;
+    type WeightInfo = ();
+}
+
 impl GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
@@ -501,6 +502,21 @@ pub struct ExtBuilder {
     channel_migration: ChannelMigrationConfig<Test>,
     max_reward_allowed: BalanceOf<Test>,
     min_cashout_allowed: BalanceOf<Test>,
+    min_auction_duration: u64,
+    max_auction_duration: u64,
+    min_auction_extension_period: u64,
+    max_auction_extension_period: u64,
+    min_bid_lock_duration: u64,
+    max_bid_lock_duration: u64,
+    min_starting_price: u64,
+    max_starting_price: u64,
+    min_creator_royalty: Perbill,
+    max_creator_royalty: Perbill,
+    min_bid_step: u64,
+    max_bid_step: u64,
+    platform_fee_percentage: Perbill,
+    auction_starts_at_max_delta: u64,
+    max_auction_whitelist_length: u32,
 }
 
 impl Default for ExtBuilder {
@@ -527,6 +543,21 @@ impl Default for ExtBuilder {
             },
             max_reward_allowed: BalanceOf::<Test>::from(1_000u32),
             min_cashout_allowed: BalanceOf::<Test>::from(1u32),
+            min_auction_duration: 5,
+            max_auction_duration: 20,
+            min_auction_extension_period: 4,
+            max_auction_extension_period: 30,
+            min_bid_lock_duration: 2,
+            max_bid_lock_duration: 10,
+            min_starting_price: 10,
+            max_starting_price: 1000,
+            min_creator_royalty: Perbill::from_percent(1),
+            max_creator_royalty: Perbill::from_percent(5),
+            min_bid_step: 10,
+            max_bid_step: 100,
+            platform_fee_percentage: Perbill::from_percent(1),
+            auction_starts_at_max_delta: 90_000,
+            max_auction_whitelist_length: 4,
         }
     }
 }
@@ -553,6 +584,21 @@ impl ExtBuilder {
             channel_migration: self.channel_migration,
             max_reward_allowed: self.max_reward_allowed,
             min_cashout_allowed: self.min_cashout_allowed,
+            min_auction_duration: self.min_auction_duration,
+            max_auction_duration: self.max_auction_duration,
+            min_auction_extension_period: self.min_auction_extension_period,
+            max_auction_extension_period: self.max_auction_extension_period,
+            min_bid_lock_duration: self.min_bid_lock_duration,
+            max_bid_lock_duration: self.max_bid_lock_duration,
+            min_starting_price: self.min_starting_price,
+            max_starting_price: self.max_starting_price,
+            min_creator_royalty: self.min_creator_royalty,
+            max_creator_royalty: self.max_creator_royalty,
+            min_bid_step: self.min_bid_step,
+            max_bid_step: self.max_bid_step,
+            platform_fee_percentage: self.platform_fee_percentage,
+            auction_starts_at_max_delta: self.auction_starts_at_max_delta,
+            max_auction_whitelist_length: self.max_auction_whitelist_length,
         }
         .assimilate_storage(&mut t)
         .unwrap();
@@ -572,5 +618,137 @@ pub fn run_to_block(n: u64) {
         <Content as OnFinalize<u64>>::on_finalize(System::block_number());
         System::set_block_number(System::block_number() + 1);
         <Content as OnInitialize<u64>>::on_initialize(System::block_number());
+        <System as OnInitialize<u64>>::on_initialize(System::block_number());
+    }
+}
+
+// Events
+
+type RawEvent = crate::RawEvent<
+    ContentActor<CuratorGroupId, CuratorId, MemberId>,
+    MemberId,
+    CuratorGroupId,
+    CuratorId,
+    VideoId,
+    VideoCategoryId,
+    ChannelId,
+    ChannelCategoryId,
+    ChannelOwnershipTransferRequestId,
+    u64,
+    u64,
+    u64,
+    ChannelOwnershipTransferRequest<Test>,
+    Series<<Test as StorageOwnership>::ChannelId, VideoId>,
+    Channel<Test>,
+    <Test as storage::Trait>::DataObjectId,
+    bool,
+    AuctionParams<<Test as frame_system::Trait>::BlockNumber, BalanceOf<Test>, MemberId>,
+    BalanceOf<Test>,
+    ChannelCreationParameters<Test>,
+    ChannelUpdateParameters<Test>,
+    VideoCreationParameters<Test>,
+    VideoUpdateParameters<Test>,
+    NewAssets<Test>,
+    bool,
+>;
+
+pub fn get_test_event(raw_event: RawEvent) -> MetaEvent {
+    MetaEvent::content(raw_event)
+}
+
+pub fn assert_event(tested_event: MetaEvent, number_of_events_after_call: usize) {
+    // Ensure  runtime events length is equal to expected number of events after call
+    assert_eq!(System::events().len(), number_of_events_after_call);
+
+    // Ensure  last emitted event is equal to expected one
+    assert_eq!(System::events().iter().last().unwrap().event, tested_event);
+}
+
+pub fn create_member_channel() -> ChannelId {
+    let channel_id = Content::next_channel_id();
+
+    // Member can create the channel
+    assert_ok!(Content::create_channel(
+        Origin::signed(FIRST_MEMBER_ORIGIN),
+        ContentActor::Member(FIRST_MEMBER_ID),
+        ChannelCreationParametersRecord {
+            assets: NewAssets::<Test>::Urls(vec![]),
+            meta: vec![],
+            reward_account: None,
+        }
+    ));
+
+    channel_id
+}
+
+pub fn get_video_creation_parameters() -> VideoCreationParameters<Test> {
+    VideoCreationParametersRecord {
+        assets: NewAssets::<Test>::Upload(CreationUploadParameters {
+            object_creation_list: vec![
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"first".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"second".to_vec(),
+                },
+                DataObjectCreationParameters {
+                    size: 3,
+                    ipfs_content_id: b"third".to_vec(),
+                },
+            ],
+            expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+        }),
+        meta: b"test".to_vec(),
+    }
+}
+
+/// Get good params for open auction
+pub fn get_open_auction_params(
+) -> AuctionParams<<Test as frame_system::Trait>::BlockNumber, BalanceOf<Test>, MemberId> {
+    AuctionParams {
+        starting_price: Content::min_starting_price(),
+        buy_now_price: None,
+        auction_type: AuctionType::Open(OpenAuctionDetails {
+            bid_lock_duration: Content::min_bid_lock_duration(),
+        }),
+        minimal_bid_step: Content::min_bid_step(),
+        starts_at: None,
+        whitelist: BTreeSet::new(),
     }
 }
+
+pub type CollectiveFlip = randomness_collective_flip::Module<Test>;
+
+pub fn create_simple_channel_and_video(sender: u64, member_id: u64) {
+    // deposit initial balance
+    let _ = balances::Module::<Test>::deposit_creating(
+        &sender,
+        <Test as balances::Trait>::Balance::from(30u32),
+    );
+
+    let channel_id = NextChannelId::<Test>::get();
+
+    create_channel_mock(
+        sender,
+        ContentActor::Member(member_id),
+        ChannelCreationParametersRecord {
+            assets: NewAssets::<Test>::Urls(vec![]),
+            meta: vec![],
+            reward_account: Some(REWARD_ACCOUNT_ID),
+        },
+        Ok(()),
+    );
+
+    let params = get_video_creation_parameters();
+
+    // Create simple video using member actor
+    create_video_mock(
+        sender,
+        ContentActor::Member(member_id),
+        channel_id,
+        params,
+        Ok(()),
+    );
+}

+ 1 - 0
runtime-modules/content/src/tests/mod.rs

@@ -6,5 +6,6 @@ mod fixtures;
 mod merkle;
 mod migration;
 mod mock;
+mod nft;
 mod posts;
 mod videos;

+ 14 - 0
runtime-modules/content/src/tests/nft.rs

@@ -0,0 +1,14 @@
+mod accept_incoming_offer;
+mod buy_nft;
+mod cancel_buy_now;
+mod cancel_nft_auction;
+mod cancel_offer;
+mod cancel_open_auction_bid;
+mod claim_won_english_auction;
+mod issue_nft;
+mod make_bid;
+mod offer_nft;
+mod pick_open_auction_winner;
+mod sell_nft;
+mod sling_nft_back;
+mod start_nft_auction;

+ 287 - 0
runtime-modules/content/src/tests/nft/accept_incoming_offer.rs

@@ -0,0 +1,287 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn accept_incoming_offer() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Accept nft offer
+        assert_ok!(Content::accept_incoming_offer(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft offer accepted succesfully
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                owner: NFTOwner::Member(member_id),
+                transactional_status: TransactionalStatus::Idle,
+                ..
+            }) if member_id == SECOND_MEMBER_ID
+        ));
+
+        let offer_accepted_event = get_test_event(RawEvent::OfferAccepted(video_id));
+
+        // Last event checked
+        assert_event(offer_accepted_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn accept_incoming_offer_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to accept incoming nft offer if corresponding video does not exist
+        let accept_incoming_offer_result =
+            Content::accept_incoming_offer(Origin::signed(SECOND_MEMBER_ORIGIN), video_id);
+
+        // Failure checked
+        assert_err!(
+            accept_incoming_offer_result,
+            Error::<Test>::VideoDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn accept_incoming_offer_nft_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to accept incoming nft offer if corresponding nft is not issued yet
+        let accept_incoming_offer_result =
+            Content::accept_incoming_offer(Origin::signed(SECOND_MEMBER_ORIGIN), video_id);
+
+        // Failure checked
+        assert_err!(accept_incoming_offer_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn accept_incoming_offer_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Make an attempt to accept incoming nft offer providing wrong credentials
+        let accept_incoming_offer_result =
+            Content::accept_incoming_offer(Origin::signed(UNKNOWN_ORIGIN), video_id);
+
+        // Failure checked
+        assert_err!(
+            accept_incoming_offer_result,
+            Error::<Test>::MemberAuthFailed
+        );
+    })
+}
+
+#[test]
+fn accept_incoming_offer_no_incoming_offers() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to accept incoming nft offer if there is no incoming transfers
+        let accept_incoming_offer_result =
+            Content::accept_incoming_offer(Origin::signed(SECOND_MEMBER_ORIGIN), video_id);
+
+        // Failure checked
+        assert_err!(
+            accept_incoming_offer_result,
+            Error::<Test>::PendingOfferDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn accept_incoming_offer_reward_account_is_not_set() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        let _ = balances::Module::<Test>::deposit_creating(
+            &FIRST_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(100u32),
+        );
+
+        let channel_id = NextChannelId::<Test>::get();
+
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
+                meta: vec![],
+                reward_account: None,
+            },
+            Ok(()),
+        );
+
+        let params = get_video_creation_parameters();
+
+        // Create simple video using member actor
+        create_video_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            params,
+            Ok(()),
+        );
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Make an attempt to accept incoming nft offer if sender is owner and reward account is not set
+        let accept_incoming_offer_result =
+            Content::accept_incoming_offer(Origin::signed(SECOND_MEMBER_ORIGIN), video_id);
+
+        // Failure checked
+        assert_err!(
+            accept_incoming_offer_result,
+            Error::<Test>::RewardAccountIsNotSet
+        );
+    })
+}
+
+#[test]
+fn accept_incoming_offer_insufficient_balance() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let price = 10000;
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            Some(price),
+        ));
+
+        // Make an attempt to accept incoming nft offer if there is no incoming transfers
+        let accept_incoming_offer_result =
+            Content::accept_incoming_offer(Origin::signed(SECOND_MEMBER_ORIGIN), video_id);
+
+        // Failure checked
+        assert_err!(
+            accept_incoming_offer_result,
+            Error::<Test>::InsufficientBalance
+        );
+    })
+}

+ 344 - 0
runtime-modules/content/src/tests/nft/buy_nft.rs

@@ -0,0 +1,344 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn buy_nft() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let price = 1000u64;
+
+        // deposit balance to second member
+        let _ = balances::Module::<Test>::deposit_creating(
+            &SECOND_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(price),
+        );
+
+        // Sell nft
+        assert_ok!(Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Buy nft
+        assert_ok!(Content::buy_nft(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+            SECOND_MEMBER_ID,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure buyer balance was succesfully slashed after nft had been bought
+        assert_eq!(
+            balances::Module::<Test>::free_balance(SECOND_MEMBER_ORIGIN),
+            0
+        );
+
+        // Ensure the price of nft - platform fee was succesfully deposited into seller account (channel reward account id in this case)
+        assert_eq!(
+            balances::Module::<Test>::free_balance(REWARD_ACCOUNT_ID),
+            price - Content::platform_fee_percentage() * price
+        );
+
+        // Ensure nft succesfully bought
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                owner: NFTOwner::Member(SECOND_MEMBER_ID),
+                transactional_status: TransactionalStatus::Idle,
+                ..
+            })
+        ));
+
+        let nft_bought_event = get_test_event(RawEvent::NFTBought(video_id, SECOND_MEMBER_ID));
+
+        // Last event checked
+        assert_event(nft_bought_event, number_of_events_before_call + 3);
+    })
+}
+
+#[test]
+fn buy_nft_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        let price = 1000u64;
+
+        // deposit balance to second member
+        let _ = balances::Module::<Test>::deposit_creating(
+            &SECOND_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(price),
+        );
+
+        // Make an attempt to buy nft which corresponding video does not exist yet
+        let buy_nft_result = Content::buy_nft(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+            SECOND_MEMBER_ID,
+        );
+
+        // Failure checked
+        assert_err!(buy_nft_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn buy_nft_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        let price = 1000u64;
+
+        // deposit balance to second member
+        let _ = balances::Module::<Test>::deposit_creating(
+            &SECOND_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(price),
+        );
+
+        // Make an attempt to buy nft which is not issued yet
+        let buy_nft_result = Content::buy_nft(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+            SECOND_MEMBER_ID,
+        );
+
+        // Failure checked
+        assert_err!(buy_nft_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn buy_nft_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        let price = 1000u64;
+
+        // deposit balance to second member
+        let _ = balances::Module::<Test>::deposit_creating(
+            &SECOND_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(price),
+        );
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Sell nft
+        assert_ok!(Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Make an attempt to buy nft with wrong credentials
+        let buy_nft_result =
+            Content::buy_nft(Origin::signed(SECOND_MEMBER_ORIGIN), video_id, UNKNOWN_ID);
+
+        // Failure checked
+        assert_err!(buy_nft_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn buy_nft_not_in_buy_now_state() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        let price = 1000u64;
+
+        // deposit balance to second member
+        let _ = balances::Module::<Test>::deposit_creating(
+            &SECOND_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(price),
+        );
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to buy nft which is not in BuyNow state
+        let buy_nft_result = Content::buy_nft(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+            SECOND_MEMBER_ID,
+        );
+
+        // Failure checked
+        assert_err!(buy_nft_result, Error::<Test>::NFTNotInBuyNowState);
+    })
+}
+
+#[test]
+fn buy_nft_insufficient_balance() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let price = 1000u64;
+
+        // Sell nft
+        assert_ok!(Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Make an attempt to buy nft with wrong credentials
+        let buy_nft_result = Content::buy_nft(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+            SECOND_MEMBER_ID,
+        );
+
+        // Failure checked
+        assert_err!(buy_nft_result, Error::<Test>::InsufficientBalance);
+    })
+}
+
+#[test]
+fn buy_nft_reward_account_is_not_set() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        let _ = balances::Module::<Test>::deposit_creating(
+            &FIRST_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(100u32),
+        );
+
+        let channel_id = NextChannelId::<Test>::get();
+
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![]),
+                meta: vec![],
+                reward_account: None,
+            },
+            Ok(()),
+        );
+
+        let params = get_video_creation_parameters();
+
+        // Create simple video using member actor
+        create_video_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            params,
+            Ok(()),
+        );
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let price = 1000u64;
+
+        // deposit balance to second member
+        let _ = balances::Module::<Test>::deposit_creating(
+            &SECOND_MEMBER_ORIGIN,
+            <Test as balances::Trait>::Balance::from(price),
+        );
+
+        // Sell nft
+        assert_ok!(Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Make an attempt to buy nft when reward account is not set
+        let buy_nft_result = Content::buy_nft(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+            SECOND_MEMBER_ID,
+        );
+
+        // Failure checked
+        assert_err!(buy_nft_result, Error::<Test>::RewardAccountIsNotSet);
+    })
+}

+ 226 - 0
runtime-modules/content/src/tests/nft/cancel_buy_now.rs

@@ -0,0 +1,226 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn cancel_buy_now() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let price = 100;
+
+        // Sell nft
+        assert_ok!(Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Cancel buy now
+        assert_ok!(Content::cancel_buy_now(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft status changed to given Auction
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Idle,
+                ..
+            })
+        ));
+
+        let buy_now_canceled_event = get_test_event(RawEvent::BuyNowCanceled(
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+        ));
+
+        // Last event checked
+        assert_event(buy_now_canceled_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn cancel_buy_now_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to cancel buy now which corresponding video does not exist yet
+        let cancel_buy_now_result = Content::cancel_buy_now(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_buy_now_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn cancel_buy_now_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to cancel buy now for nft which is not issued yet
+        let cancel_buy_now_result = Content::cancel_buy_now(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_buy_now_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn cancel_buy_now_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let price = 100;
+
+        // Sell nft
+        assert_ok!(Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Make an attempt to cancel buy now with wrong credentials
+        let cancel_buy_now_result = Content::cancel_buy_now(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(UNKNOWN_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_buy_now_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn cancel_buy_now_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let price = 100;
+
+        // Sell nft
+        assert_ok!(Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Make an attempt to cancel buy now if actor is not authorized
+        let cancel_buy_now_result = Content::cancel_buy_now(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_buy_now_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn cancel_buy_now_not_in_auction_state() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to cancel buy now if there is no pending one
+        let cancel_buy_now_result = Content::cancel_buy_now(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_buy_now_result, Error::<Test>::NFTNotInBuyNowState);
+    })
+}

+ 288 - 0
runtime-modules/content/src/tests/nft/cancel_nft_auction.rs

@@ -0,0 +1,288 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn cancel_nft_auction() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            get_open_auction_params()
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Cancel nft auction
+        assert_ok!(Content::cancel_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft status changed to given Auction
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Idle,
+                ..
+            })
+        ));
+
+        let nft_auction_canceled_event = get_test_event(RawEvent::AuctionCanceled(
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        ));
+
+        // Last event checked
+        assert_event(nft_auction_canceled_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn cancel_nft_auction_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to cancel nft auction which corresponding video does not exist yet
+        let cancel_nft_auction_result = Content::cancel_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_nft_auction_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn cancel_nft_auction_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to cancel nft auction for nft which is not issued yet
+        let cancel_nft_auction_result = Content::cancel_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_nft_auction_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn cancel_nft_auction_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            get_open_auction_params()
+        ));
+
+        // Make an attempt to cancel nft auction with wrong credentials
+        let cancel_nft_auction_result = Content::cancel_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(UNKNOWN_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_nft_auction_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn cancel_nft_auction_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            get_open_auction_params()
+        ));
+
+        // Make an attempt to cancel nft auction if actor is not authorized
+        let cancel_nft_auction_result = Content::cancel_nft_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_nft_auction_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn cancel_nft_auction_not_in_auction_state() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to cancel nft auction if there is no pending one
+        let cancel_nft_auction_result = Content::cancel_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_nft_auction_result, Error::<Test>::NotInAuctionState);
+    })
+}
+
+#[test]
+fn cancel_nft_auction_english_auction_with_bids() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make an english auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Make an attempt to cancel an english auction which already contains a bid
+        let cancel_nft_auction_result = Content::cancel_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_nft_auction_result,
+            Error::<Test>::ActionHasBidsAlready
+        );
+    })
+}

+ 223 - 0
runtime-modules/content/src/tests/nft/cancel_offer.rs

@@ -0,0 +1,223 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn cancel_offer() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Cancel offer
+        assert_ok!(Content::cancel_offer(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft status changed to given Auction
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Idle,
+                ..
+            })
+        ));
+
+        let buy_now_canceled_event = get_test_event(RawEvent::OfferCanceled(
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+        ));
+
+        // Last event checked
+        assert_event(buy_now_canceled_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn cancel_offer_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to cancel offer which corresponding video does not exist yet
+        let cancel_offer_result = Content::cancel_offer(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_offer_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn cancel_offer_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to cancel offer for nft which is not issued yet
+        let cancel_offer_result = Content::cancel_offer(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_offer_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn cancel_offer_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Make an attempt to cancel offer with wrong credentials
+        let cancel_offer_result = Content::cancel_offer(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(UNKNOWN_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_offer_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn cancel_offer_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Make an attempt to cancel offer if actor is not authorized
+        let cancel_offer_result = Content::cancel_offer(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_offer_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn cancel_offer_not_in_auction_state() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to cancel offer if there is no pending one
+        let cancel_offer_result = Content::cancel_offer(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(cancel_offer_result, Error::<Test>::PendingOfferDoesNotExist);
+    })
+}

+ 509 - 0
runtime-modules/content/src/tests/nft/cancel_open_auction_bid.rs

@@ -0,0 +1,509 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn cancel_open_auction_bid() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Run to the block where bid lock duration expires
+        run_to_block(bid_lock_duration + 1);
+
+        // Cancel auction bid
+        assert_ok!(Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure bid on specific auction successfully canceled
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Auction(auction_without_bid,),
+                ..
+            }) if auction_without_bid.last_bid.is_none()
+        ));
+
+        let cancel_open_auction_bid_event =
+            get_test_event(RawEvent::AuctionBidCanceled(SECOND_MEMBER_ID, video_id));
+
+        // Last event checked
+        assert_event(
+            cancel_open_auction_bid_event,
+            number_of_events_before_call + 1,
+        );
+    })
+}
+
+#[test]
+fn cancel_open_auction_bid_lock_duration_did_not_expire() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Make an attempt to cancel open auction bid if lock duration did not expire
+        let cancel_open_auction_bid_result = Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_open_auction_bid_result,
+            Error::<Test>::BidLockDurationIsNotExpired
+        );
+    })
+}
+
+#[test]
+fn cancel_open_auction_bid_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Run to the block where bid lock duration expires
+        run_to_block(bid_lock_duration + 1);
+
+        // Make an attempt to cancel open auction bid with wrong credentials
+        let cancel_open_auction_bid_result = Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            UNKNOWN_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_open_auction_bid_result,
+            Error::<Test>::MemberAuthFailed
+        );
+    })
+}
+
+#[test]
+fn cancel_open_auction_bid_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to cancel open auction bid which corresponding video does not exist
+        let cancel_open_auction_bid_result = Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_open_auction_bid_result,
+            Error::<Test>::VideoDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn cancel_open_auction_bid_nft_is_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to cancel open auction bid for nft which is not issued yet
+        let cancel_open_auction_bid_result = Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_open_auction_bid_result,
+            Error::<Test>::NFTDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn cancel_open_auction_bid_not_in_auction_state() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to cancel open auction bid for nft which is not in auction state
+        let cancel_open_auction_bid_result = Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_open_auction_bid_result,
+            Error::<Test>::NotInAuctionState
+        );
+    })
+}
+
+#[test]
+fn cancel_open_auction_bid_is_not_open_auction_type() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Make an attempt to cancel open auction bid for nft which is not in open auction state
+        let cancel_open_auction_bid_result = Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_open_auction_bid_result,
+            Error::<Test>::IsNotOpenAuctionType
+        );
+    })
+}
+
+#[test]
+fn cancel_open_auction_bid_last_bid_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // Run to the block where bid lock duration expires
+        run_to_block(bid_lock_duration + 1);
+
+        // Make an attempt to cancel open auction bid if it does not exist
+        let cancel_open_auction_bid_result = Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_open_auction_bid_result,
+            Error::<Test>::LastBidDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn cancel_open_auction_bid_actor_is_not_a_last_bidder() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Run to the block where bid lock duration expires
+        run_to_block(bid_lock_duration + 1);
+
+        // Make an attempt to cancel open auction bid if actor is not a last bidder
+        let cancel_open_auction_bid_result = Content::cancel_open_auction_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            THIRD_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            cancel_open_auction_bid_result,
+            Error::<Test>::ActorIsNotALastBidder
+        );
+    })
+}

+ 444 - 0
runtime-modules/content/src/tests/nft/claim_won_english_auction.rs

@@ -0,0 +1,444 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn claim_won_english_auction() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Run to the block where auction expires
+        run_to_block(Content::max_auction_duration() + 1);
+
+        // Claim won english auction
+        assert_ok!(Content::claim_won_english_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure english auction successfully completed
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Idle,
+                ..
+            })
+        ));
+
+        let claim_won_english_auction_event = get_test_event(RawEvent::EnglishAuctionCompleted(
+            SECOND_MEMBER_ID,
+            video_id,
+        ));
+
+        // Last event checked
+        assert_event(
+            claim_won_english_auction_event,
+            number_of_events_before_call + 3,
+        );
+    })
+}
+
+#[test]
+fn claim_won_english_auction_cannot_be_completed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Make an attempt to claim won english auction if it did not expire yet
+        let claim_won_english_auction_result = Content::claim_won_english_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            claim_won_english_auction_result,
+            Error::<Test>::AuctionCannotBeCompleted
+        );
+    })
+}
+
+#[test]
+fn claim_won_english_auction_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Run to the block where auction expires
+        run_to_block(Content::max_auction_duration() + 1);
+
+        // Make an attempt to claim won english auction with wrong credentials
+        let claim_won_english_auction_result = Content::claim_won_english_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            UNKNOWN_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            claim_won_english_auction_result,
+            Error::<Test>::MemberAuthFailed
+        );
+    })
+}
+
+#[test]
+fn claim_won_english_auction_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to claim won english auction which corresponding video does not exist
+        let claim_won_english_auction_result = Content::claim_won_english_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            claim_won_english_auction_result,
+            Error::<Test>::VideoDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn claim_won_english_auction_nft_is_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to claim won english auction for nft which is not issued yet
+        let claim_won_english_auction_result = Content::claim_won_english_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            claim_won_english_auction_result,
+            Error::<Test>::NFTDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn claim_won_english_auction_not_in_auction_state() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to claim won english auction for nft which is not in auction state
+        let claim_won_english_auction_result = Content::claim_won_english_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            claim_won_english_auction_result,
+            Error::<Test>::NotInAuctionState
+        );
+    })
+}
+
+#[test]
+fn claim_won_english_auction_is_not_english_auction_type() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Make an attempt to claim won english auction for nft which is not in english auction state
+        let claim_won_english_auction_result = Content::claim_won_english_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            claim_won_english_auction_result,
+            Error::<Test>::IsNotEnglishAuctionType
+        );
+    })
+}
+
+#[test]
+fn claim_won_english_auction_last_bid_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // Run to the block where auction expires
+        run_to_block(Content::max_auction_duration() + 1);
+
+        // Make an attempt to claim won english auction if last bid does not exist
+        let claim_won_english_auction_result = Content::claim_won_english_auction(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            claim_won_english_auction_result,
+            Error::<Test>::LastBidDoesNotExist
+        );
+    })
+}

+ 198 - 0
runtime-modules/content/src/tests/nft/issue_nft.rs

@@ -0,0 +1,198 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn issue_nft() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Video does not have an nft
+        assert_eq!(None, Content::video_by_id(video_id).nft_status);
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft created succesfully
+        let nft_status = Some(OwnedNFT::new(NFTOwner::ChannelOwner, None));
+        assert_eq!(nft_status, Content::video_by_id(video_id).nft_status);
+
+        let nft_issued_event = get_test_event(RawEvent::NftIssued(
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None,
+        ));
+
+        // Last event checked
+        assert_event(nft_issued_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn issue_nft_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to issue nft for non existent video
+        let issue_nft_result = Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None,
+        );
+
+        // Failure checked
+        assert_err!(issue_nft_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn issue_nft_already_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to issue nft once again for the same video
+        let issue_nft_result = Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None,
+        );
+
+        // Failure checked
+        assert_err!(issue_nft_result, Error::<Test>::NFTAlreadyExists);
+    })
+}
+
+#[test]
+fn issue_nft_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to issue nft with wrong credentials
+        let issue_nft_result = Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(UNKNOWN_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None,
+        );
+
+        // Failure checked
+        assert_err!(issue_nft_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn issue_nft_actor_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to issue nft if actor is not authorized
+        let issue_nft_result = Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None,
+        );
+
+        // Failure checked
+        assert_err!(issue_nft_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn issue_nft_royalty_bounds_violated() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to issue nft with wrong credentials
+        let issue_nft_result = Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            Some(Perbill::one()),
+            b"metablob".to_vec(),
+            None,
+        );
+
+        // Failure checked
+        assert_err!(issue_nft_result, Error::<Test>::RoyaltyUpperBoundExceeded);
+
+        // Make an attempt to issue nft with wrong credentials
+        let issue_nft_result = Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            Some(Perbill::from_perthousand(1)),
+            b"metablob".to_vec(),
+            None,
+        );
+
+        // Failure checked
+        assert_err!(issue_nft_result, Error::<Test>::RoyaltyLowerBoundExceeded);
+    })
+}

+ 660 - 0
runtime-modules/content/src/tests/nft/make_bid.rs

@@ -0,0 +1,660 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+use std::iter::FromIterator;
+
+#[test]
+fn make_bid() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = get_open_auction_params();
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Runtime tested state after call
+
+        let mut auction: Auction<Test> = AuctionRecord::new(auction_params.clone());
+        let current_block = <frame_system::Module<Test>>::block_number();
+
+        if auction_params.starts_at.is_none() {
+            auction.starts_at = current_block;
+        }
+
+        let (auction, _, _) =
+            auction.make_bid(SECOND_MEMBER_ID, SECOND_MEMBER_ORIGIN, bid, current_block);
+
+        // Ensure nft status changed to given Auction
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Auction(auction_with_bid,),
+                ..
+            }) if auction == auction_with_bid
+        ));
+
+        let auction_bid_made_event = get_test_event(RawEvent::AuctionBidMade(
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+            false,
+        ));
+
+        // Last event checked
+        assert_event(auction_bid_made_event, number_of_events_before_call + 4);
+    })
+}
+
+#[test]
+fn make_bid_completes_auction() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let buy_now_price = Content::min_starting_price();
+
+        let auction_params = AuctionParams {
+            starting_price: buy_now_price,
+            buy_now_price: Some(2 * buy_now_price),
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // deposit initial balance
+        let bid = 2 * Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft status changed to given Auction
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Idle,
+                owner,
+                ..
+            }) if owner == NFTOwner::Member(SECOND_MEMBER_ID)
+        ));
+
+        let nft_auction_started_event = get_test_event(RawEvent::BidMadeCompletingAuction(
+            SECOND_MEMBER_ID,
+            video_id,
+        ));
+
+        // Last event checked
+        assert_event(nft_auction_started_event, number_of_events_before_call + 5);
+    })
+}
+
+#[test]
+fn make_bid_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = get_open_auction_params();
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make an attempt to make auction bid providing wrong credentials
+        let make_bid_result = Content::make_bid(
+            Origin::signed(UNKNOWN_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        );
+
+        // Failure checked
+        assert_err!(make_bid_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn make_bid_insufficient_balance() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = get_open_auction_params();
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        let bid = Content::min_starting_price();
+
+        // Make an attempt to make auction bid if account has insufficient balance
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        );
+
+        // Failure checked
+        assert_err!(make_bid_result, Error::<Test>::InsufficientBalance);
+    })
+}
+
+#[test]
+fn make_bid_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make an attempt to make auction bid if corresponding video does not exist
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        );
+
+        // Failure checked
+        assert_err!(make_bid_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn make_bid_nft_is_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make an attempt to make auction bid if corresponding nft is not issued yet
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        );
+
+        // Failure checked
+        assert_err!(make_bid_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn make_bid_nft_is_not_in_auction_state() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make an attempt to make auction bid if corresponding nft is not in auction state
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        );
+
+        // Failure checked
+        assert_err!(make_bid_result, Error::<Test>::NotInAuctionState);
+    })
+}
+
+#[test]
+fn make_bid_nft_auction_expired() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::min_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // Run to the block when auction expires
+        run_to_block(Content::min_auction_duration() + 1);
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, 2 * bid);
+
+        // Make an attempt to make auction bid if corresponding english nft auction is already expired
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        );
+
+        // Failure checked
+        assert_err!(make_bid_result, Error::<Test>::NFTAuctionIsAlreadyExpired);
+    })
+}
+
+#[test]
+fn make_bid_nft_auction_is_not_started() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let starting_price = Content::min_starting_price();
+
+        let auction_params = AuctionParams {
+            starting_price,
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: Some(<frame_system::Module<Test>>::block_number() + 1),
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, starting_price);
+
+        // Make an attempt to make auction bid if auction is not started
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            starting_price,
+        );
+
+        // Failure checked
+        assert_err!(make_bid_result, Error::<Test>::AuctionDidNotStart);
+    })
+}
+
+#[test]
+fn make_bid_member_is_not_allowed_to_participate() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: Some(<frame_system::Module<Test>>::block_number() + 1),
+            whitelist: BTreeSet::from_iter(vec![THIRD_MEMBER_ID, FOURTH_MEMBER_ID].into_iter()),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // Run to the block when auction expires
+        run_to_block(Content::min_auction_duration() + 1);
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, 2 * bid);
+
+        // Make an attempt to make auction bid on auction with whitelist if member is not whitelisted
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        );
+
+        // Failure checked
+        assert_err!(
+            make_bid_result,
+            Error::<Test>::MemberIsNotAllowedToParticipate
+        );
+    })
+}
+
+#[test]
+fn make_bid_starting_price_constraint_violated() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make an attempt to make auction bid if bid amount provided is less then auction starting price
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        );
+
+        // Failure checked
+        assert_err!(
+            make_bid_result,
+            Error::<Test>::StartingPriceConstraintViolated
+        );
+    })
+}
+
+#[test]
+fn make_bid_bid_step_constraint_violated() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make a successfull bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        let new_bid = bid + Content::min_bid_step() - 1;
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, new_bid);
+
+        // Make an attempt to make auction bid if bid step constraint violated
+        let make_bid_result = Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            new_bid,
+        );
+
+        // Failure checked
+        assert_err!(make_bid_result, Error::<Test>::BidStepConstraintViolated);
+    })
+}

+ 222 - 0
runtime-modules/content/src/tests/nft/offer_nft.rs

@@ -0,0 +1,222 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn offer_nft() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft offered succesfully
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::InitiatedOfferToMember(
+                    SECOND_MEMBER_ID,
+                    None
+                ),
+                ..
+            })
+        ));
+
+        let offer_started_event = get_test_event(RawEvent::OfferStarted(
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Last event checked
+        assert_event(offer_started_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn offer_nft_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to offer nft which corresponding video does not exist
+        let offer_nft_result = Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        );
+
+        // Failure checked
+        assert_err!(offer_nft_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn offer_nft_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to offer nft which is not issued yet
+        let offer_nft_result = Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        );
+
+        // Failure checked
+        assert_err!(offer_nft_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn offer_nft_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to offer nft with wrong credentials
+        let offer_nft_result = Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(UNKNOWN_ID),
+            SECOND_MEMBER_ID,
+            None,
+        );
+
+        // Failure checked
+        assert_err!(offer_nft_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn offer_nft_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to offer nft if actor is not authorized
+        let offer_nft_result = Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(SECOND_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        );
+
+        // Failure checked
+        assert_err!(offer_nft_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn offer_nft_transactional_status_is_not_idle() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Make an attempt to offer nft when it is already offered
+        let offer_nft_result = Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        );
+
+        // Failure checked
+        assert_err!(offer_nft_result, Error::<Test>::NftIsNotIdle);
+    })
+}

+ 441 - 0
runtime-modules/content/src/tests/nft/pick_open_auction_winner.rs

@@ -0,0 +1,441 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn pick_open_auction_winner() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Pick open auction winner
+        assert_ok!(Content::pick_open_auction_winner(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure english auction successfully completed
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Idle,
+                ..
+            })
+        ));
+
+        let pick_open_auction_winner_event = get_test_event(RawEvent::OpenAuctionBidAccepted(
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        ));
+
+        // Last event checked
+        assert_event(
+            pick_open_auction_winner_event,
+            number_of_events_before_call + 3,
+        );
+    })
+}
+
+#[test]
+fn pick_open_auction_winner_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Run to the block where auction expires
+        run_to_block(Content::max_auction_duration() + 1);
+
+        // Make an attempt to pick open auction winner with wrong credentials
+        let pick_open_auction_winner_result = Content::pick_open_auction_winner(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            ContentActor::Member(UNKNOWN_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            pick_open_auction_winner_result,
+            Error::<Test>::MemberAuthFailed
+        );
+    })
+}
+
+#[test]
+fn pick_open_auction_winner_actor_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Run to the block where auction expires
+        run_to_block(Content::max_auction_duration() + 1);
+
+        // Make an attempt to pick open auction winner if actor is not authorized to do this
+        let pick_open_auction_winner_result = Content::pick_open_auction_winner(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            pick_open_auction_winner_result,
+            Error::<Test>::ActorNotAuthorized
+        );
+    })
+}
+
+#[test]
+fn pick_open_auction_winner_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to pick open auction winner which corresponding video does not exist
+        let pick_open_auction_winner_result = Content::pick_open_auction_winner(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            pick_open_auction_winner_result,
+            Error::<Test>::VideoDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn pick_open_auction_winner_nft_is_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to pick open auction winner for nft which is not issued yet
+        let pick_open_auction_winner_result = Content::pick_open_auction_winner(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            pick_open_auction_winner_result,
+            Error::<Test>::NFTDoesNotExist
+        );
+    })
+}
+
+#[test]
+fn pick_open_auction_winner_not_in_auction_state() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to pick open auction winner for nft which is not in auction state
+        let pick_open_auction_winner_result = Content::pick_open_auction_winner(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            pick_open_auction_winner_result,
+            Error::<Test>::NotInAuctionState
+        );
+    })
+}
+
+#[test]
+fn pick_open_auction_winner_is_not_open_auction_type() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // deposit initial balance
+        let bid = Content::min_starting_price();
+
+        let _ = balances::Module::<Test>::deposit_creating(&SECOND_MEMBER_ORIGIN, bid);
+
+        // Make nft auction bid
+        assert_ok!(Content::make_bid(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            SECOND_MEMBER_ID,
+            video_id,
+            bid,
+        ));
+
+        // Make an attempt to pick open auction winner for nft which is in english auction state
+        let pick_open_auction_winner_result = Content::pick_open_auction_winner(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            pick_open_auction_winner_result,
+            Error::<Test>::IsNotOpenAuctionType
+        );
+    })
+}
+
+#[test]
+fn pick_open_auction_winner_last_bid_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let bid_lock_duration = Content::min_bid_lock_duration();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails { bid_lock_duration }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // Run to the block where auction expires
+        run_to_block(Content::max_auction_duration() + 1);
+
+        // Make an attempt to pick open auction winner if last bid does not exist
+        let pick_open_auction_winner_result = Content::pick_open_auction_winner(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+        );
+
+        // Failure checked
+        assert_err!(
+            pick_open_auction_winner_result,
+            Error::<Test>::LastBidDoesNotExist
+        );
+    })
+}

+ 226 - 0
runtime-modules/content/src/tests/nft/sell_nft.rs

@@ -0,0 +1,226 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn sell_nft() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let price = 100;
+
+        // Sell nft
+        assert_ok!(Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft offer made succesfully
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::BuyNow(
+                    cost,
+                ),
+                ..
+            }) if price == cost
+        ));
+
+        let sell_order_made_event = get_test_event(RawEvent::NFTSellOrderMade(
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        ));
+
+        // Last event checked
+        assert_event(sell_order_made_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn sell_nft_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        let price = 100;
+
+        // Make an attempt to sell nft which corresponding video does not exist yet
+        let sell_nft_result = Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        );
+
+        // Failure checked
+        assert_err!(sell_nft_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn sell_nft_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        let price = 100;
+
+        // Make an attempt to sell nft which is not issued yet
+        let sell_nft_result = Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        );
+
+        // Failure checked
+        assert_err!(sell_nft_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn sell_nft_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        let price = 100;
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to sell nft with wrong credentials
+        let sell_nft_result = Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(UNKNOWN_ID),
+            price,
+        );
+
+        // Failure checked
+        assert_err!(sell_nft_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn sell_nft_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let price = 100;
+
+        // Make an attempt to sell nft if actor is not authorized
+        let sell_nft_result = Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(SECOND_MEMBER_ID),
+            price,
+        );
+
+        // Failure checked
+        assert_err!(sell_nft_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn sell_nft_transactional_status_is_not_idle() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        let price = 100;
+
+        // Make an attempt to sell nft when it is already offered
+        let sell_nft_result = Content::sell_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            price,
+        );
+
+        // Failure checked
+        assert_err!(sell_nft_result, Error::<Test>::NftIsNotIdle);
+    })
+}

+ 212 - 0
runtime-modules/content/src/tests/nft/sling_nft_back.rs

@@ -0,0 +1,212 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+#[test]
+fn sling_nft_back() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            Some(SECOND_MEMBER_ID),
+        ));
+
+        // Runtime tested state before call
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                owner: NFTOwner::Member(SECOND_MEMBER_ID),
+                ..
+            })
+        ));
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        // Sling nft back to the original artist
+        assert_ok!(Content::sling_nft_back(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(SECOND_MEMBER_ID),
+        ));
+
+        // Runtime tested state after call
+
+        // Ensure nft slinged back successfully
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                owner: NFTOwner::ChannelOwner,
+                ..
+            })
+        ));
+
+        let offer_started_event = get_test_event(RawEvent::NftSlingedBackToTheOriginalArtist(
+            video_id,
+            ContentActor::Member(SECOND_MEMBER_ID),
+        ));
+
+        // Last event checked
+        assert_event(offer_started_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn sling_nft_back_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        // Make an attempt to sling nft back which corresponding video does not exist
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn sling_nft_back_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Make an attempt to sling nft back which is not issued yet
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn sling_nft_back_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to sling nft back with wrong credentials
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(UNKNOWN_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn sling_nft_back_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to sling nft back if actor is not authorized
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(SECOND_MEMBER_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn sling_nft_back_transactional_status_is_not_idle() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        // Make an attempt to sling nft back when it is already offered
+        let sling_nft_back_result = Content::sling_nft_back(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+        );
+
+        // Failure checked
+        assert_err!(sling_nft_back_result, Error::<Test>::NftIsNotIdle);
+    })
+}

+ 676 - 0
runtime-modules/content/src/tests/nft/start_nft_auction.rs

@@ -0,0 +1,676 @@
+#![cfg(test)]
+
+use crate::tests::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+use std::iter::FromIterator;
+
+#[test]
+fn start_nft_auction() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Runtime tested state before call
+
+        // Events number before tested calls
+        let number_of_events_before_call = System::events().len();
+
+        let auction_params = get_open_auction_params();
+
+        // Start nft auction
+        assert_ok!(Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        ));
+
+        // Runtime tested state after call
+
+        let mut auction: Auction<Test> = AuctionRecord::new(auction_params.clone());
+
+        if auction_params.starts_at.is_none() {
+            auction.starts_at = <frame_system::Module<Test>>::block_number();
+        }
+
+        // Ensure nft status changed to given Auction
+        assert!(matches!(
+            Content::video_by_id(video_id).nft_status,
+            Some(OwnedNFT {
+                transactional_status: TransactionalStatus::Auction(created_auction,),
+                ..
+            }) if auction == created_auction
+        ));
+
+        let nft_auction_started_event = get_test_event(RawEvent::AuctionStarted(
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params,
+        ));
+
+        // Last event checked
+        assert_event(nft_auction_started_event, number_of_events_before_call + 1);
+    })
+}
+
+#[test]
+fn start_nft_auction_video_does_not_exist() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        let auction_params = get_open_auction_params();
+
+        // Make an attempt to start nft auction which corresponding video does not exist yet
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(start_nft_auction_result, Error::<Test>::VideoDoesNotExist);
+    })
+}
+
+#[test]
+fn start_nft_auction_not_issued() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        let auction_params = get_open_auction_params();
+
+        // Make an attempt to start nft auction for nft which is not issued yet
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(start_nft_auction_result, Error::<Test>::NFTDoesNotExist);
+    })
+}
+
+#[test]
+fn start_nft_auction_auth_failed() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = get_open_auction_params();
+
+        // Make an attempt to start nft auction with wrong credentials
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(UNKNOWN_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(start_nft_auction_result, Error::<Test>::MemberAuthFailed);
+    })
+}
+
+#[test]
+fn start_nft_auction_not_authorized() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        let auction_params = get_open_auction_params();
+
+        // Make an attempt to start nft auction if actor is not authorized
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(start_nft_auction_result, Error::<Test>::ActorNotAuthorized);
+    })
+}
+
+#[test]
+fn start_nft_auction_transactional_status_is_not_idle() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Offer nft
+        assert_ok!(Content::offer_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            video_id,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            SECOND_MEMBER_ID,
+            None,
+        ));
+
+        let auction_params = get_open_auction_params();
+
+        // Make an attempt to start nft auction if nft transaction status is not idle
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(start_nft_auction_result, Error::<Test>::NftIsNotIdle);
+    })
+}
+
+#[test]
+fn start_nft_auction_invalid_params() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+
+        let video_id = NextVideoId::<Test>::get();
+
+        create_simple_channel_and_video(FIRST_MEMBER_ORIGIN, FIRST_MEMBER_ID);
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to start nft auction if starting price provided is less then min starting price
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price() - 1,
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::StartingPriceLowerBoundExceeded
+        );
+
+        // Make an attempt to start nft auction if starting price provided is greater then max starting price
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price() + 1,
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::StartingPriceUpperBoundExceeded
+        );
+
+        // Make an attempt to start nft auction if minimal bid step provided is less then min allowed bid step
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::min_bid_step() - 1,
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::AuctionBidStepLowerBoundExceeded
+        );
+
+        // Make an attempt to start nft auction if minimal bid step provided is greater then max allowed bid step
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step() + 1,
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::AuctionBidStepUpperBoundExceeded
+        );
+
+        // Make an attempt to start open nft auction if minimal bid lock duration
+        // of auction provided is less then min allowed bid lock duration
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration() - 1,
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::BidLockDurationLowerBoundExceeded
+        );
+
+        // Make an attempt to start open nft auction if minimal bid lock duration
+        // of auction provided is greater then max allowed bid lock duration
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::max_bid_lock_duration() + 1,
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::BidLockDurationUpperBoundExceeded
+        );
+
+        // Make an attempt to start english nft auction if extension period
+        // of auction provided is less then min allowed extension period
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period() - 1,
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::ExtensionPeriodLowerBoundExceeded
+        );
+
+        // Make an attempt to start english nft auction if extension period
+        // of auction provided is greater then max allowed extension period
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::max_auction_extension_period() + 1,
+                auction_duration: Content::max_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::ExtensionPeriodUpperBoundExceeded
+        );
+
+        // Make an attempt to start english nft auction if auction duration
+        // of auction provided is less then min allowed auction duration
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::min_auction_extension_period(),
+                auction_duration: Content::min_auction_duration() - 1,
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::AuctionDurationLowerBoundExceeded
+        );
+
+        // Make an attempt to start english nft auction if auction duration
+        // of auction provided is greater then max allowed auction duration
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::max_auction_extension_period(),
+                auction_duration: Content::max_auction_duration() + 1,
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::AuctionDurationUpperBoundExceeded
+        );
+
+        // Make an attempt to start english nft auction if extension period
+        // of auction provided is greater auction duration
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::English(EnglishAuctionDetails {
+                extension_period: Content::max_auction_extension_period(),
+                auction_duration: Content::min_auction_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::ExtensionPeriodIsGreaterThenAuctionDuration
+        );
+
+        // Make an attempt to start nft auction if starts_at provided is less then now
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::min_bid_step(),
+            starts_at: Some(<frame_system::Module<Test>>::block_number() - 1),
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::StartsAtLowerBoundExceeded
+        );
+
+        // Make an attempt to start nft auction if starts_at provided is greater then now + auction_starts_at_max_delta
+        let auction_params = AuctionParams {
+            starting_price: Content::max_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: Some(
+                <frame_system::Module<Test>>::block_number()
+                    + Content::auction_starts_at_max_delta()
+                    + 1,
+            ),
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::StartsAtUpperBoundExceeded
+        );
+
+        // Make an attempt to start nft auction if auction related buy now is less then starting price
+        let buy_now_price = Content::min_starting_price();
+
+        let auction_params = AuctionParams {
+            starting_price: buy_now_price + 1,
+            buy_now_price: Some(buy_now_price),
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::new(),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::BuyNowIsLessThenStartingPrice
+        );
+
+        // Make an attempt to start nft auction if auction whitelist provided consists only 1 member
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist: BTreeSet::from_iter(vec![SECOND_MEMBER_ID].into_iter()),
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::WhitelistHasOnlyOneMember
+        );
+
+        // Make an attempt to start nft auction if length of auction whitelist provided exceeds max allowed length
+        let whitelist: BTreeSet<_> = (0..=Content::max_auction_whitelist_length())
+            .into_iter()
+            .map(|member| member as u64)
+            .collect();
+
+        let auction_params = AuctionParams {
+            starting_price: Content::min_starting_price(),
+            buy_now_price: None,
+            auction_type: AuctionType::Open(OpenAuctionDetails {
+                bid_lock_duration: Content::min_bid_lock_duration(),
+            }),
+            minimal_bid_step: Content::max_bid_step(),
+            starts_at: None,
+            whitelist,
+        };
+
+        let start_nft_auction_result = Content::start_nft_auction(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            auction_params.clone(),
+        );
+
+        // Failure checked
+        assert_err!(
+            start_nft_auction_result,
+            Error::<Test>::MaxAuctionWhiteListLengthUpperBoundExceeded
+        );
+    })
+}

+ 42 - 5
runtime-modules/content/src/tests/videos.rs

@@ -6,16 +6,53 @@ use crate::*;
 use frame_support::{assert_err, assert_ok};
 
 #[test]
-fn curators_can_censor_videos() {
+fn delete_video_nft_is_issued() {
     with_default_mock_builder(|| {
         // Run to block one to see emitted events
         run_to_block(1);
+        let channel_id = create_member_channel();
 
-        create_initial_storage_buckets_helper();
-        increase_account_balance_helper(DEFAULT_MEMBER_ACCOUNT_ID, INITIAL_BALANCE);
-        create_default_member_owned_channel();
+        let video_id = Content::next_video_id();
 
-        let channel_id = NextChannelId::<Test>::get() - 1;
+        // Create a video
+        assert_ok!(Content::create_video(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id,
+            VideoCreationParametersRecord {
+                assets: NewAssets::<Test>::Urls(vec![vec![b"https://somewhere.com/".to_vec()]]),
+                meta: b"metablob".to_vec(),
+            }
+        ));
+
+        // Issue nft
+        assert_ok!(Content::issue_nft(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            video_id,
+            None,
+            b"metablob".to_vec(),
+            None
+        ));
+
+        // Make an attempt to delete a video, which has an nft issued already.
+        assert_err!(
+            Content::delete_video(
+                Origin::signed(FIRST_MEMBER_ORIGIN),
+                ContentActor::Member(FIRST_MEMBER_ID),
+                video_id
+            ),
+            Error::<Test>::NFTAlreadyExists
+        );
+    })
+}
+
+#[test]
+fn curators_can_censor_videos() {
+    with_default_mock_builder(|| {
+        // Run to block one to see emitted events
+        run_to_block(1);
+        let channel_id = create_member_channel();
 
         let video_id = Content::next_video_id();
         assert_ok!(Content::create_video(

+ 431 - 0
runtime-modules/content/src/types.rs

@@ -0,0 +1,431 @@
+use crate::*;
+
+/// Specifies how a new asset will be provided on creating and updating
+/// Channels, Videos, Series and Person
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum NewAssetsRecord<Balance> {
+    /// Upload to the storage frame_system
+    Upload(CreationUploadParameters<Balance>),
+    /// Multiple url strings pointing at an asset
+    Urls(Vec<AssetUrls>),
+}
+
+pub type NewAssets<T> = NewAssetsRecord<<T as balances::Trait>::Balance>;
+
+/// The owner of a channel, is the authorized "actor" that can update
+/// or delete or transfer a channel and its contents.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum ChannelOwner<MemberId, CuratorGroupId> {
+    /// A Member owns the channel
+    Member(MemberId),
+    /// A specific curation group owns the channel
+    CuratorGroup(CuratorGroupId),
+}
+
+// simplification type
+pub(crate) type ActorToChannelOwnerResult<T> = Result<
+    ChannelOwner<
+        <T as membership::Trait>::MemberId,
+        <T as ContentActorAuthenticator>::CuratorGroupId,
+    >,
+    Error<T>,
+>;
+
+// Default trait implemented only because its used in a Channel which needs to implement a Default trait
+// since it is a StorageValue.
+impl<MemberId: Default, CuratorGroupId> Default for ChannelOwner<MemberId, CuratorGroupId> {
+    fn default() -> Self {
+        ChannelOwner::Member(MemberId::default())
+    }
+}
+
+/// A category which channels can belong to.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelCategory {
+    // No runtime information is currently stored for a Category.
+}
+
+/// Information on the category being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelCategoryCreationParameters {
+    /// Metadata for the category.
+    meta: Vec<u8>,
+}
+
+/// Information on the category being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelCategoryUpdateParameters {
+    // as this is the only field it is not an Option
+    /// Metadata update for the category.
+    new_meta: Vec<u8>,
+}
+
+/// Type representing an owned channel which videos, playlists, and series can belong to.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelRecord<MemberId, CuratorGroupId, AccountId> {
+    /// The owner of a channel
+    pub owner: ChannelOwner<MemberId, CuratorGroupId>,
+    /// The videos under this channel
+    pub num_videos: u64,
+    /// If curators have censored this channel or not
+    pub is_censored: bool,
+    /// Reward account where revenue is sent if set.
+    pub reward_account: Option<AccountId>,
+    /// Account for withdrawing deletion prize funds
+    pub deletion_prize_source_account_id: AccountId,
+    /// Number of asset held in storage
+    pub num_assets: u64,
+}
+
+impl<MemberId, CuratorGroupId, AccountId> ChannelRecord<MemberId, CuratorGroupId, AccountId> {
+    /// Ensure censorship status have been changed
+    pub fn ensure_censorship_status_changed<T: Trait>(&self, is_censored: bool) -> DispatchResult {
+        ensure!(
+            self.is_censored != is_censored,
+            Error::<T>::ChannelCensorshipStatusDidNotChange
+        );
+        Ok(())
+    }
+}
+
+// Channel alias type for simplification.
+pub type Channel<T> = ChannelRecord<
+    <T as membership::Trait>::MemberId,
+    <T as ContentActorAuthenticator>::CuratorGroupId,
+    <T as frame_system::Trait>::AccountId,
+>;
+
+/// A request to buy a channel by a new ChannelOwner.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelOwnershipTransferRequestRecord<
+    ChannelId,
+    MemberId,
+    CuratorGroupId,
+    Balance,
+    AccountId,
+> {
+    channel_id: ChannelId,
+    new_owner: ChannelOwner<MemberId, CuratorGroupId>,
+    payment: Balance,
+    new_reward_account: Option<AccountId>,
+}
+
+// ChannelOwnershipTransferRequest type alias for simplification.
+pub type ChannelOwnershipTransferRequest<T> = ChannelOwnershipTransferRequestRecord<
+    <T as storage::Trait>::ChannelId,
+    <T as membership::Trait>::MemberId,
+    <T as ContentActorAuthenticator>::CuratorGroupId,
+    BalanceOf<T>,
+    <T as frame_system::Trait>::AccountId,
+>;
+
+/// Information about channel being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelCreationParametersRecord<NewAssets, AccountId> {
+    /// Asset collection for the channel, referenced by metadata
+    pub assets: NewAssets,
+    /// Metadata about the channel.
+    pub meta: Vec<u8>,
+    /// optional reward account
+    pub reward_account: Option<AccountId>,
+}
+
+pub type ChannelCreationParameters<T> =
+    ChannelCreationParametersRecord<NewAssets<T>, <T as frame_system::Trait>::AccountId>;
+
+/// Information about channel being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct ChannelUpdateParametersRecord<NewAssets, AccountId> {
+    /// Asset collection for the channel, referenced by metadata    
+    pub assets: Option<NewAssets>,
+    /// If set, metadata update for the channel.
+    pub new_meta: Option<Vec<u8>>,
+    /// If set, updates the reward account of the channel
+    pub reward_account: Option<Option<AccountId>>,
+}
+
+pub type ChannelUpdateParameters<T> =
+    ChannelUpdateParametersRecord<NewAssets<T>, <T as frame_system::Trait>::AccountId>;
+
+/// Information about the video category being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoCategoryUpdateParameters {
+    // Because it is the only field it is not an Option
+    /// Metadata update for the video category.
+    pub new_meta: Vec<u8>,
+}
+
+/// Information regarding the content being uploaded
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub struct CreationUploadParameters<Balance> {
+    /// Data object parameters.
+    pub object_creation_list: Vec<DataObjectCreationParameters>,
+
+    /// Expected data size fee value for this extrinsic call.
+    pub expected_data_size_fee: Balance,
+}
+
+/// Information about the video being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub struct VideoCreationParametersRecord<NewAssets> {
+    /// Asset collection for the video
+    pub assets: NewAssets,
+    /// Metadata for the video.
+    pub meta: Vec<u8>,
+}
+
+pub type VideoCreationParameters<T> = VideoCreationParametersRecord<NewAssets<T>>;
+
+/// Information about the video being updated
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoUpdateParametersRecord<NewAssets> {
+    /// Assets referenced by metadata
+    pub assets: Option<NewAssets>,
+    /// If set, metadata update for the video.
+    pub new_meta: Option<Vec<u8>>,
+}
+
+pub type VideoUpdateParameters<T> = VideoUpdateParametersRecord<NewAssets<T>>;
+
+/// Information about the plyalist being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct PlaylistCreationParameters {
+    /// Metadata about the playlist.
+    pub meta: Vec<u8>,
+}
+
+/// Information about the playlist being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct PlaylistUpdateParameters {
+    // It is the only field so its not an Option
+    /// Metadata update for the playlist.
+    pub new_meta: Vec<u8>,
+}
+
+/// A playlist is an ordered collection of videos.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Playlist<ChannelId> {
+    /// The channel the playlist belongs to.
+    in_channel: ChannelId,
+}
+
+/// Information about the episode being created or updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum EpisodeParameters<VideoId, NewAssets> {
+    /// A new video is being added as the episode.
+    NewVideo(VideoCreationParametersRecord<NewAssets>),
+    /// An existing video is being made into an episode.
+    ExistingVideo(VideoId),
+}
+
+/// Information about the season being created or updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct SeasonParameters<VideoId, NewAssets> {
+    /// Season assets referenced by metadata
+    pub assets: Option<NewAssets>,
+    // ?? It might just be more straighforward to always provide full list of episodes at cost of larger tx.
+    /// If set, updates the episodes of a season. Extends the number of episodes in a season
+    /// when length of new_episodes is greater than previously set. Last elements must all be
+    /// 'Some' in that case.
+    /// Will truncate existing season when length of new_episodes is less than previously set.
+    episodes: Option<Vec<Option<EpisodeParameters<VideoId, NewAssets>>>>,
+
+    pub meta: Option<Vec<u8>>,
+}
+
+/// Information about the series being created or updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct SeriesParameters<VideoId, NewAssets> {
+    /// Series assets referenced by metadata
+    pub assets: Option<NewAssets>,
+    // ?? It might just be more straighforward to always provide full list of seasons at cost of larger tx.
+    /// If set, updates the seasons of a series. Extend a series when length of seasons is
+    /// greater than previoulsy set. Last elements must all be 'Some' in that case.
+    /// Will truncate existing series when length of seasons is less than previously set.
+    seasons: Option<Vec<Option<SeasonParameters<VideoId, NewAssets>>>>,
+    meta: Option<Vec<u8>>,
+}
+
+/// A season is an ordered list of videos (episodes).
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Season<VideoId> {
+    episodes: Vec<VideoId>,
+}
+
+/// A series is an ordered list of seasons that belongs to a channel.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Series<ChannelId, VideoId> {
+    in_channel: ChannelId,
+    seasons: Vec<Season<VideoId>>,
+}
+
+/// The actor the caller/origin is trying to act as for Person creation and update and delete calls.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum PersonActor<MemberId, CuratorId> {
+    Member(MemberId),
+    Curator(CuratorId),
+}
+
+/// The authorized actor that may update or delete a Person.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum PersonController<MemberId> {
+    /// Member controls the person
+    Member(MemberId),
+    /// Any curator controls the person
+    Curators,
+}
+
+/// Default trait implemented only because its used in Person which needs to implement a Default trait
+/// since it is a StorageValue.
+impl<MemberId: Default> Default for PersonController<MemberId> {
+    fn default() -> Self {
+        PersonController::Member(MemberId::default())
+    }
+}
+
+/// Information for Person being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub struct PersonCreationParameters<NewAssets> {
+    /// Assets referenced by metadata
+    pub assets: NewAssets,
+    /// Metadata for person.
+    meta: Vec<u8>,
+}
+
+/// Information for Persion being updated.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct PersonUpdateParameters<NewAssets> {
+    /// Assets referenced by metadata
+    pub assets: Option<NewAssets>,
+    /// Metadata to update person.
+    new_meta: Option<Vec<u8>>,
+}
+
+/// A Person represents a real person that may be associated with a video.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct Person<MemberId> {
+    /// Who can update or delete this person.
+    pub controlled_by: PersonController<MemberId>,
+}
+
+/// A category that videos can belong to.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoCategory {
+    // No runtime information is currently stored for a Category.
+}
+
+/// Information about the video category being created.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoCategoryCreationParameters {
+    /// Metadata about the video category.
+    pub meta: Vec<u8>,
+}
+
+/// A video which belongs to a channel. A video may be part of a series or playlist.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct VideoRecord<
+    ChannelId,
+    SeriesId,
+    DataObjectId: Ord,
+    BlockNumber: BaseArithmetic + Copy + Default,
+    MemberId: Default + Copy + Ord,
+    AccountId: Default + Clone + Ord,
+    Balance: Default + Clone + BaseArithmetic,
+> {
+    pub in_channel: ChannelId,
+    // keep track of which season the video is in if it is an 'episode'
+    // - prevent removing a video if it is in a season (because order is important)
+    pub in_series: Option<SeriesId>,
+    /// Whether the curators have censored the video or not.
+    pub is_censored: bool,
+    /// storage parameters used during deletion
+    pub maybe_data_objects_id_set: Option<BTreeSet<DataObjectId>>,
+    /// Whether nft for this video have been issued.
+    pub nft_status: Option<OwnedNFT<BlockNumber, MemberId, AccountId, Balance>>,
+}
+
+impl<
+        ChannelId: Clone,
+        SeriesId: Clone,
+        DataObjectId: Ord,
+        BlockNumber: BaseArithmetic + Copy + Default,
+        MemberId: Default + Copy + PartialEq + Ord,
+        AccountId: Default + Clone + PartialEq + Ord,
+        Balance: Clone + Default + BaseArithmetic,
+    > VideoRecord<ChannelId, SeriesId, DataObjectId, BlockNumber, MemberId, AccountId, Balance>
+{
+    /// Ensure nft is not issued
+    pub fn ensure_nft_is_not_issued<T: Trait>(&self) -> DispatchResult {
+        ensure!(self.nft_status.is_none(), Error::<T>::NFTAlreadyExists);
+        Ok(())
+    }
+
+    /// Ensure nft is issued
+    pub fn ensure_nft_is_issued<T: Trait>(
+        &self,
+    ) -> Result<OwnedNFT<BlockNumber, MemberId, AccountId, Balance>, Error<T>> {
+        if let Some(owned_nft) = &self.nft_status {
+            Ok(owned_nft.to_owned())
+        } else {
+            Err(Error::<T>::NFTDoesNotExist)
+        }
+    }
+
+    /// Set video nft status
+    pub fn set_nft_status(
+        mut self,
+        nft: OwnedNFT<BlockNumber, MemberId, AccountId, Balance>,
+    ) -> Self {
+        self.nft_status = Some(nft);
+        self
+    }
+
+    /// Ensure censorship status have been changed
+    pub fn ensure_censorship_status_changed<T: Trait>(&self, is_censored: bool) -> DispatchResult {
+        ensure!(
+            self.is_censored != is_censored,
+            Error::<T>::VideoCensorshipStatusDidNotChange
+        );
+        Ok(())
+    }
+}
+
+/// Video alias type for simplification.
+pub type Video<T> = VideoRecord<
+    <T as storage::Trait>::ChannelId,
+    <T as Trait>::SeriesId,
+    <T as storage::Trait>::DataObjectId,
+    <T as frame_system::Trait>::BlockNumber,
+    MemberId<T>,
+    <T as frame_system::Trait>::AccountId,
+    BalanceOf<T>,
+>;

+ 5 - 0
runtime-modules/governance/src/mock.rs

@@ -61,11 +61,13 @@ impl pallet_timestamp::Trait for Test {
     type MinimumPeriod = MinimumPeriod;
     type WeightInfo = ();
 }
+
 impl council::Trait for Test {
     type Event = ();
 
     type CouncilTermEnded = (Election,);
 }
+
 impl election::Trait for Test {
     type Event = ();
 
@@ -87,15 +89,18 @@ impl membership::Trait for Test {
     type PaidTermId = u32;
     type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
+
 impl minting::Trait for Test {
     type Currency = Balances;
     type MintId = u64;
 }
+
 impl recurringrewards::Trait for Test {
     type PayoutStatusHandler = ();
     type RecipientId = u64;
     type RewardRelationshipId = u64;
 }
+
 parameter_types! {
     pub const ExistentialDeposit: u32 = 0;
 }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 16 - 0
types/augment-codec/all.ts


+ 2857 - 0
types/augment-codec/augment-api-errors.ts

@@ -0,0 +1,2857 @@
+// Auto-generated via `yarn polkadot-types-from-chain`, do not edit
+/* eslint-disable */
+
+import type { ApiTypes } from '@polkadot/api/types';
+
+declare module '@polkadot/api/types/errors' {
+  export interface AugmentedErrors<ApiType> {
+    authorship: {
+      /**
+       * The uncle is genesis.
+       **/
+      GenesisUncle: AugmentedError<ApiType>;
+      /**
+       * The uncle parent not in the chain.
+       **/
+      InvalidUncleParent: AugmentedError<ApiType>;
+      /**
+       * The uncle isn't recent enough to be included.
+       **/
+      OldUncle: AugmentedError<ApiType>;
+      /**
+       * The uncle is too high in chain.
+       **/
+      TooHighUncle: AugmentedError<ApiType>;
+      /**
+       * Too many uncles.
+       **/
+      TooManyUncles: AugmentedError<ApiType>;
+      /**
+       * The uncle is already included.
+       **/
+      UncleAlreadyIncluded: AugmentedError<ApiType>;
+      /**
+       * Uncles already set in the block.
+       **/
+      UnclesAlreadySet: AugmentedError<ApiType>;
+    };
+    balances: {
+      /**
+       * Beneficiary account must pre-exist
+       **/
+      DeadAccount: AugmentedError<ApiType>;
+      /**
+       * Value too low to create account due to existential deposit
+       **/
+      ExistentialDeposit: AugmentedError<ApiType>;
+      /**
+       * A vesting schedule already exists for this account
+       **/
+      ExistingVestingSchedule: AugmentedError<ApiType>;
+      /**
+       * Balance too low to send value
+       **/
+      InsufficientBalance: AugmentedError<ApiType>;
+      /**
+       * Transfer/payment would kill account
+       **/
+      KeepAlive: AugmentedError<ApiType>;
+      /**
+       * Account liquidity restrictions prevent withdrawal
+       **/
+      LiquidityRestrictions: AugmentedError<ApiType>;
+      /**
+       * Got an overflow after adding
+       **/
+      Overflow: AugmentedError<ApiType>;
+      /**
+       * Vesting balance too high to send value
+       **/
+      VestingBalance: AugmentedError<ApiType>;
+    };
+    content: {
+      /**
+       * Operation cannot be perfomed with this Actor
+       **/
+      ActorNotAuthorized: AugmentedError<ApiType>;
+      /**
+       * Expected root or signed origin
+       **/
+      BadOrigin: AugmentedError<ApiType>;
+      /**
+       * Curators can only censor non-curator group owned channels
+       **/
+      CannotCensoreCuratorGroupOwnedChannels: AugmentedError<ApiType>;
+      /**
+       * A Channel or Video Category does not exist.
+       **/
+      CategoryDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Channel does not exist
+       **/
+      ChannelDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Curator authentication failed
+       **/
+      CuratorAuthFailed: AugmentedError<ApiType>;
+      /**
+       * Given curator group does not exist
+       **/
+      CuratorGroupDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Curator group is not active
+       **/
+      CuratorGroupIsNotActive: AugmentedError<ApiType>;
+      /**
+       * Curator id is not a worker id in content working group
+       **/
+      CuratorIdInvalid: AugmentedError<ApiType>;
+      /**
+       * Curator under provided curator id is already a member of curaror group under given id
+       **/
+      CuratorIsAlreadyAMemberOfGivenCuratorGroup: AugmentedError<ApiType>;
+      /**
+       * Curator under provided curator id is not a member of curaror group under given id
+       **/
+      CuratorIsNotAMemberOfGivenCuratorGroup: AugmentedError<ApiType>;
+      /**
+       * Max number of curators per group limit reached
+       **/
+      CuratorsPerGroupLimitReached: AugmentedError<ApiType>;
+      /**
+       * Feature Not Implemented
+       **/
+      FeatureNotImplemented: AugmentedError<ApiType>;
+      /**
+       * Lead authentication failed
+       **/
+      LeadAuthFailed: AugmentedError<ApiType>;
+      /**
+       * Member authentication failed
+       **/
+      MemberAuthFailed: AugmentedError<ApiType>;
+      /**
+       * Video does not exist
+       **/
+      VideoDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Video in season can`t be removed (because order is important)
+       **/
+      VideoInSeason: AugmentedError<ApiType>;
+    };
+    contentWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    distributionWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    finalityTracker: {
+      /**
+       * Final hint must be updated only once in the block
+       **/
+      AlreadyUpdated: AugmentedError<ApiType>;
+      /**
+       * Finalized height above block number
+       **/
+      BadHint: AugmentedError<ApiType>;
+    };
+    gatewayWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    grandpa: {
+      /**
+       * Attempt to signal GRANDPA change with one already pending.
+       **/
+      ChangePending: AugmentedError<ApiType>;
+      /**
+       * A given equivocation report is valid but already previously reported.
+       **/
+      DuplicateOffenceReport: AugmentedError<ApiType>;
+      /**
+       * An equivocation proof provided as part of an equivocation report is invalid.
+       **/
+      InvalidEquivocationProof: AugmentedError<ApiType>;
+      /**
+       * A key ownership proof provided as part of an equivocation report is invalid.
+       **/
+      InvalidKeyOwnershipProof: AugmentedError<ApiType>;
+      /**
+       * Attempt to signal GRANDPA pause when the authority set isn't live
+       * (either paused or already pending pause).
+       **/
+      PauseFailed: AugmentedError<ApiType>;
+      /**
+       * Attempt to signal GRANDPA resume when the authority set isn't paused
+       * (either live or already pending resume).
+       **/
+      ResumeFailed: AugmentedError<ApiType>;
+      /**
+       * Cannot signal forced change so soon after last.
+       **/
+      TooSoon: AugmentedError<ApiType>;
+    };
+    imOnline: {
+      /**
+       * Duplicated heartbeat.
+       **/
+      DuplicatedHeartbeat: AugmentedError<ApiType>;
+      /**
+       * Non existent public key.
+       **/
+      InvalidKey: AugmentedError<ApiType>;
+    };
+    members: {
+      /**
+       * Avatar url is too long.
+       **/
+      AvatarUriTooLong: AugmentedError<ApiType>;
+      /**
+       * Controller account required.
+       **/
+      ControllerAccountRequired: AugmentedError<ApiType>;
+      /**
+       * Handle already registered.
+       **/
+      HandleAlreadyRegistered: AugmentedError<ApiType>;
+      /**
+       * Handle must be provided during registration.
+       **/
+      HandleMustBeProvidedDuringRegistration: AugmentedError<ApiType>;
+      /**
+       * Handle too long.
+       **/
+      HandleTooLong: AugmentedError<ApiType>;
+      /**
+       * Handle too short.
+       **/
+      HandleTooShort: AugmentedError<ApiType>;
+      /**
+       * Screening authority attempting to endow more that maximum allowed.
+       **/
+      InitialBalanceExceedsMaxInitialBalance: AugmentedError<ApiType>;
+      /**
+       * Member profile not found (invalid member id).
+       **/
+      MemberProfileNotFound: AugmentedError<ApiType>;
+      /**
+       * New memberships not allowed.
+       **/
+      NewMembershipsNotAllowed: AugmentedError<ApiType>;
+      /**
+       * A screening authority is not defined.
+       **/
+      NoScreeningAuthorityDefined: AugmentedError<ApiType>;
+      /**
+       * Not enough balance to buy membership.
+       **/
+      NotEnoughBalanceToBuyMembership: AugmentedError<ApiType>;
+      /**
+       * Origin is not the screeing authority.
+       **/
+      NotScreeningAuthority: AugmentedError<ApiType>;
+      /**
+       * Only new accounts can be used for screened members.
+       **/
+      OnlyNewAccountsCanBeUsedForScreenedMembers: AugmentedError<ApiType>;
+      /**
+       * Paid term id not active.
+       **/
+      PaidTermIdNotActive: AugmentedError<ApiType>;
+      /**
+       * Paid term id not found.
+       **/
+      PaidTermIdNotFound: AugmentedError<ApiType>;
+      /**
+       * Root account required.
+       **/
+      RootAccountRequired: AugmentedError<ApiType>;
+      /**
+       * Invalid origin.
+       **/
+      UnsignedOrigin: AugmentedError<ApiType>;
+    };
+    operationsWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    proposalsCodex: {
+      /**
+       * Invalid 'decrease stake proposal' parameter - cannot decrease by zero balance.
+       **/
+      DecreasingStakeIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid content working group mint capacity parameter
+       **/
+      InvalidContentWorkingGroupMintCapacity: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - announcing_period
+       **/
+      InvalidCouncilElectionParameterAnnouncingPeriod: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - candidacy-limit
+       **/
+      InvalidCouncilElectionParameterCandidacyLimit: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - council_size
+       **/
+      InvalidCouncilElectionParameterCouncilSize: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - min_council_stake
+       **/
+      InvalidCouncilElectionParameterMinCouncilStake: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - min-voting_stake
+       **/
+      InvalidCouncilElectionParameterMinVotingStake: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - new_term_duration
+       **/
+      InvalidCouncilElectionParameterNewTermDuration: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - revealing_period
+       **/
+      InvalidCouncilElectionParameterRevealingPeriod: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - voting_period
+       **/
+      InvalidCouncilElectionParameterVotingPeriod: AugmentedError<ApiType>;
+      /**
+       * Invalid 'set lead proposal' parameter - proposed lead cannot be a councilor
+       **/
+      InvalidSetLeadParameterCannotBeCouncilor: AugmentedError<ApiType>;
+      /**
+       * Invalid balance value for the spending proposal
+       **/
+      InvalidSpendingProposalBalance: AugmentedError<ApiType>;
+      /**
+       * Invalid validator count for the 'set validator count' proposal
+       **/
+      InvalidValidatorCount: AugmentedError<ApiType>;
+      /**
+       * Invalid working group mint capacity parameter
+       **/
+      InvalidWorkingGroupMintCapacity: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Provided WASM code for the runtime upgrade proposal is empty
+       **/
+      RuntimeProposalIsEmpty: AugmentedError<ApiType>;
+      /**
+       * The size of the provided WASM code for the runtime upgrade proposal exceeded the limit
+       **/
+      RuntimeProposalSizeExceeded: AugmentedError<ApiType>;
+      /**
+       * Invalid 'slash stake proposal' parameter - cannot slash by zero balance.
+       **/
+      SlashingStakeIsZero: AugmentedError<ApiType>;
+      /**
+       * Provided text for text proposal is empty
+       **/
+      TextProposalIsEmpty: AugmentedError<ApiType>;
+      /**
+       * The size of the provided text for text proposal exceeded the limit
+       **/
+      TextProposalSizeExceeded: AugmentedError<ApiType>;
+    };
+    proposalsDiscussion: {
+      /**
+       * Post cannot be empty
+       **/
+      EmptyPostProvided: AugmentedError<ApiType>;
+      /**
+       * Discussion cannot have an empty title
+       **/
+      EmptyTitleProvided: AugmentedError<ApiType>;
+      /**
+       * Max number of threads by same author in a row limit exceeded
+       **/
+      MaxThreadInARowLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Author should match the post creator
+       **/
+      NotAuthor: AugmentedError<ApiType>;
+      /**
+       * Post doesn't exist
+       **/
+      PostDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Post edition limit reached
+       **/
+      PostEditionNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Post is too long
+       **/
+      PostIsTooLong: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Thread doesn't exist
+       **/
+      ThreadDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Title is too long
+       **/
+      TitleIsTooLong: AugmentedError<ApiType>;
+    };
+    proposalsEngine: {
+      /**
+       * The proposal have been already voted on
+       **/
+      AlreadyVoted: AugmentedError<ApiType>;
+      /**
+       * Description is too long
+       **/
+      DescriptionIsTooLong: AugmentedError<ApiType>;
+      /**
+       * Proposal cannot have an empty body
+       **/
+      EmptyDescriptionProvided: AugmentedError<ApiType>;
+      /**
+       * Stake cannot be empty with this proposal
+       **/
+      EmptyStake: AugmentedError<ApiType>;
+      /**
+       * Proposal cannot have an empty title"
+       **/
+      EmptyTitleProvided: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance for operation.
+       **/
+      InsufficientBalance: AugmentedError<ApiType>;
+      /**
+       * Approval threshold cannot be zero
+       **/
+      InvalidParameterApprovalThreshold: AugmentedError<ApiType>;
+      /**
+       * Slashing threshold cannot be zero
+       **/
+      InvalidParameterSlashingThreshold: AugmentedError<ApiType>;
+      /**
+       * Max active proposals number exceeded
+       **/
+      MaxActiveProposalNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Not an author
+       **/
+      NotAuthor: AugmentedError<ApiType>;
+      /**
+       * Proposal is finalized already
+       **/
+      ProposalFinalized: AugmentedError<ApiType>;
+      /**
+       * The proposal does not exist
+       **/
+      ProposalNotFound: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Stake differs from the proposal requirements
+       **/
+      StakeDiffersFromRequired: AugmentedError<ApiType>;
+      /**
+       * Stake should be empty for this proposal
+       **/
+      StakeShouldBeEmpty: AugmentedError<ApiType>;
+      /**
+       * Title is too long
+       **/
+      TitleIsTooLong: AugmentedError<ApiType>;
+    };
+    session: {
+      /**
+       * Registered duplicate key.
+       **/
+      DuplicatedKey: AugmentedError<ApiType>;
+      /**
+       * Invalid ownership proof.
+       **/
+      InvalidProof: AugmentedError<ApiType>;
+      /**
+       * No associated validator ID for account.
+       **/
+      NoAssociatedValidatorId: AugmentedError<ApiType>;
+      /**
+       * No keys are associated with this account.
+       **/
+      NoKeys: AugmentedError<ApiType>;
+    };
+    staking: {
+      /**
+       * Stash is already bonded.
+       **/
+      AlreadyBonded: AugmentedError<ApiType>;
+      /**
+       * Rewards for this era have already been claimed for this validator.
+       **/
+      AlreadyClaimed: AugmentedError<ApiType>;
+      /**
+       * Controller is already paired.
+       **/
+      AlreadyPaired: AugmentedError<ApiType>;
+      /**
+       * The call is not allowed at the given time due to restrictions of election period.
+       **/
+      CallNotAllowed: AugmentedError<ApiType>;
+      /**
+       * Duplicate index.
+       **/
+      DuplicateIndex: AugmentedError<ApiType>;
+      /**
+       * Targets cannot be empty.
+       **/
+      EmptyTargets: AugmentedError<ApiType>;
+      /**
+       * Attempting to target a stash that still has funds.
+       **/
+      FundedTarget: AugmentedError<ApiType>;
+      /**
+       * Incorrect previous history depth input provided.
+       **/
+      IncorrectHistoryDepth: AugmentedError<ApiType>;
+      /**
+       * Incorrect number of slashing spans provided.
+       **/
+      IncorrectSlashingSpans: AugmentedError<ApiType>;
+      /**
+       * Can not bond with value less than minimum balance.
+       **/
+      InsufficientValue: AugmentedError<ApiType>;
+      /**
+       * Invalid era to reward.
+       **/
+      InvalidEraToReward: AugmentedError<ApiType>;
+      /**
+       * Invalid number of nominations.
+       **/
+      InvalidNumberOfNominations: AugmentedError<ApiType>;
+      /**
+       * Slash record index out of bounds.
+       **/
+      InvalidSlashIndex: AugmentedError<ApiType>;
+      /**
+       * Can not schedule more unlock chunks.
+       **/
+      NoMoreChunks: AugmentedError<ApiType>;
+      /**
+       * Not a controller account.
+       **/
+      NotController: AugmentedError<ApiType>;
+      /**
+       * Items are not sorted and unique.
+       **/
+      NotSortedAndUnique: AugmentedError<ApiType>;
+      /**
+       * Not a stash account.
+       **/
+      NotStash: AugmentedError<ApiType>;
+      /**
+       * Can not rebond without unlocking chunks.
+       **/
+      NoUnlockChunk: AugmentedError<ApiType>;
+      /**
+       * Error while building the assignment type from the compact. This can happen if an index
+       * is invalid, or if the weights _overflow_.
+       **/
+      OffchainElectionBogusCompact: AugmentedError<ApiType>;
+      /**
+       * The submitted result has unknown edges that are not among the presented winners.
+       **/
+      OffchainElectionBogusEdge: AugmentedError<ApiType>;
+      /**
+       * The election size is invalid.
+       **/
+      OffchainElectionBogusElectionSize: AugmentedError<ApiType>;
+      /**
+       * One of the submitted nominators has an edge to which they have not voted on chain.
+       **/
+      OffchainElectionBogusNomination: AugmentedError<ApiType>;
+      /**
+       * One of the submitted nominators is not an active nominator on chain.
+       **/
+      OffchainElectionBogusNominator: AugmentedError<ApiType>;
+      /**
+       * The claimed score does not match with the one computed from the data.
+       **/
+      OffchainElectionBogusScore: AugmentedError<ApiType>;
+      /**
+       * A self vote must only be originated from a validator to ONLY themselves.
+       **/
+      OffchainElectionBogusSelfVote: AugmentedError<ApiType>;
+      /**
+       * One of the submitted winners is not an active candidate on chain (index is out of range
+       * in snapshot).
+       **/
+      OffchainElectionBogusWinner: AugmentedError<ApiType>;
+      /**
+       * Incorrect number of winners were presented.
+       **/
+      OffchainElectionBogusWinnerCount: AugmentedError<ApiType>;
+      /**
+       * The submitted result is received out of the open window.
+       **/
+      OffchainElectionEarlySubmission: AugmentedError<ApiType>;
+      /**
+       * One of the submitted nominators has an edge which is submitted before the last non-zero
+       * slash of the target.
+       **/
+      OffchainElectionSlashedNomination: AugmentedError<ApiType>;
+      /**
+       * The submitted result is not as good as the one stored on chain.
+       **/
+      OffchainElectionWeakSubmission: AugmentedError<ApiType>;
+      /**
+       * The snapshot data of the current window is missing.
+       **/
+      SnapshotUnavailable: AugmentedError<ApiType>;
+    };
+    storage: {
+      /**
+       * Blacklist size limit exceeded.
+       **/
+      BlacklistSizeLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Cannot delete non empty dynamic bag.
+       **/
+      CannotDeleteNonEmptyDynamicBag: AugmentedError<ApiType>;
+      /**
+       * Cannot delete a non-empty storage bucket.
+       **/
+      CannotDeleteNonEmptyStorageBucket: AugmentedError<ApiType>;
+      /**
+       * Data object hash is part of the blacklist.
+       **/
+      DataObjectBlacklisted: AugmentedError<ApiType>;
+      /**
+       * Data object doesn't exist.
+       **/
+      DataObjectDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Data object id collection is empty.
+       **/
+      DataObjectIdCollectionIsEmpty: AugmentedError<ApiType>;
+      /**
+       * The `data_object_ids` extrinsic parameter collection is empty.
+       **/
+      DataObjectIdParamsAreEmpty: AugmentedError<ApiType>;
+      /**
+       * Upload data error: data objects per bag limit exceeded.
+       **/
+      DataObjectsPerBagLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Invalid extrinsic call: data size fee changed.
+       **/
+      DataSizeFeeChanged: AugmentedError<ApiType>;
+      /**
+       * Invalid operation with invites: another storage provider was invited.
+       **/
+      DifferentStorageProviderInvited: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket doesn't accept new bags.
+       **/
+      DistributionBucketDoesntAcceptNewBags: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket doesn't exist.
+       **/
+      DistributionBucketDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket family doesn't exist.
+       **/
+      DistributionBucketFamilyDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket id collections are empty.
+       **/
+      DistributionBucketIdCollectionsAreEmpty: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket is bound to a bag.
+       **/
+      DistributionBucketIsBoundToBag: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket is not bound to a bag.
+       **/
+      DistributionBucketIsNotBoundToBag: AugmentedError<ApiType>;
+      /**
+       * The new `DistributionBucketsPerBagLimit` number is too high.
+       **/
+      DistributionBucketsPerBagLimitTooHigh: AugmentedError<ApiType>;
+      /**
+       * The new `DistributionBucketsPerBagLimit` number is too low.
+       **/
+      DistributionBucketsPerBagLimitTooLow: AugmentedError<ApiType>;
+      /**
+       * Distribution family bound to a bag creation policy.
+       **/
+      DistributionFamilyBoundToBagCreationPolicy: AugmentedError<ApiType>;
+      /**
+       * Distribution provider operator already invited.
+       **/
+      DistributionProviderOperatorAlreadyInvited: AugmentedError<ApiType>;
+      /**
+       * Distribution provider operator doesn't exist.
+       **/
+      DistributionProviderOperatorDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Distribution provider operator already set.
+       **/
+      DistributionProviderOperatorSet: AugmentedError<ApiType>;
+      /**
+       * Dynamic bag doesn't exist.
+       **/
+      DynamicBagDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Cannot create the dynamic bag: dynamic bag exists.
+       **/
+      DynamicBagExists: AugmentedError<ApiType>;
+      /**
+       * Upload data error: empty content ID provided.
+       **/
+      EmptyContentId: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance for an operation.
+       **/
+      InsufficientBalance: AugmentedError<ApiType>;
+      /**
+       * Insufficient module treasury balance for an operation.
+       **/
+      InsufficientTreasuryBalance: AugmentedError<ApiType>;
+      /**
+       * Upload data error: invalid deletion prize source account.
+       **/
+      InvalidDeletionPrizeSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Invalid storage provider for bucket.
+       **/
+      InvalidStorageProvider: AugmentedError<ApiType>;
+      /**
+       * Invalid operation with invites: storage provider was already invited.
+       **/
+      InvitedStorageProvider: AugmentedError<ApiType>;
+      /**
+       * Max data object size exceeded.
+       **/
+      MaxDataObjectSizeExceeded: AugmentedError<ApiType>;
+      /**
+       * Max distribution bucket family number limit exceeded.
+       **/
+      MaxDistributionBucketFamilyNumberLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Max distribution bucket number per bag limit exceeded.
+       **/
+      MaxDistributionBucketNumberPerBagLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Max distribution bucket number per family limit exceeded.
+       **/
+      MaxDistributionBucketNumberPerFamilyLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Max number of pending invitations limit for a distribution bucket reached.
+       **/
+      MaxNumberOfPendingInvitationsLimitForDistributionBucketReached: AugmentedError<ApiType>;
+      /**
+       * Invalid operations: must be a distribution provider operator for a bucket.
+       **/
+      MustBeDistributionProviderOperatorForBucket: AugmentedError<ApiType>;
+      /**
+       * No distribution bucket invitation.
+       **/
+      NoDistributionBucketInvitation: AugmentedError<ApiType>;
+      /**
+       * Empty "data object creation" collection.
+       **/
+      NoObjectsOnUpload: AugmentedError<ApiType>;
+      /**
+       * Invalid operation with invites: there is no storage bucket invitation.
+       **/
+      NoStorageBucketInvitation: AugmentedError<ApiType>;
+      /**
+       * Cannot move objects within the same bag.
+       **/
+      SourceAndDestinationBagsAreEqual: AugmentedError<ApiType>;
+      /**
+       * The storage bucket doesn't accept new bags.
+       **/
+      StorageBucketDoesntAcceptNewBags: AugmentedError<ApiType>;
+      /**
+       * The requested storage bucket doesn't exist.
+       **/
+      StorageBucketDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Storage bucket id collections are empty.
+       **/
+      StorageBucketIdCollectionsAreEmpty: AugmentedError<ApiType>;
+      /**
+       * The requested storage bucket is already bound to a bag.
+       **/
+      StorageBucketIsBoundToBag: AugmentedError<ApiType>;
+      /**
+       * The requested storage bucket is not bound to a bag.
+       **/
+      StorageBucketIsNotBoundToBag: AugmentedError<ApiType>;
+      /**
+       * Object number limit for the storage bucket reached.
+       **/
+      StorageBucketObjectNumberLimitReached: AugmentedError<ApiType>;
+      /**
+       * Objects total size limit for the storage bucket reached.
+       **/
+      StorageBucketObjectSizeLimitReached: AugmentedError<ApiType>;
+      /**
+       * `StorageBucketsPerBagLimit` was exceeded for a bag.
+       **/
+      StorageBucketPerBagLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * The new `StorageBucketsPerBagLimit` number is too high.
+       **/
+      StorageBucketsPerBagLimitTooHigh: AugmentedError<ApiType>;
+      /**
+       * The new `StorageBucketsPerBagLimit` number is too low.
+       **/
+      StorageBucketsPerBagLimitTooLow: AugmentedError<ApiType>;
+      /**
+       * Invalid operation with invites: storage provider was already set.
+       **/
+      StorageProviderAlreadySet: AugmentedError<ApiType>;
+      /**
+       * Storage provider must be set.
+       **/
+      StorageProviderMustBeSet: AugmentedError<ApiType>;
+      /**
+       * Storage provider operator doesn't exist.
+       **/
+      StorageProviderOperatorDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Uploading of the new object is blocked.
+       **/
+      UploadingBlocked: AugmentedError<ApiType>;
+      /**
+       * Max object number limit exceeded for voucher.
+       **/
+      VoucherMaxObjectNumberLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Max object size limit exceeded for voucher.
+       **/
+      VoucherMaxObjectSizeLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Upload data error: zero object size.
+       **/
+      ZeroObjectSize: AugmentedError<ApiType>;
+    };
+    storageWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    sudo: {
+      /**
+       * Sender must be the Sudo account
+       **/
+      RequireSudo: AugmentedError<ApiType>;
+    };
+    system: {
+      /**
+       * Failed to extract the runtime version from the new runtime.
+       * 
+       * Either calling `Core_version` or decoding `RuntimeVersion` failed.
+       **/
+      FailedToExtractRuntimeVersion: AugmentedError<ApiType>;
+      /**
+       * The name of specification does not match between the current runtime
+       * and the new runtime.
+       **/
+      InvalidSpecName: AugmentedError<ApiType>;
+      /**
+       * Suicide called when the account has non-default composite data.
+       **/
+      NonDefaultComposite: AugmentedError<ApiType>;
+      /**
+       * There is a non-zero reference count preventing the account from being purged.
+       **/
+      NonZeroRefCount: AugmentedError<ApiType>;
+      /**
+       * The specification version is not allowed to decrease between the current runtime
+       * and the new runtime.
+       **/
+      SpecVersionNeedsToIncrease: AugmentedError<ApiType>;
+    };
+  }
+
+  export interface DecoratedErrors<ApiType extends ApiTypes> extends AugmentedErrors<ApiType> {
+  }
+}

+ 2857 - 0
types/augment/augment-api-errors.ts

@@ -0,0 +1,2857 @@
+// Auto-generated via `yarn polkadot-types-from-chain`, do not edit
+/* eslint-disable */
+
+import type { ApiTypes } from '@polkadot/api/types';
+
+declare module '@polkadot/api/types/errors' {
+  export interface AugmentedErrors<ApiType> {
+    authorship: {
+      /**
+       * The uncle is genesis.
+       **/
+      GenesisUncle: AugmentedError<ApiType>;
+      /**
+       * The uncle parent not in the chain.
+       **/
+      InvalidUncleParent: AugmentedError<ApiType>;
+      /**
+       * The uncle isn't recent enough to be included.
+       **/
+      OldUncle: AugmentedError<ApiType>;
+      /**
+       * The uncle is too high in chain.
+       **/
+      TooHighUncle: AugmentedError<ApiType>;
+      /**
+       * Too many uncles.
+       **/
+      TooManyUncles: AugmentedError<ApiType>;
+      /**
+       * The uncle is already included.
+       **/
+      UncleAlreadyIncluded: AugmentedError<ApiType>;
+      /**
+       * Uncles already set in the block.
+       **/
+      UnclesAlreadySet: AugmentedError<ApiType>;
+    };
+    balances: {
+      /**
+       * Beneficiary account must pre-exist
+       **/
+      DeadAccount: AugmentedError<ApiType>;
+      /**
+       * Value too low to create account due to existential deposit
+       **/
+      ExistentialDeposit: AugmentedError<ApiType>;
+      /**
+       * A vesting schedule already exists for this account
+       **/
+      ExistingVestingSchedule: AugmentedError<ApiType>;
+      /**
+       * Balance too low to send value
+       **/
+      InsufficientBalance: AugmentedError<ApiType>;
+      /**
+       * Transfer/payment would kill account
+       **/
+      KeepAlive: AugmentedError<ApiType>;
+      /**
+       * Account liquidity restrictions prevent withdrawal
+       **/
+      LiquidityRestrictions: AugmentedError<ApiType>;
+      /**
+       * Got an overflow after adding
+       **/
+      Overflow: AugmentedError<ApiType>;
+      /**
+       * Vesting balance too high to send value
+       **/
+      VestingBalance: AugmentedError<ApiType>;
+    };
+    content: {
+      /**
+       * Operation cannot be perfomed with this Actor
+       **/
+      ActorNotAuthorized: AugmentedError<ApiType>;
+      /**
+       * Expected root or signed origin
+       **/
+      BadOrigin: AugmentedError<ApiType>;
+      /**
+       * Curators can only censor non-curator group owned channels
+       **/
+      CannotCensoreCuratorGroupOwnedChannels: AugmentedError<ApiType>;
+      /**
+       * A Channel or Video Category does not exist.
+       **/
+      CategoryDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Channel does not exist
+       **/
+      ChannelDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Curator authentication failed
+       **/
+      CuratorAuthFailed: AugmentedError<ApiType>;
+      /**
+       * Given curator group does not exist
+       **/
+      CuratorGroupDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Curator group is not active
+       **/
+      CuratorGroupIsNotActive: AugmentedError<ApiType>;
+      /**
+       * Curator id is not a worker id in content working group
+       **/
+      CuratorIdInvalid: AugmentedError<ApiType>;
+      /**
+       * Curator under provided curator id is already a member of curaror group under given id
+       **/
+      CuratorIsAlreadyAMemberOfGivenCuratorGroup: AugmentedError<ApiType>;
+      /**
+       * Curator under provided curator id is not a member of curaror group under given id
+       **/
+      CuratorIsNotAMemberOfGivenCuratorGroup: AugmentedError<ApiType>;
+      /**
+       * Max number of curators per group limit reached
+       **/
+      CuratorsPerGroupLimitReached: AugmentedError<ApiType>;
+      /**
+       * Feature Not Implemented
+       **/
+      FeatureNotImplemented: AugmentedError<ApiType>;
+      /**
+       * Lead authentication failed
+       **/
+      LeadAuthFailed: AugmentedError<ApiType>;
+      /**
+       * Member authentication failed
+       **/
+      MemberAuthFailed: AugmentedError<ApiType>;
+      /**
+       * Video does not exist
+       **/
+      VideoDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Video in season can`t be removed (because order is important)
+       **/
+      VideoInSeason: AugmentedError<ApiType>;
+    };
+    contentWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    distributionWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    finalityTracker: {
+      /**
+       * Final hint must be updated only once in the block
+       **/
+      AlreadyUpdated: AugmentedError<ApiType>;
+      /**
+       * Finalized height above block number
+       **/
+      BadHint: AugmentedError<ApiType>;
+    };
+    gatewayWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    grandpa: {
+      /**
+       * Attempt to signal GRANDPA change with one already pending.
+       **/
+      ChangePending: AugmentedError<ApiType>;
+      /**
+       * A given equivocation report is valid but already previously reported.
+       **/
+      DuplicateOffenceReport: AugmentedError<ApiType>;
+      /**
+       * An equivocation proof provided as part of an equivocation report is invalid.
+       **/
+      InvalidEquivocationProof: AugmentedError<ApiType>;
+      /**
+       * A key ownership proof provided as part of an equivocation report is invalid.
+       **/
+      InvalidKeyOwnershipProof: AugmentedError<ApiType>;
+      /**
+       * Attempt to signal GRANDPA pause when the authority set isn't live
+       * (either paused or already pending pause).
+       **/
+      PauseFailed: AugmentedError<ApiType>;
+      /**
+       * Attempt to signal GRANDPA resume when the authority set isn't paused
+       * (either live or already pending resume).
+       **/
+      ResumeFailed: AugmentedError<ApiType>;
+      /**
+       * Cannot signal forced change so soon after last.
+       **/
+      TooSoon: AugmentedError<ApiType>;
+    };
+    imOnline: {
+      /**
+       * Duplicated heartbeat.
+       **/
+      DuplicatedHeartbeat: AugmentedError<ApiType>;
+      /**
+       * Non existent public key.
+       **/
+      InvalidKey: AugmentedError<ApiType>;
+    };
+    members: {
+      /**
+       * Avatar url is too long.
+       **/
+      AvatarUriTooLong: AugmentedError<ApiType>;
+      /**
+       * Controller account required.
+       **/
+      ControllerAccountRequired: AugmentedError<ApiType>;
+      /**
+       * Handle already registered.
+       **/
+      HandleAlreadyRegistered: AugmentedError<ApiType>;
+      /**
+       * Handle must be provided during registration.
+       **/
+      HandleMustBeProvidedDuringRegistration: AugmentedError<ApiType>;
+      /**
+       * Handle too long.
+       **/
+      HandleTooLong: AugmentedError<ApiType>;
+      /**
+       * Handle too short.
+       **/
+      HandleTooShort: AugmentedError<ApiType>;
+      /**
+       * Screening authority attempting to endow more that maximum allowed.
+       **/
+      InitialBalanceExceedsMaxInitialBalance: AugmentedError<ApiType>;
+      /**
+       * Member profile not found (invalid member id).
+       **/
+      MemberProfileNotFound: AugmentedError<ApiType>;
+      /**
+       * New memberships not allowed.
+       **/
+      NewMembershipsNotAllowed: AugmentedError<ApiType>;
+      /**
+       * A screening authority is not defined.
+       **/
+      NoScreeningAuthorityDefined: AugmentedError<ApiType>;
+      /**
+       * Not enough balance to buy membership.
+       **/
+      NotEnoughBalanceToBuyMembership: AugmentedError<ApiType>;
+      /**
+       * Origin is not the screeing authority.
+       **/
+      NotScreeningAuthority: AugmentedError<ApiType>;
+      /**
+       * Only new accounts can be used for screened members.
+       **/
+      OnlyNewAccountsCanBeUsedForScreenedMembers: AugmentedError<ApiType>;
+      /**
+       * Paid term id not active.
+       **/
+      PaidTermIdNotActive: AugmentedError<ApiType>;
+      /**
+       * Paid term id not found.
+       **/
+      PaidTermIdNotFound: AugmentedError<ApiType>;
+      /**
+       * Root account required.
+       **/
+      RootAccountRequired: AugmentedError<ApiType>;
+      /**
+       * Invalid origin.
+       **/
+      UnsignedOrigin: AugmentedError<ApiType>;
+    };
+    operationsWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    proposalsCodex: {
+      /**
+       * Invalid 'decrease stake proposal' parameter - cannot decrease by zero balance.
+       **/
+      DecreasingStakeIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid content working group mint capacity parameter
+       **/
+      InvalidContentWorkingGroupMintCapacity: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - announcing_period
+       **/
+      InvalidCouncilElectionParameterAnnouncingPeriod: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - candidacy-limit
+       **/
+      InvalidCouncilElectionParameterCandidacyLimit: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - council_size
+       **/
+      InvalidCouncilElectionParameterCouncilSize: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - min_council_stake
+       **/
+      InvalidCouncilElectionParameterMinCouncilStake: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - min-voting_stake
+       **/
+      InvalidCouncilElectionParameterMinVotingStake: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - new_term_duration
+       **/
+      InvalidCouncilElectionParameterNewTermDuration: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - revealing_period
+       **/
+      InvalidCouncilElectionParameterRevealingPeriod: AugmentedError<ApiType>;
+      /**
+       * Invalid council election parameter - voting_period
+       **/
+      InvalidCouncilElectionParameterVotingPeriod: AugmentedError<ApiType>;
+      /**
+       * Invalid 'set lead proposal' parameter - proposed lead cannot be a councilor
+       **/
+      InvalidSetLeadParameterCannotBeCouncilor: AugmentedError<ApiType>;
+      /**
+       * Invalid balance value for the spending proposal
+       **/
+      InvalidSpendingProposalBalance: AugmentedError<ApiType>;
+      /**
+       * Invalid validator count for the 'set validator count' proposal
+       **/
+      InvalidValidatorCount: AugmentedError<ApiType>;
+      /**
+       * Invalid working group mint capacity parameter
+       **/
+      InvalidWorkingGroupMintCapacity: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Provided WASM code for the runtime upgrade proposal is empty
+       **/
+      RuntimeProposalIsEmpty: AugmentedError<ApiType>;
+      /**
+       * The size of the provided WASM code for the runtime upgrade proposal exceeded the limit
+       **/
+      RuntimeProposalSizeExceeded: AugmentedError<ApiType>;
+      /**
+       * Invalid 'slash stake proposal' parameter - cannot slash by zero balance.
+       **/
+      SlashingStakeIsZero: AugmentedError<ApiType>;
+      /**
+       * Provided text for text proposal is empty
+       **/
+      TextProposalIsEmpty: AugmentedError<ApiType>;
+      /**
+       * The size of the provided text for text proposal exceeded the limit
+       **/
+      TextProposalSizeExceeded: AugmentedError<ApiType>;
+    };
+    proposalsDiscussion: {
+      /**
+       * Post cannot be empty
+       **/
+      EmptyPostProvided: AugmentedError<ApiType>;
+      /**
+       * Discussion cannot have an empty title
+       **/
+      EmptyTitleProvided: AugmentedError<ApiType>;
+      /**
+       * Max number of threads by same author in a row limit exceeded
+       **/
+      MaxThreadInARowLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Author should match the post creator
+       **/
+      NotAuthor: AugmentedError<ApiType>;
+      /**
+       * Post doesn't exist
+       **/
+      PostDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Post edition limit reached
+       **/
+      PostEditionNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Post is too long
+       **/
+      PostIsTooLong: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Thread doesn't exist
+       **/
+      ThreadDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Title is too long
+       **/
+      TitleIsTooLong: AugmentedError<ApiType>;
+    };
+    proposalsEngine: {
+      /**
+       * The proposal have been already voted on
+       **/
+      AlreadyVoted: AugmentedError<ApiType>;
+      /**
+       * Description is too long
+       **/
+      DescriptionIsTooLong: AugmentedError<ApiType>;
+      /**
+       * Proposal cannot have an empty body
+       **/
+      EmptyDescriptionProvided: AugmentedError<ApiType>;
+      /**
+       * Stake cannot be empty with this proposal
+       **/
+      EmptyStake: AugmentedError<ApiType>;
+      /**
+       * Proposal cannot have an empty title"
+       **/
+      EmptyTitleProvided: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance for operation.
+       **/
+      InsufficientBalance: AugmentedError<ApiType>;
+      /**
+       * Approval threshold cannot be zero
+       **/
+      InvalidParameterApprovalThreshold: AugmentedError<ApiType>;
+      /**
+       * Slashing threshold cannot be zero
+       **/
+      InvalidParameterSlashingThreshold: AugmentedError<ApiType>;
+      /**
+       * Max active proposals number exceeded
+       **/
+      MaxActiveProposalNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Not an author
+       **/
+      NotAuthor: AugmentedError<ApiType>;
+      /**
+       * Proposal is finalized already
+       **/
+      ProposalFinalized: AugmentedError<ApiType>;
+      /**
+       * The proposal does not exist
+       **/
+      ProposalNotFound: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Stake differs from the proposal requirements
+       **/
+      StakeDiffersFromRequired: AugmentedError<ApiType>;
+      /**
+       * Stake should be empty for this proposal
+       **/
+      StakeShouldBeEmpty: AugmentedError<ApiType>;
+      /**
+       * Title is too long
+       **/
+      TitleIsTooLong: AugmentedError<ApiType>;
+    };
+    session: {
+      /**
+       * Registered duplicate key.
+       **/
+      DuplicatedKey: AugmentedError<ApiType>;
+      /**
+       * Invalid ownership proof.
+       **/
+      InvalidProof: AugmentedError<ApiType>;
+      /**
+       * No associated validator ID for account.
+       **/
+      NoAssociatedValidatorId: AugmentedError<ApiType>;
+      /**
+       * No keys are associated with this account.
+       **/
+      NoKeys: AugmentedError<ApiType>;
+    };
+    staking: {
+      /**
+       * Stash is already bonded.
+       **/
+      AlreadyBonded: AugmentedError<ApiType>;
+      /**
+       * Rewards for this era have already been claimed for this validator.
+       **/
+      AlreadyClaimed: AugmentedError<ApiType>;
+      /**
+       * Controller is already paired.
+       **/
+      AlreadyPaired: AugmentedError<ApiType>;
+      /**
+       * The call is not allowed at the given time due to restrictions of election period.
+       **/
+      CallNotAllowed: AugmentedError<ApiType>;
+      /**
+       * Duplicate index.
+       **/
+      DuplicateIndex: AugmentedError<ApiType>;
+      /**
+       * Targets cannot be empty.
+       **/
+      EmptyTargets: AugmentedError<ApiType>;
+      /**
+       * Attempting to target a stash that still has funds.
+       **/
+      FundedTarget: AugmentedError<ApiType>;
+      /**
+       * Incorrect previous history depth input provided.
+       **/
+      IncorrectHistoryDepth: AugmentedError<ApiType>;
+      /**
+       * Incorrect number of slashing spans provided.
+       **/
+      IncorrectSlashingSpans: AugmentedError<ApiType>;
+      /**
+       * Can not bond with value less than minimum balance.
+       **/
+      InsufficientValue: AugmentedError<ApiType>;
+      /**
+       * Invalid era to reward.
+       **/
+      InvalidEraToReward: AugmentedError<ApiType>;
+      /**
+       * Invalid number of nominations.
+       **/
+      InvalidNumberOfNominations: AugmentedError<ApiType>;
+      /**
+       * Slash record index out of bounds.
+       **/
+      InvalidSlashIndex: AugmentedError<ApiType>;
+      /**
+       * Can not schedule more unlock chunks.
+       **/
+      NoMoreChunks: AugmentedError<ApiType>;
+      /**
+       * Not a controller account.
+       **/
+      NotController: AugmentedError<ApiType>;
+      /**
+       * Items are not sorted and unique.
+       **/
+      NotSortedAndUnique: AugmentedError<ApiType>;
+      /**
+       * Not a stash account.
+       **/
+      NotStash: AugmentedError<ApiType>;
+      /**
+       * Can not rebond without unlocking chunks.
+       **/
+      NoUnlockChunk: AugmentedError<ApiType>;
+      /**
+       * Error while building the assignment type from the compact. This can happen if an index
+       * is invalid, or if the weights _overflow_.
+       **/
+      OffchainElectionBogusCompact: AugmentedError<ApiType>;
+      /**
+       * The submitted result has unknown edges that are not among the presented winners.
+       **/
+      OffchainElectionBogusEdge: AugmentedError<ApiType>;
+      /**
+       * The election size is invalid.
+       **/
+      OffchainElectionBogusElectionSize: AugmentedError<ApiType>;
+      /**
+       * One of the submitted nominators has an edge to which they have not voted on chain.
+       **/
+      OffchainElectionBogusNomination: AugmentedError<ApiType>;
+      /**
+       * One of the submitted nominators is not an active nominator on chain.
+       **/
+      OffchainElectionBogusNominator: AugmentedError<ApiType>;
+      /**
+       * The claimed score does not match with the one computed from the data.
+       **/
+      OffchainElectionBogusScore: AugmentedError<ApiType>;
+      /**
+       * A self vote must only be originated from a validator to ONLY themselves.
+       **/
+      OffchainElectionBogusSelfVote: AugmentedError<ApiType>;
+      /**
+       * One of the submitted winners is not an active candidate on chain (index is out of range
+       * in snapshot).
+       **/
+      OffchainElectionBogusWinner: AugmentedError<ApiType>;
+      /**
+       * Incorrect number of winners were presented.
+       **/
+      OffchainElectionBogusWinnerCount: AugmentedError<ApiType>;
+      /**
+       * The submitted result is received out of the open window.
+       **/
+      OffchainElectionEarlySubmission: AugmentedError<ApiType>;
+      /**
+       * One of the submitted nominators has an edge which is submitted before the last non-zero
+       * slash of the target.
+       **/
+      OffchainElectionSlashedNomination: AugmentedError<ApiType>;
+      /**
+       * The submitted result is not as good as the one stored on chain.
+       **/
+      OffchainElectionWeakSubmission: AugmentedError<ApiType>;
+      /**
+       * The snapshot data of the current window is missing.
+       **/
+      SnapshotUnavailable: AugmentedError<ApiType>;
+    };
+    storage: {
+      /**
+       * Blacklist size limit exceeded.
+       **/
+      BlacklistSizeLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Cannot delete non empty dynamic bag.
+       **/
+      CannotDeleteNonEmptyDynamicBag: AugmentedError<ApiType>;
+      /**
+       * Cannot delete a non-empty storage bucket.
+       **/
+      CannotDeleteNonEmptyStorageBucket: AugmentedError<ApiType>;
+      /**
+       * Data object hash is part of the blacklist.
+       **/
+      DataObjectBlacklisted: AugmentedError<ApiType>;
+      /**
+       * Data object doesn't exist.
+       **/
+      DataObjectDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Data object id collection is empty.
+       **/
+      DataObjectIdCollectionIsEmpty: AugmentedError<ApiType>;
+      /**
+       * The `data_object_ids` extrinsic parameter collection is empty.
+       **/
+      DataObjectIdParamsAreEmpty: AugmentedError<ApiType>;
+      /**
+       * Upload data error: data objects per bag limit exceeded.
+       **/
+      DataObjectsPerBagLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Invalid extrinsic call: data size fee changed.
+       **/
+      DataSizeFeeChanged: AugmentedError<ApiType>;
+      /**
+       * Invalid operation with invites: another storage provider was invited.
+       **/
+      DifferentStorageProviderInvited: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket doesn't accept new bags.
+       **/
+      DistributionBucketDoesntAcceptNewBags: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket doesn't exist.
+       **/
+      DistributionBucketDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket family doesn't exist.
+       **/
+      DistributionBucketFamilyDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket id collections are empty.
+       **/
+      DistributionBucketIdCollectionsAreEmpty: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket is bound to a bag.
+       **/
+      DistributionBucketIsBoundToBag: AugmentedError<ApiType>;
+      /**
+       * Distribution bucket is not bound to a bag.
+       **/
+      DistributionBucketIsNotBoundToBag: AugmentedError<ApiType>;
+      /**
+       * The new `DistributionBucketsPerBagLimit` number is too high.
+       **/
+      DistributionBucketsPerBagLimitTooHigh: AugmentedError<ApiType>;
+      /**
+       * The new `DistributionBucketsPerBagLimit` number is too low.
+       **/
+      DistributionBucketsPerBagLimitTooLow: AugmentedError<ApiType>;
+      /**
+       * Distribution family bound to a bag creation policy.
+       **/
+      DistributionFamilyBoundToBagCreationPolicy: AugmentedError<ApiType>;
+      /**
+       * Distribution provider operator already invited.
+       **/
+      DistributionProviderOperatorAlreadyInvited: AugmentedError<ApiType>;
+      /**
+       * Distribution provider operator doesn't exist.
+       **/
+      DistributionProviderOperatorDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Distribution provider operator already set.
+       **/
+      DistributionProviderOperatorSet: AugmentedError<ApiType>;
+      /**
+       * Dynamic bag doesn't exist.
+       **/
+      DynamicBagDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Cannot create the dynamic bag: dynamic bag exists.
+       **/
+      DynamicBagExists: AugmentedError<ApiType>;
+      /**
+       * Upload data error: empty content ID provided.
+       **/
+      EmptyContentId: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance for an operation.
+       **/
+      InsufficientBalance: AugmentedError<ApiType>;
+      /**
+       * Insufficient module treasury balance for an operation.
+       **/
+      InsufficientTreasuryBalance: AugmentedError<ApiType>;
+      /**
+       * Upload data error: invalid deletion prize source account.
+       **/
+      InvalidDeletionPrizeSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Invalid storage provider for bucket.
+       **/
+      InvalidStorageProvider: AugmentedError<ApiType>;
+      /**
+       * Invalid operation with invites: storage provider was already invited.
+       **/
+      InvitedStorageProvider: AugmentedError<ApiType>;
+      /**
+       * Max data object size exceeded.
+       **/
+      MaxDataObjectSizeExceeded: AugmentedError<ApiType>;
+      /**
+       * Max distribution bucket family number limit exceeded.
+       **/
+      MaxDistributionBucketFamilyNumberLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Max distribution bucket number per bag limit exceeded.
+       **/
+      MaxDistributionBucketNumberPerBagLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Max distribution bucket number per family limit exceeded.
+       **/
+      MaxDistributionBucketNumberPerFamilyLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Max number of pending invitations limit for a distribution bucket reached.
+       **/
+      MaxNumberOfPendingInvitationsLimitForDistributionBucketReached: AugmentedError<ApiType>;
+      /**
+       * Invalid operations: must be a distribution provider operator for a bucket.
+       **/
+      MustBeDistributionProviderOperatorForBucket: AugmentedError<ApiType>;
+      /**
+       * No distribution bucket invitation.
+       **/
+      NoDistributionBucketInvitation: AugmentedError<ApiType>;
+      /**
+       * Empty "data object creation" collection.
+       **/
+      NoObjectsOnUpload: AugmentedError<ApiType>;
+      /**
+       * Invalid operation with invites: there is no storage bucket invitation.
+       **/
+      NoStorageBucketInvitation: AugmentedError<ApiType>;
+      /**
+       * Cannot move objects within the same bag.
+       **/
+      SourceAndDestinationBagsAreEqual: AugmentedError<ApiType>;
+      /**
+       * The storage bucket doesn't accept new bags.
+       **/
+      StorageBucketDoesntAcceptNewBags: AugmentedError<ApiType>;
+      /**
+       * The requested storage bucket doesn't exist.
+       **/
+      StorageBucketDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Storage bucket id collections are empty.
+       **/
+      StorageBucketIdCollectionsAreEmpty: AugmentedError<ApiType>;
+      /**
+       * The requested storage bucket is already bound to a bag.
+       **/
+      StorageBucketIsBoundToBag: AugmentedError<ApiType>;
+      /**
+       * The requested storage bucket is not bound to a bag.
+       **/
+      StorageBucketIsNotBoundToBag: AugmentedError<ApiType>;
+      /**
+       * Object number limit for the storage bucket reached.
+       **/
+      StorageBucketObjectNumberLimitReached: AugmentedError<ApiType>;
+      /**
+       * Objects total size limit for the storage bucket reached.
+       **/
+      StorageBucketObjectSizeLimitReached: AugmentedError<ApiType>;
+      /**
+       * `StorageBucketsPerBagLimit` was exceeded for a bag.
+       **/
+      StorageBucketPerBagLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * The new `StorageBucketsPerBagLimit` number is too high.
+       **/
+      StorageBucketsPerBagLimitTooHigh: AugmentedError<ApiType>;
+      /**
+       * The new `StorageBucketsPerBagLimit` number is too low.
+       **/
+      StorageBucketsPerBagLimitTooLow: AugmentedError<ApiType>;
+      /**
+       * Invalid operation with invites: storage provider was already set.
+       **/
+      StorageProviderAlreadySet: AugmentedError<ApiType>;
+      /**
+       * Storage provider must be set.
+       **/
+      StorageProviderMustBeSet: AugmentedError<ApiType>;
+      /**
+       * Storage provider operator doesn't exist.
+       **/
+      StorageProviderOperatorDoesntExist: AugmentedError<ApiType>;
+      /**
+       * Uploading of the new object is blocked.
+       **/
+      UploadingBlocked: AugmentedError<ApiType>;
+      /**
+       * Max object number limit exceeded for voucher.
+       **/
+      VoucherMaxObjectNumberLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Max object size limit exceeded for voucher.
+       **/
+      VoucherMaxObjectSizeLimitExceeded: AugmentedError<ApiType>;
+      /**
+       * Upload data error: zero object size.
+       **/
+      ZeroObjectSize: AugmentedError<ApiType>;
+    };
+    storageWorkingGroup: {
+      /**
+       * Opening does not exist.
+       **/
+      AcceptWorkerApplicationsOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting to begin.
+       **/
+      AcceptWorkerApplicationsOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Opening does not activate in the future.
+       **/
+      AddWorkerOpeningActivatesInThePast: AugmentedError<ApiType>;
+      /**
+       * Add worker opening application stake cannot be zero.
+       **/
+      AddWorkerOpeningApplicationStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Application stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningAppliicationStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * New application was crowded out.
+       **/
+      AddWorkerOpeningNewApplicationWasCrowdedOut: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      AddWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening is not in accepting applications stage.
+       **/
+      AddWorkerOpeningOpeningNotInAcceptingApplicationStage: AugmentedError<ApiType>;
+      /**
+       * Add worker opening role stake cannot be zero.
+       **/
+      AddWorkerOpeningRoleStakeCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Role stake amount less than minimum currency balance.
+       **/
+      AddWorkerOpeningRoleStakeLessThanMinimum: AugmentedError<ApiType>;
+      /**
+       * Stake amount too low.
+       **/
+      AddWorkerOpeningStakeAmountTooLow: AugmentedError<ApiType>;
+      /**
+       * Stake missing when required.
+       **/
+      AddWorkerOpeningStakeMissingWhenRequired: AugmentedError<ApiType>;
+      /**
+       * Stake provided when redundant.
+       **/
+      AddWorkerOpeningStakeProvidedWhenRedundant: AugmentedError<ApiType>;
+      /**
+       * Application rationing has zero max active applicants.
+       **/
+      AddWorkerOpeningZeroMaxApplicantCount: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_rationing_policy):
+       * max_active_applicants should be non-zero.
+       **/
+      ApplicationRationingPolicyMaxActiveApplicantsIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (application_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      ApplicationStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer does not match controller account.
+       **/
+      ApplyOnWorkerOpeningSignerNotControllerAccount: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      BeginWorkerApplicantReviewOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening Is Not in Waiting.
+       **/
+      BeginWorkerApplicantReviewOpeningOpeningIsNotWaitingToBegin: AugmentedError<ApiType>;
+      /**
+       * Cannot find mint in the minting module.
+       **/
+      CannotFindMint: AugmentedError<ApiType>;
+      /**
+       * There is leader already, cannot hire another one.
+       **/
+      CannotHireLeaderWhenLeaderExists: AugmentedError<ApiType>;
+      /**
+       * Cannot fill opening with multiple applications.
+       **/
+      CannotHireMultipleLeaders: AugmentedError<ApiType>;
+      /**
+       * Current lead is not set.
+       **/
+      CurrentLeadNotSet: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_application_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * exit_role_stake_unstaking_period should be non-zero.
+       **/
+      ExitRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_failed_applicant_role_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningFailedApplicantRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Reward policy has invalid next payment block number.
+       **/
+      FillOpeningInvalidNextPaymentBlock: AugmentedError<ApiType>;
+      /**
+       * Working group mint does not exist.
+       **/
+      FillOpeningMintDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * fill_opening_successful_applicant_application_stake_unstaking_period should be non-zero.
+       **/
+      FillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Applications not for opening.
+       **/
+      FillWorkerOpeningApplicationForWrongOpening: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      FullWorkerOpeningApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application not in active stage.
+       **/
+      FullWorkerOpeningApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * OpeningDoesNotExist.
+       **/
+      FullWorkerOpeningOpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening not in review period stage.
+       **/
+      FullWorkerOpeningOpeningNotInReviewPeriodStage: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants redundant.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningSuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Application stake unstaking period for successful applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulApplicationStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants redundant.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodRedundant: AugmentedError<ApiType>;
+      /**
+       * Role stake unstaking period for failed applicants too short.
+       **/
+      FullWorkerOpeningUnsuccessfulRoleStakeUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to apply.
+       **/
+      InsufficientBalanceToApply: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance to cover stake.
+       **/
+      InsufficientBalanceToCoverStake: AugmentedError<ApiType>;
+      /**
+       * Not a lead account.
+       **/
+      IsNotLeadAccount: AugmentedError<ApiType>;
+      /**
+       * Working group size limit exceeded.
+       **/
+      MaxActiveWorkerNumberExceeded: AugmentedError<ApiType>;
+      /**
+       * Member already has an active application on the opening.
+       **/
+      MemberHasActiveApplicationOnOpening: AugmentedError<ApiType>;
+      /**
+       * Member id is invalid.
+       **/
+      MembershipInvalidMemberId: AugmentedError<ApiType>;
+      /**
+       * Unsigned origin.
+       **/
+      MembershipUnsignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Minting error: NextAdjustmentInPast
+       **/
+      MintingErrorNextAdjustmentInPast: AugmentedError<ApiType>;
+      /**
+       * Cannot get the worker stake profile.
+       **/
+      NoWorkerStakeProfile: AugmentedError<ApiType>;
+      /**
+       * Opening does not exist.
+       **/
+      OpeningDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Opening text too long.
+       **/
+      OpeningTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Opening text too short.
+       **/
+      OpeningTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Origin must be controller or root account of member.
+       **/
+      OriginIsNeitherMemberControllerOrRoot: AugmentedError<ApiType>;
+      /**
+       * Origin is not applicant.
+       **/
+      OriginIsNotApplicant: AugmentedError<ApiType>;
+      /**
+       * Next payment is not in the future.
+       **/
+      RecurringRewardsNextPaymentNotInFuture: AugmentedError<ApiType>;
+      /**
+       * Recipient not found.
+       **/
+      RecurringRewardsRecipientNotFound: AugmentedError<ApiType>;
+      /**
+       * Reward relationship not found.
+       **/
+      RecurringRewardsRewardRelationshipNotFound: AugmentedError<ApiType>;
+      /**
+       * Recipient reward source not found.
+       **/
+      RecurringRewardsRewardSourceNotFound: AugmentedError<ApiType>;
+      /**
+       * Relationship must exist.
+       **/
+      RelationshipMustExist: AugmentedError<ApiType>;
+      /**
+       * Require root origin in extrinsics.
+       **/
+      RequireRootOrigin: AugmentedError<ApiType>;
+      /**
+       * Require signed origin in extrinsics.
+       **/
+      RequireSignedOrigin: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * crowded_out_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyCrowdedOutUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter (role_staking_policy):
+       * review_period_expired_unstaking_period_length should be non-zero.
+       **/
+      RoleStakingPolicyReviewPeriodUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Signer is not worker role account.
+       **/
+      SignerIsNotWorkerRoleAccount: AugmentedError<ApiType>;
+      /**
+       * Provided stake balance cannot be zero.
+       **/
+      StakeBalanceCannotBeZero: AugmentedError<ApiType>;
+      /**
+       * Already unstaking.
+       **/
+      StakingErrorAlreadyUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot change stake by zero.
+       **/
+      StakingErrorCannotChangeStakeByZero: AugmentedError<ApiType>;
+      /**
+       * Cannot decrease stake while slashes ongoing.
+       **/
+      StakingErrorCannotDecreaseWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Cannot increase stake while unstaking.
+       **/
+      StakingErrorCannotIncreaseStakeWhileUnstaking: AugmentedError<ApiType>;
+      /**
+       * Cannot unstake while slashes ongoing.
+       **/
+      StakingErrorCannotUnstakeWhileSlashesOngoing: AugmentedError<ApiType>;
+      /**
+       * Insufficient balance in source account.
+       **/
+      StakingErrorInsufficientBalanceInSourceAccount: AugmentedError<ApiType>;
+      /**
+       * Insufficient stake to decrease.
+       **/
+      StakingErrorInsufficientStake: AugmentedError<ApiType>;
+      /**
+       * Not staked.
+       **/
+      StakingErrorNotStaked: AugmentedError<ApiType>;
+      /**
+       * Slash amount should be greater than zero.
+       **/
+      StakingErrorSlashAmountShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Stake not found.
+       **/
+      StakingErrorStakeNotFound: AugmentedError<ApiType>;
+      /**
+       * Unstaking period should be greater than zero.
+       **/
+      StakingErrorUnstakingPeriodShouldBeGreaterThanZero: AugmentedError<ApiType>;
+      /**
+       * Successful worker application does not exist.
+       **/
+      SuccessfulWorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_application_stake_unstaking_period should be non-zero.
+       **/
+      TerminateApplicationStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Invalid OpeningPolicyCommitment parameter:
+       * terminate_role_stake_unstaking_period should be non-zero.
+       **/
+      TerminateRoleStakeUnstakingPeriodIsZero: AugmentedError<ApiType>;
+      /**
+       * Application does not exist.
+       **/
+      WithdrawWorkerApplicationApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Application is not active.
+       **/
+      WithdrawWorkerApplicationApplicationNotActive: AugmentedError<ApiType>;
+      /**
+       * Opening not accepting applications.
+       **/
+      WithdrawWorkerApplicationOpeningNotAcceptingApplications: AugmentedError<ApiType>;
+      /**
+       * Redundant unstaking period provided
+       **/
+      WithdrawWorkerApplicationRedundantUnstakingPeriod: AugmentedError<ApiType>;
+      /**
+       * UnstakingPeriodTooShort .... // <== SHOULD REALLY BE TWO SEPARATE, ONE FOR EACH STAKING PURPOSE
+       **/
+      WithdrawWorkerApplicationUnstakingPeriodTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker application does not exist.
+       **/
+      WorkerApplicationDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker application text too long.
+       **/
+      WorkerApplicationTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker application text too short.
+       **/
+      WorkerApplicationTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker does not exist.
+       **/
+      WorkerDoesNotExist: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too long.
+       **/
+      WorkerExitRationaleTextTooLong: AugmentedError<ApiType>;
+      /**
+       * Worker exit rationale text is too short.
+       **/
+      WorkerExitRationaleTextTooShort: AugmentedError<ApiType>;
+      /**
+       * Worker has no recurring reward.
+       **/
+      WorkerHasNoReward: AugmentedError<ApiType>;
+      /**
+       * Worker storage text is too long.
+       **/
+      WorkerStorageValueTooLong: AugmentedError<ApiType>;
+    };
+    sudo: {
+      /**
+       * Sender must be the Sudo account
+       **/
+      RequireSudo: AugmentedError<ApiType>;
+    };
+    system: {
+      /**
+       * Failed to extract the runtime version from the new runtime.
+       * 
+       * Either calling `Core_version` or decoding `RuntimeVersion` failed.
+       **/
+      FailedToExtractRuntimeVersion: AugmentedError<ApiType>;
+      /**
+       * The name of specification does not match between the current runtime
+       * and the new runtime.
+       **/
+      InvalidSpecName: AugmentedError<ApiType>;
+      /**
+       * Suicide called when the account has non-default composite data.
+       **/
+      NonDefaultComposite: AugmentedError<ApiType>;
+      /**
+       * There is a non-zero reference count preventing the account from being purged.
+       **/
+      NonZeroRefCount: AugmentedError<ApiType>;
+      /**
+       * The specification version is not allowed to decrease between the current runtime
+       * and the new runtime.
+       **/
+      SpecVersionNeedsToIncrease: AugmentedError<ApiType>;
+    };
+  }
+
+  export interface DecoratedErrors<ApiType extends ApiTypes> extends AugmentedErrors<ApiType> {
+  }
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů