977 lines
35 KiB
TypeScript
977 lines
35 KiB
TypeScript
|
import { ActionTree, GetterTree, MutationTree } from 'vuex'
|
||
|
import { BigNumber } from 'ethers'
|
||
|
|
||
|
import {
|
||
|
RootState,
|
||
|
ConfirmationStep,
|
||
|
ApplicationState,
|
||
|
ContractConstants,
|
||
|
AmountToViewPayload,
|
||
|
ApplicationMutation,
|
||
|
} from '@/types'
|
||
|
|
||
|
import { BackupModal, ConfirmationModal, ContinueModal, MergeInputsModal } from '@/modals'
|
||
|
import { eventService, getProvider, getWalletProvider, relayerService, workerProvider, ens } from '@/services'
|
||
|
import { getMulticall, getTornadoPool, getBridgeFeeManager, getForeignOmnibridge, getOmnibridge } from '@/contracts'
|
||
|
|
||
|
import {
|
||
|
sleep,
|
||
|
toWei,
|
||
|
fromWei,
|
||
|
checkEns,
|
||
|
isAmount,
|
||
|
isAddress,
|
||
|
reduceText,
|
||
|
errorParser,
|
||
|
createModalArgs,
|
||
|
toDecimalsPlaces,
|
||
|
onStaticMulticall,
|
||
|
getMessageIdFromTransaction,
|
||
|
getOperationChecker,
|
||
|
} from '@/utilities'
|
||
|
|
||
|
import {
|
||
|
BRIBE,
|
||
|
errors,
|
||
|
numbers,
|
||
|
BG_ZERO,
|
||
|
L1_CHAIN_ID,
|
||
|
WRAPPED_TOKEN,
|
||
|
MIN_GAS_PRICE,
|
||
|
BRIDGE_HELPER,
|
||
|
confirmationStep,
|
||
|
transactionTitles,
|
||
|
confirmationStatus,
|
||
|
operationGasLimits,
|
||
|
transactionMethods,
|
||
|
L1_WITHDRAW_GAS_LIMIT,
|
||
|
} from '@/constants'
|
||
|
|
||
|
export const actions: ActionTree<ApplicationState, RootState> = {
|
||
|
async getBackUpShieldedKey(_, { privateKey }) {
|
||
|
return await new Promise((resolve, reject) => {
|
||
|
const onReject = (err: string) => {
|
||
|
return reject(new Error(err))
|
||
|
}
|
||
|
|
||
|
const onResolve = () => {
|
||
|
resolve(true)
|
||
|
}
|
||
|
|
||
|
// @ts-expect-error
|
||
|
this._vm.$modal.show(
|
||
|
...createModalArgs(
|
||
|
BackupModal,
|
||
|
{
|
||
|
privateKey,
|
||
|
callback: onResolve,
|
||
|
rejectCallback: onReject,
|
||
|
},
|
||
|
{ clickToClose: false },
|
||
|
),
|
||
|
)
|
||
|
})
|
||
|
},
|
||
|
|
||
|
async getContinueConfirmation({ getters }, { method }) {
|
||
|
return await new Promise((resolve, reject) => {
|
||
|
if (!getters.dependencies.shouldShowConfirmModal) {
|
||
|
resolve(true)
|
||
|
return
|
||
|
}
|
||
|
const onReject = (err: string) => {
|
||
|
return reject(new Error(err))
|
||
|
}
|
||
|
|
||
|
const onResolve = () => {
|
||
|
resolve(true)
|
||
|
}
|
||
|
|
||
|
// @ts-expect-error
|
||
|
this._vm.$modal.show(
|
||
|
...createModalArgs(ContinueModal, { method, rejectCallback: onReject, callback: onResolve }, { clickToClose: false }),
|
||
|
)
|
||
|
})
|
||
|
},
|
||
|
async checkRecipientAddress({ dispatch }, recipientAddress) {
|
||
|
try {
|
||
|
const { isInvalidAddress, isRegistered, isENS, ensAddress } = await dispatch('validateAddress', recipientAddress)
|
||
|
|
||
|
return { isRegistered, isInvalidAddress, isENS, ensAddress }
|
||
|
} catch (err) {
|
||
|
if (err.message.includes('bad address checksum')) {
|
||
|
return { isRegistered: false, isInvalidAddress: false }
|
||
|
} else {
|
||
|
this.$notification({
|
||
|
type: 'error',
|
||
|
title: 'Check address error',
|
||
|
text: err.message,
|
||
|
})
|
||
|
return undefined
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
async createPullOperation({ commit, getters, dispatch }, { address, amount, title, type }) {
|
||
|
try {
|
||
|
const l1Fee = getters.l1Fee
|
||
|
const chainId = getters.dependencies.l2ChainId
|
||
|
commit(ApplicationMutation.SET_PROCESSING_MODAL, { type, title, chainId })
|
||
|
|
||
|
// @ts-expect-error
|
||
|
this._vm.$modal.show(...createModalArgs(ConfirmationModal, {}, { clickToClose: false }))
|
||
|
const action = type === transactionMethods.WITHDRAW ? 'createWithdrawalWithStatus' : 'createTransferWithStatus'
|
||
|
await dispatch(action, { address, amount, l1Fee })
|
||
|
} catch (err) {
|
||
|
if (err.message.includes(errors.validation.INSUFFICIENT_INPUTS)) {
|
||
|
const [, subString] = err.message.split('Insufficient inputs')
|
||
|
const [currentAmount, availableAmount] = subString.split(':')
|
||
|
|
||
|
// @ts-expect-error
|
||
|
this._vm.$modal.show(...createModalArgs(MergeInputsModal, { currentAmount, availableAmount }))
|
||
|
}
|
||
|
throw new Error(err.message)
|
||
|
}
|
||
|
},
|
||
|
async getContractConstants({ commit, getters }) {
|
||
|
try {
|
||
|
const poolContract = getTornadoPool(getters.dependencies.l2ChainId)
|
||
|
const multicallContract = getMulticall(getters.dependencies.l2ChainId)
|
||
|
|
||
|
const params = [
|
||
|
{
|
||
|
gasLimit: '0x6f6d',
|
||
|
target: poolContract.address,
|
||
|
callData: poolContract.interface.encodeFunctionData('maximumDepositAmount'),
|
||
|
},
|
||
|
// {
|
||
|
// gasLimit: '0x6f9a',
|
||
|
// target: poolContract.address,
|
||
|
// callData: poolContract.interface.encodeFunctionData('minimalWithdrawalAmount'),
|
||
|
// },
|
||
|
]
|
||
|
const { returnData } = await multicallContract.callStatic.multicall(params)
|
||
|
|
||
|
const [maximumDepositAmount] = returnData
|
||
|
|
||
|
commit(ApplicationMutation.SET_CONTRACT_CONSTANTS, {
|
||
|
maximumDepositAmount: maximumDepositAmount.returnData,
|
||
|
minimalWithdrawalAmount: BG_ZERO,
|
||
|
})
|
||
|
} catch (err) {
|
||
|
console.log('getContractConstants has error:', err.message)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async checkWithdrawalComplete({ dispatch, getters, commit }, { txHash }) {
|
||
|
try {
|
||
|
const { provider } = getProvider(getters.dependencies.l2ChainId)
|
||
|
|
||
|
const withdrawalTx = getters.dependencies.currentTransaction(txHash)
|
||
|
|
||
|
const receipt = await provider.getTransactionReceipt(txHash)
|
||
|
|
||
|
const messageId = getMessageIdFromTransaction('withdrawal', receipt)
|
||
|
|
||
|
const [request] = await eventService.getUserRequestForSignature({ messageId, blockFrom: withdrawalTx.blockNumber })
|
||
|
|
||
|
const [event] = await eventService.getRelayedMessage({
|
||
|
messageId: request.topics[numbers.ONE],
|
||
|
})
|
||
|
|
||
|
if (request.topics[numbers.ONE] === event.topics[numbers.THREE]) {
|
||
|
dispatch('wallet/getWalletBalance', {}, { root: true })
|
||
|
|
||
|
const tx = getters.dependencies.currentTransaction(request.transactionHash)
|
||
|
|
||
|
dispatch(
|
||
|
'transaction/transactionWatcher',
|
||
|
{
|
||
|
txHash: event.transactionHash,
|
||
|
chainId: getters.dependencies.l1ChainId,
|
||
|
transactionInfo: {
|
||
|
amount: tx.amount,
|
||
|
type: transactionTitles.BRIDGE,
|
||
|
method: transactionMethods.BRIDGE,
|
||
|
account: getters.dependencies.accountAddress,
|
||
|
},
|
||
|
},
|
||
|
{ root: true },
|
||
|
)
|
||
|
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: confirmationStep.BRIDGE, status: confirmationStatus.SUCCESS })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, {
|
||
|
step: confirmationStep.COMPLETE,
|
||
|
status: confirmationStatus.SUCCESS,
|
||
|
})
|
||
|
}
|
||
|
} catch (err) {
|
||
|
console.log('checkWithdrawalComplete error:', err.message)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async checkDepositComplete({ dispatch, commit, getters }, { txHash }) {
|
||
|
try {
|
||
|
const { provider } = getProvider(getters.dependencies.l1ChainId)
|
||
|
|
||
|
const tx = getters.dependencies.currentTransaction(txHash)
|
||
|
|
||
|
const receipt = await provider.getTransactionReceipt(txHash)
|
||
|
|
||
|
const messageId = getMessageIdFromTransaction('deposit', receipt)
|
||
|
|
||
|
const [affirmation] = await eventService.getUserRequestForAffirmation({ messageId, blockFrom: tx.blockNumber })
|
||
|
|
||
|
const [event] = await eventService.getAffirmationCompleted({ messageId: affirmation.args[numbers.ZERO] })
|
||
|
|
||
|
if (event.topics[numbers.THREE] === affirmation.args[numbers.ZERO]) {
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: confirmationStep.BRIDGE, status: confirmationStatus.SUCCESS })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: confirmationStep.COMPLETE, status: confirmationStatus.SUCCESS })
|
||
|
}
|
||
|
} catch (err) {
|
||
|
console.log('checkDepositComplete has error:', err.message)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async checkWalletParams({ getters }, payload) {
|
||
|
const { walletAddress, mismatchNetwork, isRelayer } = getters.dependencies
|
||
|
|
||
|
if (!walletAddress) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
const provider = getWalletProvider(getters.nameProvider || 'METAMASK')
|
||
|
const network = await provider.checkNetworkVersion()
|
||
|
const defaultParams = {
|
||
|
isRelayer,
|
||
|
additionalCondition: payload.isRelayerPossible,
|
||
|
}
|
||
|
|
||
|
if (getOperationChecker({ ...defaultParams, checker: mismatchNetwork })) {
|
||
|
throw new Error(errors.validation.MISMATCH_NETWORK)
|
||
|
}
|
||
|
|
||
|
if (getOperationChecker({ ...defaultParams, checker: network !== payload.network })) {
|
||
|
throw new Error('Network changed during operation')
|
||
|
}
|
||
|
|
||
|
if (getOperationChecker({ ...defaultParams, additionalCondition: false, checker: walletAddress !== payload.walletAddress })) {
|
||
|
throw new Error('Wallet changed during operation')
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async createDepositWithStatus({ getters, commit, dispatch }, { amount, address, withRegister = false }) {
|
||
|
try {
|
||
|
const account = getters.dependencies.accountAddress
|
||
|
const { network, walletAddress, l1ChainId: chainId } = getters.dependencies
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.GENERATE)) {
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, { type: 'deposit', account })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: confirmationStep.GENERATE, status: confirmationStatus.LOADING })
|
||
|
|
||
|
await dispatch('checkWalletParams', { isRelayerPossible: false, walletAddress, network })
|
||
|
|
||
|
const calldata = withRegister
|
||
|
? await dispatch('account/prepareDepositWithRegister', { amount }, { root: true })
|
||
|
: await dispatch('account/prepareDeposit', { amount, address }, { root: true })
|
||
|
|
||
|
dispatch('processingOnNextStep', { currentStep: confirmationStep.GENERATE, nextStep: confirmationStep.TRANSACT })
|
||
|
|
||
|
if (withRegister) {
|
||
|
const keypair = await dispatch('account/getKeypairFromStorage', null, { root: true })
|
||
|
await dispatch('getBackUpShieldedKey', { privateKey: keypair.privkey })
|
||
|
}
|
||
|
|
||
|
await dispatch('checkWalletParams', { isRelayerPossible: false, walletAddress, network })
|
||
|
|
||
|
const params = {
|
||
|
calldata,
|
||
|
amount: toWei(amount)._hex,
|
||
|
to: BRIDGE_HELPER[chainId],
|
||
|
gas: operationGasLimits.FUND,
|
||
|
recipient: withRegister ? getters.dependencies.walletAddress : address,
|
||
|
transactionInfo: {
|
||
|
amount,
|
||
|
account,
|
||
|
recipient: address,
|
||
|
method: transactionMethods.FUND,
|
||
|
type: withRegister ? transactionTitles.SETUP : transactionTitles.FUND,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
await dispatch('checkWalletParams', { isRelayerPossible: false, walletAddress, network })
|
||
|
|
||
|
const txHash = await dispatch('wallet/createWalletTransaction', params, { root: true })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, { txHash })
|
||
|
|
||
|
await dispatch('processingOnNextStep', { currentStep: confirmationStep.TRANSACT, nextStep: confirmationStep.WAIT })
|
||
|
}
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.WAIT) && getters.processingTxHash) {
|
||
|
await dispatch(
|
||
|
'transaction/waitConfirmation',
|
||
|
{ txHash: getters.processingTxHash, account: getters.processingAccount, chainId },
|
||
|
{ root: true },
|
||
|
)
|
||
|
await dispatch('processingOnNextStep', { currentStep: confirmationStep.WAIT, nextStep: confirmationStep.BRIDGE })
|
||
|
}
|
||
|
|
||
|
if (withRegister) {
|
||
|
dispatch('account/checkRegisterInPool', address, { root: true })
|
||
|
}
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.BRIDGE) && getters.processingTxHash) {
|
||
|
await dispatch('checkDepositComplete', { txHash: getters.processingTxHash })
|
||
|
}
|
||
|
|
||
|
dispatch('account/getAccountBalance', {}, { root: true })
|
||
|
|
||
|
await dispatch('markProcessingAsComplete')
|
||
|
} catch (err) {
|
||
|
await dispatch('markPendingStepAsFail', { errorMessage: err.message, title: 'Fund error' })
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async createTransferWithStatus({ getters, commit, dispatch }, { amount, address }) {
|
||
|
try {
|
||
|
const network = getters.dependencies.network
|
||
|
const chainId = getters.dependencies.l2ChainId
|
||
|
const walletAddress = getters.dependencies.walletAddress
|
||
|
const account = getters.dependencies.accountAddress
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.GENERATE)) {
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, { type: transactionTitles.TRANSFER, account })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: confirmationStep.GENERATE, status: confirmationStatus.LOADING })
|
||
|
|
||
|
const { args, extData } = getters.dependencies.isRelayer
|
||
|
? await dispatch('relayer/prepareTransfer', { amount, address }, { root: true })
|
||
|
: await dispatch('account/prepareTransfer', { amount, address }, { root: true })
|
||
|
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, {
|
||
|
params: {
|
||
|
args,
|
||
|
extData,
|
||
|
transactionInfo: {
|
||
|
amount,
|
||
|
recipient: address,
|
||
|
type: transactionTitles.TRANSFER,
|
||
|
account: getters.processingAccount,
|
||
|
method: transactionMethods.TRANSFER,
|
||
|
},
|
||
|
},
|
||
|
})
|
||
|
|
||
|
dispatch('processingOnNextStep', { currentStep: confirmationStep.GENERATE, nextStep: confirmationStep.TRANSACT })
|
||
|
await dispatch('checkWalletParams', { isRelayerPossible: true, walletAddress, network })
|
||
|
await dispatch('getContinueConfirmation', { method: 'Transfer' })
|
||
|
}
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.TRANSACT) && !getters.dependencies.activeJob) {
|
||
|
await dispatch('checkWalletParams', { isRelayerPossible: true, walletAddress, network })
|
||
|
|
||
|
const txHash = getters.dependencies.isRelayer
|
||
|
? await dispatch('relayer/createRelayerTransaction', getters.processingParams, { root: true })
|
||
|
: await dispatch('account/submitAction', getters.processingParams, { root: true })
|
||
|
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, { txHash })
|
||
|
|
||
|
await dispatch('processingOnNextStep', { currentStep: confirmationStep.TRANSACT, nextStep: confirmationStep.WAIT })
|
||
|
}
|
||
|
|
||
|
if (getters.processingStatuses.generate === confirmationStatus.SUCCESS && getters.dependencies.activeJob) {
|
||
|
const txHash = await dispatch('relayer/checkActiveJob', getters.dependencies.activeJob, { root: true })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, { txHash })
|
||
|
|
||
|
await dispatch('processingOnNextStep', { currentStep: confirmationStep.TRANSACT, nextStep: confirmationStep.WAIT })
|
||
|
}
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.WAIT) && getters.processingTxHash) {
|
||
|
await dispatch(
|
||
|
'transaction/waitConfirmation',
|
||
|
{
|
||
|
txHash: getters.processingTxHash,
|
||
|
account: getters.processingAccount,
|
||
|
chainId,
|
||
|
minConfirmation: numbers.MIN_TRANSFER_CONFIRMATION,
|
||
|
},
|
||
|
{ root: true },
|
||
|
)
|
||
|
await dispatch('processingOnNextStep', { currentStep: confirmationStep.WAIT, nextStep: confirmationStep.BRIDGE })
|
||
|
}
|
||
|
|
||
|
dispatch('account/getAccountBalance', {}, { root: true })
|
||
|
|
||
|
await dispatch('markProcessingAsComplete')
|
||
|
} catch (err) {
|
||
|
await dispatch('markPendingStepAsFail', { errorMessage: err.message, title: 'Transfer error' })
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async createWithdrawalWithStatus({ getters, commit, dispatch }, { amount, address, l1Fee }) {
|
||
|
try {
|
||
|
const network = getters.dependencies.network
|
||
|
const chainId = getters.dependencies.l2ChainId
|
||
|
const account = getters.dependencies.accountAddress
|
||
|
const walletAddress = getters.dependencies.walletAddress
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.GENERATE)) {
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, { type: 'withdrawal', account })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: confirmationStep.GENERATE, status: confirmationStatus.LOADING })
|
||
|
|
||
|
const { args, extData } = getters.dependencies.isRelayer
|
||
|
? await dispatch('relayer/prepareWithdrawal', { amount, address, l1Fee }, { root: true })
|
||
|
: await dispatch('account/prepareWithdrawal', { amount, address, l1Fee }, { root: true })
|
||
|
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, {
|
||
|
params: {
|
||
|
args,
|
||
|
extData,
|
||
|
transactionInfo: {
|
||
|
amount,
|
||
|
recipient: address,
|
||
|
type: transactionTitles.WITHDRAW,
|
||
|
account: getters.processingAccount,
|
||
|
method: transactionMethods.WITHDRAW,
|
||
|
},
|
||
|
},
|
||
|
})
|
||
|
|
||
|
dispatch('processingOnNextStep', { currentStep: confirmationStep.GENERATE, nextStep: confirmationStep.TRANSACT })
|
||
|
await dispatch('checkWalletParams', { isRelayerPossible: true, walletAddress, network })
|
||
|
await dispatch('getContinueConfirmation', { method: 'Withdrawal' })
|
||
|
}
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.TRANSACT) && !getters.dependencies.activeJob) {
|
||
|
await dispatch('checkWalletParams', { isRelayerPossible: true, walletAddress, network })
|
||
|
await dispatch('getDayLimit')
|
||
|
|
||
|
const isGreaterThanMax = Number(amount) > Number(getters.maximumWithdrawal)
|
||
|
if (isGreaterThanMax) {
|
||
|
throw new Error(errors.errorsGetter([getters.maximumWithdrawal]).MAX_WITHDRAW_AMOUNT)
|
||
|
}
|
||
|
const txHash = getters.dependencies.isRelayer
|
||
|
? await dispatch('relayer/createRelayerTransaction', getters.processingParams, { root: true })
|
||
|
: await dispatch('account/submitAction', getters.processingParams, { root: true })
|
||
|
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, { txHash })
|
||
|
|
||
|
await dispatch('processingOnNextStep', { currentStep: confirmationStep.TRANSACT, nextStep: confirmationStep.WAIT })
|
||
|
}
|
||
|
|
||
|
if (getters.processingStatuses.generate === confirmationStatus.SUCCESS && getters.dependencies.activeJob) {
|
||
|
const txHash = await dispatch('relayer/checkActiveJob', getters.dependencies.activeJob, { root: true })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_INFO, { txHash })
|
||
|
|
||
|
await dispatch('processingOnNextStep', { currentStep: confirmationStep.TRANSACT, nextStep: confirmationStep.WAIT })
|
||
|
}
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.WAIT) && getters.processingTxHash) {
|
||
|
await dispatch(
|
||
|
'transaction/waitConfirmation',
|
||
|
{ txHash: getters.processingTxHash, chainId, account: getters.processingAccount },
|
||
|
{ root: true },
|
||
|
)
|
||
|
await dispatch('processingOnNextStep', { currentStep: confirmationStep.WAIT, nextStep: confirmationStep.BRIDGE })
|
||
|
}
|
||
|
|
||
|
if (getters.isNotCompleteStep(confirmationStep.BRIDGE) && getters.processingTxHash) {
|
||
|
await dispatch('checkWithdrawalComplete', { txHash: getters.processingTxHash })
|
||
|
}
|
||
|
|
||
|
dispatch('account/getAccountBalance', {}, { root: true })
|
||
|
|
||
|
await dispatch('markProcessingAsComplete')
|
||
|
} catch (err) {
|
||
|
await dispatch('markPendingStepAsFail', { errorMessage: err.message, title: 'Withdrawal error' })
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async getBridgeFeePercent({ getters }) {
|
||
|
try {
|
||
|
const bridgeFeeManager = getBridgeFeeManager(getters.dependencies.l2ChainId)
|
||
|
const HOME_TO_FOREIGN_FEE = '0x741ede137d0537e88e0ea0ff25b1f22d837903dbbee8980b4a06e8523247ee26'
|
||
|
|
||
|
const bridgeFeePercent = await bridgeFeeManager.getFee(HOME_TO_FOREIGN_FEE, WRAPPED_TOKEN[getters.dependencies.l2ChainId])
|
||
|
return bridgeFeePercent
|
||
|
} catch (err) {
|
||
|
throw new Error(`getBridgeFeePercent has error: ${err.message}`)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
checkProcessing({ getters, dispatch }) {
|
||
|
try {
|
||
|
const type = getters.processingType
|
||
|
|
||
|
// TODO enum for type
|
||
|
if (type === 'withdrawal') {
|
||
|
dispatch('createWithdrawalWithStatus', {})
|
||
|
} else if (type === 'transfer') {
|
||
|
dispatch('createTransferWithStatus', {})
|
||
|
} else if (type === 'deposit') {
|
||
|
dispatch('createDepositWithStatus', {})
|
||
|
}
|
||
|
} catch (err) {
|
||
|
console.warn(`checkProcessing has error: ${err.message}`)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async markPendingStepAsFail({ getters, dispatch, commit }, { errorMessage, title }) {
|
||
|
const pendingStep = Object.keys(getters.processingStatuses).find(
|
||
|
(key) => getters.processingStatuses[key] === confirmationStatus.LOADING,
|
||
|
)
|
||
|
if (pendingStep) {
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: pendingStep, status: confirmationStatus.FAIL })
|
||
|
}
|
||
|
|
||
|
const errorText = await dispatch('errorHandler', { errorMessage, title })
|
||
|
|
||
|
throw new Error(errorText)
|
||
|
},
|
||
|
|
||
|
processingOnNextStep({ commit }, { currentStep, nextStep }) {
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: currentStep, status: confirmationStatus.SUCCESS })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: nextStep, status: confirmationStatus.LOADING })
|
||
|
},
|
||
|
|
||
|
async setTransactionComplete({ commit, dispatch }, txInfo) {
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: confirmationStep.TRANSACT, status: confirmationStatus.SUCCESS })
|
||
|
await dispatch('checkConfirmation', txInfo)
|
||
|
},
|
||
|
|
||
|
async markProcessingAsComplete({ commit }) {
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, { step: confirmationStep.BRIDGE, status: confirmationStatus.SUCCESS })
|
||
|
commit(ApplicationMutation.SET_PROCESSING_STATUS, {
|
||
|
step: confirmationStep.COMPLETE,
|
||
|
status: confirmationStatus.SUCCESS,
|
||
|
})
|
||
|
|
||
|
await sleep(numbers.SECOND)
|
||
|
commit(ApplicationMutation.CLEAR_PROCESSING)
|
||
|
},
|
||
|
|
||
|
setupWorker({ getters }) {
|
||
|
workerProvider.workerSetup(getters.dependencies.l2ChainId)
|
||
|
},
|
||
|
|
||
|
async validateAddress({ dispatch, getters }, address) {
|
||
|
try {
|
||
|
if (!address) {
|
||
|
return { isInvalidAddress: false, isRegistered: false, isENS: false, ensAddress: '' }
|
||
|
}
|
||
|
|
||
|
const isENS = checkEns(address)
|
||
|
|
||
|
if (isENS) {
|
||
|
address = await ens.getAddress(address, getters.dependencies.l1ChainId)
|
||
|
}
|
||
|
const isInvalidAddress = !isAddress(address)
|
||
|
|
||
|
if (isInvalidAddress) {
|
||
|
return { isInvalidAddress, isENS, ensAddress: address, isRegistered: false }
|
||
|
}
|
||
|
|
||
|
const isRegistered = await dispatch('account/getIsRegisterInPool', address, { root: true })
|
||
|
|
||
|
return { isInvalidAddress, isENS, isRegistered, ensAddress: address }
|
||
|
} catch (err) {
|
||
|
throw new Error(err.message)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
async getDayLimit({ getters, commit }) {
|
||
|
const { l1ChainId, l2ChainId } = getters.dependencies
|
||
|
|
||
|
const WETHL1 = WRAPPED_TOKEN[l1ChainId]
|
||
|
const WETHL2 = WRAPPED_TOKEN[l2ChainId]
|
||
|
|
||
|
const foreignOmnibridge = getForeignOmnibridge(l1ChainId)
|
||
|
const omnibridge = getOmnibridge(l2ChainId)
|
||
|
|
||
|
const currentDayL1 = await foreignOmnibridge.callStatic.getCurrentDay()
|
||
|
|
||
|
const [multicallL1Res, multicallL2Res] = await Promise.all([
|
||
|
onStaticMulticall(l1ChainId, [
|
||
|
{ contract: foreignOmnibridge, methodName: 'executionDailyLimit', args: [WETHL1] },
|
||
|
{ contract: foreignOmnibridge, methodName: 'totalExecutedPerDay', args: [WETHL1, currentDayL1] },
|
||
|
]),
|
||
|
onStaticMulticall(l2ChainId, [
|
||
|
{ contract: omnibridge, methodName: 'dailyLimit', args: [WETHL2] },
|
||
|
{ contract: omnibridge, methodName: 'totalSpentPerDay', args: [WETHL2, currentDayL1] },
|
||
|
]),
|
||
|
])
|
||
|
|
||
|
const [{ returnData: executionDailyLimit }, { returnData: totalExecutedPerDay }] = multicallL1Res.returnData
|
||
|
const [{ returnData: dailyLimit }, { returnData: totalSpentPerDay }] = multicallL2Res.returnData
|
||
|
|
||
|
const l1Diff = BigNumber.from(executionDailyLimit).sub(totalExecutedPerDay)
|
||
|
const l2Diff = BigNumber.from(dailyLimit).sub(totalSpentPerDay)
|
||
|
|
||
|
commit(ApplicationMutation.SET_CONTRACT_CONSTANTS, {
|
||
|
omnibridgeDailyLimit: dailyLimit,
|
||
|
maximumWithdrawalAmount: l2Diff.gte(l1Diff) ? l1Diff : l2Diff,
|
||
|
})
|
||
|
},
|
||
|
|
||
|
errorHandler(_, { errorMessage, title = '' }) {
|
||
|
const errorText = errorParser(errorMessage)
|
||
|
this.$notification({ type: 'error', title, text: reduceText(errorText), duration: numbers.TOAST_DURATION })
|
||
|
if (errorMessage.includes(errors.validation.INSUFFICIENT_INPUTS)) {
|
||
|
return errorMessage
|
||
|
}
|
||
|
return errorText
|
||
|
},
|
||
|
}
|
||
|
|
||
|
export const getters: GetterTree<ApplicationState, RootState> = {
|
||
|
networkFee: (state: ApplicationState, getters) => (method: string) => {
|
||
|
return BigNumber.from(getters.dependencies.currentGasPriceL2).mul(operationGasLimits[method.toUpperCase()])
|
||
|
},
|
||
|
networkFeeFund: (state: ApplicationState, getters) => {
|
||
|
const { maxFeePerGas, gasPrice } = getters.dependencies.txGasParams(getters.dependencies.l1ChainId, transactionMethods.FUND)
|
||
|
return BigNumber.from(maxFeePerGas || gasPrice).mul(operationGasLimits.FUND)
|
||
|
},
|
||
|
l2EthNetworkFee: (state: ApplicationState, getters) => (method: string) => {
|
||
|
const networkFee = getters.networkFee(method)
|
||
|
|
||
|
return networkFee.mul(getters.dependencies.ethRate).div(toWei('1'))
|
||
|
},
|
||
|
|
||
|
operationFee: (state: ApplicationState, getters) => (amount: BigNumber, method: string, isRelayer: boolean) => {
|
||
|
const isRelayerMethod = isRelayer === undefined ? getters.dependencies.isRelayer : isRelayer
|
||
|
|
||
|
if (isRelayerMethod) {
|
||
|
if (!getters.dependencies.currentRelayer) {
|
||
|
return BG_ZERO
|
||
|
}
|
||
|
|
||
|
const { serviceFee } = getters.dependencies.currentRelayer
|
||
|
|
||
|
return relayerService.getOperationFee({
|
||
|
method,
|
||
|
amount,
|
||
|
serviceFee,
|
||
|
networkFee: getters.l2EthNetworkFee(method),
|
||
|
})
|
||
|
}
|
||
|
return getters.networkFee(method)
|
||
|
},
|
||
|
operationAmounts: (state: ApplicationState, getters) => (amount: string, method: string, isRelayer: boolean) => {
|
||
|
try {
|
||
|
if (!isAmount(amount)) {
|
||
|
return { toSend: '', toReceive: '' }
|
||
|
}
|
||
|
|
||
|
const l1Fee = getters.l1Fee
|
||
|
const amountInWei = toWei(amount)
|
||
|
|
||
|
if (isRelayer) {
|
||
|
const operationFee = getters.operationFee(amountInWei, method, isRelayer)
|
||
|
|
||
|
if (method === 'transfer') {
|
||
|
const toReceive = fromWei(amountInWei.add(operationFee))
|
||
|
return { toSend: toReceive, toReceive: toReceive }
|
||
|
}
|
||
|
|
||
|
const toReceive = amountInWei.sub(operationFee).sub(l1Fee)
|
||
|
return { toSend: amount, toReceive: fromWei(toReceive) }
|
||
|
}
|
||
|
|
||
|
const toReceive = method === 'transfer' ? amount : fromWei(amountInWei.sub(l1Fee))
|
||
|
|
||
|
return { toSend: amount, toReceive }
|
||
|
} catch (err) {
|
||
|
return { toSend: amount, toReceive: amount }
|
||
|
}
|
||
|
},
|
||
|
|
||
|
tokensToSend: (state: ApplicationState, getters) => (amount: string, method: string, isRelayer: boolean) => {
|
||
|
try {
|
||
|
if (!isAmount(amount)) {
|
||
|
return ''
|
||
|
}
|
||
|
|
||
|
const l1Fee = getters.l1Fee
|
||
|
const amountInWei = toWei(amount)
|
||
|
|
||
|
if (isRelayer) {
|
||
|
if (method === 'transfer') {
|
||
|
const operationFee = getters.operationFee(amountInWei, method, isRelayer)
|
||
|
return fromWei(amountInWei.add(operationFee))
|
||
|
}
|
||
|
const operationFee = getters.operationFee(amountInWei.add(l1Fee), method, isRelayer)
|
||
|
|
||
|
const toSend = amountInWei.add(operationFee).add(l1Fee)
|
||
|
return fromWei(toSend)
|
||
|
}
|
||
|
|
||
|
return method === 'transfer' ? amount : fromWei(amountInWei.add(l1Fee))
|
||
|
} catch (err) {
|
||
|
return amount
|
||
|
}
|
||
|
},
|
||
|
|
||
|
amountsToView:
|
||
|
(state: ApplicationState, getters) =>
|
||
|
({ amount, method, isRelayer, withRelayer, isCustom }: AmountToViewPayload) => {
|
||
|
if (method === 'transfer') {
|
||
|
const { toReceive, toSend } = getters.operationAmounts(amount, method, withRelayer)
|
||
|
|
||
|
return {
|
||
|
toReceive: isAmount(toReceive) ? toDecimalsPlaces(toReceive, numbers.FEE_PRECISION) : null,
|
||
|
toSend: isAmount(toSend) ? toDecimalsPlaces(toSend, numbers.FEE_PRECISION) : null,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let toSendValue = null
|
||
|
let toReceiveValue = null
|
||
|
|
||
|
if (isCustom) {
|
||
|
const toSendWithType = getters.tokensToSend(amount, method, withRelayer)
|
||
|
|
||
|
toReceiveValue = amount
|
||
|
toSendValue = toSendWithType
|
||
|
} else {
|
||
|
const toSendWithType = getters.tokensToSend(amount, method, isRelayer)
|
||
|
const { toReceive, toSend } = getters.operationAmounts(toSendWithType, method, withRelayer)
|
||
|
|
||
|
toReceiveValue = toReceive
|
||
|
toSendValue = toSend
|
||
|
}
|
||
|
return {
|
||
|
toSend: isAmount(toSendValue) ? toDecimalsPlaces(toSendValue, numbers.FEE_PRECISION) : null,
|
||
|
toReceive: isAmount(toReceiveValue) ? toDecimalsPlaces(toReceiveValue, numbers.FEE_PRECISION) : null,
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getMaxAmount: (state: ApplicationState, getters) => (method: string) => {
|
||
|
const balance = BigNumber.from(getters.dependencies.accountBalance)
|
||
|
|
||
|
if (getters.dependencies.isRelayer) {
|
||
|
const operationFee = getters.operationFee(balance, method)
|
||
|
|
||
|
const maxAmount = balance.sub(operationFee)
|
||
|
if (maxAmount.isNegative()) {
|
||
|
return BG_ZERO
|
||
|
}
|
||
|
|
||
|
return maxAmount
|
||
|
}
|
||
|
return balance
|
||
|
},
|
||
|
getIsBalanceEnough: (state: ApplicationState, getters) => (amount: string, method: string) => {
|
||
|
if (!isAmount(amount)) {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
if (!isAmount(getters.dependencies.accountBalance)) {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
const balance = BigNumber.from(getters.dependencies.accountBalance)
|
||
|
|
||
|
if (getters.dependencies.isRelayer) {
|
||
|
const { toSend } = getters.operationAmounts(amount, method, getters.dependencies.isRelayer)
|
||
|
|
||
|
return balance.gte(toWei(toSend))
|
||
|
}
|
||
|
|
||
|
return balance.gte(toWei(amount))
|
||
|
},
|
||
|
getIsBalanceEnoughFund: (state: ApplicationState, getters) => (amount: string) => {
|
||
|
const amountInWei = toWei(amount)
|
||
|
|
||
|
const existAmount = BigNumber.from(getters.dependencies.walletBalance).sub(amountInWei).sub(getters.networkFeeFund)
|
||
|
|
||
|
return !existAmount.lt(numbers.ZERO)
|
||
|
},
|
||
|
getMaxAmountFund: (state: ApplicationState, getters) => () => {
|
||
|
const balance = BigNumber.from(getters.dependencies.walletBalance)
|
||
|
const operationFee = getters.networkFeeFund
|
||
|
|
||
|
const maxAmount = balance.sub(operationFee)
|
||
|
|
||
|
if (maxAmount.isNegative()) {
|
||
|
return BG_ZERO
|
||
|
}
|
||
|
|
||
|
return maxAmount
|
||
|
},
|
||
|
|
||
|
actionButtonText: (state: ApplicationState, getters) => (baseText: string, amount: string) => {
|
||
|
const buttonSuffix =
|
||
|
Number(amount) > numbers.ZERO
|
||
|
? `${toDecimalsPlaces(amount, numbers.FEE_PRECISION)} ${getters.dependencies.chainConfig.symbol}`
|
||
|
: ''
|
||
|
return `${baseText} ${buttonSuffix}`
|
||
|
},
|
||
|
|
||
|
maximumDeposit: (state: ApplicationState) => {
|
||
|
return fromWei(state.contract.maximumDepositAmount)
|
||
|
},
|
||
|
minimalWithdrawal: (state: ApplicationState) => {
|
||
|
return fromWei(state.contract.minimalWithdrawalAmount)
|
||
|
},
|
||
|
maximumWithdrawal: (state: ApplicationState) => {
|
||
|
return fromWei(state.contract.maximumWithdrawalAmount)
|
||
|
},
|
||
|
omnibridgeDailyLimit: (state: ApplicationState) => {
|
||
|
return toDecimalsPlaces(fromWei(state.contract.omnibridgeDailyLimit), numbers.ZERO)
|
||
|
},
|
||
|
l1Fee: (state: ApplicationState, getters) => {
|
||
|
const minGasPrice = BigNumber.from(MIN_GAS_PRICE)
|
||
|
const gasLimit = BigNumber.from(L1_WITHDRAW_GAS_LIMIT)
|
||
|
|
||
|
let gasPrice = getters.dependencies.gasPrice
|
||
|
|
||
|
if (gasPrice.lt(minGasPrice)) {
|
||
|
gasPrice = minGasPrice
|
||
|
}
|
||
|
|
||
|
const l1Fee = gasLimit.mul(gasPrice).add(BRIBE)
|
||
|
return l1Fee
|
||
|
},
|
||
|
|
||
|
shouldProcessingWatch: (state: ApplicationState, getters) => {
|
||
|
const isProcessingTx = Boolean(state.processing.info.txHash)
|
||
|
const processingTxHasActiveJob = Boolean(getters.dependencies.activeJob)
|
||
|
|
||
|
return processingTxHasActiveJob || isProcessingTx
|
||
|
},
|
||
|
isNotCompleteStep: (state: ApplicationState) => (step: ConfirmationStep) => {
|
||
|
return state.processing.statuses[step] !== confirmationStatus.SUCCESS
|
||
|
},
|
||
|
processingBlockNumber: (state: ApplicationState) => {
|
||
|
return state.processing.info.blockNumber
|
||
|
},
|
||
|
processingTxHash: (state: ApplicationState) => {
|
||
|
return state.processing.info.txHash
|
||
|
},
|
||
|
processingAccount: (state: ApplicationState) => {
|
||
|
return state.processing.info.account
|
||
|
},
|
||
|
processingType: (state: ApplicationState) => {
|
||
|
return state.processing.info.type
|
||
|
},
|
||
|
processingParams: (state: ApplicationState) => {
|
||
|
return state.processing.info.params
|
||
|
},
|
||
|
processingStatuses: (state: ApplicationState) => {
|
||
|
return state.processing.statuses
|
||
|
},
|
||
|
isProcessingComplete: (state: ApplicationState) => {
|
||
|
return state.processing.statuses.complete === confirmationStatus.SUCCESS
|
||
|
},
|
||
|
isProcessingStarted: (state: ApplicationState) => {
|
||
|
return Boolean(state.processing.statuses.generate)
|
||
|
},
|
||
|
isShowProcessingModal: (state: ApplicationState) => {
|
||
|
return state.processing.modal.isShow
|
||
|
},
|
||
|
isProcessingError: (state: ApplicationState) => {
|
||
|
const statusesValues = Object.values(state.processing.statuses)
|
||
|
return statusesValues.includes(confirmationStatus.FAIL)
|
||
|
},
|
||
|
|
||
|
// another module dependencies
|
||
|
dependencies: (state: ApplicationState, getters, rootState, rootGetters) => {
|
||
|
return {
|
||
|
// transaction
|
||
|
currentTransaction: rootGetters['transaction/currentTransaction'],
|
||
|
// wallet
|
||
|
network: rootGetters['wallet/chainId'],
|
||
|
l1ChainId: rootGetters['wallet/l1ChainId'],
|
||
|
l2ChainId: rootGetters['wallet/l2ChainId'],
|
||
|
chainConfig: rootGetters['wallet/chainConfig'],
|
||
|
walletBalance: rootGetters['wallet/walletBalance'],
|
||
|
walletAddress: rootGetters['wallet/walletAddress'],
|
||
|
mismatchNetwork: rootGetters['wallet/mismatchNetwork'],
|
||
|
// account
|
||
|
isRelayer: rootGetters['account/isRelayer'],
|
||
|
accountBalance: rootGetters['account/accountBalance'],
|
||
|
accountAddress: rootGetters['account/accountAddress'],
|
||
|
shouldShowConfirmModal: rootGetters['account/shouldShowConfirmModal'],
|
||
|
// relayer
|
||
|
ethRate: rootGetters['relayer/ethRate'],
|
||
|
activeJob: rootGetters['relayer/activeJob'],
|
||
|
currentRelayer: rootGetters['relayer/currentRelayer'],
|
||
|
// gasPrice
|
||
|
gasPrice: rootGetters['gasPrice/gasPrice'],
|
||
|
txGasParams: rootGetters['gasPrice/txGasParams'],
|
||
|
currentGasPriceL2: rootGetters['gasPrice/currentGasPriceL2'],
|
||
|
}
|
||
|
},
|
||
|
}
|
||
|
|
||
|
export const mutations: MutationTree<ApplicationState> = {
|
||
|
[ApplicationMutation.SET_CONTRACT_CONSTANTS](state, payload: ContractConstants) {
|
||
|
state.contract = { ...state.contract, ...payload }
|
||
|
},
|
||
|
[ApplicationMutation.SET_PROCESSING_MODAL](state, payload) {
|
||
|
state.processing.modal = { ...state.processing.modal, ...payload }
|
||
|
},
|
||
|
[ApplicationMutation.SET_PROCESSING_INFO](state, payload) {
|
||
|
// @ts-expect-error
|
||
|
this._vm.$set(state.processing, 'info', { ...state.processing.info, ...payload })
|
||
|
},
|
||
|
[ApplicationMutation.SET_PROCESSING_STATUS](state, payload) {
|
||
|
// @ts-expect-error
|
||
|
this._vm.$set(state.processing.statuses, payload.step, payload.status)
|
||
|
},
|
||
|
[ApplicationMutation.CLEAR_PROCESSING](state) {
|
||
|
state.processing = {
|
||
|
info: {
|
||
|
type: '',
|
||
|
txHash: '',
|
||
|
params: {},
|
||
|
account: '',
|
||
|
blockNumber: 0,
|
||
|
},
|
||
|
modal: {
|
||
|
title: '',
|
||
|
chainId: L1_CHAIN_ID,
|
||
|
type: transactionMethods.TRANSFER,
|
||
|
isShow: false,
|
||
|
},
|
||
|
statuses: {
|
||
|
generate: '',
|
||
|
transact: '',
|
||
|
wait: '',
|
||
|
bridge: '',
|
||
|
complete: '',
|
||
|
},
|
||
|
}
|
||
|
},
|
||
|
}
|
||
|
|
||
|
export const state = (): ApplicationState => {
|
||
|
return {
|
||
|
contract: {
|
||
|
maximumDepositAmount: BG_ZERO,
|
||
|
omnibridgeDailyLimit: BG_ZERO,
|
||
|
minimalWithdrawalAmount: BG_ZERO,
|
||
|
maximumWithdrawalAmount: BG_ZERO,
|
||
|
},
|
||
|
processing: {
|
||
|
info: {
|
||
|
txHash: '',
|
||
|
blockNumber: 0,
|
||
|
params: {},
|
||
|
type: '',
|
||
|
account: '',
|
||
|
},
|
||
|
modal: {
|
||
|
title: '',
|
||
|
isShow: false,
|
||
|
chainId: L1_CHAIN_ID,
|
||
|
type: transactionMethods.TRANSFER,
|
||
|
},
|
||
|
statuses: {
|
||
|
generate: '',
|
||
|
transact: '',
|
||
|
wait: '',
|
||
|
bridge: '',
|
||
|
complete: '',
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|