This commit is contained in:
Danil Kovtonyuk 2022-04-22 13:05:56 +10:00
commit 44f31f8b9f
No known key found for this signature in database
GPG key ID: E72A919BF08C3746
402 changed files with 47865 additions and 0 deletions

View file

@ -0,0 +1,148 @@
<template>
<b-field :type="type" :message="error">
<template slot="label">
{{ $t('ethPurchase', { currency }) }}
<b-tooltip
:label="$t('ethPurchaseTooltip', { currency: selectedStatisticCurrency, networkCurrency: currency })"
size="is-small"
position="is-right"
multilined
>
<button class="button is-primary has-icon">
<span class="icon icon-info"></span>
</button>
</b-tooltip>
</template>
<div
class="field has-eth-purchase"
:class="[type, { 'is-disabled': disabled }]"
@click="onEthPurchaseClick"
>
<div class="columns is-mobile">
<div class="column currency-container is-light">
<div class="currency">{{ currency }}</div>
<b-input
ref="input"
v-model.number="newValue"
type="number"
step="0.01"
:max="max"
:min="min"
:disabled="disabled"
:use-html5-validation="false"
expanded
custom-class="hide-spinner"
@input="onInput"
@focus="$emit('focus', $event)"
@blur="$emit('blur', $event)"
/>
<div class="withdraw-data">
<div class="withdraw-data-item">
{{ $t('rate') }}
<span> {{ toDecimals(tokenRate, 18, 6) }} {{ currency }}/{{ selectedStatisticCurrency }} </span>
</div>
</div>
</div>
<div class="column arrow-container">
<EthPurchaseArrow />
</div>
<div class="column currency-container is-inverted">
<div class="currency">{{ selectedStatisticCurrency }}</div>
<div class="input">{{ ethToReceiveInToken }}</div>
</div>
</div>
</div>
</b-field>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import { EthPurchaseArrow } from '@/components/icons'
import { debounce } from '@/utils'
const { toBN, toWei } = require('web3-utils')
export default {
components: {
EthPurchaseArrow
},
props: {
disabled: Boolean,
value: {
type: Number,
required: true
},
defaultEthToReceive: {
type: Number,
required: true
}
},
data() {
return {
type: '',
min: 0,
error: '',
newValue: this.value
}
},
computed: {
...mapState('application', ['selectedStatistic']),
...mapGetters('application', ['maxEthToReceive', 'selectedStatisticCurrency']),
...mapGetters('token', ['toDecimals']),
...mapGetters('metamask', ['networkConfig', 'currency']),
...mapGetters('price', ['tokenRate']),
max() {
return Math.max(0, Number(this.toDecimals(this.maxEthToReceive, 18, 5)))
},
ethToReceiveInToken() {
const { currency } = this.selectedStatistic
const { decimals } = this.networkConfig.tokens[currency]
const price = this.tokenRate
const ethToReceive = toBN(toWei(Math.min(Math.max(this.min, this.newValue), this.max).toString()))
return this.toDecimals(ethToReceive.mul(toBN(10 ** decimals)).div(toBN(price)), null, 6)
}
},
watch: {
value(value) {
this.newValue = value
},
newValue(value) {
debounce(this.validateEthToReceive, value)
}
},
mounted() {
this.validateEthToReceive(this.newValue)
},
methods: {
onEthPurchaseClick() {
this.$refs.input.focus()
},
onInput(value) {
const parsedValue = parseFloat(value)
if (!Number.isNaN(parsedValue)) {
this.$emit('input', parsedValue)
}
},
validateEthToReceive(value) {
let type = ''
let error = ''
if (value === '') {
type = 'is-warning'
error = this.$t('incorrectAmount')
} else if (value < 0) {
type = 'is-warning'
error = this.$t('amountIsLow', { value: this.min })
} else if (value > this.max) {
type = 'is-warning'
error = this.$t('amountIsHigh', { value: this.max })
} else if (value === this.defaultEthToReceive) {
type = 'is-primary'
}
this.error = error
this.type = type
this.$emit('isValidEthToReceive', type !== 'is-warning')
}
}
}
</script>

View file

@ -0,0 +1,66 @@
<template>
<div class="modal-card box box-modal">
<header class="box-modal-header is-spaced">
<div class="box-modal-title">{{ $t('withdrawalSettings') }}</div>
<button type="button" class="delete" @click="$parent.cancel('escape')" />
</header>
<b-tabs v-model="withdrawType" :animated="false" class="is-modal">
<RelayerTab />
<WalletTab />
</b-tabs>
</div>
</template>
<script>
/* eslint-disable no-console */
import { mapState, mapMutations } from 'vuex'
import { RelayerTab, WalletTab } from '@/components/settings/tabs'
export default {
components: {
RelayerTab,
WalletTab
},
props: {
currency: {
type: String,
default: 'ETH'
},
title: {
type: String,
default: 'withdrawalSettings'
}
},
provide() {
return {
currency: this.currency,
save: this.save,
reset: this.reset
}
},
data() {
return {
withdrawType: 'relayer'
}
},
computed: {
...mapState('application', {
defaultWithdrawType: 'withdrawType'
})
},
created() {
this.withdrawType = this.defaultWithdrawType
},
methods: {
...mapMutations('application', ['SET_WITHDRAW_TYPE']),
reset() {
this.withdrawType = 'relayer'
this.$root.$emit('resetSettings')
},
save() {
this.SET_WITHDRAW_TYPE({ withdrawType: this.withdrawType })
this.$emit('close')
}
}
}
</script>

