relayers-network-ui/store/wallet.ts

343 lines
9.7 KiB
TypeScript

import { ActionTree, GetterTree, MutationTree } from 'vuex'
import { ApproveParams, ChainId, RootState } from '@/types'
import { WalletMutation, WalletState } from '@/types/store/wallet'
import { getRelayerRegistry, getTorn } from '@/contracts'
import { getProvider, getWalletProvider, onPermit } from '@/services'
import { CHAINS, errors, METAMASK_LIST, numbers, APPROVE_AMOUNT } from '@/constants'
import { fromWei, hexToNumber, numberToHex, toChecksumAddress, toDecimalsPlaces } from '@/utilities'
export const actions: ActionTree<WalletState, RootState> = {
setProvider({ commit, dispatch }, params) {
try {
if (ChainId.MAINNET !== Number(params.network)) {
commit(WalletMutation.MISMATCH_NETWORK, true)
throw new Error('errors.wallet.mismatchNetwork')
}
commit(WalletMutation.MISMATCH_NETWORK, false)
commit(WalletMutation.SET_PROVIDER, params)
} catch (err) {
throw new Error(err.message)
}
},
async checkNetwork({ commit, getters }) {
const provider = getWalletProvider(getters.nameProvider)
const network = await provider.checkNetworkVersion()
commit(WalletMutation.MISMATCH_NETWORK, network !== getters.chainId)
},
async getBalances({ dispatch }) {
try {
await dispatch('getTokenBalance')
await dispatch('getWalletBalance')
} finally {
setTimeout(() => {
dispatch('getBalances')
}, numbers.FETCH_BALANCES_INTERVAL)
}
},
async getTokenBalance({ getters, commit }) {
try {
const { chainId, walletAddress } = getters
if (!walletAddress) {
return
}
const tornContract = getTorn(chainId)
const balance = await tornContract.callStatic.balanceOf(walletAddress)
commit(WalletMutation.SET_TOKEN_BALANCE, balance.toString())
} catch (err) {
console.log('getWalletBalance has error:', err.message)
}
},
async getWalletBalance({ commit, getters }) {
try {
const { chainId, walletAddress } = getters
if (!walletAddress) {
return
}
const { provider } = getProvider(chainId)
const balance = await provider.getBalance(walletAddress)
commit(WalletMutation.SET_WALLET_BALANCE, balance.toString())
} catch (err) {
console.log('getWalletBalance has error:', err.message)
}
},
setWalletParams({ commit, dispatch }, address) {
try {
commit(WalletMutation.SET_WALLET_ADDRESS, address)
dispatch('getBalances')
} catch (err) {
throw new Error(err.message)
}
},
async createWalletTransaction({ getters, dispatch }, { to, data, gas, txType, value = '0x0' }) {
try {
const { txGasParams } = getters.dependencies
const { nameProvider, walletAddress, chainId } = getters
const provider = getWalletProvider(nameProvider)
const txHash = await provider.sendRequest<string>({
method: 'eth_sendTransaction',
params: [{ to, gas, data, value, from: walletAddress, ...txGasParams }],
})
await dispatch('transaction/transactionWatcher', { txHash, chainId, txType }, { root: true })
return txHash
} catch (err) {
throw new Error(err.message)
}
},
async changeChain({ getters, dispatch, commit }, chainId) {
const provider = getWalletProvider(getters.nameProvider)
try {
commit(WalletMutation.SET_LOADER_CHAIN_CHANGING, true)
await provider.sendRequest({
method: 'wallet_switchEthereumChain',
params: [{ chainId: CHAINS[chainId].hexChainId }],
})
await dispatch('checkNetwork')
} catch (error) {
const parsedError = await dispatch(
'application/errorHandler',
{ errorMessage: error.message, title: 'notifyTitles.wallet' },
{ root: true },
)
if (parsedError === errors.wallet.TRY_ADDING_THE_CHAIN) {
await provider.sendRequest({
method: 'wallet_addEthereumChain',
params: [METAMASK_LIST[chainId]],
})
}
} finally {
commit(WalletMutation.SET_LOADER_CHAIN_CHANGING, false)
}
},
async getStakePermit({ getters }, tornStake) {
try {
const { walletAddress, nameProvider, chainId } = getters
const tornContract = getTorn(chainId)
const registryContract = getRelayerRegistry(chainId)
const nonce = await tornContract.callStatic.nonces(walletAddress)
const deadline = Math.trunc(new Date().getTime() / numbers.SECOND) + numbers.ONE_DAY
const signature = await onPermit({
deadline,
nameProvider,
value: tornStake,
owner: walletAddress,
token: tornContract.address,
nonce: hexToNumber(nonce._hex),
spender: registryContract.address,
})
return { signature, deadline }
} catch (err) {
throw new Error(`Create permit sign error: ${err.message}`)
}
},
// ToDo save tx hash, show loader for user, keep out user without inned allowance
async checkAllowance({ getters, commit }, txHash) {
try {
const { walletAddress, chainId, nameProvider } = getters
if (!walletAddress) {
return
}
const provider = getWalletProvider(nameProvider)
if (txHash) {
await provider.waitForTxReceipt({ txHash })
}
const tornContract = getTorn(chainId)
const registryContract = getRelayerRegistry(chainId)
const approveAmount = await tornContract.callStatic.allowance(walletAddress, registryContract.address)
return approveAmount
} catch (err) {
throw new Error(err.message)
}
},
async setApprove({ getters, dispatch }) {
try {
const { walletAddress, chainId } = getters
const tornContract = getTorn(chainId)
const registryContract = getRelayerRegistry(chainId)
const unlimitedApprove = numberToHex(APPROVE_AMOUNT)
const params: ApproveParams = [registryContract.address, unlimitedApprove]
const approveCalldata = tornContract.interface.encodeFunctionData('approve', params)
const gas = await tornContract.estimateGas.approve(...params, { from: walletAddress })
const txHash = await dispatch(
'wallet/createWalletTransaction',
{
gas: gas._hex,
txType: 'approve',
data: approveCalldata,
to: tornContract.address,
},
{ root: true },
)
return txHash
} catch (err) {
throw new Error(`Set approve error: ${err.message}`)
}
},
}
export const getters: GetterTree<WalletState, RootState> = {
mismatchNetwork: (state: WalletState) => {
return state.provider.mismatchNetwork
},
nameProvider: (state: WalletState) => {
return state.provider.name
},
isConnected: (state: WalletState) => {
return state.provider.isConnected
},
chainId: (state: WalletState) => {
return Number(state.provider.network)
},
chainConfig: (state: WalletState) => {
return CHAINS[state.provider.network]
},
walletAddress: (state: WalletState) => {
return toChecksumAddress(state.account.address)
},
formattedWalletBalance: (state: WalletState) => {
if (!state.account.balance) {
return '0'
}
return toDecimalsPlaces(fromWei(state.account.balance), numbers.PRECISION)
},
walletBalance: (state: WalletState) => {
return state.account.balance
},
tokenBalance: (state: WalletState) => {
if (!state.account.tokenBalance) {
return '0'
}
return fromWei(state.account.tokenBalance)
},
formattedTokenBalance: (state: WalletState, getters) => {
return toDecimalsPlaces(getters.tokenBalance)
},
isConnecting(state: WalletState) {
return state.loaders.isConnecting
},
isChainChanging(state: WalletState) {
return state.loaders.isChainChanging
},
// another module dependencies
dependencies: (state, getters, rootState, rootGetters) => {
return {
// gasPrice
txGasParams: rootGetters['gasPrice/txGasParams'],
}
},
}
export const mutations: MutationTree<WalletState> = {
[WalletMutation.SET_WALLET_ADDRESS](state, payload) {
state.account.address = toChecksumAddress(payload)
},
[WalletMutation.SET_TOKEN_BALANCE](state, payload) {
state.account.tokenBalance = payload
},
[WalletMutation.SET_WALLET_BALANCE](state, payload) {
state.account.balance = payload
},
[WalletMutation.MISMATCH_NETWORK](state, payload) {
state.provider.mismatchNetwork = payload
},
[WalletMutation.SET_PROVIDER](state, { network, name }) {
state.provider = {
...state.provider,
name,
network,
mismatchNetwork: false,
}
},
[WalletMutation.SET_PROVIDER_CONNECTION](state, payload) {
// @ts-expect-error
this._vm.$set(state.provider, 'isConnected', payload)
},
[WalletMutation.SET_LOADER_CONNECTION](state, payload) {
// @ts-expect-error
this._vm.$set(state.loaders, 'isConnecting', payload)
},
[WalletMutation.SET_LOADER_CHAIN_CHANGING](state, payload) {
// @ts-expect-error
this._vm.$set(state.loaders, 'isChainChanging', payload)
},
[WalletMutation.CLEAR_LOADERS](state) {
state.loaders = {
isConnecting: false,
isChainChanging: false,
}
},
[WalletMutation.CLEAR_PROVIDER](state) {
state.provider = {
name: 'METAMASK',
isConnected: false,
mismatchNetwork: false,
network: ChainId.MAINNET,
}
state.account = {
address: '',
balance: '0',
tokenBalance: '0',
}
state.loaders = {
isConnecting: false,
isChainChanging: false,
}
},
}
export const state = () => {
return {
provider: {
name: 'METAMASK',
isConnected: false,
mismatchNetwork: false,
network: ChainId.MAINNET,
},
account: {
address: '',
balance: '0',
tokenBalance: '0',
},
loaders: {
isConnecting: false,
isChainChanging: false,
},
}
}