Skip to content

Commit dc4a3b7

Browse files
authored
add getLastCreditBatchId to depositApi & useTokenBalances claimable check (gnosis#1302)
* add getLastCreditBatchId to depositApi 1. added inside useTokenBalances a check against current batchId * fixed broken mock * fix depostApiMock.test * move lastCreditedBatchId check to onClaim * css and useNoScroll edit * immatureClaim 1. added svg indicators 2. warning 3. added global state to track * fixed types * fix test
1 parent 687b413 commit dc4a3b7

File tree

12 files changed

+135
-33
lines changed

12 files changed

+135
-33
lines changed

src/api/deposit/DepositApi.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface DepositApi {
4141
getBalance(params: GetBalanceParams): Promise<BN>
4242
getPendingDeposit(params: GetPendingDepositParams): Promise<PendingFlux>
4343
getPendingWithdraw(params: GetPendingWithdrawParams): Promise<PendingFlux>
44+
getLastCreditBatchId(params: GetPendingDepositParams): Promise<number>
4445

4546
deposit(params: DepositParams): Promise<Receipt>
4647
requestWithdraw(params: RequestWithdrawParams): Promise<Receipt>
@@ -87,6 +88,16 @@ export class DepositApiImpl implements DepositApi {
8788
return +batchId
8889
}
8990

91+
public async getLastCreditBatchId({
92+
userAddress,
93+
tokenAddress,
94+
networkId,
95+
}: GetPendingDepositParams): Promise<number> {
96+
const contract = await this._getContract(networkId)
97+
const lastCreditBatchId = await contract.methods.lastCreditBatchId(userAddress, tokenAddress).call()
98+
return +lastCreditBatchId
99+
}
100+
90101
public async getSecondsRemainingInBatch(networkId: number): Promise<number> {
91102
const contract = await this._getContract(networkId)
92103
const secondsRemainingInBatch = await contract.methods.getSecondsRemainingInBatch().call()

src/api/deposit/DepositApiMock.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Erc20Api } from 'api/erc20/Erc20Api'
2222

2323
export interface BalanceState {
2424
balance: BN
25+
lastCreditedBatchId: number
2526
pendingDeposits: PendingFlux
2627
pendingWithdraws: PendingFlux
2728
}
@@ -47,6 +48,16 @@ export class DepositApiMock implements DepositApi {
4748
return BATCH_TIME
4849
}
4950

51+
public async getLastCreditBatchId({ userAddress, tokenAddress }: GetPendingDepositParams): Promise<number> {
52+
const userBalanceStates = this._balanceStates[userAddress]
53+
if (!userBalanceStates) {
54+
return 0
55+
}
56+
const balanceState = userBalanceStates[tokenAddress]
57+
58+
return balanceState ? balanceState.lastCreditedBatchId : 0
59+
}
60+
5061
public async getCurrentBatchId(_networkId = 0): Promise<number> {
5162
return Math.floor(getEpoch() / BATCH_TIME)
5263
}
@@ -191,6 +202,7 @@ export class DepositApiMock implements DepositApi {
191202
if (!balanceState) {
192203
balanceState = {
193204
balance: ZERO,
205+
lastCreditedBatchId: 0,
194206
pendingDeposits: {
195207
batchId: currentBatchId,
196208
amount: ZERO,

src/components/DepositWidget/Row.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import BN from 'bn.js'
33

44
// Assets
55
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6-
import { faClock, faPlus, faMinus } from '@fortawesome/free-solid-svg-icons'
6+
import { faClock, faPlus, faMinus, faBaby } from '@fortawesome/free-solid-svg-icons'
77
import { MinusSVG, PlusSVG } from 'assets/img/SVG'
88

99
// const, utils, types
@@ -24,6 +24,7 @@ import { TokenRow, RowClaimButton, RowClaimSpan } from 'components/DepositWidget
2424
import useNoScroll from 'hooks/useNoScroll'
2525
import { TokenLocalState } from 'reducers-actions'
2626
import { TokenSymbol } from 'components/TokenSymbol'
27+
import { HelpTooltip, HelpTooltipContainer } from 'components/Tooltip'
2728

2829
export interface RowProps extends Record<keyof TokenLocalState, boolean> {
2930
ethBalance: BN | null
@@ -37,6 +38,12 @@ export interface RowProps extends Record<keyof TokenLocalState, boolean> {
3738
}
3839

3940
const spinner = <Spinner style={{ marginRight: 7 }} />
41+
const ImmatureClaimTooltip: React.FC<{ displayName: string }> = ({ displayName }) => (
42+
<HelpTooltipContainer>
43+
You cannot withdraw in this batch because you received {displayName} in the previous batch and the protocol requires
44+
one additional batch for settling the received tokens. Wait for the next batch (max 5 min) and try again.
45+
</HelpTooltipContainer>
46+
)
4047

4148
export const Row: React.FC<RowProps> = (props: RowProps) => {
4249
const {
@@ -53,6 +60,7 @@ export const Row: React.FC<RowProps> = (props: RowProps) => {
5360
claiming,
5461
withdrawing,
5562
depositing,
63+
immatureClaim: isImmatureClaim,
5664
} = props
5765

5866
const {
@@ -72,10 +80,11 @@ export const Row: React.FC<RowProps> = (props: RowProps) => {
7280
} = tokenBalances
7381

7482
const [visibleForm, showForm] = useState<'deposit' | 'withdraw' | void>()
83+
// block background scrolling on open modals
84+
useNoScroll(!!visibleForm)
7585

7686
// Checks innerWidth
7787
const showResponsive = !!innerWidth && innerWidth < MEDIA.MOBILE_LARGE_PX
78-
useNoScroll(!!visibleForm && showResponsive)
7988

8089
let className
8190
if (highlighted) {
@@ -116,7 +125,15 @@ export const Row: React.FC<RowProps> = (props: RowProps) => {
116125
<RowClaimButton className="success" onClick={onClaim} disabled={claiming}>
117126
{(claiming || withdrawing) && spinner}
118127
{formatSmart(pendingWithdraw.amount, decimals)}
119-
<RowClaimSpan className={claiming || withdrawing ? 'disabled' : 'success'}>Claim</RowClaimSpan>
128+
<div>
129+
<RowClaimSpan className={claiming || withdrawing ? 'disabled' : 'success'}>Claim</RowClaimSpan>
130+
{isImmatureClaim && (
131+
<span className="immatureClaimTooltip">
132+
<FontAwesomeIcon icon={faBaby} />
133+
<HelpTooltip tooltip={<ImmatureClaimTooltip displayName={symbol || name || 'tokens'} />} />
134+
</span>
135+
)}
136+
</div>
120137
</RowClaimButton>
121138
</>
122139
) : pendingWithdraw.amount.gt(ZERO) ? (

src/components/DepositWidget/Styled.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,20 @@ export const RowClaimButton = styled.button`
129129
cursor: not-allowed;
130130
opacity: 0.5;
131131
}
132+
133+
> div {
134+
display: flex;
135+
align-items: inherit;
136+
137+
> .immatureClaimTooltip {
138+
color: #d2a827;
139+
margin-left: 0.5rem;
140+
141+
> span {
142+
margin: 0.5rem;
143+
}
144+
}
145+
}
132146
`
133147

134148
export const RowClaimSpan = styled.span`

src/components/DepositWidget/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ const NoTokensMessage = styled.tr`
171171
interface BalanceDisplayProps extends TokenLocalState {
172172
enableToken: (tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void>
173173
depositToken: (amount: BN, tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void>
174-
claimToken: (tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void>
174+
claimToken: (tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void | React.ReactText>
175175
ethBalance: BN | null
176176
balances: TokenBalanceDetails[]
177177
error: boolean
@@ -211,6 +211,7 @@ const BalancesDisplay: React.FC<BalanceDisplayProps> = ({
211211
highlighted,
212212
enabling,
213213
enabled,
214+
immatureClaim,
214215
requestWithdrawConfirmation,
215216
hasTokensToShow = false,
216217
}) => {
@@ -307,13 +308,14 @@ const BalancesDisplay: React.FC<BalanceDisplayProps> = ({
307308
onTxHash,
308309
)
309310
}}
310-
onClaim={(): Promise<void> => claimToken(tokenBalances.address)}
311+
onClaim={(): Promise<void | React.ReactText> => claimToken(tokenBalances.address)}
311312
claiming={claiming.has(tokenBalances.address)}
312313
withdrawing={withdrawing.has(tokenBalances.address)}
313314
depositing={depositing.has(tokenBalances.address)}
314315
highlighted={highlighted.has(tokenBalances.address)}
315316
enabling={enabling.has(tokenBalances.address)}
316317
enabled={enabled.has(tokenBalances.address)}
318+
immatureClaim={immatureClaim.has(tokenBalances.address)}
317319
{...windowSpecs}
318320
/>
319321
))

src/components/DepositWidget/useRowActions.tsx

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
setHighlightAndDepositing,
2323
setHighlightAndWithdrawing,
2424
setEnabledAction,
25+
setImmatureClaim,
2526
} from 'reducers-actions'
2627

2728
const ON_ERROR_MESSAGE = 'No logged in user found. Please check wallet connectivity status and try again.'
@@ -34,7 +35,7 @@ interface Result extends TokenLocalState {
3435
enableToken: (tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void>
3536
depositToken: (amount: BN, tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void>
3637
requestWithdrawToken: (amount: BN, tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void>
37-
claimToken: (tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void>
38+
claimToken: (tokenAddress: string, onTxHash?: (hash: string) => void) => Promise<void | React.ReactText>
3839
}
3940

4041
export const useRowActions = (params: Params): Result => {
@@ -149,14 +150,45 @@ export const useRowActions = (params: Params): Result => {
149150
}
150151
}
151152

152-
async function claimToken(tokenAddress: string, onTxHash?: (hash: string) => void): Promise<void> {
153+
async function claimToken(
154+
tokenAddress: string,
155+
onTxHash?: (hash: string) => void,
156+
): Promise<void | React.ReactText> {
153157
try {
154158
assert(userAddress, ON_ERROR_MESSAGE)
155159
assert(networkId, ON_ERROR_MESSAGE)
156-
157160
const token = getToken('address', tokenAddress, balances)
158161
assert(token, 'No token')
159162

163+
// highlight row after asserting tokenaddress exists
164+
dispatch(setHighlightAndClaimingAction(tokenAddress))
165+
166+
const [lastCreditedBatchId, currentBatchId] = await Promise.all([
167+
depositApi.getLastCreditBatchId({ userAddress, tokenAddress, networkId }),
168+
depositApi.getCurrentBatchId(networkId),
169+
])
170+
171+
// Check if user is in immature claim state
172+
// if so, show warning and set to global
173+
// else if theyre not, but state exists, remove them
174+
if (lastCreditedBatchId === currentBatchId) {
175+
const isClaimImmature = state.immatureClaim.has(tokenAddress)
176+
const immatureClaimWarning = `You cannot withdraw in this batch because you received ${token.symbol} in the previous batch and the protocol requires one additional batch for settling the received tokens.
177+
Wait for the next batch (max 5 min) and try again.`
178+
179+
// user does NOT already have immature claim status on this token
180+
if (!isClaimImmature) {
181+
dispatch(setImmatureClaim(tokenAddress))
182+
}
183+
184+
return toast.warn(immatureClaimWarning, { autoClose: false })
185+
// user no longer is in block state
186+
// but has global state blocked status
187+
// REMOVE them
188+
} else if (state.immatureClaim.has(tokenAddress)) {
189+
dispatch(setImmatureClaim(tokenAddress))
190+
}
191+
160192
const { pendingWithdraw, symbol, decimals } = safeFilledToken<TokenBalanceDetails>(token)
161193

162194
console.debug(
@@ -166,8 +198,6 @@ export const useRowActions = (params: Params): Result => {
166198
})} of ${symbol}`,
167199
)
168200

169-
dispatch(setHighlightAndClaimingAction(tokenAddress))
170-
171201
const receipt = await depositApi.withdraw({
172202
userAddress,
173203
tokenAddress,
@@ -191,7 +221,7 @@ export const useRowActions = (params: Params): Result => {
191221
requestWithdrawToken,
192222
claimToken,
193223
}
194-
}, [balances, dispatch, networkId, userAddress])
224+
}, [balances, dispatch, networkId, userAddress, state.immatureClaim])
195225

196226
return useMemo(
197227
() => ({

src/components/Layout/Card/Card.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,36 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
77
import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons'
88

99
const CardRowDrawer = styled.tr`
10+
position: fixed;
11+
z-index: 1050;
1012
display: flex;
1113
justify-content: center;
1214
align-items: center;
13-
position: fixed;
1415
top: 0;
1516
left: 0;
16-
right: 0;
17-
bottom: 0;
18-
margin: auto;
19-
box-shadow: 0 100vh 0 999vw rgba(47, 62, 78, 0.5);
20-
z-index: 9998;
21-
width: 50rem;
22-
height: 50rem;
17+
width: 100%;
18+
height: 100%;
2319
border-radius: 0.6rem;
24-
background: var(--color-background-pageWrapper);
25-
26-
@media ${MEDIA.mobile} {
27-
width: 100%;
28-
bottom: 0;
29-
top: initial;
30-
height: 100vh;
31-
overflow-y: scroll;
32-
}
20+
background: var(--color-background-modali);
3321
3422
// Inner td wrapper
3523
> td {
3624
position: relative;
37-
background: transparent;
25+
z-index: 9999;
26+
background: var(--color-background-pageWrapper);
3827
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.14);
39-
border-radius: 6px;
28+
border-radius: var(--border-radius);
4029
margin: 0;
41-
width: 100%;
42-
height: 100%;
30+
width: 50rem;
31+
height: 50rem;
4332
border: 0;
4433
4534
@media ${MEDIA.mobile} {
35+
width: 100%;
36+
bottom: 0;
37+
top: initial;
38+
height: 100vh;
39+
overflow-y: scroll;
4640
box-shadow: none;
4741
}
4842

src/reducers-actions/tokenRow.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const enum ActionTypes {
1010
SET_HIGHLIGHTED_AND_CLAIMING = 'highlighted_and_claiming',
1111
SET_HIGHLIGHTED_AND_DEPOSITING = 'highlighted_and_depositing',
1212
SET_HIGHLIGHTED_AND_WITHDRAWING = 'highlighted_and_withdrawing',
13+
SET_IMMATURE_CLAIM = 'immatureClaim',
1314
}
1415

1516
type TokenRowActions = Actions<ActionTypes, string>
@@ -59,13 +60,19 @@ export const setHighlightAndWithdrawing = (payload: string): TokenRowActions =>
5960
payload,
6061
})
6162

63+
export const setImmatureClaim = (payload: string): TokenRowActions => ({
64+
type: ActionTypes.SET_IMMATURE_CLAIM,
65+
payload,
66+
})
67+
6268
export interface TokenLocalState {
6369
[ActionTypes.SET_ENABLING]: Set<string>
6470
[ActionTypes.SET_ENABLED]: Set<string>
6571
[ActionTypes.SET_HIGHLIGHTED]: Set<string>
6672
[ActionTypes.SET_CLAIMING]: Set<string>
6773
[ActionTypes.SET_DEPOSITING]: Set<string>
6874
[ActionTypes.SET_WITHDRAWING]: Set<string>
75+
[ActionTypes.SET_IMMATURE_CLAIM]: Set<string>
6976
}
7077

7178
export const TokenRowInitialState: TokenLocalState = {
@@ -75,6 +82,7 @@ export const TokenRowInitialState: TokenLocalState = {
7582
claiming: new Set(),
7683
depositing: new Set(),
7784
withdrawing: new Set(),
85+
immatureClaim: new Set(),
7886
}
7987

8088
function getRemainingType(type: ActionTypes.SET_HIGHLIGHTED_AND_CLAIMING): ActionTypes.SET_CLAIMING
@@ -110,7 +118,8 @@ export const reducer = (state: TokenLocalState, action: TokenRowActions): TokenL
110118
case ActionTypes.SET_CLAIMING:
111119
case ActionTypes.SET_DEPOSITING:
112120
case ActionTypes.SET_WITHDRAWING:
113-
case ActionTypes.SET_HIGHLIGHTED: {
121+
case ActionTypes.SET_HIGHLIGHTED:
122+
case ActionTypes.SET_IMMATURE_CLAIM: {
114123
const newSet = new Set(state[type])
115124
return {
116125
...state,

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export interface TokenBalanceDetails extends TokenDetails {
3838
claimable: boolean
3939
enabled: boolean
4040
totalExchangeBalance: BN
41+
immatureClaim?: boolean
4142
}
4243

4344
export interface WithTxOptionalParams {

0 commit comments

Comments
 (0)