View file

@ -0,0 +1,259 @@
<template>
<b-tab-item :label="$t('relayer')" value="relayer">
<div class="field">
<b-field :label="$t('relayer')">
<b-dropdown v-model="selectedRelayer" expanded aria-role="list" @change="onChangeRelayer">
<div slot="trigger" class="control" :class="{ 'is-loading': checkingRelayer || isLoadingRelayers }">
<div class="input">
<span>{{ dropdownValue }}</span>
</div>
</div>
<b-dropdown-item
v-for="{ name, tornadoServiceFee } in relayers"
v-show="!isLoadingRelayers"
:key="name"
:value="name"
aria-role="listitem"
>
{{ getRelayerName({ name, tornadoServiceFee }) }}
</b-dropdown-item>
<b-dropdown-item value="custom" aria-role="listitem">
{{ $t('custom') }}
</b-dropdown-item>
</b-dropdown>
</b-field>
<div v-if="isCustomRelayer" class="field has-custom-field">
<b-input
ref="customInput"
v-model="customRelayerUrl"
type="url"
:placeholder="$t('pasteYourRelayerUrlorEnsRecord')"
:custom-class="hasErrorRelayer.type"
:use-html5-validation="false"
@input="onInputCustomRelayer"
></b-input>
</div>
<div class="withdraw-data is-spaced">
<div class="withdraw-data-item">
{{ $t('relayerFee') }}
<span> {{ relayer.tornadoServiceFee }}% </span>
</div>
</div>
<p v-if="hasErrorRelayer.msg" class="help" :class="hasErrorRelayer.type">
{{ hasErrorRelayer.msg }}
</p>
</div>
<eth-purchase
v-if="isEnabledEthPurchase"
v-model="ethToReceive"
:default-eth-to-receive="defaultEthToReceive"
@isValidEthToReceive="ethToReceiveErrorHandler"
/>
<WithdrawTotal
:currency="currency"
withdraw-type="relayer"
:eth-to-receive="ethToReceiveToWei"
:service-fee="relayer.tornadoServiceFee"
/>
<div class="buttons buttons__halfwidth mt-5">
<b-button type="is-primary" outlined @mousedown.prevent @click="onReset">
{{ $t('reset') }}
</b-button>
<b-button
type="is-primary"
:disabled="isDisabledSave"
:loading="checkingRelayer || isLoadingRelayers"
@click="onSave"
>
{{ $t('save') }}
</b-button>
</div>
</b-tab-item>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
import EthPurchase from '@/components/settings/EthPurchase'
import WithdrawTotal from '@/components/withdraw/WithdrawTotal'
import { debounce } from '@/utils'
const { fromWei, toWei } = require('web3-utils')
export default {
components: {
EthPurchase,
WithdrawTotal
},
inject: ['currency', 'save'],
data() {
return {
selectedRelayer: 'custom',
checkingRelayer: false,
customRelayerUrl: '',
hasErrorRelayer: { type: '', msg: '' },
isValidEthToReceive: true,
isValidRelayer: true,
ethToReceive: 0.02,
relayer: {
name: 'custom',
url: '',
address: '',
tornadoServiceFee: 0.0,
miningServiceFee: 0.0,
ethPrices: {
torn: '1'
}
}
}
},
computed: {
...mapGetters('token', ['toDecimals']),
...mapGetters('metamask', ['networkConfig', 'nativeCurrency']),
...mapGetters('metamask', {
networkCurrency: 'currency'
}),
...mapState('relayer', ['isLoadingRelayers']),
...mapState('relayer', {
defaultRelayer: (state) => state.selectedRelayer,
relayers: (state) => state.validRelayers
}),
...mapState('application', {
ethToReceiveFromStore: (state) => Number(fromWei(state.ethToReceive)),
defaultEthToReceive: (state) => Number(fromWei(state.defaultEthToReceive))
}),
isEnabledEthPurchase() {
return this.currency.toLowerCase() !== this.nativeCurrency
},
ethToReceiveToWei() {
return toWei(this.ethToReceive.toString())
},
isCustomRelayer() {
return this.selectedRelayer === 'custom'
},
dropdownValue() {
if (this.isLoadingRelayers) {
return this.$t('loading')
}
if (this.isCustomRelayer) {
return this.$t('custom')
}
return this.selectedRelayer
},
isDisabledSave() {
return !this.isValidRelayer || this.hasErrorRelayer.type === 'is-warning' || !this.isValidEthToReceive
}
},
watch: {
defaultRelayer: {
handler(relayer) {
this.setRelayer(relayer)
},
immediate: true
}
},
created() {
this.setEthToReceive(this.ethToReceiveFromStore)
},
mounted() {
this.$root.$on('resetSettings', this.onReset)
},
beforeDestroy() {
this.$root.$off('resetSettings', this.onReset)
},
methods: {
...mapMutations('relayer', ['SET_SELECTED_RELAYER']),
...mapMutations('application', ['SAVE_ETH_TO_RECEIVE']),
getRelayerName({ name, tornadoServiceFee }) {
return `${name} - ${tornadoServiceFee}%`
},
onInputCustomRelayer(url) {
const trimmedUrl = url.toLowerCase().trim()
if (!trimmedUrl) {
this.hasErrorRelayer = { type: '', msg: '' }
this.isValidRelayer = false
return
}
if (
window.location.protocol !== 'http:' &&
trimmedUrl.startsWith('http:') &&
!trimmedUrl.includes('.onion')
) {
this.hasErrorRelayer.type = 'is-warning'
this.hasErrorRelayer.msg = this.$t('relayerShouldSupportSSL')
this.isValidRelayer = false
return
}
debounce(this.checkRelayer, { url, name: 'custom' })
},
onChangeRelayer(keyName) {
if (keyName === 'custom') {
this.hasErrorRelayer = { type: '', msg: '' }
this.isValidRelayer = false
this.customRelayerUrl = ''
return
}
const { realUrl: url, name } = this.relayers.find((el) => el.name === keyName)
debounce(this.checkRelayer, { url, name })
},
async checkRelayer({ url, name }) {
this.hasErrorRelayer = { type: '', msg: '' }
this.isValidRelayer = false
this.checkingRelayer = true
const { isValid, error, ...relayer } = await this.$store.dispatch('relayer/setupRelayer', {
name,
url
})
if (isValid) {
this.hasErrorRelayer.type = 'is-primary'
this.hasErrorRelayer.msg = this.$t('relayerStatusOk')
this.relayer = relayer
} else {
this.hasErrorRelayer.type = 'is-warning'
this.hasErrorRelayer.msg = error
}
this.checkingRelayer = false
this.isValidRelayer = isValid
},
ethToReceiveErrorHandler(value) {
this.isValidEthToReceive = value
},
setEthToReceive(ethToReceive) {
if (this.isEnabledEthPurchase) {
this.ethToReceive = ethToReceive
}
},
setRelayer(relayer) {
this.relayer = relayer
this.selectedRelayer = relayer.name
if (this.selectedRelayer === 'custom') {
this.customRelayerUrl = relayer.url
}
},
onReset() {
this.hasErrorRelayer = { type: '', msg: '' }
this.checkingRelayer = false
this.isValidRelayer = true
this.setRelayer(this.defaultRelayer)
this.setEthToReceive(this.defaultEthToReceive)
},
onSave() {
this.SET_SELECTED_RELAYER(this.relayer)
if (this.isEnabledEthPurchase) {
this.SAVE_ETH_TO_RECEIVE({
ethToReceive: this.ethToReceiveToWei
})
}
this.save()
}
}
}
</script>

