-
Mateusz Tyszczak authoredMateusz Tyszczak authored
account_update.ts 5.75 KiB
import { operation } from "../protocol.js";
import type { TAccountName } from "../hive_apps_operations/index.js";
import { OperationBase, IOperationSink } from "../operation_base.js";
import type { IHiveChainInterface } from "../interfaces.js";
import { HiveAccountCategory } from "./role_classes/categories/hive_authority/index.js";
import { RoleCategoryBase } from "./role_classes/role_category_base.js";
import { WaxError } from "../errors.js";
// Here are all of the role categories. They are automatically parsed. Add new categories here
const AuthorityRoleCategories = [
HiveAccountCategory
] as const satisfies Readonly<Array<(new () => RoleCategoryBase<any>)>>;
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends
(k: infer I) => void ? I : never;
export type TRolesStruct = UnionToIntersection<InstanceType<typeof AuthorityRoleCategories[number]>["authorities"]>;
export type TRole = keyof TRolesStruct;
export type TRoleContainerNames = InstanceType<typeof AuthorityRoleCategories[number]>["category"];
export type TRoleKeyToValueMap = {
[K in keyof TRolesStruct]: Omit<TRolesStruct[K], 'init'>
};
export type TRoleContainerKeyToValueMap = {
[C in InstanceType<typeof AuthorityRoleCategories[number]> as C["category"]]: {
[K in keyof C["authorities"]]: Omit<C["authorities"][K], 'init'>
}[keyof C["authorities"]];
};
/**
* Online operation - it is automatically filled in with the data acquired from the chain API.
*
* The purpose of this operation is to simplify account authority update process by automatic gathering current authority data
* from blockchain and then supplementing them by provided action methods. Operation is designed to support different authority categories
* (right now is implemented Hive builtin authority).
*
* It is initialized with all of the supported roles for the given account using {@link AccountAuthorityUpdateOperation.createFor} automatically, so
* for example: If you want to add a single key to the active level of the account, you can just add the key, without worrying about your other key authorities.
*
* After initialization, you can use the {@link AccountAuthorityUpdateOperation.role} method to retrieve the role instance for the given role level.
*
* @example
* ```ts
* const operation = await AccountAuthorityUpdateOperation.createFor(myChain, "initminer");
*
* const active = operation.role("active");
* active.add(myKey);
*
* const memo = operation.role("memo");
* memo.set(myKey);
* ```
*/
export class AccountAuthorityUpdateOperation extends OperationBase {
private constructor(
private readonly instancesPerContainerName: Map<TRoleContainerNames, RoleCategoryBase<any>>,
private readonly instancesPerRoleName: Map<TRole, RoleCategoryBase<any>>
) {
super();
}
/**
* Creates an instance of AccountAuthorityUpdateOperation with all supported roles pre-initialized for the given account.
*
* @param {IHiveChainInterface} chain chain interface required for online user roles parsing
* @param {TAccountName} account account we will operate on
* @returns {Promise<AccountAuthorityUpdateOperation>} initialized account authority update operation
*/
public static async createFor(chain: IHiveChainInterface, account: TAccountName): Promise<AccountAuthorityUpdateOperation> {
const roles = new Map<TRole, RoleCategoryBase<any>>();
const instances = new Map(AuthorityRoleCategories.map(role => {
const container = new role();
return [container.category, container];
}));
for (const roleClass of instances.values()) {
await roleClass.init(chain, account);
for (const roleName in roleClass.authorities)
roles.set(roleName as TRole, roleClass);
}
return new AccountAuthorityUpdateOperation(instances, roles);
}
/**
* Returns the role instance for the given role level.
*
* @param {KRole} level role level to retrieve
* @returns Role class instance for the given role level
*/
public role<KRole extends TRole>(level: KRole): TRoleKeyToValueMap[KRole] {
const roleInstance = this.instancesPerRoleName.get(level);
if (!roleInstance)
throw new Error(`Role level ${level} is not initialized`);
return roleInstance.authorities[level];
}
/**
* Returns all of the roles for the given role category.
*
* Note: You can differentiate between different role categories by using the `level` property
*
* @param {KRoleContainer} category role category to retrieve
* @returns Iterable of all roles for the given role category
*
* @example
* ```ts
* for(const role of operation.roles("hive"))
* if (role.level === "memo")
* role.set(myKey);
* else
* role.add(myKey);
* ```
*/
public roles<KRoleContainer extends keyof TRoleContainerKeyToValueMap>(category: KRoleContainer): Iterable<TRoleContainerKeyToValueMap[KRoleContainer]> {
const roleInstance = this.instancesPerContainerName.get(category);
if (!roleInstance)
throw new Error(`Role category ${category} is not initialized`);
return Object.values(roleInstance.authorities);
}
/**
* Checks if the authority has changed since the last update and it is possible to transmit this operation (any changes applied)
*/
public get isEffective(): boolean {
let effective = false;
for (const role of this.instancesPerContainerName.values())
effective ||= role.changed;
return effective;
}
/**
* @internal
*/
public finalize(sink: IOperationSink): Iterable<operation> {
const operations: operation[] = [];
for (const role of this.instancesPerContainerName.values())
operations.push(...role.finalize(sink));
if (operations.length === 0)
throw new WaxError("No operations updating account authority generated");
return operations;
}
}