From 322b278f206552ecd3753c41143ecd25f111c950 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Tue, 2 Jul 2024 12:43:07 +0300 Subject: [PATCH 01/21] alphanet relaychain support --- src/assets/img/networks/alphanet.svg | 136 ++++++++++++++++++ src/assets/img/networks/sora-alphanet.svg | 6 + .../mixins/NetworkFormatterMixin.ts | 4 + src/consts/sub.ts | 10 +- src/store/web3/actions.ts | 19 +-- src/styles/common.scss | 14 +- src/utils/bridge/sub/classes/adapter.ts | 6 +- .../classes/adapters/relaychain/alphanet.ts | 24 ++++ .../adapters/{ => relaychain}/relaychain.ts | 2 +- src/utils/bridge/sub/classes/history.ts | 2 +- 10 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 src/assets/img/networks/alphanet.svg create mode 100644 src/assets/img/networks/sora-alphanet.svg create mode 100644 src/utils/bridge/sub/classes/adapters/relaychain/alphanet.ts rename src/utils/bridge/sub/classes/adapters/{ => relaychain}/relaychain.ts (98%) diff --git a/src/assets/img/networks/alphanet.svg b/src/assets/img/networks/alphanet.svg new file mode 100644 index 000000000..1714c1059 --- /dev/null +++ b/src/assets/img/networks/alphanet.svg @@ -0,0 +1,136 @@ + + diff --git a/src/assets/img/networks/sora-alphanet.svg b/src/assets/img/networks/sora-alphanet.svg new file mode 100644 index 000000000..7e1da7af6 --- /dev/null +++ b/src/assets/img/networks/sora-alphanet.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/mixins/NetworkFormatterMixin.ts b/src/components/mixins/NetworkFormatterMixin.ts index 3d43475bd..eb84c7c44 100644 --- a/src/components/mixins/NetworkFormatterMixin.ts +++ b/src/components/mixins/NetworkFormatterMixin.ts @@ -157,6 +157,10 @@ 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/consts/sub.ts b/src/consts/sub.ts index d2d05bd7f..f4db91e8a 100644 --- a/src/consts/sub.ts +++ b/src/consts/sub.ts @@ -130,7 +130,7 @@ export const SUB_NETWORKS: Partial> = { { chain: 'Moonbase Relay Testnet', name: 'Parity', - address: 'wss://frag-moonbase-relay-rpc-ws.g.moonbase.moonbeam.network', + address: 'wss://fro-moon-rpc-1-moonbase-relay-rpc-1.moonbase.ol-infra.network', }, ], }, @@ -300,8 +300,14 @@ export const SUB_TRANSFER_FEES: SubNetworksFees = { [BridgeTxDirection.Incoming]: '0', }, }, + [SubNetworkId.Alphanet]: { + ALPHA: { + [BridgeTxDirection.Outgoing]: '2700000000', + [BridgeTxDirection.Incoming]: '0', + }, + }, [SubNetworkId.AlphanetMoonbase]: { - ACA: { + GLMR: { [BridgeTxDirection.Outgoing]: '34313700000000', [BridgeTxDirection.Incoming]: '0', }, diff --git a/src/store/web3/actions.ts b/src/store/web3/actions.ts index 6037a2874..70da5c0b5 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -1,5 +1,4 @@ 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 } from '@soramitsu/soraneo-wallet-web'; import { defineActions } from 'direct-vuex'; @@ -185,22 +184,8 @@ const actions = defineActions({ async getSupportedApps(context): Promise { const { commit, getters } = web3ActionContext(context); - // [TODO] uncomment - // const supportedApps = await api.bridgeProxy.getListApps(); - // [TODO] remove this production mock after nodes update - const supportedApps = { - [BridgeNetworkType.Eth]: {}, - [BridgeNetworkType.Evm]: {}, - [BridgeNetworkType.Sub]: [ - SubNetworkId.Kusama, - SubNetworkId.KusamaSora, - SubNetworkId.Polkadot, - SubNetworkId.PolkadotAstar, - SubNetworkId.PolkadotAcala, - SubNetworkId.PolkadotSora, - SubNetworkId.Liberland, - ], - }; + + const supportedApps = await soraApi.bridgeProxy.getListApps(); commit.setSupportedApps(supportedApps as any); diff --git a/src/styles/common.scss b/src/styles/common.scss index 396a81201..f2efb4f07 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', 'moonbase', 'sora-polkadot', 'sora-kusama'; +$networks: 'sora', 'ethereum', 'ethereum-classic', 'avalanche', 'klaytn', 'polygon', 'binance-smart-chain', 'rococo', 'polkadot', 'kusama', 'acala', 'astar', 'shiden', 'liberland', 'alphanet', 'moonbase', 'sora-polkadot', 'sora-kusama'; /* Networks Icons */ .network-icon { display: inline-block; @@ -52,6 +52,18 @@ $networks: 'sora', 'ethereum', 'ethereum-classic', 'avalanche', 'klaytn', 'polyg background-image: url('~@/assets/img/networks/sora.svg'); } } + + &--sora-alphanet { + background-image: url('~@/assets/img/networks/alphanet.svg'); + &::after { + content: ''; + display: block; + width: 50%; + height: 50%; + filter: drop-shadow(2px 4px 6px black); + background-image: url('~@/assets/img/networks/sora.svg'); + } + } } /*________________________________________COMMON LAYOUT________________________________________*/ diff --git a/src/utils/bridge/sub/classes/adapter.ts b/src/utils/bridge/sub/classes/adapter.ts index 53d1f37c6..9168a77cf 100644 --- a/src/utils/bridge/sub/classes/adapter.ts +++ b/src/utils/bridge/sub/classes/adapter.ts @@ -11,7 +11,8 @@ import { AcalaParachainAdapter } from './adapters/parachain/acala'; import { AstarParachainAdapter } from './adapters/parachain/astar'; import { MoonbaseParachainAdapter } from './adapters/parachain/moonbase'; import { SoraParachainAdapter } from './adapters/parachain/sora'; -import { RelaychainAdapter } from './adapters/relaychain'; +import { AlphanetRelaychainAdapter } from './adapters/relaychain/alphanet'; +import { RelaychainAdapter } from './adapters/relaychain/relaychain'; import { LiberlandAdapter } from './adapters/standalone/liberland'; import { SubAdapter } from './adapters/substrate'; @@ -100,6 +101,9 @@ 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)) { diff --git a/src/utils/bridge/sub/classes/adapters/relaychain/alphanet.ts b/src/utils/bridge/sub/classes/adapters/relaychain/alphanet.ts new file mode 100644 index 000000000..0c16e220c --- /dev/null +++ b/src/utils/bridge/sub/classes/adapters/relaychain/alphanet.ts @@ -0,0 +1,24 @@ +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/relaychain.ts b/src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts similarity index 98% rename from src/utils/bridge/sub/classes/adapters/relaychain.ts rename to src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts index 5adc2fcca..1affcbad3 100644 --- a/src/utils/bridge/sub/classes/adapters/relaychain.ts +++ b/src/utils/bridge/sub/classes/adapters/relaychain/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'; diff --git a/src/utils/bridge/sub/classes/history.ts b/src/utils/bridge/sub/classes/history.ts index 36a498a0a..33b5fc867 100644 --- a/src/utils/bridge/sub/classes/history.ts +++ b/src/utils/bridge/sub/classes/history.ts @@ -466,7 +466,7 @@ class SubBridgeHistory extends SubNetworksConnector { // relay chain should have send message in this blocks range const startSearch = relayChainBlockNumber; - const endSearch = startSearch - 6; + const endSearch = startSearch - 10; for (let relaychainBlockHeight = startSearch; relaychainBlockHeight >= endSearch; relaychainBlockHeight--) { const blockId = await api.system.getBlockHash(relaychainBlockHeight, this.externalApi); From b733085a6b2776e1ca1eb5517d976535cea79a93 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Wed, 3 Jul 2024 11:10:54 +0300 Subject: [PATCH 02/21] update connected evm network check --- src/components/mixins/WalletConnectMixin.ts | 1 + src/consts/sub.ts | 6 ++++-- src/store/bridge/getters.ts | 21 ++++++++++++++----- src/store/web3/actions.ts | 9 ++++---- src/store/web3/getters.ts | 18 ++++++++++++---- src/types/bridge.ts | 2 ++ .../classes/adapters/parachain/moonbase.ts | 17 ++++++++------- src/utils/ethers-util.ts | 2 +- src/views/Bridge.vue | 2 +- 9 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/components/mixins/WalletConnectMixin.ts b/src/components/mixins/WalletConnectMixin.ts index 83e87a60a..db2872a35 100644 --- a/src/components/mixins/WalletConnectMixin.ts +++ b/src/components/mixins/WalletConnectMixin.ts @@ -17,6 +17,7 @@ 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; diff --git a/src/consts/sub.ts b/src/consts/sub.ts index f4db91e8a..437d9c800 100644 --- a/src/consts/sub.ts +++ b/src/consts/sub.ts @@ -142,8 +142,9 @@ export const SUB_NETWORKS: Partial> = { symbol: 'GLMR', // "DEV" decimals: 18, }, - blockExplorerUrls: [], - shortName: 'Alpha', + endpointUrls: ['https://rpc.api.moonbase.moonbeam.network', 'https://moonbase-rpc.dwellir.com'], + blockExplorerUrls: ['https://moonbase.moonscan.io'], + shortName: 'Moonbase', nodes: [ { chain: 'Moonbase Alpha', @@ -151,6 +152,7 @@ export const SUB_NETWORKS: Partial> = { address: 'wss://wss.api.moonbase.moonbeam.network', }, ], + evmId: 1287, }, // SORA Parachains [SubNetworkId.RococoSora]: { diff --git a/src/store/bridge/getters.ts b/src/store/bridge/getters.ts index 3d8a24dbd..2da5b4384 100644 --- a/src/store/bridge/getters.ts +++ b/src/store/bridge/getters.ts @@ -110,11 +110,22 @@ 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.isSubBridge) { + if (getters.isSubAccountType) { return subAddress; } else { return evmAddress; @@ -128,7 +139,7 @@ const getters = defineGetters()({ if (state.isSoraToEvm) return soraAddress; - return getters.isSubBridge ? chainAddress(subAddress, state.subBridgeConnector) : evmAddress; + return getters.isSubAccountType ? chainAddress(subAddress, state.subBridgeConnector) : evmAddress; }, senderName(...args): string { @@ -138,7 +149,7 @@ const getters = defineGetters()({ if (state.isSoraToEvm) return soraName; - return getters.isSubBridge ? subAddressName : ''; + return getters.isSubAccountType ? subAddressName : ''; }, recipient(...args): string { @@ -148,7 +159,7 @@ const getters = defineGetters()({ if (!state.isSoraToEvm) return soraAddress; - return getters.isSubBridge ? chainAddress(subAddress, state.subBridgeConnector) : evmAddress; + return getters.isSubAccountType ? chainAddress(subAddress, state.subBridgeConnector) : evmAddress; }, recipientName(...args): string { @@ -158,7 +169,7 @@ const getters = defineGetters()({ if (!state.isSoraToEvm) return soraName; - return getters.isSubBridge ? subAddressName : ''; + return getters.isSubAccountType ? subAddressName : ''; }, isEthBridge(...args): boolean { diff --git a/src/store/web3/actions.ts b/src/store/web3/actions.ts index 70da5c0b5..70019cf45 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -173,13 +173,12 @@ const actions = defineActions({ }, async changeEvmNetworkProvided(context): Promise { - const { getters, state } = web3ActionContext(context); + const { getters } = web3ActionContext(context); const { selectedNetwork } = getters; - const { networkType } = state; - if (selectedNetwork && networkType !== BridgeNetworkType.Sub) { - await ethersUtil.switchOrAddChain(selectedNetwork); - } + if (!selectedNetwork) return; + + await ethersUtil.switchOrAddChain(selectedNetwork); }, async getSupportedApps(context): Promise { diff --git a/src/store/web3/getters.ts b/src/store/web3/getters.ts index 7e3c675c8..10533d3db 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, rootState } = web3GetterContext(args); + const { state } = web3GetterContext(args); const hashi = [state.ethBridgeEvmNetwork].reduce((buffer, id) => { const data = EVM_NETWORKS[id]; @@ -79,11 +79,21 @@ const getters = defineGetters()({ }, isValidNetwork(...args): boolean { - const { state } = web3GetterContext(args); + const { state, getters } = web3GetterContext(args); + const { evmProviderNetwork } = state; + const { selectedNetwork } = getters; - if (state.networkType === BridgeNetworkType.Sub) return true; + if (!selectedNetwork) return false; + + if (state.networkType === BridgeNetworkType.Sub) { + if (selectedNetwork.evmId) { + return evmProviderNetwork === selectedNetwork.evmId; + } else { + return true; + } + } - return state.evmProviderNetwork === state.networkSelected; + return evmProviderNetwork === selectedNetwork.id; }, contractAddress(...args): (asset: KnownEthBridgeAsset) => Nullable { diff --git a/src/types/bridge.ts b/src/types/bridge.ts index 1227f5467..f0e306b5d 100644 --- a/src/types/bridge.ts +++ b/src/types/bridge.ts @@ -19,6 +19,8 @@ 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/sub/classes/adapters/parachain/moonbase.ts b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts index 3bc0669c7..5eeb7c559 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -1,13 +1,14 @@ -import { u8aToHex } from '@polkadot/util'; -import { addressToEvm } from '@polkadot/util-crypto'; +import { SubNetworkId } from '@sora-substrate/util/build/bridgeProxy/sub/consts'; + +import { SUB_NETWORKS } from '@/consts/sub'; import { SubAdapter } from '../substrate'; +const MOONBASE_DATA = SUB_NETWORKS[SubNetworkId.AlphanetMoonbase]; + 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)); - }; + // overrides "WithConnectionApi" + override get chainSymbol(): string | undefined { + return MOONBASE_DATA?.nativeCurrency?.symbol; + } } diff --git a/src/utils/ethers-util.ts b/src/utils/ethers-util.ts index 556f83cb9..00f39ee34 100644 --- a/src/utils/ethers-util.ts +++ b/src/utils/ethers-util.ts @@ -326,7 +326,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.id); + const chainId = ethers.toQuantity(network.evmId ?? network.id); try { await ethereumProvider.request({ diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index a563e6cde..9daf26ec7 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -668,7 +668,7 @@ export default class Bridge extends Mixins( } private connectExternalWallet(): void { - if (this.isSubBridge) { + if (this.isSubAccountType) { this.connectSubWallet(); } else { this.connectEvmWallet(); From 7466b6268575fb7fa9739cb9d5825a38cae2b4dc Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Wed, 3 Jul 2024 15:52:14 +0300 Subject: [PATCH 03/21] fetch moonbase asset ids --- src/store/assets/actions.ts | 42 +++++++++-- src/utils/bridge/sub/classes/adapter.ts | 6 +- .../sub/classes/adapters/parachain/acala.ts | 48 ++---------- .../sub/classes/adapters/parachain/astar.ts | 72 +++--------------- .../classes/adapters/parachain/moonbase.ts | 37 +++++++++- .../classes/adapters/parachain/parachain.ts | 74 +++++++++++++++++++ .../classes/adapters/standalone/liberland.ts | 15 +++- .../bridge/sub/classes/adapters/substrate.ts | 13 ---- 8 files changed, 178 insertions(+), 129 deletions(-) create mode 100644 src/utils/bridge/sub/classes/adapters/parachain/parachain.ts diff --git a/src/store/assets/actions.ts b/src/store/assets/actions.ts index c0c378edd..ed60f8877 100644 --- a/src/store/assets/actions.ts +++ b/src/store/assets/actions.ts @@ -130,6 +130,35 @@ async function getSubRegisteredAssets( return registeredAssets; } +async function updateSubAssetsData(context: ActionContext): Promise { + const { state, commit, rootState } = 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 }; + if (!asset.address && 'getAssetIdByMultilocation' in parachain) { + const multilocation = await subBridgeApi.soraParachainApi.getAssetMulilocation(soraAddress, soraParachain.api); + const id = await (parachain as any).getAssetIdByMultilocation(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); @@ -172,15 +201,18 @@ const actions = defineActions({ }, async updateRegisteredAssets(context): Promise { + console.log('updateRegisteredAssets'); const { commit, rootState } = assetsActionContext(context); - // only for ETH bridge, because of sora assets broken registration - if (rootState.web3.networkType === BridgeNetworkType.Eth) { - commit.setRegisteredAssetsFetching(true); - await updateEthAssetsData(context); + commit.setRegisteredAssetsFetching(true); - commit.setRegisteredAssetsFetching(false); + if (rootState.web3.networkType === BridgeNetworkType.Sub) { + await updateSubAssetsData(context); + } else { + await updateEthAssetsData(context); } + + commit.setRegisteredAssetsFetching(false); }, }); diff --git a/src/utils/bridge/sub/classes/adapter.ts b/src/utils/bridge/sub/classes/adapter.ts index 9168a77cf..062702e1d 100644 --- a/src/utils/bridge/sub/classes/adapter.ts +++ b/src/utils/bridge/sub/classes/adapter.ts @@ -10,6 +10,7 @@ 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'; @@ -28,8 +29,8 @@ type PathNetworks = { export class SubNetworksConnector { public soraParachain?: SoraParachainAdapter; - public relaychain?: SubAdapter; - public parachain?: SubAdapter; + public relaychain?: RelaychainAdapter; + public parachain?: ParachainAdapter; public standalone?: SubAdapter; public destinationNetwork!: SubNetwork; @@ -119,6 +120,7 @@ export class SubNetworksConnector { if (subBridgeApi.isSoraParachain(network)) { return new SoraParachainAdapter(network); } + return new ParachainAdapter(network); } if (subBridgeApi.isStandalone(network)) { if (network === SubNetworkId.Liberland) { diff --git a/src/utils/bridge/sub/classes/adapters/parachain/acala.ts b/src/utils/bridge/sub/classes/adapters/parachain/acala.ts index 05ea461ea..3a0325fee 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/acala.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/acala.ts @@ -3,7 +3,7 @@ import { formatBalance } from '@sora-substrate/util/build/assets'; import { ZeroStringValue } from '@/consts'; -import { SubAdapter } from '../substrate'; +import { ParachainAdapter, type IParachainAssetMetadata } from './parachain'; import type { CodecString } from '@sora-substrate/util'; import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; @@ -29,13 +29,6 @@ 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; @@ -50,13 +43,11 @@ function getAcalaCurrencyId(nature: any): Nullable { return null; } -export class AcalaParachainAdapter extends SubAdapter { - protected assets: Record | null = null; - +export class AcalaParachainAdapter extends ParachainAdapter { protected async getAssetsMetadata(): Promise { - if (this.assets) return; + if (Array.isArray(this.assets)) return; - const assets = {}; + const assets: IParachainAssetMetadata[] = []; const entries = await (this.api.query.assetRegistry as any).assetMetadatas.entries(); for (const [key, option] of entries) { @@ -69,34 +60,12 @@ export class AcalaParachainAdapter extends SubAdapter { const decimals = option.value.decimals.toNumber(); const minimalBalance = option.value.minimalBalance.toString(); - assets[symbol] = { id, symbol, decimals, minimalBalance }; + assets.push({ 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]; - } - - // overrides SubAdapter - public override async connect(): Promise { - await super.connect(); - await this.getAssetsMetadata(); - } - - protected override async getAssetDeposit(asset: RegisteredAsset): Promise { - const assetMeta = this.getAssetMeta(asset); - - if (!assetMeta) return ZeroStringValue; - - const minBalance = assetMeta.minimalBalance; - - return minBalance > '1' ? minBalance : ZeroStringValue; - } - protected override async getAccountAssetBalance( accountAddress: string, asset: RegisteredAsset @@ -148,11 +117,4 @@ export class AcalaParachainAdapter extends SubAdapter { '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 eab8b563f..94e6afcee 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/astar.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/astar.ts @@ -1,62 +1,15 @@ import { FPNumber } from '@sora-substrate/util'; -import { formatBalance } from '@sora-substrate/util/build/assets'; import { ZeroStringValue } from '@/consts'; -import { SubAdapter } from '../substrate'; +import { ParachainAdapter } from './parachain'; import type { CodecString } from '@sora-substrate/util'; 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]; - } - - public override async connect(): Promise { - await super.connect(); - await this.getAssetsMetadata(); - } - - protected override async getAssetDeposit(asset: RegisteredAsset): Promise { - const assetMeta = this.getAssetMeta(asset); - - if (!assetMeta) return ZeroStringValue; - - const minBalance = assetMeta.minimalBalance; - - return minBalance > '1' ? minBalance : ZeroStringValue; - } +type IAstarAssetId = string; +export class AstarParachainAdapter extends ParachainAdapter { protected override async getAccountAssetBalance( accountAddress: string, asset: RegisteredAsset @@ -68,6 +21,12 @@ export class AstarParachainAdapter extends SubAdapter { 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) */ @@ -157,17 +116,4 @@ export class AstarParachainAdapter extends SubAdapter { '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 5eeb7c559..3642ac839 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -1,12 +1,45 @@ import { SubNetworkId } from '@sora-substrate/util/build/bridgeProxy/sub/consts'; +import { ZeroStringValue } from '@/consts'; import { SUB_NETWORKS } from '@/consts/sub'; -import { SubAdapter } from '../substrate'; +import { ParachainAdapter } from './parachain'; + +import type { CodecString } from '@sora-substrate/util'; +import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; const MOONBASE_DATA = SUB_NETWORKS[SubNetworkId.AlphanetMoonbase]; -export class MoonbaseParachainAdapter extends SubAdapter { +type IMoonbaseAssetId = string; + +export class MoonbaseParachainAdapter extends ParachainAdapter { + public async getAssetIdByMultilocation(multilocation: any): Promise { + const assetType = { + XCM: multilocation, + }; + + const result = await (this.api.query.assetManager as any).assetTypeId(assetType); + + if (result.isEmpty) return null; + + const id = result.unwrap().toString(); + + console.log(id); + + return id; + } + + protected override async getAccountAssetBalance( + accountAddress: string, + asset: RegisteredAsset + ): Promise { + const assetMeta = this.getAssetMeta(asset); + + if (!assetMeta) return ZeroStringValue; + + return await this.assetsAccountRequest(accountAddress, assetMeta.id); + } + // overrides "WithConnectionApi" override get chainSymbol(): string | undefined { return MOONBASE_DATA?.nativeCurrency?.symbol; diff --git a/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts b/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts new file mode 100644 index 000000000..571b85740 --- /dev/null +++ b/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts @@ -0,0 +1,74 @@ +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 { 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; + + // overrides SubAdapter + public override async connect(): Promise { + await super.connect(); + await this.getAssetsMetadata(); + } + + 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(); + const minimalBalance = value.deposit.toString(); + + assets.push({ id, symbol, decimals, minimalBalance }); + } + + this.assets = Object.freeze(assets); + } + + protected getAssetMeta(asset: RegisteredAsset): Nullable> { + if (!Array.isArray(this.assets)) return null; + + return this.assets.find((item) => item.id === asset.externalAddress || item.symbol === asset.symbol); + } + + // overrides SubAdapter + protected override async getAssetDeposit(asset: RegisteredAsset): Promise { + const assetMeta = this.getAssetMeta(asset); + + if (!assetMeta) return ZeroStringValue; + + const minBalance = assetMeta.minimalBalance; + + return minBalance > '1' ? minBalance : ZeroStringValue; + } + + public 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'; + default: + return '0'; + } + } +} diff --git a/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts b/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts index dae7d0931..d971bae6c 100644 --- a/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts +++ b/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts @@ -2,6 +2,8 @@ import { FPNumber } from '@sora-substrate/util'; import { BridgeAccountType } from '@sora-substrate/util/build/bridgeProxy/consts'; import { SubNetworkId, LiberlandAssetType } from '@sora-substrate/util/build/bridgeProxy/sub/consts'; +import { ZeroStringValue } from '@/consts'; + import { SubAdapter } from '../substrate'; import type { CodecString } from '@sora-substrate/util'; @@ -9,7 +11,18 @@ import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; export class LiberlandAdapter extends SubAdapter { protected override async getAssetDeposit(asset: RegisteredAsset): Promise { - return await this.assetsAssetMinBalanceRequest(Number(asset.externalAddress)); + const assetId = Number(asset.externalAddress); + + return await this.withConnection(async () => { + const result = await (this.api.query.assets as any).asset(assetId); + + if (result.isEmpty) return ZeroStringValue; + + const data = result.unwrap(); + const minBalance = data.minBalance.toString(); + + return minBalance > '1' ? minBalance : ZeroStringValue; + }, ZeroStringValue); } 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 68856c606..6c470a66c 100644 --- a/src/utils/bridge/sub/classes/adapters/substrate.ts +++ b/src/utils/bridge/sub/classes/adapters/substrate.ts @@ -153,17 +153,4 @@ export class SubAdapter extends BaseSubAdapter { return data.balance.toString(); }, ZeroStringValue); } - - protected async assetsAssetMinBalanceRequest(assetId: number | string): Promise { - return await this.withConnection(async () => { - const result = await (this.api.query.assets as any).asset(assetId); - - if (result.isEmpty) return ZeroStringValue; - - const data = result.unwrap(); - const minBalance = data.minBalance.toString(); - - return minBalance > '1' ? minBalance : ZeroStringValue; - }, ZeroStringValue); - } } From 52d25c3840339a8a83d3928123eee3fa2862b098 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Thu, 4 Jul 2024 10:02:35 +0300 Subject: [PATCH 04/21] fix history restoration address issue --- src/store/assets/actions.ts | 1 - .../classes/adapters/parachain/moonbase.ts | 8 ++++-- src/utils/bridge/sub/classes/history.ts | 12 ++++++--- src/utils/bridge/sub/classes/reducers.ts | 16 +++++++----- src/utils/bridge/sub/utils.ts | 26 +++++++++++-------- 5 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/store/assets/actions.ts b/src/store/assets/actions.ts index ed60f8877..ff26903c5 100644 --- a/src/store/assets/actions.ts +++ b/src/store/assets/actions.ts @@ -201,7 +201,6 @@ const actions = defineActions({ }, async updateRegisteredAssets(context): Promise { - console.log('updateRegisteredAssets'); const { commit, rootState } = assetsActionContext(context); commit.setRegisteredAssetsFetching(true); diff --git a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts index 3642ac839..38ccd6c75 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -24,8 +24,6 @@ export class MoonbaseParachainAdapter extends ParachainAdapter const id = result.unwrap().toString(); - console.log(id); - return id; } @@ -44,4 +42,10 @@ export class MoonbaseParachainAdapter extends ParachainAdapter 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; + } } diff --git a/src/utils/bridge/sub/classes/history.ts b/src/utils/bridge/sub/classes/history.ts index 33b5fc867..cd11e4022 100644 --- a/src/utils/bridge/sub/classes/history.ts +++ b/src/utils/bridge/sub/classes/history.ts @@ -334,7 +334,7 @@ class SubBridgeHistory extends SubNetworksConnector { const [receivedAmount, externalEventIndex] = getDepositedBalance( extrinsicEvents, history.to as string, - soraParachainApi + soraParachain ); // balances.Deposit event index history.externalEventIndex = externalEventIndex; @@ -369,7 +369,7 @@ class SubBridgeHistory extends SubNetworksConnector { }): Promise> { const { soraApi } = this; // Token is minted to account event - const [_, eventIndex] = getDepositedBalance(blockEvents, history.from as string, this.soraApi); + const [_, eventIndex] = getDepositedBalance(blockEvents, history.from as string, subBridgeApi); history.payload.eventIndex = eventIndex; // find SORA hash event index @@ -609,7 +609,11 @@ class SubBridgeHistory extends SubNetworksConnector { const blockEvents = await api.system.getBlockEvents(blockId, this.externalApi); const messageEventIndex = blockEvents.findIndex((e) => { - if (isEvent(e, 'messageQueue', 'Processed') || isEvent(e, 'xcmpQueue', 'Success')) { + if ( + isEvent(e, 'messageQueue', 'Processed') || + isEvent(e, 'xcmpQueue', 'Success') || + isEvent(e, 'xcmpQueue', 'Fail') + ) { const messageHashMatches = e.event.data[0].toString() === messageHash; return messageHashMatches; @@ -626,7 +630,7 @@ class SubBridgeHistory extends SubNetworksConnector { const [receivedAmount, externalEventIndex] = getDepositedBalance( blockEvents.slice(0, messageEventIndex), history.to, - this.externalApi + this.network ); // Deposit event index diff --git a/src/utils/bridge/sub/classes/reducers.ts b/src/utils/bridge/sub/classes/reducers.ts index 5aa0958bd..944eae5bd 100644 --- a/src/utils/bridge/sub/classes/reducers.ts +++ b/src/utils/bridge/sub/classes/reducers.ts @@ -262,7 +262,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { if (!isStandalone) { assetSendEventIndex = events.findIndex((e) => - isAssetAddedToChannel(e, this.asset, to, sended, adapter.api) + isAssetAddedToChannel(e, this.asset, to, sended, adapter) ); if (assetSendEventIndex !== -1) { @@ -273,7 +273,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { } } else { assetSendEventIndex = events.findIndex((e) => - isSoraBridgeAppBurned(e, this.asset, from, to, sended, adapter.api) + isSoraBridgeAppBurned(e, this.asset, from, to, sended, adapter) ); if (assetSendEventIndex !== -1) { @@ -350,7 +350,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { soraHash = getBridgeProxyHash(foundedEvents, subBridgeApi.api); - [amount, eventIndex] = getDepositedBalance(foundedEvents, tx.to as string, subBridgeApi.api); + [amount, eventIndex] = getDepositedBalance(foundedEvents, tx.to as string, subBridgeApi); resolve(); } catch (error) { @@ -534,7 +534,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { [amountReceived] = getDepositedBalance( events.slice(substrateDispatchEventIndex), tx.to as string, - adapter.api + adapter ); } } else { @@ -604,7 +604,11 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { try { const events = eventsVec.toArray(); const messageQueueProcessedEventIndex = events.findIndex((e) => { - if (isEvent(e, 'messageQueue', 'Processed') || isEvent(e, 'xcmpQueue', 'Success')) { + if ( + isEvent(e, 'messageQueue', 'Processed') || + isEvent(e, 'xcmpQueue', 'Success') || + isEvent(e, 'xcmpQueue', 'Fail') + ) { return e.event.data[0].toString() === messageHash; } return false; @@ -617,7 +621,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { [amount, externalEventIndex] = getDepositedBalance( events.slice(0, messageQueueProcessedEventIndex), tx.to as string, - adapter.api + adapter ); resolve(); diff --git a/src/utils/bridge/sub/utils.ts b/src/utils/bridge/sub/utils.ts index 401bd4a99..e82660b10 100644 --- a/src/utils/bridge/sub/utils.ts +++ b/src/utils/bridge/sub/utils.ts @@ -4,7 +4,7 @@ import { subBridgeApi } from '@/utils/bridge/sub/api'; import { SubTransferType } from '@/utils/bridge/sub/types'; import type { ApiPromise } from '@polkadot/api'; -import type { CodecString } from '@sora-substrate/util'; +import type { CodecString, WithConnectionApi } 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'; @@ -54,8 +54,8 @@ export const isEvent = (e, section: string, method: string) => { export const isTransactionFeePaid = (e) => isEvent(e, 'transactionPayment', 'TransactionFeePaid'); -export const getDepositedBalance = (events: Array, to: string, api: ApiPromise): [string, number] => { - const recipient = subBridgeApi.formatAddress(to); +export const getDepositedBalance = (events: Array, to: string, chainApi: WithConnectionApi): [string, number] => { + const recipient = chainApi.formatAddress(to).toLowerCase(); const index = events.findIndex((e) => { let eventRecipient = ''; @@ -64,11 +64,15 @@ export const getDepositedBalance = (events: Array, to: string, api: ApiProm 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; - return subBridgeApi.formatAddress(eventRecipient) === recipient; + const formatted = chainApi.formatAddress(eventRecipient).toLowerCase(); + + return formatted === recipient; }); if (index === -1) throw new Error(`Unable to find balance deposit like event`); @@ -156,13 +160,13 @@ export const isAssetAddedToChannel = ( asset: RegisteredAccountAsset, to: string, sended: CodecString, - api: ApiPromise + chainApi: WithConnectionApi ): boolean => { - if (!api.events.xcmApp.AssetAddedToChannel.is(e.event)) return false; + if (!isEvent(e, 'xcmApp', 'AssetAddedToChannel')) return false; const { amount, assetId, recipient } = e.event.data[0].asTransfer; // address check - if (subBridgeApi.formatAddress(recipient.toString()) !== subBridgeApi.formatAddress(to)) return false; + if (chainApi.formatAddress(recipient.toString()) !== chainApi.formatAddress(to)) return false; // asset check if (assetId.toString() !== asset.address) return false; // amount check @@ -179,9 +183,9 @@ export const isSoraBridgeAppBurned = ( from: string, to: string, sended: CodecString, - api: ApiPromise + chainApi: WithConnectionApi ) => { - if (!api.events.soraBridgeApp.Burned.is(e.event)) return false; + if (!isEvent(e, 'soraBridgeApp', 'Burned')) return false; const [networkIdCodec, assetIdCodec, senderCodec, recipientCodec, amountCodec] = e.event.data; @@ -193,8 +197,8 @@ export const isSoraBridgeAppBurned = ( const amount = amountCodec.toString(); // address check - if (subBridgeApi.formatAddress(sender) !== subBridgeApi.formatAddress(from)) return false; - if (subBridgeApi.formatAddress(recipient) !== subBridgeApi.formatAddress(to)) return false; + if (chainApi.formatAddress(sender) !== chainApi.formatAddress(from)) return false; + if (chainApi.formatAddress(recipient) !== chainApi.formatAddress(to)) return false; // asset check if (assetId !== asset.externalAddress) return false; // amount check From f0cc9b51203cbd447cba0688fc7f2b7479ec45fd Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Thu, 4 Jul 2024 14:20:03 +0300 Subject: [PATCH 05/21] update alphanet fees --- src/consts/sub.ts | 4 ++++ .../bridge/sub/classes/adapters/relaychain/relaychain.ts | 4 ++-- src/views/Bridge.vue | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/consts/sub.ts b/src/consts/sub.ts index 437d9c800..b0e24a941 100644 --- a/src/consts/sub.ts +++ b/src/consts/sub.ts @@ -313,5 +313,9 @@ export const SUB_TRANSFER_FEES: SubNetworksFees = { [BridgeTxDirection.Outgoing]: '34313700000000', [BridgeTxDirection.Incoming]: '0', }, + XOR: { + [BridgeTxDirection.Outgoing]: '8140448382622083802', + [BridgeTxDirection.Incoming]: '0', + }, }, }; diff --git a/src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts b/src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts index 1affcbad3..dc226789e 100644 --- a/src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts +++ b/src/utils/bridge/sub/classes/adapters/relaychain/relaychain.ts @@ -71,11 +71,11 @@ export class RelaychainAdapter extends SubAdapter { case SubNetworkId.Rococo: return toCodec(0.000125); case SubNetworkId.Alphanet: - return toCodec(0.019); + return toCodec(0.019 + 0.037); case SubNetworkId.Kusama: return toCodec(0.002); case SubNetworkId.Polkadot: - return toCodec(0.059); + return toCodec(0.019 + 0.037); default: return '0'; } diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index 9daf26ec7..e7f88dcef 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -440,8 +440,9 @@ export default class Bridge extends Mixins( isExternalBalance: !this.isSoraToEvm, isExternalNative: this.isNativeTokenSelected, }); + const result = maxBalance.sub(minBalance).max(FPNumber.ZERO); - return maxBalance.sub(minBalance).max(FPNumber.ZERO); + return result; } get maxValue(): string { From 69d1a13c8c1886effa049846f02c6c9520056511 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Mon, 8 Jul 2024 10:21:03 +0300 Subject: [PATCH 06/21] add xTokens abi & refactoring abi files --- src/abi/ethereum/internal/MASTER.json | 106 ++++--- src/abi/ethereum/other/BRIDGE.json | 308 +++++++++---------- src/abi/ethereum/other/ERC20.json | 230 +++++++------- src/abi/ethereum/other/moonbeam/xTokens.json | 94 ++++++ src/store/moonpay/actions.ts | 2 +- src/store/web3/actions.ts | 2 +- src/utils/bridge/eth/classes/history.ts | 4 +- src/utils/bridge/eth/utils.ts | 4 +- src/utils/ethers-util.ts | 2 +- 9 files changed, 420 insertions(+), 332 deletions(-) create mode 100644 src/abi/ethereum/other/moonbeam/xTokens.json diff --git a/src/abi/ethereum/internal/MASTER.json b/src/abi/ethereum/internal/MASTER.json index ed2b5af6c..3a79fc419 100644 --- a/src/abi/ethereum/internal/MASTER.json +++ b/src/abi/ethereum/internal/MASTER.json @@ -1,54 +1,52 @@ -{ - "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" - } - ] -} +[ + { + "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 9b7d6a609..94de998c5 100644 --- a/src/abi/ethereum/other/BRIDGE.json +++ b/src/abi/ethereum/other/BRIDGE.json @@ -1,155 +1,153 @@ -{ - "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" - } - ] -} +[ + { + "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 diff --git a/src/abi/ethereum/other/ERC20.json b/src/abi/ethereum/other/ERC20.json index 528b15672..0623a6669 100644 --- a/src/abi/ethereum/other/ERC20.json +++ b/src/abi/ethereum/other/ERC20.json @@ -1,130 +1,128 @@ -{ - "abi": [ - { - "inputs": [ +[ + { + "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": [ { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", "name": "", - "type": "uint256" + "type": "uint8" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { - "internalType": "address", - "name": "spender", + "name": "to", "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": [ + ], + "name": "transfer", + "outputs": [ { - "internalType": "bool", "name": "", "type": "bool" } - ], - "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" - } - ] -} + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/ethereum/other/moonbeam/xTokens.json b/src/abi/ethereum/other/moonbeam/xTokens.json new file mode 100644 index 000000000..34ee16fa2 --- /dev/null +++ b/src/abi/ethereum/other/moonbeam/xTokens.json @@ -0,0 +1,94 @@ +[ + { + "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/store/moonpay/actions.ts b/src/store/moonpay/actions.ts index b23cd4384..eded9490d 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].abi; + const abi = SmartContracts[SmartContractType.ERC20]; 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 70019cf45..b9578b8e6 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -232,7 +232,7 @@ const actions = defineActions({ if (!soraAssetId) { return ''; } - const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other].abi; + const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other]; const contractAddress = getters.contractAddress(KnownEthBridgeAsset.Other); if (!contractAddress || !contractAbi) { throw new Error('Contract address/abi is not found'); diff --git a/src/utils/bridge/eth/classes/history.ts b/src/utils/bridge/eth/classes/history.ts index c122fa706..0d4f20e0c 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].abi, // XOR or VAL - ...SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other].abi, // Other + ...SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.XOR], // XOR or VAL + ...SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other], // Other ]); const { ETH_BRIDGE_STATES } = WALLET_CONSTS; diff --git a/src/utils/bridge/eth/utils.ts b/src/utils/bridge/eth/utils.ts index cde815bb4..57deead54 100644 --- a/src/utils/bridge/eth/utils.ts +++ b/src/utils/bridge/eth/utils.ts @@ -130,7 +130,7 @@ export async function getIncomingEvmTransactionData({ asset, value, recipient, g const amount = new FPNumber(value, asset.externalDecimals).toCodecString(); const contractAddress = getContractAddress(KnownEthBridgeAsset.Other) as string; - const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other].abi; + const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other]; const contract = new ethers.Contract(contractAddress, contractAbi, signer); const method = isNativeEvmToken ? 'sendEthToSidechain' : 'sendERC20ToSidechain'; @@ -167,7 +167,7 @@ export async function getOutgoingEvmTransactionData({ 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].abi; + const contractAbi = SmartContracts[SmartContractType.EthBridge][bridgeAsset]; const contract = new ethers.Contract(contractAddress, contractAbi, signer); const amount = new FPNumber(value, asset.externalDecimals).toCodecString(); diff --git a/src/utils/ethers-util.ts b/src/utils/ethers-util.ts index 00f39ee34..828a3cc64 100644 --- a/src/utils/ethers-util.ts +++ b/src/utils/ethers-util.ts @@ -192,7 +192,7 @@ async function getAccount(): Promise { async function getTokenContract(tokenAddress: string): Promise { const signer = await getSigner(); - const contract = new ethers.Contract(tokenAddress, SmartContracts[SmartContractType.ERC20].abi, signer); + const contract = new ethers.Contract(tokenAddress, SmartContracts[SmartContractType.ERC20], signer); return contract; } From 7d2cfe7daabb86c2b160251c33c89098eab4e362 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Mon, 8 Jul 2024 17:01:31 +0300 Subject: [PATCH 07/21] transfer from moonbase (xor check) --- src/store/web3/actions.ts | 4 +- src/utils/bridge/eth/utils.ts | 10 +-- src/utils/bridge/sub/classes/adapter.ts | 40 ++++----- .../classes/adapters/parachain/moonbase.ts | 81 +++++++++++++++---- src/utils/ethers-util.ts | 11 ++- 5 files changed, 103 insertions(+), 43 deletions(-) diff --git a/src/store/web3/actions.ts b/src/store/web3/actions.ts index b9578b8e6..2e330e529 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -2,7 +2,6 @@ import { BridgeNetworkType } from '@sora-substrate/util/build/bridgeProxy/consts import { BridgeNetworkId } from '@sora-substrate/util/build/bridgeProxy/types'; import { api as soraApi, accountUtils, WALLET_TYPES } 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'; @@ -237,8 +236,7 @@ const actions = defineActions({ if (!contractAddress || !contractAbi) { throw new Error('Contract address/abi is not found'); } - const signer = await ethersUtil.getSigner(); - const contractInstance = new ethers.Contract(contractAddress, contractAbi, signer); + const contractInstance = await ethersUtil.getContract(contractAddress, contractAbi); 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/utils/bridge/eth/utils.ts b/src/utils/bridge/eth/utils.ts index 57deead54..dd5f8720c 100644 --- a/src/utils/bridge/eth/utils.ts +++ b/src/utils/bridge/eth/utils.ts @@ -1,7 +1,6 @@ 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'; @@ -10,6 +9,7 @@ import ethersUtil from '@/utils/ethers-util'; import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; import type { EthHistory, EthApprovedRequest } from '@sora-substrate/util/build/bridgeProxy/eth/types'; +import type { ethers } from 'ethers'; import type { Subscription } from 'rxjs'; type EthTxParams = { @@ -124,14 +124,12 @@ 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 = new ethers.Contract(contractAddress, contractAbi, signer); + const contract = await ethersUtil.getContract(contractAddress, contractAbi); const method = isNativeEvmToken ? 'sendEthToSidechain' : 'sendERC20ToSidechain'; const methodArgs = isNativeEvmToken @@ -162,14 +160,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 contract = new ethers.Contract(contractAddress, contractAbi, signer); const amount = new FPNumber(value, asset.externalDecimals).toCodecString(); const isEthereumCurrency = request.currencyType === EthCurrencyType.TokenAddress; diff --git a/src/utils/bridge/sub/classes/adapter.ts b/src/utils/bridge/sub/classes/adapter.ts index 062702e1d..0e700ca15 100644 --- a/src/utils/bridge/sub/classes/adapter.ts +++ b/src/utils/bridge/sub/classes/adapter.ts @@ -207,23 +207,27 @@ export class SubNetworksConnector { * Transfer funds from destination network to SORA */ 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); + if ('transfer' in this.network) { + await (this.network as any).transfer(asset, recipient, amount); + } 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); + } } } diff --git a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts index 38ccd6c75..515545211 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -1,7 +1,11 @@ +import { FPNumber } 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 ethersUtil from '@/utils/ethers-util'; import { ParachainAdapter } from './parachain'; @@ -10,9 +14,33 @@ import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; const MOONBASE_DATA = SUB_NETWORKS[SubNetworkId.AlphanetMoonbase]; +const xTokensContractAddress = '0x0000000000000000000000000000000000000804'; + type IMoonbaseAssetId = string; export class MoonbaseParachainAdapter extends ParachainAdapter { + // 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; + } + + protected override async getAccountAssetBalance( + accountAddress: string, + asset: RegisteredAsset + ): Promise { + const assetMeta = this.getAssetMeta(asset); + + if (!assetMeta) return ZeroStringValue; + + return await this.assetsAccountRequest(accountAddress, assetMeta.id); + } + public async getAssetIdByMultilocation(multilocation: any): Promise { const assetType = { XCM: multilocation, @@ -27,25 +55,50 @@ export class MoonbaseParachainAdapter extends ParachainAdapter return id; } - protected override async getAccountAssetBalance( - accountAddress: string, - asset: RegisteredAsset - ): Promise { - const assetMeta = this.getAssetMeta(asset); + protected assetIdToEvmContractAddress(id: string): string { + const base = new BN(id).toString(16); + const padded = base.padStart(40, 'f'); + return `0x${padded}`; + } - if (!assetMeta) return ZeroStringValue; + protected toParachainAddress(id: number | undefined): string { + const selector = '0x00'; // Parachain selector + const address = Number(id ?? 0) + .toString(16) + .padStart(8, '0'); // bytes4 - return await this.assetsAccountRequest(accountAddress, assetMeta.id); + return selector + address; } - // overrides "WithConnectionApi" - override get chainSymbol(): string | undefined { - return MOONBASE_DATA?.nativeCurrency?.symbol; + 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; } - // overrides "WithConnectionApi" - public override formatAddress(address: string, _withPrefix = true): string { - // return evm address without changes - return address; + public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number) { + const assetMeta = this.getAssetMeta(asset); + + if (!assetMeta) throw new Error(`[${this.constructor.name}] Asset not found`); + + const currencyAddress = this.assetIdToEvmContractAddress(assetMeta.id); + const value = new FPNumber(amount, asset.externalDecimals).toCodecString(); + const weight = 4_000_000_000; // 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(xTokensContractAddress, xTokensAbi); + + const transaction = await xTokens.transfer(currencyAddress, value, destination, weight); + + // Waits for the transaction to be included in a block + await transaction.wait(); + console.log(transaction); } } diff --git a/src/utils/ethers-util.ts b/src/utils/ethers-util.ts index 828a3cc64..75544ae34 100644 --- a/src/utils/ethers-util.ts +++ b/src/utils/ethers-util.ts @@ -190,9 +190,15 @@ async function getAccount(): Promise { return signer.getAddress(); } -async function getTokenContract(tokenAddress: string): Promise { +async function getContract(contractAddress: string, contractAbi: ethers.InterfaceAbi): Promise { const signer = await getSigner(); - const contract = new ethers.Contract(tokenAddress, SmartContracts[SmartContractType.ERC20], signer); + const contract = new ethers.Contract(contractAddress, contractAbi, signer); + + return contract; +} + +async function getTokenContract(tokenAddress: string): Promise { + const contract = await getContract(tokenAddress, SmartContracts[SmartContractType.ERC20]); return contract; } @@ -478,6 +484,7 @@ export default { getAccount, getAccountBalance, getAccountAssetBalance, + getContract, getTokenContract, getTokenDecimals, getAllowance, From 59da30fe54e4dcccfd9e099c0cdbd8d7a83d61dc Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Tue, 9 Jul 2024 09:39:22 +0300 Subject: [PATCH 08/21] send GLMR from moonbase --- .../classes/adapters/parachain/moonbase.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts index 515545211..38e83c1d0 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -14,11 +14,12 @@ import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; const MOONBASE_DATA = SUB_NETWORKS[SubNetworkId.AlphanetMoonbase]; -const xTokensContractAddress = '0x0000000000000000000000000000000000000804'; - type IMoonbaseAssetId = string; export class MoonbaseParachainAdapter extends ParachainAdapter { + protected nativeAssetContractAddress = '0x0000000000000000000000000000000000000802'; + protected xTokensContractAddress = '0x0000000000000000000000000000000000000804'; + // overrides "WithConnectionApi" override get chainSymbol(): string | undefined { return MOONBASE_DATA?.nativeCurrency?.symbol; @@ -79,13 +80,18 @@ export class MoonbaseParachainAdapter extends ParachainAdapter } public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number) { - const assetMeta = this.getAssetMeta(asset); + let currencyAddress = this.nativeAssetContractAddress; + + if (asset.symbol !== this.chainSymbol) { + const assetMeta = this.getAssetMeta(asset); + + if (!assetMeta) throw new Error(`[${this.constructor.name}] Asset not found`); - if (!assetMeta) throw new Error(`[${this.constructor.name}] Asset not found`); + currencyAddress = this.assetIdToEvmContractAddress(assetMeta.id); + } - const currencyAddress = this.assetIdToEvmContractAddress(assetMeta.id); const value = new FPNumber(amount, asset.externalDecimals).toCodecString(); - const weight = 4_000_000_000; // taken from successful xcm message on SORA parachain + const weight = 5_000_000_000; // max waight, taken from successful xcm message on SORA parachain const parents = 1; const parachainJunction = this.toParachainAddress(this.getSoraParachainId()); @@ -93,7 +99,7 @@ export class MoonbaseParachainAdapter extends ParachainAdapter const interior = [parachainJunction, accountJunction]; // interior = X2 (the array has a length of 2) const destination = [parents, interior]; - const xTokens = await ethersUtil.getContract(xTokensContractAddress, xTokensAbi); + const xTokens = await ethersUtil.getContract(this.xTokensContractAddress, xTokensAbi); const transaction = await xTokens.transfer(currencyAddress, value, destination, weight); From 080504ad4466eaeba65b75e59cd0d763d34bebb2 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Tue, 9 Jul 2024 15:17:51 +0300 Subject: [PATCH 09/21] moonbase tracking improvements --- src/consts/sub.ts | 4 ++ src/utils/bridge/common/classes.ts | 15 +++-- src/utils/bridge/common/utils.ts | 59 +++++++++++++++++-- src/utils/bridge/eth/classes/reducers.ts | 43 ++------------ src/utils/bridge/eth/utils.ts | 18 ++---- src/utils/bridge/evm/utils.ts | 10 ++-- src/utils/bridge/sub/classes/adapter.ts | 4 +- .../classes/adapters/parachain/moonbase.ts | 38 +++++++++--- .../classes/adapters/parachain/parachain.ts | 2 + src/utils/bridge/sub/classes/reducers.ts | 47 ++++++++++----- src/utils/bridge/sub/utils.ts | 11 +++- 11 files changed, 158 insertions(+), 93 deletions(-) diff --git a/src/consts/sub.ts b/src/consts/sub.ts index b0e24a941..90cb0b944 100644 --- a/src/consts/sub.ts +++ b/src/consts/sub.ts @@ -313,6 +313,10 @@ export const SUB_TRANSFER_FEES: SubNetworksFees = { [BridgeTxDirection.Outgoing]: '34313700000000', [BridgeTxDirection.Incoming]: '0', }, + ALPHA: { + [BridgeTxDirection.Outgoing]: '44415350668', + [BridgeTxDirection.Incoming]: '46453162841', + }, XOR: { [BridgeTxDirection.Outgoing]: '8140448382622083802', [BridgeTxDirection.Incoming]: '0', diff --git a/src/utils/bridge/common/classes.ts b/src/utils/bridge/common/classes.ts index 12d6a6f7f..9ebef9dd5 100644 --- a/src/utils/bridge/common/classes.ts +++ b/src/utils/bridge/common/classes.ts @@ -18,6 +18,7 @@ import type { IBridgeConstructorOptions, TransactionHandlerPayload, } from '@/utils/bridge/common/types'; +import { isUnsignedTx } from '@/utils/bridge/common/utils'; import type { IBridgeTransaction } from '@sora-substrate/util'; @@ -180,25 +181,27 @@ export class BridgeReducer implements IB } private async checkTransactionBlockId(id: string): Promise { - const { blockId } = this.getTransaction(id); + const { blockId, externalBlockId } = this.getTransaction(id); - if (blockId) return; + if (blockId || externalBlockId) return; await delay(1_000); await this.checkTransactionBlockId(id); } async waitForTransactionBlockId(id: string): Promise { - const { txId } = this.getTransaction(id); + const tx = this.getTransaction(id); - if (!txId) { - throw new Error(`[${this.constructor.name}]: Transaction "id" is empty, first sign the transaction`); + if (isUnsignedTx(tx)) { + throw new Error( + `[${this.constructor.name}]: Transaction "id" or "externalHash" is empty, first sign the transaction` + ); } try { await Promise.race([ this.checkTransactionBlockId(id), - delay(BLOCK_PRODUCE_TIME * 6, false), // 36s + delay(BLOCK_PRODUCE_TIME * 10, false), // 60s ]); } 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 37c9737a9..3cb8cd989 100644 --- a/src/utils/bridge/common/utils.ts +++ b/src/utils/bridge/common/utils.ts @@ -1,10 +1,11 @@ -import { Operation, isEthOperation, isEvmOperation, isSubstrateOperation } from '@sora-substrate/util'; +import { isEthOperation, isEvmOperation, isSubstrateOperation } from '@sora-substrate/util'; import { api as soraApi } from '@soramitsu/soraneo-wallet-web'; import { ethers } from 'ethers'; -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 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 ethersUtil from '@/utils/ethers-util'; import type { ApiPromise } from '@polkadot/api'; @@ -13,6 +14,13 @@ 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 @@ -77,7 +85,11 @@ export const getTransactionEvents = async (blockHash: string, transactionHash: s export const isOutgoingTransaction = (transaction: Nullable): boolean => { if (!transaction?.type) return false; - return [Operation.EthBridgeOutgoing, Operation.EvmOutgoing, Operation.SubstrateOutgoing].includes(transaction.type); + 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; }; export const isUnsignedTx = (transaction: Nullable): boolean => { @@ -89,3 +101,40 @@ 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/reducers.ts b/src/utils/bridge/eth/classes/reducers.ts index 09c6c1e0f..ee7a2f40f 100644 --- a/src/utils/bridge/eth/classes/reducers.ts +++ b/src/utils/bridge/eth/classes/reducers.ts @@ -3,16 +3,10 @@ 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, waitForEvmTransactionMined } from '@/utils/bridge/common/utils'; +import { getTransactionEvents, getEvmTransactionFee, onEvmTransactionPending } from '@/utils/bridge/common/utils'; import { ethBridgeApi } from '@/utils/bridge/eth/api'; import type { EthBridgeHistory } from '@/utils/bridge/eth/classes/history'; -import { - getTransaction, - getTransactionFee, - waitForApprovedRequest, - waitForIncomingRequest, -} from '@/utils/bridge/eth/utils'; -import ethersUtil from '@/utils/ethers-util'; +import { getTransaction, waitForApprovedRequest, waitForIncomingRequest } from '@/utils/bridge/eth/utils'; import type { IBridgeTransaction } from '@sora-substrate/util'; import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; @@ -40,36 +34,7 @@ export class EthBridgeReducer extends BridgeReducer { } async onEvmPending(id: string): Promise { - 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, - }); + await onEvmTransactionPending(id, this.getTransaction, this.updateTransactionParams); } async onEvmSubmitted(id: string, signExternal: SignExternal): Promise { @@ -83,7 +48,7 @@ export class EthBridgeReducer extends BridgeReducer { // update after sign this.updateTransactionParams(id, { externalHash: signedTx.hash, - externalNetworkFee: getTransactionFee(signedTx), + externalNetworkFee: getEvmTransactionFee(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 dd5f8720c..c6f4d3945 100644 --- a/src/utils/bridge/eth/utils.ts +++ b/src/utils/bridge/eth/utils.ts @@ -9,7 +9,6 @@ import ethersUtil from '@/utils/ethers-util'; import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; import type { EthHistory, EthApprovedRequest } from '@sora-substrate/util/build/bridgeProxy/eth/types'; -import type { ethers } from 'ethers'; import type { Subscription } from 'rxjs'; type EthTxParams = { @@ -20,13 +19,15 @@ type EthTxParams = { request?: EthApprovedRequest; }; +export const isOutgoingTx = (tx: EthHistory): boolean => { + return tx.type === Operation.EthBridgeOutgoing; +}; + export const isUnsignedFromPart = (tx: EthHistory): boolean => { - if (tx.type === Operation.EthBridgeOutgoing) { + if (isOutgoingTx(tx)) { return !tx.blockId && !tx.txId; - } else if (tx.type === Operation.EthBridgeIncoming) { - return !tx.externalHash; } else { - return true; + return !tx.externalHash; } }; @@ -290,10 +291,3 @@ 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 8640cd6ee..5dca0845c 100644 --- a/src/utils/bridge/evm/utils.ts +++ b/src/utils/bridge/evm/utils.ts @@ -4,13 +4,15 @@ 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 (tx.type === Operation.EvmOutgoing) { + if (isOutgoingTx(tx)) { return !tx.blockId && !tx.txId; - } else if (tx.type === Operation.EvmIncoming) { - return true; } else { - return true; + return !tx.externalHash; } }; diff --git a/src/utils/bridge/sub/classes/adapter.ts b/src/utils/bridge/sub/classes/adapter.ts index 0e700ca15..f9aa9f605 100644 --- a/src/utils/bridge/sub/classes/adapter.ts +++ b/src/utils/bridge/sub/classes/adapter.ts @@ -206,9 +206,9 @@ export class SubNetworksConnector { /** * Transfer funds from destination network to SORA */ - public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId?: string) { + public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId: string) { if ('transfer' in this.network) { - await (this.network as any).transfer(asset, recipient, amount); + await (this.network as any).transfer(asset, recipient, amount, historyId); } else { const { api, accountPair, signer } = this.accountApi; diff --git a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts index 38e83c1d0..bf463d055 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -1,16 +1,20 @@ -import { FPNumber } from '@sora-substrate/util'; +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 { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; +import type { ethers } from 'ethers'; const MOONBASE_DATA = SUB_NETWORKS[SubNetworkId.AlphanetMoonbase]; @@ -79,7 +83,7 @@ export class MoonbaseParachainAdapter extends ParachainAdapter return selector + publicKey + networkOption; } - public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number) { + public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId: string) { let currencyAddress = this.nativeAssetContractAddress; if (asset.symbol !== this.chainSymbol) { @@ -91,7 +95,7 @@ export class MoonbaseParachainAdapter extends ParachainAdapter } const value = new FPNumber(amount, asset.externalDecimals).toCodecString(); - const weight = 5_000_000_000; // max waight, taken from successful xcm message on SORA parachain + 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()); @@ -101,10 +105,28 @@ export class MoonbaseParachainAdapter extends ParachainAdapter const xTokens = await ethersUtil.getContract(this.xTokensContractAddress, xTokensAbi); - const transaction = await xTokens.transfer(currencyAddress, value, destination, weight); - - // Waits for the transaction to be included in a block - await transaction.wait(); - console.log(transaction); + 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, + }); + }); } } diff --git a/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts b/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts index 571b85740..ed52448c6 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts @@ -67,6 +67,8 @@ export class ParachainAdapter extends SubAdapter { return '3000000000'; case SubNetworkId.PolkadotAstar: return '57000000000000000'; + case SubNetworkId.AlphanetMoonbase: + return '40000000000000'; default: return '0'; } diff --git a/src/utils/bridge/sub/classes/reducers.ts b/src/utils/bridge/sub/classes/reducers.ts index 944eae5bd..49b677fee 100644 --- a/src/utils/bridge/sub/classes/reducers.ts +++ b/src/utils/bridge/sub/classes/reducers.ts @@ -105,6 +105,11 @@ 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 { @@ -183,6 +188,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { private async updateTxExternalData(id: string): Promise { const tx = this.getTransaction(id); + const adapter = this.connector.network; await adapter.connect(); @@ -209,8 +215,10 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { } private async updateTxIncomingData(id: string): Promise { - await this.waitForTransactionStatus(id); - await this.waitForTransactionBlockId(id); + await this.waitForTxBlockAndStatus(id); + + if (subBridgeApi.isEvmAccount(this.getTransaction(id).externalNetwork as SubNetwork)) return; + await this.updateTxSigningData(id); await this.updateTxExternalData(id); } @@ -231,8 +239,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 from = tx.from as string; - const to = tx.to as string; + const sender = tx.to as string; + const recipient = tx.from as string; let subscription!: Subscription; let messageNonce!: number; @@ -262,7 +270,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { if (!isStandalone) { assetSendEventIndex = events.findIndex((e) => - isAssetAddedToChannel(e, this.asset, to, sended, adapter) + isAssetAddedToChannel(e, this.asset, recipient, sended, adapter) ); if (assetSendEventIndex !== -1) { @@ -273,7 +281,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { } } else { assetSendEventIndex = events.findIndex((e) => - isSoraBridgeAppBurned(e, this.asset, from, to, sended, adapter) + isSoraBridgeAppBurned(e, this.asset, sender, recipient, sended, adapter) ); if (assetSendEventIndex !== -1) { @@ -350,7 +358,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { soraHash = getBridgeProxyHash(foundedEvents, subBridgeApi.api); - [amount, eventIndex] = getDepositedBalance(foundedEvents, tx.to as string, subBridgeApi); + [amount, eventIndex] = getDepositedBalance(foundedEvents, tx.from as string, subBridgeApi); resolve(); } catch (error) { @@ -369,9 +377,9 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { } private async waitSoraBlockByHash(id: string): Promise { - const { hash, to, externalNetwork } = this.getTransaction(id); + const { hash, from, externalNetwork } = this.getTransaction(id); - if (!(hash && to && externalNetwork)) { + if (!(hash && from && externalNetwork)) { throw new Error(`[${this.constructor.name}] Lost transaction params`); } @@ -380,7 +388,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { try { await new Promise((resolve) => { - subscription = subBridgeApi.subscribeOnTransactionDetails(to, externalNetwork, hash).subscribe((data) => { + subscription = subBridgeApi.subscribeOnTransactionDetails(from, externalNetwork, hash).subscribe((data) => { if (data?.endBlock) { soraBlockNumber = data.endBlock; resolve(); @@ -417,8 +425,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { this.updateTransactionParams(id, { transactionState: BridgeTxStatus.Pending }); await this.checkTxId(id); - await this.waitForTransactionStatus(id); - await this.waitForTransactionBlockId(id); + await this.waitForTxBlockAndStatus(id); await this.waitForSendingExecution(id); await this.waitForIntermediateExecution(id); @@ -580,6 +587,8 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { private async waitForDestinationExecution(id: string): Promise { if (![SubTransferType.Relaychain, SubTransferType.Parachain].includes(this.transferType)) return; + console.log('waitForDestinationExecution'); + const tx = this.getTransaction(id); const messageHash = tx.payload.messageHash as string; @@ -601,6 +610,9 @@ 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) => { @@ -609,7 +621,9 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { isEvent(e, 'xcmpQueue', 'Success') || isEvent(e, 'xcmpQueue', 'Fail') ) { - return e.event.data[0].toString() === messageHash; + isReliableMessage = e.event.data[0].toString() === messageHash; + + return true; } return false; }); @@ -617,7 +631,7 @@ 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, @@ -626,7 +640,10 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { resolve(); } catch (error) { - reject(error); + // The message is reliable, but the deposit was not found + if (isReliableMessage) { + reject(error); + } } } ); diff --git a/src/utils/bridge/sub/utils.ts b/src/utils/bridge/sub/utils.ts index e82660b10..852d53d1d 100644 --- a/src/utils/bridge/sub/utils.ts +++ b/src/utils/bridge/sub/utils.ts @@ -1,4 +1,4 @@ -import { FPNumber } from '@sora-substrate/util'; +import { FPNumber, Operation } from '@sora-substrate/util'; import { subBridgeApi } from '@/utils/bridge/sub/api'; import { SubTransferType } from '@/utils/bridge/sub/types'; @@ -8,8 +8,15 @@ import type { CodecString, WithConnectionApi } 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 => { - return !tx.blockId && !tx.txId; + const signId = + subBridgeApi.isEvmAccount(tx.externalNetwork as SubNetwork) && !isOutgoingTx(tx) ? tx.externalHash : tx.txId; + + return !tx.blockId && !signId; }; export const getTransaction = (id: string): SubHistory => { From fdf0c5b64640346b15782b885746e732674a2f1f Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Tue, 9 Jul 2024 16:51:48 +0300 Subject: [PATCH 10/21] update history restoration --- src/consts/sub.ts | 2 +- src/utils/bridge/sub/classes/history.ts | 41 ++++++++++++++---------- src/utils/bridge/sub/classes/reducers.ts | 2 -- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/consts/sub.ts b/src/consts/sub.ts index 90cb0b944..8d29046f4 100644 --- a/src/consts/sub.ts +++ b/src/consts/sub.ts @@ -143,7 +143,7 @@ export const SUB_NETWORKS: Partial> = { decimals: 18, }, endpointUrls: ['https://rpc.api.moonbase.moonbeam.network', 'https://moonbase-rpc.dwellir.com'], - blockExplorerUrls: ['https://moonbase.moonscan.io'], + blockExplorerUrls: ['https://moonbase.subscan.io'], shortName: 'Moonbase', nodes: [ { diff --git a/src/utils/bridge/sub/classes/history.ts b/src/utils/bridge/sub/classes/history.ts index cd11e4022..7ca9b3acb 100644 --- a/src/utils/bridge/sub/classes/history.ts +++ b/src/utils/bridge/sub/classes/history.ts @@ -12,6 +12,7 @@ import { getDepositedBalance, getMessageAcceptedNonces, getMessageDispatchedNonces, + getParachainSystemMessageHash, isMessageDispatchedNonces, getReceivedAmount, isEvent, @@ -269,19 +270,15 @@ class SubBridgeHistory extends SubNetworksConnector { // SORA Parachain extrinsic events for next search const parachainExtrinsicEvents = networkEventsReversed.slice(messageDispatchedIndex); - // 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')); + // sended from SORA Parachain message hash (1) + const messageHash = getParachainSystemMessageHash(parachainExtrinsicEvents, soraParachainApi); - if (!messageToRelaychain && !messageToParachain) { + if (!messageHash) { return await this.processOutgoingToSoraParachain(history, asset, parachainExtrinsicEvents); } - const isRelaychain = subBridgeApi.isRelayChain(externalNetwork) && messageToRelaychain; - const isParachain = subBridgeApi.isParachain(externalNetwork) && messageToParachain; + const isRelaychain = subBridgeApi.isRelayChain(externalNetwork); + const isParachain = subBridgeApi.isParachain(externalNetwork); if (!isRelaychain && !isParachain) { console.info(`[${history.id}] not "${externalNetwork}" transaction, skip;`); @@ -290,7 +287,6 @@ 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 @@ -428,7 +424,9 @@ 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) => isEvent(e, 'xcmpQueue', 'Success')); + const messageEvent = networkExtrinsicEvents.find( + (e) => isEvent(e, 'xcmpQueue', 'Success') || isEvent(e, 'dmpQueue', 'ExecutedDownward') + ); const externalNetwork = history.externalNetwork as SubNetwork; const isRelayChain = subBridgeApi.isRelayChain(externalNetwork) && !messageEvent; const isParachain = @@ -447,7 +445,7 @@ class SubBridgeHistory extends SubNetworksConnector { ); if (isParachain) { - const messageHash = messageEvent.event.data.messageHash.toString(); + const messageHash = messageEvent.event.data[0].toString(); // Parachain block, found through relaychain validation data const parachainBlockId = await this.findParachainBlockIdOnRelaychain(history, relayChainBlockNumber, false); @@ -528,7 +526,7 @@ class SubBridgeHistory extends SubNetworksConnector { // relay chain should have send validation data in this blocks range const startSearch = relayChainBlockNumber; - const blocksRange = 3; + const blocksRange = 10; const endSearch = isOutgoing ? startSearch + blocksRange : startSearch - blocksRange; for (let relaychainBlockHeight = startSearch; relaychainBlockHeight !== endSearch; ) { @@ -540,7 +538,10 @@ class SubBridgeHistory extends SubNetworksConnector { const { descriptor } = e.event.data[0]; - if (descriptor.paraId.toNumber() !== network.getParachainId()) continue; + const descriptorParaId = descriptor.paraId.toNumber(); + const paraId = network.getParachainId(); + + if (descriptorParaId !== paraId) continue; history.relaychainBlockHeight = relaychainBlockHeight; history.relaychainBlockId = blockId; @@ -604,6 +605,8 @@ 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); @@ -614,9 +617,9 @@ class SubBridgeHistory extends SubNetworksConnector { isEvent(e, 'xcmpQueue', 'Success') || isEvent(e, 'xcmpQueue', 'Fail') ) { - const messageHashMatches = e.event.data[0].toString() === messageHash; + isReliableMessage = e.event.data[0].toString() === messageHash; - return messageHashMatches; + return true; } return false; }); @@ -647,7 +650,11 @@ class SubBridgeHistory extends SubNetworksConnector { return history; } catch { - continue; + if (isReliableMessage) { + break; + } else { + continue; + } } } diff --git a/src/utils/bridge/sub/classes/reducers.ts b/src/utils/bridge/sub/classes/reducers.ts index 49b677fee..15407207a 100644 --- a/src/utils/bridge/sub/classes/reducers.ts +++ b/src/utils/bridge/sub/classes/reducers.ts @@ -587,8 +587,6 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { private async waitForDestinationExecution(id: string): Promise { if (![SubTransferType.Relaychain, SubTransferType.Parachain].includes(this.transferType)) return; - console.log('waitForDestinationExecution'); - const tx = this.getTransaction(id); const messageHash = tx.payload.messageHash as string; From 6b02f06a1f11c0cbaff8c87499a1fd256b14a158 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Wed, 10 Jul 2024 11:39:19 +0300 Subject: [PATCH 11/21] ui updates --- src/components/mixins/InternalConnectMixin.ts | 8 +- src/components/mixins/WalletConnectMixin.ts | 4 +- src/components/pages/Bridge/AccountPanel.vue | 57 ++++++++-- .../shared/Dialog/SelectSoraAccount.vue | 2 +- src/store/bridge/actions.ts | 38 +++++-- src/utils/bridge/sub/classes/adapter.ts | 13 ++- .../classes/adapters/parachain/moonbase.ts | 26 ++--- .../classes/adapters/parachain/parachain.ts | 2 +- .../bridge/sub/classes/adapters/substrate.ts | 4 + src/utils/bridge/sub/classes/reducers.ts | 4 +- src/views/Bridge.vue | 105 ++++++++---------- src/views/Rewards.vue | 6 +- 12 files changed, 161 insertions(+), 108 deletions(-) diff --git a/src/components/mixins/InternalConnectMixin.ts b/src/components/mixins/InternalConnectMixin.ts index 15b61d701..cb5356ca3 100644 --- a/src/components/mixins/InternalConnectMixin.ts +++ b/src/components/mixins/InternalConnectMixin.ts @@ -3,11 +3,13 @@ import { Component, Mixins } from 'vue-property-decorator'; import TranslationMixin from '@/components/mixins/TranslationMixin'; import { PageNames } from '@/consts'; import { goTo } from '@/router'; -import { getter, mutation, state } from '@/store/decorators'; +import { action, 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; @@ -20,6 +22,10 @@ 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/WalletConnectMixin.ts b/src/components/mixins/WalletConnectMixin.ts index db2872a35..9d627a959 100644 --- a/src/components/mixins/WalletConnectMixin.ts +++ b/src/components/mixins/WalletConnectMixin.ts @@ -24,9 +24,11 @@ export default class WalletConnectMixin extends Mixins(InternalConnectMixin) { @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 937b192d2..0df2d8133 100644 --- a/src/components/pages/Bridge/AccountPanel.vue +++ b/src/components/pages/Bridge/AccountPanel.vue @@ -1,12 +1,25 @@ @@ -26,11 +39,19 @@ export default class BridgeAccountPanel extends Mixins(mixins.CopyAddressMixin, @Prop({ default: '', type: String }) readonly address!: string; @Prop({ default: '', type: String }) readonly name!: string; @Prop({ default: '', type: String }) readonly tooltip!: string; + + handleConnect(): void { + this.$emit('connect'); + } + + handleDisconnect(): void { + this.$emit('disconnect'); + } } diff --git a/src/components/shared/Dialog/SelectSoraAccount.vue b/src/components/shared/Dialog/SelectSoraAccount.vue index 6f49f7298..b04afc447 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!: (forgetAddress?: string) => Promise; + @action.wallet.account.logout public logout!: () => Promise; @action.wallet.account.renameAccount public rename!: (data: { address: string; name: string }) => Promise; getApi() { diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index 67d517d76..bec895a54 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -84,6 +84,26 @@ 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) { @@ -394,7 +414,7 @@ async function updateSoraNetworkFee(context: ActionContext): Promise): Promise { +async function updateBalancesFeesAndAmounts(context: ActionContext): Promise { const { dispatch } = bridgeActionContext(context); await Promise.allSettled([ @@ -448,19 +468,14 @@ const actions = defineActions({ }, async switchDirection(context): Promise { - const { commit, dispatch, state } = bridgeActionContext(context); + const { commit, state } = bridgeActionContext(context); commit.setSoraToEvm(!state.isSoraToEvm); commit.setAssetSenderBalance(); commit.setAssetRecipientBalance(); - await updateBalancesAndFees(context); - - if (state.focusedField === FocusedField.Received) { - await dispatch.setSendedAmount(state.amountReceived); - } else { - await dispatch.setReceivedAmount(state.amountSend); - } + await updateBalancesFeesAndAmounts(context); + await switchAmounts(context); }, async setAssetAddress(context, address?: string): Promise { @@ -474,8 +489,9 @@ const actions = defineActions({ dispatch.updateOutgoingMinLimit(), dispatch.updateOutgoingMaxLimit(), dispatch.updateIncomingMinLimit(), - updateBalancesAndFees(context), + updateBalancesFeesAndAmounts(context), ]); + await updateAmounts(context); }, async updateExternalBalance(context): Promise { @@ -567,7 +583,7 @@ const actions = defineActions({ const subscription = api.system.updated.subscribe(() => { updateExternalBlockNumber(context); - updateBalancesAndFees(context); + updateBalancesFeesAndAmounts(context); }); commit.setBlockUpdatesSubscription(subscription); diff --git a/src/utils/bridge/sub/classes/adapter.ts b/src/utils/bridge/sub/classes/adapter.ts index f9aa9f605..6fe021132 100644 --- a/src/utils/bridge/sub/classes/adapter.ts +++ b/src/utils/bridge/sub/classes/adapter.ts @@ -203,12 +203,19 @@ 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 transfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId: string) { - if ('transfer' in this.network) { - await (this.network as any).transfer(asset, recipient, amount, historyId); + 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; diff --git a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts index bf463d055..f9fbb8c36 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -39,11 +39,7 @@ export class MoonbaseParachainAdapter extends ParachainAdapter accountAddress: string, asset: RegisteredAsset ): Promise { - const assetMeta = this.getAssetMeta(asset); - - if (!assetMeta) return ZeroStringValue; - - return await this.assetsAccountRequest(accountAddress, assetMeta.id); + return await this.assetsAccountRequest(accountAddress, asset.externalAddress); } public async getAssetIdByMultilocation(multilocation: any): Promise { @@ -83,16 +79,16 @@ export class MoonbaseParachainAdapter extends ParachainAdapter return selector + publicKey + networkOption; } - public async transfer(asset: RegisteredAsset, recipient: string, amount: string | number, historyId: string) { - let currencyAddress = this.nativeAssetContractAddress; - - if (asset.symbol !== this.chainSymbol) { - const assetMeta = this.getAssetMeta(asset); - - if (!assetMeta) throw new Error(`[${this.constructor.name}] Asset not found`); - - currencyAddress = this.assetIdToEvmContractAddress(assetMeta.id); - } + 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 diff --git a/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts b/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts index ed52448c6..b62f25372 100644 --- a/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/parachain.ts @@ -58,7 +58,7 @@ export class ParachainAdapter extends SubAdapter { return minBalance > '1' ? minBalance : ZeroStringValue; } - public async getNetworkFee(asset: RegisteredAsset, sender: string, recipient: string): Promise { + 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 diff --git a/src/utils/bridge/sub/classes/adapters/substrate.ts b/src/utils/bridge/sub/classes/adapters/substrate.ts index 6c470a66c..ed5e4d6ab 100644 --- a/src/utils/bridge/sub/classes/adapters/substrate.ts +++ b/src/utils/bridge/sub/classes/adapters/substrate.ts @@ -135,6 +135,10 @@ 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 { diff --git a/src/utils/bridge/sub/classes/reducers.ts b/src/utils/bridge/sub/classes/reducers.ts index 15407207a..fd073d39e 100644 --- a/src/utils/bridge/sub/classes/reducers.ts +++ b/src/utils/bridge/sub/classes/reducers.ts @@ -161,7 +161,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { // open connections await this.connector.start(); // sign transaction (from is sora account) - await this.connector.transfer(asset, tx.from as string, tx.amount as string, id); + await this.connector.incomingTransfer(asset, tx.from as string, tx.amount as string, id); // save start block when tx was signed await this.saveStartBlock(id); } @@ -473,7 +473,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { // open connections await this.connector.start(); // sign transaction - await subBridgeApi.transfer(asset, tx.to as string, tx.amount as string, tx.externalNetwork as SubNetwork, id); + await this.connector.outgoingTransfer(asset, tx.to as string, tx.amount as string, id); // save start block when tx was signed await this.saveStartBlock(id); } diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index e7f88dcef..05fa639b3 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -68,22 +68,19 @@ /> -
- - - - -
- - {{ t('changeAccountText') }} - - - {{ t('disconnectWalletText') }} - -
-
+ + + + -
- - - - -
- - {{ t('changeAccountText') }} - - - {{ t('disconnectWalletText') }} - -
-
+ + + + @@ -693,34 +703,9 @@ export default class Bridge extends Mixins(