diff --git a/env.json b/env.json index 319e9c74f..cc40ff0bb 100644 --- a/env.json +++ b/env.json @@ -56,6 +56,7 @@ "KusamaSora": true, "Polkadot": true, "PolkadotSora": true, + "PolkadotAcala": true, "Liberland": false }, "EVM_NETWORKS_IDS": [56, 8217], diff --git a/package.json b/package.json index 73a2839bc..c4b5eb58b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "polkaswap-exchange-web", - "version": "1.32.2", + "version": "1.33.0", "repository": { "type": "git", "url": "https://github.com/sora-xor/polkaswap-exchange-web.git" @@ -27,7 +27,7 @@ }, "dependencies": { "@metamask/detect-provider": "^2.0.0", - "@soramitsu/soraneo-wallet-web": "1.32.8-beta", + "@soramitsu/soraneo-wallet-web": "1.33.1", "@walletconnect/ethereum-provider": "^2.11.2", "@walletconnect/modal": "^2.6.2", "core-js": "^3.36.0", diff --git a/src/assets/img/networks/acala.svg b/src/assets/img/networks/acala.svg new file mode 100644 index 000000000..1b92bb9b8 --- /dev/null +++ b/src/assets/img/networks/acala.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/networks/moonbase.svg b/src/assets/img/networks/moonbase.svg new file mode 100644 index 000000000..6e7977e3a --- /dev/null +++ b/src/assets/img/networks/moonbase.svg @@ -0,0 +1,136 @@ + + diff --git a/src/components/mixins/BridgeTransactionMixin.ts b/src/components/mixins/BridgeTransactionMixin.ts index 18fb58fad..8ca6a313e 100644 --- a/src/components/mixins/BridgeTransactionMixin.ts +++ b/src/components/mixins/BridgeTransactionMixin.ts @@ -49,7 +49,7 @@ export default class BridgeTransactionMixin extends Mixins(NetworkFormatterMixin return this.tx?.blockId ?? ''; } - get txEventIndex(): number | undefined { + get txInternalEventIndex(): number | undefined { return this.tx?.payload?.eventIndex; } @@ -65,6 +65,10 @@ export default class BridgeTransactionMixin extends Mixins(NetworkFormatterMixin return this.tx?.externalBlockId ?? ''; } + get txExternalEventIndex(): number | undefined { + return this.tx && 'externalEventIndex' in this.tx ? this.tx.externalEventIndex : undefined; + } + get externalNetworkType(): Nullable { return this.tx?.externalNetworkType; } @@ -78,7 +82,7 @@ export default class BridgeTransactionMixin extends Mixins(NetworkFormatterMixin this.soraNetwork, this.txSoraId, this.txInternalBlockNumber ?? this.txInternalBlockId, - this.txEventIndex + this.txInternalEventIndex ); } @@ -90,7 +94,7 @@ export default class BridgeTransactionMixin extends Mixins(NetworkFormatterMixin this.externalNetworkId, this.txExternalHash, this.txExternalBlockNumber ?? this.txExternalBlockId, - this.txEventIndex + this.txExternalEventIndex ); } diff --git a/src/components/mixins/NetworkFormatterMixin.ts b/src/components/mixins/NetworkFormatterMixin.ts index 342410f2b..8c662f453 100644 --- a/src/components/mixins/NetworkFormatterMixin.ts +++ b/src/components/mixins/NetworkFormatterMixin.ts @@ -141,6 +141,8 @@ export default class NetworkFormatterMixin extends Mixins(TranslationMixin) { return 'polkadot'; case SubNetworkId.PolkadotSora: return 'sora-polkadot'; + case SubNetworkId.PolkadotAcala: + return 'acala'; case SubNetworkId.Kusama: return 'kusama'; case SubNetworkId.Rococo: @@ -151,6 +153,8 @@ export default class NetworkFormatterMixin extends Mixins(TranslationMixin) { return 'sora-kusama'; case SubNetworkId.Liberland: return 'liberland'; + case SubNetworkId.AlphanetMoonbase: + return 'moonbase'; default: return 'ethereum'; } diff --git a/src/consts/sub.ts b/src/consts/sub.ts index 411a7aca8..76e59fa42 100644 --- a/src/consts/sub.ts +++ b/src/consts/sub.ts @@ -14,7 +14,6 @@ export const SUB_NETWORKS: Partial> = { symbol: 'KSM', decimals: 12, }, - endpointUrls: ['wss://kusama-rpc.polkadot.io', 'wss://kusama-rpc.dwellir.com'], blockExplorerUrls: ['https://kusama.subscan.io'], shortName: 'Kusama', nodes: [ @@ -38,7 +37,6 @@ export const SUB_NETWORKS: Partial> = { symbol: 'DOT', decimals: 10, }, - endpointUrls: ['wss://rpc.polkadot.io', 'wss://polkadot-rpc.dwellir.com'], blockExplorerUrls: ['https://polkadot.subscan.io'], shortName: 'Polkadot', nodes: [ @@ -54,6 +52,29 @@ export const SUB_NETWORKS: Partial> = { }, ], }, + [SubNetworkId.PolkadotAcala]: { + id: SubNetworkId.PolkadotAcala, + name: 'Acala', + nativeCurrency: { + name: 'ACA', + symbol: 'ACA', + decimals: 12, + }, + blockExplorerUrls: ['https://acala.subscan.io'], + shortName: 'Acala', + nodes: [ + { + chain: 'Acala', + name: 'Acala Foundation', + address: 'wss://acala-rpc-0.aca-api.network', + }, + { + chain: 'Acala', + name: 'Dwellir', + address: 'wss://acala-rpc.dwellir.com', + }, + ], + }, [SubNetworkId.Rococo]: { id: SubNetworkId.Rococo, name: 'Rococo', @@ -62,7 +83,6 @@ export const SUB_NETWORKS: Partial> = { symbol: 'ROC', decimals: 12, }, - endpointUrls: ['wss://rococo-rpc.polkadot.io'], blockExplorerUrls: [], shortName: 'Rococo', nodes: [ @@ -73,6 +93,42 @@ export const SUB_NETWORKS: Partial> = { }, ], }, + [SubNetworkId.Alphanet]: { + id: SubNetworkId.Alphanet, + name: 'Moonbase Relay Testnet', + nativeCurrency: { + name: 'ALPHA', + symbol: 'ALPHA', // "DEV" + decimals: 12, + }, + blockExplorerUrls: [], + shortName: 'Alphanet', + nodes: [ + { + chain: 'Moonbase Relay Testnet', + name: 'Parity', + address: 'wss://frag-moonbase-relay-rpc-ws.g.moonbase.moonbeam.network', + }, + ], + }, + [SubNetworkId.AlphanetMoonbase]: { + id: SubNetworkId.AlphanetMoonbase, + name: 'Moonbase Alpha', + nativeCurrency: { + name: 'GLMR', + symbol: 'GLMR', // "DEV" + decimals: 18, + }, + blockExplorerUrls: [], + shortName: 'Alpha', + nodes: [ + { + chain: 'Moonbase Alpha', + name: 'Parity', + address: 'wss://wss.api.moonbase.moonbeam.network', + }, + ], + }, // SORA Parachains [SubNetworkId.RococoSora]: { id: SubNetworkId.RococoSora, @@ -82,9 +138,6 @@ export const SUB_NETWORKS: Partial> = { symbol: 'XOR', decimals: 18, }, - endpointUrls: [ - 'wss://ws.parachain-collator-1.c1.stg1.sora2.soramitsu.co.jp', // stage - ], blockExplorerUrls: [], shortName: 'SORA ROC', nodes: [ @@ -103,9 +156,6 @@ export const SUB_NETWORKS: Partial> = { symbol: 'XOR', decimals: 18, }, - endpointUrls: [ - 'wss://ws.parachain-collator-2.c2.sora2.soramitsu.co.jp', // prod - ], blockExplorerUrls: [], shortName: 'SORA KSM', nodes: [ @@ -124,9 +174,6 @@ export const SUB_NETWORKS: Partial> = { symbol: 'XOR', decimals: 18, }, - endpointUrls: [ - 'wss://ws.parachain-collator-3.pc3.sora2.soramitsu.co.jp', // prod - ], blockExplorerUrls: [], shortName: 'SORA DOT', nodes: [ @@ -137,6 +184,25 @@ export const SUB_NETWORKS: Partial> = { }, ], }, + [SubNetworkId.AlphanetSora]: { + id: SubNetworkId.AlphanetSora, + name: 'SORA Alphanet Parachain', + nativeCurrency: { + name: 'XOR', + symbol: 'XOR', + decimals: 18, + }, + blockExplorerUrls: [], + shortName: 'SORA Alphanet', + nodes: [ + { + chain: 'SORA Alphanet Parachain', + name: 'Soramitsu', + address: 'wss://ws.parachain-collator-2.c1.stg1.sora2.soramitsu.co.jp', + }, + ], + }, + // Standalones [SubNetworkId.Liberland]: { id: SubNetworkId.Liberland, name: 'Liberland', @@ -145,8 +211,19 @@ export const SUB_NETWORKS: Partial> = { symbol: 'LLD', decimals: 12, }, - endpointUrls: [], - blockExplorerUrls: [], + nodes: [ + { + chain: 'Liberland', + name: 'Dwellir', + address: 'wss://liberland-rpc.dwellir.com', + }, + { + chain: 'Liberland', + name: 'Liberland Governance', + address: 'wss://mainnet.liberland.org', + }, + ], + blockExplorerUrls: ['https://chainscan.mainnet.liberland.org'], shortName: 'Liberland', }, }; @@ -170,4 +247,16 @@ export const SUB_TRANSFER_FEES: SubNetworksFees = { [BridgeTxDirection.Incoming]: '0', }, }, + [SubNetworkId.PolkadotAcala]: { + ACA: { + [BridgeTxDirection.Outgoing]: '6429600000', + [BridgeTxDirection.Incoming]: '0', + }, + }, + [SubNetworkId.AlphanetMoonbase]: { + ACA: { + [BridgeTxDirection.Outgoing]: '34313700000000', + [BridgeTxDirection.Incoming]: '0', + }, + }, }; diff --git a/src/lang/zh_CN.json b/src/lang/zh_CN.json index 880ff86b0..636950c64 100644 --- a/src/lang/zh_CN.json +++ b/src/lang/zh_CN.json @@ -180,7 +180,7 @@ "fee": "@:networkFeeText", "enterAddress": "输入地址", "badAddress": "地址不正确", - "enterAmount": "输入数额", + "enterAmount": "输入数量", "badAmount": "@:insufficientBalanceText", "confirmTitle": "确认交易", "errorAddress": "无效地址。请检查并重试。", @@ -328,7 +328,7 @@ "transaction": { "title": "交易细节", "blockId": "区块 ID", - "status": "状况", + "status": "状态", "statuses": { "pending": "等待确认...", "failed": "交易失败", @@ -372,7 +372,7 @@ "confirmTransactionText": "确认交易", "retryText": "重试", "networkFeeText": "网络费用", - "networkFeeTooltipText": "网络费是用来保证{Sora}系统的增长和稳定的性能。", + "networkFeeTooltipText": "网络费用用于确保{Sora}系统的增长和稳定运行。", "ethNetworkFeeTooltipText": "请注意, {AppName}上显示的{network}网络费用只是粗略估计,您可以在确认交易前在连接的{network}钱包中查看正确的费用金额。", "marketText": "市场", "marketAlgorithmText": "市场算法", @@ -395,7 +395,7 @@ "Explore\/Staking": "质押", "Explore\/Farming": "农耕", "Staking": "质押", - "OrderBook": "贸易", + "OrderBook": "现货交易", "Explore\/Books": "@:pageTitle.OrderBook", "Kensetsu": "Kensetsu" }, @@ -412,7 +412,7 @@ "StakingContainer": "质押", "Explore\/Container": "探索", "SoraCard": "{Sora}卡", - "OrderBook": "贸易", + "OrderBook": "现货交易", "Kensetsu": "Kensetsu" }, "social": { @@ -484,7 +484,7 @@ "chooseToken": "选择代币", "chooseAToken": "选择一个代币", "chooseTokens": "选择代币", - "enterAmount": "输入金额" + "enterAmount": "输入数量" }, "transfers": { "from": "从", @@ -640,7 +640,7 @@ "networkTitle": "{network}交易", "transactionHash": "交易哈希值", "networkInfo": { - "status": "状况", + "status": "状态", "date": "日期", "amount": "数量", "transactionFee": "交易费用", @@ -1302,11 +1302,11 @@ }, "overview": { "title": "{Sora}质押", - "description": "在 {Sora} 网络上质押 {XOR} 代币作为提名人,以验证交易并获得 {VAL} 代币奖励。" + "description": "在{Sora}网络上作为提名人质押{XOR}代币,以验证交易并赚取{VAL}代币奖励。" }, "newStake": { "title": "开始质押", - "minStakeWarning": "获得奖励的最低赌注是{min} {symbol}" + "minStakeWarning": "领取奖励的最低质押额为{min} {symbol}" }, "validators": { "save": "保存更改", @@ -1333,8 +1333,8 @@ "rewarded": "奖励", "totalLiquidityStaked": "质押的总流动性", "rewardToken": "奖励代币", - "unstakingPeriod": "质押期限", - "minimumStake": "最低投注额", + "unstakingPeriod": "解除质押期", + "minimumStake": "最低质押额", "nominators": "提名人", "validators": "验证者", "selectedValidators": "选定的验证者" @@ -1435,18 +1435,18 @@ "Sell": "出售{asset}", "orderBook": "订单簿", "marketTrades": "市场交易", - "market": "市场", - "limit": "限制", + "market": "市价", + "limit": "限价", "price": "价格", - "total": "全部的", + "total": "总计", "amount": "数量", "time": "时间", "month": "月", - "change": "改变", + "change": "变化", "cantPlaceOrder": "无法下订单", - "enterAmount": "输入金额", + "enterAmount": "输入数量", "setPrice": "设定价格", - "dayVolume": "一维体积", + "dayVolume": "一天 成交量", "stop": "书已停止", "tokenPair": "代币对", "book": { @@ -1454,9 +1454,9 @@ "noBids": "没有公开投标" }, "history": { - "tradeHistory": "贸易历史", + "tradeHistory": "交易历史", "orderHistory": "订单历史", - "openOrders": "未结订单{value}", + "openOrders": "未完成订单{value}", "connect": "连接账户开始交易", "cancel": "取消订单{value}", "cancelAll": "全部取消" @@ -1480,13 +1480,13 @@ }, "tradingPair": { "choosePair": "选择交易对", - "volume": "体积", + "volume": "成交量", "dailyChange": "每日变化", - "status": "地位", + "status": "状态", "total": "{amount} {symbol} 在{amount2} {symbol2}" }, "bookStatus": { - "active": "积极的", + "active": "活跃", "placeable": "可放置", "cancelable": "可取消", "inactive": "不活跃" @@ -1498,11 +1498,11 @@ "bookWidget": "特定资产的实时、不断更新的买入(买入)和卖出(卖出)订单记录,按价格水平组织。订单簿显示市场深度,包括以不同价格提供的资产数量。交易者利用这种详细的视图来衡量市场情绪,确定潜在的阻力位和支撑位,并根据现有的需求和供应预测价格变动", "marketWidget": "该小部件显示市场中已执行交易的实时流,提供有关交易量、近期活动和当前市场趋势的信息。通过观察实际交易的时间、价格和规模,交易者可以深入了解市场动态和情绪,帮助他们发现交易机会并做出明智的决策", "txDetails": { - "orderType": "“买入”订单将仅以指定价格或更低价格执行,而“卖出”订单将仅以指定价格或更高价格执行。这种控制可确保您支付的费用或售价不会低于您愿意接受的价格。", - "expiryDate": "“到期日”是您的订单执行的最后期限。如果在此日期之前市场未达到您指定的价格,订单将自动取消。如果市场状况与您的交易偏好不符,您不必永远等待。", - "amount": "“金额”是指您想要在订单中购买或出售的资产总数。指定很重要,因为它决定了交易规模,影响买入订单的总成本或卖出订单的收入。", - "limit": "“限价”是您为限价单设置的精确价格。仅当资产的市场价格达到您的限价时才会执行交易,确保您不会以高于此指定值的价格买入或以低于此指定值的价格卖出。", - "locked": "“锁定”显示订单正在进行时要持有的资产数量。" + "orderType": "“买入”订单只会以指定价格或更低价格执行,而“卖出”订单只会以指定价格或更高价格执行。这种控制确保您不会以比您舒适的价格更高的价格购买,或以比您舒适的价格更低的价格出售。", + "expiryDate": "“到期日”是您订单执行的截止日期。如果市场在此日期之前未达到您指定的价格,订单将自动取消。如果市场条件与您的交易偏好不符,您不必永远等待。", + "amount": "“数量”指的是您在订单中想要购买或出售的资产总数。这一点很重要,因为它决定了您的交易规模,影响着买入订单的总成本或卖出订单的收入。", + "limit": "“限价”是您为限价订单设定的精确价格。只有当资产的市场价格达到您的限价时,交易才会执行,确保您不会以高于或低于指定值的价格购买或出售。", + "locked": "“已锁定”显示了订单进行时需要持有的资产数量。" }, "bookStatus": { "active": "启用完整的交易功能。您可以下新订单或取消现有订单。", @@ -1542,7 +1542,7 @@ "reading": "已达限额:每个仓位限价单数量为1024个。请等待部分订单完成" }, "outOfBounds": { - "reading": "超出区块链范围:您输入的金额超出了区块链允许的范围。分钟: {min} ;最大值: {max}", + "reading": "超出区块链范围:您输入的金额超出了区块链允许的范围。最小值: {min} ;最大值: {max}", "reason": "金额不符合区块链范围" }, "multipleOf": { diff --git a/src/store/assets/actions.ts b/src/store/assets/actions.ts index 94b83e8a7..7533465a9 100644 --- a/src/store/assets/actions.ts +++ b/src/store/assets/actions.ts @@ -91,6 +91,20 @@ async function getSubRegisteredAssets( if (!subNetwork) return []; const subNetworkId = subNetwork as SubNetwork; + + // [TODO] remove when non ACA tokens are supported + if (subNetworkId === SubNetworkId.PolkadotAcala) { + return [ + { + '0x001ddbe1a880031da72f7ea421260bec635fa7d1aa72593d5412795408b6b2ba': { + address: '', + decimals: 12, + kind: 'Sidechain', + }, + }, + ]; + } + const networkAssets = await subBridgeApi.getRegisteredAssets(subNetworkId); const registeredAssets = Object.entries(networkAssets).map(([soraAddress, assetData]) => { return { diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index d53047b90..33c83cb49 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -28,7 +28,7 @@ import evmBridge from '@/utils/bridge/evm'; import { evmBridgeApi } from '@/utils/bridge/evm/api'; import subBridge from '@/utils/bridge/sub'; import { subBridgeApi } from '@/utils/bridge/sub/api'; -import { subBridgeConnector } from '@/utils/bridge/sub/classes/adapter'; +import type { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; import { updateSubBridgeHistory } from '@/utils/bridge/sub/classes/history'; import ethersUtil from '@/utils/ethers-util'; @@ -49,10 +49,11 @@ const getSoraBalance = async (accountAddress: string, asset: RegisteredAccountAs const getExternalBalance = async ( accountAddress: string, asset: RegisteredAccountAsset, - isSub: boolean + isSub: boolean, + subConnector: SubNetworksConnector ): Promise => { return isSub - ? await subBridgeConnector.network.getTokenBalance(accountAddress, asset?.externalAddress) + ? await subConnector.network.getTokenBalance(accountAddress, asset) : await ethersUtil.getAccountAssetBalance(accountAddress, asset?.externalAddress); }; @@ -60,14 +61,15 @@ const getAccountBridgeBalance = async ( accountAddress: string, asset: Nullable, isSora: boolean, - isSub: boolean + isSub: boolean, + subConnector: SubNetworksConnector ): Promise => { if (!(asset?.address && accountAddress)) return ZeroStringValue; try { return isSora ? await getSoraBalance(accountAddress, asset) - : await getExternalBalance(accountAddress, asset, isSub); + : await getExternalBalance(accountAddress, asset, isSub, subConnector); } catch { return ZeroStringValue; } @@ -154,11 +156,11 @@ async function getEvmNetworkFee(context: ActionContext): Promise } async function getSubNetworkFee(context: ActionContext): Promise { - const { commit, getters } = bridgeActionContext(context); + const { commit, getters, state } = bridgeActionContext(context); let fee = ZeroStringValue; if (getters.asset && getters.isRegisteredAsset && getters.sender && getters.recipient) { - fee = await subBridgeConnector.network.getNetworkFee(getters.asset, getters.sender, getters.recipient); + fee = await state.subBridgeConnector.network.getNetworkFee(getters.asset, getters.sender, getters.recipient); } commit.setExternalNetworkFee(fee); @@ -187,13 +189,13 @@ async function updateExternalLockedBalance(context: ActionContext): Pr async function updateEvmBalances(context: ActionContext): Promise { const { commit, getters, state } = bridgeActionContext(context); const { sender, recipient, asset, nativeToken } = getters; - const { isSoraToEvm } = state; + const { isSoraToEvm, subBridgeConnector: subConnector } = state; const spender = isSoraToEvm ? recipient : sender; const [senderBalance, recipientBalance, nativeBalance] = await Promise.all([ - getAccountBridgeBalance(sender, asset, isSoraToEvm, false), - getAccountBridgeBalance(recipient, asset, !isSoraToEvm, false), - getAccountBridgeBalance(spender, nativeToken, false, false), + getAccountBridgeBalance(sender, asset, isSoraToEvm, false, subConnector), + getAccountBridgeBalance(recipient, asset, !isSoraToEvm, false, subConnector), + getAccountBridgeBalance(spender, nativeToken, false, false, subConnector), ]); commit.setAssetSenderBalance(senderBalance); @@ -204,13 +206,13 @@ async function updateEvmBalances(context: ActionContext): Promise): Promise { const { commit, getters, state } = bridgeActionContext(context); const { sender, recipient, asset, nativeToken } = getters; - const { isSoraToEvm } = state; + const { isSoraToEvm, subBridgeConnector: subConnector } = state; const spender = sender; const [senderBalance, recipientBalance, nativeBalance] = await Promise.all([ - getAccountBridgeBalance(sender, asset, isSoraToEvm, true), - getAccountBridgeBalance(recipient, asset, !isSoraToEvm, true), - getAccountBridgeBalance(spender, nativeToken, false, true), + getAccountBridgeBalance(sender, asset, isSoraToEvm, true, subConnector), + getAccountBridgeBalance(recipient, asset, !isSoraToEvm, true, subConnector), + getAccountBridgeBalance(spender, nativeToken, false, true, subConnector), ]); commit.setAssetSenderBalance(senderBalance); @@ -296,7 +298,7 @@ async function updateExternalMinBalance(context: ActionContext): Promi let minBalance = ZeroStringValue; if (getters.isSubBridge && getters.asset && !state.isSoraToEvm) { - minBalance = await subBridgeConnector.network.getAssetMinDeposit(getters.asset.externalAddress); + minBalance = await state.subBridgeConnector.network.getAssetMinDeposit(getters.asset); } commit.setExternalMinBalance(minBalance); @@ -329,10 +331,10 @@ function calculateMaxLimit( } async function updateExternalBlockNumber(context: ActionContext): Promise { - const { getters, commit } = bridgeActionContext(context); + const { getters, commit, state } = bridgeActionContext(context); try { const blockNumber = getters.isSubBridge - ? await subBridgeConnector.network.getBlockNumber() + ? await state.subBridgeConnector.network.getBlockNumber() : await ethersUtil.getBlockNumber(); commit.setExternalBlockNumber(blockNumber); @@ -482,13 +484,13 @@ const actions = defineActions({ }, async updateIncomingMinLimit(context): Promise { - const { commit, getters } = bridgeActionContext(context); + const { commit, getters, state } = bridgeActionContext(context); let minLimit = FPNumber.ZERO; - if (getters.isSubBridge && getters.asset && getters.isRegisteredAsset && subBridgeConnector.soraParachain) { + if (getters.isSubBridge && getters.asset && getters.isRegisteredAsset && state.subBridgeConnector.soraParachain) { try { - const value = await subBridgeConnector.soraParachain.getAssetMinimumAmount(getters.asset.address); + const value = await state.subBridgeConnector.soraParachain.getAssetMinimumAmount(getters.asset.address); minLimit = FPNumber.fromCodecValue(value, getters.asset.externalDecimals); } catch (error) { console.error(error); @@ -499,14 +501,14 @@ const actions = defineActions({ }, async updateOutgoingMinLimit(context): Promise { - const { commit, getters } = bridgeActionContext(context); + const { commit, getters, state } = bridgeActionContext(context); let minLimit = FPNumber.ZERO; if (getters.isSubBridge && getters.asset && getters.isRegisteredAsset) { try { // [TODO: Bridge] should be a backend call in future. Now it is existential deposit - const value = await subBridgeConnector.network.getAssetMinDeposit(getters.asset.externalAddress); + const value = await state.subBridgeConnector.network.getAssetMinDeposit(getters.asset); minLimit = FPNumber.fromCodecValue(value, getters.asset.externalDecimals); } catch (error) { console.error(error); diff --git a/src/store/bridge/getters.ts b/src/store/bridge/getters.ts index cb7216d26..4a071b43d 100644 --- a/src/store/bridge/getters.ts +++ b/src/store/bridge/getters.ts @@ -7,7 +7,6 @@ import { defineGetters } from 'direct-vuex'; import { ZeroStringValue } from '@/consts'; import { bridgeGetterContext } from '@/store/bridge'; import { subBridgeApi } from '@/utils/bridge/sub/api'; -import { formatSubAddress } from '@/utils/bridge/sub/utils'; import type { BridgeState } from './types'; import type { IBridgeTransaction, CodecString } from '@sora-substrate/util'; @@ -48,7 +47,10 @@ const getters = defineGetters()({ if (!selectedNetwork) return null; - const { symbol } = selectedNetwork.nativeCurrency; + const symbol = selectedNetwork.nativeCurrency?.symbol; + + if (!symbol) return null; + const filteredBySymbol = assets.filter((asset) => asset.symbol === symbol); const registered = filteredBySymbol.find((asset) => asset.address in registeredAssets); @@ -115,21 +117,26 @@ const getters = defineGetters()({ }, externalAccountFormatted(...args): string { - const { getters, rootState } = bridgeGetterContext(args); - const { subSS58 } = rootState.web3; + const { getters, state } = bridgeGetterContext(args); if (!getters.isSubBridge) return getters.externalAccount; - return formatSubAddress(getters.externalAccount, subSS58); + return state.subBridgeConnector.network?.subNetworkConnection.nodeIsConnected + ? state.subBridgeConnector.network.formatAddress(getters.externalAccount) + : getters.externalAccount; }, sender(...args): string { const { state, rootState, getters } = bridgeGetterContext(args); const { address: soraAddress } = rootState.wallet.account; - const { evmAddress, subSS58 } = rootState.web3; + const { evmAddress } = rootState.web3; if (getters.isSubBridge) { - return !state.isSoraToEvm && soraAddress ? formatSubAddress(soraAddress, subSS58) : soraAddress; + if (state.isSoraToEvm) return soraAddress; + + return state.subBridgeConnector.network?.subNetworkConnection.nodeIsConnected + ? state.subBridgeConnector.network.formatAddress(soraAddress) + : soraAddress; } return state.isSoraToEvm ? soraAddress : evmAddress; @@ -146,10 +153,14 @@ const getters = defineGetters()({ recipient(...args): string { const { state, rootState, getters } = bridgeGetterContext(args); const { address: soraAddress } = rootState.wallet.account; - const { evmAddress, subAddress, subSS58 } = rootState.web3; + const { evmAddress, subAddress } = rootState.web3; if (getters.isSubBridge) { - return state.isSoraToEvm && subAddress ? formatSubAddress(subAddress, subSS58) : subAddress; + if (!state.isSoraToEvm) return subAddress; + + return state.subBridgeConnector.network?.subNetworkConnection.nodeIsConnected + ? state.subBridgeConnector.network.formatAddress(subAddress) + : subAddress; } return state.isSoraToEvm ? evmAddress : soraAddress; diff --git a/src/store/bridge/state.ts b/src/store/bridge/state.ts index 86b51483a..f41bd43da 100644 --- a/src/store/bridge/state.ts +++ b/src/store/bridge/state.ts @@ -1,6 +1,7 @@ import { FPNumber } from '@sora-substrate/util'; import { ZeroStringValue } from '@/consts'; +import { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; import type { BridgeState } from './types'; @@ -38,6 +39,8 @@ function initialState(): BridgeState { waitingForApprove: {}, inProgressIds: {}, notificationData: null, + // connector + subBridgeConnector: new SubNetworksConnector(), }; } diff --git a/src/store/bridge/types.ts b/src/store/bridge/types.ts index e62030870..97c71230e 100644 --- a/src/store/bridge/types.ts +++ b/src/store/bridge/types.ts @@ -1,3 +1,5 @@ +import type { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; + import type { FPNumber, CodecString, IBridgeTransaction } from '@sora-substrate/util'; import type { BridgeNetworkId } from '@sora-substrate/util/build/bridgeProxy/types'; import type { Subscription } from 'rxjs'; @@ -36,4 +38,5 @@ export type BridgeState = { waitingForApprove: Record; inProgressIds: Record; notificationData: Nullable; + subBridgeConnector: SubNetworksConnector; }; diff --git a/src/store/rewards/actions.ts b/src/store/rewards/actions.ts index 9aea195b7..0d131d309 100644 --- a/src/store/rewards/actions.ts +++ b/src/store/rewards/actions.ts @@ -176,7 +176,7 @@ const actions = defineActions({ commit.setTxError(false); if (externalRewardsSelected && state.transactionStep === 1) { - const internalAddressHex = await ethersUtil.accountAddressToHex(internalAddress); + const internalAddressHex = ethersUtil.accountAddressToHex(internalAddress); const keccakHex = ethers.keccak256(internalAddressHex); const message = ethers.getBytes(keccakHex); // Uint8Array const signer = await ethersUtil.getSigner(); diff --git a/src/store/web3/actions.ts b/src/store/web3/actions.ts index 83c7fc2be..a27708f88 100644 --- a/src/store/web3/actions.ts +++ b/src/store/web3/actions.ts @@ -6,23 +6,19 @@ import { ethers } from 'ethers'; import { KnownEthBridgeAsset, SmartContracts, SmartContractType } from '@/consts/evm'; import { web3ActionContext } from '@/store/web3'; -import { SubNetworksConnector, subBridgeConnector } from '@/utils/bridge/sub/classes/adapter'; +import { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; import ethersUtil, { Provider, PROVIDER_ERROR } from '@/utils/ethers-util'; import type { SubNetwork } from '@sora-substrate/util/build/bridgeProxy/sub/types'; import type { ActionContext } from 'vuex'; async function connectSubNetwork(context: ActionContext): Promise { - const { getters, commit } = web3ActionContext(context); + const { getters, rootState } = web3ActionContext(context); const subNetwork = getters.selectedNetwork; if (!subNetwork) return; - await subBridgeConnector.open(subNetwork.id as SubNetwork); - - const ss58 = subBridgeConnector.network.api.registry.chainSS58; - - if (ss58 !== undefined) commit.setSubSS58(ss58); + await rootState.bridge.subBridgeConnector.open(subNetwork.id as SubNetwork); } async function updateProvidedEvmNetwork(context: ActionContext, evmNetworkId?: number): Promise { @@ -127,9 +123,10 @@ const actions = defineActions({ ethersUtil.disconnectEvmProvider(provider); }, - async disconnectExternalNetwork(_context): Promise { + async disconnectExternalNetwork(context): Promise { + const { rootState } = web3ActionContext(context); // SUB - await subBridgeConnector.stop(); + await rootState.bridge.subBridgeConnector.stop(); }, async selectExternalNetwork(context, { id, type }: { id: BridgeNetworkId; type: BridgeNetworkType }): Promise { diff --git a/src/store/web3/mutations.ts b/src/store/web3/mutations.ts index 050a68c46..955a37532 100644 --- a/src/store/web3/mutations.ts +++ b/src/store/web3/mutations.ts @@ -24,10 +24,6 @@ const mutations = defineMutations()({ state.subAddressName = name; }, - setSubSS58(state, prefix: number) { - state.subSS58 = prefix; - }, - setEvmNetworksApp(state, networksIds: EvmNetwork[]): void { state.evmNetworkApps = Object.freeze([...networksIds]); }, diff --git a/src/store/web3/state.ts b/src/store/web3/state.ts index 4f3d5fd20..5c36d0ef9 100644 --- a/src/store/web3/state.ts +++ b/src/store/web3/state.ts @@ -8,7 +8,6 @@ export function initialState(): Web3State { evmAddress: '', // external evm address subAddress: '', // external sub address subAddressName: '', - subSS58: 69, // external sub network ss58 prefix (sora by default) networkType: null, // network type for selected network networkSelected: null, // network selected by user diff --git a/src/store/web3/types.ts b/src/store/web3/types.ts index ce2b58615..b04935f69 100644 --- a/src/store/web3/types.ts +++ b/src/store/web3/types.ts @@ -30,7 +30,6 @@ export type Web3State = { evmAddress: string; subAddress: string; subAddressName: string; - subSS58: number; networkType: Nullable; networkSelected: Nullable; diff --git a/src/styles/common.scss b/src/styles/common.scss index 9b30019d9..6b2ac61a9 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -18,7 +18,7 @@ $country-emoji-font: 'Twemoji Country Flags'; } } -$networks: 'sora', 'ethereum', 'ethereum-classic', 'avalanche', 'klaytn', 'polygon', 'binance-smart-chain', 'rococo', 'karura', 'polkadot', 'kusama', 'liberland', 'sora-polkadot', 'sora-kusama'; +$networks: 'sora', 'ethereum', 'ethereum-classic', 'avalanche', 'klaytn', 'polygon', 'binance-smart-chain', 'rococo', 'polkadot', 'kusama', 'acala', 'liberland', 'moonbase', 'sora-polkadot', 'sora-kusama'; /* Networks Icons */ .network-icon { display: inline-block; diff --git a/src/types/bridge.ts b/src/types/bridge.ts index 076f0d928..1227f5467 100644 --- a/src/types/bridge.ts +++ b/src/types/bridge.ts @@ -12,10 +12,12 @@ export interface NetworkData { name: string; symbol: string; decimals: number; - }; - endpointUrls: string[]; + } | null; blockExplorerUrls: string[]; shortName: string; + /** Endpoints for EVM network */ + endpointUrls?: string[]; + /** Nodes for Substrate network */ nodes?: Node[]; } diff --git a/src/utils/bridge/eth/index.ts b/src/utils/bridge/eth/index.ts index 7ffa79ca0..e47b2343f 100644 --- a/src/utils/bridge/eth/index.ts +++ b/src/utils/bridge/eth/index.ts @@ -3,7 +3,7 @@ import { WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; import store from '@/store'; import { Bridge } from '@/utils/bridge/common/classes'; -import type { GetBridgeHistoryInstance, IBridgeConstructorOptions } from '@/utils/bridge/common/types'; +import type { GetBridgeHistoryInstance, IBridgeConstructorOptions, SignExternal } from '@/utils/bridge/common/types'; import type { EthBridgeHistory } from '@/utils/bridge/eth/classes/history'; import { EthBridgeOutgoingReducer, EthBridgeIncomingReducer } from '@/utils/bridge/eth/classes/reducers'; import type { EthBridgeReducer } from '@/utils/bridge/eth/classes/reducers'; @@ -13,6 +13,8 @@ import type { EthHistory } from '@sora-substrate/util/build/bridgeProxy/eth/type interface EthBridgeConstructorOptions extends IBridgeConstructorOptions { getBridgeHistoryInstance: GetBridgeHistoryInstance; + signExternalOutgoing: SignExternal; + signExternalIncoming: SignExternal; } type EthBridge = Bridge; diff --git a/src/utils/bridge/eth/utils.ts b/src/utils/bridge/eth/utils.ts index a3378532f..4d6cf7753 100644 --- a/src/utils/bridge/eth/utils.ts +++ b/src/utils/bridge/eth/utils.ts @@ -123,8 +123,8 @@ 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, accountId] = await Promise.all([ethersUtil.getSigner(), ethersUtil.accountAddressToHex(recipient)]); + const signer = await ethersUtil.getSigner(); + const accountId = ethersUtil.accountAddressToHex(recipient); const amount = new FPNumber(value, asset.externalDecimals).toCodecString(); diff --git a/src/utils/bridge/sub/classes/adapter.ts b/src/utils/bridge/sub/classes/adapter.ts index 37b28cd8a..0c818dc08 100644 --- a/src/utils/bridge/sub/classes/adapter.ts +++ b/src/utils/bridge/sub/classes/adapter.ts @@ -5,9 +5,11 @@ import { subBridgeApi } from '@/utils/bridge/sub/api'; import { SubTransferType } from '@/utils/bridge/sub/types'; import { determineTransferType } from '@/utils/bridge/sub/utils'; -import { LiberlandAdapter } from './adapters/liberland'; +import { AcalaParachainAdapter } from './adapters/parachain/acala'; +import { MoonbaseParachainAdapter } from './adapters/parachain/moonbase'; +import { SoraParachainAdapter } from './adapters/parachain/sora'; import { RelaychainAdapter } from './adapters/relaychain'; -import { SoraParachainAdapter } from './adapters/soraParachain'; +import { LiberlandAdapter } from './adapters/standalone/liberland'; import { SubAdapter } from './adapters/substrate'; import type { SubNetwork } from '@sora-substrate/util/build/bridgeProxy/sub/types'; @@ -29,16 +31,6 @@ export class SubNetworksConnector { public static nodes: Partial> = {}; - public readonly adapters = Object.freeze({ - [SubNetworkId.Rococo]: () => new RelaychainAdapter(SubNetworkId.Rococo), - [SubNetworkId.Kusama]: () => new RelaychainAdapter(SubNetworkId.Kusama), - [SubNetworkId.Polkadot]: () => new RelaychainAdapter(SubNetworkId.Polkadot), - [SubNetworkId.RococoSora]: () => new SoraParachainAdapter(SubNetworkId.RococoSora), - [SubNetworkId.KusamaSora]: () => new SoraParachainAdapter(SubNetworkId.KusamaSora), - [SubNetworkId.PolkadotSora]: () => new SoraParachainAdapter(SubNetworkId.PolkadotSora), - [SubNetworkId.Liberland]: () => new LiberlandAdapter(SubNetworkId.Liberland), - }); - get uniqueAdapters(): SubAdapter[] { return [this.soraParachain, this.relaychain, this.parachain, this.standalone].filter((c) => !!c) as SubAdapter[]; } @@ -72,11 +64,11 @@ export class SubNetworksConnector { ): Adapter | undefined { if (!network) return undefined; - const adapter = this.getAdapterForNetwork(network); + const adapter = this.getAdapterForNetwork(network); // reuse api from connectorAdapter if possible this.cloneApi(adapter, connectorAdapter); - return adapter; + return adapter as Adapter; } protected cloneApi(adapter?: SubAdapter, connectorAdapter?: SubAdapter): void { @@ -88,19 +80,40 @@ export class SubNetworksConnector { } } - public getAdapterForNetwork(network: SubNetwork): T { - if (!(network in this.adapters)) { - throw new Error(`[${this.constructor.name}] Adapter for "${network}" network not implemented`); + protected getAdapter(network: SubNetwork) { + if (subBridgeApi.isRelayChain(network)) { + return new RelaychainAdapter(network); + } + if (subBridgeApi.isParachain(network)) { + if (network === SubNetworkId.AlphanetMoonbase) { + return new MoonbaseParachainAdapter(network); + } + if (network === SubNetworkId.PolkadotAcala) { + return new AcalaParachainAdapter(network); + } + if (subBridgeApi.isSoraParachain(network)) { + return new SoraParachainAdapter(network); + } } + if (subBridgeApi.isStandalone(network)) { + if (network === SubNetworkId.Liberland) { + return new LiberlandAdapter(network); + } + } + + console.info(`[${this.constructor.name}] Adapter for "${network}" network not implemented, "SubAdapter" is used`); + return new SubAdapter(network); + } + + public getAdapterForNetwork(network: SubNetwork) { + const adapter = this.getAdapter(network); const nodes = SubNetworksConnector.nodes[network]; if (!nodes) { throw new Error(`[${this.constructor.name}] Nodes for "${network}" network is not defined`); } - const adapter = this.adapters[network](); - adapter.subNetworkConnection.setDefaultNodes(nodes); return adapter; @@ -157,5 +170,3 @@ export class SubNetworksConnector { await Promise.all(this.uniqueAdapters.map((c) => c.stop())); } } - -export const subBridgeConnector = new SubNetworksConnector(); diff --git a/src/utils/bridge/sub/classes/adapters/parachain/acala.ts b/src/utils/bridge/sub/classes/adapters/parachain/acala.ts new file mode 100644 index 000000000..281671d55 --- /dev/null +++ b/src/utils/bridge/sub/classes/adapters/parachain/acala.ts @@ -0,0 +1,173 @@ +import { FPNumber } from '@sora-substrate/util'; +import { formatBalance } from '@sora-substrate/util/build/assets'; + +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'; + +enum AcalaPrimitivesCurrencyCurrencyId { + Token = 'Token', + Erc20 = 'Erc20', + ForeignAsset = 'ForeignAsset', + // this types below looks like not for transfer + DexShare = 'DexShare', + LiquidCrowdloan = 'LiquidCrowdloan', + StableAssetPoolToken = 'StableAssetPoolToken', +} + +type IAcalaCurrencyId = + | { + [AcalaPrimitivesCurrencyCurrencyId.Token]: string; + } + | { + [AcalaPrimitivesCurrencyCurrencyId.ForeignAsset]: number; + } + | { + [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; + if (!value.isToken) return null; + return { [AcalaPrimitivesCurrencyCurrencyId.Token]: value.asToken.toString() }; + } else if (nature.isForeignAssetId) { + return { [AcalaPrimitivesCurrencyCurrencyId.ForeignAsset]: nature.asForeignAssetId.toNumber() }; + } else if (nature.isErc20) { + return { [AcalaPrimitivesCurrencyCurrencyId.Erc20]: nature.asErc20.toString() }; + } + + return null; +} + +export class AcalaParachainAdapter extends SubAdapter { + protected assets: Record | null = null; + + protected async getAssetsMetadata(): Promise { + if (this.assets) return; + + const assets = {}; + const entries = await (this.api.query.assetRegistry as any).assetMetadatas.entries(); + + for (const [key, option] of entries) { + const nature = key.args[0]; + const id = getAcalaCurrencyId(nature); + + if (!(id && option.isSome)) continue; + + const symbol = new TextDecoder().decode(option.value.symbol); // bytes to string + const decimals = option.value.decimals.toNumber(); + const minimalBalance = option.value.minimalBalance.toString(); + + assets[symbol] = { id, symbol, decimals, minimalBalance }; + } + + this.assets = Object.freeze(assets); + } + + // overrides SubAdapter + public async connect(): Promise { + await super.connect(); + await this.getAssetsMetadata(); + } + + // overrides SubAdapter method + public async getTokenBalance(accountAddress: string, asset: RegisteredAsset): Promise { + return await this.withConnection(async () => { + return asset.symbol === this.chainSymbol + ? await this.getAccountBalance(accountAddress) + : await this.getAccountAssetBalance(accountAddress, asset.symbol); + }, ZeroStringValue); + } + + // overrides SubAdapter method + public async getAssetMinDeposit(asset: RegisteredAsset): Promise { + return await this.withConnection(async () => { + return asset.symbol === this.chainSymbol + ? await this.getExistentialDeposit() + : await this.getAssetDeposit(asset.symbol); + }, ZeroStringValue); + } + + protected async getAssetDeposit(assetSymbol: string): Promise { + if (!(assetSymbol && this.assets)) return ZeroStringValue; + + const assetMeta = this.assets[assetSymbol]; + + if (!assetMeta) return ZeroStringValue; + + const minBalance = assetMeta.minimalBalance; + + return minBalance > '1' ? minBalance : ZeroStringValue; + } + + protected async getAccountAssetBalance(accountAddress: string, assetSymbol: string): Promise { + if (!(accountAddress && this.assets)) return ZeroStringValue; + + const assetMeta = this.assets[assetSymbol]; + + if (!assetMeta) return ZeroStringValue; + + return await this.withConnection(async () => { + const ormlTokensAccountData = await (this.api.query.tokens as any).accounts(accountAddress, assetMeta.id); + const balance = formatBalance(ormlTokensAccountData, assetMeta.decimals); + + return balance.transferable; + }, ZeroStringValue); + } + + // overrides SubAdapter + protected getTransferExtrinsic(asset: RegisteredAsset, recipient: string, amount: number | string) { + if (!this.assets) throw new Error(`[${this.constructor.name}] assets metadata is empty`); + + const { id } = this.assets[asset.symbol]; + const value = new FPNumber(amount, asset.externalDecimals).toCodecString(); + + return this.api.tx.xTokens.transfer( + // currencyId: AcalaPrimitivesCurrencyCurrencyId + id, + // amount: u128 + value, + // dest: XcmVersionedMultiLocation + { + V3: { + parents: 1, + interior: { + X2: [ + { + Parachain: this.getSoraParachainId(), + }, + { + AccountId32: { + id: this.api.createType('AccountId32', recipient).toHex(), + }, + }, + ], + }, + }, + }, + // destWeightLimit: XcmV3WeightLimit + 'Unlimited' + ); + } + + /* Throws error until Substrate 5 migration */ + public async getNetworkFee(asset: RegisteredAsset, sender: string, recipient: string): Promise { + try { + return await super.getNetworkFee(asset, sender, recipient); + } catch (error) { + // Hardcoded value for Acala - 0.003 ACA + return '3000000000'; + } + } +} diff --git a/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts new file mode 100644 index 000000000..3bc0669c7 --- /dev/null +++ b/src/utils/bridge/sub/classes/adapters/parachain/moonbase.ts @@ -0,0 +1,13 @@ +import { u8aToHex } from '@polkadot/util'; +import { addressToEvm } from '@polkadot/util-crypto'; + +import { SubAdapter } from '../substrate'; + +export class MoonbaseParachainAdapter extends SubAdapter { + // overrides SubAdapter method + public formatAddress = (address?: string): string => { + if (!address) return ''; + // [TODO] research how to get evm address as on moonbase + return u8aToHex(addressToEvm(address)); + }; +} diff --git a/src/utils/bridge/sub/classes/adapters/soraParachain.ts b/src/utils/bridge/sub/classes/adapters/parachain/sora.ts similarity index 94% rename from src/utils/bridge/sub/classes/adapters/soraParachain.ts rename to src/utils/bridge/sub/classes/adapters/parachain/sora.ts index 9fd6067a2..6064ebfdd 100644 --- a/src/utils/bridge/sub/classes/adapters/soraParachain.ts +++ b/src/utils/bridge/sub/classes/adapters/parachain/sora.ts @@ -1,7 +1,7 @@ import { ZeroStringValue } from '@/consts'; import { subBridgeApi } from '@/utils/bridge/sub/api'; -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/adapters/relaychain.ts b/src/utils/bridge/sub/classes/adapters/relaychain.ts index f691f79f0..5790281d3 100644 --- a/src/utils/bridge/sub/classes/adapters/relaychain.ts +++ b/src/utils/bridge/sub/classes/adapters/relaychain.ts @@ -7,6 +7,7 @@ import type { CodecString } from '@sora-substrate/util'; import type { RegisteredAsset } from '@sora-substrate/util/build/assets/types'; export class RelaychainAdapter extends SubAdapter { + // overrides SubAdapter protected getTransferExtrinsic(asset: RegisteredAsset, recipient: string, amount: number | string) { const value = new FPNumber(amount, asset.externalDecimals).toCodecString(); @@ -61,16 +62,17 @@ export class RelaychainAdapter extends SubAdapter { try { return await super.getNetworkFee(asset, sender, recipient); } catch { + const toCodec = (fee: number) => new FPNumber(fee, asset.externalDecimals).toCodecString(); + // Hardcoded values switch (this.subNetwork) { case SubNetworkId.Rococo: - // Hardcoded value for Rococo - 0.000125 ROC - return new FPNumber(0.000125, asset.externalDecimals).toCodecString(); + return toCodec(0.000125); + case SubNetworkId.Alphanet: + return toCodec(0.019); case SubNetworkId.Kusama: - // Hardcoded value for Kusama - 0.002 KSM - return new FPNumber(0.002, asset.externalDecimals).toCodecString(); + return toCodec(0.002); case SubNetworkId.Polkadot: - // Hardcoded value for Polkadot - 0.059 DOT - return new FPNumber(0.059, asset.externalDecimals).toCodecString(); + return toCodec(0.059); default: return '0'; } diff --git a/src/utils/bridge/sub/classes/adapters/liberland.ts b/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts similarity index 84% rename from src/utils/bridge/sub/classes/adapters/liberland.ts rename to src/utils/bridge/sub/classes/adapters/standalone/liberland.ts index 87cb19d8d..d159327e5 100644 --- a/src/utils/bridge/sub/classes/adapters/liberland.ts +++ b/src/utils/bridge/sub/classes/adapters/standalone/liberland.ts @@ -4,22 +4,24 @@ import { SubNetworkId, LiberlandAssetType } from '@sora-substrate/util/build/bri import { ZeroStringValue } from '@/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'; export class LiberlandAdapter extends SubAdapter { // overrides SubAdapter method - public async getTokenBalance(accountAddress: string, assetAddress: string): Promise { - return assetAddress - ? await this.getAccountAssetBalance(accountAddress, assetAddress) + public async getTokenBalance(accountAddress: string, asset: RegisteredAsset): Promise { + return asset.externalAddress + ? await this.getAccountAssetBalance(accountAddress, asset.externalAddress) : await this.getAccountBalance(accountAddress); } // overrides SubAdapter method - public async getAssetMinDeposit(assetAddress: string): Promise { - return assetAddress ? await this.getAssetDeposit(assetAddress) : await this.getExistentialDeposit(); + public async getAssetMinDeposit(asset: RegisteredAsset): Promise { + return asset.externalAddress + ? await this.getAssetDeposit(asset.externalAddress) + : await this.getExistentialDeposit(); } protected async getAssetDeposit(assetAddress: string): Promise { diff --git a/src/utils/bridge/sub/classes/adapters/substrate.ts b/src/utils/bridge/sub/classes/adapters/substrate.ts index 95e8cdd4d..782e436e5 100644 --- a/src/utils/bridge/sub/classes/adapters/substrate.ts +++ b/src/utils/bridge/sub/classes/adapters/substrate.ts @@ -1,3 +1,4 @@ +import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'; import { Connection } from '@sora-substrate/connection'; import { FPNumber, Operation, Storage } from '@sora-substrate/util'; import { formatBalance } from '@sora-substrate/util/build/assets'; @@ -39,6 +40,26 @@ export class SubAdapter { return !!this.api?.isConnected; } + get chainSymbol(): string | undefined { + return this.api?.registry.chainTokens[0]; + } + + get chainDecimals(): number | undefined { + return this.api?.registry.chainDecimals[0]; + } + + get chainSS58(): number | undefined { + return this.api?.registry.chainSS58; + } + + public formatAddress = (address?: string): string => { + if (!address) return ''; + + const publicKey = decodeAddress(address, false); + + return encodeAddress(publicKey, this.chainSS58); + }; + protected async withConnection(onSuccess: AsyncFnWithoutArgs | FnWithoutArgs, fallback: T) { if (!this.connected && !this.connection.loading) return fallback; @@ -94,10 +115,9 @@ export class SubAdapter { return await this.withConnection(async () => { const accountInfo = await this.api.query.system.account(accountAddress); - const accountBalance = formatBalance(accountInfo.data); - const balance = accountBalance.transferable; + const balance = formatBalance(accountInfo.data, this.chainDecimals); - return balance; + return balance.transferable; }, ZeroStringValue); } @@ -121,18 +141,17 @@ export class SubAdapter { /* [Substrate 5] Runtime call transactionPaymentApi */ public async getNetworkFee(asset: RegisteredAsset, sender: string, recipient: string): Promise { return await this.withConnection(async () => { - const decimals = this.api.registry.chainDecimals[0]; const tx = this.getTransferExtrinsic(asset, recipient, ZeroStringValue); const res = await tx.paymentInfo(sender); - return new FPNumber(res.partialFee, decimals).toCodecString(); + return new FPNumber(res.partialFee, this.chainDecimals).toCodecString(); }, ZeroStringValue); } - public async getTokenBalance(accountAddress: string, address?: string): Promise { + public async getTokenBalance(accountAddress: string, asset?: RegisteredAsset): Promise { return await this.getAccountBalance(accountAddress); } - public async getAssetMinDeposit(assetAddress: string): Promise { + public async getAssetMinDeposit(asset: RegisteredAsset): Promise { return await this.getExistentialDeposit(); } diff --git a/src/utils/bridge/sub/classes/history.ts b/src/utils/bridge/sub/classes/history.ts index 081398350..ebab2de3e 100644 --- a/src/utils/bridge/sub/classes/history.ts +++ b/src/utils/bridge/sub/classes/history.ts @@ -7,14 +7,12 @@ import { ZeroStringValue } from '@/consts'; import { rootActionContext } from '@/store'; import { getBlockEventsByTxIndex } from '@/utils/bridge/common/utils'; import { subBridgeApi } from '@/utils/bridge/sub/api'; -import { SubNetworksConnector, subBridgeConnector } from '@/utils/bridge/sub/classes/adapter'; +import { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; import { getDepositedBalance, - getParachainBridgeAppMintedBalance, getMessageAcceptedNonces, getMessageDispatchedNonces, isMessageDispatchedNonces, - formatSubAddress, getReceivedAmount, } from '@/utils/bridge/sub/utils'; @@ -64,10 +62,14 @@ class SubBridgeHistory extends SubNetworksConnector { return subBridgeApi.api; } - get parachainApi(): ApiPromise | undefined { + get soraParachainApi(): ApiPromise | undefined { return this.soraParachain?.api; } + get relaychainApi(): ApiPromise | undefined { + return this.relaychain?.api; + } + get externalApi(): ApiPromise { return this.network.api; } @@ -145,7 +147,13 @@ class SubBridgeHistory extends SubNetworksConnector { } const asset = assetDataByAddress(tx.soraAssetAddress); - const amount = FPNumber.fromCodecValue(tx.amount, asset?.decimals).toString(); + + if (!asset) { + console.info(`[${this.constructor.name}] Asset is not exists: "${tx.soraAssetAddress}, skip;"`); + return null; + } + + const amount = FPNumber.fromCodecValue(tx.amount, asset.decimals).toString(); const type = getType(isOutgoing); const history: SubHistory = { @@ -158,8 +166,8 @@ class SubBridgeHistory extends SubNetworksConnector { externalNetwork: this.network.subNetwork, externalNetworkType: BridgeNetworkType.Sub, amount, - assetAddress: asset?.address, - symbol: asset?.symbol, + assetAddress: asset.address, + symbol: asset.symbol, from: tx.soraAccount, to: tx.externalAccount, soraNetworkFee: ZeroStringValue, // overrides in Outgoing @@ -186,20 +194,20 @@ class SubBridgeHistory extends SubNetworksConnector { history.startTime = history.endTime = startTime; if (isOutgoing) { - return await this.processOutgoingTxExternalData({ + return await this.processOutgoingTx({ history, asset, events: soraTxEvents, }); } else { - return await this.processIncomingTxExternalData({ + return await this.processIncomingTx({ history, txEvents: soraTxEvents, blockEvents: soraBlockEvents, }); } } catch (error) { - console.error(`[${id}]`, error); + console.info(`[${id}]`, error); return null; } } @@ -209,20 +217,24 @@ class SubBridgeHistory extends SubNetworksConnector { if (subBridgeApi.isStandalone(history.externalNetwork)) return this.externalApi; - if (!this.parachainApi) throw new Error(`[${history.txId}] Parachain Api is not exists`); + if (!this.soraParachainApi) throw new Error(`[${history.txId}] SORA Parachain Api is not exists`); - return this.parachainApi; + return this.soraParachainApi; } - private async processOutgoingTxExternalData({ + private async processOutgoingTx({ history, asset, events, }: { history: SubHistory; - asset: Nullable; + asset: RegisteredAccountAsset; events: any[]; - }): Promise { + }): Promise> { + const { soraParachainApi } = this; + + if (!soraParachainApi) throw new Error('SORA Parachain Api is not exists'); + // update SORA network fee const soraFeeEvent = events.find((e) => this.soraApi.events.transactionPayment.TransactionFeePaid.is(e.event)); history.soraNetworkFee = soraFeeEvent.event.data[1].toString(); @@ -243,53 +255,85 @@ class SubBridgeHistory extends SubNetworksConnector { throw new Error(`[${history.id}] Message sent from SORA to network is not found in block "${networkBlockId}"`); } - if (history.externalNetwork === SubNetworkId.Liberland) { + const externalNetwork = history.externalNetwork as SubNetwork; + + if (externalNetwork === SubNetworkId.Liberland) { return await this.processOutgoingToLiberland(history); } // SORA Parachain extrinsic events for next search const parachainExtrinsicEvents = networkEventsReversed.slice(messageDispatchedIndex); - // sended from SORA Parachain to Relaychain message hash - const outgoingMessageToRelaychain = parachainExtrinsicEvents.find((e) => + // sended from SORA Parachain to Relaychain message hash (1) + const messageToRelaychain = parachainExtrinsicEvents.find((e) => networkApi.events.parachainSystem.UpwardMessageSent.is(e.event) ); + // sended from SORA Parachain to Parachain message hash (2) + const messageToParachain = parachainExtrinsicEvents.find((e) => + networkApi.events.xcmpQueue.XcmpMessageSent.is(e.event) + ); + + if (!messageToRelaychain && !messageToParachain) { + return await this.processOutgoingToSoraParachain(history, asset, parachainExtrinsicEvents); + } + + const isRelaychain = subBridgeApi.isRelayChain(externalNetwork) && messageToRelaychain; + const isParachain = subBridgeApi.isParachain(externalNetwork) && messageToParachain; + + if (!isRelaychain && !isParachain) { + console.info(`[${history.id}] not "${externalNetwork}" transaction, skip;`); + return null; + } - if (outgoingMessageToRelaychain) { - history.parachainBlockId = history.externalBlockId; - history.parachainBlockHeight = history.externalBlockHeight; + this.updateSoraParachainBlockData(history); - // message hash sended to Relaychain - const messageHash = outgoingMessageToRelaychain.event.data.messageHash.toString(); + const messageHash = (messageToRelaychain ?? messageToParachain).event.data.messageHash.toString(); + const relayChainBlockNumber = await subBridgeApi.soraParachainApi.getRelayChainBlockNumber( + history.parachainBlockId as string, + soraParachainApi + ); - return await this.processOutgoingToRelaychain(history, asset, messageHash); + let startSearch!: number; + let endSearch!: number; + + if (isRelaychain) { + // Relaychain should have received message in this blocks range + [startSearch, endSearch] = [relayChainBlockNumber + 2, relayChainBlockNumber + 4]; } else { - return await this.processOutgoingToSoraParachain(history, asset, parachainExtrinsicEvents); + // Parachain block, found through relaychain validation data (start block for search) + const parachainBlockId = await this.findParachainBlockIdOnRelaychain(history, relayChainBlockNumber); + const parachainBlockNumber = await api.system.getBlockNumber(parachainBlockId, this.externalApi); + // Parachain should have receive message in this blocks range + [startSearch, endSearch] = [parachainBlockNumber, parachainBlockNumber + 6]; } + + return await this.processOutgoingTxOnDestination(history, asset, messageHash, startSearch, endSearch); } private async processOutgoingToLiberland(history: SubHistory): Promise { history.amount2 = history.amount; history.externalTransferFee = ZeroStringValue; - history.to = formatSubAddress(history.to as string, this.externalApi.registry.chainSS58 as number); + history.to = this.network.formatAddress(history.to); return history; } private async processOutgoingToSoraParachain( history: SubHistory, - asset: Nullable, + asset: RegisteredAccountAsset, extrinsicEvents: any[] - ): Promise { - if (!this.parachainApi) throw new Error('[processOutgoingToSoraParachain] Parachain Api is not exists'); + ) { + const { soraParachain, soraParachainApi } = this; + + if (!(soraParachain && soraParachainApi)) throw new Error('SORA Parachain Api is not exists'); try { - const [receivedAmount, eventIndex] = getDepositedBalance( + const [receivedAmount, externalEventIndex] = getDepositedBalance( extrinsicEvents, history.to as string, - this.parachainApi + soraParachainApi ); // balances.Deposit event index - history.payload.eventIndex = eventIndex; + history.externalEventIndex = externalEventIndex; const { amount, transferFee } = getReceivedAmount( history.amount as string, @@ -305,81 +349,12 @@ class SubBridgeHistory extends SubNetworksConnector { } history.externalNetwork = subBridgeApi.getSoraParachain(history.externalNetwork as SubNetwork); - history.to = formatSubAddress(history.to as string, this.parachainApi.registry.chainSS58 as number); + history.to = soraParachain.formatAddress(history.to); return history; } - private async processOutgoingToRelaychain( - history: SubHistory, - asset: Nullable, - messageHash: string - ): Promise { - if (!this.parachainApi) throw new Error('[processOutgoingToRelaychain] Parachain Api is not exists'); - - const relayChainBlockNumber = await subBridgeApi.soraParachainApi.getRelayChainBlockNumber( - history.parachainBlockId as string, - this.parachainApi - ); - // Relaychain should have received message in this blocks range - const startSearch = relayChainBlockNumber + 2; - const endSearch = startSearch + 2; - - for (let relaychainBlockHeight = startSearch; relaychainBlockHeight <= endSearch; relaychainBlockHeight++) { - try { - const blockId = await api.system.getBlockHash(relaychainBlockHeight, this.externalApi); - const blockEvents = await api.system.getBlockEvents(blockId, this.externalApi); - - const messageQueueEventIndex = blockEvents.findIndex(({ event }) => { - if (this.externalApi.events.messageQueue.Processed.is(event)) { - const messageHashMatches = event.data[0].toString() === messageHash; - - return messageHashMatches; - } - return false; - }); - - if (messageQueueEventIndex === -1) continue; - - history.externalBlockId = blockId; - history.externalBlockHeight = relaychainBlockHeight; - history.to = formatSubAddress(history.to as string, this.externalApi.registry.chainSS58 as number); - - // Native token for network - const [receivedAmount, eventIndex] = getDepositedBalance( - blockEvents.slice(0, messageQueueEventIndex), - history.to as string, - this.externalApi - ); - // balances.Deposit event index - history.payload.eventIndex = eventIndex; - - const { amount, transferFee } = getReceivedAmount( - history.amount as string, - receivedAmount, - asset?.externalDecimals - ); - - history.amount2 = amount; - history.externalTransferFee = transferFee; - - return history; - } catch (error) { - console.error(error); - continue; - } - } - - console.info( - `[${history.id}] Relaychain transaction for SORA Parachain block "${history.parachainBlockId}" not found in blocks range [${startSearch}; ${endSearch}]` - ); - - history.transactionState = BridgeTxStatus.Failed; - - return history; - } - - private async processIncomingTxExternalData({ + private async processIncomingTx({ history, txEvents, blockEvents, @@ -388,9 +363,17 @@ class SubBridgeHistory extends SubNetworksConnector { txEvents: any[]; blockEvents: any[]; }): Promise> { + const { soraApi, soraParachainApi } = this; + + if (!soraParachainApi) throw new Error('SORA Parachain Api is not exists'); + + // Token is minted to account event + const [_, eventIndex] = getDepositedBalance(blockEvents, history.from as string, this.soraApi); + history.payload.eventIndex = eventIndex; + // find SORA hash event index const requestStatusUpdateEventIndex = txEvents.findIndex((e) => { - if (!this.soraApi.events.bridgeProxy.RequestStatusUpdate.is(e.event)) return false; + if (!soraApi.events.bridgeProxy.RequestStatusUpdate.is(e.event)) return false; const hash = e.event.data[0].toString(); @@ -399,7 +382,7 @@ class SubBridgeHistory extends SubNetworksConnector { // Received on SORA nonces const [soraBatchNonce, soraMessageNonce] = getMessageDispatchedNonces( txEvents.slice(requestStatusUpdateEventIndex), - this.soraApi + soraApi ); // api for Standalone network or SORA parachain const networkApi = this.getIntermediateApi(history); @@ -417,76 +400,68 @@ class SubBridgeHistory extends SubNetworksConnector { if (!messageToSoraEvent) { throw new Error( - `[${history.id}] Message sended to SORA from external network block "${networkBlockId}" not found` + `[${history.id}] Message sended to SORA from external network not found. Block "${networkBlockId}"` ); } const networkExtrinsicIndex = messageToSoraEvent.phase.asApplyExtrinsic.toNumber(); const networkExtrinsicEvents = getTxEvents(networkEvents, networkExtrinsicIndex); - if (history.externalNetwork === SubNetworkId.Liberland) { - return await this.processIncomingFromLiberland(history, networkExtrinsicEvents); - } + // is tx signed on SORA Parachain or Standalone network + const feeEvent = networkExtrinsicEvents.find((e) => + networkApi.events.transactionPayment.TransactionFeePaid.is(e.event) + ); - const parachainApi = this.parachainApi; + if (feeEvent) { + const signer = feeEvent.event.data[0].toString(); // signer is spent balance for fee - if (!parachainApi) throw new Error('[processIncomingTxExternalData] Parachain Api is not exists'); + if (!subBridgeApi.isStandalone(history.externalNetwork as SubNetwork)) { + history.externalNetwork = subBridgeApi.getSoraParachain(history.externalNetwork as SubNetwork); + } - // If transfer received from Relaychain, extrinsic events should have downward message processed - const incomingMessageFromRelaychain = networkExtrinsicEvents.find((e) => - parachainApi.events.parachainSystem.DownwardMessagesProcessed.is(e.event) - ); - // Token is minted to account event - const [_, eventIndex] = getParachainBridgeAppMintedBalance(blockEvents, history.from as string, this.soraApi); - history.payload.eventIndex = eventIndex; + history.externalNetworkFee = feeEvent.event.data[1].toString(); + history.to = this.network.formatAddress(signer); - if (incomingMessageFromRelaychain) { - history.parachainBlockId = history.externalBlockId; - history.parachainBlockHeight = history.externalBlockHeight; - return await this.processIncomingFromRelaychain(history); - } else { - return await this.processIncomingFromSoraParachain(history, networkExtrinsicEvents); + return history; } - } - private async processIncomingFromLiberland(history: SubHistory, extrinsicEvents: any[]): Promise { - const feeEvent = extrinsicEvents.find((e) => - this.externalApi.events.transactionPayment.TransactionFeePaid.is(e.event) - ); - const signer = feeEvent.event.data[0].toString(); // signer is spent balance for fee - - history.externalNetworkFee = feeEvent.event.data[1].toString(); - history.to = formatSubAddress(signer, this.externalApi.registry.chainSS58 as number); + // If transfer received from Parachain, extrinsic events should have xcmpQueue.Success event + const messageEvent = networkExtrinsicEvents.find((e) => soraParachainApi.events.xcmpQueue.Success.is(e.event)); + const externalNetwork = history.externalNetwork as SubNetwork; + const isRelayChain = subBridgeApi.isRelayChain(externalNetwork) && !messageEvent; + const isParachain = + subBridgeApi.isParachain(externalNetwork) && !subBridgeApi.isSoraParachain(externalNetwork) && messageEvent; - return history; - } - - private async processIncomingFromSoraParachain(history: SubHistory, extrinsicEvents: any[]): Promise { - const parachainApi = this.parachainApi; + if (!isRelayChain && !isParachain) { + console.info(`[${history.id}] not "${externalNetwork}" transaction, skip;`); + return null; + } - if (!parachainApi) throw new Error('[processIncomingFromSoraParachain] Parachain Api is not exists'); + this.updateSoraParachainBlockData(history); - const feeEvent = extrinsicEvents.find((e) => parachainApi.events.transactionPayment.TransactionFeePaid.is(e.event)); - const signer = feeEvent.event.data[0].toString(); // signer is spent balance for fee + const relayChainBlockNumber = await subBridgeApi.soraParachainApi.getRelayChainBlockNumber( + history.parachainBlockId as string, + soraParachainApi + ); - history.externalNetwork = subBridgeApi.getSoraParachain(history.externalNetwork as SubNetwork); - history.externalNetworkFee = feeEvent.event.data[1].toString(); - history.to = formatSubAddress(signer, parachainApi.registry.chainSS58 as number); + if (isParachain) { + const messageHash = messageEvent.event.data.messageHash.toString(); + // Parachain block, found through relaychain validation data + const parachainBlockId = await this.findParachainBlockIdOnRelaychain(history, relayChainBlockNumber); - return history; + return await this.processIncomingFromParachain(history, parachainBlockId, messageHash); + } else { + return await this.processIncomingFromRelaychain(history, relayChainBlockNumber); + } } - private async processIncomingFromRelaychain(history: SubHistory): Promise { - const parachainApi = this.parachainApi; - const soraParachain = this.soraParachain; + private async processIncomingFromRelaychain(history: SubHistory, relayChainBlockNumber: number): Promise { + const { soraParachain, soraParachainApi } = this; - if (!parachainApi) throw new Error('[processIncomingFromRelaychain] Parachain Api is not exists'); - if (!soraParachain) throw new Error('[processIncomingFromRelaychain] Sora Parachain adapter is not exists'); + if (!(soraParachain && soraParachainApi)) throw new Error('SORA Parachain Api is not exists'); + + const soraParachainId = soraParachain.getParachainId(); - const relayChainBlockNumber = await subBridgeApi.soraParachainApi.getRelayChainBlockNumber( - history.parachainBlockId as string, - parachainApi - ); // relay chain should have send message in this blocks range const startSearch = relayChainBlockNumber; const endSearch = startSearch - 6; @@ -511,7 +486,7 @@ class SubBridgeHistory extends SubNetworksConnector { const receiver = subBridgeApi.formatAddress(accountId); const from = subBridgeApi.formatAddress(history.from as string); - if (!(parachainId === soraParachain.getParachainId() && receiver === from)) { + if (!(parachainId === soraParachainId && receiver === from)) { continue; } @@ -525,11 +500,10 @@ class SubBridgeHistory extends SubNetworksConnector { history.externalBlockId = blockId; history.externalBlockHeight = relaychainBlockHeight; history.externalHash = extrinsic.hash.toString(); - history.to = formatSubAddress(signer, this.externalApi.registry.chainSS58 as number); + history.to = this.network.formatAddress(signer); return history; - } catch (error) { - console.error(error); + } catch { continue; } } @@ -541,6 +515,158 @@ class SubBridgeHistory extends SubNetworksConnector { return history; } + + private async findParachainBlockIdOnRelaychain(history: SubHistory, relayChainBlockNumber: number) { + const { network, soraParachainApi, relaychainApi } = this; + + if (!soraParachainApi) throw new Error('SORA Parachain Api is not exists'); + if (!relaychainApi) throw new Error('Relaychain Api is not exists'); + + // relay chain should have send validation data in this blocks range + const startSearch = relayChainBlockNumber; + const endSearch = startSearch - 2; + + for (let relaychainBlockHeight = startSearch; relaychainBlockHeight >= endSearch; relaychainBlockHeight--) { + const blockId = await api.system.getBlockHash(relaychainBlockHeight, relaychainApi); + const events = await api.system.getBlockEvents(blockId, relaychainApi); + + for (const e of events) { + if (!relaychainApi.events.paraInclusion.CandidateIncluded.is(e.event)) continue; + + const { descriptor } = e.event.data[0]; + + if (descriptor.paraId.toNumber() !== network.getParachainId()) continue; + + history.relaychainBlockHeight = relaychainBlockHeight; + history.relaychainBlockId = blockId; + + // parachain block hash + return descriptor.paraHead.toString(); + } + } + + throw new Error( + `[${history.id}] Relaychain transaction for SORA Parachain block "${history.parachainBlockId}" not found in blocks range [${endSearch}; ${startSearch}]` + ); + } + + private async processIncomingFromParachain(history: SubHistory, parachainBlockId: string, messageHash: string) { + const { externalApi, network } = this; + + const soraParachainId = network.getSoraParachainId(); + const [parachainBlockEvents, parachainBlockExtrinsics] = await Promise.all([ + api.system.getBlockEvents(parachainBlockId, externalApi), + api.system.getExtrinsicsFromBlock(parachainBlockId, externalApi), + ]); + + for (const [extrinsicIndex, extrinsic] of parachainBlockExtrinsics.entries()) { + if (!(extrinsic.method.section === 'xTokens' && extrinsic.method.method === 'transfer')) continue; + + try { + const [_currencyId, _amount, dest] = extrinsic.args; + const xcmJunctions = (dest as any).asV3.interior.asX2; + const paraId = xcmJunctions[0].asParachain.toNumber(); + const accountId = xcmJunctions[1].asAccountId32.id.toString(); + const receiver = subBridgeApi.formatAddress(accountId); + const from = subBridgeApi.formatAddress(history.from as string); + + if (!(paraId === soraParachainId && receiver === from)) { + continue; + } + + const extrinsicEvents = parachainBlockEvents.filter( + ({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.toNumber() === extrinsicIndex + ); + const messageSentEvent = extrinsicEvents.find((e) => externalApi.events.xcmpQueue.XcmpMessageSent.is(e.event)); + + if (!messageSentEvent) continue; + if (messageSentEvent.event.data[0].toString() !== messageHash) continue; + + const parachainBlockHeight = await api.system.getBlockNumber(parachainBlockId, externalApi); + const signer = extrinsic.signer.toString(); + const feeEvent = extrinsicEvents.find((e) => + externalApi.events.transactionPayment.TransactionFeePaid.is(e.event) + ); + + history.externalNetworkFee = feeEvent.event.data[1].toString(); + history.externalBlockId = parachainBlockId; + history.externalBlockHeight = parachainBlockHeight; + history.externalHash = extrinsic.hash.toString(); + history.to = this.network.formatAddress(signer); + + return history; + } catch (error) { + continue; + } + } + + return history; + } + + private async processOutgoingTxOnDestination( + history: SubHistory, + asset: RegisteredAccountAsset, + messageHash: string, + startSearch: number, + endSearch: number + ) { + for (let blockHeight = startSearch; blockHeight <= endSearch; blockHeight++) { + try { + const blockId = await api.system.getBlockHash(blockHeight, this.externalApi); + const blockEvents = await api.system.getBlockEvents(blockId, this.externalApi); + + const messageQueueEventIndex = blockEvents.findIndex(({ event }) => { + if (this.externalApi.events.messageQueue.Processed.is(event)) { + const messageHashMatches = event.data[0].toString() === messageHash; + + return messageHashMatches; + } + return false; + }); + + if (messageQueueEventIndex === -1) continue; + + history.externalBlockId = blockId; + history.externalBlockHeight = blockHeight; + history.to = this.network.formatAddress(history.to); + + const [receivedAmount, externalEventIndex] = getDepositedBalance( + blockEvents.slice(0, messageQueueEventIndex), + history.to, + this.externalApi + ); + + // Deposit event index + history.externalEventIndex = externalEventIndex; + + const { amount, transferFee } = getReceivedAmount( + history.amount as string, + receivedAmount, + asset?.externalDecimals + ); + + history.amount2 = amount; + history.externalTransferFee = transferFee; + + return history; + } catch { + continue; + } + } + + console.info(`[${history.id}] Transaction not found in blocks range [${startSearch}; ${endSearch}]`); + + history.transactionState = BridgeTxStatus.Failed; + + return history; + } + + private updateSoraParachainBlockData(history: SubHistory): void { + history.parachainBlockId = history.externalBlockId; + history.parachainBlockHeight = history.externalBlockHeight; + history.externalBlockId = undefined; + history.externalBlockHeight = undefined; + } } /** @@ -557,30 +683,22 @@ export const updateSubBridgeHistory = account: { address }, }, web3: { networkSelected }, - bridge: { inProgressIds }, + bridge: { inProgressIds, subBridgeConnector }, } = rootState; - const { - bridge: { networkHistoryId }, - } = rootGetters; - if (!(networkSelected && networkHistoryId)) return; + if (!networkSelected) return; const assetDataByAddress = rootGetters.assets.assetDataByAddress; const subBridgeHistory = new SubBridgeHistory(); + const network = networkSelected as SubNetwork; - await subBridgeHistory.init(networkHistoryId as SubNetwork, subBridgeConnector); + await subBridgeHistory.init(network, subBridgeConnector); if (clearHistory) { - await subBridgeHistory.clearHistory(networkSelected as SubNetwork, inProgressIds, updateCallback); + await subBridgeHistory.clearHistory(network, inProgressIds, updateCallback); } - await subBridgeHistory.updateAccountHistory( - networkSelected as SubNetwork, - address, - inProgressIds, - assetDataByAddress, - updateCallback - ); + await subBridgeHistory.updateAccountHistory(network, address, inProgressIds, assetDataByAddress, updateCallback); } catch (error) { console.error(error); } diff --git a/src/utils/bridge/sub/classes/reducers.ts b/src/utils/bridge/sub/classes/reducers.ts index 3fc4dda8a..86521f0c5 100644 --- a/src/utils/bridge/sub/classes/reducers.ts +++ b/src/utils/bridge/sub/classes/reducers.ts @@ -6,15 +6,14 @@ import { combineLatest } from 'rxjs'; import { ZeroStringValue } from '@/consts'; import { conditionalAwait } from '@/utils'; import { BridgeReducer } from '@/utils/bridge/common/classes'; +import type { IBridgeReducerOptions } from '@/utils/bridge/common/types'; import { getTransactionEvents } from '@/utils/bridge/common/utils'; import { subBridgeApi } from '@/utils/bridge/sub/api'; -import { SubNetworksConnector, subBridgeConnector } from '@/utils/bridge/sub/classes/adapter'; +import { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; import { SubTransferType } from '@/utils/bridge/sub/types'; import { getBridgeProxyHash, getDepositedBalance, - getParachainBridgeAppMintedBalance, - getSubstrateBridgeAppMintedBalance, getMessageAcceptedNonces, isMessageDispatchedNonces, isAssetAddedToChannel, @@ -25,15 +24,27 @@ import { } from '@/utils/bridge/sub/utils'; import type { ApiRx } from '@polkadot/api'; +import type { IBridgeTransaction } 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'; import type { Subscription } from 'rxjs'; +type SubBridgeReducerOptions = IBridgeReducerOptions & { + getSubBridgeConnector: () => SubNetworksConnector; +}; + export class SubBridgeReducer extends BridgeReducer { protected asset!: RegisteredAccountAsset; + protected getSubBridgeConnector!: () => SubNetworksConnector; protected connector!: SubNetworksConnector; protected transferType!: SubTransferType; + constructor(options: SubBridgeReducerOptions) { + super(options); + + this.getSubBridgeConnector = options.getSubBridgeConnector; + } + initConnector(id: string): void { const { externalNetwork } = this.getTransaction(id); @@ -42,7 +53,7 @@ export class SubBridgeReducer extends BridgeReducer { this.transferType = determineTransferType(externalNetwork); this.connector = new SubNetworksConnector(); - this.connector.init(externalNetwork, subBridgeConnector); + this.connector.init(externalNetwork, this.getSubBridgeConnector()); } async closeConnector(): Promise { @@ -322,9 +333,6 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { let amount!: string; let eventIndex!: number; - const isStandalone = this.transferType === SubTransferType.Standalone; - const getMintedBalance = isStandalone ? getSubstrateBridgeAppMintedBalance : getParachainBridgeAppMintedBalance; - try { await new Promise((resolve, reject) => { const eventsObservable = api.system.getEventsObservable(subBridgeApi.apiRx); @@ -342,7 +350,7 @@ export class SubBridgeIncomingReducer extends SubBridgeReducer { soraHash = getBridgeProxyHash(foundedEvents, subBridgeApi.api); - [amount, eventIndex] = getMintedBalance(foundedEvents, tx.from as string, subBridgeApi.api); + [amount, eventIndex] = getDepositedBalance(foundedEvents, tx.from as string, subBridgeApi.api); resolve(); } catch (error) { @@ -413,7 +421,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { await this.waitForSendingExecution(id); await this.waitForIntermediateExecution(id); - await this.waitForRelaychainExecution(id); + await this.waitForDestinationExecution(id); await this.onComplete(id); } finally { @@ -568,8 +576,8 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { } } - private async waitForRelaychainExecution(id: string): Promise { - if (this.transferType !== SubTransferType.Relaychain) return; + private async waitForDestinationExecution(id: string): Promise { + if (![SubTransferType.Relaychain, SubTransferType.Parachain].includes(this.transferType)) return; const tx = this.getTransaction(id); const messageHash = tx.payload.messageHash as string; @@ -579,7 +587,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { let subscription!: Subscription; let blockNumber!: number; let amount!: string; - let eventIndex!: number; + let externalEventIndex!: number; const adapter = this.connector.network; @@ -603,7 +611,7 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { blockNumber = blockHeight; - [amount, eventIndex] = getDepositedBalance( + [amount, externalEventIndex] = getDepositedBalance( events.slice(0, messageQueueProcessedEventIndex), tx.to as string, adapter.api @@ -633,6 +641,6 @@ export class SubBridgeOutgoingReducer extends SubBridgeReducer { } this.updateReceivedAmount(id, amount); - this.updateTransactionPayload(id, { eventIndex }); + this.updateTransactionParams(id, { externalEventIndex }); } } diff --git a/src/utils/bridge/sub/index.ts b/src/utils/bridge/sub/index.ts index 2ce0a4125..f24a353a8 100644 --- a/src/utils/bridge/sub/index.ts +++ b/src/utils/bridge/sub/index.ts @@ -4,13 +4,18 @@ import { BridgeTxStatus } from '@sora-substrate/util/build/bridgeProxy/consts'; import store from '@/store'; import { Bridge } from '@/utils/bridge/common/classes'; import type { IBridgeConstructorOptions } from '@/utils/bridge/common/types'; +import type { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; import { SubBridgeOutgoingReducer, SubBridgeIncomingReducer } from '@/utils/bridge/sub/classes/reducers'; import type { SubBridgeReducer } from '@/utils/bridge/sub/classes/reducers'; import { getTransaction, updateTransaction } from '@/utils/bridge/sub/utils'; import type { SubHistory } from '@sora-substrate/util/build/bridgeProxy/sub/types'; -type SubBridge = Bridge>; +interface SubBridgeConstructorOptions extends IBridgeConstructorOptions { + getSubBridgeConnector: () => SubNetworksConnector; +} + +type SubBridge = Bridge; const subBridge: SubBridge = new Bridge({ reducers: { @@ -42,6 +47,8 @@ const subBridge: SubBridge = new Bridge({ removeTransactionFromProgress: (id: string) => store.commit.bridge.removeTxIdFromProgress(id), // transaction signing beforeTransactionSign: () => store.dispatch.wallet.transactions.beforeTransactionSign(), + // custom + getSubBridgeConnector: () => store.state.bridge.subBridgeConnector, }); export default subBridge; diff --git a/src/utils/bridge/sub/utils.ts b/src/utils/bridge/sub/utils.ts index a0f6d313d..408c27452 100644 --- a/src/utils/bridge/sub/utils.ts +++ b/src/utils/bridge/sub/utils.ts @@ -1,4 +1,3 @@ -import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'; import { FPNumber } from '@sora-substrate/util'; import { subBridgeApi } from '@/utils/bridge/sub/api'; @@ -49,54 +48,27 @@ export const getBridgeProxyHash = (events: Array, api: ApiPromise): string return bridgeProxyEvent.event.data[0].toString(); }; -// Native token for network export const getDepositedBalance = (events: Array, to: string, api: ApiPromise): [string, number] => { - const index = events.findIndex((e) => { - if (!api.events.balances.Deposit.is(e.event)) return false; - return subBridgeApi.formatAddress(e.event.data.who.toString()) === subBridgeApi.formatAddress(to); - }); - - if (index === -1) throw new Error(`Unable to find "balances.Deposit" event`); + const recipient = subBridgeApi.formatAddress(to); - const event = events[index]; - const balance = event.event.data.amount.toString(); - - return [balance, index]; -}; - -// for SORA from Relaychain -export const getParachainBridgeAppMintedBalance = ( - events: Array, - to: string, - api: ApiPromise -): [string, number] => { const index = events.findIndex((e) => { - if (!api.events.parachainBridgeApp.Minted.is(e.event)) return false; - return subBridgeApi.formatAddress(e.event.data[3].toString()) === subBridgeApi.formatAddress(to); - }); + let eventRecipient = ''; - if (index === -1) throw new Error(`Unable to find "parachainBridgeApp.Minted" event`); + if (api.events.balances?.Deposit.is(e.event) || api.events.tokens?.Deposited.is(e.event)) { + eventRecipient = e.event.data.who.toString(); + } else if (api.events.assets?.Transfer.is(e.event)) { + eventRecipient = e.event.data[1].toString(); + } - const event = events[index]; - const balance = event.event.data[4].toString(); + if (!eventRecipient) return false; - return [balance, index]; -}; -// for SORA from Liberland -export const getSubstrateBridgeAppMintedBalance = ( - events: Array, - to: string, - api: ApiPromise -): [string, number] => { - const index = events.findIndex((e) => { - if (!api.events.substrateBridgeApp.Minted.is(e.event)) return false; - return subBridgeApi.formatAddress(e.event.data[3].toString()) === subBridgeApi.formatAddress(to); + return subBridgeApi.formatAddress(eventRecipient) === recipient; }); - if (index === -1) throw new Error(`Unable to find "parachainBridgeApp.Minted" event`); + if (index === -1) throw new Error(`Unable to find "balances.Deposit" or "tokens.Deposited" event`); const event = events[index]; - const balance = event.event.data[4].toString(); + const balance = event.event.data.amount?.toString() ?? event.event.data[3].toString(); return [balance, index]; }; @@ -111,7 +83,9 @@ export const getReceivedAmount = (sendedAmount: string, receivedAmount: CodecStr }; export const getParachainSystemMessageHash = (events: Array, api: ApiPromise) => { - const parachainSystemEvent = events.find((e) => api.events.parachainSystem.UpwardMessageSent.is(e.event)); + const parachainSystemEvent = events.find( + (e) => api.events.parachainSystem.UpwardMessageSent.is(e.event) || api.events.xcmpQueue.XcmpMessageSent.is(e.event) + ); if (!parachainSystemEvent) { throw new Error(`Unable to find "parachainSystem.UpwardMessageSent" event`); @@ -222,10 +196,3 @@ export const isSoraBridgeAppBurned = ( return true; }; - -// [TECH] move to js-lib -export const formatSubAddress = (address: string, ss58: number): string => { - const publicKey = decodeAddress(address, false); - - return encodeAddress(publicKey, ss58); -}; diff --git a/src/utils/ethers-util.ts b/src/utils/ethers-util.ts index a94d2caf3..8c0933295 100644 --- a/src/utils/ethers-util.ts +++ b/src/utils/ethers-util.ts @@ -424,8 +424,9 @@ async function getBlockNumber(): Promise { } } -async function accountAddressToHex(address: string): Promise { - return ethers.hexlify(decodeAddress(address)); +function accountAddressToHex(address: string): string { + const publicKey = decodeAddress(address); + return ethers.hexlify(publicKey); } function hexToNumber(hex: string): number { diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index ecbd75bce..d02e2df7b 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -307,7 +307,7 @@ import { asZeroValue, delay, } from '@/utils'; -import { subBridgeConnector } from '@/utils/bridge/sub/classes/adapter'; +import type { SubNetworksConnector } from '@/utils/bridge/sub/classes/adapter'; import type { NodesConnection } from '@/utils/connection'; import type { IBridgeTransaction } from '@sora-substrate/util'; @@ -347,6 +347,7 @@ export default class Bridge extends Mixins( readonly KnownSymbols = KnownSymbols; readonly FocusedField = FocusedField; + @state.bridge.subBridgeConnector private subBridgeConnector!: SubNetworksConnector; @state.bridge.balancesFetching private balancesFetching!: boolean; @state.bridge.feesAndLockedFundsFetching private feesAndLockedFundsFetching!: boolean; @state.assets.registeredAssetsFetching private registeredAssetsFetching!: boolean; @@ -382,8 +383,6 @@ export default class Bridge extends Mixins( @state.web3.selectSubNodeDialogVisibility selectSubNodeDialogVisibility!: boolean; @mutation.web3.setSelectSubNodeDialogVisibility private setSelectSubNodeDialogVisibility!: (flag: boolean) => void; - private readonly subBridgeConnector = subBridgeConnector; - get subConnection(): Nullable { if (!this.isSubBridge) return null; if (this.networkSelected !== this.subBridgeConnector.network?.subNetwork) return null; diff --git a/yarn.lock b/yarn.lock index 96980036a..dd38275c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2349,68 +2349,68 @@ resolved "https://registry.yarnpkg.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz#a53515db25d8038374381b73af20bb4f2e508d87" integrity sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w== -"@sora-substrate/api@1.32.11-beta": - version "1.32.11-beta" - resolved "https://registry.yarnpkg.com/@sora-substrate/api/-/api-1.32.11-beta.tgz#590863864b4eda9445d54c2d52a6ec5fea4a71df" - integrity sha512-Qr2ErG+6HQWoI7ZUvP2kYr6Owt/R/4nJskfnl4OZet1Mp/AMd9adGjA4WcijYukEbZwaMibmaI9ylAQWU9VDXg== +"@sora-substrate/api@1.33.13": + version "1.33.13" + resolved "https://registry.yarnpkg.com/@sora-substrate/api/-/api-1.33.13.tgz#06957599f7d6b37670ab28e64b33875af4aae83c" + integrity sha512-Qp3DlMpeeVah1JEXgTEj2eJLBHLt7JRYsmKe8/SD4JZR431OBoL3NbKfpVJAuMgNeZnzPNsM/rF0b7Pik6zSNw== dependencies: "@open-web3/orml-api-derive" "1.1.4" "@polkadot/api" "9.14.2" - "@sora-substrate/types" "1.32.11-beta" + "@sora-substrate/types" "1.33.13" -"@sora-substrate/connection@1.32.11-beta": - version "1.32.11-beta" - resolved "https://registry.yarnpkg.com/@sora-substrate/connection/-/connection-1.32.11-beta.tgz#fcf4025989242dd5d98fff7a3797a5a94ab8fd40" - integrity sha512-coPmSoI/Pfjf+ZEro6QJyOOBp5tQSRothdw4KAKV/0t8BORS6Mc0LJABQrduTcfnLK2/k84113pTsq2C76l5YA== +"@sora-substrate/connection@1.33.13": + version "1.33.13" + resolved "https://registry.yarnpkg.com/@sora-substrate/connection/-/connection-1.33.13.tgz#426b58bbf8ae4f0cd91accf9cddcff3bb2d58753" + integrity sha512-ccliz88gG21h2HJWqeNtc7WI0E6vTm6mCYrpyRM6JF0bnq2/Ag46l9sHfXKNIwBFIYHy6pMiMxTjwWUSMZ1U9A== dependencies: - "@sora-substrate/api" "1.32.11-beta" + "@sora-substrate/api" "1.33.13" -"@sora-substrate/liquidity-proxy@1.32.11-beta": - version "1.32.11-beta" - resolved "https://registry.yarnpkg.com/@sora-substrate/liquidity-proxy/-/liquidity-proxy-1.32.11-beta.tgz#c72c0d527e85a81a1a6107cad9e0ba88017fe419" - integrity sha512-bQkmrdo05OYST0lBN3Y4xksAUToR91F6e0rR5SXfB54J//VrBFmNkweCNBTbL8sHamYIVy1pFpwckFZF4pf6/w== +"@sora-substrate/liquidity-proxy@1.33.13": + version "1.33.13" + resolved "https://registry.yarnpkg.com/@sora-substrate/liquidity-proxy/-/liquidity-proxy-1.33.13.tgz#44c716aa81842668141ab58da815a4a57020714e" + integrity sha512-CrYPcKb5HcK7bqkqKL+0oxmCyzE4h9So/0XUFNPHVQDvXmMCiGZv5jyQ32MUnlCXUYVk5qvqPePhc4FKr7Dlvg== dependencies: - "@sora-substrate/math" "1.32.11-beta" + "@sora-substrate/math" "1.33.13" -"@sora-substrate/math@1.32.11-beta": - version "1.32.11-beta" - resolved "https://registry.yarnpkg.com/@sora-substrate/math/-/math-1.32.11-beta.tgz#34ce3d3bdd8607e1c90b3c081f2bb7989acbb8be" - integrity sha512-9VBOKgfZPCYG2QkoLdDsFRjI36rqIn5M975HDF2y/Bx9AsXOlz6XF56h7EHQZghtQtlyoOjmWdw/l78UNtYB/g== +"@sora-substrate/math@1.33.13": + version "1.33.13" + resolved "https://registry.yarnpkg.com/@sora-substrate/math/-/math-1.33.13.tgz#f4d7af6c87ef292f5655746efaf6512d08be839a" + integrity sha512-Q6DaRDXWXA8YFhgwSQaYSoxt883vETcPDfu7v/3UwRRb02DWTuFjycbgAawvvKdmyodqd6mSnT2yjnHdEdS9EQ== dependencies: "@polkadot/types" "9.14.2" bignumber.js "^9.1.2" lodash "^4.17.21" -"@sora-substrate/type-definitions@1.32.11-beta": - version "1.32.11-beta" - resolved "https://registry.yarnpkg.com/@sora-substrate/type-definitions/-/type-definitions-1.32.11-beta.tgz#7a4433f46024a394c935e827efe025bf73ec781d" - integrity sha512-tdSxPORLmKpGsxNeYOLfWxWmpsrk4IYaYDw6W/Q2oyCFPoe6mWya2PQenLPqAA4uAPK1Gs2p17DgvG6BuSw14w== +"@sora-substrate/type-definitions@1.33.13": + version "1.33.13" + resolved "https://registry.yarnpkg.com/@sora-substrate/type-definitions/-/type-definitions-1.33.13.tgz#ca2526fff61effe526881af00eab115405f2cd7e" + integrity sha512-fNFVj/GG7syq8wo3On/jo0vMRQ513IxTn6vP6k8ZCxnjL5ibXYeWqCHoMx3YR/x0lM3y+hXYwhrIsGyFM/uH5g== dependencies: "@open-web3/orml-type-definitions" "1.1.4" -"@sora-substrate/types@1.32.11-beta": - version "1.32.11-beta" - resolved "https://registry.yarnpkg.com/@sora-substrate/types/-/types-1.32.11-beta.tgz#0785e3e4fed3d6beda0a4af8ed1e84fcbabe655c" - integrity sha512-BzOTsdHLCFPcy3CCBZt9Fc5GvZutBeAsEJVLpCtkXFkofkEeOve82yGddXN2+hBASgk0ZFw0YmosTZuFEbP8OA== +"@sora-substrate/types@1.33.13": + version "1.33.13" + resolved "https://registry.yarnpkg.com/@sora-substrate/types/-/types-1.33.13.tgz#3cee8a68fe0330e1098b40a95dfd9118a75f309b" + integrity sha512-wccYM5wRIhxr7OMXKrXBnn2O9wy3yaiE9MoAhNoKoJoNCqYD63cpozQ+bcGbuSiKtKg/6aFdLpst+CK4Wt+rBA== dependencies: "@open-web3/api-mobx" "0.9.4-26" "@open-web3/orml-types" "1.1.4" "@polkadot/api" "9.14.2" "@polkadot/typegen" "9.14.2" "@polkadot/types" "9.14.2" - "@sora-substrate/type-definitions" "1.32.11-beta" + "@sora-substrate/type-definitions" "1.33.13" -"@sora-substrate/util@1.32.11-beta": - version "1.32.11-beta" - resolved "https://registry.yarnpkg.com/@sora-substrate/util/-/util-1.32.11-beta.tgz#c19542fcb38fa3b08af76b8436c2245603b0b079" - integrity sha512-wODPJFxHAvV2BUP8ZI1+I7kGy72d2pzxr7kO5abJSU8KOwubptcTbKWyoY03/XIp5DzYPSGIfUI7qhfRxQeqGg== +"@sora-substrate/util@1.33.13": + version "1.33.13" + resolved "https://registry.yarnpkg.com/@sora-substrate/util/-/util-1.33.13.tgz#388d32e67160b8f7b0dd44e6875665470e47990a" + integrity sha512-7Xt9yPWflNF8pmI4Uuvj9hdqD2klhTAQr/2DM92X5F759Niefe20pPx1HK6uvuM6YlV9w1Ba9paftL3VJYGw2Q== dependencies: "@polkadot/ui-keyring" "2.12.1" - "@sora-substrate/api" "1.32.11-beta" - "@sora-substrate/connection" "1.32.11-beta" - "@sora-substrate/liquidity-proxy" "1.32.11-beta" - "@sora-substrate/math" "1.32.11-beta" - "@sora-substrate/types" "1.32.11-beta" + "@sora-substrate/api" "1.33.13" + "@sora-substrate/connection" "1.33.13" + "@sora-substrate/liquidity-proxy" "1.33.13" + "@sora-substrate/math" "1.33.13" + "@sora-substrate/types" "1.33.13" axios "^1.6.8" crypto-js "^4.2.0" lodash "^4.17.21" @@ -2443,13 +2443,13 @@ vue-property-decorator "^9.1.2" vuex "^3.6.2" -"@soramitsu/soraneo-wallet-web@1.32.8-beta": - version "1.32.8-beta" - resolved "https://nexus.iroha.tech/repository/npm-group/@soramitsu/soraneo-wallet-web/-/soraneo-wallet-web-1.32.8-beta.tgz#d98b841c82eb1cf084539933d5394a9e016361b7" - integrity sha512-KDZUm/xAkZFj3UkVf9krMKmvOrbfAESvcE9L4fBeJln0DMo1gp9kjFtBT1vYCgWcwdRWXQyGMqIQBWYGM2c3/g== +"@soramitsu/soraneo-wallet-web@1.33.1": + version "1.33.1" + resolved "https://nexus.iroha.tech/repository/npm-group/@soramitsu/soraneo-wallet-web/-/soraneo-wallet-web-1.33.1.tgz#7dd92d2a4b9305a6a9baebd7bc777a52600391fb" + integrity sha512-aP+iYfQIc9Nin6OroWLoCIXIJpz1LOMmfgrTVyYD5TmsXujQB4+aS228nhngZP8pHgLOa8V1bTWv4KB3Q2KC4A== dependencies: "@polkadot/vue-identicon" "2.12.1" - "@sora-substrate/util" "1.32.11-beta" + "@sora-substrate/util" "1.33.13" "@sora-test/wallet-connect" "^0.0.9" "@soramitsu-ui/ui-vue2" "^1.1.1" "@urql/core" "^5.0.0" @@ -2466,7 +2466,7 @@ graphql-ws "^5.16.0" is-electron "^2.2.2" lodash "^4.17.21" - maska "^1.5.1" + maska "^1.5.2" nft.storage "^7.1.1" subscriptions-transport-ws "^0.11.0" vue "2.7.14" @@ -10380,7 +10380,7 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== -maska@^1.5.1: +maska@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/maska/-/maska-1.5.2.tgz#ebc7e5165aab623814828b246b2334077841980e" integrity sha512-zDalYGEVjQvnmedj6Yaae532g1RQVKppX8w4+L4q5HPuTUCJew/YDtTsKto4ReYSk5+nfacGyyz067o7qo4xTQ==