nova-ui/pages/fund.vue
2022-12-04 07:02:30 +01:00

351 lines
10 KiB
Vue

<template>
<styled-switcher slot-name="fund">
<template slot="body">
<div :class="$style.fund__inputWrap">
<base-input
:ticker="chainConfig.symbol"
button-text="max"
input-mode="numeric"
label="Token amount"
:model-value="amount"
:error="hasError"
:button-click="onSetMax"
:icon="chainConfig.icon"
:disabled="isInputDisabled"
:info="balance"
:error-message="errorMessage"
@update:modelValue="onInputChangeAmount"
/>
</div>
<base-input
type="text"
label="Recipient address"
:error="isRecipientAddressError"
:loading="isAddressChecking"
:disabled="isInputDisabled"
:model-value="recipientAddress"
:error-message="recipientAddressError"
:error-link="recipientAddressErrorLink"
placeholder-text="Place recipient address here"
@update:modelValue="onChangeRecipientAddress"
/>
</template>
<template slot="actions">
<base-button v-if="shouldChangeChain" full-width @click="onChainChange">Change network</base-button>
<span v-else-if="isConnected" v-tooltip="actionButtonTooltip" :class="$style.fund__actionWrapper">
<base-button full-width :loading="isLoading" :disabled="isActionButtonDisabled" @click="onFundAction">
{{ actionButtonText('Fund', amount) }}
</base-button>
</span>
<base-button v-else full-width :loading="isLoading" @click="connectModal">Connect wallet</base-button>
</template>
</styled-switcher>
</template>
<script>
import { mapActions, mapGetters, mapMutations } from 'vuex'
import { ApplicationMutation, AccountMutation } from '@/types'
import { ConfirmationModal } from '@/modals'
import { StyledSwitcher } from '@/containers'
import { debounce, createModalArgs, toDecimalsPlaces, fromWei } from '@/utilities'
import { errors, BG_ZERO, numbers, registerGuideUrl, transactionMethods, registerStatuses } from '@/constants'
export default {
components: {
StyledSwitcher,
},
inject: ['setupProvider'],
data: function () {
return {
amount: '',
isENS: false,
ensAddress: '',
hasError: false,
isLoading: false,
errorMessage: '',
recipientAddress: '',
isEnoughBalance: true,
isInvalidAddress: false,
isAddressChecking: false,
isRecipientRegistered: false,
}
},
computed: {
...mapGetters('transaction', ['isPendingTxs']),
...mapGetters('account', ['accountAddress', 'isRegisteredInPool', 'isRegisteredInPoolNotChecked', 'isRegisterProcessing']),
...mapGetters('wallet', [
'isL2Chain',
'l1ChainId',
'isConnected',
'chainConfig',
'walletBalance',
'walletAddress',
'mismatchNetwork',
]),
...mapGetters('application', [
'maximumDeposit',
'getMaxAmountFund',
'actionButtonText',
'isProcessingStarted',
'getIsBalanceEnoughFund',
]),
isFundForSelf() {
return this.recipientAddress === this.accountAddress || this.accountAddress === this.ensAddress
},
isExternalAddressNotRegistered() {
const isAddress = !this.isInvalidAddress && Boolean(this.recipientAddress.length)
const isValidRecipient = !this.isRecipientRegistered && !this.isFundForSelf
return !this.isAddressChecking && isAddress && isValidRecipient
},
actionButtonTooltip() {
if (this.isPendingTxs) {
return 'Has pending transactions'
}
if (this.isRegisteredInPoolNotChecked) {
return 'Checking registration'
}
if (this.isRegisterProcessing) {
return 'Waiting for registration'
}
return null
},
shouldChangeChain() {
return this.isConnected && (this.isL2Chain || this.mismatchNetwork)
},
isRecipientAddressError() {
const isLoading = this.isAddressChecking || this.isLoading
const isError = this.isInvalidAddress || this.isExternalAddressNotRegistered
return isError && !isLoading && !!this.recipientAddress
},
isActionButtonDisabled() {
const isAddressError = !this.recipientAddress || this.isRecipientAddressError
const isLoading = this.isPendingTxs || this.isLoading || this.isAddressChecking || this.isProcessingStarted
const isFundDisabled =
this.isConnected && (isAddressError || !Number(this.amount) || !this.isEnoughBalance || this.hasError)
return isFundDisabled || isLoading || this.isRegisteredInPoolNotChecked || this.isRegisterProcessing
},
isInputDisabled() {
return this.isLoading || !this.isConnected
},
balance() {
let formattedBalance = '0'
if (this.isConnected) {
formattedBalance = toDecimalsPlaces(fromWei(this.walletBalance))
}
return 'Wallet balance: ' + formattedBalance
},
recipientAddressError() {
if (this.isInvalidAddress) {
return errors.validation.INVALID_ADDRESS
}
if (this.isExternalAddressNotRegistered) {
return errors.validation.NOT_REGISTERED_IN_POOL
}
return ''
},
recipientAddressErrorLink() {
if (this.isExternalAddressNotRegistered && !this.isInvalidAddress) {
return registerGuideUrl
}
return null
},
},
watch: {
accountAddress(newAddress) {
this.recipientAddress = newAddress
},
balance() {
this.checkIsBalanceEnough()
},
isConnected(newValue) {
if (!newValue) {
this.resetError()
this.amount = ''
this.isENS = false
this.ensAddress = ''
this.recipientAddress = ''
}
},
amount() {
this.checkIsBalanceEnough()
this.checkAmountDebouncer()
},
isRegisteredInPool(newValue) {
if (this.isFundForSelf) {
this.isRecipientRegistered = newValue
}
},
},
created() {
this.isENS = false
this.ensAddress = ''
this.recipientAddress = this.accountAddress
this.isRecipientRegistered = this.isRegisteredInPool
},
methods: {
...mapMutations('application', [ApplicationMutation.SET_PROCESSING_MODAL]),
...mapMutations('account', [AccountMutation.SET_REGISTERED_IN_POOL_STATUS]),
...mapActions('account', ['checkRegisterInPool']),
...mapActions('wallet', ['changeChain', 'getWalletBalance']),
...mapActions('application', ['errorHandler', 'validateAddress', 'checkRecipientAddress', 'createDepositWithStatus']),
resetError() {
this.hasError = false
this.errorMessage = ''
},
async onChainChange() {
try {
this.isLoading = true
this.resetError()
await this.changeChain(this.l1ChainId)
this.checkIsBalanceEnough()
this.checkAmountDebouncer()
} catch (err) {
await this.errorHandler({ errorMessage: err.message, title: 'Change network error' })
} finally {
this.isLoading = false
}
},
async onFundAction() {
try {
this.isLoading = true
this.resetError()
await this.getWalletBalance()
this.checkIsBalanceEnough()
if (!this.isEnoughBalance) {
return
}
const maximumDeposit = Number(this.maximumDeposit)
if (Number(this.amount) > maximumDeposit) {
this.errorMessage = `${errors.validation.MAX_DEPOSIT_AMOUNT} ${maximumDeposit} ETH`
this.hasError = true
return
}
this[ApplicationMutation.SET_PROCESSING_MODAL]({
title: 'Deposit',
chainId: this.l1ChainId,
type: transactionMethods.FUND,
})
this.$modal.show(...createModalArgs(ConfirmationModal, {}, { clickToClose: false }))
if (this.isRecipientRegistered) {
const address = this.isENS ? this.ensAddress : this.recipientAddress
await this.createDepositWithStatus({ amount: this.amount, address })
return
}
if (this.isFundForSelf) {
this[AccountMutation.SET_REGISTERED_IN_POOL_STATUS](registerStatuses.PROCESSING)
await this.createDepositWithStatus({ amount: this.amount, withRegister: true, address: this.walletAddress })
}
} catch {
if (this.isFundForSelf && !this.isRecipientRegistered) {
this.checkRegisterInPool(this.walletAddress)
}
} finally {
if (!this.hasError) {
this.amount = ''
this.getWalletBalance()
}
this.isLoading = false
}
},
checkIsBalanceEnough() {
if (!Number(this.amount)) {
this.isEnoughBalance = true
return
}
this.isEnoughBalance = this.getIsBalanceEnoughFund(this.amount)
this.hasError = !this.isEnoughBalance
this.errorMessage = !this.isEnoughBalance ? errors.validation.INSUFFICIENT_FUNDS : ''
},
checkAmountDebouncer: debounce(function () {
const maximumDeposit = Number(this.maximumDeposit)
const isLessThanMax = Number(this.amount) > maximumDeposit
if (isLessThanMax && !this.hasError) {
this.errorMessage = `${errors.validation.MAX_DEPOSIT_AMOUNT} ${maximumDeposit} ETH`
this.hasError = true
}
}, numbers.CHECK_AMOUNT_DELAY),
onInputChangeAmount(value) {
this.resetError()
this.amount = value
},
onSetMax() {
this.resetError()
const maxAmount = this.getMaxAmountFund()
this.amount = maxAmount.gt(BG_ZERO) ? fromWei(maxAmount) : '0'
this.checkAmountDebouncer()
},
onChangeRecipientAddress(value) {
this.recipientAddress = value
this.isAddressChecking = true
this.checkAddressDebouncer()
},
async connectModal() {
try {
this.isLoading = true
await this.setupProvider('METAMASK')
} catch (err) {
} finally {
this.isLoading = false
}
},
checkAddressDebouncer: debounce(async function () {
this.isAddressChecking = true
this.isRecipientRegistered = false
const validationState = await this.checkRecipientAddress(this.recipientAddress)
if (validationState) {
this.isENS = validationState.isENS
this.ensAddress = validationState.ensAddress
this.isInvalidAddress = validationState.isInvalidAddress
this.isRecipientRegistered = validationState.isRegistered
}
this.isAddressChecking = false
}, numbers.CHECK_ADDRESS_DELAY),
},
}
</script>
<style lang="scss" module>
.fund {
&__inputWrap {
margin: 0 0 2.4rem;
}
&__actionWrapper {
width: 100%;
}
}
</style>