Files
nym/clients/validator/src/index.ts
T
2023-04-05 10:57:45 +02:00

622 lines
20 KiB
TypeScript

import {
ExecuteResult,
InstantiateOptions,
InstantiateResult,
MigrateResult,
UploadResult,
} from '@cosmjs/cosmwasm-stargate';
import { Bip39, Random } from '@cosmjs/crypto';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { Coin, coin as cosmosCoin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate';
import {
ContractStateParams,
Delegation,
Gateway,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNode,
MixNodeBond,
MixNodeCostParams,
MixNodeDetails,
MixNodeRewarding,
MixOwnershipResponse,
OriginalVestingResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
RewardingParams,
StakeSaturationResponse,
UnbondedMixnodeResponse,
VestingAccountInfo,
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
} from '@nymproject/types';
import QueryClient from './query-client';
import SigningClient, { ISigningClient } from './signing-client';
// import { DelegationBlock } from './types/shared';
export interface INymClient {
readonly mixnetContract: string;
readonly vestingContract: string;
}
export default class ValidatorClient implements INymClient {
readonly client: SigningClient | QueryClient;
private readonly denom: string;
private readonly prefix: string;
readonly mixnetContract: string;
readonly vestingContract: string;
readonly mainnetDenom = 'unym';
readonly mainnetPrefix = 'n';
private constructor(
client: SigningClient | QueryClient,
prefix: string,
mixnetContract: string,
vestingContract: string,
denom: string,
) {
this.client = client;
this.prefix = prefix;
this.denom = `u${denom}`;
this.mixnetContract = mixnetContract;
this.vestingContract = vestingContract;
}
static async connect(
mnemonic: string,
nyxdUrl: string,
nymApiUrl: string,
prefix: string,
mixnetContract: string,
vestingContract: string,
denom: string,
): Promise<ValidatorClient> {
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
const signingClient = await SigningClient.connectWithNymSigner(wallet, nyxdUrl, nymApiUrl, prefix, denom);
return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract, denom);
}
static async connectForQuery(
nyxdUrl: string,
nymApiUrl: string,
prefix: string,
mixnetContract: string,
vestingContract: string,
denom: string,
): Promise<ValidatorClient> {
const queryClient = await QueryClient.connectWithNym(nyxdUrl, nymApiUrl);
return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract, denom);
}
public get address(): string {
if (this.client instanceof SigningClient) {
return this.client.clientAddress;
}
return '';
}
private assertSigning() {
if (this.client instanceof QueryClient) {
throw new Error('Tried to perform signing action with a query client!');
}
}
/**
* Generates a random mnemonic, useful for creating new accounts.
* @returns a fresh mnemonic.
*/
static randomMnemonic(): string {
return Bip39.encode(Random.getBytes(32)).toString();
}
/**
* @param mnemonic A mnemonic from which to generate a public/private keypair.
* @param prefix the bech32 address prefix (human readable part)
* @returns the address for this client wallet
*/
static async mnemonicToAddress(mnemonic: string, prefix: string): Promise<string> {
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
const [{ address }] = await wallet.getAccounts();
return address;
}
static async buildWallet(mnemonic: string, prefix: string): Promise<DirectSecp256k1HdWallet> {
const signerOptions = { prefix };
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions);
}
async getBalance(address: string): Promise<Coin> {
return this.client.getBalance(address, this.denom);
}
async getCachedGateways(): Promise<GatewayBond[]> {
return this.client.getCachedGateways();
}
async getCachedMixnodes(): Promise<MixNodeBond[]> {
return this.client.getCachedMixnodes();
}
async getStakeSaturation(mixId: number): Promise<StakeSaturationResponse> {
return this.client.getStakeSaturation(this.mixnetContract, mixId);
}
async getActiveMixnodes(): Promise<MixNodeDetails[]> {
return this.client.getActiveMixnodes();
}
async getUnbondedMixNodeInformation(mixId: number): Promise<UnbondedMixnodeResponse> {
return this.client.getUnbondedMixNodeInformation(this.mixnetContract, mixId);
}
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.client.getRewardedMixnodes();
}
async getMixnodeRewardingDetails(mixId: number): Promise<MixNodeRewarding> {
return this.client.getMixnodeRewardingDetails(this.mixnetContract, mixId);
}
async getOwnedMixnode(address: string): Promise<MixOwnershipResponse> {
return this.client.getOwnedMixnode(this.mixnetContract, address);
}
async ownsGateway(address: string): Promise<GatewayOwnershipResponse> {
return this.client.ownsGateway(this.mixnetContract, address);
}
async getLayerDistribution(): Promise<LayerDistribution> {
return this.client.getLayerDistribution(this.mixnetContract);
}
public async getMixnetContractSettings(): Promise<ContractState> {
return this.client.getStateParams(this.mixnetContract);
}
public async getMixnetContractVersion(): Promise<MixnetContractVersion> {
return this.client.getContractVersion(this.mixnetContract);
}
public async getVestingContractVersion(): Promise<MixnetContractVersion> {
return this.client.getContractVersion(this.vestingContract);
}
public async getSpendableCoins(vestingAccountAddress: string): Promise<MixnetContractVersion> {
return this.client.getSpendableCoins(this.vestingContract, vestingAccountAddress);
}
public async getRewardParams(): Promise<RewardingParams> {
return this.client.getRewardParams(this.mixnetContract);
}
async getUnbondedMixNodes(): Promise<UnbondedMixnodeResponse[]> {
let mixNodes: UnbondedMixnodeResponse[] = [];
const limit = 50;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
}
public async getMixNodeBonds(): Promise<MixNodeBond[]> {
let mixNodes: MixNodeBond[] = [];
const limit = 50;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
}
public async getMixNodesDetailed(): Promise<MixNodeDetails[]> {
let mixNodes: MixNodeDetails[] = [];
const limit = 50;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
}
public async getAllNyxdGateways(): Promise<GatewayBond[]> {
const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract);
return pagedResponse.nodes;
}
/**
* Gets list of all delegations towards particular mixnode.
*
* @param mix_id identity of the node to which the delegation was sent
*/
public async getAllNyxdSingleMixnodeDelegations(mix_id: number): Promise<Delegation[]> {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged(
this.mixnetContract,
mix_id,
limit,
startAfter,
);
delegations = delegations.concat(pagedResponse.delegations);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return delegations;
}
public async getAllNyxdDelegatorDelegations(delegator: string): Promise<Delegation[]> {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged(
this.mixnetContract,
delegator,
limit,
startAfter,
);
delegations = delegations.concat(pagedResponse.delegations);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return delegations;
}
public async getAllNyxdDelegations(): Promise<Delegation[]> {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged(
this.mixnetContract,
limit,
startAfter,
);
delegations = delegations.concat(pagedResponse.delegations);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return delegations;
}
public async getDelegationDetails(mix_id: number, delegator: string): Promise<Delegation> {
return this.client.getDelegationDetails(this.mixnetContract, mix_id, delegator);
}
/**
* Generate a minimum gateway bond required to create a fresh mixnode.
*
* @returns a `Coin` instance containing minimum amount of coins to pledge a gateway.
*/
public async minimumMixnodePledge(): Promise<Coin> {
const stateParams = await this.getMixnetContractSettings();
// we trust the contract to return a valid number
return cosmosCoin(stateParams.params.minimum_mixnode_pledge, this.prefix);
}
/**
* Generate a minimum gateway bond required to create a fresh gateway.
*
* @returns a `Coin` instance containing minimum amount of coins to pledge a gateway.
*/
public async minimumGatewayPledge(): Promise<Coin> {
const stateParams = await this.getMixnetContractSettings();
// we trust the contract to return a valid number
return cosmosCoin(stateParams.params.minimum_gateway_pledge, this.prefix);
}
public async send(
recipientAddress: string,
coins: readonly Coin[],
fee: StdFee | 'auto' | number = 'auto',
memo?: string,
): Promise<DeliverTxResponse> {
this.assertSigning();
const result = await (this.client as ISigningClient).sendTokens(this.address, recipientAddress, coins, fee, memo);
if (isDeliverTxFailure(result)) {
throw new Error(
`Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
);
}
return result;
}
public async executeCustom(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee | 'auto' | number = 'auto',
memo?: string,
): Promise<DeliverTxResponse> {
this.assertSigning();
const result = await (this.client as ISigningClient).signAndBroadcast(signerAddress, messages, fee, memo);
if (isDeliverTxFailure(result)) {
throw new Error(
`Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
);
}
return result;
}
public async upload(
senderAddress: string,
wasmCode: Uint8Array,
fee: StdFee | 'auto' | number = 'auto',
memo?: string,
): Promise<UploadResult> {
this.assertSigning();
return (this.client as ISigningClient).upload(senderAddress, wasmCode, fee, memo);
}
public async instantiate(
senderAddress: string,
codeId: number,
initMsg: Record<string, unknown>,
label: string,
fee: StdFee | 'auto' | number = 'auto',
options?: InstantiateOptions,
): Promise<InstantiateResult> {
this.assertSigning();
return (this.client as ISigningClient).instantiate(senderAddress, codeId, initMsg, label, fee, options);
}
public async migrate(
senderAddress: string,
contractAddress: string,
codeId: number,
migrateMsg: Record<string, unknown>,
fee: StdFee | 'auto' | number = 'auto',
memo?: string,
): Promise<MigrateResult> {
this.assertSigning();
return (this.client as ISigningClient).migrate(senderAddress, contractAddress, codeId, migrateMsg, fee, memo);
}
public async bondMixNode(
mixNode: MixNode,
ownerSignature: string,
costParams: MixNodeCostParams,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).bondMixNode(
this.mixnetContract,
mixNode,
costParams,
ownerSignature,
pledge,
fee,
memo,
);
}
public async unbondMixNode(fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).unbondMixNode(this.mixnetContract, fee, memo);
}
public async bondGateway(
gateway: Gateway,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).bondGateway(this.mixnetContract, gateway, ownerSignature, pledge, fee, memo);
}
public async unbondGateway(fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).unbondGateway(this.mixnetContract, fee, memo);
}
public async delegateToMixNode(
mixId: number,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixId, amount, fee, memo);
}
public async undelegateFromMixNode(
mixId: number,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixId, fee, memo);
}
public async updateMixnodeConfig(
mixId: number,
fee: StdFee | 'auto' | number,
profitPercentage: number,
): Promise<ExecuteResult> {
return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixId, profitPercentage, fee);
}
public async updateContractStateParams(
newParams: ContractStateParams,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).updateContractStateParams(this.mixnetContract, newParams, fee, memo);
}
// VESTING
// TODO - MOVE TO A DIFFERENT FILE
public async getVestingAccountsPaged(): Promise<VestingAccountsPaged> {
return this.client.getVestingAccountsPaged(this.vestingContract);
}
public async getVestingAmountsAccountsPaged(): Promise<VestingAccountsCoinPaged> {
return this.client.getVestingAmountsAccountsPaged(this.vestingContract);
}
public async getLockedTokens(vestingAccountAddress: string): Promise<Coin> {
return this.client.getLockedTokens(this.vestingContract, vestingAccountAddress);
}
public async getSpendableTokens(vestingAccountAddress: string): Promise<Coin> {
return this.client.getSpendableTokens(this.vestingContract, vestingAccountAddress);
}
public async getVestedTokens(vestingAccountAddress: string): Promise<Coin> {
return this.client.getVestedTokens(this.vestingContract, vestingAccountAddress);
}
public async getVestingTokens(vestingAccountAddress: string): Promise<Coin> {
return this.client.getVestingTokens(this.vestingContract, vestingAccountAddress);
}
public async getSpendableVestedTokens(vestingAccountAddress: string): Promise<Coin> {
return this.client.getSpendableVestedTokens(this.vestingContract, vestingAccountAddress);
}
public async getSpendableRewards(vestingAccountAddress: string): Promise<Coin> {
return this.client.getSpendableRewards(this.vestingContract, vestingAccountAddress);
}
public async getDelegatedCoins(vestingAccountAddress: string): Promise<Coin> {
return this.client.getDelegatedCoins(this.vestingContract, vestingAccountAddress);
}
public async getPledgedCoins(vestingAccountAddress: string): Promise<Coin> {
return this.client.getPledgedCoins(this.vestingContract, vestingAccountAddress);
}
public async getStakedCoins(vestingAccountAddress: string): Promise<Coin> {
return this.client.getStakedCoins(this.vestingContract, vestingAccountAddress);
}
public async getWithdrawnCoins(vestingAccountAddress: string): Promise<Coin> {
return this.client.getWithdrawnCoins(this.vestingContract, vestingAccountAddress);
}
public async getStartTime(vestingAccountAddress: string): Promise<string> {
return this.client.getStartTime(this.vestingContract, vestingAccountAddress);
}
public async getEndTime(vestingAccountAddress: string): Promise<string> {
return this.client.getEndTime(this.vestingContract, vestingAccountAddress);
}
public async getOriginalVestingDetails(vestingAccountAddress: string): Promise<OriginalVestingResponse> {
return this.client.getOriginalVestingDetails(this.vestingContract, vestingAccountAddress);
}
public async getHistoricStakingRewards(vestingAccountAddress: string): Promise<Coin> {
return this.client.getHistoricStakingRewards(this.vestingContract, vestingAccountAddress);
}
public async getAccountDetails(address: string): Promise<VestingAccountInfo> {
return this.client.getAccountDetails(this.vestingContract, address);
}
public async getMixnode(address: string): Promise<VestingAccountNode> {
return this.client.getMixnode(this.vestingContract, address);
}
public async getGateway(address: string): Promise<VestingAccountNode> {
return this.client.getGateway(this.vestingContract, address);
}
public async getDelegationTimes(mix_id: number, delegatorAddress: string): Promise<DelegationTimes> {
return this.client.getDelegationTimes(this.vestingContract, mix_id, delegatorAddress);
}
public async getAllDelegations(): Promise<Delegations> {
return this.client.getAllDelegations(this.vestingContract);
}
public async getDelegation(address: string, mix_id: number): Promise<DelegationBlock> {
return this.client.getDelegation(this.vestingContract, address, mix_id );
}
public async getTotalDelegationAmount(address: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
return this.client.getTotalDelegationAmount(this.vestingContract, address, mix_id, block_timestamp_sec);
}
public async getCurrentVestingPeriod(address: string): Promise<Period> {
return this.client.getCurrentVestingPeriod(this.vestingContract, address);
}
}