=
{
rowRenderer: (rowData: KeyValueData) => {
// we have to figure if this is the row for validator homepage, hench an anchor
- const linkOrText = rowData.value.startsWith("https:") ? (
+ const linkOrText = /^https?:/.test(rowData.value) ? (
{rowData.value}
@@ -59,7 +60,7 @@ const getMyStakingWithValidatorConfigurations = (
{truncateInMiddle(stakingPosition.owner || "", 5, 5)} |
{stakingPosition.stakingStatus} |
- NAM {stakingPosition.stakedAmount}{" "}
+ {showMaybeNam(stakingPosition.stakedAmount)}{" "}
{
setModalState(ModalState.Unbond);
@@ -101,8 +102,12 @@ const validatorToDataRows = (
}
return [
{ uuid: "1", key: "Name", value: truncateInMiddle(validator.name, 5, 5) },
- { uuid: "2", key: "Commission", value: validator.commission },
- { uuid: "3", key: "Voting Power", value: validator.votingPower },
+ {
+ uuid: "2",
+ key: "Commission",
+ value: formatPercentage(validator.commission),
+ },
+ { uuid: "3", key: "Voting Power", value: validator.votingPower?.toString() ?? "" },
{
uuid: "4",
key: "Description",
diff --git a/apps/namada-interface/src/App/StakingAndGovernance/StakingAndGovernance.tsx b/apps/namada-interface/src/App/StakingAndGovernance/StakingAndGovernance.tsx
index 21aac1330f..ead310a39f 100644
--- a/apps/namada-interface/src/App/StakingAndGovernance/StakingAndGovernance.tsx
+++ b/apps/namada-interface/src/App/StakingAndGovernance/StakingAndGovernance.tsx
@@ -45,7 +45,7 @@ export const StakingAndGovernance = (): JSX.Element => {
const [_integration, _status, withConnection] =
useIntegrationConnection(chainId);
- const { validators, myValidators, selectedValidatorId, myStakingPositions } =
+ const { validators, selectedValidatorId, myStakingPositions } =
stakingAndGovernance;
// we need one of the sub routes, staking alone has nothing
@@ -97,7 +97,6 @@ export const StakingAndGovernance = (): JSX.Element => {
({
+import { getToast, Toasts } from "slices/transfers";
+import { actions as notificationsActions } from "slices/notifications";
+
+const toValidator = (
+ [address, stake]: [string, string | null]
+): Validator => ({
uuid: address,
name: address,
- // TODO: voting power is multiplied by votes_per_token value defined in genesis file
- // currently it is 10
- votingPower: new BigNumber(votingPower).multipliedBy(10).toString(),
+ votingPower: stake === null ? undefined : new BigNumber(stake),
homepageUrl: "http://namada.net",
- commission: "TBD",
+ commission: new BigNumber(0), // TODO: implement commission
description: "TBD",
});
const toMyValidators = (
acc: MyValidators[],
- [_, validator, stake]: [string, string, string]
+ [_, validator, stake, unbonded, withdrawable]: [string, string, string, string, string]
): MyValidators[] => {
const index = acc.findIndex((myValidator) => myValidator.uuid === validator);
const v = acc[index];
@@ -49,8 +54,14 @@ const toMyValidators = (
];
const stakedAmount = new BigNumber(stake)
- .plus(new BigNumber(v?.stakedAmount || 0))
- .toString();
+ .plus(new BigNumber(v?.stakedAmount || 0));
+
+ const unbondedAmount =
+ (new BigNumber(unbonded)).plus(new BigNumber(v?.unbondedAmount || 0));
+
+ const withdrawableAmount =
+ (new BigNumber(withdrawable)).plus(new BigNumber(v?.withdrawableAmount || 0));
+
return [
...sliceFn(acc, index),
@@ -58,7 +69,9 @@ const toMyValidators = (
uuid: validator,
stakingStatus: "Bonded",
stakedAmount,
- validator: toValidator([validator, stakedAmount]),
+ unbondedAmount,
+ withdrawableAmount,
+ validator: toValidator([validator, stakedAmount.toString()]),
},
];
};
@@ -70,14 +83,12 @@ const toStakingPosition = ([owner, validator, stake]: [
]): StakingPosition => ({
uuid: owner + validator,
stakingStatus: "Bonded",
- stakedAmount: stake,
+ stakedAmount: new BigNumber(stake),
owner,
totalRewards: "TBD",
validatorId: validator,
});
-// this retrieves the validators
-// this dispatches further actions that are depending on
-// validators data
+
export const fetchValidators = createAsyncThunk<
{ allValidators: Validator[] },
void,
@@ -87,9 +98,12 @@ export const fetchValidators = createAsyncThunk<
const { rpc } = chains[chainId];
const query = new Query(rpc);
- const allValidators = (await query.query_all_validators()).map(toValidator);
+ const queryResult =
+ (await query.query_all_validators()) as [string, string | null][];
+ const allValidators = queryResult.map(toValidator);
thunkApi.dispatch(fetchMyValidators(allValidators));
+
return Promise.resolve({ allValidators });
});
@@ -161,18 +175,50 @@ export const postNewBonding = createAsyncThunk<
const { chainId } = thunkApi.getState().settings;
const integration = getIntegration(chainId);
const signer = integration.signer() as Signer;
- await signer.submitBond({
- source: change.owner,
- validator: change.validatorId,
- amount: new BigNumber(change.amount),
- nativeToken: Tokens.NAM.address || "",
- tx: {
- token: Tokens.NAM.address || "",
- feeAmount: new BigNumber(0),
- gasLimit: new BigNumber(0),
- chainId,
- },
- });
+
+ const toastId = `${thunkApi.requestId}-transfer`;
+
+ thunkApi.dispatch(
+ notificationsActions.createToast(
+ getToast(toastId, Toasts.TransferStarted)({
+ msgId: "Staking..."
+ })
+ )
+ );
+
+ let success = true;
+ try {
+ await signer.submitBond({
+ source: change.owner,
+ validator: change.validatorId,
+ amount: change.amount,
+ nativeToken: Tokens.NAM.address || "",
+ tx: {
+ token: Tokens.NAM.address || "",
+ feeAmount: new BigNumber(0),
+ gasLimit: new BigNumber(0),
+ chainId,
+ },
+ });
+ } catch (e) {
+ success = false;
+ }
+
+ thunkApi.dispatch(
+ notificationsActions.createToast(
+ getToast(toastId, Toasts.TransferCompleted)({
+ success,
+ msgId: success
+ ? "Staking completed"
+ : "Staking did not complete"
+ })
+ )
+ );
+
+ if (success) {
+ thunkApi.dispatch(fetchBalances());
+ thunkApi.dispatch(fetchValidators());
+ }
});
// we post an unstake transaction
@@ -188,15 +234,42 @@ export const postNewUnbonding = createAsyncThunk<
const { chainId } = thunkApi.getState().settings;
const integration = getIntegration(chainId);
const signer = integration.signer() as Signer;
- await signer.submitUnbond({
- source: change.owner,
- validator: change.validatorId,
- amount: new BigNumber(change.amount),
- tx: {
- token: Tokens.NAM.address || "",
- feeAmount: new BigNumber(0),
- gasLimit: new BigNumber(0),
- chainId,
- },
- });
+
+ const toastId = `${thunkApi.requestId}-transfer`;
+
+ thunkApi.dispatch(
+ notificationsActions.createToast(
+ getToast(toastId, Toasts.TransferStarted)({
+ msgId: "Unbonding..."
+ })
+ )
+ );
+
+ let success = true;
+ try {
+ await signer.submitUnbond({
+ source: change.owner,
+ validator: change.validatorId,
+ amount: change.amount,
+ tx: {
+ token: Tokens.NAM.address || "",
+ feeAmount: new BigNumber(0),
+ gasLimit: new BigNumber(0),
+ chainId,
+ },
+ });
+ } catch (e) {
+ success = false;
+ }
+
+ thunkApi.dispatch(
+ notificationsActions.createToast(
+ getToast(toastId, Toasts.TransferCompleted)({
+ success,
+ msgId: success
+ ? "Unbonding completed"
+ : "Unbonding did not complete"
+ })
+ )
+ );
});
diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/fakeData.ts b/apps/namada-interface/src/slices/StakingAndGovernance/fakeData.ts
index e6480bcb5a..e3c81361bc 100644
--- a/apps/namada-interface/src/slices/StakingAndGovernance/fakeData.ts
+++ b/apps/namada-interface/src/slices/StakingAndGovernance/fakeData.ts
@@ -1,3 +1,5 @@
+import BigNumber from "bignumber.js";
+
import { Validator, StakingPosition, MyBalanceEntry } from "./types";
export const myBalancesData: MyBalanceEntry[] = [
{
@@ -30,7 +32,7 @@ export const myStakingData: StakingPosition[] = [
{
uuid: "1",
stakingStatus: "Bonded",
- stakedAmount: "10.00",
+ stakedAmount: new BigNumber(10_000_000),
owner: "some-owner",
totalRewards: "0.55",
validatorId: "polychain-capital",
@@ -38,7 +40,7 @@ export const myStakingData: StakingPosition[] = [
{
uuid: "2",
stakingStatus: "Bonded Pending",
- stakedAmount: "3.00",
+ stakedAmount: new BigNumber(3_000_000),
owner: "some-owner",
totalRewards: "0.15",
validatorId: "coinbase-custody",
@@ -46,7 +48,7 @@ export const myStakingData: StakingPosition[] = [
{
uuid: "3",
stakingStatus: "Unboding (22 days left)",
- stakedAmount: "20.00",
+ stakedAmount: new BigNumber(20_000_000),
owner: "some-owner",
totalRewards: "1.05",
validatorId: "kraken",
@@ -58,8 +60,8 @@ export const allValidatorsData: Validator[] = [
uuid: "polychain-capital",
name: "Polychain capital",
homepageUrl: "https://polychain.capital",
- votingPower: "NAM 100 000",
- commission: "22%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.22),
description:
"Polychain is an investment firm committed to exceptional returns for investors through actively managed portfolios of blockchain assets.",
},
@@ -67,8 +69,8 @@ export const allValidatorsData: Validator[] = [
uuid: "figment",
name: "Figment",
homepageUrl: "https://figment.io",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description:
"Makers of Hubble and Canada’s largest Cosmos validator, Figment is the easiest and most secure way to stake your Atoms.",
},
@@ -76,8 +78,8 @@ export const allValidatorsData: Validator[] = [
uuid: "p2p",
name: "P2P",
homepageUrl: "https://p2p.org",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description:
"One of the winners of Cosmos Game of Stakes. We provide a simple, secure and intelligent staking service to help you generate rewards on your blockchain assets across 9+ networks within a single interface. Let’s stake together - p2p.org.",
},
@@ -85,16 +87,16 @@ export const allValidatorsData: Validator[] = [
uuid: "coinbase-custody",
name: "Coinbase Custody",
homepageUrl: "https://custody.coinbase.com",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description: "Coinbase Custody Cosmos Validator",
},
{
uuid: "chorus-one",
name: "Chorus One",
homepageUrl: "https://chorus.one",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description:
"Secure Cosmos and shape its future by delegating to Chorus One, a highly secure and stable validator. By delegating, you agree to the terms of service at: https://chorus.one/cosmos/tos",
},
@@ -102,16 +104,16 @@ export const allValidatorsData: Validator[] = [
uuid: "binance-staking",
name: "Binance Staking",
homepageUrl: "https://binance.com",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description: "Exchange the world",
},
{
uuid: "dokiacapital",
name: "DokiaCapital",
homepageUrl: "https://staking.dokia.cloud",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description:
"Downtime is not an option for Dokia Capital. We operate an enterprise-grade infrastructure that is robust and secure.",
},
@@ -119,16 +121,16 @@ export const allValidatorsData: Validator[] = [
uuid: "kraken",
name: "Kraken",
homepageUrl: "https://kraken.com",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description: "Kraken Exchange validator",
},
{
uuid: "zero-knowledge-validator-(ZKV)",
name: "Zero Knowledge Validator (ZKV)",
homepageUrl: "https://zkvalidator.com",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description:
"Zero Knowledge Validator: Stake & Support ZKP Research & Privacy Tech",
},
@@ -136,8 +138,8 @@ export const allValidatorsData: Validator[] = [
uuid: "paradigm",
name: "Paradigm",
homepageUrl: "https://www.paradigm.xyz",
- votingPower: "NAM 100 000",
- commission: "20%",
+ votingPower: new BigNumber(100_000),
+ commission: new BigNumber(0.20),
description: "",
},
];
diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/reducers.ts b/apps/namada-interface/src/slices/StakingAndGovernance/reducers.ts
index 542e5b938b..6cc7a86ea7 100644
--- a/apps/namada-interface/src/slices/StakingAndGovernance/reducers.ts
+++ b/apps/namada-interface/src/slices/StakingAndGovernance/reducers.ts
@@ -15,7 +15,7 @@ import {
const initialState: StakingAndGovernanceState = {
validators: [],
- myValidators: [],
+ myValidators: undefined,
myStakingPositions: [],
stakingOrUnstakingState: StakingOrUnstakingState.Idle,
};
diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/types.ts b/apps/namada-interface/src/slices/StakingAndGovernance/types.ts
index f9bdea9677..d4e4dca7af 100644
--- a/apps/namada-interface/src/slices/StakingAndGovernance/types.ts
+++ b/apps/namada-interface/src/slices/StakingAndGovernance/types.ts
@@ -1,3 +1,5 @@
+import BigNumber from "bignumber.js";
+
export const STAKING_AND_GOVERNANCE = "stakingAndGovernance";
export const FETCH_VALIDATORS = `${STAKING_AND_GOVERNANCE}/FETCH_VALIDATORS`;
export const FETCH_MY_VALIDATORS = `${STAKING_AND_GOVERNANCE}/FETCH_MY_VALIDATORS`;
@@ -21,16 +23,16 @@ type Unique = {
// represents the details of a validator
export type Validator = Unique & {
name: string;
- votingPower: string;
+ votingPower?: BigNumber;
homepageUrl: string;
- commission: string;
+ commission: BigNumber;
description: string;
};
// represents users staking position
export type StakingPosition = Unique & {
stakingStatus: string;
- stakedAmount: string;
+ stakedAmount: BigNumber;
owner: string;
totalRewards: string;
validatorId: ValidatorId;
@@ -39,7 +41,9 @@ export type StakingPosition = Unique & {
// represents users staking position combined with the validator
export type MyValidators = Unique & {
stakingStatus: string;
- stakedAmount: string;
+ stakedAmount?: BigNumber;
+ unbondedAmount?: BigNumber;
+ withdrawableAmount?: BigNumber;
validator: Validator;
};
@@ -63,17 +67,15 @@ export enum StakingOrUnstakingState {
}
// this represents a change in staking position
-// if positive, we are posting new bonding
-// negative, we are decreasing it
export type ChangeInStakingPosition = {
validatorId: ValidatorId;
owner: string;
- amount: string;
+ amount: BigNumber;
};
export type StakingAndGovernanceState = {
validators: Validator[];
- myValidators: MyValidators[];
+ myValidators?: MyValidators[];
myStakingPositions: StakingPosition[];
selectedValidatorId?: ValidatorId;
stakingOrUnstakingState: StakingOrUnstakingState;
diff --git a/packages/components/src/Table/Table.tsx b/packages/components/src/Table/Table.tsx
index 974228852c..63b8888df7 100644
--- a/packages/components/src/Table/Table.tsx
+++ b/packages/components/src/Table/Table.tsx
@@ -6,6 +6,7 @@ export type Props = {
data: RowType[];
tableConfigurations: TableConfigurations;
className?: string;
+ subheadingSlot?: JSX.Element;
};
const getRenderedHeaderRow = (
@@ -22,6 +23,7 @@ const getRenderedHeaderRow = (
{columnDefinition.columnLabel}
|
@@ -54,7 +56,7 @@ const getRenderedDataRows = (
export const Table = (
props: Props
): JSX.Element => {
- const { data, tableConfigurations, title, className } = props;
+ const { data, tableConfigurations, title, className, subheadingSlot } = props;
const { columns, rowRenderer, callbacks } =
tableConfigurations && tableConfigurations;
@@ -69,6 +71,7 @@ export const Table = (
return (
{title}
+ {subheadingSlot}
{renderedRows}
diff --git a/packages/components/src/Table/types.ts b/packages/components/src/Table/types.ts
index a2eb4fa743..b48805b6f4 100644
--- a/packages/components/src/Table/types.ts
+++ b/packages/components/src/Table/types.ts
@@ -2,6 +2,7 @@ export type ColumnDefinition = {
uuid: string;
columnLabel: string;
width: string;
+ onClick?: () => void;
};
export type TableConfigurations = {
diff --git a/packages/shared/lib/src/query.rs b/packages/shared/lib/src/query.rs
index 33bc13187a..62840530d9 100644
--- a/packages/shared/lib/src/query.rs
+++ b/packages/shared/lib/src/query.rs
@@ -52,7 +52,7 @@ impl Query {
.validator_addresses(&self.client, &None)
.await?;
- let mut result: Vec<(Address, token::Amount)> = Vec::new();
+ let mut result: Vec<(Address, Option)> = Vec::new();
for address in validator_addresses.into_iter() {
let total_bonds = RPC
@@ -61,7 +61,7 @@ impl Query {
.validator_stake(&self.client, &address, &None)
.await?;
- result.push((address, total_bonds.unwrap_or(token::Amount::zero())));
+ result.push((address, total_bonds.map(|amount| amount.to_string_native())));
}
to_js_result(result)
@@ -102,19 +102,28 @@ impl Query {
validators_per_address.insert(address, validators);
}
- //TODO: Change to Vec of structs
- //Owner, Validator, Amount
- let mut result: Vec<(Address, Address, token::Amount)> = Vec::new();
+ let mut result: Vec<(Address, Address, String, String, String)> =
+ Vec::new();
+ let epoch = namada::ledger::rpc::query_epoch(&self.client).await;
for (owner, validators) in validators_per_address.into_iter() {
for validator in validators.into_iter() {
- let total_bonds = RPC
+ let owner_option = &Some(owner.clone());
+ let validator_option = &Some(validator.clone());
+
+ let enriched = RPC
.vp()
.pos()
- .bond(&self.client, &owner, &validator, &None)
+ .enriched_bonds_and_unbonds(&self.client, epoch, owner_option, validator_option)
.await?;
- result.push((owner.clone(), validator, total_bonds));
+ result.push((
+ owner.clone(),
+ validator,
+ enriched.bonds_total.to_string_native(),
+ enriched.unbonds_total.to_string_native(),
+ enriched.total_withdrawable.to_string_native(),
+ ));
}
}
diff --git a/packages/shared/lib/src/sdk/tx.rs b/packages/shared/lib/src/sdk/tx.rs
index 01887dddfd..0a6b80ab8a 100644
--- a/packages/shared/lib/src/sdk/tx.rs
+++ b/packages/shared/lib/src/sdk/tx.rs
@@ -59,7 +59,7 @@ pub fn bond_tx_args(tx_msg: &[u8], password: Option) -> Result) -> Result {
- return amount.multipliedBy(MICRO_FACTOR);
-};
-
-/**
- * Amount from Micro
- */
-export const amountFromMicro = (micro: BigNumber): BigNumber => {
- return micro.dividedBy(MICRO_FACTOR);
-};
+export const showMaybeNam = (maybeNam: BigNumber | undefined): string =>
+ nullishMap(nam => `NAM ${nam.toString()}`, maybeNam) ?? "-";
/**
* Format a proper JSON RPC request from method and params
@@ -151,6 +140,12 @@ export const assertNever = (x: never): never => {
return x;
};
+export const assertDevOnly = (test: boolean, message: string): void => {
+ if (process.env.NODE_ENV === "development" && !test) {
+ throw new Error(message);
+ }
+}
+
export type Ok = { ok: true; value: T };
export type Err = { ok: false; error: E };
@@ -240,3 +235,11 @@ export type SchemaObject =
} :
never :
never;
+
+export const formatPercentage = (bigNumber: BigNumber): string =>
+ bigNumber.multipliedBy(100).toString() + "%";
+
+export const nullishMap = (f: (a: A) => B, a: A | undefined): B | undefined =>
+ a === undefined
+ ? undefined
+ : f(a);
|