Skip to content

Commit af6e738

Browse files
component refactor (Kwenta#1726)
* Create StakeCard * Temporarily remove amount from Redux * Some things * Fix issue with parsing claimable rewards * Move some things around * Websocket init * Move yieldPerDay to Redux selector * Little things * Requested changes * Use existing constant
1 parent f662aee commit af6e738

40 files changed

+453
-898
lines changed

components/StakeCard/StakeCard.tsx

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import Wei from '@synthetixio/wei';
2+
import { FC, memo, useCallback, useMemo, useState } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
import styled from 'styled-components';
5+
6+
import Button from 'components/Button';
7+
import NumericInput from 'components/Input/NumericInput';
8+
import SegmentedControl from 'components/SegmentedControl';
9+
import { DEFAULT_CRYPTO_DECIMALS, DEFAULT_TOKEN_DECIMALS } from 'constants/defaults';
10+
import { StakingCard } from 'sections/dashboard/Stake/common';
11+
import { FlexDivRowCentered, numericValueCSS } from 'styles/common';
12+
import { toWei, truncateNumbers } from 'utils/formatters/number';
13+
14+
type StakeCardProps = {
15+
title: string;
16+
stakeBalance: Wei;
17+
unstakeBalance: Wei;
18+
onStake(amount: string): void;
19+
onUnstake(amount: string): void;
20+
stakeEnabled?: boolean;
21+
unstakeEnabled?: boolean;
22+
isApproved?: boolean;
23+
onApprove?: () => void;
24+
};
25+
26+
const StakeCard: FC<StakeCardProps> = memo(
27+
({
28+
title,
29+
stakeBalance,
30+
unstakeBalance,
31+
onStake,
32+
onUnstake,
33+
stakeEnabled = true,
34+
unstakeEnabled = true,
35+
isApproved,
36+
onApprove,
37+
}) => {
38+
const { t } = useTranslation();
39+
40+
const [amount, setAmount] = useState('');
41+
const [activeTab, setActiveTab] = useState(0);
42+
43+
const balance = useMemo(() => {
44+
return activeTab === 0 ? stakeBalance : unstakeBalance;
45+
}, [activeTab, stakeBalance, unstakeBalance]);
46+
47+
const isEnabled = useMemo(() => {
48+
return toWei(amount).gt(0) && balance.gt(0);
49+
}, [amount, balance]);
50+
51+
const isStakeEnabled = useMemo(() => {
52+
return activeTab === 0 && isEnabled && stakeEnabled;
53+
}, [activeTab, isEnabled, stakeEnabled]);
54+
55+
const isUnstakeEnabled = useMemo(() => {
56+
return activeTab === 1 && isEnabled && unstakeEnabled;
57+
}, [activeTab, isEnabled, unstakeEnabled]);
58+
59+
const isDisabled = useMemo(() => {
60+
return activeTab === 0 ? !isStakeEnabled : !isUnstakeEnabled;
61+
}, [activeTab, isStakeEnabled, isUnstakeEnabled]);
62+
63+
const balanceString = useMemo(() => {
64+
return truncateNumbers(balance, DEFAULT_CRYPTO_DECIMALS);
65+
}, [balance]);
66+
67+
const onMaxClick = useCallback(() => {
68+
setAmount(truncateNumbers(balance, DEFAULT_TOKEN_DECIMALS));
69+
}, [balance]);
70+
71+
const handleTabChange = useCallback((tabIndex: number) => {
72+
setAmount('');
73+
setActiveTab(tabIndex);
74+
}, []);
75+
76+
const handleSubmit = useCallback(() => {
77+
if (!isApproved) {
78+
onApprove?.();
79+
} else if (isStakeEnabled) {
80+
onStake(amount);
81+
} else if (isUnstakeEnabled) {
82+
onUnstake(amount);
83+
}
84+
}, [isStakeEnabled, isUnstakeEnabled, onStake, onUnstake, amount, onApprove, isApproved]);
85+
86+
const handleChange = useCallback((_, newValue: string) => {
87+
if (newValue !== '' && newValue.indexOf('.') === -1) {
88+
setAmount(parseFloat(newValue).toString());
89+
} else {
90+
setAmount(newValue);
91+
}
92+
}, []);
93+
94+
return (
95+
<StakingInputCardContainer>
96+
<SegmentedControl
97+
values={[
98+
t('dashboard.stake.tabs.stake-table.stake'),
99+
t('dashboard.stake.tabs.stake-table.unstake'),
100+
]}
101+
onChange={handleTabChange}
102+
selectedIndex={activeTab}
103+
/>
104+
<StakeInputContainer>
105+
<StakeInputHeader>
106+
<div>{title}</div>
107+
<StyledFlexDivRowCentered>
108+
<div>{t('dashboard.stake.tabs.stake-table.balance')}</div>
109+
<div className="max" onClick={onMaxClick}>
110+
{balanceString}
111+
</div>
112+
</StyledFlexDivRowCentered>
113+
</StakeInputHeader>
114+
<StyledInput value={amount} onChange={handleChange} />
115+
</StakeInputContainer>
116+
<Button fullWidth variant="flat" size="sm" disabled={isDisabled} onClick={handleSubmit}>
117+
{!isApproved
118+
? t('dashboard.stake.tabs.stake-table.approve')
119+
: activeTab === 0
120+
? t('dashboard.stake.tabs.stake-table.stake')
121+
: t('dashboard.stake.tabs.stake-table.unstake')}
122+
</Button>
123+
</StakingInputCardContainer>
124+
);
125+
}
126+
);
127+
128+
const StyledFlexDivRowCentered = styled(FlexDivRowCentered)`
129+
column-gap: 5px;
130+
`;
131+
132+
const StakingInputCardContainer = styled(StakingCard)`
133+
min-height: 125px;
134+
max-height: 250px;
135+
display: flex;
136+
flex-direction: column;
137+
justify-content: space-between;
138+
`;
139+
140+
const StakeInputHeader = styled.div`
141+
display: flex;
142+
justify-content: space-between;
143+
align-items: center;
144+
margin-bottom: 10px;
145+
color: ${(props) => props.theme.colors.selectedTheme.title};
146+
font-size: 14px;
147+
148+
.max {
149+
cursor: pointer;
150+
color: ${(props) => props.theme.colors.selectedTheme.button.text.primary};
151+
${numericValueCSS};
152+
}
153+
`;
154+
155+
const StakeInputContainer = styled.div`
156+
margin: 20px 0;
157+
`;
158+
159+
const StyledInput = styled(NumericInput)`
160+
font-family: ${(props) => props.theme.fonts.monoBold};
161+
`;
162+
163+
export default StakeCard;

containers/Connector/Connector.tsx

+12-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ethers } from 'ethers';
44
import { keyBy } from 'lodash';
55
import { useCallback, useEffect, useMemo, useState } from 'react';
66
import { createContainer } from 'unstated-next';
7-
import { chain, useAccount, useNetwork, useProvider, useSigner } from 'wagmi';
7+
import { chain, useAccount, useNetwork, useSigner, useWebSocketProvider } from 'wagmi';
88

