0xweb
Version:
Contract package manager and other web3 tools
217 lines (193 loc) • 7.84 kB
text/typescript
import alot from 'alot';
import type { Config as Appcfg } from 'appcfg/Config';
import { Config } from './config/Config';
import { IAccount } from "./models/TAccount";
import { JsonArrayStore } from './json/JsonArrayStore';
import { TAddress } from './models/TAddress';
import { TPlatform } from './models/TPlatform';
import { TEth } from './models/TEth';
import { $address } from './utils/$address';
import { $is } from './utils/$is';
import { $require } from './utils/$require';
import { $ns } from './ns/utils/$ns';
import { $sig } from './utils/$sig';
import { Web3ClientFactory } from './clients/Web3ClientFactory';
import { NameService } from './ns/NameService';
export class ChainAccountService {
private storeConfig: ConfigStore;
private storeFs: ConfigStore;
private storeCustom: IAccountsStore;
private writable: 'config' | 'fs' | 'custom';
constructor (params?: {
store?: IAccountsStore
writable?: 'config' | 'fs' | 'custom'
config?: InstanceType<typeof Appcfg>
}) {
this.storeConfig = new ConfigStore(params?.config);
this.storeFs = new FileStore();
this.storeCustom = params?.store;
this.writable = params?.writable;
}
async get <T extends IAccount = IAccount> (key: TEth.Hex | TAddress): Promise<T>
async get <T extends IAccount = IAccount> (mnemonic: string, params?: { index?: number, path?: string }): Promise<T>
async get <T extends IAccount = IAccount> (ns: string, params?: { platform?: TPlatform }): Promise<T>
async get <T extends IAccount = IAccount> (name: string, params?: { platform?: TPlatform }): Promise<T>
async get <T extends IAccount = IAccount> (address: TAddress, params?: { platform?: TPlatform }): Promise<T>
async get<T extends IAccount = IAccount> (mix: string | TEth.Hex | TAddress, params?: { platform?: TPlatform , index?: number, path?: string }): Promise<T>
async get<T extends IAccount = IAccount> (mix: string | TEth.Hex | TAddress, params?: { platform?: TPlatform , index?: number, path?: string }): Promise<T> {
if ($is.Hex(mix) && mix.length >= 64) {
return await $sig.$account.fromKey(mix) as T;
}
if ($address.isValid(mix) === false) {
// Check mnemonic
let isMnemonic = /^(?:[a-zA-Z]+ ){11,}[a-zA-Z]+$/.test(mix);
if (isMnemonic) {
return await $sig.$account.fromMnemonic(mix, params?.index ?? params.path ?? 0) as T;
}
// Check NS
if ($ns.isNsAlike(mix)) {
let client = Web3ClientFactory.get(params?.platform ?? 'eth');
let ns = new NameService(client);
if (ns.supports(mix)) {
let { address } = await ns.getAddress(mix);
$require.Address(address, `Address from ${mix} not resolve from NameService`);
mix = address;
}
}
}
let acc = await this.storeConfig.get(mix);
if (acc == null) {
acc = await this.storeFs.get(mix);
}
if (acc == null && this.storeCustom != null) {
acc = await this.storeCustom.get(mix);
}
return acc as T;
}
static async get<T = IAccount> (name: string, params?: {
platform?: TPlatform
store?: IAccountsStore
writable?: 'config' | 'fs' | 'custom'
config?: Appcfg
}): Promise<T> {
let service = new ChainAccountService(params)
return service.get<T>(name, { platform: params?.platform });
}
async getAll (): Promise<IAccount[]> {
let [ configAccounts, fsAccounts, customAccounts ] = await Promise.all([
this.storeConfig.getAll(),
this.storeFs.getAll(),
this.storeCustom?.getAll(),
]);
return [
...(configAccounts ?? []),
...(fsAccounts ?? []),
...(customAccounts ?? []),
];
}
async create (opts: { name: string, platform?: TPlatform }) {
let current = await this.get(opts.name, { platform: opts.platform });
if (current != null) {
return current;
}
let account = $sig.$account.generate(opts);
let store = this.getWritableStore();
await store.save(account);
return account;
}
async createMany (names: string[], platform: TPlatform) {
let newAccounts = [];
let accounts = await alot(names).mapAsync(async name => {
let current = await this.get(name, { platform });
if (current) {
return current;
}
let account = $sig.$account.generate({ name, platform });
newAccounts.push(account)
return account;
}).toArrayAsync();
if (newAccounts.length > 0) {
let store = this.getWritableStore();
await store.saveMany(newAccounts);
}
return accounts;
}
private getWritableStore (): IAccountsStore {
switch (this.writable) {
case 'config':
return this.storeConfig;
case 'fs':
return this.storeFs;
case 'custom':
return this.storeCustom;
default:
return this.storeCustom ?? this.storeConfig ?? this.storeFs;
}
}
}
export interface IAccountsStore {
get (name: string): Promise<IAccount>
get (address: TAddress): Promise<IAccount>
getAll (): Promise<IAccount[]>
save (account: IAccount): Promise<void>
saveMany (accounts: IAccount[]): Promise<void>
}
class FileStore implements IAccountsStore {
private fs = new JsonArrayStore <IAccount> ({
path: './db/accounts/accounts.json',
key: x => x.address,
format: true,
})
async get (name: string): Promise<IAccount>
async get (address: TAddress): Promise<IAccount>
async get (mix: string | TAddress): Promise<IAccount> {
let accounts = await this.fs.getAll();
if ($address.isValid(mix)) {
return accounts.find(x => $address.eq(mix, x.address));
}
return accounts.find(x => x.name === mix);
}
async getAll () {
return await this.fs.getAll();
}
async save (account: IAccount) {
await this.fs.upsert(account);
}
async saveMany (accounts: IAccount[]) {
await this.fs.upsertMany(accounts);
}
}
class ConfigStore implements IAccountsStore {
constructor (private config?: Appcfg) {
}
async get(name: string): Promise<IAccount>;
async get(address: string): Promise<IAccount>;
async get(mix: string | TAddress): Promise<IAccount> {
let accounts = await this.getAll();
if ($address.isValid(mix)) {
return accounts.find(x => $address.eq(mix, x.address));
}
return accounts.find(x => x.name === mix);
}
async getAll(): Promise<IAccount[]> {
let config: any = this.config ?? await Config.get();
let accounts = config.accounts ?? [];
return accounts;
}
async save(account: IAccount): Promise<void> {
let accounts = await this.getAll();
let current = await this.get(account.name ?? account.address);
if (current != null) {
return;
}
// Not found
accounts.push(account);
await this.saveMany(accounts);
}
async saveMany(accounts: IAccount[]): Promise<void> {
let config = this.config ?? await Config.fetch();
let sources = $require.notNull(config?.$sources?.array, `Invalid config library. Fetched "config.$sources.array" is undefined`)
let accountsConfig = sources.find(x =>x.data.name === 'accounts');
await accountsConfig.write({ accounts }, false);
}
}