fix(wallet-bonding): bonding context mock
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { MajorCurrencyAmount, MixnodeStatus, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { FeeDetails, MajorCurrencyAmount, MixnodeStatus, TransactionExecuteResult } from '@nymproject/types';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import type { Network } from 'src/types';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
|
||||
@@ -7,9 +7,28 @@ import {
|
||||
bondMixNode as bondMixNodeRequest,
|
||||
claimOperatorRewards,
|
||||
compoundOperatorRewards,
|
||||
simulateBondGateway,
|
||||
simulateBondMixnode,
|
||||
simulateUnbondGateway,
|
||||
simulateUnbondMixnode,
|
||||
simulateVestingBondGateway,
|
||||
simulateVestingBondMixnode,
|
||||
simulateVestingUnbondGateway,
|
||||
simulateVestingUnbondMixnode,
|
||||
simulateUpdateMixnode,
|
||||
simulateVestingUpdateMixnode,
|
||||
unbondGateway as unbondGatewayRequest,
|
||||
unbondMixNode as unbondMixNodeRequest,
|
||||
unbondMixNode as unbondMixnodeRequest,
|
||||
vestingBondGateway,
|
||||
vestingBondMixNode,
|
||||
vestingUnbondGateway,
|
||||
vestingUnbondMixnode,
|
||||
updateMixnode as updateMixnodeRequest,
|
||||
vestingUpdateMixnode as updateMixnodeVestingRequest,
|
||||
} from '../requests';
|
||||
import { useGetFee } from '../hooks/useGetFee';
|
||||
import { useCheckOwnership } from '../hooks/useCheckOwnership';
|
||||
import { AppContext } from './main';
|
||||
|
||||
const bounded: BondedMixnode = {
|
||||
bond: { denom: 'NYM', amount: '1234' },
|
||||
@@ -53,22 +72,51 @@ export interface BondedGateway {
|
||||
location?: string; // TODO not yet available, only available in Network Explorer API
|
||||
}
|
||||
|
||||
export type TokenPool = 'locked' | 'balance';
|
||||
|
||||
export type FeeOperation =
|
||||
| 'bondMixnode'
|
||||
| 'bondMixnodeWithVesting'
|
||||
| 'bondGateway'
|
||||
| 'bondGatewayWithVesting'
|
||||
| 'unbondMixnode'
|
||||
| 'unbondGateway'
|
||||
| 'updateMixnode'
|
||||
| 'bondMore'
|
||||
| 'compoundRewards'
|
||||
| 'redeemRewards';
|
||||
|
||||
export type TBondingContext = {
|
||||
isLoading: boolean;
|
||||
loading: boolean;
|
||||
error?: string;
|
||||
bondedMixnode?: BondedMixnode | null;
|
||||
bondedGateway?: BondedGateway | null;
|
||||
refresh: () => Promise<void>;
|
||||
bondMixnode: (data: TBondMixNodeArgs) => Promise<TransactionExecuteResult>;
|
||||
bondGateway: (data: TBondGatewayArgs) => Promise<TransactionExecuteResult>;
|
||||
unbondMixnode: () => Promise<TransactionExecuteResult>;
|
||||
unbondGateway: () => Promise<TransactionExecuteResult>;
|
||||
redeemRewards: () => Promise<TransactionExecuteResult[]>;
|
||||
compoundRewards: () => Promise<TransactionExecuteResult[]>;
|
||||
bondMixnode: (
|
||||
data: Omit<TBondMixNodeArgs, 'fee'>,
|
||||
tokenPool: TokenPool,
|
||||
) => Promise<TransactionExecuteResult | undefined>;
|
||||
bondGateway: (
|
||||
data: Omit<TBondGatewayArgs, 'fee'>,
|
||||
tokenPool: TokenPool,
|
||||
) => Promise<TransactionExecuteResult | undefined>;
|
||||
bondMore: (signature: string, additionalBond: MajorCurrencyAmount) => Promise<TransactionExecuteResult | undefined>;
|
||||
unbondMixnode: () => Promise<TransactionExecuteResult | undefined>;
|
||||
unbondGateway: () => Promise<TransactionExecuteResult | undefined>;
|
||||
redeemRewards: () => Promise<TransactionExecuteResult[] | undefined>;
|
||||
compoundRewards: () => Promise<TransactionExecuteResult[] | undefined>;
|
||||
updateMixnode: (pm: number) => Promise<TransactionExecuteResult | undefined>;
|
||||
fee?: FeeDetails;
|
||||
getFee: <T>(feeOperation: FeeOperation, args: T) => Promise<FeeDetails | undefined>;
|
||||
feeDetails?: FeeDetails;
|
||||
feeLoading: boolean;
|
||||
feeError?: string;
|
||||
resetFeeState: () => void;
|
||||
};
|
||||
|
||||
export const BondingContext = createContext<TBondingContext>({
|
||||
isLoading: true,
|
||||
loading: true,
|
||||
feeLoading: false,
|
||||
refresh: async () => undefined,
|
||||
bondMixnode: async () => {
|
||||
throw new Error('Not implemented');
|
||||
@@ -88,6 +136,16 @@ export const BondingContext = createContext<TBondingContext>({
|
||||
compoundRewards: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
getFee(): Promise<FeeDetails> {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
resetFeeState(): void {},
|
||||
updateMixnode: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
bondMore(): Promise<TransactionExecuteResult | undefined> {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
});
|
||||
|
||||
export const BondingContextProvider = ({
|
||||
@@ -97,13 +155,24 @@ export const BondingContextProvider = ({
|
||||
network?: Network;
|
||||
children?: React.ReactNode;
|
||||
}): JSX.Element => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string>();
|
||||
const [bondedMixnode, setBondedMixnode] = useState<BondedMixnode | null>(null);
|
||||
const [bondedGateway, setBondedGateway] = useState<BondedGateway | null>(null);
|
||||
const { fee, resetFeeState, feeError, isFeeLoading } = useGetFee();
|
||||
const { ownership, checkOwnership } = useCheckOwnership();
|
||||
const { userBalance } = useContext(AppContext);
|
||||
|
||||
const isVesting = Boolean(ownership.vestingPledge);
|
||||
|
||||
useEffect(() => {
|
||||
if (feeError) {
|
||||
setError(`An error occurred while retrieving fee: ${feeError}`);
|
||||
}
|
||||
}, [feeError]);
|
||||
|
||||
const resetState = () => {
|
||||
setIsLoading(true);
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
setBondedGateway(null);
|
||||
setBondedMixnode(null);
|
||||
@@ -122,7 +191,7 @@ export const BondingContextProvider = ({
|
||||
if (bounded && !('stake' in bounded)) {
|
||||
setBondedGateway(bounded);
|
||||
}
|
||||
setIsLoading(false);
|
||||
setLoading(false);
|
||||
}, [network]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -130,75 +199,159 @@ export const BondingContextProvider = ({
|
||||
refresh();
|
||||
}, [network]);
|
||||
|
||||
const bondMixnode = async (data: TBondMixNodeArgs) => {
|
||||
// TODO some logic
|
||||
let tx;
|
||||
const bondMixnode = async (data: Omit<TBondMixNodeArgs, 'fee'>, tokenPool: TokenPool) => {
|
||||
let tx: TransactionExecuteResult | undefined;
|
||||
const payload = {
|
||||
...data,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
setLoading(true);
|
||||
try {
|
||||
tx = await bondMixNodeRequest(data);
|
||||
if (tokenPool === 'balance') {
|
||||
tx = await bondMixNodeRequest(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
if (tokenPool === 'locked') {
|
||||
tx = await vestingBondMixNode(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
return tx;
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
return tx;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const bondGateway = async (data: TBondGatewayArgs) => {
|
||||
// TODO some logic
|
||||
let tx;
|
||||
const bondGateway = async (data: Omit<TBondGatewayArgs, 'fee'>, tokenPool: TokenPool) => {
|
||||
let tx: TransactionExecuteResult | undefined;
|
||||
const payload = {
|
||||
...data,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
setLoading(true);
|
||||
try {
|
||||
tx = await bondGatewayRequest(data);
|
||||
if (tokenPool === 'balance') {
|
||||
tx = await bondGatewayRequest(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
if (tokenPool === 'locked') {
|
||||
tx = await vestingBondGateway(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
return tx;
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
return tx;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const unbondMixnode = async () => {
|
||||
// TODO some logic
|
||||
let tx;
|
||||
setLoading(true);
|
||||
try {
|
||||
tx = await unbondMixNodeRequest();
|
||||
if (isVesting) tx = await vestingUnbondMixnode(fee?.fee);
|
||||
if (!isVesting) tx = await unbondMixnodeRequest(fee?.fee);
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
await checkOwnership();
|
||||
setLoading(false);
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
const unbondGateway = async () => {
|
||||
// TODO some logic
|
||||
let tx;
|
||||
setLoading(true);
|
||||
try {
|
||||
tx = await unbondGatewayRequest();
|
||||
if (isVesting) tx = await vestingUnbondGateway(fee?.fee);
|
||||
if (!isVesting) tx = await unbondGatewayRequest(fee?.fee);
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
await checkOwnership();
|
||||
setLoading(false);
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
const updateMixnode = async (pm: number) => {
|
||||
let tx;
|
||||
setLoading(true);
|
||||
try {
|
||||
if (isVesting) tx = await updateMixnodeRequest(pm);
|
||||
if (!isVesting) tx = await updateMixnodeVestingRequest(pm);
|
||||
} catch (e: any) {
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
const redeemRewards = async () => {
|
||||
// TODO some logic
|
||||
let tx;
|
||||
setLoading(true);
|
||||
try {
|
||||
tx = await claimOperatorRewards();
|
||||
tx = await claimOperatorRewards(); // TODO use fee, need a new request
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
const compoundRewards = async () => {
|
||||
// TODO some logic
|
||||
let tx;
|
||||
setLoading(true);
|
||||
try {
|
||||
tx = await compoundOperatorRewards();
|
||||
tx = await compoundOperatorRewards(); // TODO use fee, need a new request
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
setError(`an error occurred: ${e}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
const bondMore = async (_signature: string, _additionalBond: MajorCurrencyAmount) =>
|
||||
// TODO to implement
|
||||
undefined;
|
||||
|
||||
const feeOps = useMemo(
|
||||
() => ({
|
||||
bondMixnode: simulateBondMixnode,
|
||||
bondMixnodeWithVesting: simulateVestingBondMixnode,
|
||||
bondGateway: simulateBondGateway,
|
||||
bondGatewayWithVesting: simulateVestingBondGateway,
|
||||
unbondMixnode: isVesting ? simulateVestingUnbondMixnode : simulateUnbondMixnode,
|
||||
unbondGateway: isVesting ? simulateVestingUnbondGateway : simulateUnbondGateway,
|
||||
updateMixnode: isVesting ? simulateVestingUpdateMixnode : simulateUpdateMixnode,
|
||||
bondMore: () => undefined as unknown as Promise<FeeDetails>, // TODO fee request to implement
|
||||
compoundRewards: () => undefined as unknown as Promise<FeeDetails>, // TODO fee request to implement
|
||||
redeemRewards: () => undefined as unknown as Promise<FeeDetails>, // TODO fee request to implement
|
||||
}),
|
||||
[isVesting],
|
||||
);
|
||||
|
||||
const getFee = async (feeOperation: FeeOperation, args: any) => {
|
||||
let details;
|
||||
try {
|
||||
details = feeOps[feeOperation](args);
|
||||
} catch (e: any) {
|
||||
setError(`An error occurred while retrieving fee: ${e}`);
|
||||
}
|
||||
return details;
|
||||
};
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
isLoading,
|
||||
loading,
|
||||
error,
|
||||
bondMixnode,
|
||||
bondedMixnode,
|
||||
@@ -206,11 +359,18 @@ export const BondingContextProvider = ({
|
||||
bondGateway,
|
||||
unbondMixnode,
|
||||
unbondGateway,
|
||||
updateMixnode,
|
||||
refresh,
|
||||
redeemRewards,
|
||||
compoundRewards,
|
||||
feeLoading: isFeeLoading,
|
||||
feeError,
|
||||
getFee,
|
||||
fee,
|
||||
resetFeeState,
|
||||
bondMore,
|
||||
}),
|
||||
[isLoading, error, bondedMixnode, bondedGateway],
|
||||
[loading, error, bondedMixnode, bondedGateway, isFeeLoading, feeError, fee, resetFeeState, isVesting],
|
||||
);
|
||||
|
||||
return <BondingContext.Provider value={memoizedValue}>{children}</BondingContext.Provider>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { FeeDetails, MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { Network } from 'src/types';
|
||||
import { BondedGateway, BondedMixnode, BondingContext } from '../bonding';
|
||||
import { BondedGateway, BondedMixnode, BondingContext, FeeOperation } from '../bonding';
|
||||
import { mockSleep } from './utils';
|
||||
|
||||
const SLEEP_MS = 1000;
|
||||
@@ -37,6 +37,11 @@ const TxResultMock: TransactionExecuteResult = {
|
||||
fee: { amount: '1', denom: 'NYM' },
|
||||
};
|
||||
|
||||
const feeMock: FeeDetails = {
|
||||
amount: { denom: 'NYM', amount: '1' },
|
||||
fee: { Auto: 1 },
|
||||
};
|
||||
|
||||
export const MockBondingContextProvider = ({
|
||||
network,
|
||||
children,
|
||||
@@ -44,7 +49,9 @@ export const MockBondingContextProvider = ({
|
||||
network?: Network;
|
||||
children?: React.ReactNode;
|
||||
}): JSX.Element => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [feeLoading, setFeeLoading] = useState(false);
|
||||
const [fee, setFee] = useState<FeeDetails | undefined>();
|
||||
const [error, setError] = useState<string>();
|
||||
const [bondedData, setBondedData] = useState<BondedMixnode | BondedGateway | null>(null);
|
||||
const [bondedMixnode, setBondedMixnode] = useState<BondedMixnode | null>(null);
|
||||
@@ -54,7 +61,7 @@ export const MockBondingContextProvider = ({
|
||||
const triggerStateUpdate = () => setTrigger(new Date());
|
||||
|
||||
const resetState = () => {
|
||||
setIsLoading(true);
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
setBondedGateway(null);
|
||||
setBondedMixnode(null);
|
||||
@@ -74,7 +81,7 @@ export const MockBondingContextProvider = ({
|
||||
if (bounded && !('stake' in bounded)) {
|
||||
setBondedGateway(bounded);
|
||||
}
|
||||
setIsLoading(false);
|
||||
setLoading(false);
|
||||
}, [network]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -83,44 +90,82 @@ export const MockBondingContextProvider = ({
|
||||
}, [network, bondedData]);
|
||||
|
||||
const bondMixnode = async (): Promise<TransactionExecuteResult> => {
|
||||
setLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
setBondedData(bondedMixnodeMock);
|
||||
setLoading(false);
|
||||
return TxResultMock;
|
||||
};
|
||||
|
||||
const bondGateway = async (): Promise<TransactionExecuteResult> => {
|
||||
setLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
setBondedData(bondedGatewayMock);
|
||||
setLoading(false);
|
||||
return TxResultMock;
|
||||
};
|
||||
|
||||
const unbondMixnode = async (): Promise<TransactionExecuteResult> => {
|
||||
setLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
setBondedData(null);
|
||||
setLoading(false);
|
||||
return TxResultMock;
|
||||
};
|
||||
|
||||
const unbondGateway = async (): Promise<TransactionExecuteResult> => {
|
||||
setLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
setBondedData(null);
|
||||
setLoading(false);
|
||||
return TxResultMock;
|
||||
};
|
||||
|
||||
const redeemRewards = async (): Promise<TransactionExecuteResult[]> => {
|
||||
const redeemRewards = async (): Promise<TransactionExecuteResult[] | undefined> => {
|
||||
setLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
triggerStateUpdate();
|
||||
setLoading(false);
|
||||
return [TxResultMock];
|
||||
};
|
||||
|
||||
const compoundRewards = async (): Promise<TransactionExecuteResult[]> => {
|
||||
const compoundRewards = async (): Promise<TransactionExecuteResult[] | undefined> => {
|
||||
setLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
triggerStateUpdate();
|
||||
setLoading(false);
|
||||
return [TxResultMock];
|
||||
};
|
||||
|
||||
const updateMixnode = async (): Promise<TransactionExecuteResult> => {
|
||||
setLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
triggerStateUpdate();
|
||||
setLoading(false);
|
||||
return TxResultMock;
|
||||
};
|
||||
|
||||
const bondMore = async (_signature: string, _additionalBond: MajorCurrencyAmount) => {
|
||||
setLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
triggerStateUpdate();
|
||||
setLoading(false);
|
||||
return TxResultMock;
|
||||
};
|
||||
|
||||
const getFee = async (_feeOperation: FeeOperation, _args: any) => {
|
||||
setFeeLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
setFeeLoading(false);
|
||||
setFee(feeMock);
|
||||
return feeMock;
|
||||
};
|
||||
|
||||
const resetFeeState = () => {};
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
isLoading,
|
||||
loading,
|
||||
error,
|
||||
bondMixnode,
|
||||
bondGateway,
|
||||
@@ -129,8 +174,14 @@ export const MockBondingContextProvider = ({
|
||||
refresh,
|
||||
redeemRewards,
|
||||
compoundRewards,
|
||||
fee,
|
||||
feeLoading,
|
||||
getFee,
|
||||
resetFeeState,
|
||||
updateMixnode,
|
||||
bondMore,
|
||||
}),
|
||||
[isLoading, error, bondedMixnode, bondedGateway, trigger],
|
||||
[loading, error, bondedMixnode, bondedGateway, trigger, fee],
|
||||
);
|
||||
|
||||
return <BondingContext.Provider value={memoizedValue}>{children}</BondingContext.Provider>;
|
||||
|
||||
@@ -17,15 +17,8 @@ import {
|
||||
NodeData,
|
||||
} from '../types';
|
||||
import AmountModal from './AmountModal';
|
||||
import { AppContext, urls } from '../../../context';
|
||||
import { AppContext, urls, useBondingContext } from '../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import {
|
||||
bondGateway as bondGatewayRequest,
|
||||
bondMixNode as bondMixNodeRequest,
|
||||
vestingBondGateway,
|
||||
vestingBondMixNode,
|
||||
} from '../../../requests';
|
||||
import { useGetFee } from '../../../hooks/useGetFee';
|
||||
|
||||
const initialState: BondState = {
|
||||
showModal: false,
|
||||
@@ -71,15 +64,20 @@ const BondingCard = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const { formStep, showModal } = state;
|
||||
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
const { fee } = useGetFee();
|
||||
const { clientDetails, network } = useContext(AppContext);
|
||||
const { error, bondMixnode: bondMixnodeRequest, bondGateway: bondGatewayRequest } = useBondingContext();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'reset' });
|
||||
}, [clientDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
dispatch({ type: 'set_error', payload: error });
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const bondMixnode = async () => {
|
||||
let tx: TransactionExecuteResult | undefined;
|
||||
const { signature, identityKey, sphinxKey, host, version, mixPort, verlocPort, httpApiPort } =
|
||||
state.nodeData as NodeData<MixnodeData>;
|
||||
const { profitMargin, amount, tokenPool } = state.amountData as MixnodeAmount;
|
||||
@@ -96,30 +94,20 @@ const BondingCard = () => {
|
||||
http_api_port: httpApiPort,
|
||||
},
|
||||
pledge: amount,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
if (tokenPool !== 'locked' && tokenPool !== 'balance') {
|
||||
throw new Error(`token pool [${tokenPool}] not supported`);
|
||||
}
|
||||
try {
|
||||
if (tokenPool === 'balance') {
|
||||
tx = await bondMixNodeRequest(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
if (tokenPool === 'locked') {
|
||||
tx = await vestingBondMixNode(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
const tx = await bondMixnodeRequest(payload, tokenPool);
|
||||
if (tx) {
|
||||
dispatch({ type: 'set_bond_status', payload: 'success' });
|
||||
return tx;
|
||||
} catch (e: any) {
|
||||
dispatch({ type: 'set_error', payload: e });
|
||||
} else {
|
||||
dispatch({ type: 'set_bond_status', payload: 'error' });
|
||||
}
|
||||
return undefined;
|
||||
return tx;
|
||||
};
|
||||
|
||||
const bondGateway = async () => {
|
||||
let tx: TransactionExecuteResult | undefined;
|
||||
const { signature, identityKey, sphinxKey, host, version, location, mixPort, clientsPort } =
|
||||
state.nodeData as NodeData<GatewayData>;
|
||||
const { amount, tokenPool } = state.amountData as GatewayAmount;
|
||||
@@ -135,21 +123,15 @@ const BondingCard = () => {
|
||||
clients_port: clientsPort,
|
||||
},
|
||||
pledge: amount,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
try {
|
||||
if (tokenPool === 'balance') {
|
||||
tx = await bondGatewayRequest(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
if (tokenPool === 'locked') {
|
||||
tx = await vestingBondGateway(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
if (tokenPool !== 'locked' && tokenPool !== 'balance') {
|
||||
throw new Error(`token pool [${tokenPool}] not supported`);
|
||||
}
|
||||
const tx = await bondGatewayRequest(payload, tokenPool);
|
||||
if (tx) {
|
||||
dispatch({ type: 'set_bond_status', payload: 'success' });
|
||||
return tx;
|
||||
} catch (e: any) {
|
||||
dispatch({ type: 'set_error', payload: e });
|
||||
} else {
|
||||
dispatch({ type: 'set_bond_status', payload: 'error' });
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import {
|
||||
simulateBondGateway,
|
||||
simulateBondMixnode,
|
||||
simulateVestingBondGateway,
|
||||
simulateVestingBondMixnode,
|
||||
} from '../../../requests';
|
||||
import { GatewayAmount, GatewayData, MixnodeAmount, MixnodeData, NodeData } from '../types';
|
||||
import { useGetFee } from '../../../hooks/useGetFee';
|
||||
import { SimpleModal } from '../../../components/Modals/SimpleModal';
|
||||
import { useBondingContext } from '../../../context';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
@@ -21,7 +15,7 @@ export interface Props {
|
||||
}
|
||||
|
||||
const SummaryModal = ({ open, onClose, onSubmit, node, amount, onCancel, onError }: Props) => {
|
||||
const { fee, getFee, resetFeeState, feeError, isFeeLoading } = useGetFee();
|
||||
const { fee, getFee, resetFeeState, feeError, feeLoading } = useBondingContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (feeError) onError(feeError);
|
||||
@@ -31,7 +25,7 @@ const SummaryModal = ({ open, onClose, onSubmit, node, amount, onCancel, onError
|
||||
const { signature, host, version, mixPort, identityKey, sphinxKey } = node;
|
||||
try {
|
||||
if (node.nodeType === 'mixnode') {
|
||||
await getFee(amount.tokenPool === 'locked' ? simulateVestingBondMixnode : simulateBondMixnode, {
|
||||
await getFee(amount.tokenPool === 'locked' ? 'bondMixnodeWithVesting' : 'bondMixnode', {
|
||||
ownerSignature: signature,
|
||||
mixnode: {
|
||||
identity_key: identityKey,
|
||||
@@ -46,7 +40,7 @@ const SummaryModal = ({ open, onClose, onSubmit, node, amount, onCancel, onError
|
||||
pledge: amount.amount,
|
||||
});
|
||||
} else {
|
||||
await getFee(amount.tokenPool === 'locked' ? simulateVestingBondGateway : simulateBondGateway, {
|
||||
await getFee(amount.tokenPool === 'locked' ? 'bondGatewayWithVesting' : 'bondGateway', {
|
||||
ownerSignature: signature,
|
||||
gateway: {
|
||||
identity_key: identityKey,
|
||||
@@ -98,7 +92,7 @@ const SummaryModal = ({ open, onClose, onSubmit, node, amount, onCancel, onError
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography>Fee for this operation</Typography>
|
||||
{isFeeLoading ? (
|
||||
{feeLoading ? (
|
||||
<Typography>loading</Typography>
|
||||
) : (
|
||||
<Typography>{fee ? `${fee.amount?.amount} ${fee.amount?.denom}` : ''}</Typography>
|
||||
|
||||
@@ -4,7 +4,7 @@ import EditIcon from '@mui/icons-material/Edit';
|
||||
import { BondedGateway } from '../../../context';
|
||||
import { NodeTable, BondedNodeCard, Cell, Header, NodeMenu } from '../components';
|
||||
import { GatewayFlow } from './types';
|
||||
import Unbond from './unbond';
|
||||
import Unbond from '../unbond';
|
||||
|
||||
const headers: Header[] = [
|
||||
{
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Typography } from '@mui/material';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import { AppContext, BondedGateway, BondedMixnode, urls } from '../../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import { ConfirmationModal } from '../../../../components';
|
||||
import {
|
||||
simulateUnbondGateway,
|
||||
simulateVestingUnbondGateway,
|
||||
unbondGateway,
|
||||
vestingUnbondGateway,
|
||||
} from '../../../../requests';
|
||||
import { useCheckOwnership } from '../../../../hooks/useCheckOwnership';
|
||||
import { useGetFee } from '../../../../hooks/useGetFee';
|
||||
import { LoadingModal } from '../../../../components/Modals/LoadingModal';
|
||||
|
||||
interface Props {
|
||||
node: BondedGateway;
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type UnbondStatus = 'success' | 'error';
|
||||
|
||||
const Unbond = ({ node, show, onClose }: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [step, setStep] = useState<1 | 2>(1);
|
||||
const [txHash, setTxHash] = useState<string>();
|
||||
const [status, setStatus] = useState<UnbondStatus>();
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const { fee, getFee, resetFeeState, isFeeLoading } = useGetFee();
|
||||
|
||||
const { network } = useContext(AppContext);
|
||||
const { checkOwnership, ownership } = useCheckOwnership();
|
||||
|
||||
const isVesting = Boolean(ownership.vestingPledge);
|
||||
|
||||
const unbond = async () => {
|
||||
let tx;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isVesting) tx = await vestingUnbondGateway(fee?.fee);
|
||||
if (!isVesting) tx = await unbondGateway(fee?.fee);
|
||||
setTxHash(tx?.transaction_hash);
|
||||
setStatus('success');
|
||||
} catch (err: any) {
|
||||
setStatus('error');
|
||||
setError(err as string);
|
||||
} finally {
|
||||
await checkOwnership();
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchFee = async () => {
|
||||
try {
|
||||
if (isVesting) await getFee(simulateVestingUnbondGateway, {});
|
||||
if (!isVesting) await getFee(simulateUnbondGateway, {});
|
||||
} catch (e: any) {
|
||||
setStatus('error');
|
||||
setError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchFee();
|
||||
}, [node, isVesting]);
|
||||
|
||||
const submit = async () => {
|
||||
if (status === 'error') {
|
||||
// Fetch fee failed
|
||||
return;
|
||||
}
|
||||
unbond();
|
||||
resetFeeState();
|
||||
setStep(2);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setStep(1);
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (isFeeLoading || isLoading) return <LoadingModal />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SummaryModal
|
||||
open={show && step === 1}
|
||||
onClose={reset}
|
||||
onConfirm={submit}
|
||||
onCancel={reset}
|
||||
bond={node.bond}
|
||||
rewards={(node as BondedMixnode).operatorRewards}
|
||||
fee={fee?.amount as MajorCurrencyAmount}
|
||||
/>
|
||||
{status === 'success' && (
|
||||
<ConfirmationModal
|
||||
open={show && step === 2}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Unbonding succesfull"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography sx={{ mb: 2 }}>This operation can take up to one hour to process</Typography>
|
||||
<Link href={`${urls(network).blockExplorer}/transaction/${txHash}`} noIcon>
|
||||
View on blockchain
|
||||
</Link>
|
||||
</ConfirmationModal>
|
||||
)}
|
||||
{status === 'error' && (
|
||||
<ConfirmationModal
|
||||
open={show}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Unbonding failed"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography variant="caption">Error: {error}</Typography>
|
||||
<ErrorOutline color="error" />
|
||||
</ConfirmationModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Unbond;
|
||||
@@ -9,7 +9,7 @@ import NodeSettings from './node-settings';
|
||||
import BondMore from './bond-more';
|
||||
import { MixnodeFlow } from './types';
|
||||
import RedeemRewards from './redeem';
|
||||
import Unbond from './unbond';
|
||||
import Unbond from '../unbond';
|
||||
import CompoundRewards from './compound';
|
||||
import { Bond as BondIcon, Unbond as UnbondIcon } from '../../../svg-icons';
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Typography } from '@mui/material';
|
||||
import { AppContext, BondedMixnode, urls } from '../../../../context';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import { AppContext, BondedMixnode, urls, useBondingContext } from '../../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import { ConfirmationModal } from '../../../../components';
|
||||
import BondModal from './BondModal';
|
||||
@@ -17,20 +18,18 @@ interface Props {
|
||||
const BondMore = ({ mixnode, show, onClose }: Props) => {
|
||||
const [addBond, setAddBond] = useState<MajorCurrencyAmount>({ amount: '0', denom: 'NYM' });
|
||||
const [signature, setSignature] = useState<string>();
|
||||
const [fee, setFee] = useState<MajorCurrencyAmount>({ amount: '0', denom: 'NYM' });
|
||||
const [step, setStep] = useState<1 | 2 | 3>(1);
|
||||
const [tx, setTx] = useState<TransactionExecuteResult>();
|
||||
|
||||
const { network } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
setFee({ amount: '42', denom: 'NYM' }); // TODO fetch real fee amount
|
||||
}, [addBond]);
|
||||
const { bondMore, error } = useBondingContext();
|
||||
|
||||
const submit = async () => {
|
||||
// TODO send request to update bond
|
||||
setStep(3); // on success
|
||||
// setTx(requestResult)
|
||||
const txResult = await bondMore(signature as string, addBond);
|
||||
if (txResult) {
|
||||
setStep(3);
|
||||
}
|
||||
setTx(txResult);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
@@ -59,10 +58,9 @@ const BondMore = ({ mixnode, show, onClose }: Props) => {
|
||||
onCancel={() => setStep(1)}
|
||||
currentBond={mixnode.bond}
|
||||
addBond={addBond}
|
||||
fee={fee as MajorCurrencyAmount}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
open={show && step === 3}
|
||||
open={show && step === 3 && !error}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Bonding successful"
|
||||
@@ -74,6 +72,19 @@ const BondMore = ({ mixnode, show, onClose }: Props) => {
|
||||
View on blockchain
|
||||
</Link>
|
||||
</ConfirmationModal>
|
||||
{error && (
|
||||
<ConfirmationModal
|
||||
open={show}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Operation failed"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography variant="caption">Error: {error}</Typography>
|
||||
<ErrorOutline color="error" />
|
||||
</ConfirmationModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { useEffect } from 'react';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import { SimpleModal } from '../../../../components/Modals/SimpleModal';
|
||||
import { useBondingContext } from '../../../../context';
|
||||
import { ConfirmationModal } from '../../../../components';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
@@ -10,33 +14,60 @@ export interface Props {
|
||||
onCancel: () => void;
|
||||
currentBond: MajorCurrencyAmount;
|
||||
addBond: MajorCurrencyAmount;
|
||||
fee: MajorCurrencyAmount;
|
||||
}
|
||||
|
||||
const SummaryModal = ({ open, onClose, onConfirm, onCancel, currentBond, addBond, fee }: Props) => (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={onConfirm}
|
||||
onBack={onCancel}
|
||||
header="Bond mor details"
|
||||
okLabel="Confirm"
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Current bond</Typography>
|
||||
<Typography fontWeight={400}>{`${currentBond.amount} ${currentBond.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Additional bond</Typography>
|
||||
<Typography fontWeight={400}>{`${addBond.amount} ${addBond.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Fee for this operation</Typography>
|
||||
<Typography fontWeight={400}>{`${fee.amount} ${fee.denom}`}</Typography>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
const SummaryModal = ({ open, onClose, onConfirm, onCancel, currentBond, addBond }: Props) => {
|
||||
const { getFee, fee, error } = useBondingContext();
|
||||
|
||||
const fetchFee = async () => {
|
||||
await getFee('bondMore', {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchFee();
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ConfirmationModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onClose}
|
||||
title="Operation failed"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography variant="caption">Error: {error}</Typography>
|
||||
<ErrorOutline color="error" />
|
||||
</ConfirmationModal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={onConfirm}
|
||||
onBack={onCancel}
|
||||
header="Bond mor details"
|
||||
okLabel="Confirm"
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Current bond</Typography>
|
||||
<Typography fontWeight={400}>{`${currentBond.amount} ${currentBond.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Additional bond</Typography>
|
||||
<Typography fontWeight={400}>{`${addBond.amount} ${addBond.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Fee for this operation</Typography>
|
||||
<Typography fontWeight={400}>{fee ? `${fee.amount?.amount} ${fee.amount?.denom}` : ''}</Typography>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SummaryModal;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Typography } from '@mui/material';
|
||||
import { AppContext, BondedMixnode, urls } from '../../../../context';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import { AppContext, BondedMixnode, urls, useBondingContext } from '../../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import { ConfirmationModal } from '../../../../components';
|
||||
|
||||
@@ -14,20 +15,26 @@ interface Props {
|
||||
}
|
||||
|
||||
const CompoundRewards = ({ mixnode, show, onClose }: Props) => {
|
||||
const [fee, setFee] = useState<MajorCurrencyAmount>({ amount: '0', denom: 'NYM' });
|
||||
const [step, setStep] = useState<1 | 2>(1);
|
||||
const [tx, setTx] = useState<TransactionExecuteResult>();
|
||||
|
||||
const { network } = useContext(AppContext);
|
||||
const { compoundRewards, error, fee, getFee } = useBondingContext();
|
||||
|
||||
const fetchFee = async () => {
|
||||
await getFee('compoundRewards', {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFee({ amount: '42', denom: 'NYM' }); // TODO fetch real fee amount
|
||||
fetchFee();
|
||||
}, []);
|
||||
|
||||
const submit = async () => {
|
||||
// TODO send request to compound rewards
|
||||
setStep(2); // on success
|
||||
// setTx(requestResult)
|
||||
const txResult = await compoundRewards();
|
||||
if (txResult) {
|
||||
setStep(2);
|
||||
}
|
||||
setTx(txResult?.[0]);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
@@ -43,7 +50,7 @@ const CompoundRewards = ({ mixnode, show, onClose }: Props) => {
|
||||
onConfirm={submit}
|
||||
onCancel={reset}
|
||||
rewards={mixnode.operatorRewards}
|
||||
fee={fee as MajorCurrencyAmount}
|
||||
fee={fee?.amount}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
open={show && step === 2}
|
||||
@@ -58,6 +65,19 @@ const CompoundRewards = ({ mixnode, show, onClose }: Props) => {
|
||||
View on blockchain
|
||||
</Link>
|
||||
</ConfirmationModal>
|
||||
{error && (
|
||||
<ConfirmationModal
|
||||
open={show}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Operation failed"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography variant="caption">Error: {error}</Typography>
|
||||
<ErrorOutline color="error" />
|
||||
</ConfirmationModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface Props {
|
||||
onConfirm: () => Promise<void>;
|
||||
onCancel: () => void;
|
||||
rewards: MajorCurrencyAmount;
|
||||
fee: MajorCurrencyAmount;
|
||||
fee?: MajorCurrencyAmount | null;
|
||||
}
|
||||
|
||||
const SummaryModal = ({ open, onClose, onConfirm, onCancel, rewards, fee }: Props) => (
|
||||
@@ -29,7 +29,7 @@ const SummaryModal = ({ open, onClose, onConfirm, onCancel, rewards, fee }: Prop
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Fee for this operation</Typography>
|
||||
<Typography fontWeight={400}>{`${fee.amount} ${fee.denom}`}</Typography>
|
||||
<Typography fontWeight={400}>{fee ? `${fee?.amount} ${fee?.denom}` : ''}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Typography fontWeight={400}>Rewards will be added to your bonding pool</Typography>
|
||||
|
||||
@@ -3,8 +3,9 @@ import { useContext, useEffect, useState } from 'react';
|
||||
import { MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Typography } from '@mui/material';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import ProfitMarginModal from './ProfitMarginModal';
|
||||
import { AppContext, BondedMixnode, urls } from '../../../../context';
|
||||
import { AppContext, BondedMixnode, urls, useBondingContext } from '../../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import { ConfirmationModal } from '../../../../components';
|
||||
|
||||
@@ -18,21 +19,36 @@ interface Props {
|
||||
const MOCK_ESTIMATED_OP_REWARD: MajorCurrencyAmount = { amount: '42', denom: 'NYM' };
|
||||
|
||||
const NodeSettings = ({ mixnode, show, onClose }: Props) => {
|
||||
const [status, setStatus] = useState<'success' | 'error'>();
|
||||
const [profitMargin, setProfitMargin] = useState<number>();
|
||||
const [fee, setFee] = useState<MajorCurrencyAmount>({ amount: '0', denom: 'NYM' });
|
||||
const [step, setStep] = useState<1 | 2 | 3>(1);
|
||||
const [step, setStep] = useState<1 | 2>(1);
|
||||
const [tx, setTx] = useState<TransactionExecuteResult>();
|
||||
|
||||
const { network } = useContext(AppContext);
|
||||
const { updateMixnode, error, fee, getFee } = useBondingContext();
|
||||
|
||||
useEffect(() => {
|
||||
setFee({ amount: '42', denom: 'NYM' }); // TODO fetch real fee amount
|
||||
}, [profitMargin]);
|
||||
if (error) {
|
||||
setStatus('error');
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const fetchFee = async () => {
|
||||
await getFee('updateMixnode', {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchFee();
|
||||
}, []);
|
||||
|
||||
const submit = async () => {
|
||||
// TODO send request to update profit margin
|
||||
setStep(3); // on success
|
||||
// setTx(requestResult)
|
||||
const txResult = await updateMixnode(profitMargin as number);
|
||||
if (txResult) {
|
||||
setStatus('success');
|
||||
} else {
|
||||
setStatus('error');
|
||||
}
|
||||
setTx(txResult);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
@@ -60,21 +76,36 @@ const NodeSettings = ({ mixnode, show, onClose }: Props) => {
|
||||
onCancel={() => setStep(1)}
|
||||
currentPm={mixnode.profitMargin}
|
||||
newPm={profitMargin as number}
|
||||
fee={fee as MajorCurrencyAmount}
|
||||
fee={fee?.amount}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
open={show && step === 3}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Operation successful"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography sx={{ mb: 2 }}>This operation can take up to one hour to process</Typography>
|
||||
<Link href={`${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`} noIcon>
|
||||
View on blockchain
|
||||
</Link>
|
||||
</ConfirmationModal>
|
||||
{status === 'success' && (
|
||||
<ConfirmationModal
|
||||
open={show}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Operation successful"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography sx={{ mb: 2 }}>This operation can take up to one hour to process</Typography>
|
||||
<Link href={`${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`} noIcon>
|
||||
View on blockchain
|
||||
</Link>
|
||||
</ConfirmationModal>
|
||||
)}
|
||||
{status === 'error' && (
|
||||
<ConfirmationModal
|
||||
open={show}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Operation failed"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography variant="caption">Error: {error}</Typography>
|
||||
<ErrorOutline color="error" />
|
||||
</ConfirmationModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface Props {
|
||||
onCancel: () => void;
|
||||
currentPm: number;
|
||||
newPm: number;
|
||||
fee: MajorCurrencyAmount;
|
||||
fee?: MajorCurrencyAmount | null;
|
||||
}
|
||||
|
||||
const SummaryModal = ({ open, onClose, onConfirm, onCancel, currentPm, newPm, fee }: Props) => (
|
||||
@@ -35,7 +35,7 @@ const SummaryModal = ({ open, onClose, onConfirm, onCancel, currentPm, newPm, fe
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Fee for this operation</Typography>
|
||||
<Typography fontWeight={400}>{`${fee.amount} ${fee.denom}`}</Typography>
|
||||
<Typography fontWeight={400}>{fee ? `${fee?.amount} ${fee?.denom}` : ''}</Typography>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Typography } from '@mui/material';
|
||||
import { AppContext, BondedMixnode, urls } from '../../../../context';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import { AppContext, BondedMixnode, urls, useBondingContext } from '../../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import { ConfirmationModal } from '../../../../components';
|
||||
|
||||
@@ -14,20 +15,26 @@ interface Props {
|
||||
}
|
||||
|
||||
const RedeemRewards = ({ mixnode, show, onClose }: Props) => {
|
||||
const [fee, setFee] = useState<MajorCurrencyAmount>({ amount: '0', denom: 'NYM' });
|
||||
const [step, setStep] = useState<1 | 2>(1);
|
||||
const [tx, setTx] = useState<TransactionExecuteResult>();
|
||||
|
||||
const { network } = useContext(AppContext);
|
||||
const { redeemRewards, error, fee, getFee } = useBondingContext();
|
||||
|
||||
const fetchFee = async () => {
|
||||
await getFee('redeemRewards', {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFee({ amount: '42', denom: 'NYM' }); // TODO fetch real fee amount
|
||||
fetchFee();
|
||||
}, []);
|
||||
|
||||
const submit = async () => {
|
||||
// TODO send request to redeem rewards
|
||||
setStep(2); // on success
|
||||
// setTx(requestResult)
|
||||
const txResult = await redeemRewards();
|
||||
if (txResult) {
|
||||
setStep(2);
|
||||
}
|
||||
setTx(txResult?.[0]);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
@@ -43,7 +50,7 @@ const RedeemRewards = ({ mixnode, show, onClose }: Props) => {
|
||||
onConfirm={submit}
|
||||
onCancel={reset}
|
||||
rewards={mixnode.operatorRewards}
|
||||
fee={fee as MajorCurrencyAmount}
|
||||
fee={fee?.amount}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
open={show && step === 2}
|
||||
@@ -58,6 +65,19 @@ const RedeemRewards = ({ mixnode, show, onClose }: Props) => {
|
||||
View on blockchain
|
||||
</Link>
|
||||
</ConfirmationModal>
|
||||
{error && (
|
||||
<ConfirmationModal
|
||||
open={show}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Operation failed"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
>
|
||||
<Typography variant="caption">Error: {error}</Typography>
|
||||
<ErrorOutline color="error" />
|
||||
</ConfirmationModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface Props {
|
||||
onConfirm: () => Promise<void>;
|
||||
onCancel: () => void;
|
||||
rewards: MajorCurrencyAmount;
|
||||
fee: MajorCurrencyAmount;
|
||||
fee?: MajorCurrencyAmount | null;
|
||||
}
|
||||
|
||||
const SummaryModal = ({ open, onClose, onConfirm, onCancel, rewards, fee }: Props) => (
|
||||
@@ -29,7 +29,7 @@ const SummaryModal = ({ open, onClose, onConfirm, onCancel, rewards, fee }: Prop
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Fee for this operation</Typography>
|
||||
<Typography fontWeight={400}>{`${fee.amount} ${fee.denom}`}</Typography>
|
||||
<Typography fontWeight={400}>{fee ? `${fee.amount} ${fee.denom}` : ''}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Typography fontWeight={400}>Rewards will be transferred to the account you are logged in with</Typography>
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { SimpleModal } from '../../../../components/Modals/SimpleModal';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => Promise<void>;
|
||||
onCancel: () => void;
|
||||
bond: MajorCurrencyAmount;
|
||||
rewards?: MajorCurrencyAmount;
|
||||
fee: MajorCurrencyAmount;
|
||||
}
|
||||
|
||||
const SummaryModal = ({ open, onClose, onConfirm, onCancel, bond, rewards, fee }: Props) => (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={onConfirm}
|
||||
onBack={onCancel}
|
||||
header="Unbond"
|
||||
subHeader="Unbond and remove your node from the mixnet"
|
||||
okLabel="Unbond"
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Amount to unbond</Typography>
|
||||
<Typography fontWeight={400}>{`${bond.amount} ${bond.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
{rewards?.amount && (
|
||||
<>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Operator rewards</Typography>
|
||||
<Typography fontWeight={400}>{`${rewards.amount} ${rewards.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
</>
|
||||
)}
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Fee for this operation</Typography>
|
||||
<Typography fontWeight={400}>{`${fee.amount} ${fee.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Typography fontWeight={400}>Tokens will be transferred to account you are logged in with now</Typography>
|
||||
</SimpleModal>
|
||||
);
|
||||
|
||||
export default SummaryModal;
|
||||
@@ -1,3 +0,0 @@
|
||||
import Unbond from './Unbond';
|
||||
|
||||
export default Unbond;
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { SimpleModal } from '../../../../components/Modals/SimpleModal';
|
||||
import { SimpleModal } from '../../../components/Modals/SimpleModal';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
+38
-39
@@ -4,21 +4,14 @@ import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Typography } from '@mui/material';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import { AppContext, BondedMixnode, urls } from '../../../../context';
|
||||
import { AppContext, BondedGateway, BondedMixnode, urls, useBondingContext } from '../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import { ConfirmationModal } from '../../../../components';
|
||||
import {
|
||||
simulateUnbondMixnode,
|
||||
simulateVestingUnbondMixnode,
|
||||
unbondMixNode,
|
||||
vestingUnbondMixnode,
|
||||
} from '../../../../requests';
|
||||
import { useCheckOwnership } from '../../../../hooks/useCheckOwnership';
|
||||
import { useGetFee } from '../../../../hooks/useGetFee';
|
||||
import { LoadingModal } from '../../../../components/Modals/LoadingModal';
|
||||
import { ConfirmationModal } from '../../../components';
|
||||
import { LoadingModal } from '../../../components/Modals/LoadingModal';
|
||||
import { NodeType } from '../types';
|
||||
|
||||
interface Props {
|
||||
node: BondedMixnode;
|
||||
node: BondedMixnode | BondedGateway;
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
@@ -26,49 +19,55 @@ interface Props {
|
||||
type UnbondStatus = 'success' | 'error';
|
||||
|
||||
const Unbond = ({ node, show, onClose }: Props) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [step, setStep] = useState<1 | 2>(1);
|
||||
const [txHash, setTxHash] = useState<string>();
|
||||
const [status, setStatus] = useState<UnbondStatus>();
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const { fee, getFee, resetFeeState, isFeeLoading } = useGetFee();
|
||||
const [nodeType, setNodeType] = useState<NodeType>('mixnode');
|
||||
|
||||
const { network } = useContext(AppContext);
|
||||
const { checkOwnership, ownership } = useCheckOwnership();
|
||||
const { fee, getFee, resetFeeState, feeLoading, feeError, loading, unbondMixnode, unbondGateway, error } =
|
||||
useBondingContext();
|
||||
|
||||
const isVesting = Boolean(ownership.vestingPledge);
|
||||
useEffect(() => {
|
||||
if (error || feeError) {
|
||||
setStatus('error');
|
||||
}
|
||||
}, [error, feeError]);
|
||||
|
||||
useEffect(() => {
|
||||
if ('profitMarfin' in node) {
|
||||
setNodeType('mixnode');
|
||||
} else {
|
||||
setNodeType('gateway');
|
||||
}
|
||||
}, [node]);
|
||||
|
||||
const unbond = async () => {
|
||||
let tx;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isVesting) tx = await vestingUnbondMixnode(fee?.fee);
|
||||
if (!isVesting) tx = await unbondMixNode(fee?.fee);
|
||||
setTxHash(tx?.transaction_hash);
|
||||
setStatus('success');
|
||||
} catch (err: any) {
|
||||
setStatus('error');
|
||||
setError(err as string);
|
||||
} finally {
|
||||
await checkOwnership();
|
||||
setIsLoading(false);
|
||||
if (nodeType === 'mixnode') {
|
||||
tx = await unbondMixnode();
|
||||
} else {
|
||||
tx = await unbondGateway();
|
||||
}
|
||||
if (!tx) {
|
||||
setStatus('error');
|
||||
}
|
||||
setStatus('success');
|
||||
setTxHash(tx?.transaction_hash);
|
||||
return tx;
|
||||
};
|
||||
|
||||
const fetchFee = async () => {
|
||||
try {
|
||||
if (isVesting) await getFee(simulateVestingUnbondMixnode, {});
|
||||
if (!isVesting) await getFee(simulateUnbondMixnode, {});
|
||||
} catch (e: any) {
|
||||
setStatus('error');
|
||||
setError(e as string);
|
||||
if (nodeType === 'mixnode') {
|
||||
await getFee('unbondMixnode', {});
|
||||
} else {
|
||||
await getFee('unbondGateway', {});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchFee();
|
||||
}, [node, isVesting]);
|
||||
}, [node]);
|
||||
|
||||
const submit = async () => {
|
||||
if (status === 'error') {
|
||||
@@ -85,7 +84,7 @@ const Unbond = ({ node, show, onClose }: Props) => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (isFeeLoading || isLoading) return <LoadingModal />;
|
||||
if (feeLoading || loading) return <LoadingModal />;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -95,7 +94,7 @@ const Unbond = ({ node, show, onClose }: Props) => {
|
||||
onConfirm={submit}
|
||||
onCancel={reset}
|
||||
bond={node.bond}
|
||||
rewards={(node as BondedMixnode).operatorRewards}
|
||||
rewards={nodeType === 'mixnode' ? (node as BondedMixnode).operatorRewards : undefined}
|
||||
fee={fee?.amount as MajorCurrencyAmount}
|
||||
/>
|
||||
{status === 'success' && (
|
||||
Reference in New Issue
Block a user