diff --git a/src/abi/ethereum/internal/MASTER.json b/src/abi/ethereum/internal/MASTER.json index 3a79fc419..ed2b5af6c 100644 --- a/src/abi/ethereum/internal/MASTER.json +++ b/src/abi/ethereum/internal/MASTER.json @@ -1,52 +1,54 @@ -[ - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "txHash", - "type": "bytes32" - }, - { - "internalType": "uint8[]", - "name": "v", - "type": "uint8[]" - }, - { - "internalType": "bytes32[]", - "name": "r", - "type": "bytes32[]" - }, - { - "internalType": "bytes32[]", - "name": "s", - "type": "bytes32[]" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - } - ], - "name": "mintTokensByPeers", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] +{ + "abi": [ + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint8[]", + "name": "v", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "r", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "s", + "type": "bytes32[]" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + } + ], + "name": "mintTokensByPeers", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ] +} diff --git a/src/abi/ethereum/other/BRIDGE.json b/src/abi/ethereum/other/BRIDGE.json index 94de998c5..9b7d6a609 100644 --- a/src/abi/ethereum/other/BRIDGE.json +++ b/src/abi/ethereum/other/BRIDGE.json @@ -1,153 +1,155 @@ -[ - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "_sidechainTokens", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address payable", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "txHash", - "type": "bytes32" - }, - { - "internalType": "uint8[]", - "name": "v", - "type": "uint8[]" - }, - { - "internalType": "bytes32[]", - "name": "r", - "type": "bytes32[]" - }, - { - "internalType": "bytes32[]", - "name": "s", - "type": "bytes32[]" - } - ], - "name": "receiveByEthereumAssetAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "sidechainAssetId", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "txHash", - "type": "bytes32" - }, - { - "internalType": "uint8[]", - "name": "v", - "type": "uint8[]" - }, - { - "internalType": "bytes32[]", - "name": "r", - "type": "bytes32[]" - }, - { - "internalType": "bytes32[]", - "name": "s", - "type": "bytes32[]" - } - ], - "name": "receiveBySidechainAssetId", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "to", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - } - ], - "name": "sendERC20ToSidechain", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "to", - "type": "bytes32" - } - ], - "name": "sendEthToSidechain", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] \ No newline at end of file +{ + "abi": [ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "_sidechainTokens", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address payable", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint8[]", + "name": "v", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "r", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "s", + "type": "bytes32[]" + } + ], + "name": "receiveByEthereumAssetAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "sidechainAssetId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint8[]", + "name": "v", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "r", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "s", + "type": "bytes32[]" + } + ], + "name": "receiveBySidechainAssetId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "sendERC20ToSidechain", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "to", + "type": "bytes32" + } + ], + "name": "sendEthToSidechain", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } + ] +} diff --git a/src/abi/ethereum/other/ERC20.json b/src/abi/ethereum/other/ERC20.json index 0623a6669..528b15672 100644 --- a/src/abi/ethereum/other/ERC20.json +++ b/src/abi/ethereum/other/ERC20.json @@ -1,128 +1,130 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ +{ + "abi": [ + { + "inputs": [ { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", "name": "", - "type": "uint8" + "type": "uint256" } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { - "name": "to", + "internalType": "address", + "name": "spender", "type": "address" }, { + "internalType": "uint256", "name": "value", "type": "uint256" } - ], - "name": "transfer", - "outputs": [ + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ { + "internalType": "bool", "name": "", "type": "bool" } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ] +} diff --git a/src/abi/ethereum/other/moonbeam/xTokens.json b/src/abi/ethereum/other/moonbeam/xTokens.json deleted file mode 100644 index 34ee16fa2..000000000 --- a/src/abi/ethereum/other/moonbeam/xTokens.json +++ /dev/null @@ -1,94 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "currency_address", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint8", - "name": "parents", - "type": "uint8" - }, - { - "internalType": "bytes[]", - "name": "interior", - "type": "bytes[]" - } - ], - "internalType": "structXtokens.Multilocation", - "name": "destination", - "type": "tuple" - }, - { - "internalType": "uint64", - "name": "weight", - "type": "uint64" - } - ], - "name": "transfer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint8", - "name": "parents", - "type": "uint8" - }, - { - "internalType": "bytes[]", - "name": "interior", - "type": "bytes[]" - } - ], - "internalType": "structXtokens.Multilocation", - "name": "asset", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "uint8", - "name": "parents", - "type": "uint8" - }, - { - "internalType": "bytes[]", - "name": "interior", - "type": "bytes[]" - } - ], - "internalType": "structXtokens.Multilocation", - "name": "destination", - "type": "tuple" - }, - { - "internalType": "uint64", - "name": "weight", - "type": "uint64" - } - ], - "name": "transfer_multiasset", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/src/assets/img/networks/alphanet.svg b/src/assets/img/networks/alphanet.svg deleted file mode 100644 index 1714c1059..000000000 --- a/src/assets/img/networks/alphanet.svg +++ /dev/null @@ -1,136 +0,0 @@ - - diff --git a/src/assets/img/networks/moonbeam.svg b/src/assets/img/networks/moonbeam.svg deleted file mode 100644 index bfc782321..000000000 --- a/src/assets/img/networks/moonbeam.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - diff --git a/src/components/mixins/InternalConnectMixin.ts b/src/components/mixins/InternalConnectMixin.ts index cb5356ca3..15b61d701 100644 --- a/src/components/mixins/InternalConnectMixin.ts +++ b/src/components/mixins/InternalConnectMixin.ts @@ -3,13 +3,11 @@ import { Component, Mixins } from 'vue-property-decorator'; import TranslationMixin from '@/components/mixins/TranslationMixin'; import { PageNames } from '@/consts'; import { goTo } from '@/router'; -import { action, getter, mutation, state } from '@/store/decorators'; +import { getter, mutation, state } from '@/store/decorators'; import { formatAddress } from '@/utils'; @Component export default class InternalConnectMixin extends Mixins(TranslationMixin) { - @action.wallet.account.logout public logout!: () => Promise; - @state.wallet.account.address public soraAddress!: string; @getter.wallet.account.isLoggedIn public isLoggedIn!: boolean; @@ -22,10 +20,6 @@ export default class InternalConnectMixin extends Mixins(TranslationMixin) { this.setSoraAccountDialogVisibility(true); } - public disconnectSoraWallet(): void { - this.logout(); - } - public navigateToWallet(): void { goTo(PageNames.Wallet); } diff --git a/src/components/mixins/NetworkFormatterMixin.ts b/src/components/mixins/NetworkFormatterMixin.ts index 60989601f..3d43475bd 100644 --- a/src/components/mixins/NetworkFormatterMixin.ts +++ b/src/components/mixins/NetworkFormatterMixin.ts @@ -145,8 +145,6 @@ export default class NetworkFormatterMixin extends Mixins(TranslationMixin) { return 'acala'; case SubNetworkId.PolkadotAstar: return 'astar'; - case SubNetworkId.PolkadotMoonbeam: - return 'moonbeam'; case SubNetworkId.Kusama: return 'kusama'; case SubNetworkId.KusamaShiden: @@ -159,10 +157,6 @@ export default class NetworkFormatterMixin extends Mixins(TranslationMixin) { return 'sora-kusama'; case SubNetworkId.Liberland: return 'liberland'; - case SubNetworkId.Alphanet: - return 'alphanet'; - case SubNetworkId.AlphanetSora: - return 'sora-alphanet'; case SubNetworkId.AlphanetMoonbase: return 'moonbase'; default: diff --git a/src/components/mixins/WalletConnectMixin.ts b/src/components/mixins/WalletConnectMixin.ts index 9d627a959..83e87a60a 100644 --- a/src/components/mixins/WalletConnectMixin.ts +++ b/src/components/mixins/WalletConnectMixin.ts @@ -17,18 +17,15 @@ export default class WalletConnectMixin extends Mixins(InternalConnectMixin) { @state.web3.networkType networkType!: BridgeNetworkType; @getter.bridge.isSubBridge isSubBridge!: boolean; - @getter.bridge.isSubAccountType isSubAccountType!: boolean; @mutation.web3.setSubAccountDialogVisibility setSubAccountDialogVisibility!: (flag: boolean) => void; @mutation.web3.setSelectProviderDialogVisibility setSelectProviderDialogVisibility!: (flag: boolean) => void; @action.web3.changeEvmNetworkProvided changeEvmNetworkProvided!: AsyncFnWithoutArgs; @action.web3.selectEvmProvider selectEvmProvider!: (provider: Provider) => Promise; + @action.web3.resetEvmProviderConnection resetEvmProviderConnection!: FnWithoutArgs; @action.web3.disconnectExternalNetwork disconnectExternalNetwork!: AsyncFnWithoutArgs; - @action.web3.resetEvmProviderConnection disconnectEvmWallet!: FnWithoutArgs; - @action.web3.resetSubAccount disconnectSubWallet!: FnWithoutArgs; - connectSubWallet(): void { this.setSubAccountDialogVisibility(true); } diff --git a/src/components/pages/Bridge/AccountPanel.vue b/src/components/pages/Bridge/AccountPanel.vue index 7b73b3f87..937b192d2 100644 --- a/src/components/pages/Bridge/AccountPanel.vue +++ b/src/components/pages/Bridge/AccountPanel.vue @@ -1,37 +1,13 @@ diff --git a/src/components/pages/Bridge/TransferNotification.vue b/src/components/pages/Bridge/TransferNotification.vue index eb9c16a24..8b93f1c66 100644 --- a/src/components/pages/Bridge/TransferNotification.vue +++ b/src/components/pages/Bridge/TransferNotification.vue @@ -26,13 +26,10 @@ import { Component, Mixins } from 'vue-property-decorator'; import BridgeTransactionMixin from '@/components/mixins/BridgeTransactionMixin'; import { getter, state, mutation } from '@/store/decorators'; import { isOutgoingTransaction } from '@/utils/bridge/common/utils'; -import { subBridgeApi } from '@/utils/bridge/sub/api'; -import type { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; import ethersUtil from '@/utils/ethers-util'; import type { IBridgeTransaction } from '@sora-substrate/util'; import type { Whitelist, RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; -import type { SubNetwork } from '@sora-substrate/util/build/bridgeProxy/sub/types'; import type { BridgeNetworkId } from '@sora-substrate/util/build/bridgeProxy/types'; @Component({ @@ -45,7 +42,6 @@ import type { BridgeNetworkId } from '@sora-substrate/util/build/bridgeProxy/typ }) export default class BridgeTransferNotification extends Mixins(BridgeTransactionMixin) { @state.bridge.notificationData private notificationData!: Nullable; - @state.bridge.subBridgeConnector private subBridgeConnector!: SubNetworksConnector; @getter.wallet.account.whitelist private whitelist!: Whitelist; @getter.assets.assetDataByAddress private getAsset!: (addr?: string) => Nullable; @@ -77,22 +73,13 @@ export default class BridgeTransferNotification extends Mixins(BridgeTransaction return this.asset?.symbol ?? ''; } - get isSubEvm(): boolean { - return subBridgeApi.isEvmAccount(this.tx?.externalNetwork as SubNetwork); - } - - get isEvmNetwork(): boolean { - if (!this.tx?.externalNetworkType) return false; - if (this.tx.externalNetworkType === BridgeNetworkType.Sub && !this.isSubEvm) return false; - return true; - } - get addTokenBtnVisibility(): boolean { - if (!this.isEvmNetwork) return false; - - const address = this.asset?.externalAddress; + if (!this.tx?.externalNetworkType) return false; + if (this.tx.externalNetworkType === BridgeNetworkType.Sub) return false; - return !!address && !ethersUtil.isNativeEvmTokenAddress(address) && isOutgoingTransaction(this.tx); + return ( + !!this.asset && !ethersUtil.isNativeEvmTokenAddress(this.asset.externalAddress) && isOutgoingTransaction(this.tx) + ); } get txLink() { @@ -131,30 +118,11 @@ export default class BridgeTransferNotification extends Mixins(BridgeTransaction async addToken(): Promise { if (!this.asset) return; - try { - const { externalAddress, externalDecimals, symbol, address } = this.asset; - const image = this.whitelist[address]?.icon; - - let tokenAddress = externalAddress; - let tokenSymbol = symbol; - let tokenDecimals = +externalDecimals; - - if (this.isSubEvm) { - const adapter = this.subBridgeConnector.parachain; - if (!adapter) throw new Error('Adapter not found'); - const assetMeta = adapter.getAssetMeta(this.asset); - if (!assetMeta) throw new Error('Asset metadata not found'); - tokenAddress = adapter.assetIdToEvmContractAddress(externalAddress); - tokenSymbol = assetMeta.symbol; - tokenDecimals = assetMeta.decimals; - } - - await ethersUtil.addToken(tokenAddress, tokenSymbol, tokenDecimals, image); - } catch (error) { - console.error(error); - } finally { - this.visibility = false; - } + const { externalAddress, externalDecimals, symbol, address } = this.asset; + const image = this.whitelist[address]?.icon; + await ethersUtil.addToken(externalAddress, symbol, +externalDecimals, image); + + this.visibility = false; } } diff --git a/src/components/shared/Dialog/SelectSoraAccount.vue b/src/components/shared/Dialog/SelectSoraAccount.vue index bea8dffd7..095e52f18 100644 --- a/src/components/shared/Dialog/SelectSoraAccount.vue +++ b/src/components/shared/Dialog/SelectSoraAccount.vue @@ -33,7 +33,7 @@ export default class SelectSoraAccountDialog extends Mixins(TranslationMixin) { @getter.wallet.account.account public soraAccount!: Nullable; @action.wallet.account.loginAccount public loginAccount!: (account: WALLET_TYPES.PolkadotJsAccount) => Promise; - @action.wallet.account.logout public logout!: () => Promise; + @action.wallet.account.logout public logout!: (forgetAddress?: string) => Promise; @action.wallet.account.renameAccount public rename!: (data: { address: string; name: string }) => Promise; get chainApi() { diff --git a/src/consts/sub.ts b/src/consts/sub.ts index 8f7591fa3..d2d05bd7f 100644 --- a/src/consts/sub.ts +++ b/src/consts/sub.ts @@ -98,31 +98,6 @@ export const SUB_NETWORKS: Partial> = { }, ], }, - [SubNetworkId.PolkadotMoonbeam]: { - id: SubNetworkId.PolkadotMoonbeam, - name: 'Moonbeam', - nativeCurrency: { - name: 'GLMR', - symbol: 'GLMR', - decimals: 18, - }, - endpointUrls: ['https://rpc.api.moonbeam.network', 'https://moonbeam-rpc.dwellir.com'], - blockExplorerUrls: ['https://moonbeam.subscan.io'], - shortName: 'Moonbeam', - nodes: [ - { - chain: 'Moonbeam', - name: 'Moonbeam Foundation', - address: 'wss://wss.api.moonbeam.network', - }, - { - chain: 'Moonbeam', - name: 'Dwellir', - address: 'wss://moonbeam-rpc.dwellir.com', - }, - ], - evmId: 1284, - }, [SubNetworkId.Rococo]: { id: SubNetworkId.Rococo, name: 'Rococo', @@ -155,7 +130,7 @@ export const SUB_NETWORKS: Partial> = { { chain: 'Moonbase Relay Testnet', name: 'Parity', - address: 'wss://fro-moon-rpc-1-moonbase-relay-rpc-1.moonbase.ol-infra.network', + address: 'wss://frag-moonbase-relay-rpc-ws.g.moonbase.moonbeam.network', }, ], }, @@ -167,9 +142,8 @@ export const SUB_NETWORKS: Partial> = { symbol: 'GLMR', // "DEV" decimals: 18, }, - endpointUrls: ['https://rpc.api.moonbase.moonbeam.network', 'https://moonbase-rpc.dwellir.com'], - blockExplorerUrls: ['https://moonbase.subscan.io'], - shortName: 'Moonbase', + blockExplorerUrls: [], + shortName: 'Alpha', nodes: [ { chain: 'Moonbase Alpha', @@ -177,7 +151,6 @@ export const SUB_NETWORKS: Partial> = { address: 'wss://wss.api.moonbase.moonbeam.network', }, ], - evmId: 1287, }, // SORA Parachains [SubNetworkId.RococoSora]: { @@ -327,24 +300,10 @@ export const SUB_TRANSFER_FEES: SubNetworksFees = { [BridgeTxDirection.Incoming]: '0', }, }, - [SubNetworkId.Alphanet]: { - ALPHA: { - [BridgeTxDirection.Outgoing]: '2700000000', - [BridgeTxDirection.Incoming]: '0', - }, - }, [SubNetworkId.AlphanetMoonbase]: { - GLMR: { + ACA: { [BridgeTxDirection.Outgoing]: '34313700000000', [BridgeTxDirection.Incoming]: '0', }, - ALPHA: { - [BridgeTxDirection.Outgoing]: '44415350668', - [BridgeTxDirection.Incoming]: '46453162841', - }, - XOR: { - [BridgeTxDirection.Outgoing]: '8140448382622083802', - [BridgeTxDirection.Incoming]: '0', - }, }, }; diff --git a/src/store/assets/actions.ts b/src/store/assets/actions.ts index b0f6f2856..c0c378edd 100644 --- a/src/store/assets/actions.ts +++ b/src/store/assets/actions.ts @@ -91,6 +91,31 @@ async function getSubRegisteredAssets( if (!subNetwork) return []; const subNetworkId = subNetwork as SubNetwork; + + // [TODO] remove when non native parachain tokens are supported + if (subNetworkId === SubNetworkId.PolkadotAcala) { + return [ + { + '0x001ddbe1a880031da72f7ea421260bec635fa7d1aa72593d5412795408b6b2ba': { + address: '', + decimals: 12, + kind: 'Sidechain', + }, + }, + ]; + } + if (subNetworkId === SubNetworkId.PolkadotAstar) { + return [ + { + '0x009dd037fcb32f4fe17c513abd4641a2ece844d106e30788124f0c0acc6e748e': { + address: '', + decimals: 18, + kind: 'Sidechain', + }, + }, + ]; + } + const networkAssets = await subBridgeApi.getRegisteredAssets(subNetworkId); const registeredAssets = Object.entries(networkAssets).map(([soraAddress, assetData]) => { return { @@ -105,36 +130,6 @@ async function getSubRegisteredAssets( return registeredAssets; } -async function updateSubAssetsData(context: ActionContext): Promise { - const { state, commit, rootState, rootGetters } = assetsActionContext(context); - const { registeredAssets } = state; - - const { destinationNetwork, soraParachain, parachain } = rootState.bridge.subBridgeConnector; - - if (!subBridgeApi.isParachain(destinationNetwork)) return; - - if (!(soraParachain && parachain)) return; - - await Promise.all([soraParachain.connect(), parachain.connect()]); - - const updatedEntries = await Promise.all( - Object.entries(registeredAssets).map(async ([soraAddress, assetData]) => { - const asset = { ...assetData }; - const soraAsset = rootGetters.wallet.account.assetsDataTable[soraAddress]; - if (!asset.address && soraAsset) { - const multilocation = await subBridgeApi.soraParachainApi.getAssetMulilocation(soraAddress, soraParachain.api); - const id = await parachain.getAssetIdByMultilocation(soraAsset, multilocation); - asset.address = id; - } - return [soraAddress, asset]; - }) - ); - - const assets = Object.fromEntries(updatedEntries); - - commit.setRegisteredAssets(assets); -} - async function getRegisteredAssets(context: ActionContext): Promise[]> { const { rootState } = assetsActionContext(context); @@ -178,16 +173,14 @@ const actions = defineActions({ async updateRegisteredAssets(context): Promise { const { commit, rootState } = assetsActionContext(context); + // only for ETH bridge, because of sora assets broken registration + if (rootState.web3.networkType === BridgeNetworkType.Eth) { + commit.setRegisteredAssetsFetching(true); - commit.setRegisteredAssetsFetching(true); - - if (rootState.web3.networkType === BridgeNetworkType.Sub) { - await updateSubAssetsData(context); - } else { await updateEthAssetsData(context); - } - commit.setRegisteredAssetsFetching(false); + commit.setRegisteredAssetsFetching(false); + } }, }); diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index bec895a54..67d517d76 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -84,26 +84,6 @@ function getBridgeApi(context: ActionContext) { return ethBridgeApi; } -async function switchAmounts(context: ActionContext): Promise { - const { state, dispatch } = bridgeActionContext(context); - - if (state.focusedField === FocusedField.Received) { - await dispatch.setSendedAmount(state.amountReceived); - } else { - await dispatch.setReceivedAmount(state.amountSend); - } -} - -async function updateAmounts(context: ActionContext): Promise { - const { state, dispatch } = bridgeActionContext(context); - - if (state.focusedField === FocusedField.Received) { - await dispatch.setReceivedAmount(state.amountReceived); - } else { - await dispatch.setSendedAmount(state.amountSend); - } -} - function checkEvmNetwork(context: ActionContext): void { const { rootGetters } = bridgeActionContext(context); if (!rootGetters.web3.isValidNetwork) { @@ -414,7 +394,7 @@ async function updateSoraNetworkFee(context: ActionContext): Promise): Promise { +async function updateBalancesAndFees(context: ActionContext): Promise { const { dispatch } = bridgeActionContext(context); await Promise.allSettled([ @@ -468,14 +448,19 @@ const actions = defineActions({ }, async switchDirection(context): Promise { - const { commit, state } = bridgeActionContext(context); + const { commit, dispatch, state } = bridgeActionContext(context); commit.setSoraToEvm(!state.isSoraToEvm); commit.setAssetSenderBalance(); commit.setAssetRecipientBalance(); - await updateBalancesFeesAndAmounts(context); - await switchAmounts(context); + await updateBalancesAndFees(context); + + if (state.focusedField === FocusedField.Received) { + await dispatch.setSendedAmount(state.amountReceived); + } else { + await dispatch.setReceivedAmount(state.amountSend); + } }, async setAssetAddress(context, address?: string): Promise { @@ -489,9 +474,8 @@ const actions = defineActions({ dispatch.updateOutgoingMinLimit(), dispatch.updateOutgoingMaxLimit(), dispatch.updateIncomingMinLimit(), - updateBalancesFeesAndAmounts(context), + updateBalancesAndFees(context), ]); - await updateAmounts(context); }, async updateExternalBalance(context): Promise { @@ -583,7 +567,7 @@ const actions = defineActions({ const subscription = api.system.updated.subscribe(() => { updateExternalBlockNumber(context); - updateBalancesFeesAndAmounts(context); + updateBalancesAndFees(context); }); commit.setBlockUpdatesSubscription(subscription); diff --git a/src/store/bridge/getters.ts b/src/store/bridge/getters.ts index 2da5b4384..3d8a24dbd 100644 --- a/src/store/bridge/getters.ts +++ b/src/store/bridge/getters.ts @@ -110,22 +110,11 @@ const getters = defineGetters()({ return assetIds[0]; }, - isSubAccountType(...args): boolean { - const { rootState } = bridgeGetterContext(args); - const { networkSelected, networkType } = rootState.web3; - - if (networkType === BridgeNetworkType.Sub) { - return !subBridgeApi.isEvmAccount(networkSelected as SubNetwork); - } - - return false; - }, - externalAccount(...args): string { const { getters, rootState } = bridgeGetterContext(args); const { evmAddress, subAddress } = rootState.web3; - if (getters.isSubAccountType) { + if (getters.isSubBridge) { return subAddress; } else { return evmAddress; @@ -139,7 +128,7 @@ const getters = defineGetters()({ if (state.isSoraToEvm) return soraAddress; - return getters.isSubAccountType ? chainAddress(subAddress, state.subBridgeConnector) : evmAddress; + return getters.isSubBridge ? chainAddress(subAddress, state.subBridgeConnector) : evmAddress; }, senderName(...args): string { @@ -149,7 +138,7 @@ const getters = defineGetters()({ if (state.isSoraToEvm) return soraName; - return getters.isSubAccountType ? subAddressName : ''; + return getters.isSubBridge ? subAddressName : ''; }, recipient(...args): string { @@ -159,7 +148,7 @@ const getters = defineGetters()({ if (!state.isSoraToEvm) return soraAddress; - return getters.isSubAccountType ? chainAddress(subAddress, state.subBridgeConnector) : evmAddress; + return getters.isSubBridge ? chainAddress(subAddress, state.subBridgeConnector) : evmAddress; }, recipientName(...args): string { @@ -169,7 +158,7 @@ const getters = defineGetters()({ if (!state.isSoraToEvm) return soraName; - return getters.isSubAccountType ? subAddressName : ''; + return getters.isSubBridge ? subAddressName : ''; }, isEthBridge(...args): boolean { diff --git a/src/store/moonpay/actions.ts b/src/store/moonpay/actions.ts index eded9490d..b23cd4384 100644 --- a/src/store/moonpay/actions.ts +++ b/src/store/moonpay/actions.ts @@ -84,7 +84,7 @@ const actions = defineActions({ }; } else { // Parse ERC-20 transfer - const abi = SmartContracts[SmartContractType.ERC20]; + const abi = SmartContracts[SmartContractType.ERC20].abi; const inter = new ethers.Interface(abi); const decodedInput = inter.parseTransaction({ data: tx.data }); diff --git a/src/store/web3/actions.ts b/src/store/web3/actions.ts index 9fa2abe67..77ba5f684 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -1,8 +1,9 @@ import { BridgeNetworkType } from '@sora-substrate/util/build/bridgeProxy/consts'; import { SubNetworkId } from '@sora-substrate/util/build/bridgeProxy/sub/consts'; import { BridgeNetworkId } from '@sora-substrate/util/build/bridgeProxy/types'; -import { api as soraApi, accountUtils, WALLET_TYPES, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; +import { accountUtils, WALLET_TYPES, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; import { defineActions } from 'direct-vuex'; +import { ethers } from 'ethers'; import { KnownEthBridgeAsset, SmartContracts, SmartContractType } from '@/consts/evm'; import { web3ActionContext } from '@/store/web3'; @@ -172,18 +173,21 @@ const actions = defineActions({ }, async changeEvmNetworkProvided(context): Promise { - const { getters } = web3ActionContext(context); + const { getters, state } = web3ActionContext(context); const { selectedNetwork } = getters; + const { networkType } = state; - if (!selectedNetwork) return; - - await ethersUtil.switchOrAddChain(selectedNetwork); + if (selectedNetwork && networkType !== BridgeNetworkType.Sub) { + await ethersUtil.switchOrAddChain(selectedNetwork); + } }, async getSupportedApps(context): Promise { const { commit, getters } = web3ActionContext(context); - // production mock - let supportedApps = { + // [TODO] uncomment + // const supportedApps = await api.bridgeProxy.getListApps(); + // [TODO] remove this production mock after nodes update + const supportedApps = { [BridgeNetworkType.Eth]: {}, [BridgeNetworkType.Evm]: {}, [BridgeNetworkType.Sub]: [ @@ -197,12 +201,6 @@ const actions = defineActions({ ], }; - try { - supportedApps = await soraApi.bridgeProxy.getListApps(); - } catch (error) { - console.error(error); - } - commit.setSupportedApps(supportedApps as any); const networks = getters.availableNetworks[BridgeNetworkType.Sub]; @@ -249,12 +247,13 @@ const actions = defineActions({ if (!soraAssetId) { return ''; } - const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other]; + const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other].abi; const contractAddress = getters.contractAddress(KnownEthBridgeAsset.Other); if (!contractAddress || !contractAbi) { throw new Error('Contract address/abi is not found'); } - const contractInstance = await ethersUtil.getContract(contractAddress, contractAbi); + const signer = await ethersUtil.getSigner(); + const contractInstance = new ethers.Contract(contractAddress, contractAbi, signer); const methodArgs = [soraAssetId]; const externalAddress = await contractInstance._sidechainTokens(...methodArgs); // Not (wrong) registered Sora asset on bridge contract return '0' address (like native token) diff --git a/src/store/web3/getters.ts b/src/store/web3/getters.ts index 10533d3db..7e3c675c8 100644 --- a/src/store/web3/getters.ts +++ b/src/store/web3/getters.ts @@ -14,7 +14,7 @@ import type { BridgeNetworkId } from '@sora-substrate/util/build/bridgeProxy/typ const getters = defineGetters()({ availableNetworks(...args): Record>> { - const { state } = web3GetterContext(args); + const { state, rootState } = web3GetterContext(args); const hashi = [state.ethBridgeEvmNetwork].reduce((buffer, id) => { const data = EVM_NETWORKS[id]; @@ -79,21 +79,11 @@ const getters = defineGetters()({ }, isValidNetwork(...args): boolean { - const { state, getters } = web3GetterContext(args); - const { evmProviderNetwork } = state; - const { selectedNetwork } = getters; - - if (!selectedNetwork) return false; + const { state } = web3GetterContext(args); - if (state.networkType === BridgeNetworkType.Sub) { - if (selectedNetwork.evmId) { - return evmProviderNetwork === selectedNetwork.evmId; - } else { - return true; - } - } + if (state.networkType === BridgeNetworkType.Sub) return true; - return evmProviderNetwork === selectedNetwork.id; + return state.evmProviderNetwork === state.networkSelected; }, contractAddress(...args): (asset: KnownEthBridgeAsset) => Nullable { diff --git a/src/styles/common.scss b/src/styles/common.scss index 380a49e41..396a81201 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -22,7 +22,7 @@ $country-emoji-font: 'Twemoji Country Flags'; } } -$networks: 'sora', 'ethereum', 'ethereum-classic', 'avalanche', 'klaytn', 'polygon', 'binance-smart-chain', 'rococo', 'polkadot', 'kusama', 'acala', 'astar', 'shiden', 'liberland', 'alphanet', 'moonbase', 'moonbeam', 'sora-polkadot', 'sora-kusama'; +$networks: 'sora', 'ethereum', 'ethereum-classic', 'avalanche', 'klaytn', 'polygon', 'binance-smart-chain', 'rococo', 'polkadot', 'kusama', 'acala', 'astar', 'shiden', 'liberland', 'moonbase', 'sora-polkadot', 'sora-kusama'; /* Networks Icons */ .network-icon { display: inline-block; @@ -41,17 +41,15 @@ $networks: 'sora', 'ethereum', 'ethereum-classic', 'avalanche', 'klaytn', 'polyg } } - @each $network in 'rococo', 'alphanet' { - &--sora-#{$network} { - background-image: url('~@/assets/img/networks/#{$network}.svg'); - &::after { - content: ''; - display: block; - width: 50%; - height: 50%; - filter: drop-shadow(2px 4px 6px black); - background-image: url('~@/assets/img/networks/sora.svg'); - } + &--sora-rococo { + background-image: url('~@/assets/img/networks/rococo.svg'); + &::after { + content: ''; + display: block; + width: 50%; + height: 50%; + filter: drop-shadow(2px 4px 6px black); + background-image: url('~@/assets/img/networks/sora.svg'); } } } diff --git a/src/types/bridge.ts b/src/types/bridge.ts index f0e306b5d..1227f5467 100644 --- a/src/types/bridge.ts +++ b/src/types/bridge.ts @@ -19,8 +19,6 @@ export interface NetworkData { endpointUrls?: string[]; /** Nodes for Substrate network */ nodes?: Node[]; - /** Evm chain id for substrate network */ - evmId?: number; } export type SubNetworksFees = Partial>>>; diff --git a/src/utils/bridge/common/classes.ts b/src/utils/bridge/common/classes.ts index 9ebef9dd5..12d6a6f7f 100644 --- a/src/utils/bridge/common/classes.ts +++ b/src/utils/bridge/common/classes.ts @@ -18,7 +18,6 @@ import type { IBridgeConstructorOptions, TransactionHandlerPayload, } from '@/utils/bridge/common/types'; -import { isUnsignedTx } from '@/utils/bridge/common/utils'; import type { IBridgeTransaction } from '@sora-substrate/util'; @@ -181,27 +180,25 @@ export class BridgeReducer implements IB } private async checkTransactionBlockId(id: string): Promise { - const { blockId, externalBlockId } = this.getTransaction(id); + const { blockId } = this.getTransaction(id); - if (blockId || externalBlockId) return; + if (blockId) return; await delay(1_000); await this.checkTransactionBlockId(id); } async waitForTransactionBlockId(id: string): Promise { - const tx = this.getTransaction(id); + const { txId } = this.getTransaction(id); - if (isUnsignedTx(tx)) { - throw new Error( - `[${this.constructor.name}]: Transaction "id" or "externalHash" is empty, first sign the transaction` - ); + if (!txId) { + throw new Error(`[${this.constructor.name}]: Transaction "id" is empty, first sign the transaction`); } try { await Promise.race([ this.checkTransactionBlockId(id), - delay(BLOCK_PRODUCE_TIME * 10, false), // 60s + delay(BLOCK_PRODUCE_TIME * 6, false), // 36s ]); } catch (error) { console.info(`[${this.constructor.name}]: Implement "blockId" restoration by "txId"`); diff --git a/src/utils/bridge/common/utils.ts b/src/utils/bridge/common/utils.ts index 3cb8cd989..37c9737a9 100644 --- a/src/utils/bridge/common/utils.ts +++ b/src/utils/bridge/common/utils.ts @@ -1,11 +1,10 @@ -import { isEthOperation, isEvmOperation, isSubstrateOperation } from '@sora-substrate/util'; +import { Operation, isEthOperation, isEvmOperation, isSubstrateOperation } from '@sora-substrate/util'; import { api as soraApi } from '@soramitsu/soraneo-wallet-web'; import { ethers } from 'ethers'; -import type { GetTransaction, UpdateTransaction } from '@/utils/bridge/common/types'; -import { isUnsignedTx as isUnsignedEthTx, isOutgoingTx as isOutgoingEthTx } from '@/utils/bridge/eth/utils'; -import { isUnsignedTx as isUnsignedEvmTx, isOutgoingTx as isOutgoingEvmTx } from '@/utils/bridge/evm/utils'; -import { isUnsignedTx as isUnsignedSubTx, isOutgoingTx as isOutgoingSubTx } from '@/utils/bridge/sub/utils'; +import { isUnsignedTx as isUnsignedEthTx } from '@/utils/bridge/eth/utils'; +import { isUnsignedTx as isUnsignedEvmTx } from '@/utils/bridge/evm/utils'; +import { isUnsignedTx as isUnsignedSubTx } from '@/utils/bridge/sub/utils'; import ethersUtil from '@/utils/ethers-util'; import type { ApiPromise } from '@polkadot/api'; @@ -14,13 +13,6 @@ import type { EthHistory } from '@sora-substrate/util/build/bridgeProxy/eth/type import type { EvmHistory } from '@sora-substrate/util/build/bridgeProxy/evm/types'; import type { SubHistory } from '@sora-substrate/util/build/bridgeProxy/sub/types'; -export const getEvmTransactionFee = (tx: ethers.TransactionResponse | ethers.TransactionReceipt) => { - const gasPrice = tx.gasPrice; - const gasAmount = 'gasUsed' in tx ? tx.gasUsed : tx.gasLimit; - - return ethersUtil.calcEvmFee(gasPrice, gasAmount); -}; - export const waitForEvmTransactionMined = async ( tx: ethers.TransactionResponse | null, replaceCallback?: (tx: ethers.TransactionResponse | null) => void @@ -85,11 +77,7 @@ export const getTransactionEvents = async (blockHash: string, transactionHash: s export const isOutgoingTransaction = (transaction: Nullable): boolean => { if (!transaction?.type) return false; - if (isEthOperation(transaction.type)) return isOutgoingEthTx(transaction as EthHistory); - if (isEvmOperation(transaction.type)) return isOutgoingEvmTx(transaction as EvmHistory); - if (isSubstrateOperation(transaction.type)) return isOutgoingSubTx(transaction as SubHistory); - - return false; + return [Operation.EthBridgeOutgoing, Operation.EvmOutgoing, Operation.SubstrateOutgoing].includes(transaction.type); }; export const isUnsignedTx = (transaction: Nullable): boolean => { @@ -101,40 +89,3 @@ export const isUnsignedTx = (transaction: Nullable): boolean return true; }; - -export const onEvmTransactionPending = async ( - id: string, - getTransaction: GetTransaction, - updateTransaction: UpdateTransaction -) => { - const tx = getTransaction(id); - const hash = tx.externalHash; - - if (!hash) throw new Error(`[onEvmTransactionPending] Evm transaction hash is empty`); - - const txResponse = await ethersUtil.getEvmTransaction(hash); - const txReceipt = await waitForEvmTransactionMined(txResponse, (replacedTx) => { - if (replacedTx) { - updateTransaction(id, { - externalHash: replacedTx.hash, - externalNetworkFee: getEvmTransactionFee(replacedTx), - }); - } - }); - - const { fee, blockNumber, blockHash } = txReceipt || {}; - - if (!(fee && blockNumber && blockHash)) { - updateTransaction(id, { externalHash: undefined, externalNetworkFee: undefined }); - throw new Error( - `[onEvmTransactionPending]: Ethereum transaction not found, hash: ${tx.externalHash}. 'externalHash' is reset` - ); - } - - // In EthHistory 'blockHeight' will store evm block number - updateTransaction(id, { - externalNetworkFee: fee.toString(), - externalBlockHeight: blockNumber, - externalBlockId: blockHash, - }); -}; diff --git a/src/utils/bridge/eth/classes/history.ts b/src/utils/bridge/eth/classes/history.ts index 0d4f20e0c..c122fa706 100644 --- a/src/utils/bridge/eth/classes/history.ts +++ b/src/utils/bridge/eth/classes/history.ts @@ -33,8 +33,8 @@ export default class EtherscanHistoryProvider extends EtherscanProvider { } const BRIDGE_INTERFACE = new ethers.Interface([ - ...SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.XOR], // XOR or VAL - ...SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other], // Other + ...SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.XOR].abi, // XOR or VAL + ...SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other].abi, // Other ]); const { ETH_BRIDGE_STATES } = WALLET_CONSTS; diff --git a/src/utils/bridge/eth/classes/reducers.ts b/src/utils/bridge/eth/classes/reducers.ts index ee7a2f40f..09c6c1e0f 100644 --- a/src/utils/bridge/eth/classes/reducers.ts +++ b/src/utils/bridge/eth/classes/reducers.ts @@ -3,10 +3,16 @@ import first from 'lodash/fp/first'; import { BridgeReducer } from '@/utils/bridge/common/classes'; import type { IBridgeReducerOptions, GetBridgeHistoryInstance, SignExternal } from '@/utils/bridge/common/types'; -import { getTransactionEvents, getEvmTransactionFee, onEvmTransactionPending } from '@/utils/bridge/common/utils'; +import { getTransactionEvents, waitForEvmTransactionMined } from '@/utils/bridge/common/utils'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; import type { EthBridgeHistory } from '@/utils/bridge/eth/classes/history'; -import { getTransaction, waitForApprovedRequest, waitForIncomingRequest } from '@/utils/bridge/eth/utils'; +import { + getTransaction, + getTransactionFee, + waitForApprovedRequest, + waitForIncomingRequest, +} from '@/utils/bridge/eth/utils'; +import ethersUtil from '@/utils/ethers-util'; import type { IBridgeTransaction } from '@sora-substrate/util'; import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; @@ -34,7 +40,36 @@ export class EthBridgeReducer extends BridgeReducer { } async onEvmPending(id: string): Promise { - await onEvmTransactionPending(id, this.getTransaction, this.updateTransactionParams); + const tx = this.getTransaction(id); + const hash = tx.externalHash; + + if (!hash) throw new Error(`[${this.constructor.name}]: Ethereum transaction hash is empty`); + + const txResponse = await ethersUtil.getEvmTransaction(hash); + const txReceipt = await waitForEvmTransactionMined(txResponse, (replacedTx) => { + if (replacedTx) { + this.updateTransactionParams(id, { + externalHash: replacedTx.hash, + externalNetworkFee: getTransactionFee(replacedTx), + }); + } + }); + + const { fee, blockNumber, blockHash } = txReceipt || {}; + + if (!(fee && blockNumber && blockHash)) { + this.updateTransactionParams(id, { externalHash: undefined, externalNetworkFee: undefined }); + throw new Error( + `[${this.constructor.name}]: Ethereum transaction not found, hash: ${tx.externalHash}. 'externalHash' is reset` + ); + } + + // In EthHistory 'blockHeight' will store evm block number + this.updateTransactionParams(id, { + externalNetworkFee: fee.toString(), + externalBlockHeight: blockNumber, + externalBlockId: blockHash, + }); } async onEvmSubmitted(id: string, signExternal: SignExternal): Promise { @@ -48,7 +83,7 @@ export class EthBridgeReducer extends BridgeReducer { // update after sign this.updateTransactionParams(id, { externalHash: signedTx.hash, - externalNetworkFee: getEvmTransactionFee(signedTx), + externalNetworkFee: getTransactionFee(signedTx), }); } catch (error: any) { // maybe transaction already completed, try to restore ethereum transaction hash diff --git a/src/utils/bridge/eth/utils.ts b/src/utils/bridge/eth/utils.ts index c6f4d3945..cde815bb4 100644 --- a/src/utils/bridge/eth/utils.ts +++ b/src/utils/bridge/eth/utils.ts @@ -1,6 +1,7 @@ import { Operation, FPNumber } from '@sora-substrate/util'; import { BridgeTxStatus } from '@sora-substrate/util/build/bridgeProxy/consts'; import { EthCurrencyType, EthAssetKind } from '@sora-substrate/util/build/bridgeProxy/eth/consts'; +import { ethers } from 'ethers'; import { SmartContractType, KnownEthBridgeAsset, SmartContracts } from '@/consts/evm'; import { asZeroValue } from '@/utils'; @@ -19,15 +20,13 @@ type EthTxParams = { request?: EthApprovedRequest; }; -export const isOutgoingTx = (tx: EthHistory): boolean => { - return tx.type === Operation.EthBridgeOutgoing; -}; - export const isUnsignedFromPart = (tx: EthHistory): boolean => { - if (isOutgoingTx(tx)) { + if (tx.type === Operation.EthBridgeOutgoing) { return !tx.blockId && !tx.txId; - } else { + } else if (tx.type === Operation.EthBridgeIncoming) { return !tx.externalHash; + } else { + return true; } }; @@ -125,12 +124,14 @@ export const waitForIncomingRequest = async (tx: EthHistory): Promise<{ hash: st export async function getIncomingEvmTransactionData({ asset, value, recipient, getContractAddress }: EthTxParams) { const isNativeEvmToken = ethersUtil.isNativeEvmTokenAddress(asset.externalAddress); + const signer = await ethersUtil.getSigner(); const accountId = ethersUtil.accountAddressToHex(recipient); + const amount = new FPNumber(value, asset.externalDecimals).toCodecString(); const contractAddress = getContractAddress(KnownEthBridgeAsset.Other) as string; - const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other]; - const contract = await ethersUtil.getContract(contractAddress, contractAbi); + const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other].abi; + const contract = new ethers.Contract(contractAddress, contractAbi, signer); const method = isNativeEvmToken ? 'sendEthToSidechain' : 'sendERC20ToSidechain'; const methodArgs = isNativeEvmToken @@ -161,14 +162,14 @@ export async function getOutgoingEvmTransactionData({ }: EthTxParams) { if (!request) throw new Error('request is required!'); + const signer = await ethersUtil.getSigner(); const symbol = asset.symbol as KnownEthBridgeAsset; const isValOrXor = [KnownEthBridgeAsset.XOR, KnownEthBridgeAsset.VAL].includes(symbol); const bridgeAsset: KnownEthBridgeAsset = isValOrXor ? symbol : KnownEthBridgeAsset.Other; - const contractAddress = getContractAddress(bridgeAsset) as string; - const contractAbi = SmartContracts[SmartContractType.EthBridge][bridgeAsset]; - const contract = await ethersUtil.getContract(contractAddress, contractAbi); + const contractAbi = SmartContracts[SmartContractType.EthBridge][bridgeAsset].abi; + const contract = new ethers.Contract(contractAddress, contractAbi, signer); const amount = new FPNumber(value, asset.externalDecimals).toCodecString(); const isEthereumCurrency = request.currencyType === EthCurrencyType.TokenAddress; @@ -291,3 +292,10 @@ export async function getEthNetworkFee( return ethersUtil.calcEvmFee(gasPrice, gasLimitTotal); } + +export const getTransactionFee = (tx: ethers.TransactionResponse | ethers.TransactionReceipt) => { + const gasPrice = tx.gasPrice; + const gasAmount = 'gasUsed' in tx ? tx.gasUsed : tx.gasLimit; + + return ethersUtil.calcEvmFee(gasPrice, gasAmount); +}; diff --git a/src/utils/bridge/evm/utils.ts b/src/utils/bridge/evm/utils.ts index 5dca0845c..8640cd6ee 100644 --- a/src/utils/bridge/evm/utils.ts +++ b/src/utils/bridge/evm/utils.ts @@ -4,15 +4,13 @@ import { evmBridgeApi } from '@/utils/bridge/evm/api'; import type { EvmHistory } from '@sora-substrate/util/build/bridgeProxy/evm/types'; -export const isOutgoingTx = (tx: EvmHistory): boolean => { - return tx.type === Operation.EvmOutgoing; -}; - export const isUnsignedTx = (tx: EvmHistory): boolean => { - if (isOutgoingTx(tx)) { + if (tx.type === Operation.EvmOutgoing) { return !tx.blockId && !tx.txId; + } else if (tx.type === Operation.EvmIncoming) { + return true; } else { - return !tx.externalHash; + return true; } }; diff --git a/src/utils/bridge/sub/classes/adapter.ts b/src/utils/bridge/sub/classes/adapter.ts index 4880e970a..ca668c2b0 100644 --- a/src/utils/bridge/sub/classes/adapter.ts +++ b/src/utils/bridge/sub/classes/adapter.ts @@ -10,10 +10,8 @@ import { determineTransferType } from '@/utils/bridge/sub/utils'; import { AcalaParachainAdapter } from './adapters/parachain/acala'; import { AstarParachainAdapter } from './adapters/parachain/astar'; import { MoonbaseParachainAdapter } from './adapters/parachain/moonbase'; -import { ParachainAdapter } from './adapters/parachain/parachain'; import { SoraParachainAdapter } from './adapters/parachain/sora'; -import { AlphanetRelaychainAdapter } from './adapters/relaychain/alphanet'; -import { RelaychainAdapter } from './adapters/relaychain/relaychain'; +import { RelaychainAdapter } from './adapters/relaychain'; import { LiberlandAdapter } from './adapters/standalone/liberland'; import { SubAdapter } from './adapters/substrate'; @@ -29,8 +27,8 @@ type PathNetworks = { export class SubNetworksConnector { public soraParachain?: SoraParachainAdapter; - public relaychain?: RelaychainAdapter; - public parachain?: ParachainAdapter; + public relaychain?: SubAdapter; + public parachain?: SubAdapter; public standalone?: SubAdapter; public destinationNetwork!: SubNetwork; @@ -97,9 +95,6 @@ export class SubNetworksConnector { protected getAdapter(network: SubNetwork) { if (subBridgeApi.isRelayChain(network)) { - if (network === SubNetworkId.Alphanet) { - return new AlphanetRelaychainAdapter(network); - } return new RelaychainAdapter(network); } if (subBridgeApi.isParachain(network)) { @@ -115,7 +110,6 @@ export class SubNetworksConnector { if (subBridgeApi.isSoraParachain(network)) { return new SoraParachainAdapter(network); } - return new ParachainAdapter(network); } if (subBridgeApi.isStandalone(network)) { if (network === SubNetworkId.Liberland) { @@ -198,38 +192,27 @@ export class SubNetworksConnector { await Promise.all(this.uniqueAdapters.map((c) => c.stop())); } - /** - * Transfer funds from SORA to destination network - */ - public async outgoingTransfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId: string) { - await subBridgeApi.transfer(asset, recipient, amount, this.destinationNetwork, historyId); - } - /** * Transfer funds from destination network to SORA */ - public async incomingTransfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId: string) { - if (subBridgeApi.isEvmAccount(this.destinationNetwork)) { - await this.network.transfer(asset, recipient, amount, historyId); - } else { - const { api, accountPair, signer } = this.accountApi; - - if (!accountPair) throw new Error(`[${this.constructor.name}] Account pair is not set.`); - - const historyItem = subBridgeApi.getHistory(historyId as string) ?? { - type: Operation.SubstrateIncoming, - symbol: asset.symbol, - assetAddress: asset.address, - amount: `${amount}`, - externalNetwork: this.destinationNetwork, - externalNetworkType: BridgeNetworkType.Sub, - from: subBridgeApi.address, // "from" is always SORA account address - to: this.accountApi.address, - }; - - const extrinsic = this.network.getTransferExtrinsic(asset, recipient, amount); - // submit extrinsic using SORA api, because current implementation using "subHistory" from SORA api scope - await subBridgeApi.submitApiExtrinsic(api, extrinsic as any, accountPair, signer, historyItem); - } + public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId?: string) { + const { api, accountPair, signer } = this.accountApi; + + if (!accountPair) throw new Error(`[${this.constructor.name}] Account pair is not set.`); + + const historyItem = subBridgeApi.getHistory(historyId as string) ?? { + type: Operation.SubstrateIncoming, + symbol: asset.symbol, + assetAddress: asset.address, + amount: `${amount}`, + externalNetwork: this.destinationNetwork, + externalNetworkType: BridgeNetworkType.Sub, + from: subBridgeApi.address, // "from" is always SORA account address + to: this.accountApi.address, + }; + + const extrinsic = this.network.getTransferExtrinsic(asset, recipient, amount); + // submit extrinsic using SORA api, because current implementation using "subHistory" from SORA api scope + await subBridgeApi.submitApiExtrinsic(api, extrinsic as any, accountPair, signer, historyItem); } } diff --git a/src/utils/bridge/sub/classes/adapters/parachain/acala.ts b/src/utils/bridge/sub/classes/adapters/parachain/acala.ts index 5f8b7a97d..05ea461ea 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/acala.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/acala.ts @@ -3,10 +3,10 @@ import { formatBalance } from '@sora-substrate/util/build/assets'; import { ZeroStringValue } from '@/consts'; -import { ParachainAdapter, type IParachainAssetMetadata } from './parachain'; +import { SubAdapter } from '../substrate'; import type { CodecString } from '@sora-substrate/util'; -import type { Asset, RegisteredAsset } from '@sora-substrate/util/build/assets/types'; +import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; enum AcalaPrimitivesCurrencyCurrencyId { Token = 'Token', @@ -29,6 +29,13 @@ type IAcalaCurrencyId = [AcalaPrimitivesCurrencyCurrencyId.Erc20]: string; }; +type IAcalaAssetMetadata = { + id: IAcalaCurrencyId; + symbol: string; + decimals: number; + minimalBalance: string; +}; + function getAcalaCurrencyId(nature: any): Nullable { if (nature.isNativeAssetId) { const value = nature.asNativeAssetId; @@ -43,18 +50,13 @@ function getAcalaCurrencyId(nature: any): Nullable { return null; } -export class AcalaParachainAdapter extends ParachainAdapter { - // overrides "SubAdapter" - public override async connect(): Promise { - await super.connect(); - await this.getAssetsMetadata(); - } +export class AcalaParachainAdapter extends SubAdapter { + protected assets: Record | null = null; - // overrides "ParachainAdapter" - protected override async getAssetsMetadata(): Promise { - if (Array.isArray(this.assets)) return; + protected async getAssetsMetadata(): Promise { + if (this.assets) return; - const assets: IParachainAssetMetadata[] = []; + const assets = {}; const entries = await (this.api.query.assetRegistry as any).assetMetadatas.entries(); for (const [key, option] of entries) { @@ -67,36 +69,32 @@ export class AcalaParachainAdapter extends ParachainAdapter { const decimals = option.value.decimals.toNumber(); const minimalBalance = option.value.minimalBalance.toString(); - assets.push({ id, symbol, decimals, minimalBalance }); + assets[symbol] = { id, symbol, decimals, minimalBalance }; } this.assets = Object.freeze(assets); } - // overrides "ParachainAdapter" - public override getAssetMeta(asset: RegisteredAsset): Nullable> { - if (!Array.isArray(this.assets)) return null; + private getAssetMeta(asset: RegisteredAsset): Nullable { + if (!(asset.symbol && this.assets)) return null; - return this.assets.find( - (item) => JSON.stringify(item.id) === asset.externalAddress || item.symbol === asset.symbol - ); + return this.assets[asset.symbol]; } - // overrides "ParachainAdapter" - public override async getAssetIdByMultilocation(asset: Asset, multilocation: any): Promise { - const v3Multilocation = multilocation; + // overrides SubAdapter + public override async connect(): Promise { + await super.connect(); + await this.getAssetsMetadata(); + } - const result = await (this.api.query.assetRegistry as any).locationToCurrencyIds(v3Multilocation); + protected override async getAssetDeposit(asset: RegisteredAsset): Promise { + const assetMeta = this.getAssetMeta(asset); - let id!: Nullable; + if (!assetMeta) return ZeroStringValue; - if (result.isEmpty) { - id = { [AcalaPrimitivesCurrencyCurrencyId.Token]: asset.symbol }; - } else { - id = getAcalaCurrencyId(result.unwrap()); - } + const minBalance = assetMeta.minimalBalance; - return id ? JSON.stringify(id) : ''; + return minBalance > '1' ? minBalance : ZeroStringValue; } protected override async getAccountAssetBalance( @@ -115,7 +113,7 @@ export class AcalaParachainAdapter extends ParachainAdapter { }, ZeroStringValue); } - // overrides "SubAdapter" + // overrides SubAdapter public override getTransferExtrinsic(asset: RegisteredAsset, recipient: string, amount: number | string) { const assetMeta = this.getAssetMeta(asset); @@ -150,4 +148,11 @@ export class AcalaParachainAdapter extends ParachainAdapter { 'Unlimited' ); } + + public override async getNetworkFee(asset: RegisteredAsset, sender: string, recipient: string): Promise { + /* Throws error until Substrate 5 migration */ + // return await super.getNetworkFee(asset, sender, recipient); + // Hardcoded value for Acala - 0.003 ACA + return '3000000000'; + } } diff --git a/src/utils/bridge/sub/classes/adapters/parachain/astar.ts b/src/utils/bridge/sub/classes/adapters/parachain/astar.ts index 3c4239e38..eab8b563f 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/astar.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/astar.ts @@ -1,32 +1,60 @@ import { FPNumber } from '@sora-substrate/util'; +import { formatBalance } from '@sora-substrate/util/build/assets'; import { ZeroStringValue } from '@/consts'; -import { ParachainAdapter } from './parachain'; +import { SubAdapter } from '../substrate'; import type { CodecString } from '@sora-substrate/util'; -import type { Asset, RegisteredAsset } from '@sora-substrate/util/build/assets/types'; +import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; + +type IAstarAssetMetadata = { + id: string; + symbol: string; + decimals: number; + minimalBalance: string; +}; + +export class AstarParachainAdapter extends SubAdapter { + protected assets: Record | null = null; + + protected async getAssetsMetadata(): Promise { + if (this.assets) return; + + const assets = {}; + const entries = await (this.api.query.assets as any).metadata.entries(); + + for (const [key, value] of entries) { + const id = key.args[0].toString(); + const symbol = new TextDecoder().decode(value.symbol); // bytes to string + const decimals = value.decimals.toNumber(); + const minimalBalance = value.deposit.toString(); + + assets[symbol] = { id, symbol, decimals, minimalBalance }; + } + + this.assets = Object.freeze(assets); + } + + private getAssetMeta(asset: RegisteredAsset): Nullable { + if (!(asset.symbol && this.assets)) return null; + + return this.assets[asset.symbol]; + } -export class AstarParachainAdapter extends ParachainAdapter { - // overrides "SubAdapter" public override async connect(): Promise { await super.connect(); await this.getAssetsMetadata(); } - // overrides "ParachainAdapter" - public override async getAssetIdByMultilocation(asset: Asset, multilocation: any): Promise { - const versionedMultilocation = { - V3: multilocation, - }; - - const result = await (this.api.query.xcAssetConfig as any).assetLocationToId(versionedMultilocation); + protected override async getAssetDeposit(asset: RegisteredAsset): Promise { + const assetMeta = this.getAssetMeta(asset); - if (result.isEmpty) return ''; + if (!assetMeta) return ZeroStringValue; - const id = result.unwrap().toString(); + const minBalance = assetMeta.minimalBalance; - return id; + return minBalance > '1' ? minBalance : ZeroStringValue; } protected override async getAccountAssetBalance( @@ -40,12 +68,6 @@ export class AstarParachainAdapter extends ParachainAdapter { return await this.assetsAccountRequest(accountAddress, assetMeta.id); } - public override getTransferExtrinsic(asset: RegisteredAsset, recipient: string, amount: number | string) { - return asset.symbol === this.chainSymbol - ? this.getNativeTransferExtrinsic(asset, recipient, amount) - : this.getAssetTransferExtrinsic(asset, recipient, amount); - } - /** * Transfer native token (ASTR) */ @@ -135,4 +157,17 @@ export class AstarParachainAdapter extends ParachainAdapter { 'Unlimited' ); } + + public override getTransferExtrinsic(asset: RegisteredAsset, recipient: string, amount: number | string) { + return asset.symbol === this.chainSymbol + ? this.getNativeTransferExtrinsic(asset, recipient, amount) + : this.getAssetTransferExtrinsic(asset, recipient, amount); + } + + public async getNetworkFee(asset: RegisteredAsset, sender: string, recipient: string): Promise { + /* Throws error until Substrate 5 migration */ + // return await super.getNetworkFee(asset, sender, recipient); + // Hardcoded value for Astar - 0.057 ASTR + return '57000000000000000'; + } } diff --git a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts index 76dfeebe6..3bc0669c7 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -1,147 +1,13 @@ -import { FPNumber, TransactionStatus } from '@sora-substrate/util'; -import { SubNetworkId } from '@sora-substrate/util/build/bridgeProxy/sub/consts'; -import BN from 'bignumber.js'; - -import xTokensAbi from '@/abi/ethereum/other/moonbeam/xTokens.json'; -import { ZeroStringValue } from '@/consts'; -import { SUB_NETWORKS } from '@/consts/sub'; -import { delay } from '@/utils'; -import { getEvmTransactionFee, onEvmTransactionPending } from '@/utils/bridge/common/utils'; -import { getTransaction, updateTransaction } from '@/utils/bridge/sub/utils'; -import ethersUtil from '@/utils/ethers-util'; - -import { ParachainAdapter } from './parachain'; - -import type { CodecString } from '@sora-substrate/util'; -import type { Asset, RegisteredAsset } from '@sora-substrate/util/build/assets/types'; -import type { ethers } from 'ethers'; - -const MOONBASE_DATA = SUB_NETWORKS[SubNetworkId.AlphanetMoonbase]; - -export class MoonbaseParachainAdapter extends ParachainAdapter { - protected nativeAssetContractAddress = '0x0000000000000000000000000000000000000802'; - protected xTokensContractAddress = '0x0000000000000000000000000000000000000804'; - - // overrides "WithConnectionApi" - override get chainSymbol(): string | undefined { - return MOONBASE_DATA?.nativeCurrency?.symbol; - } - - // overrides "WithConnectionApi" - public override formatAddress(address: string, _withPrefix = true): string { - // return evm address without changes - return address; - } - - // overrides "SubAdapter" - public override async connect(): Promise { - await super.connect(); - await this.getAssetsMetadata(); - } - - // overrides "SubAdapter" - protected override async getAccountAssetBalance( - accountAddress: string, - asset: RegisteredAsset - ): Promise { - return await this.assetsAccountRequest(accountAddress, asset.externalAddress); - } - - // Assets has not minimal deposit on Moonbase - // overrides "ParachainAdapter" - protected override async getAssetDeposit(asset: RegisteredAsset): Promise { - return ZeroStringValue; - } - - // overrides "ParachainAdapter" - public override async getAssetIdByMultilocation(asset: Asset, multilocation: any): Promise { - const assetType = { - XCM: multilocation, - }; - - const result = await (this.api.query.assetManager as any).assetTypeId(assetType); - - if (result.isEmpty) return ''; - - const id = result.unwrap().toString(); - - return id; - } - - // overrides "ParachainAdapter" - public override assetIdToEvmContractAddress(id: string): string { - const base = new BN(id).toString(16); - const padded = base.padStart(40, 'f'); - return `0x${padded}`; - } - - /** - * Convert parachain id to xcm junction - */ - protected toParachainAddress(id: number | undefined): string { - const selector = '0x00'; // Parachain selector - const address = Number(id ?? 0) - .toString(16) - .padStart(8, '0'); // bytes4 - - return selector + address; - } - - /** - * Convert substrate account address to xcm junction - */ - protected toAccountId32(address: string): string { - const selector = '0x01'; // AccountKey32 selector - const publicKey = this.getPublicKeyByAddress(address); // AccountId32 address in hex - const networkOption = '00'; // Network(Option) Null - - return selector + publicKey + networkOption; - } - - public override async transfer( - asset: RegisteredAsset, - recipient: string, - amount: string | number, - historyId: string - ) { - const currencyAddress = - asset.symbol === this.chainSymbol - ? this.nativeAssetContractAddress - : this.assetIdToEvmContractAddress(asset.externalAddress); - - const value = new FPNumber(amount, asset.externalDecimals).toCodecString(); - const weight = 5_000_000_000; // max weight, taken from successful xcm message on SORA parachain - - const parents = 1; - const parachainJunction = this.toParachainAddress(this.getSoraParachainId()); - const accountJunction = this.toAccountId32(recipient); - const interior = [parachainJunction, accountJunction]; // interior = X2 (the array has a length of 2) - const destination = [parents, interior]; - - const xTokens = await ethersUtil.getContract(this.xTokensContractAddress, xTokensAbi); - - const signedTx: ethers.TransactionResponse = await xTokens.transfer(currencyAddress, value, destination, weight); - - updateTransaction(historyId, { - externalHash: signedTx.hash, - externalNetworkFee: getEvmTransactionFee(signedTx), - status: TransactionStatus.InBlock, - }); - - // wait a little to update tx in storage - await delay(); - - // run non blocking promise to update tx data - onEvmTransactionPending(historyId, getTransaction, updateTransaction) - .then(() => { - updateTransaction(historyId, { - status: TransactionStatus.Finalized, - }); - }) - .catch(() => { - updateTransaction(historyId, { - status: TransactionStatus.Error, - }); - }); - } +import { u8aToHex } from '@polkadot/util'; +import { addressToEvm } from '@polkadot/util-crypto'; + +import { SubAdapter } from '../substrate'; + +export class MoonbaseParachainAdapter extends SubAdapter { + // overrides SubAdapter method + public formatAddress = (address?: string): string => { + if (!address) return ''; + // [TODO] research how to get evm address as on moonbase + return u8aToHex(addressToEvm(address)); + }; } diff --git a/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts b/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts deleted file mode 100644 index b6f7f7cfd..000000000 --- a/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { SubNetworkId } from '@sora-substrate/util/build/bridgeProxy/sub/consts'; - -import { ZeroStringValue } from '@/consts'; - -import { SubAdapter } from '../substrate'; - -import type { CodecString } from '@sora-substrate/util'; -import type { Asset, RegisteredAsset } from '@sora-substrate/util/build/assets/types'; - -export type IParachainAssetMetadata = { - id: AssetId; - symbol: string; - decimals: number; - minimalBalance?: string; -}; - -export class ParachainAdapter extends SubAdapter { - protected assets: readonly IParachainAssetMetadata[] | null = null; - - protected async getAssetsMetadata(): Promise { - if (Array.isArray(this.assets)) return; - - const assets: IParachainAssetMetadata[] = []; - const entries = await (this.api.query.assets as any).metadata.entries(); - - for (const [key, value] of entries) { - const id = key.args[0].toString(); - const symbol = new TextDecoder().decode(value.symbol); // bytes to string - const decimals = value.decimals.toNumber(); - - assets.push({ id, symbol, decimals }); - } - - this.assets = Object.freeze(assets); - } - - public getAssetMeta(asset: RegisteredAsset): Nullable> { - if (!Array.isArray(this.assets)) return null; - - return this.assets.find((item) => item.id === asset.externalAddress || item.symbol === asset.symbol); - } - - /** - * Get asset external address by multilocation (for "registeredAsset.externalAddress" struct)) - */ - public async getAssetIdByMultilocation(asset: Asset, multilocation: any): Promise { - throw new Error(`[${this.constructor.name}] "getAssetIdByMultilocation" method is not implemented`); - } - - /** - * Convert substrate asset id to evm token contract address - */ - public assetIdToEvmContractAddress(id: string): string { - throw new Error(`[${this.constructor.name}] "assetIdToEvmContractAddress" method is not implemented`); - } - - // overrides SubAdapter - protected override async getAssetDeposit(asset: RegisteredAsset): Promise { - const assetMeta = this.getAssetMeta(asset); - - if (!assetMeta) return ZeroStringValue; - - const { minimalBalance, id } = assetMeta; - - if (minimalBalance) return minimalBalance; - - return await this.assetMinBalanceRequest(id as string); - } - - public override async getNetworkFee(asset: RegisteredAsset, sender: string, recipient: string): Promise { - /* Throws error until Substrate 5 migration */ - // return await super.getNetworkFee(asset, sender, recipient); - // Hardcoded values - switch (this.subNetwork) { - case SubNetworkId.PolkadotAcala: - return '3000000000'; - case SubNetworkId.PolkadotAstar: - return '57000000000000000'; - case SubNetworkId.AlphanetMoonbase: - return '40000000000000'; - default: - return '0'; - } - } -} diff --git a/src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts b/src/utils/bridge/sub/classes/adapters/relaychain.ts similarity index 94% rename from src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts rename to src/utils/bridge/sub/classes/adapters/relaychain.ts index dc226789e..5adc2fcca 100644 --- a/src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts +++ b/src/utils/bridge/sub/classes/adapters/relaychain.ts @@ -1,7 +1,7 @@ import { FPNumber } from '@sora-substrate/util'; import { SubNetworkId } from '@sora-substrate/util/build/bridgeProxy/sub/consts'; -import { SubAdapter } from '../substrate'; +import { SubAdapter } from './substrate'; import type { CodecString } from '@sora-substrate/util'; import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; @@ -71,11 +71,11 @@ export class RelaychainAdapter extends SubAdapter { case SubNetworkId.Rococo: return toCodec(0.000125); case SubNetworkId.Alphanet: - return toCodec(0.019 + 0.037); + return toCodec(0.019); case SubNetworkId.Kusama: return toCodec(0.002); case SubNetworkId.Polkadot: - return toCodec(0.019 + 0.037); + return toCodec(0.059); default: return '0'; } diff --git a/src/utils/bridge/sub/classes/adapters/relaychain/alphanet.ts b/src/utils/bridge/sub/classes/adapters/relaychain/alphanet.ts deleted file mode 100644 index 0c16e220c..000000000 --- a/src/utils/bridge/sub/classes/adapters/relaychain/alphanet.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { SubNetworkId } from '@sora-substrate/util/build/bridgeProxy/sub/consts'; - -import { SUB_NETWORKS } from '@/consts/sub'; - -import { RelaychainAdapter } from './relaychain'; - -const ALPHANET_DATA = SUB_NETWORKS[SubNetworkId.Alphanet]; - -export class AlphanetRelaychainAdapter extends RelaychainAdapter { - // overrides "WithConnectionApi" - override get chainSymbol(): string | undefined { - return ALPHANET_DATA?.nativeCurrency?.symbol; - } - - // overrides "WithConnectionApi" - override get chainDecimals(): number | undefined { - return ALPHANET_DATA?.nativeCurrency?.decimals; - } - - // overrides "WithConnectionApi" - override get chainSS58(): number | undefined { - return 42; // (Substrate, 42) - } -} diff --git a/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts b/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts index db4bbdea7..dae7d0931 100644 --- a/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts +++ b/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts @@ -9,9 +9,7 @@ import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; export class LiberlandAdapter extends SubAdapter { protected override async getAssetDeposit(asset: RegisteredAsset): Promise { - const assetId = Number(asset.externalAddress); - - return await this.assetMinBalanceRequest(assetId); + return await this.assetsAssetMinBalanceRequest(Number(asset.externalAddress)); } protected override async getAccountAssetBalance( diff --git a/src/utils/bridge/sub/classes/adapters/substrate.ts b/src/utils/bridge/sub/classes/adapters/substrate.ts index 4b7ddf6e9..68856c606 100644 --- a/src/utils/bridge/sub/classes/adapters/substrate.ts +++ b/src/utils/bridge/sub/classes/adapters/substrate.ts @@ -135,10 +135,6 @@ class BaseSubAdapter extends WithConnectionApi { public getTransferExtrinsic(asset: RegisteredAsset, recipient: string, amount: string | number) { throw new Error(`[${this.constructor.name}] "getTransferExtrinsic" method is not implemented`); } - - public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId: string) { - throw new Error(`[${this.constructor.name}] "transfer" method is not implemented`); - } } export class SubAdapter extends BaseSubAdapter { @@ -158,7 +154,7 @@ export class SubAdapter extends BaseSubAdapter { }, ZeroStringValue); } - protected async assetMinBalanceRequest(assetId: number | string): Promise { + protected async assetsAssetMinBalanceRequest(assetId: number | string): Promise { return await this.withConnection(async () => { const result = await (this.api.query.assets as any).asset(assetId); diff --git a/src/utils/bridge/sub/classes/history.ts b/src/utils/bridge/sub/classes/history.ts index 20da783af..36a498a0a 100644 --- a/src/utils/bridge/sub/classes/history.ts +++ b/src/utils/bridge/sub/classes/history.ts @@ -12,14 +12,10 @@ import { getDepositedBalance, getMessageAcceptedNonces, getMessageDispatchedNonces, - getParachainSystemMessageHash, isMessageDispatchedNonces, getReceivedAmount, - isParaInclusion, + isEvent, isTransactionFeePaid, - isBridgeProxyHash, - isMessageAccepted, - isQueueMessage, } from '@/utils/bridge/sub/utils'; import type { ApiPromise } from '@polkadot/api'; @@ -49,7 +45,9 @@ const getTxEvents = (blockEvents: any[], txIndex: number) => { const findTxInBlock = async (blockHash: string, soraHash: string) => { const blockEvents = await api.system.getBlockEvents(blockHash); - const event = blockEvents.find((e) => isBridgeProxyHash(e, soraHash)); + const event = blockEvents.find( + (e) => isEvent(e, 'bridgeProxy', 'RequestStatusUpdate') && e.event.data?.[0]?.toString() === soraHash + ); if (!event) throw new Error('Unable to find "bridgeProxy.RequestStatusUpdate" event'); @@ -243,7 +241,7 @@ class SubBridgeHistory extends SubNetworksConnector { const soraFeeEvent = events.find((e) => isTransactionFeePaid(e)); history.soraNetworkFee = soraFeeEvent.event.data[1].toString(); // sended from SORA nonces - const [soraBatchNonce, soraMessageNonce] = getMessageAcceptedNonces(events); + const [soraBatchNonce, soraMessageNonce] = getMessageAcceptedNonces(events, this.soraApi); // api for Standalone network or SORA parachain const networkApi = this.getIntermediateApi(history); const networkBlockId = history.externalBlockId as string; @@ -252,7 +250,7 @@ class SubBridgeHistory extends SubNetworksConnector { const networkEventsReversed = [...networkEvents].reverse(); // Network received nonces const messageDispatchedIndex = networkEventsReversed.findIndex((e) => - isMessageDispatchedNonces(soraBatchNonce, soraMessageNonce, e) + isMessageDispatchedNonces(soraBatchNonce, soraMessageNonce, e, networkApi) ); if (messageDispatchedIndex === -1) { @@ -271,15 +269,19 @@ class SubBridgeHistory extends SubNetworksConnector { // SORA Parachain extrinsic events for next search const parachainExtrinsicEvents = networkEventsReversed.slice(messageDispatchedIndex); - // sended from SORA Parachain message hash (1) - const messageHash = getParachainSystemMessageHash(parachainExtrinsicEvents); + // sended from SORA Parachain to Relaychain message hash (1) + const messageToRelaychain = parachainExtrinsicEvents.find((e) => + isEvent(e, 'parachainSystem', 'UpwardMessageSent') + ); + // sended from SORA Parachain to Parachain message hash (2) + const messageToParachain = parachainExtrinsicEvents.find((e) => isEvent(e, 'xcmpQueue', 'XcmpMessageSent')); - if (!messageHash) { + if (!messageToRelaychain && !messageToParachain) { return await this.processOutgoingToSoraParachain(history, asset, parachainExtrinsicEvents); } - const isRelaychain = subBridgeApi.isRelayChain(externalNetwork); - const isParachain = subBridgeApi.isParachain(externalNetwork); + const isRelaychain = subBridgeApi.isRelayChain(externalNetwork) && messageToRelaychain; + const isParachain = subBridgeApi.isParachain(externalNetwork) && messageToParachain; if (!isRelaychain && !isParachain) { console.info(`[${history.id}] not "${externalNetwork}" transaction, skip;`); @@ -288,6 +290,7 @@ class SubBridgeHistory extends SubNetworksConnector { this.updateSoraParachainBlockData(history); + const messageHash = (messageToRelaychain ?? messageToParachain).event.data.messageHash.toString(); const relayChainBlockNumber = await subBridgeApi.soraParachainApi.getRelayChainBlockNumber( history.parachainBlockId as string, soraParachainApi @@ -331,7 +334,7 @@ class SubBridgeHistory extends SubNetworksConnector { const [receivedAmount, externalEventIndex] = getDepositedBalance( extrinsicEvents, history.to as string, - soraParachain + soraParachainApi ); // balances.Deposit event index history.externalEventIndex = externalEventIndex; @@ -364,15 +367,23 @@ class SubBridgeHistory extends SubNetworksConnector { txEvents: any[]; blockEvents: any[]; }): Promise> { + const { soraApi } = this; // Token is minted to account event - const [_, eventIndex] = getDepositedBalance(blockEvents, history.from as string, subBridgeApi); + const [_, eventIndex] = getDepositedBalance(blockEvents, history.from as string, this.soraApi); history.payload.eventIndex = eventIndex; // find SORA hash event index - const requestStatusUpdateEventIndex = txEvents.findIndex((e) => isBridgeProxyHash(e, history.id as string)); + const requestStatusUpdateEventIndex = txEvents.findIndex((e) => { + if (!isEvent(e, 'bridgeProxy', 'RequestStatusUpdate')) return false; + + const hash = e.event.data[0].toString(); + + return hash === history.id; + }); // Received on SORA nonces const [soraBatchNonce, soraMessageNonce] = getMessageDispatchedNonces( - txEvents.slice(requestStatusUpdateEventIndex) + txEvents.slice(requestStatusUpdateEventIndex), + soraApi ); // api for Standalone network or SORA parachain const networkApi = this.getIntermediateApi(history); @@ -381,9 +392,9 @@ class SubBridgeHistory extends SubNetworksConnector { const networkEvents = await api.system.getBlockEvents(networkBlockId, networkApi); // Network message sended to SORA const messageToSoraEvent = networkEvents.find((e) => { - if (!isMessageAccepted(e)) return false; + if (!isEvent(e, 'substrateBridgeOutboundChannel', 'MessageAccepted')) return false; - const [networkBatchNonce, networkMessageNonce] = getMessageAcceptedNonces([e]); + const [networkBatchNonce, networkMessageNonce] = getMessageAcceptedNonces([e], networkApi); return networkBatchNonce === soraBatchNonce && networkMessageNonce === soraMessageNonce; }); @@ -417,7 +428,7 @@ class SubBridgeHistory extends SubNetworksConnector { if (!soraParachainApi) throw new Error('SORA Parachain Api is not exists'); // If transfer received from Parachain, extrinsic events should have xcmpQueue.Success event - const messageEvent = networkExtrinsicEvents.find((e) => isQueueMessage(e)); + const messageEvent = networkExtrinsicEvents.find((e) => isEvent(e, 'xcmpQueue', 'Success')); const externalNetwork = history.externalNetwork as SubNetwork; const isRelayChain = subBridgeApi.isRelayChain(externalNetwork) && !messageEvent; const isParachain = @@ -436,7 +447,7 @@ class SubBridgeHistory extends SubNetworksConnector { ); if (isParachain) { - const messageHash = messageEvent.event.data[0].toString(); + const messageHash = messageEvent.event.data.messageHash.toString(); // Parachain block, found through relaychain validation data const parachainBlockId = await this.findParachainBlockIdOnRelaychain(history, relayChainBlockNumber, false); @@ -455,7 +466,7 @@ class SubBridgeHistory extends SubNetworksConnector { // relay chain should have send message in this blocks range const startSearch = relayChainBlockNumber; - const endSearch = startSearch - 10; + const endSearch = startSearch - 6; for (let relaychainBlockHeight = startSearch; relaychainBlockHeight >= endSearch; relaychainBlockHeight--) { const blockId = await api.system.getBlockHash(relaychainBlockHeight, this.externalApi); @@ -517,7 +528,7 @@ class SubBridgeHistory extends SubNetworksConnector { // relay chain should have send validation data in this blocks range const startSearch = relayChainBlockNumber; - const blocksRange = 10; + const blocksRange = 3; const endSearch = isOutgoing ? startSearch + blocksRange : startSearch - blocksRange; for (let relaychainBlockHeight = startSearch; relaychainBlockHeight !== endSearch; ) { @@ -525,14 +536,11 @@ class SubBridgeHistory extends SubNetworksConnector { const events = await api.system.getBlockEvents(blockId, relaychainApi); for (const e of events) { - if (!isParaInclusion(e)) continue; + if (!isEvent(e, 'paraInclusion', 'CandidateIncluded')) continue; const { descriptor } = e.event.data[0]; - const descriptorParaId = descriptor.paraId.toNumber(); - const paraId = network.getParachainId(); - - if (descriptorParaId !== paraId) continue; + if (descriptor.paraId.toNumber() !== network.getParachainId()) continue; history.relaychainBlockHeight = relaychainBlockHeight; history.relaychainBlockId = blockId; @@ -564,10 +572,10 @@ class SubBridgeHistory extends SubNetworksConnector { const extrinsicEvents = parachainBlockEvents.filter( ({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.toNumber() === extrinsicIndex ); + const messageSentEvent = extrinsicEvents.find((e) => isEvent(e, 'xcmpQueue', 'XcmpMessageSent')); - const messageSentHash = getParachainSystemMessageHash(extrinsicEvents); - - if (messageSentHash !== messageHash) continue; + if (!messageSentEvent) continue; + if (messageSentEvent.event.data[0].toString() !== messageHash) continue; const parachainBlockHeight = await api.system.getBlockNumber(parachainBlockId, externalApi); const signer = extrinsic.signer.toString(); @@ -596,17 +604,15 @@ class SubBridgeHistory extends SubNetworksConnector { endSearch: number ) { for (let blockHeight = startSearch; blockHeight <= endSearch; blockHeight++) { - let isReliableMessage = false; - try { const blockId = await api.system.getBlockHash(blockHeight, this.externalApi); const blockEvents = await api.system.getBlockEvents(blockId, this.externalApi); const messageEventIndex = blockEvents.findIndex((e) => { - if (isQueueMessage(e)) { - isReliableMessage = e.event.data[0].toString() === messageHash; + if (isEvent(e, 'messageQueue', 'Processed') || isEvent(e, 'xcmpQueue', 'Success')) { + const messageHashMatches = e.event.data[0].toString() === messageHash; - return true; + return messageHashMatches; } return false; }); @@ -620,7 +626,7 @@ class SubBridgeHistory extends SubNetworksConnector { const [receivedAmount, externalEventIndex] = getDepositedBalance( blockEvents.slice(0, messageEventIndex), history.to, - this.network + this.externalApi ); // Deposit event index @@ -637,11 +643,7 @@ class SubBridgeHistory extends SubNetworksConnector { return history; } catch { - if (isReliableMessage) { - break; - } else { - continue; - } + continue; } } diff --git a/src/utils/bridge/sub/classes/reducers.ts b/src/utils/bridge/sub/classes/reducers.ts index 031d09024..5aa0958bd 100644 --- a/src/utils/bridge/sub/classes/reducers.ts +++ b/src/utils/bridge/sub/classes/reducers.ts @@ -21,9 +21,8 @@ import { determineTransferType, getReceivedAmount, getParachainSystemMessageHash, - isXcmPalletAttempted, + isEvent, isTransactionFeePaid, - isQueueMessage, } from '@/utils/bridge/sub/utils'; import type { ApiRx } from '@polkadot/api'; @@ -106,11 +105,6 @@ export class SubBridgeReducer extends BridgeReducer { // update history data this.updateTransactionPayload(id, { startBlock }); } - - async waitForTxBlockAndStatus(id: string): Promise { - await this.waitForTransactionStatus(id); - await this.waitForTransactionBlockId(id); - } } export class SubBridgeIncomingReducer extends SubBridgeReducer { @@ -162,7 +156,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { // open connections await this.connector.start(); // sign transaction (from is sora account) - await this.connector.incomingTransfer(asset, tx.from as string, tx.amount as string, id); + await this.connector.transfer(asset, tx.from as string, tx.amount as string, id); // save start block when tx was signed await this.saveStartBlock(id); } @@ -189,7 +183,6 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { private async updateTxExternalData(id: string): Promise { const tx = this.getTransaction(id); - const adapter = this.connector.network; await adapter.connect(); @@ -207,7 +200,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { } if (this.transferType === SubTransferType.Relaychain) { - const xcmEvent = transactionEvents.find((e) => isXcmPalletAttempted(e)); + const xcmEvent = transactionEvents.find((e) => isEvent(e, 'xcmPallet', 'Attempted')); if (!xcmEvent?.event?.data?.[0]?.isComplete) { throw new Error(`[${this.constructor.name}]: Transaction is not completed`); @@ -216,10 +209,8 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { } private async updateTxIncomingData(id: string): Promise { - await this.waitForTxBlockAndStatus(id); - - if (subBridgeApi.isEvmAccount(this.getTransaction(id).externalNetwork as SubNetwork)) return; - + await this.waitForTransactionStatus(id); + await this.waitForTransactionBlockId(id); await this.updateTxSigningData(id); await this.updateTxExternalData(id); } @@ -240,8 +231,8 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { const isFirstStep = [SubTransferType.SoraParachain, SubTransferType.Standalone].includes(this.transferType); const sended = new FPNumber(tx.amount as string, this.asset.externalDecimals).toCodecString(); - const sender = tx.to as string; - const recipient = tx.from as string; + const from = tx.from as string; + const to = tx.to as string; let subscription!: Subscription; let messageNonce!: number; @@ -271,7 +262,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { if (!isStandalone) { assetSendEventIndex = events.findIndex((e) => - isAssetAddedToChannel(e, this.asset, recipient, sended, adapter) + isAssetAddedToChannel(e, this.asset, to, sended, adapter.api) ); if (assetSendEventIndex !== -1) { @@ -282,7 +273,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { } } else { assetSendEventIndex = events.findIndex((e) => - isSoraBridgeAppBurned(e, this.asset, sender, recipient, sended, adapter) + isSoraBridgeAppBurned(e, this.asset, from, to, sended, adapter.api) ); if (assetSendEventIndex !== -1) { @@ -293,7 +284,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { if (assetSendEventIndex === -1) return; blockNumber = blockHeight; - [batchNonce, messageNonce] = getMessageAcceptedNonces(events.slice(assetSendEventIndex)); + [batchNonce, messageNonce] = getMessageAcceptedNonces(events.slice(assetSendEventIndex), adapter.api); resolve(); } catch (error) { @@ -350,16 +341,16 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { try { const events = [...eventsVec.toArray()].reverse(); const substrateDispatchEventIndex = events.findIndex((e) => - isMessageDispatchedNonces(tx.payload.batchNonce, tx.payload.messageNonce, e) + isMessageDispatchedNonces(tx.payload.batchNonce, tx.payload.messageNonce, e, subBridgeApi.api) ); if (substrateDispatchEventIndex === -1) return; const foundedEvents = events.slice(substrateDispatchEventIndex); - soraHash = getBridgeProxyHash(foundedEvents); + soraHash = getBridgeProxyHash(foundedEvents, subBridgeApi.api); - [amount, eventIndex] = getDepositedBalance(foundedEvents, tx.from as string, subBridgeApi); + [amount, eventIndex] = getDepositedBalance(foundedEvents, tx.to as string, subBridgeApi.api); resolve(); } catch (error) { @@ -378,9 +369,9 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { } private async waitSoraBlockByHash(id: string): Promise { - const { hash, from, externalNetwork } = this.getTransaction(id); + const { hash, to, externalNetwork } = this.getTransaction(id); - if (!(hash && from && externalNetwork)) { + if (!(hash && to && externalNetwork)) { throw new Error(`[${this.constructor.name}] Lost transaction params`); } @@ -389,7 +380,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { try { await new Promise((resolve) => { - subscription = subBridgeApi.subscribeOnTransactionDetails(from, externalNetwork, hash).subscribe((data) => { + subscription = subBridgeApi.subscribeOnTransactionDetails(to, externalNetwork, hash).subscribe((data) => { if (data?.endBlock) { soraBlockNumber = data.endBlock; resolve(); @@ -426,7 +417,8 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { this.updateTransactionParams(id, { transactionState: BridgeTxStatus.Pending }); await this.checkTxId(id); - await this.waitForTxBlockAndStatus(id); + await this.waitForTransactionStatus(id); + await this.waitForTransactionBlockId(id); await this.waitForSendingExecution(id); await this.waitForIntermediateExecution(id); @@ -474,7 +466,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { // open connections await this.connector.start(); // sign transaction - await this.connector.outgoingTransfer(asset, tx.to as string, tx.amount as string, id); + await subBridgeApi.transfer(asset, tx.to as string, tx.amount as string, tx.externalNetwork as SubNetwork, id); // save start block when tx was signed await this.saveStartBlock(id); } @@ -488,10 +480,10 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { const transactionHash = tx.txId as string; const transactionEvents = await getTransactionEvents(blockHash, transactionHash, subBridgeApi.api); - const hash = getBridgeProxyHash(transactionEvents); + const hash = getBridgeProxyHash(transactionEvents, subBridgeApi.api); this.updateTransactionParams(id, { hash }); - const [batchNonce, messageNonce] = getMessageAcceptedNonces(transactionEvents); + const [batchNonce, messageNonce] = getMessageAcceptedNonces(transactionEvents, subBridgeApi.api); this.updateTransactionPayload(id, { batchNonce, messageNonce }); } @@ -529,7 +521,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { try { const events = [...eventsVec.toArray()].reverse(); const substrateDispatchEventIndex = events.findIndex((e) => - isMessageDispatchedNonces(tx.payload.batchNonce, tx.payload.messageNonce, e) + isMessageDispatchedNonces(tx.payload.batchNonce, tx.payload.messageNonce, e, adapter.api) ); if (substrateDispatchEventIndex === -1) return; @@ -542,11 +534,11 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { [amountReceived] = getDepositedBalance( events.slice(substrateDispatchEventIndex), tx.to as string, - adapter + adapter.api ); } } else { - messageHash = getParachainSystemMessageHash(events.slice(substrateDispatchEventIndex)); + messageHash = getParachainSystemMessageHash(events.slice(substrateDispatchEventIndex), adapter.api); } resolve(); @@ -609,16 +601,11 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { subscription = combineLatest([eventsObservable, blockNumberObservable]).subscribe( ([eventsVec, blockHeight]) => { - // when received message is equal to sended - let isReliableMessage = false; - try { const events = eventsVec.toArray(); const messageQueueProcessedEventIndex = events.findIndex((e) => { - if (isQueueMessage(e)) { - isReliableMessage = e.event.data[0].toString() === messageHash; - - return true; + if (isEvent(e, 'messageQueue', 'Processed') || isEvent(e, 'xcmpQueue', 'Success')) { + return e.event.data[0].toString() === messageHash; } return false; }); @@ -626,19 +613,16 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { if (messageQueueProcessedEventIndex === -1) return; blockNumber = blockHeight; - // throws error, is deposit not found + [amount, externalEventIndex] = getDepositedBalance( events.slice(0, messageQueueProcessedEventIndex), tx.to as string, - adapter + adapter.api ); resolve(); } catch (error) { - // The message is reliable, but the deposit was not found - if (isReliableMessage) { - reject(error); - } + reject(error); } } ); diff --git a/src/utils/bridge/sub/utils.ts b/src/utils/bridge/sub/utils.ts index a328fd3fe..401bd4a99 100644 --- a/src/utils/bridge/sub/utils.ts +++ b/src/utils/bridge/sub/utils.ts @@ -1,21 +1,15 @@ -import { FPNumber, Operation } from '@sora-substrate/util'; +import { FPNumber } from '@sora-substrate/util'; import { subBridgeApi } from '@/utils/bridge/sub/api'; import { SubTransferType } from '@/utils/bridge/sub/types'; -import type { CodecString, WithConnectionApi } from '@sora-substrate/util'; +import type { ApiPromise } from '@polkadot/api'; +import type { CodecString } from '@sora-substrate/util'; import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; import type { SubNetwork, SubHistory } from '@sora-substrate/util/build/bridgeProxy/sub/types'; -export const isOutgoingTx = (tx: SubHistory): boolean => { - return tx.type === Operation.SubstrateOutgoing; -}; - export const isUnsignedTx = (tx: SubHistory): boolean => { - const signId = - subBridgeApi.isEvmAccount(tx.externalNetwork as SubNetwork) && !isOutgoingTx(tx) ? tx.externalHash : tx.txId; - - return !tx.blockId && !signId; + return !tx.blockId && !tx.txId; }; export const getTransaction = (id: string): SubHistory => { @@ -44,26 +38,8 @@ export const determineTransferType = (network: SubNetwork) => { } }; -export const isEvent = (e, section: string, method: string) => { - return e.event.section === section && e.event.method === method; -}; - -export const isQueueMessage = (e) => - isEvent(e, 'messageQueue', 'Processed') || - isEvent(e, 'xcmpQueue', 'Success') || - isEvent(e, 'xcmpQueue', 'Fail') || - isEvent(e, 'dmpQueue', 'ExecutedDownward'); - -export const isParaInclusion = (e) => isEvent(e, 'paraInclusion', 'CandidateIncluded'); - -export const isXcmPalletAttempted = (e) => isEvent(e, 'xcmPallet', 'Attempted'); - -export const isTransactionFeePaid = (e) => isEvent(e, 'transactionPayment', 'TransactionFeePaid'); - -export const isBridgeProxyUpdate = (e) => isEvent(e, 'bridgeProxy', 'RequestStatusUpdate'); - -export const getBridgeProxyHash = (events: Array): string => { - const bridgeProxyEvent = events.find((e) => isBridgeProxyUpdate(e)); +export const getBridgeProxyHash = (events: Array, api: ApiPromise): string => { + const bridgeProxyEvent = events.find((e) => api.events.bridgeProxy.RequestStatusUpdate.is(e.event)); if (!bridgeProxyEvent) { throw new Error(`Unable to find "bridgeProxy.RequestStatusUpdate" event`); @@ -72,16 +48,14 @@ export const getBridgeProxyHash = (events: Array): string => { return bridgeProxyEvent.event.data[0].toString(); }; -export const isBridgeProxyHash = (e: any, hash: string): boolean => { - if (!isBridgeProxyUpdate(e)) return false; - - const proxyHash = e.event.data[0].toString(); - - return hash === proxyHash; +export const isEvent = (e, section: string, method: string) => { + return e.event.section === section && e.event.method === method; }; -export const getDepositedBalance = (events: Array, to: string, chainApi: WithConnectionApi): [string, number] => { - const recipient = chainApi.formatAddress(to).toLowerCase(); +export const isTransactionFeePaid = (e) => isEvent(e, 'transactionPayment', 'TransactionFeePaid'); + +export const getDepositedBalance = (events: Array, to: string, api: ApiPromise): [string, number] => { + const recipient = subBridgeApi.formatAddress(to); const index = events.findIndex((e) => { let eventRecipient = ''; @@ -90,15 +64,11 @@ export const getDepositedBalance = (events: Array, to: string, chainApi: Wi eventRecipient = e.event.data.who.toString(); } else if (isEvent(e, 'assets', 'Transfer')) { eventRecipient = e.event.data[1].toString(); - } else if (isEvent(e, 'assets', 'Issued')) { - eventRecipient = e.event.data.owner.toString(); } if (!eventRecipient) return false; - const formatted = chainApi.formatAddress(eventRecipient).toLowerCase(); - - return formatted === recipient; + return subBridgeApi.formatAddress(eventRecipient) === recipient; }); if (index === -1) throw new Error(`Unable to find balance deposit like event`); @@ -118,22 +88,22 @@ export const getReceivedAmount = (sendedAmount: string, receivedAmount: CodecStr return { amount: amount2, transferFee }; }; -export const getParachainSystemMessageHash = (events: Array) => { +export const getParachainSystemMessageHash = (events: Array, api: ApiPromise) => { const parachainSystemEvent = events.find( - (e) => isEvent(e, 'parachainSystem', 'UpwardMessageSent') || isEvent(e, 'xcmpQueue', 'XcmpMessageSent') + (e) => api.events.parachainSystem.UpwardMessageSent.is(e.event) || api.events.xcmpQueue.XcmpMessageSent.is(e.event) ); if (!parachainSystemEvent) { throw new Error(`Unable to find "parachainSystem.UpwardMessageSent" event`); } - return parachainSystemEvent.event.data[0].toString(); + return parachainSystemEvent.event.data.messageHash.toString(); }; -export const isMessageAccepted = (e) => isEvent(e, 'substrateBridgeOutboundChannel', 'MessageAccepted'); - -export const getMessageAcceptedNonces = (events: Array): [number, number] => { - const messageAcceptedEvent = events.find((e) => isMessageAccepted(e)); +export const getMessageAcceptedNonces = (events: Array, api: ApiPromise): [number, number] => { + const messageAcceptedEvent = events.find((e) => + api.events.substrateBridgeOutboundChannel.MessageAccepted.is(e.event) + ); if (!messageAcceptedEvent) { throw new Error('Unable to find "substrateBridgeOutboundChannel.MessageAccepted" event'); @@ -145,10 +115,8 @@ export const getMessageAcceptedNonces = (events: Array): [number, number] = return [batchNonce, messageNonce]; }; -export const isMessageDispatched = (e) => isEvent(e, 'substrateDispatch', 'MessageDispatched'); - -export const getMessageDispatchedNonces = (events: Array): [number, number] => { - const messageDispatchedEvent = events.find((e) => isMessageDispatched(e)); +export const getMessageDispatchedNonces = (events: Array, api: ApiPromise): [number, number] => { + const messageDispatchedEvent = events.find((e) => api.events.substrateDispatch.MessageDispatched.is(e.event)); if (!messageDispatchedEvent) { throw new Error('Unable to find "substrateDispatch.MessageDispatched" event'); @@ -161,8 +129,13 @@ export const getMessageDispatchedNonces = (events: Array): [number, number] return [eventBatchNonce, eventMessageNonce]; }; -export const isMessageDispatchedNonces = (sendedBatchNonce: number, sendedMessageNonce: number, e: any): boolean => { - if (!isMessageDispatched(e)) return false; +export const isMessageDispatchedNonces = ( + sendedBatchNonce: number, + sendedMessageNonce: number, + e: any, + api: ApiPromise +): boolean => { + if (!api.events.substrateDispatch.MessageDispatched.is(e.event)) return false; const { batchNonce, messageNonce } = e.event.data[0]; @@ -183,13 +156,13 @@ export const isAssetAddedToChannel = ( asset: RegisteredAccountAsset, to: string, sended: CodecString, - chainApi: WithConnectionApi + api: ApiPromise ): boolean => { - if (!isEvent(e, 'xcmApp', 'AssetAddedToChannel')) return false; + if (!api.events.xcmApp.AssetAddedToChannel.is(e.event)) return false; const { amount, assetId, recipient } = e.event.data[0].asTransfer; // address check - if (chainApi.formatAddress(recipient.toString()) !== chainApi.formatAddress(to)) return false; + if (subBridgeApi.formatAddress(recipient.toString()) !== subBridgeApi.formatAddress(to)) return false; // asset check if (assetId.toString() !== asset.address) return false; // amount check @@ -206,9 +179,9 @@ export const isSoraBridgeAppBurned = ( from: string, to: string, sended: CodecString, - chainApi: WithConnectionApi + api: ApiPromise ) => { - if (!isEvent(e, 'soraBridgeApp', 'Burned')) return false; + if (!api.events.soraBridgeApp.Burned.is(e.event)) return false; const [networkIdCodec, assetIdCodec, senderCodec, recipientCodec, amountCodec] = e.event.data; @@ -220,8 +193,8 @@ export const isSoraBridgeAppBurned = ( const amount = amountCodec.toString(); // address check - if (chainApi.formatAddress(sender) !== chainApi.formatAddress(from)) return false; - if (chainApi.formatAddress(recipient) !== chainApi.formatAddress(to)) return false; + if (subBridgeApi.formatAddress(sender) !== subBridgeApi.formatAddress(from)) return false; + if (subBridgeApi.formatAddress(recipient) !== subBridgeApi.formatAddress(to)) return false; // asset check if (assetId !== asset.externalAddress) return false; // amount check diff --git a/src/utils/ethers-util.ts b/src/utils/ethers-util.ts index 3a11feb11..b64ccebe5 100644 --- a/src/utils/ethers-util.ts +++ b/src/utils/ethers-util.ts @@ -160,15 +160,9 @@ async function getAccount(): Promise { return signer.getAddress(); } -async function getContract(contractAddress: string, contractAbi: ethers.InterfaceAbi): Promise { - const signer = await getSigner(); - const contract = new ethers.Contract(contractAddress, contractAbi, signer); - - return contract; -} - async function getTokenContract(tokenAddress: string): Promise { - const contract = await getContract(tokenAddress, SmartContracts[SmartContractType.ERC20]); + const signer = await getSigner(); + const contract = new ethers.Contract(tokenAddress, SmartContracts[SmartContractType.ERC20].abi, signer); return contract; } @@ -302,7 +296,7 @@ async function addToken(address: string, symbol: string, decimals: number, image * @param chainName translated chain name */ async function switchOrAddChain(network: NetworkData, chainName?: string): Promise { - const chainId = ethers.toQuantity(network.evmId ?? network.id); + const chainId = ethers.toQuantity(network.id); try { await ethereumProvider.request({ @@ -454,7 +448,6 @@ export default { getAccount, getAccountBalance, getAccountAssetBalance, - getContract, getTokenContract, getTokenDecimals, getAllowance, diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index ffba73f6d..2ed68c431 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -68,15 +68,31 @@ /> - + + + + +
+ + {{ t('changeAccountText') }} + + + {{ t('disconnectWalletText') }} + +
+ + + type="primary" + @click="connectWallet(isSoraToEvm)" + > + {{ t('connectWalletText') }} + - + + + + +
+ + {{ t('changeAccountText') }} + + + {{ t('disconnectWalletText') }} + +
+ + + type="primary" + @click="connectWallet(!isSoraToEvm)" + > + {{ t('connectWalletText') }} + @@ -687,9 +696,34 @@ export default class Bridge extends Mixins(