From 307a5c4b28b319bbc215b3358743d2b7af190c1a Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Fri, 2 Sep 2022 13:26:03 +0300 Subject: [PATCH 001/109] refactoring utils bridge folder structure & exports --- .../Bridge/TransferNotification.vue | 2 +- src/components/mixins/BridgeHistoryMixin.ts | 4 +- src/store/assets/actions.ts | 4 +- src/store/bridge/actions.ts | 24 ++- src/store/bridge/mutations.ts | 4 +- src/store/web3/mutations.ts | 4 +- src/utils/bridge/{ => eth}/api.ts | 2 +- src/utils/bridge/{ => eth}/history.ts | 39 ++-- src/utils/bridge/{ => eth}/types.ts | 11 +- src/utils/bridge/eth/utils.ts | 164 ++++++++++++++++ src/utils/bridge/index.ts | 151 +++++++-------- src/utils/bridge/utils.ts | 176 +----------------- src/views/BridgeContainer.vue | 4 +- src/views/BridgeTransaction.vue | 20 +- src/views/BridgeTransactionsHistory.vue | 2 +- 15 files changed, 308 insertions(+), 303 deletions(-) rename src/utils/bridge/{ => eth}/api.ts (57%) rename src/utils/bridge/{ => eth}/history.ts (88%) rename src/utils/bridge/{ => eth}/types.ts (55%) create mode 100644 src/utils/bridge/eth/utils.ts diff --git a/src/components/Bridge/TransferNotification.vue b/src/components/Bridge/TransferNotification.vue index 7ab90477f..abae74421 100644 --- a/src/components/Bridge/TransferNotification.vue +++ b/src/components/Bridge/TransferNotification.vue @@ -27,7 +27,7 @@ import { lazyComponent } from '@/router'; import { Components } from '@/consts'; import ethersUtil from '@/utils/ethers-util'; -import { isOutgoingTransaction } from '@/utils/bridge'; +import { isOutgoingTransaction } from '@/utils/bridge/eth/utils'; import { getter, state, mutation } from '@/store/decorators'; import type { BridgeHistory, RegisteredAccountAsset, RegisteredAsset } from '@sora-substrate/util'; diff --git a/src/components/mixins/BridgeHistoryMixin.ts b/src/components/mixins/BridgeHistoryMixin.ts index cad4126d0..121ea10fa 100644 --- a/src/components/mixins/BridgeHistoryMixin.ts +++ b/src/components/mixins/BridgeHistoryMixin.ts @@ -5,7 +5,7 @@ import type { BridgeHistory, CodecString } from '@sora-substrate/util'; import router from '@/router'; import { PageNames, ZeroStringValue } from '@/consts'; -import { bridgeApi } from '@/utils/bridge'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; import { state, mutation, action } from '@/store/decorators'; @Component @@ -35,7 +35,7 @@ export default class BridgeHistoryMixin extends Mixins(mixins.LoadingMixin) { this.handleBack(); } await this.withLoading(async () => { - const tx = bridgeApi.getHistory(id as string) as BridgeHistory; + const tx = ethBridgeApi.getHistory(id as string) as BridgeHistory; if (!tx?.id) { this.handleBack(); diff --git a/src/store/assets/actions.ts b/src/store/assets/actions.ts index beee791aa..cde8f15d1 100644 --- a/src/store/assets/actions.ts +++ b/src/store/assets/actions.ts @@ -1,7 +1,7 @@ import { defineActions } from 'direct-vuex'; import { assetsActionContext } from '@/store/assets'; -import { bridgeApi } from '@/utils/bridge'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; import { ZeroStringValue } from '@/consts'; import type { RegisterAssetWithExternalBalance } from './types'; @@ -26,7 +26,7 @@ const actions = defineActions({ commit.setRegisteredAssetsFetching(true); try { - const registeredAssets = await bridgeApi.getRegisteredAssets(); + const registeredAssets = await ethBridgeApi.getRegisteredAssets(); const enabledRegisteredAssets = registeredAssets.filter( (item) => !DISABLED_ASSETS_FOR_BRIDGE.includes(item.address) ); diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index 96e39e67d..761ba5c97 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -5,10 +5,14 @@ import { BridgeCurrencyType, BridgeHistory, BridgeNetworks, FPNumber, Operation import type { ActionContext } from 'vuex'; import type { AccountBalance } from '@sora-substrate/util/build/assets/types'; +import appBridge from '@/utils/bridge'; import { bridgeActionContext } from '@/store/bridge'; import { MaxUint256 } from '@/consts'; import { TokenBalanceSubscriptions } from '@/utils/subscriptions'; -import { appBridge, bridgeApi, EthBridgeHistory, STATES, waitForApprovedRequest } from '@/utils/bridge'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; +import { ETH_BRIDGE_STATES } from '@/utils/bridge/eth/types'; +import { waitForApprovedRequest } from '@/utils/bridge/eth/utils'; +import { EthBridgeHistory } from '@/utils/bridge/eth/history'; import ethersUtil, { ABI, KnownBridgeAsset, OtherContractType } from '@/utils/ethers-util'; import { isEthereumAddress } from '@/utils'; import type { SignTxResult } from './types'; @@ -39,7 +43,7 @@ function bridgeDataToHistoryItem( transactionStep: step as 1 | 2, hash: '', ethereumHash: '', - transactionState: STATES.INITIAL, + transactionState: ETH_BRIDGE_STATES.INITIAL, soraNetworkFee: (params as any).soraNetworkFee ?? getters.soraNetworkFee, ethereumNetworkFee: (params as any).ethereumNetworkFee ?? state.evmNetworkFee, externalNetwork: rootState.web3.evmNetwork, @@ -86,14 +90,14 @@ const actions = defineActions({ const blockNumber = value ?? (await (await ethersUtil.getEthersInstance()).getBlockNumber()); commit.setEvmBlockNumber(blockNumber); }, - async getBridgeHistoryInstance(context): Promise { + async getEthBridgeHistoryInstance(context): Promise { const { rootState } = bridgeActionContext(context); const etherscanApiKey = rootState.wallet.settings.apiKeys?.etherscan; - const bridgeHistory = new EthBridgeHistory(etherscanApiKey); + const ethBridgeHistory = new EthBridgeHistory(etherscanApiKey); - await bridgeHistory.init(); + await ethBridgeHistory.init(); - return bridgeHistory; + return ethBridgeHistory; }, // TODO: Need to restore transactions for all networks async updateHistory(context): Promise { @@ -102,7 +106,7 @@ const actions = defineActions({ commit.setHistoryLoading(true); - const bridgeHistory = await dispatch.getBridgeHistoryInstance(); + const bridgeHistory = await dispatch.getEthBridgeHistoryInstance(); const address = rootState.wallet.account.address; const assets = rootGetters.assets.assetsDataTable; const networkFees = rootState.wallet.settings.networkFees; @@ -135,7 +139,7 @@ const actions = defineActions({ async generateHistoryItem(context, playground): Promise { const { commit } = bridgeActionContext(context); const historyData = bridgeDataToHistoryItem(context, playground); - const historyItem = bridgeApi.generateHistoryItem(historyData); + const historyItem = ethBridgeApi.generateHistoryItem(historyData); if (!historyItem) { throw new Error('[Bridge]: "generateHistoryItem" failed'); @@ -147,7 +151,7 @@ const actions = defineActions({ }, async signEvmTransactionSoraToEvm(context, id: string): Promise { const { getters, rootState, rootGetters } = bridgeActionContext(context); - const tx = bridgeApi.getHistory(id) as Nullable; + const tx = ethBridgeApi.getHistory(id) as Nullable; if (!tx?.hash) throw new Error('TX ID cannot be empty!'); if (!tx.amount) throw new Error('TX amount cannot be empty!'); @@ -225,7 +229,7 @@ const actions = defineActions({ }, async signEvmTransactionEvmToSora(context, id: string): Promise { const { commit, rootState, rootGetters, rootDispatch } = bridgeActionContext(context); - const tx = bridgeApi.getHistory(id); + const tx = ethBridgeApi.getHistory(id); if (!tx?.id) throw new Error('TX cannot be empty!'); if (!tx.amount) throw new Error('TX amount cannot be empty!'); diff --git a/src/store/bridge/mutations.ts b/src/store/bridge/mutations.ts index a709ec845..92bd997f4 100644 --- a/src/store/bridge/mutations.ts +++ b/src/store/bridge/mutations.ts @@ -4,7 +4,7 @@ import type { BridgeHistory, CodecString } from '@sora-substrate/util'; import type { AccountBalance } from '@sora-substrate/util/build/assets/types'; import { ZeroStringValue } from '@/consts'; -import { bridgeApi } from '@/utils/bridge'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; import type { BridgeState } from './types'; const mutations = defineMutations()({ @@ -32,7 +32,7 @@ const mutations = defineMutations()({ state.evmNetworkFeeFetching = false; }, setHistory(state): void { - state.history = [...bridgeApi.historyList] as Array; + state.history = [...ethBridgeApi.historyList] as Array; }, setHistoryPage(state, historyPage?: number): void { state.historyPage = historyPage || 1; diff --git a/src/store/web3/mutations.ts b/src/store/web3/mutations.ts index 11c21f522..0993bd216 100644 --- a/src/store/web3/mutations.ts +++ b/src/store/web3/mutations.ts @@ -3,7 +3,7 @@ import { defineMutations } from 'direct-vuex'; import { BridgeNetworks, CodecString } from '@sora-substrate/util'; import ethersUtil from '@/utils/ethers-util'; -import { bridgeApi } from '@/utils/bridge'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; import { initialState } from './state'; import type { SubNetwork } from '@/utils/ethers-util'; import type { Web3State } from './types'; @@ -37,7 +37,7 @@ const mutations = defineMutations()({ }, setEvmNetwork(state, network: BridgeNetworks): void { state.evmNetwork = network; - bridgeApi.externalNetwork = network; + ethBridgeApi.externalNetwork = network; }, setEvmBalance(state, balance: CodecString): void { state.evmBalance = balance; diff --git a/src/utils/bridge/api.ts b/src/utils/bridge/eth/api.ts similarity index 57% rename from src/utils/bridge/api.ts rename to src/utils/bridge/eth/api.ts index 25583962a..ab5dd794d 100644 --- a/src/utils/bridge/api.ts +++ b/src/utils/bridge/eth/api.ts @@ -1,3 +1,3 @@ import { api } from '@soramitsu/soraneo-wallet-web'; -export const bridgeApi = api.bridge; +export const ethBridgeApi = api.bridge; diff --git a/src/utils/bridge/history.ts b/src/utils/bridge/eth/history.ts similarity index 88% rename from src/utils/bridge/history.ts rename to src/utils/bridge/eth/history.ts index 8c437771b..a214bd577 100644 --- a/src/utils/bridge/history.ts +++ b/src/utils/bridge/eth/history.ts @@ -6,9 +6,12 @@ import { BridgeNetworks, BridgeTxStatus, Operation } from '@sora-substrate/util' import { SubqueryExplorerService, historyElementsFilter, SUBQUERY_TYPES, api } from '@soramitsu/soraneo-wallet-web'; import ethersUtil from '@/utils/ethers-util'; -import { bridgeApi } from './api'; -import { STATES } from './types'; -import { isOutgoingTransaction, getEvmTxRecieptByHash } from './utils'; +import { getEvmTransactionRecieptByHash } from '@/utils/bridge/utils'; + +import { ethBridgeApi } from '@/utils/bridge/eth/api'; +import { ETH_BRIDGE_STATES } from '@/utils/bridge/eth/types'; +import { isOutgoingTransaction } from '@/utils/bridge/eth/utils'; + import { ZeroStringValue } from '@/consts'; import type { BridgeHistory, NetworkFeesObject } from '@sora-substrate/util'; @@ -42,11 +45,11 @@ export class EthBridgeHistory { } public get historySyncTimestamp(): number { - return +(bridgeApi.accountStorage?.get('bridgeHistorySyncTimestamp') || 0); + return +(ethBridgeApi.accountStorage?.get('bridgeHistorySyncTimestamp') || 0); } public set historySyncTimestamp(timestamp: number) { - bridgeApi.accountStorage?.set('bridgeHistorySyncTimestamp', timestamp); + ethBridgeApi.accountStorage?.set('bridgeHistorySyncTimestamp', timestamp); } public async init(): Promise { @@ -194,7 +197,7 @@ export class EthBridgeHistory { contracts?: string[], updateCallback?: AsyncVoidFn | VoidFunction ): Promise { - const currentHistory = bridgeApi.historyList as BridgeHistory[]; + const currentHistory = ethBridgeApi.historyList as BridgeHistory[]; const historyElements = await this.fetchHistoryElements(address, this.historySyncTimestamp); if (!historyElements.length) return; @@ -223,7 +226,8 @@ export class EthBridgeHistory { // skip, if local bridge transaction has "Done" status if (localHistoryItem?.status === BridgeTxStatus.Done) continue; - const hash = isOutgoing ? requestHash : await bridgeApi.getSoraHashByEthereumHash(requestHash); + // [WARNING]: api.query.ethBridge storage usage + const hash = isOutgoing ? requestHash : await ethBridgeApi.getSoraHashByEthereumHash(requestHash); const amount = historyElementData.amount; const assetAddress = historyElementData.assetId; const from = address; @@ -232,8 +236,9 @@ export class EthBridgeHistory { const txId = historyElement.id; const soraNetworkFee = isOutgoing ? networkFees[Operation.EthBridgeOutgoing] : ZeroStringValue; const soraTimestamp = historyElement.timestamp * 1000; + // [WARNING]: api.query.ethBridge storage usage const soraPartCompleted = - !isOutgoing || (!!hash && (await bridgeApi.getRequestStatus(hash))) === BridgeTxStatus.Ready; + !isOutgoing || (!!hash && (await ethBridgeApi.getRequestStatus(hash))) === BridgeTxStatus.Ready; const transactionStep = soraPartCompleted ? 2 : 1; const ethereumTx = isOutgoing @@ -243,10 +248,10 @@ export class EthBridgeHistory { : await this.findEthTxByEthereumHash(requestHash); const ethereumHash = ethereumTx?.hash ?? ''; - const recieptData = ethereumHash ? await getEvmTxRecieptByHash(ethereumHash) : null; + const recieptData = ethereumHash ? await getEvmTransactionRecieptByHash(ethereumHash) : null; const to = isOutgoing ? historyElementData.sidechainAddress : recieptData?.from; - const ethereumNetworkFee = recieptData?.ethereumNetworkFee; + const ethereumNetworkFee = recieptData?.evmNetworkFee; const blockHeight = ethereumTx ? String(ethereumTx.blockNumber) : undefined; const evmTimestamp = ethereumTx?.timestamp ? ethereumTx.timestamp * 1000 @@ -258,13 +263,13 @@ export class EthBridgeHistory { const transactionState = isOutgoing ? ethereumHash - ? STATES.EVM_COMMITED + ? ETH_BRIDGE_STATES.EVM_COMMITED : soraPartCompleted - ? STATES.EVM_REJECTED + ? ETH_BRIDGE_STATES.EVM_REJECTED : hash - ? STATES.SORA_PENDING - : STATES.SORA_REJECTED - : STATES.SORA_COMMITED; + ? ETH_BRIDGE_STATES.SORA_PENDING + : ETH_BRIDGE_STATES.SORA_REJECTED + : ETH_BRIDGE_STATES.SORA_COMMITED; const status = isOutgoing ? ethereumHash @@ -300,9 +305,9 @@ export class EthBridgeHistory { // update or create local history item if (localHistoryItem) { - bridgeApi.saveHistory({ ...localHistoryItem, ...historyItemData } as BridgeHistory); + ethBridgeApi.saveHistory({ ...localHistoryItem, ...historyItemData } as BridgeHistory); } else { - bridgeApi.generateHistoryItem(historyItemData as BridgeHistory); + ethBridgeApi.generateHistoryItem(historyItemData as BridgeHistory); } await updateCallback?.(); diff --git a/src/utils/bridge/types.ts b/src/utils/bridge/eth/types.ts similarity index 55% rename from src/utils/bridge/types.ts rename to src/utils/bridge/eth/types.ts index 914fe6c03..6a5876915 100644 --- a/src/utils/bridge/types.ts +++ b/src/utils/bridge/eth/types.ts @@ -1,6 +1,4 @@ -import type { BridgeTxStatus } from '@sora-substrate/util'; - -export enum STATES { +export enum ETH_BRIDGE_STATES { INITIAL = 'INITIAL', SORA_SUBMITTED = 'SORA_SUBMITTED', SORA_PENDING = 'SORA_PENDING', @@ -11,10 +9,3 @@ export enum STATES { EVM_REJECTED = 'EVM_REJECTED', EVM_COMMITED = 'EVM_COMMITED', } - -export type HandleTransactionPayload = { - status?: BridgeTxStatus; - nextState: STATES; - rejectState: STATES; - handler?: (id: string) => Promise; -}; diff --git a/src/utils/bridge/eth/utils.ts b/src/utils/bridge/eth/utils.ts new file mode 100644 index 000000000..510f7f085 --- /dev/null +++ b/src/utils/bridge/eth/utils.ts @@ -0,0 +1,164 @@ +import { Operation, BridgeTxStatus } from '@sora-substrate/util'; +import { api } from '@soramitsu/soraneo-wallet-web'; +import type { Subscription } from 'rxjs'; +import type { BridgeHistory, BridgeApprovedRequest } from '@sora-substrate/util'; + +import { delay } from '@/utils'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; +import { waitForEvmTransactionStatus } from '@/utils/bridge/utils'; + +const SORA_REQUESTS_TIMEOUT = 6_000; // Block production time + +export const isUnsignedFromPart = (tx: BridgeHistory): boolean => { + if (tx.type === Operation.EthBridgeOutgoing) { + return !tx.blockId && !tx.txId; + } else if (tx.type === Operation.EthBridgeIncoming) { + return !tx.ethereumHash; + } else { + return true; + } +}; + +export const isUnsignedToPart = (tx: BridgeHistory): boolean => { + if (tx.type === Operation.EthBridgeOutgoing) { + return !tx.ethereumHash; + } else if (tx.type === Operation.EthBridgeIncoming) { + return false; + } else { + return true; + } +}; + +export const getTransaction = (id: string): BridgeHistory => { + const tx = ethBridgeApi.getHistory(id) as BridgeHistory; + + if (!tx) throw new Error(`[Bridge]: Transaction is not exists: ${id}`); + + return tx; +}; + +export const updateHistoryParams = async (id: string, params = {}) => { + const tx = getTransaction(id); + ethBridgeApi.saveHistory({ ...tx, ...params }); +}; + +export const isOutgoingTransaction = (tx: Nullable): boolean => { + return tx?.type === Operation.EthBridgeOutgoing; +}; + +export const waitForApprovedRequest = async (tx: BridgeHistory): Promise => { + if (!tx.hash) throw new Error(`[Bridge]: Tx hash cannot be empty`); + if (!Number.isFinite(tx.externalNetwork)) + throw new Error(`[Bridge]: Tx externalNetwork should be a number, ${tx.externalNetwork} received`); + + let subscription!: Subscription; + + await new Promise((resolve, reject) => { + subscription = ethBridgeApi.subscribeOnRequestStatus(tx.hash as string).subscribe((status) => { + switch (status) { + case BridgeTxStatus.Failed: + case BridgeTxStatus.Frozen: + reject(new Error('[Bridge]: Transaction was failed or canceled')); + break; + case BridgeTxStatus.Ready: + resolve(); + break; + } + }); + }); + + subscription.unsubscribe(); + + return ethBridgeApi.getApprovedRequest(tx.hash as string); +}; + +export const waitForIncomingRequest = async (tx: BridgeHistory): Promise<{ hash: string; blockId: string }> => { + if (!tx.ethereumHash) throw new Error('[Bridge]: ethereumHash cannot be empty!'); + if (!Number.isFinite(tx.externalNetwork)) + throw new Error(`[Bridge]: Tx externalNetwork should be a number, ${tx.externalNetwork} received`); + + let subscription!: Subscription; + + await new Promise((resolve, reject) => { + subscription = ethBridgeApi.subscribeOnRequest(tx.ethereumHash as string).subscribe((request) => { + if (request) { + switch (request.status) { + case BridgeTxStatus.Failed: + case BridgeTxStatus.Frozen: + reject(new Error('[Bridge]: Transaction was failed or canceled')); + break; + case BridgeTxStatus.Done: + resolve(); + break; + } + } + }); + }); + + subscription.unsubscribe(); + + const soraHash = await ethBridgeApi.getSoraHashByEthereumHash(tx.ethereumHash as string); + const soraBlockHash = await ethBridgeApi.getSoraBlockHashByRequestHash(tx.ethereumHash as string); + + return { hash: soraHash, blockId: soraBlockHash }; +}; + +export const waitForSoraTransactionHash = async (id: string): Promise => { + const tx = getTransaction(id); + + if (tx.hash) return tx.hash; + const blockId = tx.blockId as string; // blockId cannot be empty + const extrinsics = await api.system.getExtrinsicsFromBlock(blockId); + + if (extrinsics.length) { + const blockEvents = await api.system.getBlockEvents(blockId); + + const extrinsicIndex = extrinsics.findIndex((item) => { + const { + signer, + method: { method, section }, + } = item; + + return signer.toString() === tx.from && method === 'transferToSidechain' && section === 'ethBridge'; + }); + + if (!Number.isFinite(extrinsicIndex)) throw new Error('[Bridge]: Transaction was failed'); + + const event = blockEvents.find( + ({ phase, event }) => + phase.isApplyExtrinsic && + phase.asApplyExtrinsic.eq(extrinsicIndex) && + event.section === 'ethBridge' && + event.method === 'RequestRegistered' + ); + + if (!event) { + throw new Error('[Bridge]: Transaction was failed'); + } + + const hash = event.event.data[0].toString(); + + return hash; + } + + await delay(SORA_REQUESTS_TIMEOUT); + + return await waitForSoraTransactionHash(id); +}; + +export const waitForEvmTransaction = async (id: string) => { + const transaction = getTransaction(id); + + if (!transaction.ethereumHash) throw new Error('[Bridge]: ethereumHash cannot be empty!'); + + await waitForEvmTransactionStatus( + transaction.ethereumHash, + (ethereumHash: string) => { + updateHistoryParams(id, { ethereumHash }); + waitForEvmTransaction(id); + }, + () => { + throw new Error('[Bridge]: The transaction was canceled by the user'); + } + ); +}; diff --git a/src/utils/bridge/index.ts b/src/utils/bridge/index.ts index dd5dfec18..2741b7704 100644 --- a/src/utils/bridge/index.ts +++ b/src/utils/bridge/index.ts @@ -4,25 +4,31 @@ import { SUBQUERY_TYPES } from '@soramitsu/soraneo-wallet-web'; import { ethers } from 'ethers'; import store from '@/store'; +import { getEvmTransactionRecieptByHash } from '@/utils/bridge/utils'; -import { bridgeApi } from './api'; -import { STATES } from './types'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; +import { ETH_BRIDGE_STATES } from '@/utils/bridge/eth/types'; import { getTransaction, - updateHistoryParams, - waitForIncomingRequest, waitForApprovedRequest, + waitForIncomingRequest, waitForSoraTransactionHash, waitForEvmTransaction, - getEvmTxRecieptByHash, -} from './utils'; + updateHistoryParams, +} from '@/utils/bridge/eth/utils'; import type { BridgeHistory } from '@sora-substrate/util'; -import type { HandleTransactionPayload } from './types'; -import type { EthBridgeHistory } from './history'; +import type { EthBridgeHistory } from '@/utils/bridge/eth/history'; import type { SignTxResult } from '@/store/bridge/types'; import type { RegisteredAccountAssetWithDecimals } from '@/store/assets/types'; +type HandleTransactionPayload = { + status?: BridgeTxStatus; + nextState: ETH_BRIDGE_STATES; + rejectState: ETH_BRIDGE_STATES; + handler?: (id: string) => Promise; +}; + type SignedEvmTxResult = SignTxResult; type SignEvm = (id: string) => Promise; @@ -138,19 +144,19 @@ class BridgeTransactionStateHandler { await waitForEvmTransaction(id); const tx = getTransaction(id); - const { ethereumNetworkFee, blockHeight } = (await getEvmTxRecieptByHash(tx.ethereumHash as string)) || {}; + const { evmNetworkFee, blockHeight } = (await getEvmTransactionRecieptByHash(tx.ethereumHash as string)) || {}; - if (!ethereumNetworkFee || !blockHeight) { + if (!evmNetworkFee || !blockHeight) { this.updateTransactionParams(id, { ethereumHash: undefined, ethereumNetworkFee: undefined }); throw new Error(`[Bridge]: Ethereum transaction not found, hash: ${tx.ethereumHash}. 'ethereumHash' is reset`); } // In BridgeHistory 'blockHeight' will store evm block number - this.updateTransactionParams(id, { ethereumNetworkFee, blockHeight }); + this.updateTransactionParams(id, { ethereumNetworkFee: evmNetworkFee, blockHeight }); } async onEvmSubmitted(id: string): Promise { - this.updateTransactionParams(id, { transactionState: STATES.EVM_PENDING }); + this.updateTransactionParams(id, { transactionState: ETH_BRIDGE_STATES.EVM_PENDING }); const tx = getTransaction(id); @@ -191,22 +197,22 @@ class EthBridgeOutgoingStateReducer extends BridgeTransactionStateHandler { if (!transaction.id) throw new Error('[Bridge]: TX ID cannot be empty'); switch (transaction.transactionState) { - case STATES.INITIAL: { + case ETH_BRIDGE_STATES.INITIAL: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.SORA_SUBMITTED, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_SUBMITTED, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, }); } - case STATES.SORA_SUBMITTED: { + case ETH_BRIDGE_STATES.SORA_SUBMITTED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.SORA_PENDING, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_PENDING, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, handler: async (id: string) => { await this.beforeSubmit(id); - this.updateTransactionParams(id, { transactionState: STATES.SORA_PENDING }); + this.updateTransactionParams(id, { transactionState: ETH_BRIDGE_STATES.SORA_PENDING }); const { txId, blockId, to, amount, assetAddress } = getTransaction(id); @@ -221,12 +227,12 @@ class EthBridgeOutgoingStateReducer extends BridgeTransactionStateHandler { // transaction not signed if (!txId) { - await bridgeApi.transferToEth(asset, to, amount, id); + await ethBridgeApi.transferToEth(asset, to, amount, id); } // signed sora transaction has to be parsed by subquery if (txId && !blockId) { // format account address to sora format - const address = bridgeApi.formatAddress(bridgeApi.account.pair.address); + const address = ethBridgeApi.formatAddress(ethBridgeApi.account.pair.address); const bridgeHistory = await this.getBridgeHistoryInstance(); const historyItem = first(await bridgeHistory.fetchHistoryElements(address, 0, [txId])); @@ -243,10 +249,10 @@ class EthBridgeOutgoingStateReducer extends BridgeTransactionStateHandler { }); } - case STATES.SORA_PENDING: { + case ETH_BRIDGE_STATES.SORA_PENDING: { return await this.handleState(transaction.id, { - nextState: STATES.SORA_COMMITED, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_COMMITED, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, handler: async (id: string) => { const hash = await waitForSoraTransactionHash(id); @@ -261,52 +267,52 @@ class EthBridgeOutgoingStateReducer extends BridgeTransactionStateHandler { }); } - case STATES.SORA_COMMITED: { + case ETH_BRIDGE_STATES.SORA_COMMITED: { return await this.handleState(transaction.id, { - nextState: STATES.EVM_SUBMITTED, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_SUBMITTED, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, handler: async (id: string) => this.updateTransactionStep(id), }); } - case STATES.SORA_REJECTED: + case ETH_BRIDGE_STATES.SORA_REJECTED: return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.SORA_SUBMITTED, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_SUBMITTED, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, }); - case STATES.EVM_SUBMITTED: { + case ETH_BRIDGE_STATES.EVM_SUBMITTED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.EVM_PENDING, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_PENDING, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, handler: async (id: string) => await this.onEvmSubmitted(id), }); } - case STATES.EVM_PENDING: { + case ETH_BRIDGE_STATES.EVM_PENDING: { return await this.handleState(transaction.id, { - nextState: STATES.EVM_COMMITED, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_COMMITED, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, handler: async (id: string) => await this.onEvmPending(id), }); } - case STATES.EVM_COMMITED: { + case ETH_BRIDGE_STATES.EVM_COMMITED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Done, - nextState: STATES.EVM_COMMITED, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_COMMITED, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, handler: async (id: string) => this.onComplete(id), }); } - case STATES.EVM_REJECTED: { + case ETH_BRIDGE_STATES.EVM_REJECTED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.EVM_SUBMITTED, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_SUBMITTED, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, }); } } @@ -318,59 +324,59 @@ class EthBridgeIncomingStateReducer extends BridgeTransactionStateHandler { if (!transaction.id) throw new Error('[Bridge]: TX ID cannot be empty'); switch (transaction.transactionState) { - case STATES.INITIAL: { + case ETH_BRIDGE_STATES.INITIAL: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.EVM_SUBMITTED, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_SUBMITTED, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, }); } - case STATES.EVM_SUBMITTED: { + case ETH_BRIDGE_STATES.EVM_SUBMITTED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.EVM_PENDING, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_PENDING, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, handler: async (id: string) => await this.onEvmSubmitted(id), }); } - case STATES.EVM_PENDING: { + case ETH_BRIDGE_STATES.EVM_PENDING: { return await this.handleState(transaction.id, { - nextState: STATES.EVM_COMMITED, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_COMMITED, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, handler: async (id: string) => await this.onEvmPending(id), }); } - case STATES.EVM_COMMITED: { + case ETH_BRIDGE_STATES.EVM_COMMITED: { return await this.handleState(transaction.id, { - nextState: STATES.SORA_SUBMITTED, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_SUBMITTED, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, handler: async (id: string) => this.updateTransactionStep(id), }); } - case STATES.EVM_REJECTED: { + case ETH_BRIDGE_STATES.EVM_REJECTED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.EVM_SUBMITTED, - rejectState: STATES.EVM_REJECTED, + nextState: ETH_BRIDGE_STATES.EVM_SUBMITTED, + rejectState: ETH_BRIDGE_STATES.EVM_REJECTED, }); } - case STATES.SORA_SUBMITTED: { + case ETH_BRIDGE_STATES.SORA_SUBMITTED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.SORA_PENDING, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_PENDING, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, }); } - case STATES.SORA_PENDING: { + case ETH_BRIDGE_STATES.SORA_PENDING: { return await this.handleState(transaction.id, { - nextState: STATES.SORA_COMMITED, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_COMMITED, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, handler: async (id: string) => { const tx = getTransaction(id); const { hash, blockId } = await waitForIncomingRequest(tx); @@ -379,20 +385,20 @@ class EthBridgeIncomingStateReducer extends BridgeTransactionStateHandler { }); } - case STATES.SORA_COMMITED: { + case ETH_BRIDGE_STATES.SORA_COMMITED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Done, - nextState: STATES.SORA_COMMITED, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_COMMITED, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, handler: async (id: string) => this.onComplete(id), }); } - case STATES.SORA_REJECTED: { + case ETH_BRIDGE_STATES.SORA_REJECTED: { return await this.handleState(transaction.id, { status: BridgeTxStatus.Pending, - nextState: STATES.SORA_SUBMITTED, - rejectState: STATES.SORA_REJECTED, + nextState: ETH_BRIDGE_STATES.SORA_SUBMITTED, + rejectState: ETH_BRIDGE_STATES.SORA_REJECTED, }); } } @@ -459,10 +465,7 @@ const appBridge = new Bridge({ showNotification: (tx: BridgeHistory) => store.commit.bridge.setNotificationData(tx), getAssetByAddress: (address: string) => store.getters.assets.assetDataByAddress(address), getActiveHistoryItem: () => store.getters.bridge.historyItem, - getBridgeHistoryInstance: () => store.dispatch.bridge.getBridgeHistoryInstance(), + getBridgeHistoryInstance: () => store.dispatch.bridge.getEthBridgeHistoryInstance(), }); -export { bridgeApi, appBridge }; -export * from './types'; -export * from './utils'; -export * from './history'; +export default appBridge; diff --git a/src/utils/bridge/utils.ts b/src/utils/bridge/utils.ts index 6622a38fc..1f9af4358 100644 --- a/src/utils/bridge/utils.ts +++ b/src/utils/bridge/utils.ts @@ -1,152 +1,5 @@ -import { Operation, BridgeTxStatus } from '@sora-substrate/util'; import { ethers } from 'ethers'; -import type { Subscription } from 'rxjs'; -import type { BridgeHistory, BridgeApprovedRequest, BridgeNetworks } from '@sora-substrate/util'; - -import { bridgeApi } from './api'; - -import { delay } from '@/utils'; import ethersUtil from '@/utils/ethers-util'; -import { api } from '@soramitsu/soraneo-wallet-web'; - -const SORA_REQUESTS_TIMEOUT = 6_000; // Block production time - -export const isUnsignedFromPart = (tx: BridgeHistory): boolean => { - if (tx.type === Operation.EthBridgeOutgoing) { - return !tx.blockId && !tx.txId; - } else if (tx.type === Operation.EthBridgeIncoming) { - return !tx.ethereumHash; - } else { - return true; - } -}; - -export const isUnsignedToPart = (tx: BridgeHistory): boolean => { - if (tx.type === Operation.EthBridgeOutgoing) { - return !tx.ethereumHash; - } else if (tx.type === Operation.EthBridgeIncoming) { - return false; - } else { - return true; - } -}; - -export const getTransaction = (id: string): BridgeHistory => { - const tx = bridgeApi.getHistory(id) as BridgeHistory; - - if (!tx) throw new Error(`[Bridge]: Transaction is not exists: ${id}`); - - return tx; -}; - -export const updateHistoryParams = async (id: string, params = {}) => { - const tx = getTransaction(id); - bridgeApi.saveHistory({ ...tx, ...params }); -}; - -export const isOutgoingTransaction = (tx: Nullable): boolean => { - return tx?.type === Operation.EthBridgeOutgoing; -}; - -export const waitForApprovedRequest = async (tx: BridgeHistory): Promise => { - if (!tx.hash) throw new Error(`[Bridge]: Tx hash cannot be empty`); - if (!Number.isFinite(tx.externalNetwork)) - throw new Error(`[Bridge]: Tx externalNetwork should be a number, ${tx.externalNetwork} received`); - - let subscription!: Subscription; - - await new Promise((resolve, reject) => { - subscription = bridgeApi.subscribeOnRequestStatus(tx.hash as string).subscribe((status) => { - switch (status) { - case BridgeTxStatus.Failed: - case BridgeTxStatus.Frozen: - reject(new Error('[Bridge]: Transaction was failed or canceled')); - break; - case BridgeTxStatus.Ready: - resolve(); - break; - } - }); - }); - - subscription.unsubscribe(); - - return bridgeApi.getApprovedRequest(tx.hash as string); -}; - -export const waitForIncomingRequest = async (tx: BridgeHistory): Promise<{ hash: string; blockId: string }> => { - if (!tx.ethereumHash) throw new Error('[Bridge]: ethereumHash cannot be empty!'); - if (!Number.isFinite(tx.externalNetwork)) - throw new Error(`[Bridge]: Tx externalNetwork should be a number, ${tx.externalNetwork} received`); - - let subscription!: Subscription; - - await new Promise((resolve, reject) => { - subscription = bridgeApi.subscribeOnRequest(tx.ethereumHash as string).subscribe((request) => { - if (request) { - switch (request.status) { - case BridgeTxStatus.Failed: - case BridgeTxStatus.Frozen: - reject(new Error('[Bridge]: Transaction was failed or canceled')); - break; - case BridgeTxStatus.Done: - resolve(); - break; - } - } - }); - }); - - subscription.unsubscribe(); - - const soraHash = await bridgeApi.getSoraHashByEthereumHash(tx.ethereumHash as string); - const soraBlockHash = await bridgeApi.getSoraBlockHashByRequestHash(tx.ethereumHash as string); - - return { hash: soraHash, blockId: soraBlockHash }; -}; - -export const waitForSoraTransactionHash = async (id: string): Promise => { - const tx = getTransaction(id); - - if (tx.hash) return tx.hash; - const blockId = tx.blockId as string; // blockId cannot be empty - const extrinsics = await api.system.getExtrinsicsFromBlock(blockId); - - if (extrinsics.length) { - const blockEvents = await api.system.getBlockEvents(blockId); - - const extrinsicIndex = extrinsics.findIndex((item) => { - const { - signer, - method: { method, section }, - } = item; - - return signer.toString() === tx.from && method === 'transferToSidechain' && section === 'ethBridge'; - }); - - if (!Number.isFinite(extrinsicIndex)) throw new Error('[Bridge]: Transaction was failed'); - - const event = blockEvents.find( - ({ phase, event }) => - phase.isApplyExtrinsic && - phase.asApplyExtrinsic.eq(extrinsicIndex) && - event.section === 'ethBridge' && - event.method === 'RequestRegistered' - ); - - if (!event) { - throw new Error('[Bridge]: Transaction was failed'); - } - - const hash = event.event.data[0].toString(); - - return hash; - } - - await delay(SORA_REQUESTS_TIMEOUT); - - return await waitForSoraTransactionHash(id); -}; export const waitForEvmTransactionStatus = async ( hash: string, @@ -181,37 +34,20 @@ export const waitForEvmTransactionStatus = async ( } }; -export const waitForEvmTransaction = async (id: string) => { - const transaction = getTransaction(id); - - if (!transaction.ethereumHash) throw new Error('[Bridge]: ethereumHash cannot be empty!'); - - await waitForEvmTransactionStatus( - transaction.ethereumHash, - (ethereumHash: string) => { - updateHistoryParams(id, { ethereumHash }); - waitForEvmTransaction(id); - }, - () => { - throw new Error('[Bridge]: The transaction was canceled by the user'); - } - ); -}; - -export const getEvmTxRecieptByHash = async ( - ethereumHash: string -): Promise<{ ethereumNetworkFee: string; blockHeight: number; from: string } | null> => { +export const getEvmTransactionRecieptByHash = async ( + transactionHash: string +): Promise<{ evmNetworkFee: string; blockHeight: number; from: string } | null> => { try { const { from, effectiveGasPrice, gasUsed, blockNumber: blockHeight, - } = await ethersUtil.getEvmTransactionReceipt(ethereumHash); + } = await ethersUtil.getEvmTransactionReceipt(transactionHash); - const ethereumNetworkFee = ethersUtil.calcEvmFee(effectiveGasPrice.toNumber(), gasUsed.toNumber()); + const evmNetworkFee = ethersUtil.calcEvmFee(effectiveGasPrice.toNumber(), gasUsed.toNumber()); - return { ethereumNetworkFee, blockHeight, from }; + return { evmNetworkFee, blockHeight, from }; } catch (error) { return null; } diff --git a/src/views/BridgeContainer.vue b/src/views/BridgeContainer.vue index 31bd32bbb..087045f64 100644 --- a/src/views/BridgeContainer.vue +++ b/src/views/BridgeContainer.vue @@ -15,7 +15,7 @@ import { mixins } from '@soramitsu/soraneo-wallet-web'; import WalletConnectMixin from '@/components/mixins/WalletConnectMixin'; import ethersUtil from '@/utils/ethers-util'; -import { bridgeApi } from '@/utils/bridge'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; import { action } from '@/store/decorators'; @Component @@ -30,7 +30,7 @@ export default class BridgeContainer extends Mixins(mixins.LoadingMixin, WalletC async created(): Promise { await this.withLoading(async () => { await this.withParentLoading(async () => { - this.setEvmNetwork(bridgeApi.externalNetwork); + this.setEvmNetwork(ethBridgeApi.externalNetwork); await this.onEvmNetworkTypeChange(); this.unwatchEthereum = await ethersUtil.watchEthereum({ diff --git a/src/views/BridgeTransaction.vue b/src/views/BridgeTransaction.vue index f2837abb5..db4383646 100644 --- a/src/views/BridgeTransaction.vue +++ b/src/views/BridgeTransaction.vue @@ -304,7 +304,9 @@ import router, { lazyComponent } from '@/router'; import { Components, PageNames } from '@/consts'; import { action, state, getter, mutation } from '@/store/decorators'; import { hasInsufficientBalance, hasInsufficientXorForFee, hasInsufficientEvmNativeTokenForFee } from '@/utils'; -import { bridgeApi, STATES, isOutgoingTransaction, isUnsignedFromPart } from '@/utils/bridge'; +import { ethBridgeApi } from '@/utils/bridge/eth/api'; +import { ETH_BRIDGE_STATES } from '@/utils/bridge/eth/types'; +import { isOutgoingTransaction, isUnsignedFromPart } from '@/utils/bridge/eth/utils'; import type { RegisteredAccountAssetWithDecimals } from '@/store/assets/types'; const FORMATTED_HASH_LENGTH = 24; @@ -425,27 +427,27 @@ export default class BridgeTransaction extends Mixins( } get currentState(): string { - return this.historyItem?.transactionState ?? STATES.INITIAL; + return this.historyItem?.transactionState ?? ETH_BRIDGE_STATES.INITIAL; } get isTransferStarted(): boolean { - return this.currentState !== STATES.INITIAL; + return this.currentState !== ETH_BRIDGE_STATES.INITIAL; } get isTransactionFromPending(): boolean { - return this.currentState === (this.isSoraToEvm ? STATES.SORA_PENDING : STATES.EVM_PENDING); + return this.currentState === (this.isSoraToEvm ? ETH_BRIDGE_STATES.SORA_PENDING : ETH_BRIDGE_STATES.EVM_PENDING); } get isTransactionToPending(): boolean { - return this.currentState === (!this.isSoraToEvm ? STATES.SORA_PENDING : STATES.EVM_PENDING); + return this.currentState === (!this.isSoraToEvm ? ETH_BRIDGE_STATES.SORA_PENDING : ETH_BRIDGE_STATES.EVM_PENDING); } get isTransactionFromFailed(): boolean { - return this.currentState === (this.isSoraToEvm ? STATES.SORA_REJECTED : STATES.EVM_REJECTED); + return this.currentState === (this.isSoraToEvm ? ETH_BRIDGE_STATES.SORA_REJECTED : ETH_BRIDGE_STATES.EVM_REJECTED); } get isTransactionToFailed(): boolean { - return this.currentState === (!this.isSoraToEvm ? STATES.SORA_REJECTED : STATES.EVM_REJECTED); + return this.currentState === (!this.isSoraToEvm ? ETH_BRIDGE_STATES.SORA_REJECTED : ETH_BRIDGE_STATES.EVM_REJECTED); } get isTransactionFromCompleted(): boolean { @@ -453,7 +455,7 @@ export default class BridgeTransaction extends Mixins( } get isTransactionToCompleted(): boolean { - return this.currentState === (!this.isSoraToEvm ? STATES.SORA_COMMITED : STATES.EVM_COMMITED); + return this.currentState === (!this.isSoraToEvm ? ETH_BRIDGE_STATES.SORA_COMMITED : ETH_BRIDGE_STATES.EVM_COMMITED); } get transactionStep(): number { @@ -682,7 +684,7 @@ export default class BridgeTransaction extends Mixins( const tx = { ...this.historyItem }; if (tx.id && !this.txInProcess && isUnsignedFromPart(tx)) { - bridgeApi.removeHistory(tx.id); + ethBridgeApi.removeHistory(tx.id); this.setHistory(); // hack to update another views because of unknown hooks exucution order } } diff --git a/src/views/BridgeTransactionsHistory.vue b/src/views/BridgeTransactionsHistory.vue index 13836e232..4243e59f6 100644 --- a/src/views/BridgeTransactionsHistory.vue +++ b/src/views/BridgeTransactionsHistory.vue @@ -73,7 +73,7 @@ import NetworkFormatterMixin from '@/components/mixins/NetworkFormatterMixin'; import router, { lazyComponent } from '@/router'; import { Components, PageNames } from '@/consts'; import { state, action, getter } from '@/store/decorators'; -import { isUnsignedToPart } from '@/utils/bridge'; +import { isUnsignedToPart } from '@/utils/bridge/eth/utils'; @Component({ components: { From 13158d6834b5ab7be2ec4d0eda0126d337a60d38 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Tue, 6 Sep 2022 17:02:00 +0300 Subject: [PATCH 002/109] update wallet integration --- src/store/bridge/actions.ts | 24 +++++++++++++++++++++--- src/store/settings/actions.ts | 7 +++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index 761ba5c97..6d7bcfe2b 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -90,6 +90,24 @@ const actions = defineActions({ const blockNumber = value ?? (await (await ethersUtil.getEthersInstance()).getBlockNumber()); commit.setEvmBlockNumber(blockNumber); }, + + // For wallet + async updateEthBridgeHistory(context, setHistoryCallback?: VoidFunction): Promise { + const { commit, dispatch, rootState, rootGetters } = bridgeActionContext(context); + + const bridgeHistory = await dispatch.getEthBridgeHistoryInstance(); + const address = rootState.wallet.account.address; + const assets = rootGetters.assets.assetsDataTable; + const networkFees = rootState.wallet.settings.networkFees; + const contractsArray = Object.values(KnownBridgeAsset).map>((key) => + rootGetters.web3.contractAddress(key) + ); + const contracts = compact(contractsArray); + const updateCallback = setHistoryCallback || (() => commit.setHistory()); + + await bridgeHistory.updateAccountHistory(address, assets, networkFees, contracts, updateCallback); + }, + async getEthBridgeHistoryInstance(context): Promise { const { rootState } = bridgeActionContext(context); const etherscanApiKey = rootState.wallet.settings.apiKeys?.etherscan; @@ -99,8 +117,8 @@ const actions = defineActions({ return ethBridgeHistory; }, - // TODO: Need to restore transactions for all networks - async updateHistory(context): Promise { + + async updateHistory(context, setHistoryCallback?: VoidFunction): Promise { const { commit, state, dispatch, rootState, rootGetters } = bridgeActionContext(context); if (state.historyLoading) return; @@ -114,7 +132,7 @@ const actions = defineActions({ rootGetters.web3.contractAddress(key) ); const contracts = compact(contractsArray); - const updateCallback = () => commit.setHistory(); + const updateCallback = setHistoryCallback || (() => commit.setHistory()); await bridgeHistory.updateAccountHistory(address, assets, networkFees, contracts, updateCallback); diff --git a/src/store/settings/actions.ts b/src/store/settings/actions.ts index 1d0f1f66e..8643f8e31 100644 --- a/src/store/settings/actions.ts +++ b/src/store/settings/actions.ts @@ -26,7 +26,7 @@ async function updateNetworkChainGenesisHash(context: ActionContext): const actions = defineActions({ async connectToNode(context, options: ConnectToNodeOptions = {}): Promise { - const { dispatch, commit, state, rootState, getters } = settingsActionContext(context); + const { dispatch, commit, state, rootDispatch, rootState, getters } = settingsActionContext(context); if (!state.nodeConnectionAllowance) return; const { node, onError, currentNodeIndex = 0, ...restOptions } = options; @@ -39,7 +39,10 @@ const actions = defineActions({ // wallet init & update flow if (!rootState.wallet.settings.isWalletLoaded) { try { - await initWallet({ permissions: WalletPermissions }); + await initWallet({ + permissions: WalletPermissions, + updateEthBridgeHistory: rootDispatch.bridge.updateEthBridgeHistory, + }); } catch (error) { console.error(error); throw error; From 8d663166e5c10a62ab86465b51407322fd8032da Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Tue, 6 Sep 2022 17:33:19 +0300 Subject: [PATCH 003/109] import EthBridgeStates from wallet --- src/store/bridge/actions.ts | 5 ++++- src/utils/bridge/eth/history.ts | 11 +++++++++-- src/utils/bridge/eth/types.ts | 11 ----------- src/utils/bridge/index.ts | 9 +++++---- src/views/BridgeTransaction.vue | 3 ++- 5 files changed, 20 insertions(+), 19 deletions(-) delete mode 100644 src/utils/bridge/eth/types.ts diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index 6d7bcfe2b..853b28cf7 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -1,6 +1,8 @@ import compact from 'lodash/fp/compact'; import { defineActions } from 'direct-vuex'; import { ethers } from 'ethers'; + +import { WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; import { BridgeCurrencyType, BridgeHistory, BridgeNetworks, FPNumber, Operation } from '@sora-substrate/util'; import type { ActionContext } from 'vuex'; import type { AccountBalance } from '@sora-substrate/util/build/assets/types'; @@ -10,13 +12,14 @@ import { bridgeActionContext } from '@/store/bridge'; import { MaxUint256 } from '@/consts'; import { TokenBalanceSubscriptions } from '@/utils/subscriptions'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; -import { ETH_BRIDGE_STATES } from '@/utils/bridge/eth/types'; import { waitForApprovedRequest } from '@/utils/bridge/eth/utils'; import { EthBridgeHistory } from '@/utils/bridge/eth/history'; import ethersUtil, { ABI, KnownBridgeAsset, OtherContractType } from '@/utils/ethers-util'; import { isEthereumAddress } from '@/utils'; import type { SignTxResult } from './types'; +const { ETH_BRIDGE_STATES } = WALLET_CONSTS; + const balanceSubscriptions = new TokenBalanceSubscriptions(); function checkEvmNetwork(context: ActionContext): void { diff --git a/src/utils/bridge/eth/history.ts b/src/utils/bridge/eth/history.ts index a214bd577..4cef8b054 100644 --- a/src/utils/bridge/eth/history.ts +++ b/src/utils/bridge/eth/history.ts @@ -3,13 +3,18 @@ import first from 'lodash/fp/first'; import last from 'lodash/fp/last'; import { ethers } from 'ethers'; import { BridgeNetworks, BridgeTxStatus, Operation } from '@sora-substrate/util'; -import { SubqueryExplorerService, historyElementsFilter, SUBQUERY_TYPES, api } from '@soramitsu/soraneo-wallet-web'; +import { + api, + historyElementsFilter, + SubqueryExplorerService, + SUBQUERY_TYPES, + WALLET_CONSTS, +} from '@soramitsu/soraneo-wallet-web'; import ethersUtil from '@/utils/ethers-util'; import { getEvmTransactionRecieptByHash } from '@/utils/bridge/utils'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; -import { ETH_BRIDGE_STATES } from '@/utils/bridge/eth/types'; import { isOutgoingTransaction } from '@/utils/bridge/eth/utils'; import { ZeroStringValue } from '@/consts'; @@ -17,6 +22,8 @@ import { ZeroStringValue } from '@/consts'; import type { BridgeHistory, NetworkFeesObject } from '@sora-substrate/util'; import type { RegisteredAccountAssetObject } from '@/store/assets/types'; +const { ETH_BRIDGE_STATES } = WALLET_CONSTS; + type TimestampMap = { [key: number]: T; }; diff --git a/src/utils/bridge/eth/types.ts b/src/utils/bridge/eth/types.ts deleted file mode 100644 index 6a5876915..000000000 --- a/src/utils/bridge/eth/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum ETH_BRIDGE_STATES { - INITIAL = 'INITIAL', - SORA_SUBMITTED = 'SORA_SUBMITTED', - SORA_PENDING = 'SORA_PENDING', - SORA_REJECTED = 'SORA_REJECTED', - SORA_COMMITED = 'SORA_COMMITED', - EVM_SUBMITTED = 'EVM_SUBMITTED', - EVM_PENDING = 'EVM_PENDING', - EVM_REJECTED = 'EVM_REJECTED', - EVM_COMMITED = 'EVM_COMMITED', -} diff --git a/src/utils/bridge/index.ts b/src/utils/bridge/index.ts index 2741b7704..fd03da47f 100644 --- a/src/utils/bridge/index.ts +++ b/src/utils/bridge/index.ts @@ -1,13 +1,12 @@ import first from 'lodash/fp/first'; import { BridgeTxStatus, Operation } from '@sora-substrate/util'; -import { SUBQUERY_TYPES } from '@soramitsu/soraneo-wallet-web'; +import { SUBQUERY_TYPES, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; import { ethers } from 'ethers'; import store from '@/store'; import { getEvmTransactionRecieptByHash } from '@/utils/bridge/utils'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; -import { ETH_BRIDGE_STATES } from '@/utils/bridge/eth/types'; import { getTransaction, waitForApprovedRequest, @@ -22,10 +21,12 @@ import type { EthBridgeHistory } from '@/utils/bridge/eth/history'; import type { SignTxResult } from '@/store/bridge/types'; import type { RegisteredAccountAssetWithDecimals } from '@/store/assets/types'; +const { ETH_BRIDGE_STATES } = WALLET_CONSTS; + type HandleTransactionPayload = { status?: BridgeTxStatus; - nextState: ETH_BRIDGE_STATES; - rejectState: ETH_BRIDGE_STATES; + nextState: WALLET_CONSTS.ETH_BRIDGE_STATES; + rejectState: WALLET_CONSTS.ETH_BRIDGE_STATES; handler?: (id: string) => Promise; }; diff --git a/src/views/BridgeTransaction.vue b/src/views/BridgeTransaction.vue index db4383646..6ba773bc2 100644 --- a/src/views/BridgeTransaction.vue +++ b/src/views/BridgeTransaction.vue @@ -305,10 +305,11 @@ import { Components, PageNames } from '@/consts'; import { action, state, getter, mutation } from '@/store/decorators'; import { hasInsufficientBalance, hasInsufficientXorForFee, hasInsufficientEvmNativeTokenForFee } from '@/utils'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; -import { ETH_BRIDGE_STATES } from '@/utils/bridge/eth/types'; import { isOutgoingTransaction, isUnsignedFromPart } from '@/utils/bridge/eth/utils'; import type { RegisteredAccountAssetWithDecimals } from '@/store/assets/types'; +const { ETH_BRIDGE_STATES } = WALLET_CONSTS; + const FORMATTED_HASH_LENGTH = 24; @Component({ From 51c6bad5386ad08eb8059a1490b3b0818ca7ba26 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Tue, 6 Sep 2022 17:41:14 +0300 Subject: [PATCH 004/109] add eth hashi bridge translations --- src/lang/en.json | 12 +++++++++--- src/lang/messages.ts | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 0361aa629..5a72fb9e0 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -49,7 +49,9 @@ "DemeterFarmingWithdrawLiquidity": "Removed {symbol} and {symbol2} {amount} LP tokens", "DemeterFarmingStakeToken": "Added {amount} {symbol}", "DemeterFarmingUnstakeToken": "Removed {amount} {symbol}", - "DemeterFarmingGetRewards": "{amount} {symbol} claimed successfully" + "DemeterFarmingGetRewards": "{amount} {symbol} claimed successfully", + "EthBridgeIncoming": "Transfered {amount} {symbol} from Ethereum to SORA", + "EthBridgeOutgoing": "Transfered {amount} {symbol} from SORA to Ethereum" }, "error": { "Transfer": "Failed to {action} {amount} {symbol} {direction} {address}", @@ -67,7 +69,9 @@ "DemeterFarmingWithdrawLiquidity": "Failed to remove {amount} {symbol} and {symbol2} LP tokens", "DemeterFarmingStakeToken": "Failed to add {amount} {symbol}", "DemeterFarmingUnstakeToken": "Failed to remove {amount} {symbol}", - "DemeterFarmingGetRewards": "Failed to claim {symbol}" + "DemeterFarmingGetRewards": "Failed to claim {symbol}", + "EthBridgeIncoming": "Failed to transfer {amount} {symbol} from Ethereum to SORA", + "EthBridgeOutgoing": "Failed to transfer {amount} {symbol} from SORA to Ethereum" }, "ReferralReserveXor": "Bond XOR", "ReferralUnreserveXor": "Unbond XOR", @@ -77,7 +81,9 @@ "DemeterFarmingWithdrawLiquidity": "Unstake Liquidity", "DemeterFarmingStakeToken": "Stake Token", "DemeterFarmingUnstakeToken": "Unstake Token", - "DemeterFarmingGetRewards": "Claim Rewards" + "DemeterFarmingGetRewards": "Claim Rewards", + "EthBridgeIncoming": "Hashi Bridge", + "EthBridgeOutgoing": "Hashi Bridge" }, "polkadotjs": { "noExtensions": "No Polkadot.js extension was found. Please install it and reload this page\nhttps:\/\/polkadot.js.org\/extension\/", diff --git a/src/lang/messages.ts b/src/lang/messages.ts index 3bae6725f..b016e006e 100644 --- a/src/lang/messages.ts +++ b/src/lang/messages.ts @@ -197,6 +197,8 @@ export default { [Operation.DemeterFarmingStakeToken]: 'Add Stake', [Operation.DemeterFarmingUnstakeToken]: 'Remove Stake', [Operation.DemeterFarmingGetRewards]: 'Claim Rewards', + [Operation.EthBridgeIncoming]: 'Hashi Bridge', + [Operation.EthBridgeOutgoing]: 'Hashi Bridge', andText: 'and', [TransactionStatus.Finalized]: { [Operation.Transfer]: '{action} {amount} {symbol} {direction} {address}', @@ -214,6 +216,8 @@ export default { [Operation.DemeterFarmingStakeToken]: 'Added {amount} {symbol}', [Operation.DemeterFarmingUnstakeToken]: 'Removed {amount} {symbol}', [Operation.DemeterFarmingGetRewards]: '{amount} {symbol} claimed successfully', + [Operation.EthBridgeIncoming]: 'Transfered {amount} {symbol} from Ethereum to SORA', + [Operation.EthBridgeOutgoing]: 'Transfered {amount} {symbol} from SORA to Ethereum', }, [TransactionStatus.Error]: { [Operation.Transfer]: 'Failed to send {amount} {symbol} to {address}', @@ -231,6 +235,8 @@ export default { [Operation.DemeterFarmingStakeToken]: 'Failed to add {amount} {symbol}', [Operation.DemeterFarmingUnstakeToken]: 'Failed to remove {amount} {symbol}', [Operation.DemeterFarmingGetRewards]: 'Failed to claim {symbol}', + [Operation.EthBridgeIncoming]: 'Failed to transfer {amount} {symbol} from Ethereum to SORA', + [Operation.EthBridgeOutgoing]: 'Failed to transfer {amount} {symbol} from SORA to Ethereum', }, }, pageNotFound: { From 0f7981c23d35668f1e26ea1edbc39ad82822495b Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Wed, 7 Sep 2022 13:18:41 +0300 Subject: [PATCH 005/109] transfer updateEthBridgeHistory function to settings --- src/store/bridge/actions.ts | 19 ++--------------- src/store/index.ts | 2 +- src/store/settings/actions.ts | 40 ++++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index 853b28cf7..b7a4b4cfa 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -94,23 +94,7 @@ const actions = defineActions({ commit.setEvmBlockNumber(blockNumber); }, - // For wallet - async updateEthBridgeHistory(context, setHistoryCallback?: VoidFunction): Promise { - const { commit, dispatch, rootState, rootGetters } = bridgeActionContext(context); - - const bridgeHistory = await dispatch.getEthBridgeHistoryInstance(); - const address = rootState.wallet.account.address; - const assets = rootGetters.assets.assetsDataTable; - const networkFees = rootState.wallet.settings.networkFees; - const contractsArray = Object.values(KnownBridgeAsset).map>((key) => - rootGetters.web3.contractAddress(key) - ); - const contracts = compact(contractsArray); - const updateCallback = setHistoryCallback || (() => commit.setHistory()); - - await bridgeHistory.updateAccountHistory(address, assets, networkFees, contracts, updateCallback); - }, - + // Deprecated async getEthBridgeHistoryInstance(context): Promise { const { rootState } = bridgeActionContext(context); const etherscanApiKey = rootState.wallet.settings.apiKeys?.etherscan; @@ -121,6 +105,7 @@ const actions = defineActions({ return ethBridgeHistory; }, + // TODO: EVM Bridge History update async updateHistory(context, setHistoryCallback?: VoidFunction): Promise { const { commit, state, dispatch, rootState, rootGetters } = bridgeActionContext(context); if (state.historyLoading) return; diff --git a/src/store/index.ts b/src/store/index.ts index 8de79c9f4..15bd2d8e4 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -77,6 +77,6 @@ const localGetterContext = (args: any, moduleNam }; }; -export { modules, localGetterContext, localActionContext }; +export { modules, localGetterContext, localActionContext, rootGetterContext, rootActionContext }; export default store; diff --git a/src/store/settings/actions.ts b/src/store/settings/actions.ts index 8643f8e31..820815e62 100644 --- a/src/store/settings/actions.ts +++ b/src/store/settings/actions.ts @@ -1,17 +1,55 @@ +import compact from 'lodash/fp/compact'; import { defineActions } from 'direct-vuex'; import { initWallet, connection, api } from '@soramitsu/soraneo-wallet-web'; import type { ActionContext } from 'vuex'; +import { rootActionContext } from '@/store'; import { settingsActionContext } from '@/store/settings'; import { Language, WalletPermissions } from '@/consts'; import { getSupportedLocale, setDayJsLocale, setI18nLocale } from '@/lang'; import { updateDocumentTitle, updateFpNumberLocale } from '@/utils'; import { AppHandledError } from '@/utils/error'; import { fetchRpc, getRpcEndpoint } from '@/utils/rpc'; +import { KnownBridgeAsset } from '@/utils/ethers-util'; +import { EthBridgeHistory } from '@/utils/bridge/eth/history'; import type { ConnectToNodeOptions, Node } from '@/types/nodes'; const NODE_CONNECTION_TIMEOUT = 60_000; +/** + * Restore ETH bridge account transactions, using Subquery & Etherscan + * @param context store context + */ +const updateEthBridgeHistory = + (context: ActionContext) => + async (setHistoryCallback?: VoidFunction): Promise => { + try { + const { rootState, rootGetters } = rootActionContext(context); + + const { + wallet: { + account: { address }, + settings: { + apiKeys: { etherscan: etherscanApiKey }, + networkFees, + }, + }, + } = rootState; + + const assets = rootGetters.assets.assetsDataTable; + const contracts = compact( + Object.values(KnownBridgeAsset).map>((key) => rootGetters.web3.contractAddress(key)) + ); + + const ethBridgeHistory = new EthBridgeHistory(etherscanApiKey); + + await ethBridgeHistory.init(); + await ethBridgeHistory.updateAccountHistory(address, assets, networkFees, contracts, setHistoryCallback); + } catch (error) { + console.error(error); + } + }; + async function updateNetworkChainGenesisHash(context: ActionContext): Promise { const { commit, state } = settingsActionContext(context); try { @@ -41,7 +79,7 @@ const actions = defineActions({ try { await initWallet({ permissions: WalletPermissions, - updateEthBridgeHistory: rootDispatch.bridge.updateEthBridgeHistory, + updateEthBridgeHistory: updateEthBridgeHistory(context), }); } catch (error) { console.error(error); From e538ac42df6f45714afd316cc0268c040dd48e65 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Wed, 7 Sep 2022 16:55:16 +0300 Subject: [PATCH 006/109] wip web3 refactoring --- src/consts/index.ts | 34 ---------- src/store/assets/actions.ts | 16 +++-- src/store/bridge/actions.ts | 5 +- src/store/web3/actions.ts | 74 +++------------------ src/store/web3/mutations.ts | 4 -- src/store/web3/state.ts | 3 +- src/utils/ethers-util.ts | 129 ++++++++++++++++++++++++++++-------- src/utils/index.ts | 4 +- src/views/Bridge.vue | 4 +- 9 files changed, 128 insertions(+), 145 deletions(-) diff --git a/src/consts/index.ts b/src/consts/index.ts index e0fe4698c..53f2656dd 100644 --- a/src/consts/index.ts +++ b/src/consts/index.ts @@ -1,11 +1,9 @@ import invert from 'lodash/fp/invert'; -import { KnownAssets, KnownSymbols, XOR } from '@sora-substrate/util/build/assets/consts'; import { LiquiditySourceTypes } from '@sora-substrate/liquidity-proxy/build/consts'; import { DemeterPageNames } from '@/modules/demeterFarming/consts'; import pkg from '../../package.json'; -import { KnownBridgeAsset } from '../utils/ethers-util'; export const app = { version: pkg.version, @@ -333,38 +331,6 @@ export enum EvmSymbol { VT = 'VT', } -const gasLimit = { - approve: 70000, - sendERC20ToSidechain: 86000, - sendEthToSidechain: 50000, - mintTokensByPeers: 255000, - receiveByEthereumAssetAddress: 250000, - receiveBySidechainAssetId: 255000, -}; -/** - * It's in gwei. - * Zero index means ETH -> SORA - * First index means SORA -> ETH - */ -export const EthereumGasLimits = [ - // ETH -> SORA - { - [XOR.address]: gasLimit.approve + gasLimit.sendERC20ToSidechain, - [KnownAssets.get(KnownSymbols.VAL).address]: gasLimit.approve + gasLimit.sendERC20ToSidechain, - [KnownAssets.get(KnownSymbols.PSWAP).address]: gasLimit.approve + gasLimit.sendERC20ToSidechain, - [KnownAssets.get(KnownSymbols.ETH).address]: gasLimit.sendEthToSidechain, - [KnownBridgeAsset.Other]: gasLimit.approve + gasLimit.sendERC20ToSidechain, - }, - // SORA -> ETH - { - [XOR.address]: gasLimit.mintTokensByPeers, - [KnownAssets.get(KnownSymbols.VAL).address]: gasLimit.mintTokensByPeers, - [KnownAssets.get(KnownSymbols.PSWAP).address]: gasLimit.receiveBySidechainAssetId, - [KnownAssets.get(KnownSymbols.ETH).address]: gasLimit.receiveByEthereumAssetAddress, - [KnownBridgeAsset.Other]: gasLimit.receiveByEthereumAssetAddress, - }, -]; - export const MaxUint256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; export const EthAddress = '0x0000000000000000000000000000000000000000'; diff --git a/src/store/assets/actions.ts b/src/store/assets/actions.ts index cde8f15d1..e537c4b86 100644 --- a/src/store/assets/actions.ts +++ b/src/store/assets/actions.ts @@ -1,5 +1,6 @@ import { defineActions } from 'direct-vuex'; +import ethersUtil from '@/utils/ethers-util'; import { assetsActionContext } from '@/store/assets'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; import { ZeroStringValue } from '@/consts'; @@ -17,7 +18,7 @@ const DISABLED_ASSETS_FOR_BRIDGE = [ const actions = defineActions({ async updateRegisteredAssets(context, reset?: boolean): Promise { - const { state, commit, rootDispatch } = assetsActionContext(context); + const { state, commit, rootCommit, rootDispatch, rootState } = assetsActionContext(context); if (state.registeredAssetsFetching) return; @@ -26,6 +27,7 @@ const actions = defineActions({ commit.setRegisteredAssetsFetching(true); try { + const accountAddress = rootState.web3.evmAddress; const registeredAssets = await ethBridgeApi.getRegisteredAssets(); const enabledRegisteredAssets = registeredAssets.filter( (item) => !DISABLED_ASSETS_FOR_BRIDGE.includes(item.address) @@ -39,11 +41,17 @@ const actions = defineActions({ accountAsset.externalAddress = await rootDispatch.web3.getEvmTokenAddressByAssetId(item.address); } if (accountAsset.externalAddress) { - const { value, decimals } = await rootDispatch.web3.getBalanceByEvmAddress(accountAsset.externalAddress); + const { value, decimals } = await ethersUtil.getAccountAssetBalance( + accountAddress, + accountAsset.externalAddress + ); + accountAsset.externalBalance = value; + accountAsset.externalDecimals = decimals; - if (!accountAsset.externalDecimals) { - accountAsset.externalDecimals = decimals; + // update evmBalance + if (ethersUtil.isNativeEvmTokenAddress(accountAsset.externalAddress)) { + rootCommit.web3.setEvmBalance(value); } } } catch (error) { diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index b7a4b4cfa..34df8825d 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -15,7 +15,6 @@ import { ethBridgeApi } from '@/utils/bridge/eth/api'; import { waitForApprovedRequest } from '@/utils/bridge/eth/utils'; import { EthBridgeHistory } from '@/utils/bridge/eth/history'; import ethersUtil, { ABI, KnownBridgeAsset, OtherContractType } from '@/utils/ethers-util'; -import { isEthereumAddress } from '@/utils'; import type { SignTxResult } from './types'; const { ETH_BRIDGE_STATES } = WALLET_CONSTS; @@ -256,11 +255,11 @@ const actions = defineActions({ const ethersInstance = await ethersUtil.getEthersInstance(); const contractAddress = rootGetters.web3.contractAddress(KnownBridgeAsset.Other) as string; - const isNativeEvmToken = isEthereumAddress(asset.externalAddress); + const isNativeEvmToken = ethersUtil.isNativeEvmTokenAddress(asset.externalAddress); // don't check allowance for native EVM token if (!isNativeEvmToken) { - const allowance = await rootDispatch.web3.getAllowanceByEvmAddress(asset.externalAddress); + const allowance = await ethersUtil.getAllowance(evmAccount, contractAddress, asset.externalAddress); if (FPNumber.lte(new FPNumber(allowance), new FPNumber(tx.amount))) { commit.addTxIdInApprove(tx.id); const tokenInstance = new ethers.Contract( diff --git a/src/store/web3/actions.ts b/src/store/web3/actions.ts index 17a7414a4..77b9723cd 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -1,11 +1,10 @@ import { defineActions } from 'direct-vuex'; -import { BridgeNetworks, FPNumber, CodecString } from '@sora-substrate/util'; +import { BridgeNetworks } from '@sora-substrate/util'; import { ethers } from 'ethers'; import type { ActionContext } from 'vuex'; import { web3ActionContext } from '@/store/web3'; import ethersUtil, { - ABI, Contract, ContractNetwork, EvmNetworkTypeName, @@ -13,8 +12,7 @@ import ethersUtil, { OtherContractType, SubNetwork, } from '@/utils/ethers-util'; -import { ZeroStringValue } from '@/consts'; -import { isEthereumAddress } from '@/utils'; + import type { Provider } from '@/utils/ethers-util'; async function setEthSmartContracts(context: ActionContext, network: SubNetwork): Promise { @@ -58,16 +56,18 @@ const actions = defineActions({ const address = await ethersUtil.onConnect({ provider }); commit.setEvmAddress(address); }, - async setEvmNetworkType(context, network?: string): Promise { + + async setEvmNetworkType(context, networkHex?: string): Promise { const { commit } = web3ActionContext(context); let networkType = ''; - if (!network) { + if (!networkHex) { networkType = await ethersUtil.getEvmNetworkType(); } else { - networkType = EvmNetworkTypeName[network]; + networkType = EvmNetworkTypeName[networkHex]; } commit.setNetworkType(networkType); }, + async setSmartContracts(context, subNetworks: Array): Promise { for (const network of subNetworks) { switch (network.id) { @@ -81,51 +81,8 @@ const actions = defineActions({ } } }, - async getEvmBalance(context): Promise { - const { commit, state } = web3ActionContext(context); - let value = ZeroStringValue; - try { - const address = state.evmAddress; - if (address) { - const ethersInstance = await ethersUtil.getEthersInstance(); - const wei = await ethersInstance.getBalance(address); - const balance = ethers.utils.formatEther(wei.toString()); - value = new FPNumber(balance).toCodecString(); - } - } catch (error) { - console.error(error); - } - - commit.setEvmBalance(value); - return value; - }, - async getBalanceByEvmAddress(context, assetAddress: string): Promise<{ value: CodecString; decimals: number }> { - const { dispatch, state } = web3ActionContext(context); - let value = ZeroStringValue; - let decimals = 18; - const account = state.evmAddress; - if (!account) { - return { value, decimals }; - } - try { - const ethersInstance = await ethersUtil.getEthersInstance(); - const isNativeEvmToken = isEthereumAddress(assetAddress); - if (isNativeEvmToken) { - value = await dispatch.getEvmBalance(); - } else { - const tokenInstance = new ethers.Contract(assetAddress, ABI.balance, ethersInstance.getSigner()); - const methodArgs = [account]; - const balance = await tokenInstance.balanceOf(...methodArgs); - decimals = await tokenInstance.decimals(); - value = FPNumber.fromCodecValue(balance._hex, +decimals).toCodecString(); - } - } catch (error) { - // We've decided not to show errors - } - - return { value, decimals }; - }, + // TODO [EVM] async getEvmTokenAddressByAssetId(context, soraAssetId: string): Promise { const { getters } = web3ActionContext(context); try { @@ -148,21 +105,6 @@ const actions = defineActions({ return ''; } }, - async getAllowanceByEvmAddress(context, address: string): Promise { - const { getters, state } = web3ActionContext(context); - try { - const contractAddress = getters.contractAddress(KnownBridgeAsset.Other); - const ethersInstance = await ethersUtil.getEthersInstance(); - const tokenInstance = new ethers.Contract(address, ABI.allowance, ethersInstance.getSigner()); - const account = state.evmAddress; - const methodArgs = [account, contractAddress]; - const allowance = await tokenInstance.allowance(...methodArgs); - return FPNumber.fromCodecValue(allowance._hex).toString(); - } catch (error) { - console.error(error); - throw error; - } - }, }); export default actions; diff --git a/src/store/web3/mutations.ts b/src/store/web3/mutations.ts index 0993bd216..c2c9dadf5 100644 --- a/src/store/web3/mutations.ts +++ b/src/store/web3/mutations.ts @@ -2,7 +2,6 @@ import Vue from 'vue'; import { defineMutations } from 'direct-vuex'; import { BridgeNetworks, CodecString } from '@sora-substrate/util'; -import ethersUtil from '@/utils/ethers-util'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; import { initialState } from './state'; import type { SubNetwork } from '@/utils/ethers-util'; @@ -22,15 +21,12 @@ const mutations = defineMutations()({ }, setEvmAddress(state, address: string): void { state.evmAddress = address; - ethersUtil.storeEvmUserAddress(address); }, resetEvmAddress(state): void { state.evmAddress = ''; - ethersUtil.removeEvmUserAddress(); }, setNetworkType(state, networkType: string): void { state.networkType = networkType; - ethersUtil.storeEvmNetworkType(networkType); }, setSubNetworks(state, subNetworks: Array): void { state.subNetworks = subNetworks; diff --git a/src/store/web3/state.ts b/src/store/web3/state.ts index 919706c87..42a3fd5a7 100644 --- a/src/store/web3/state.ts +++ b/src/store/web3/state.ts @@ -1,12 +1,11 @@ import { BridgeNetworks } from '@sora-substrate/util'; import { ZeroStringValue } from '@/consts'; -import ethersUtil from '@/utils/ethers-util'; import type { Web3State } from './types'; export function initialState(): Web3State { return { - evmAddress: ethersUtil.getEvmUserAddress(), + evmAddress: '', evmBalance: ZeroStringValue, networkType: '', subNetworks: [], diff --git a/src/utils/ethers-util.ts b/src/utils/ethers-util.ts index 99a0e7a33..22625ce8b 100644 --- a/src/utils/ethers-util.ts +++ b/src/utils/ethers-util.ts @@ -3,11 +3,12 @@ import WalletConnectProvider from '@walletconnect/web3-provider'; import detectEthereumProvider from '@metamask/detect-provider'; import { decodeAddress } from '@polkadot/util-crypto'; import { FPNumber } from '@sora-substrate/util'; +import { XOR, VAL, PSWAP, ETH } from '@sora-substrate/util/build/assets/consts'; + import type { BridgeNetworks, CodecString } from '@sora-substrate/util'; -import axiosInstance from '../api'; -import storage from './storage'; -import { EthereumGasLimits } from '@/consts'; +import axiosInstance from '@/api'; +import { ZeroStringValue } from '@/consts'; type AbiType = 'function' | 'constructor' | 'event' | 'fallback'; type StateMutabilityType = 'pure' | 'view' | 'nonpayable' | 'payable'; @@ -120,6 +121,7 @@ export enum EvmNetworkType { Goerli = 'goerli', Private = 'private', EWC = 'EWC', + Mordor = 'mordor', // Ethereum Classic } export interface SubNetwork { @@ -175,12 +177,48 @@ interface JsonContract { }; } +// TODO [EVM] +const gasLimit = { + approve: 70000, + sendERC20ToSidechain: 86000, + sendEthToSidechain: 50000, + mintTokensByPeers: 255000, + receiveByEthereumAssetAddress: 250000, + receiveBySidechainAssetId: 255000, +}; + +/** + * It's in gwei. + * Zero index means ETH -> SORA + * First index means SORA -> ETH + */ +// TODO [EVM] +const EthereumGasLimits = [ + // ETH -> SORA + { + [XOR.address]: gasLimit.approve + gasLimit.sendERC20ToSidechain, + [VAL.address]: gasLimit.approve + gasLimit.sendERC20ToSidechain, + [PSWAP.address]: gasLimit.approve + gasLimit.sendERC20ToSidechain, + [ETH.address]: gasLimit.sendEthToSidechain, + [KnownBridgeAsset.Other]: gasLimit.approve + gasLimit.sendERC20ToSidechain, + }, + // SORA -> ETH + { + [XOR.address]: gasLimit.mintTokensByPeers, + [VAL.address]: gasLimit.mintTokensByPeers, + [PSWAP.address]: gasLimit.receiveBySidechainAssetId, + [ETH.address]: gasLimit.receiveByEthereumAssetAddress, + [KnownBridgeAsset.Other]: gasLimit.receiveByEthereumAssetAddress, + }, +]; + export const EvmNetworkTypeName = { '0x1': EvmNetworkType.Mainnet, '0x3': EvmNetworkType.Ropsten, '0x2a': EvmNetworkType.Kovan, '0x4': EvmNetworkType.Rinkeby, '0x5': EvmNetworkType.Goerli, + '0x3f': EvmNetworkType.Mordor, // Ethereum Classic '0x12047': EvmNetworkType.Private, }; @@ -215,6 +253,55 @@ async function getAccount(): Promise { return account.getAddress(); } +async function getAccountBalance(accountAddress: string): Promise { + try { + const ethersInstance = await getEthersInstance(); + const wei = await ethersInstance.getBalance(accountAddress); + const balance = ethers.utils.formatEther(wei.toString()); + return new FPNumber(balance).toCodecString(); + } catch (error) { + console.error(error); + return ZeroStringValue; + } +} + +async function getAccountAssetBalance( + accountAddress: string, + assetAddress: string +): Promise<{ value: CodecString; decimals: number }> { + let value = ZeroStringValue; + let decimals = 18; + + if (accountAddress && assetAddress) { + try { + const ethersInstance = await getEthersInstance(); + const isNativeEvmToken = isNativeEvmTokenAddress(assetAddress); + if (isNativeEvmToken) { + value = await getAccountBalance(accountAddress); + } else { + const tokenInstance = new ethers.Contract(assetAddress, ABI.balance, ethersInstance.getSigner()); + const methodArgs = [accountAddress]; + const balance = await tokenInstance.balanceOf(...methodArgs); + decimals = await tokenInstance.decimals(); + value = FPNumber.fromCodecValue(balance._hex, +decimals).toCodecString(); + } + } catch (error) { + console.error(error); + } + } + + return { value, decimals }; +} + +async function getAllowance(accountAddress: string, contractAddress: string, assetAddress: string): Promise { + const ethersInstance = await getEthersInstance(); + const tokenInstance = new ethers.Contract(assetAddress, ABI.allowance, ethersInstance.getSigner()); + const methodArgs = [accountAddress, contractAddress]; + const allowance = await tokenInstance.allowance(...methodArgs); + + return FPNumber.fromCodecValue(allowance._hex).toString(); +} + // TODO: remove this check, when MetaMask issue will be resolved // https://github.com/MetaMask/metamask-extension/issues/10368 async function checkAccountIsConnected(address: string): Promise { @@ -289,26 +376,6 @@ async function addToken(address: string, symbol: string, decimals: number, image } } -function storeEvmUserAddress(address: string): void { - storage.set('evmAddress', address); -} - -function getEvmUserAddress(): string { - return storage.get('evmAddress') || ''; -} - -function removeEvmUserAddress(): void { - storage.remove('evmAddress'); -} - -function storeEvmNetworkType(network: string): void { - storage.set('evmNetworkType', EvmNetworkTypeName[network] || network); -} - -function removeEvmNetworkType(): void { - storage.remove('evmNetworkType'); -} - async function getEvmNetworkType(): Promise { const ethersInstance = await getEthersInstance(); const network = await ethersInstance.getNetwork(); @@ -373,17 +440,22 @@ async function accountAddressToHex(address: string): Promise { return ethers.utils.hexlify(Array.from(decodeAddress(address).values())); } +function isNativeEvmTokenAddress(address: string): boolean { + const numberString = address.replace(/^0x/, ''); + const number = parseInt(numberString, 16); + + return number === 0; +} + export default { onConnect, getAccount, + getAccountBalance, + getAccountAssetBalance, + getAllowance, checkAccountIsConnected, - storeEvmUserAddress, - getEvmUserAddress, - storeEvmNetworkType, getEvmNetworkType, - removeEvmNetworkType, getEthersInstance, - removeEvmUserAddress, watchEthereum, readSmartContract, accountAddressToHex, @@ -394,4 +466,5 @@ export default { getEvmTransactionReceipt, getBlock, addToken, + isNativeEvmTokenAddress, }; diff --git a/src/utils/index.ts b/src/utils/index.ts index 8d2a94d34..af297171e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -28,7 +28,7 @@ export const isXorAccountAsset = (asset: Asset | AccountAsset | RegisteredAccoun return asset ? asset.address === XOR.address : false; }; -export const isEthereumAddress = (address: string): boolean => { +export const isNativeEvmTokenAddress = (address: string): boolean => { const numberString = address.replace(/^0x/, ''); const number = parseInt(numberString, 16); @@ -71,7 +71,7 @@ const getMaxBalance = ( if ( !asZeroValue(fee) && ((!isExternalBalance && isXorAccountAsset(asset)) || - (isExternalBalance && isEthereumAddress((asset as RegisteredAccountAsset).externalAddress))) && + (isExternalBalance && isNativeEvmTokenAddress((asset as RegisteredAccountAsset).externalAddress))) && !isBondedBalance ) { const fpFee = FPNumber.fromCodecValue(fee); diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index 330c7d672..fb49dc2cc 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -270,8 +270,8 @@ import { getMaxValue, getAssetBalance, asZeroValue, - isEthereumAddress, } from '@/utils'; +import ethersUtil from '@/utils/ethers-util'; import type { SubNetwork } from '@/utils/ethers-util'; import type { RegisterAssetWithExternalBalance, RegisteredAccountAssetWithDecimals } from '@/store/assets/types'; @@ -377,7 +377,7 @@ export default class Bridge extends Mixins( const fpFee = this.getFPNumberFromCodec(this.soraNetworkFee, decimals); return !FPNumber.eq(fpFee, fpBalance.sub(fpAmount)) && FPNumber.gt(fpBalance, fpFee); } - if (isEthereumAddress(this.asset.externalAddress) && !this.isSoraToEvm) { + if (ethersUtil.isNativeEvmTokenAddress(this.asset.externalAddress) && !this.isSoraToEvm) { const fpFee = this.getFPNumberFromCodec(this.evmNetworkFee); return !FPNumber.eq(fpFee, fpBalance.sub(fpAmount)) && FPNumber.gt(fpBalance, fpFee); } From da1e7c3dccfe1580e1adea26f16c7d9f0d0d5b9e Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Thu, 8 Sep 2022 17:14:41 +0300 Subject: [PATCH 007/109] wip update registered assets --- public/env.json | 56 ++--------------- .../Bridge/TransferNotification.vue | 8 +-- .../Moonpay/MoonpayBridgeInitMixin.ts | 13 ++-- .../SelectAsset/SelectRegisteredAsset.vue | 26 ++++---- src/components/SelectAsset/SelectToken.vue | 22 ++----- src/components/mixins/AssetsSearchMixin.ts | 10 ++- src/components/mixins/SelectAssetMixin.ts | 51 +++++----------- src/store/assets/actions.ts | 61 ++++++++----------- src/store/assets/getters.ts | 8 ++- src/store/assets/mutations.ts | 6 +- src/store/assets/state.ts | 2 +- src/store/assets/types.ts | 19 +++--- src/store/moonpay/actions.ts | 16 ----- src/store/web3/mutations.ts | 2 +- src/utils/bridge/evm/api.ts | 4 ++ src/utils/ethers-util.ts | 11 ++-- src/utils/index.ts | 38 +++++------- src/views/Bridge.vue | 4 +- src/views/BridgeContainer.vue | 12 ++-- src/views/BridgeTransactionsHistory.vue | 12 ++-- 20 files changed, 137 insertions(+), 244 deletions(-) create mode 100644 src/utils/bridge/evm/api.ts diff --git a/public/env.json b/public/env.json index 54954541b..a7913f6a5 100644 --- a/public/env.json +++ b/public/env.json @@ -15,61 +15,17 @@ { "chain": "SORA-dev Testnet #1", "name": "SORA", - "address": "wss://ws.framenode-1.s1.dev.sora2.soramitsu.co.jp" - }, - { - "chain": "SORA-dev Testnet #2", - "name": "SORA", - "address": "wss://ws.framenode-2.s2.dev.sora2.soramitsu.co.jp" - }, - { - "chain": "SORA-dev Testnet #3", - "name": "SORA", - "address": "wss://ws.framenode-3.s3.dev.sora2.soramitsu.co.jp" - }, - { - "chain": "SORA-dev Testnet #4", - "name": "SORA", - "address": "wss://ws.framenode-4.s3.dev.sora2.soramitsu.co.jp" - }, - { - "chain": "SORA-dev Testnet #5", - "name": "SORA", - "address": "wss://ws.framenode-5.s4.dev.sora2.soramitsu.co.jp" - }, - { - "chain": "SORA-dev Testnet #6", - "name": "SORA", - "address": "wss://ws.framenode-6.s4.dev.sora2.soramitsu.co.jp" + "address": "wss://ws.framenode-3.b1.stg1.sora2.soramitsu.co.jp" } ], "NETWORK_TYPE": "Dev", "CHAIN_GENESIS_HASH": "", "SUB_NETWORKS": [ { - "name": "ethereum", - "id": 0, - "symbol": "ETH", - "currency": "ETH", - "defaultType": "rinkeby", - "CONTRACTS": { - "XOR": { - "MASTER": "0x12c6a709925783f49fcca0b398d13b0d597e6e1c" - }, - "VAL": { - "MASTER": "0x47e229aa491763038f6a505b4f85d8eb463f0962" - }, - "OTHER": { - "MASTER": "0x24390c8f6cbd5d152c30226f809f4e3f153b88d4" - } - } - }, - { - "name": "energy", - "id": 1, - "symbol": "VT", - "currency": "VT", - "defaultType": "private", + "name": "classic", + "id": 63, + "symbol": "ETC", + "defaultType": "classicMordor", "CONTRACTS": { "XOR": { "MASTER": "" @@ -78,7 +34,7 @@ "MASTER": "" }, "OTHER": { - "MASTER": "0x1891b81AE0C5A81Ec84BC4f69322C6a01A3B3095" + "MASTER": "" } } } diff --git a/src/components/Bridge/TransferNotification.vue b/src/components/Bridge/TransferNotification.vue index abae74421..0c38e81ac 100644 --- a/src/components/Bridge/TransferNotification.vue +++ b/src/components/Bridge/TransferNotification.vue @@ -32,6 +32,7 @@ import { getter, state, mutation } from '@/store/decorators'; import type { BridgeHistory, RegisteredAccountAsset, RegisteredAsset } from '@sora-substrate/util'; import type { Whitelist } from '@sora-substrate/util/build/assets/types'; +import type { RegisteredAccountAssetWithDecimals } from '@/store/assets/types'; @Component({ components: { @@ -44,7 +45,7 @@ export default class TransferNotification extends Mixins(TranslationMixin) { @state.bridge.notificationData private tx!: Nullable; @getter.wallet.account.whitelist private whitelist!: Whitelist; - @getter.assets.assetDataByAddress private getAsset!: (addr?: string) => Nullable; + @getter.assets.assetDataByAddress private getAsset!: (addr?: string) => Nullable; @mutation.bridge.setNotificationData private setNotificationData!: (tx?: BridgeHistory) => void; @@ -58,7 +59,7 @@ export default class TransferNotification extends Mixins(TranslationMixin) { } } - get asset(): Nullable { + get asset(): Nullable { if (!this.tx?.assetAddress) return null; return this.getAsset(this.tx.assetAddress); @@ -79,8 +80,7 @@ export default class TransferNotification extends Mixins(TranslationMixin) { async addToken(): Promise { if (!this.asset) return; - const { externalAddress, externalDecimals, symbol, address } = this.asset as RegisteredAccountAsset & - RegisteredAsset; + const { externalAddress, externalDecimals, symbol, address } = this.asset; const image = this.whitelist[address]?.icon; await ethersUtil.addToken(externalAddress, symbol, +externalDecimals, image); } diff --git a/src/components/Moonpay/MoonpayBridgeInitMixin.ts b/src/components/Moonpay/MoonpayBridgeInitMixin.ts index ec6a4bbdf..9305ae05d 100644 --- a/src/components/Moonpay/MoonpayBridgeInitMixin.ts +++ b/src/components/Moonpay/MoonpayBridgeInitMixin.ts @@ -13,7 +13,7 @@ import BridgeHistoryMixin from '@/components/mixins/BridgeHistoryMixin'; import WalletConnectMixin from '@/components/mixins/WalletConnectMixin'; import type { MoonpayTransaction } from '@/utils/moonpay'; -import type { RegisterAssetWithExternalBalance } from '@/store/assets/types'; +import type { RegisteredAccountAssetObject } from '@/store/assets/types'; import type { BridgeTxData } from '@/store/moonpay/types'; const createError = (text: string, notification: MoonpayNotifications) => { @@ -30,6 +30,7 @@ export default class MoonpayBridgeInitMixin extends Mixins(BridgeHistoryMixin, W @state.wallet.settings.soraNetwork soraNetwork!: Nullable; @getter.settings.moonpayApiKey moonpayApiKey!: string; + @getter.assets.assetsDataTable assetsDataTable!: RegisteredAccountAssetObject; @mutation.moonpay.setConfirmationVisibility setConfirmationVisibility!: (flag: boolean) => void; @mutation.moonpay.setNotificationVisibility setNotificationVisibility!: (flag: boolean) => void; @@ -40,9 +41,7 @@ export default class MoonpayBridgeInitMixin extends Mixins(BridgeHistoryMixin, W hash: string ) => Promise>; - @action.moonpay.findRegisteredAssetByExternalAddress private findRegisteredAssetByExternalAddress!: ( - address: string - ) => Promise>; + @action.assets.updateRegisteredAssets private updateRegisteredAssets!: (reset?: boolean) => Promise; async prepareEvmNetwork(networkId = BridgeNetworks.ETH_NETWORK_ID): Promise { this.setEvmNetwork(networkId); // WalletConnectMixin @@ -122,7 +121,11 @@ export default class MoonpayBridgeInitMixin extends Mixins(BridgeHistoryMixin, W } // while registered assets updating, evmBalance updating too - const registeredAsset = await this.findRegisteredAssetByExternalAddress(ethTransferData.address); + await this.updateRegisteredAssets(); + + const registeredAsset = Object.values(this.assetsDataTable).find((asset) => + ethersUtil.addressesAreEqual(asset.externalAddress, ethTransferData.address) + ); if (!registeredAsset) { throw createError( diff --git a/src/components/SelectAsset/SelectRegisteredAsset.vue b/src/components/SelectAsset/SelectRegisteredAsset.vue index f1b79a643..0b9aa15fc 100644 --- a/src/components/SelectAsset/SelectRegisteredAsset.vue +++ b/src/components/SelectAsset/SelectRegisteredAsset.vue @@ -31,15 +31,16 @@ @@ -42,6 +54,7 @@ $radio-checked-size: 18px; .el-radio__label { display: flex; align-items: center; + justify-content: space-between; } .el-radio__label { padding-left: $inner-spacing-small; diff --git a/src/components/mixins/WalletConnectMixin.ts b/src/components/mixins/WalletConnectMixin.ts index 9a8f3cc11..83a25cf7d 100644 --- a/src/components/mixins/WalletConnectMixin.ts +++ b/src/components/mixins/WalletConnectMixin.ts @@ -57,7 +57,7 @@ export default class WalletConnectMixin extends Mixins(TranslationMixin) { @mutation.web3.setEvmAddress switchExternalAccount!: (address: string) => void; @action.web3.connectExternalAccount private connectExternalAccount!: (provider: Provider) => Promise; - @action.web3.setConnectedEvmNetwork setConnectedEvmNetwork!: (networkHex: string) => void; + @action.web3.setConnectedEvmNetwork setConnectedEvmNetwork!: (networkHex?: string) => void; getWalletAddress = getWalletAddress; formatAddress = formatAddress; diff --git a/src/consts/evm.ts b/src/consts/evm.ts index 06b68aef9..45817f48a 100644 --- a/src/consts/evm.ts +++ b/src/consts/evm.ts @@ -23,7 +23,7 @@ export interface EvmNetworkData { } // EVM networks data -// This data could be added to Metamask automatically using "addChain" function +// This data could be added to Metamask automatically using "switchOrAddChain" function export const EVM_NETWORKS: Record = { [EvmNetworkId.EthereumMainnet]: { id: EvmNetworkId.EthereumMainnet, @@ -38,7 +38,7 @@ export const EVM_NETWORKS: Record = { }, [EvmNetworkId.EthereumRopsten]: { id: EvmNetworkId.EthereumRopsten, - name: 'Ropsten Testnet', + name: 'Ethereum Ropsten Testnet', nativeCurrency: { name: 'RopstenETH', symbol: 'ETH', @@ -49,7 +49,7 @@ export const EVM_NETWORKS: Record = { }, [EvmNetworkId.EthereumRinkeby]: { id: EvmNetworkId.EthereumRinkeby, - name: 'Rinkeby Testnet', + name: 'Ethereum Rinkeby Testnet', nativeCurrency: { name: 'RinkebyETH', symbol: 'ETH', @@ -60,7 +60,7 @@ export const EVM_NETWORKS: Record = { }, [EvmNetworkId.EthereumGoerli]: { id: EvmNetworkId.EthereumGoerli, - name: 'Goerli Testnet', + name: 'Ethereum Goerli Testnet', nativeCurrency: { name: 'GoerliETH', symbol: 'ETH', @@ -71,7 +71,7 @@ export const EVM_NETWORKS: Record = { }, [EvmNetworkId.EthereumKovan]: { id: EvmNetworkId.EthereumKovan, - name: 'Kovan Testnet', + name: 'Ethereum Kovan Testnet', nativeCurrency: { name: 'KovanETH', symbol: 'ETH', @@ -93,7 +93,7 @@ export const EVM_NETWORKS: Record = { }, [EvmNetworkId.KlaytnBaobab]: { id: EvmNetworkId.KlaytnBaobab, - name: 'Klaytn Baobab', + name: 'Klaytn Baobab Testnet', nativeCurrency: { name: 'KLAY', symbol: 'KLAY', @@ -104,7 +104,7 @@ export const EVM_NETWORKS: Record = { }, [EvmNetworkId.KlaytnCypress]: { id: EvmNetworkId.KlaytnCypress, - name: 'Klaytn Cypress', + name: 'Klaytn Cypress Mainnet', nativeCurrency: { name: 'KLAY', symbol: 'KLAY', diff --git a/src/lang/en.json b/src/lang/en.json index 5a72fb9e0..7890fdd7f 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -452,13 +452,14 @@ "Stage": "@:soraText Testnet" }, "evm": { - "main": "Ethereum Mainnet", - "ropsten": "Ethereum Ropsten", - "rinkeby": "Ethereum Rinkeby", - "kovan": "Ethereum Kovan", - "goerli": "Ethereum Goerli", - "private": "Volta Testnet", - "EWC": "Energy Web Chain" + "1": "@:ethereumText Mainnet", + "3": "@:ethereumText Ropsten Testnet", + "4": "@:ethereumText Rinkeby Testnet", + "5": "@:ethereumText Goerli Testnet", + "42": "@:ethereumText Kovan Testnet", + "63": "@:ethereumText Classic Mordor Testnet", + "1001": "Klaytn Baobab Testnet", + "8217": "Klaytn Cypress Mainnet" }, "providers": { "metamask": "@:metamask" diff --git a/src/lang/messages.ts b/src/lang/messages.ts index b016e006e..12b90947a 100644 --- a/src/lang/messages.ts +++ b/src/lang/messages.ts @@ -4,7 +4,7 @@ import { Operation, TransactionStatus } from '@sora-substrate/util'; import { RewardingEvents } from '@sora-substrate/util/build/rewards/consts'; import { PageNames, RewardsTabsItems } from '../consts'; -import { EvmNetworkType } from '../utils/ethers-util'; +import { EvmNetworkId } from '@/consts/evm'; import { MoonpayNotifications } from '@/components/Moonpay/consts'; export default { @@ -251,13 +251,14 @@ export default { [WALLET_CONSTS.SoraNetwork.Prod]: '@:soraText Mainnet', }, evm: { - [EvmNetworkType.Mainnet]: 'Ethereum Mainnet', - [EvmNetworkType.Ropsten]: 'Ethereum Ropsten', - [EvmNetworkType.Rinkeby]: 'Ethereum Rinkeby', - [EvmNetworkType.Kovan]: 'Ethereum Kovan', - [EvmNetworkType.Goerli]: 'Ethereum Goerli', - [EvmNetworkType.Private]: 'Volta Testnet', - [EvmNetworkType.EWC]: 'Energy Web Chain', + [EvmNetworkId.EthereumMainnet]: '@:ethereumText Mainnet', + [EvmNetworkId.EthereumRopsten]: '@:ethereumText Ropsten Testnet', + [EvmNetworkId.EthereumRinkeby]: '@:ethereumText Rinkeby Testnet', + [EvmNetworkId.EthereumGoerli]: '@:ethereumText Goerli Testnet', + [EvmNetworkId.EthereumKovan]: '@:ethereumText Kovan Testnet', + [EvmNetworkId.EthereumClassicMordor]: '@:ethereumText Classic Mordor Testnet', + [EvmNetworkId.KlaytnBaobab]: 'Klaytn Baobab Testnet', + [EvmNetworkId.KlaytnCypress]: 'Klaytn Cypress Mainnet', }, providers: { metamask: '@:metamask', diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index 3445434d4..80ff57012 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -13,7 +13,7 @@ import { TokenBalanceSubscriptions } from '@/utils/subscriptions'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; import { waitForApprovedRequest } from '@/utils/bridge/eth/utils'; import { EthBridgeHistory } from '@/utils/bridge/eth/history'; -import ethersUtil, { ABI, KnownBridgeAsset, OtherContractType } from '@/utils/ethers-util'; +import ethersUtil, { ABI, KnownEthBridgeAsset, OtherContractType } from '@/utils/ethers-util'; import type { SignTxResult } from './types'; const { ETH_BRIDGE_STATES } = WALLET_CONSTS; @@ -115,8 +115,8 @@ const actions = defineActions({ const address = rootState.wallet.account.address; const assets = rootGetters.assets.assetsDataTable; const networkFees = rootState.wallet.settings.networkFees; - const contractsArray = Object.values(KnownBridgeAsset).map>( - (key) => rootState.web3.ethBridge.contractAddress[key] + const contractsArray = Object.values(KnownEthBridgeAsset).map>( + (key) => rootState.web3.ethBridgeContractAddress[key] ); const contracts = compact(contractsArray); const updateCallback = setHistoryCallback || (() => commit.setHistory()); @@ -168,15 +168,15 @@ const actions = defineActions({ // throw new Error(`Change account in MetaMask to ${request.to}`); // } // const ethersInstance = await ethersUtil.getEthersInstance(); - // const symbol = asset.symbol as KnownBridgeAsset; + // const symbol = asset.symbol as KnownEthBridgeAsset; // const evmAccount = rootState.web3.evmAddress; - // const isValOrXor = [KnownBridgeAsset.XOR, KnownBridgeAsset.VAL].includes(symbol); + // const isValOrXor = [KnownEthBridgeAsset.XOR, KnownEthBridgeAsset.VAL].includes(symbol); // const isEthereumChain = isValOrXor && rootState.web3.evmNetwork === BridgeNetworks.ETH_NETWORK_ID; - // const bridgeAsset: KnownBridgeAsset = isEthereumChain ? symbol : KnownBridgeAsset.Other; + // const bridgeAsset: KnownEthBridgeAsset = isEthereumChain ? symbol : KnownEthBridgeAsset.Other; // const contractMap = { - // [KnownBridgeAsset.XOR]: rootGetters.web3.contractAbi(KnownBridgeAsset.XOR), - // [KnownBridgeAsset.VAL]: rootGetters.web3.contractAbi(KnownBridgeAsset.VAL), - // [KnownBridgeAsset.Other]: rootGetters.web3.contractAbi(KnownBridgeAsset.Other), + // [KnownEthBridgeAsset.XOR]: rootGetters.web3.contractAbi(KnownEthBridgeAsset.XOR), + // [KnownEthBridgeAsset.VAL]: rootGetters.web3.contractAbi(KnownEthBridgeAsset.VAL), + // [KnownEthBridgeAsset.Other]: rootGetters.web3.contractAbi(KnownEthBridgeAsset.Other), // }; // const contract = contractMap[bridgeAsset]; // const jsonInterface = contract[OtherContractType.Bridge]?.abi ?? contract.abi; @@ -231,12 +231,12 @@ const actions = defineActions({ // if (!asset?.externalAddress) throw new Error(`Asset not registered: ${tx.assetAddress}`); // checkEvmNetwork(context); // try { - // const contract = rootGetters.web3.contractAbi(KnownBridgeAsset.Other); + // const contract = rootGetters.web3.contractAbi(KnownEthBridgeAsset.Other); // const evmAccount = rootState.web3.evmAddress; // const isExternalAccountConnected = await ethersUtil.checkAccountIsConnected(evmAccount); // if (!isExternalAccountConnected) throw new Error('Connect account in Metamask'); // const ethersInstance = await ethersUtil.getEthersInstance(); - // const contractAddress = rootGetters.web3.contractAddress(KnownBridgeAsset.Other) as string; + // const contractAddress = rootGetters.web3.contractAddress(KnownEthBridgeAsset.Other) as string; // const isNativeEvmToken = ethersUtil.isNativeEvmTokenAddress(asset.externalAddress); // // don't check allowance for native EVM token // if (!isNativeEvmToken) { diff --git a/src/store/settings/actions.ts b/src/store/settings/actions.ts index 839199e0d..3761f5286 100644 --- a/src/store/settings/actions.ts +++ b/src/store/settings/actions.ts @@ -10,7 +10,7 @@ import { getSupportedLocale, setDayJsLocale, setI18nLocale } from '@/lang'; import { updateDocumentTitle, updateFpNumberLocale } from '@/utils'; import { AppHandledError } from '@/utils/error'; import { fetchRpc, getRpcEndpoint } from '@/utils/rpc'; -import { KnownBridgeAsset } from '@/utils/ethers-util'; +import { KnownEthBridgeAsset } from '@/utils/ethers-util'; import { EthBridgeHistory } from '@/utils/bridge/eth/history'; import type { ConnectToNodeOptions, Node } from '@/types/nodes'; @@ -34,13 +34,13 @@ const updateEthBridgeHistory = networkFees, }, }, - web3: { - ethBridge: { contractAddress }, - }, + web3: { ethBridgeContractAddress }, } = rootState; const assets = rootGetters.assets.assetsDataTable; - const contracts = compact(Object.values(KnownBridgeAsset).map>((key) => contractAddress[key])); + const contracts = compact( + Object.values(KnownEthBridgeAsset).map>((key) => ethBridgeContractAddress[key]) + ); const ethBridgeHistory = new EthBridgeHistory(etherscanApiKey); diff --git a/src/store/web3/actions.ts b/src/store/web3/actions.ts index dbbf792ab..bd6427b21 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -3,6 +3,7 @@ import { defineActions } from 'direct-vuex'; import { web3ActionContext } from '@/store/web3'; import ethersUtil from '@/utils/ethers-util'; +import type { EvmNetworkId } from '@/consts/evm'; import type { Provider } from '@/utils/ethers-util'; const actions = defineActions({ @@ -17,6 +18,18 @@ const actions = defineActions({ const evmNetwork = networkHex ? ethersUtil.hexToNumber(networkHex) : await ethersUtil.getEvmNetworkId(); commit.setEvmNetwork(evmNetwork); }, + + async setSelectedEvmNetwork(context, evmNetwork: EvmNetworkId): Promise { + const { commit, getters } = web3ActionContext(context); + commit.setSelectedEvmNetwork(evmNetwork); + + const { selectedEvmNetwork: selected, connectedEvmNetwork: connected } = getters; + + // if connected network is not equal to selected, request for provider to change network + if (selected && connected && selected.id !== connected.id) { + await ethersUtil.switchOrAddChain(selected); + } + }, }); export default actions; diff --git a/src/store/web3/mutations.ts b/src/store/web3/mutations.ts index 71384222b..b9b1979c2 100644 --- a/src/store/web3/mutations.ts +++ b/src/store/web3/mutations.ts @@ -35,7 +35,7 @@ const mutations = defineMutations()({ }, // by user setSelectedEvmNetwork(state, networkId: EvmNetworkId): void { - state.evmNetwork = networkId; + state.evmNetworkSelected = networkId; evmBridgeApi.externalNetwork = networkId; }, setEvmBalance(state, balance: CodecString): void { @@ -46,8 +46,8 @@ const mutations = defineMutations()({ state, { evmNetwork, address }: { evmNetwork: EvmNetworkId; address: EthBridgeContracts } ): void { - state.ethBridge.evmNetwork = evmNetwork; - state.ethBridge.contractAddress = { + state.ethBridgeEvmNetwork = evmNetwork; + state.ethBridgeContractAddress = { XOR: address.XOR, VAL: address.VAL, OTHER: address.OTHER, diff --git a/src/store/web3/state.ts b/src/store/web3/state.ts index 3db4e4393..b0e7de27b 100644 --- a/src/store/web3/state.ts +++ b/src/store/web3/state.ts @@ -9,13 +9,12 @@ export function initialState(): Web3State { evmNetwork: EvmNetworkId.EthereumClassicMordor, // evm network in provider evmNetworksIds: [], evmNetworkSelected: EvmNetworkId.EthereumClassicMordor, // evm network selected by user - ethBridge: { - evmNetwork: EvmNetworkId.EthereumRinkeby, - contractAddress: { - XOR: '', - VAL: '', - OTHER: '', - }, + + ethBridgeEvmNetwork: EvmNetworkId.EthereumRinkeby, + ethBridgeContractAddress: { + XOR: '', + VAL: '', + OTHER: '', }, }; } diff --git a/src/store/web3/types.ts b/src/store/web3/types.ts index 6119e165d..6f992172e 100644 --- a/src/store/web3/types.ts +++ b/src/store/web3/types.ts @@ -14,5 +14,7 @@ export type Web3State = { evmNetwork: EvmNetworkId; evmNetworksIds: EvmNetworkId[]; evmNetworkSelected: EvmNetworkId; - ethBridge: EthBridgeSettings; + + ethBridgeEvmNetwork: EvmNetworkId; + ethBridgeContractAddress: EthBridgeContracts; }; diff --git a/src/styles/common.scss b/src/styles/common.scss index 082553e29..33a4ad93c 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -23,6 +23,16 @@ $s-icon-energy: "\0048"; content: $s-icon-eth; } } +.s-icon-energy { + &.s-icon--network { + &, &:hover { + color: #a566ff !important; + } + } + &:before { + content: $s-icon-energy; + } +} [design-system-theme="light"] { .s-icon--network.s-icon { &-sora { @@ -37,16 +47,6 @@ $s-icon-energy: "\0048"; } } } -.s-icon-energy { - &.s-icon--network { - &, &:hover { - color: #a566ff !important; - } - } - &:before { - content: $s-icon-energy; - } -} /*________________________________________COMMON LAYOUT________________________________________*/ h3 { diff --git a/src/utils/ethers-util.ts b/src/utils/ethers-util.ts index db885cb76..c852a0257 100644 --- a/src/utils/ethers-util.ts +++ b/src/utils/ethers-util.ts @@ -110,35 +110,14 @@ type ethersProvider = ethers.providers.Web3Provider; let provider: any = null; let ethersInstance: ethersProvider | null = null; -// TODO [EVM] remove and update translations to network ids -export enum EvmNetworkType { - Mainnet = 'main', - Ropsten = 'ropsten', - Kovan = 'kovan', - Rinkeby = 'rinkeby', - Goerli = 'goerli', - Private = 'private', - EWC = 'EWC', - Mordor = 'classicMordor', // Ethereum Classic Mordor -} - -// TODO: replace for KnownEthBridgeAsset -export enum KnownBridgeAsset { +// TODO [EVM] Move to utils/bridge/eth +export enum KnownEthBridgeAsset { VAL = 'VAL', XOR = 'XOR', Other = 'OTHER', } -export enum ContractNetwork { - Ethereum = 'ethereum', - Other = 'other', -} - -export enum Contract { - Internal = 'internal', - Other = 'other', -} - +// TODO [EVM] Move to utils/bridge/eth export enum OtherContractType { Bridge = 'BRIDGE', ERC20 = 'ERC20', @@ -154,15 +133,6 @@ interface ConnectOptions { url?: string; } -interface JsonContract { - abi: AbiItem; - evm: { - bytecode: { - object: string; - }; - }; -} - // TODO [EVM] const gasLimit = { approve: 70000, @@ -186,7 +156,7 @@ const EthereumGasLimits = [ [VAL.address]: gasLimit.approve + gasLimit.sendERC20ToSidechain, [PSWAP.address]: gasLimit.approve + gasLimit.sendERC20ToSidechain, [ETH.address]: gasLimit.sendEthToSidechain, - [KnownBridgeAsset.Other]: gasLimit.approve + gasLimit.sendERC20ToSidechain, + [KnownEthBridgeAsset.Other]: gasLimit.approve + gasLimit.sendERC20ToSidechain, }, // SORA -> ETH { @@ -194,7 +164,7 @@ const EthereumGasLimits = [ [VAL.address]: gasLimit.mintTokensByPeers, [PSWAP.address]: gasLimit.receiveBySidechainAssetId, [ETH.address]: gasLimit.receiveByEthereumAssetAddress, - [KnownBridgeAsset.Other]: gasLimit.receiveByEthereumAssetAddress, + [KnownEthBridgeAsset.Other]: gasLimit.receiveByEthereumAssetAddress, }, ]; @@ -356,27 +326,43 @@ async function addToken(address: string, symbol: string, decimals: number, image /** * Add chain to Metamask - * @param network + * @param network network data * @param chainName translated chain name */ -async function addChain(network: EvmNetworkData, chainName?: string): Promise { +async function switchOrAddChain(network: EvmNetworkData, chainName?: string): Promise { const ethereum = (window as any).ethereum; + const chainId = ethers.utils.hexValue(network.id); try { - ethereum.request({ - method: 'wallet_addEthereumChain', + await ethereum.request({ + method: 'wallet_switchEthereumChain', params: [ { - chainId: ethers.utils.hexValue(network.id), - chainName: chainName || network.name, - rpcUrls: network.rpcUrls, - nativeCurrency: network.nativeCurrency, - blockExplorerUrls: network.blockExplorerUrls, + chainId, }, ], }); - } catch (error) { - console.error(error); + } catch (switchError: any) { + console.error(switchError); + + if (switchError.code === 4902) { + try { + await ethereum.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId, + chainName: chainName || network.name, + rpcUrls: network.rpcUrls, + nativeCurrency: network.nativeCurrency, + blockExplorerUrls: network.blockExplorerUrls, + }, + ], + }); + } catch (addError) { + console.error(addError); + } + } } } @@ -395,7 +381,7 @@ async function getEvmNetworkFee(address: string, isSoraToEvm: boolean): Promise< const ethersInstance = await getEthersInstance(); const gasPrice = (await ethersInstance.getGasPrice()).toNumber(); const gasLimits = EthereumGasLimits[+isSoraToEvm]; - const key = address in gasLimits ? address : KnownBridgeAsset.Other; + const key = address in gasLimits ? address : KnownEthBridgeAsset.Other; const gasLimit = gasLimits[key]; const fee = calcEvmFee(gasPrice, gasLimit); @@ -431,15 +417,6 @@ async function getBlock(number: number): Promise { return block; } -async function readSmartContract(network: ContractNetwork, name: string): Promise { - try { - const { data } = await axiosInstance.get(`/abi/${network}/${name}`); - return data; - } catch (error) { - console.error(error); - } -} - async function accountAddressToHex(address: string): Promise { return ethers.utils.hexlify(Array.from(decodeAddress(address).values())); } @@ -464,7 +441,6 @@ export default { checkAccountIsConnected, getEthersInstance, watchEthereum, - readSmartContract, accountAddressToHex, addressesAreEqual, calcEvmFee, @@ -475,6 +451,6 @@ export default { getEvmTransactionReceipt, getBlock, addToken, - addChain, + switchOrAddChain, isNativeEvmTokenAddress, }; diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index 530afd10b..b04b30397 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -543,7 +543,7 @@ export default class Bridge extends Mixins( async changeProviderNetwork(): Promise { if (this.selectedEvmNetwork) { - await ethersUtil.addChain(this.selectedEvmNetwork); + await ethersUtil.switchOrAddChain(this.selectedEvmNetwork); } } diff --git a/src/views/BridgeContainer.vue b/src/views/BridgeContainer.vue index b277b4f1c..2d8187b30 100644 --- a/src/views/BridgeContainer.vue +++ b/src/views/BridgeContainer.vue @@ -31,7 +31,7 @@ export default class BridgeContainer extends Mixins(mixins.LoadingMixin, WalletC await this.withLoading(async () => { await this.withParentLoading(async () => { this.setSelectedEvmNetwork(evmBridgeApi.externalNetwork); - await this.onEvmNetworkTypeChange(); + await this.onEvmNetworkChange(); this.unwatchEthereum = await ethersUtil.watchEthereum({ onAccountChange: (addressList: string[]) => { @@ -43,7 +43,7 @@ export default class BridgeContainer extends Mixins(mixins.LoadingMixin, WalletC } }, onNetworkChange: (networkHex: string) => { - this.onEvmNetworkTypeChange(networkHex); + this.onEvmNetworkChange(networkHex); }, onDisconnect: () => { this.disconnectExternalAccount(); @@ -61,7 +61,7 @@ export default class BridgeContainer extends Mixins(mixins.LoadingMixin, WalletC this.unsubscribeEvmBlockHeaders(); } - private async onEvmNetworkTypeChange(networkHex?: string) { + private async onEvmNetworkChange(networkHex?: string) { await Promise.all([ this.setConnectedEvmNetwork(networkHex), this.updateExternalBalances(true), diff --git a/src/views/BridgeTransaction.vue b/src/views/BridgeTransaction.vue index 178cb2f32..c215211e7 100644 --- a/src/views/BridgeTransaction.vue +++ b/src/views/BridgeTransaction.vue @@ -620,7 +620,7 @@ export default class BridgeTransaction extends Mixins( handleOpenEtherscan(): void { const hash = this.isSoraToEvm ? this.transactionToHash : this.transactionFromHash; - const url = this.getEtherscanLink(hash, true); + const url = this.getEtherscanLink(hash); const win = window.open(url, '_blank'); win && win.focus(); } @@ -736,11 +736,11 @@ export default class BridgeTransaction extends Mixins( } get formattedNetworkStep1(): string { - return this.t(this.formatNetwork(this.isSoraToEvm, true)); + return this.t(this.formatNetwork(this.isSoraToEvm)); } get formattedNetworkStep2(): string { - return this.t(this.formatNetwork(!this.isSoraToEvm, true)); + return this.t(this.formatNetwork(!this.isSoraToEvm)); } get comfirmationBlocksLeft(): number { diff --git a/src/views/MoonpayHistory.vue b/src/views/MoonpayHistory.vue index fcd970563..531de2b33 100644 --- a/src/views/MoonpayHistory.vue +++ b/src/views/MoonpayHistory.vue @@ -290,7 +290,7 @@ export default class MoonpayHistory extends Mixins(mixins.PaginationSearchMixin, if (!this.selectedItem.id) return; if (this.bridgeTxToSora?.id) { - await this.prepareEvmNetwork(this.bridgeTxToSora.externalNetwork); // MoonpayBridgeInitMixin + await this.prepareEvmNetwork(); // MoonpayBridgeInitMixin await this.showHistory(this.bridgeTxToSora.id); // MoonpayBridgeInitMixin } else { await this.prepareMoonpayTxForBridgeTransfer(this.selectedItem); From da9b8082cb7a74b2e1b50554ff98097ba79b1130 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Mon, 12 Sep 2022 09:41:35 +0300 Subject: [PATCH 011/109] evmAddress evmNetwork in storage --- src/components/mixins/WalletConnectMixin.ts | 1 + src/store/web3/actions.ts | 12 ++++++++ src/store/web3/getters.ts | 4 +-- src/store/web3/mutations.ts | 4 +++ src/store/web3/state.ts | 9 ++++-- src/store/web3/types.ts | 4 +-- src/utils/ethers-util.ts | 33 +++++++++++++++++++-- src/views/Bridge.vue | 7 +---- src/views/BridgeContainer.vue | 9 +++--- 9 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/components/mixins/WalletConnectMixin.ts b/src/components/mixins/WalletConnectMixin.ts index 83a25cf7d..ce5a57e0d 100644 --- a/src/components/mixins/WalletConnectMixin.ts +++ b/src/components/mixins/WalletConnectMixin.ts @@ -58,6 +58,7 @@ export default class WalletConnectMixin extends Mixins(TranslationMixin) { @action.web3.connectExternalAccount private connectExternalAccount!: (provider: Provider) => Promise; @action.web3.setConnectedEvmNetwork setConnectedEvmNetwork!: (networkHex?: string) => void; + @action.web3.restoreSelectedEvmNetwork restoreSelectedEvmNetwork!: AsyncVoidFn; getWalletAddress = getWalletAddress; formatAddress = formatAddress; diff --git a/src/store/web3/actions.ts b/src/store/web3/actions.ts index bd6427b21..69b2d25f8 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -30,6 +30,18 @@ const actions = defineActions({ await ethersUtil.switchOrAddChain(selected); } }, + + async restoreSelectedEvmNetwork(context): Promise { + const { commit, getters } = web3ActionContext(context); + + if (getters.selectedEvmNetwork) return; + + const selectedEvmNetworkId = ethersUtil.getSelectedEvmNetwork() || getters.availableEvmNetworks[0]?.id; + + if (selectedEvmNetworkId) { + commit.setSelectedEvmNetwork(selectedEvmNetworkId); + } + }, }); export default actions; diff --git a/src/store/web3/getters.ts b/src/store/web3/getters.ts index 6397855e0..eae857241 100644 --- a/src/store/web3/getters.ts +++ b/src/store/web3/getters.ts @@ -17,11 +17,11 @@ const getters = defineGetters()({ }, connectedEvmNetwork(...args): Nullable { const { state } = web3GetterContext(args); - return EVM_NETWORKS[state.evmNetwork]; + return state.evmNetwork ? EVM_NETWORKS[state.evmNetwork] : null; }, selectedEvmNetwork(...args): Nullable { const { state } = web3GetterContext(args); - return EVM_NETWORKS[state.evmNetworkSelected]; + return state.evmNetworkSelected ? EVM_NETWORKS[state.evmNetworkSelected] : null; }, isValidNetwork(...args): boolean { const { state } = web3GetterContext(args); diff --git a/src/store/web3/mutations.ts b/src/store/web3/mutations.ts index b9b1979c2..49027106f 100644 --- a/src/store/web3/mutations.ts +++ b/src/store/web3/mutations.ts @@ -1,6 +1,7 @@ import { defineMutations } from 'direct-vuex'; import { CodecString } from '@sora-substrate/util'; +import ethersUtil from '@/utils/ethers-util'; import { evmBridgeApi } from '@/utils/bridge/evm/api'; import { initialState } from './state'; @@ -22,9 +23,11 @@ const mutations = defineMutations()({ }, setEvmAddress(state, address: string): void { state.evmAddress = address; + ethersUtil.storeEvmUserAddress(address); }, resetEvmAddress(state): void { state.evmAddress = ''; + ethersUtil.removeEvmUserAddress(); }, setEvmNetworksIds(state, networksIds: EvmNetworkId[]): void { state.evmNetworksIds = networksIds; @@ -36,6 +39,7 @@ const mutations = defineMutations()({ // by user setSelectedEvmNetwork(state, networkId: EvmNetworkId): void { state.evmNetworkSelected = networkId; + ethersUtil.storeSelectedEvmNetwork(networkId); evmBridgeApi.externalNetwork = networkId; }, setEvmBalance(state, balance: CodecString): void { diff --git a/src/store/web3/state.ts b/src/store/web3/state.ts index b0e7de27b..7eb521d1f 100644 --- a/src/store/web3/state.ts +++ b/src/store/web3/state.ts @@ -1,14 +1,17 @@ +import ethersUtil from '@/utils/ethers-util'; + import { ZeroStringValue } from '@/consts'; import { EvmNetworkId } from '@/consts/evm'; + import type { Web3State } from './types'; export function initialState(): Web3State { return { - evmAddress: '', + evmAddress: ethersUtil.getEvmUserAddress(), evmBalance: ZeroStringValue, - evmNetwork: EvmNetworkId.EthereumClassicMordor, // evm network in provider + evmNetwork: null, // evm network in provider evmNetworksIds: [], - evmNetworkSelected: EvmNetworkId.EthereumClassicMordor, // evm network selected by user + evmNetworkSelected: null, // evm network selected by user ethBridgeEvmNetwork: EvmNetworkId.EthereumRinkeby, ethBridgeContractAddress: { diff --git a/src/store/web3/types.ts b/src/store/web3/types.ts index 6f992172e..50350c8d0 100644 --- a/src/store/web3/types.ts +++ b/src/store/web3/types.ts @@ -11,9 +11,9 @@ export type EthBridgeSettings = { export type Web3State = { evmAddress: string; evmBalance: CodecString; - evmNetwork: EvmNetworkId; + evmNetwork: Nullable; evmNetworksIds: EvmNetworkId[]; - evmNetworkSelected: EvmNetworkId; + evmNetworkSelected: Nullable; ethBridgeEvmNetwork: EvmNetworkId; ethBridgeContractAddress: EthBridgeContracts; diff --git a/src/utils/ethers-util.ts b/src/utils/ethers-util.ts index c852a0257..b242aef06 100644 --- a/src/utils/ethers-util.ts +++ b/src/utils/ethers-util.ts @@ -7,10 +7,10 @@ import { XOR, VAL, PSWAP, ETH } from '@sora-substrate/util/build/assets/consts'; import type { CodecString } from '@sora-substrate/util'; -import axiosInstance from '@/api'; +import storage from './storage'; import { ZeroStringValue } from '@/consts'; -import type { EvmNetworkData } from '@/consts/evm'; +import type { EvmNetworkData, EvmNetworkId } from '@/consts/evm'; type AbiType = 'function' | 'constructor' | 'event' | 'fallback'; type StateMutabilityType = 'pure' | 'view' | 'nonpayable' | 'payable'; @@ -432,6 +432,28 @@ function isNativeEvmTokenAddress(address: string): boolean { return hexToNumber(address) === 0; } +function getEvmUserAddress(): string { + return storage.get('evmAddress') || ''; +} + +function storeEvmUserAddress(address: string): void { + storage.set('evmAddress', address); +} + +function removeEvmUserAddress(): void { + storage.remove('evmAddress'); +} + +function getSelectedEvmNetwork(): Nullable { + const evmNetwork = Number(storage.get('evmNetwork')); + + return Number.isFinite(evmNetwork) ? evmNetwork : null; +} + +function storeSelectedEvmNetwork(evmNetwork: EvmNetworkId) { + storage.set('evmNetwork', evmNetwork); +} + export default { onConnect, getAccount, @@ -453,4 +475,11 @@ export default { addToken, switchOrAddChain, isNativeEvmTokenAddress, + // evm address storage + getEvmUserAddress, + storeEvmUserAddress, + removeEvmUserAddress, + // evm network storage + getSelectedEvmNetwork, + storeSelectedEvmNetwork, }; diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index b04b30397..b0a1130a0 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -229,7 +229,7 @@ /> - + { - this.showSelectNetworkDialog = false; - this.setSelectedEvmNetwork(network); - } - async changeProviderNetwork(): Promise { if (this.selectedEvmNetwork) { await ethersUtil.switchOrAddChain(this.selectedEvmNetwork); diff --git a/src/views/BridgeContainer.vue b/src/views/BridgeContainer.vue index 2d8187b30..410952ad6 100644 --- a/src/views/BridgeContainer.vue +++ b/src/views/BridgeContainer.vue @@ -15,7 +15,6 @@ import { mixins } from '@soramitsu/soraneo-wallet-web'; import WalletConnectMixin from '@/components/mixins/WalletConnectMixin'; import ethersUtil from '@/utils/ethers-util'; -import { evmBridgeApi } from '@/utils/bridge/evm/api'; import { action } from '@/store/decorators'; @Component @@ -30,8 +29,8 @@ export default class BridgeContainer extends Mixins(mixins.LoadingMixin, WalletC async created(): Promise { await this.withLoading(async () => { await this.withParentLoading(async () => { - this.setSelectedEvmNetwork(evmBridgeApi.externalNetwork); - await this.onEvmNetworkChange(); + await this.restoreSelectedEvmNetwork(); + await this.onConnectedEvmNetworkChange(); this.unwatchEthereum = await ethersUtil.watchEthereum({ onAccountChange: (addressList: string[]) => { @@ -43,7 +42,7 @@ export default class BridgeContainer extends Mixins(mixins.LoadingMixin, WalletC } }, onNetworkChange: (networkHex: string) => { - this.onEvmNetworkChange(networkHex); + this.onConnectedEvmNetworkChange(networkHex); }, onDisconnect: () => { this.disconnectExternalAccount(); @@ -61,7 +60,7 @@ export default class BridgeContainer extends Mixins(mixins.LoadingMixin, WalletC this.unsubscribeEvmBlockHeaders(); } - private async onEvmNetworkChange(networkHex?: string) { + private async onConnectedEvmNetworkChange(networkHex?: string) { await Promise.all([ this.setConnectedEvmNetwork(networkHex), this.updateExternalBalances(true), From 73702afc3730a527afe31043597292a7ddc2f2fc Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Mon, 12 Sep 2022 11:47:48 +0300 Subject: [PATCH 012/109] update bridge confirm dialog --- src/components/ConfirmBridgeTransactionDialog.vue | 10 ++++++---- .../BridgeTransactionDetails.vue | 8 +++++--- src/components/mixins/BridgeMixin.ts | 11 ++++------- src/components/mixins/NetworkFormatterMixin.ts | 15 ++++++--------- src/consts/index.ts | 5 ----- src/store/web3/actions.ts | 7 ++++--- src/store/web3/getters.ts | 4 ---- src/views/Bridge.vue | 5 +++-- 8 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/components/ConfirmBridgeTransactionDialog.vue b/src/components/ConfirmBridgeTransactionDialog.vue index 8da6989c6..33bb144f9 100644 --- a/src/components/ConfirmBridgeTransactionDialog.vue +++ b/src/components/ConfirmBridgeTransactionDialog.vue @@ -54,14 +54,16 @@ diff --git a/src/components/mixins/BridgeMixin.ts b/src/components/mixins/BridgeMixin.ts index 029a73c97..2cb8a650c 100644 --- a/src/components/mixins/BridgeMixin.ts +++ b/src/components/mixins/BridgeMixin.ts @@ -3,7 +3,7 @@ import { mixins } from '@soramitsu/soraneo-wallet-web'; import type { CodecString } from '@sora-substrate/util'; import WalletConnectMixin from '@/components/mixins/WalletConnectMixin'; -import { getter, state } from '@/store/decorators'; +import { mutation, getter, state } from '@/store/decorators'; import type { RegisteredAccountAssetWithDecimals } from '@/store/assets/types'; import type { EvmNetworkData, EvmNetworkId } from '@/consts/evm'; @@ -19,6 +19,8 @@ export default class BridgeMixin extends Mixins(mixins.LoadingMixin, WalletConne @getter.bridge.evmNetworkFee evmNetworkFee!: CodecString; @getter.assets.xor xor!: RegisteredAccountAssetWithDecimals; + @mutation.web3.setSelectNetworkDialogVisibility setSelectNetworkDialogVisibility!: (flag: boolean) => void; + get evmTokenSymbol(): string { return this.selectedEvmNetwork?.nativeCurrency?.symbol ?? ''; } diff --git a/src/components/mixins/WalletConnectMixin.ts b/src/components/mixins/WalletConnectMixin.ts index ce5a57e0d..e67743a59 100644 --- a/src/components/mixins/WalletConnectMixin.ts +++ b/src/components/mixins/WalletConnectMixin.ts @@ -53,11 +53,11 @@ export default class WalletConnectMixin extends Mixins(TranslationMixin) { @mutation.web3.resetEvmAddress private resetEvmAddress!: VoidFunction; @mutation.web3.reset private resetWeb3Store!: VoidFunction; - @mutation.web3.setSelectedEvmNetwork setSelectedEvmNetwork!: (networkId: EvmNetworkId) => void; @mutation.web3.setEvmAddress switchExternalAccount!: (address: string) => void; @action.web3.connectExternalAccount private connectExternalAccount!: (provider: Provider) => Promise; @action.web3.setConnectedEvmNetwork setConnectedEvmNetwork!: (networkHex?: string) => void; + @action.web3.setSelectedEvmNetwork setSelectedEvmNetwork!: (networkId: EvmNetworkId) => void; @action.web3.restoreSelectedEvmNetwork restoreSelectedEvmNetwork!: AsyncVoidFn; getWalletAddress = getWalletAddress; diff --git a/src/store/assets/actions.ts b/src/store/assets/actions.ts index 1f84eddb6..df3d8e670 100644 --- a/src/store/assets/actions.ts +++ b/src/store/assets/actions.ts @@ -21,9 +21,10 @@ const actions = defineActions({ const networkAssets = await evmBridgeApi.getNetworkAssets(); const networkAssetsWithBalance = await Promise.all( - Object.entries(networkAssets).map(async ([soraAddress, externalAddress]) => { + Object.entries(networkAssets).map(async ([soraAddress, assetData]) => { const accountAsset = { - address: externalAddress as string, + // TODO [EVM] change contract to evmAddress after js-lib update + address: (assetData as any).contract as string, balance: ZeroStringValue, decimals: 18, }; diff --git a/src/store/web3/mutations.ts b/src/store/web3/mutations.ts index 49027106f..1fff6210d 100644 --- a/src/store/web3/mutations.ts +++ b/src/store/web3/mutations.ts @@ -45,6 +45,11 @@ const mutations = defineMutations()({ setEvmBalance(state, balance: CodecString): void { state.evmBalance = balance; }, + + setSelectNetworkDialogVisibility(state, flag: boolean): void { + state.selectNetworkDialogVisibility = flag; + }, + // for eth bridge history setEthBridgeSettings( state, diff --git a/src/store/web3/state.ts b/src/store/web3/state.ts index 7eb521d1f..f6eecaafb 100644 --- a/src/store/web3/state.ts +++ b/src/store/web3/state.ts @@ -13,6 +13,8 @@ export function initialState(): Web3State { evmNetworksIds: [], evmNetworkSelected: null, // evm network selected by user + selectNetworkDialogVisibility: false, + ethBridgeEvmNetwork: EvmNetworkId.EthereumRinkeby, ethBridgeContractAddress: { XOR: '', diff --git a/src/store/web3/types.ts b/src/store/web3/types.ts index 50350c8d0..0a634835a 100644 --- a/src/store/web3/types.ts +++ b/src/store/web3/types.ts @@ -15,6 +15,8 @@ export type Web3State = { evmNetworksIds: EvmNetworkId[]; evmNetworkSelected: Nullable; + selectNetworkDialogVisibility: boolean; + ethBridgeEvmNetwork: EvmNetworkId; ethBridgeContractAddress: EthBridgeContracts; }; diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index ec22187fe..99ee383d3 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -237,7 +237,11 @@ /> - + { if (!selectedAsset) return; diff --git a/src/views/BridgeTransactionsHistory.vue b/src/views/BridgeTransactionsHistory.vue index 2e8f015b3..4afe27f2b 100644 --- a/src/views/BridgeTransactionsHistory.vue +++ b/src/views/BridgeTransactionsHistory.vue @@ -1,7 +1,21 @@