View file

@ -0,0 +1,52 @@
<template>
<b-tab-item :label="$t('wallet')" value="wallet">
<fieldset :disabled="isNotLoggedIn">
<div class="notice is-warning">
<div class="notice__p">{{ $t('withdrawWalletWarning', { currency: networkCurrency }) }}</div>
<div v-if="isNotLoggedIn" class="tooltip" :data-label="$t('connectYourWalletFirst')"></div>
</div>
<WithdrawTotal :currency="currency" withdraw-type="wallet" />
</fieldset>
<div class="buttons buttons__halfwidth mt-5">
<b-button type="is-primary" outlined @mousedown.prevent @click="onReset">
{{ $t('reset') }}
</b-button>
<connect-button v-if="isNotLoggedIn" />
<b-button v-else type="is-primary" @click="onSave">
{{ $t('save') }}
</b-button>
</div>
</b-tab-item>
</template>
<script>
import { mapGetters } from 'vuex'
import { ConnectButton } from '@/components/web3Connect'
import WithdrawTotal from '@/components/withdraw/WithdrawTotal'
export default {
components: {
ConnectButton,
WithdrawTotal
},
inject: ['save', 'reset', 'currency'],
computed: {
...mapGetters('metamask', ['isLoggedIn']),
...mapGetters('metamask', {
networkCurrency: 'currency'
}),
isNotLoggedIn() {
return !this.isLoggedIn
}
},
methods: {
onReset() {
this.reset()
},
onSave() {
this.save()
}
}
}
</script>

View file

@ -0,0 +1,4 @@
import WalletTab from './WalletTab'
import RelayerTab from './RelayerTab'
export { WalletTab, RelayerTab }