|
@@ -1,13 +1,12 @@
|
|
|
import BN from 'bn.js'
|
|
|
-import { registerJoystreamTypes } from '@joystream/types/'
|
|
|
+import { types } from '@joystream/types/'
|
|
|
import { ApiPromise, WsProvider } from '@polkadot/api'
|
|
|
-import { QueryableStorageMultiArg } from '@polkadot/api/types'
|
|
|
+import { QueryableStorageMultiArg, SubmittableExtrinsic, QueryableStorageEntry } from '@polkadot/api/types'
|
|
|
import { formatBalance } from '@polkadot/util'
|
|
|
-import { Hash, Balance, Moment, BlockNumber } from '@polkadot/types/interfaces'
|
|
|
+import { Balance, Moment, BlockNumber } from '@polkadot/types/interfaces'
|
|
|
import { KeyringPair } from '@polkadot/keyring/types'
|
|
|
-import { Codec } from '@polkadot/types/types'
|
|
|
-import { Option, Vec } from '@polkadot/types'
|
|
|
-import { u32 } from '@polkadot/types/primitive'
|
|
|
+import { Codec, CodecArg } from '@polkadot/types/types'
|
|
|
+import { Option, Vec, UInt } from '@polkadot/types'
|
|
|
import {
|
|
|
AccountSummary,
|
|
|
CouncilInfoObj,
|
|
@@ -24,7 +23,7 @@ import {
|
|
|
UnstakingPeriods,
|
|
|
StakingPolicyUnstakingPeriodKey,
|
|
|
} from './Types'
|
|
|
-import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types'
|
|
|
+import { DeriveBalancesAll } from '@polkadot/api-derive/types'
|
|
|
import { CLIError } from '@oclif/errors'
|
|
|
import ExitCodes from './ExitCodes'
|
|
|
import {
|
|
@@ -46,12 +45,11 @@ import {
|
|
|
import { MemberId, Membership } from '@joystream/types/members'
|
|
|
import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards'
|
|
|
import { Stake, StakeId } from '@joystream/types/stake'
|
|
|
-import { LinkageResult } from '@polkadot/types/codec/Linkage'
|
|
|
|
|
|
import { InputValidationLengthConstraint } from '@joystream/types/common'
|
|
|
|
|
|
export const DEFAULT_API_URI = 'ws://localhost:9944/'
|
|
|
-const DEFAULT_DECIMALS = new u32(12)
|
|
|
+const DEFAULT_DECIMALS = new BN(12)
|
|
|
|
|
|
// Mapping of working group to api module
|
|
|
export const apiModuleByGroup: { [key in WorkingGroups]: string } = {
|
|
@@ -72,8 +70,7 @@ export default class Api {
|
|
|
|
|
|
private static async initApi(apiUri: string = DEFAULT_API_URI): Promise<ApiPromise> {
|
|
|
const wsProvider: WsProvider = new WsProvider(apiUri)
|
|
|
- registerJoystreamTypes()
|
|
|
- const api = await ApiPromise.create({ provider: wsProvider })
|
|
|
+ const api = await ApiPromise.create({ provider: wsProvider, types })
|
|
|
|
|
|
// Initializing some api params based on pioneer/packages/react-api/Api.tsx
|
|
|
const [properties] = await Promise.all([api.rpc.system.properties()])
|
|
@@ -110,8 +107,10 @@ export default class Api {
|
|
|
return results
|
|
|
}
|
|
|
|
|
|
- async getAccountsBalancesInfo(accountAddresses: string[]): Promise<DerivedBalances[]> {
|
|
|
- const accountsBalances: DerivedBalances[] = await this._api.derive.balances.votingBalances(accountAddresses)
|
|
|
+ async getAccountsBalancesInfo(accountAddresses: string[]): Promise<DeriveBalancesAll[]> {
|
|
|
+ const accountsBalances: DeriveBalancesAll[] = await Promise.all(
|
|
|
+ accountAddresses.map((addr) => this._api.derive.balances.all(addr))
|
|
|
+ )
|
|
|
|
|
|
return accountsBalances
|
|
|
}
|
|
@@ -119,7 +118,7 @@ export default class Api {
|
|
|
// Get on-chain data related to given account.
|
|
|
// For now it's just account balances
|
|
|
async getAccountSummary(accountAddresses: string): Promise<AccountSummary> {
|
|
|
- const balances: DerivedBalances = (await this.getAccountsBalancesInfo([accountAddresses]))[0]
|
|
|
+ const balances: DeriveBalancesAll = (await this.getAccountsBalancesInfo([accountAddresses]))[0]
|
|
|
// TODO: Some more information can be fetched here in the future
|
|
|
|
|
|
return { balances }
|
|
@@ -146,34 +145,29 @@ export default class Api {
|
|
|
return createCouncilInfoObj(...results)
|
|
|
}
|
|
|
|
|
|
- // TODO: This formula is probably not too good, so some better implementation will be required in the future
|
|
|
- async estimateFee(account: KeyringPair, recipientAddr: string, amount: BN): Promise<BN> {
|
|
|
- const transfer = this._api.tx.balances.transfer(recipientAddr, amount)
|
|
|
- const signature = account.sign(transfer.toU8a())
|
|
|
- const transactionByteSize: BN = new BN(transfer.encodedLength + signature.length)
|
|
|
-
|
|
|
- const fees: DerivedFees = await this._api.derive.balances.fees()
|
|
|
-
|
|
|
- const estimatedFee = fees.transactionBaseFee.add(fees.transactionByteFee.mul(transactionByteSize))
|
|
|
-
|
|
|
- return estimatedFee
|
|
|
+ async estimateFee(account: KeyringPair, tx: SubmittableExtrinsic<'promise'>): Promise<Balance> {
|
|
|
+ const paymentInfo = await tx.paymentInfo(account)
|
|
|
+ return paymentInfo.partialFee
|
|
|
}
|
|
|
|
|
|
- async transfer(account: KeyringPair, recipientAddr: string, amount: BN): Promise<Hash> {
|
|
|
- const txHash = await this._api.tx.balances.transfer(recipientAddr, amount).signAndSend(account)
|
|
|
- return txHash
|
|
|
+ createTransferTx(recipient: string, amount: BN) {
|
|
|
+ return this._api.tx.balances.transfer(recipient, amount)
|
|
|
}
|
|
|
|
|
|
// Working groups
|
|
|
- // TODO: This is a lot of repeated logic from "/pioneer/joy-roles/src/transport.substrate.ts"
|
|
|
- // (although simplified a little bit)
|
|
|
- // Hopefully this will be refactored to "joystream-js" soon
|
|
|
- protected singleLinkageResult<T extends Codec>(result: LinkageResult) {
|
|
|
- return result[0] as T
|
|
|
- }
|
|
|
+ // TODO: This is a lot of repeated logic from "/pioneer/joy-utils/transport"
|
|
|
+ // It will be refactored to "joystream-js" soon
|
|
|
+ async entriesByIds<IDType extends UInt, ValueType extends Codec>(
|
|
|
+ apiMethod: QueryableStorageEntry<'promise'>,
|
|
|
+ firstKey?: CodecArg // First key in case of double maps
|
|
|
+ ): Promise<[IDType, ValueType][]> {
|
|
|
+ const entries: [IDType, ValueType][] = (await apiMethod.entries<ValueType>(firstKey)).map(([storageKey, value]) => [
|
|
|
+ // If double-map (first key is provided), we map entries by second key
|
|
|
+ storageKey.args[firstKey !== undefined ? 1 : 0] as IDType,
|
|
|
+ value,
|
|
|
+ ])
|
|
|
|
|
|
- protected multiLinkageResult<K extends Codec, V extends Codec>(result: LinkageResult): [Vec<K>, Vec<V>] {
|
|
|
- return [result[0] as Vec<K>, result[1] as Vec<V>]
|
|
|
+ return entries.sort((a, b) => a[0].toNumber() - b[0].toNumber())
|
|
|
}
|
|
|
|
|
|
protected async blockHash(height: number): Promise<string> {
|
|
@@ -214,7 +208,7 @@ export default class Api {
|
|
|
}
|
|
|
|
|
|
protected async stakeValue(stakeId: StakeId): Promise<Balance> {
|
|
|
- const stake = this.singleLinkageResult<Stake>((await this._api.query.stake.stakes(stakeId)) as LinkageResult)
|
|
|
+ const stake = await this._api.query.stake.stakes<Stake>(stakeId)
|
|
|
return stake.value
|
|
|
}
|
|
|
|
|
@@ -223,8 +217,8 @@ export default class Api {
|
|
|
}
|
|
|
|
|
|
protected async workerReward(relationshipId: RewardRelationshipId): Promise<Reward> {
|
|
|
- const rewardRelationship = this.singleLinkageResult<RewardRelationship>(
|
|
|
- (await this._api.query.recurringRewards.rewardRelationships(relationshipId)) as LinkageResult
|
|
|
+ const rewardRelationship = await this._api.query.recurringRewards.rewardRelationships<RewardRelationship>(
|
|
|
+ relationshipId
|
|
|
)
|
|
|
|
|
|
return {
|
|
@@ -266,18 +260,16 @@ export default class Api {
|
|
|
}
|
|
|
|
|
|
async workerByWorkerId(group: WorkingGroups, workerId: number): Promise<Worker> {
|
|
|
- const nextId = (await this.workingGroupApiQuery(group).nextWorkerId()) as WorkerId
|
|
|
+ const nextId = await this.workingGroupApiQuery(group).nextWorkerId<WorkerId>()
|
|
|
|
|
|
// This is chain specfic, but if next id is still 0, it means no workers have been added yet
|
|
|
if (workerId < 0 || workerId >= nextId.toNumber()) {
|
|
|
throw new CLIError('Invalid worker id!')
|
|
|
}
|
|
|
|
|
|
- const worker = this.singleLinkageResult<Worker>(
|
|
|
- (await this.workingGroupApiQuery(group).workerById(workerId)) as LinkageResult
|
|
|
- )
|
|
|
+ const worker = await this.workingGroupApiQuery(group).workerById<Worker>(workerId)
|
|
|
|
|
|
- if (!worker.is_active) {
|
|
|
+ if (worker.isEmpty) {
|
|
|
throw new CLIError('This worker is not active anymore')
|
|
|
}
|
|
|
|
|
@@ -286,67 +278,51 @@ export default class Api {
|
|
|
|
|
|
async groupMember(group: WorkingGroups, workerId: number) {
|
|
|
const worker = await this.workerByWorkerId(group, workerId)
|
|
|
- return await this.parseGroupMember(new WorkerId(workerId), worker)
|
|
|
+ return await this.parseGroupMember(this._api.createType('WorkerId', workerId), worker)
|
|
|
}
|
|
|
|
|
|
async groupMembers(group: WorkingGroups): Promise<GroupMember[]> {
|
|
|
- const nextId = (await this.workingGroupApiQuery(group).nextWorkerId()) as WorkerId
|
|
|
+ const workerEntries = await this.entriesByIds<WorkerId, Worker>(this.workingGroupApiQuery(group).workerById)
|
|
|
|
|
|
- // This is chain specfic, but if next id is still 0, it means no workers have been added yet
|
|
|
- if (nextId.eq(0)) {
|
|
|
- return []
|
|
|
- }
|
|
|
-
|
|
|
- const [workerIds, workers] = this.multiLinkageResult<WorkerId, Worker>(
|
|
|
- (await this.workingGroupApiQuery(group).workerById()) as LinkageResult
|
|
|
+ const groupMembers: GroupMember[] = await Promise.all(
|
|
|
+ workerEntries.map(([id, worker]) => this.parseGroupMember(id, worker))
|
|
|
)
|
|
|
|
|
|
- const groupMembers: GroupMember[] = []
|
|
|
- for (const [index, worker] of Object.entries(workers.toArray())) {
|
|
|
- const workerId = workerIds[parseInt(index)]
|
|
|
- if (worker.is_active) {
|
|
|
- groupMembers.push(await this.parseGroupMember(workerId, worker))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return groupMembers.reverse()
|
|
|
+ return groupMembers.reverse() // Sort by newest
|
|
|
}
|
|
|
|
|
|
async openingsByGroup(group: WorkingGroups): Promise<GroupOpening[]> {
|
|
|
- const openings: GroupOpening[] = []
|
|
|
- const nextId = (await this.workingGroupApiQuery(group).nextOpeningId()) as OpeningId
|
|
|
+ let openings: GroupOpening[] = []
|
|
|
+ const nextId = await this.workingGroupApiQuery(group).nextOpeningId<OpeningId>()
|
|
|
|
|
|
// This is chain specfic, but if next id is still 0, it means no openings have been added yet
|
|
|
if (!nextId.eq(0)) {
|
|
|
- const highestId = nextId.toNumber() - 1
|
|
|
- for (let i = highestId; i >= 0; i--) {
|
|
|
- openings.push(await this.groupOpening(group, i))
|
|
|
- }
|
|
|
+ const ids = Array.from(Array(nextId.toNumber()).keys()).reverse() // Sort by newest
|
|
|
+ openings = await Promise.all(ids.map((id) => this.groupOpening(group, id)))
|
|
|
}
|
|
|
|
|
|
return openings
|
|
|
}
|
|
|
|
|
|
protected async hiringOpeningById(id: number | OpeningId): Promise<Opening> {
|
|
|
- const result = (await this._api.query.hiring.openingById(id)) as LinkageResult
|
|
|
- return this.singleLinkageResult<Opening>(result)
|
|
|
+ const result = await this._api.query.hiring.openingById<Opening>(id)
|
|
|
+ return result
|
|
|
}
|
|
|
|
|
|
protected async hiringApplicationById(id: number | ApplicationId): Promise<Application> {
|
|
|
- const result = (await this._api.query.hiring.applicationById(id)) as LinkageResult
|
|
|
- return this.singleLinkageResult<Application>(result)
|
|
|
+ const result = await this._api.query.hiring.applicationById<Application>(id)
|
|
|
+ return result
|
|
|
}
|
|
|
|
|
|
async wgApplicationById(group: WorkingGroups, wgApplicationId: number): Promise<WGApplication> {
|
|
|
- const nextAppId = (await this.workingGroupApiQuery(group).nextApplicationId()) as ApplicationId
|
|
|
+ const nextAppId = await this.workingGroupApiQuery(group).nextApplicationId<ApplicationId>()
|
|
|
|
|
|
if (wgApplicationId < 0 || wgApplicationId >= nextAppId.toNumber()) {
|
|
|
throw new CLIError('Invalid working group application ID!')
|
|
|
}
|
|
|
|
|
|
- return this.singleLinkageResult<WGApplication>(
|
|
|
- (await this.workingGroupApiQuery(group).applicationById(wgApplicationId)) as LinkageResult
|
|
|
- )
|
|
|
+ const result = await this.workingGroupApiQuery(group).applicationById<WGApplication>(wgApplicationId)
|
|
|
+ return result
|
|
|
}
|
|
|
|
|
|
protected async parseApplication(wgApplicationId: number, wgApplication: WGApplication): Promise<GroupApplication> {
|
|
@@ -376,18 +352,15 @@ export default class Api {
|
|
|
}
|
|
|
|
|
|
protected async groupOpeningApplications(group: WorkingGroups, wgOpeningId: number): Promise<GroupApplication[]> {
|
|
|
- const applications: GroupApplication[] = []
|
|
|
-
|
|
|
- const nextAppId = (await this.workingGroupApiQuery(group).nextApplicationId()) as ApplicationId
|
|
|
- for (let i = 0; i < nextAppId.toNumber(); i++) {
|
|
|
- const wgApplication = await this.wgApplicationById(group, i)
|
|
|
- if (wgApplication.opening_id.toNumber() !== wgOpeningId) {
|
|
|
- continue
|
|
|
- }
|
|
|
- applications.push(await this.parseApplication(i, wgApplication))
|
|
|
- }
|
|
|
+ const wgApplicationEntries = await this.entriesByIds<ApplicationId, WGApplication>(
|
|
|
+ this.workingGroupApiQuery(group).applicationById
|
|
|
+ )
|
|
|
|
|
|
- return applications
|
|
|
+ return Promise.all(
|
|
|
+ wgApplicationEntries
|
|
|
+ .filter(([id, wgApplication]) => wgApplication.opening_id.eqn(wgOpeningId))
|
|
|
+ .map(([id, wgApplication]) => this.parseApplication(id.toNumber(), wgApplication))
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
async groupOpening(group: WorkingGroups, wgOpeningId: number): Promise<GroupOpening> {
|
|
@@ -397,9 +370,7 @@ export default class Api {
|
|
|
throw new CLIError('Invalid working group opening ID!')
|
|
|
}
|
|
|
|
|
|
- const groupOpening = this.singleLinkageResult<WGOpening>(
|
|
|
- (await this.workingGroupApiQuery(group).openingById(wgOpeningId)) as LinkageResult
|
|
|
- )
|
|
|
+ const groupOpening = await this.workingGroupApiQuery(group).openingById<WGOpening>(wgOpeningId)
|
|
|
|
|
|
const openingId = groupOpening.hiring_opening_id.toNumber()
|
|
|
const opening = await this.hiringOpeningById(openingId)
|
|
@@ -493,11 +464,11 @@ export default class Api {
|
|
|
}
|
|
|
|
|
|
async getMemberIdsByControllerAccount(address: string): Promise<MemberId[]> {
|
|
|
- const ids = (await this._api.query.members.memberIdsByControllerAccountId(address)) as Vec<MemberId>
|
|
|
+ const ids = await this._api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address)
|
|
|
return ids.toArray()
|
|
|
}
|
|
|
|
|
|
async workerExitRationaleConstraint(group: WorkingGroups): Promise<InputValidationLengthConstraint> {
|
|
|
- return (await this.workingGroupApiQuery(group).workerExitRationaleText()) as InputValidationLengthConstraint
|
|
|
+ return await this.workingGroupApiQuery(group).workerExitRationaleText<InputValidationLengthConstraint>()
|
|
|
}
|
|
|
}
|