99
import { sdk } from 'state/config';
1010
import { useAppDispatch } from 'state/hooks';
@@ -30,8 +30,8 @@ const useConnector = () => {
3030

3131
const walletAddress = useMemo(() => address ?? null, [address]);
3232

33-
const provider = useProvider({ chainId: network.id });
34-
const l2Provider = useProvider({ chainId: chain.optimism.id });
33+
const provider = useWebSocketProvider({ chainId: network.id });
34+
const l2Provider = useWebSocketProvider({ chainId: chain.optimism.id });
3535
const { data: signer } = useSigner();
3636

3737
// Provides a default mainnet provider, irrespective of the current network
@@ -58,11 +58,13 @@ const useConnector = () => {
5858
);
5959

6060
useEffect(() => {
61-
sdk.setProvider(provider).then((networkId) => {
62-
handleNetworkChange(networkId);
63-
setProviderReady(true);
64-
});
65-
transactionNotifier = new BaseTN(provider);
61+
if (!!provider) {
62+
sdk.setProvider(provider).then((networkId) => {
63+
handleNetworkChange(networkId);
64+
setProviderReady(true);
65+
});
66+
transactionNotifier = new BaseTN(provider);
67+
}
6668
}, [provider, handleNetworkChange]);
6769

6870
useEffect(() => {
@@ -90,8 +92,8 @@ const useConnector = () => {
9092
unsupportedNetwork,
9193
isWalletConnected,
9294
walletAddress,
93-
provider,
94-
l2Provider,
95+
provider: provider!,
96+
l2Provider: l2Provider!,
9597
signer,
9698
network,
9799
synthsMap,

containers/Connector/config.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import Safe from 'components/Rainbowkit/Gnosis';
1919
import Tally from 'components/Rainbowkit/Tally';
2020
import { BLAST_NETWORK_LOOKUP } from 'constants/network';
2121

22-
const { chains, provider } = configureChains(
22+
const { chains, provider, webSocketProvider } = configureChains(
2323
[chain.optimism, chain.mainnet, chain.optimismGoerli, chain.goerli],
2424
[
2525
infuraProvider({
@@ -30,11 +30,17 @@ const { chains, provider } = configureChains(
3030
jsonRpcProvider({
3131
rpc: (networkChain) => {
3232
return !BLAST_NETWORK_LOOKUP[networkChain.id]
33-
? { http: networkChain.rpcUrls.default }
33+
? {
34+
http: networkChain.rpcUrls.default,
35+
webSocket: networkChain.rpcUrls.default.replace('https', 'wss'),
36+
}
3437
: {
3538
http: `https://${BLAST_NETWORK_LOOKUP[networkChain.id]}.blastapi.io/${
3639
process.env.NEXT_PUBLIC_BLASTAPI_PROJECT_ID
3740
}`,
41+
webSocket: `wss://${BLAST_NETWORK_LOOKUP[networkChain.id]}.blastapi.io/${
42+
process.env.NEXT_PUBLIC_BLASTAPI_PROJECT_ID
43+
}`,
3844
};
3945
},
4046
stallTimeout: 5000,
@@ -72,6 +78,7 @@ export const wagmiClient = createClient({
7278
autoConnect: true,
7379
connectors,
7480
provider,
81+
webSocketProvider,
7582
});
7683

7784
export { chains };

sdk/context.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { ethers } from 'ethers';
55
import * as sdkErrors from './common/errors';
66
import {
77
ContractsMap,
8-
MultiCallContractsMap,
8+
MulticallContractsMap,
99
getContractsByNetwork,
10-
getMultiCallContractsByNetwork,
10+
getMulticallContractsByNetwork,
1111
} from './contracts';
1212

1313
export interface IContext {
@@ -25,7 +25,7 @@ export default class Context implements IContext {
2525
private context: IContext;
2626
public multicallProvider = new EthCallProvider();
2727
public contracts: ContractsMap;
28-
public mutliCallContracts: MultiCallContractsMap;
28+
public multicallContracts: MulticallContractsMap;
2929

3030
constructor(context: IContext) {
3131
this.context = { ...DEFAULT_CONTEXT, ...context };
@@ -39,7 +39,7 @@ export default class Context implements IContext {
3939
}
4040

4141
this.contracts = getContractsByNetwork(context.networkId, context.signer ?? context.provider);
42-
this.mutliCallContracts = getMultiCallContractsByNetwork(context.networkId);
42+
this.multicallContracts = getMulticallContractsByNetwork(context.networkId);
4343
}
4444

4545
get networkId() {
@@ -86,7 +86,7 @@ export default class Context implements IContext {
8686
public setNetworkId(networkId: NetworkId) {
8787
this.context.networkId = networkId;
8888
this.contracts = getContractsByNetwork(networkId, this.context.signer ?? this.provider);
89-
this.mutliCallContracts = getMultiCallContractsByNetwork(networkId);
89+
this.multicallContracts = getMulticallContractsByNetwork(networkId);
9090
}
9191

9292
public async setSigner(signer: ethers.Signer) {

sdk/contracts/index.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import FuturesMarketDataABI from './abis/FuturesMarketData.json';
1212
import FuturesMarketSettingsABI from './abis/FuturesMarketSettings.json';
1313
import KwentaStakingRewardsABI from './abis/KwentaStakingRewards.json';
1414
import StakingRewardsABI from './abis/StakingRewards.json';
15+
import SynthRedeemerABI from './abis/SynthRedeemer.json';
1516
import { ADDRESSES } from './constants';
1617
import {
1718
CrossMarginAccountFactory__factory,
@@ -132,8 +133,11 @@ export const getContractsByNetwork = (
132133
};
133134
};
134135

135-
export const getMultiCallContractsByNetwork = (networkId: NetworkId) => {
136+
export const getMulticallContractsByNetwork = (networkId: NetworkId) => {
136137
return {
138+
SynthRedeemer: ADDRESSES.SynthRedeemer[networkId]
139+
? new EthCallContract(ADDRESSES.SynthRedeemer[networkId], SynthRedeemerABI)
140+
: undefined,
137141
CrossMarginBaseSettings: ADDRESSES.CrossMarginBaseSettings[networkId]
138142
? new EthCallContract(
139143
ADDRESSES.CrossMarginBaseSettings[networkId],
@@ -180,5 +184,5 @@ export const getMultiCallContractsByNetwork = (networkId: NetworkId) => {
180184
};
181185

182186
export type ContractsMap = ReturnType<typeof getContractsByNetwork>;
183-
export type MultiCallContractsMap = ReturnType<typeof getMultiCallContractsByNetwork>;
187+
export type MulticallContractsMap = ReturnType<typeof getMulticallContractsByNetwork>;
184188
export type ContractName = keyof ContractsMap;

0 commit comments

Comments
 (0)