diff --git a/package.json b/package.json index a95368ca8..75b37dca3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soramitsu/soraneo-wallet-web", - "version": "1.43.4", + "version": "1.43.4-beta-1", "license": "Apache-2.0", "private": false, "publishConfig": { diff --git a/src/SoraWallet.vue b/src/SoraWallet.vue index b56e4e2d3..b05ebe786 100644 --- a/src/SoraWallet.vue +++ b/src/SoraWallet.vue @@ -14,7 +14,6 @@ import { Component, Mixins } from 'vue-property-decorator'; import AddAsset from './components/AddAsset/AddAsset.vue'; -import CreateToken from './components/CreateToken.vue'; import LoadingMixin from './components/mixins/LoadingMixin'; import TranslationMixin from './components/mixins/TranslationMixin'; import ReceiveToken from './components/ReceiveToken.vue'; @@ -34,7 +33,6 @@ import type { AccountAsset } from '@sora-substrate/sdk/build/assets/types'; components: { AddAsset, SelectAsset, - CreateToken, ReceiveToken, Wallet, WalletAssetDetails, diff --git a/src/components/AssetDetails/AssetDetailsSBT.vue b/src/components/AssetDetails/AssetDetailsSBT.vue new file mode 100644 index 000000000..7b6d60105 --- /dev/null +++ b/src/components/AssetDetails/AssetDetailsSBT.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/src/components/AssetDetails/AssetDetailsTransferable.vue b/src/components/AssetDetails/AssetDetailsTransferable.vue new file mode 100644 index 000000000..64bfbddc3 --- /dev/null +++ b/src/components/AssetDetails/AssetDetailsTransferable.vue @@ -0,0 +1,495 @@ + + + + + + + diff --git a/src/components/AssetListItem.vue b/src/components/AssetListItem.vue index 618e056da..b30ef9155 100644 --- a/src/components/AssetListItem.vue +++ b/src/components/AssetListItem.vue @@ -39,11 +39,13 @@ + + diff --git a/src/components/TokenLogo.vue b/src/components/AssetLogos/TokenLogo.vue similarity index 76% rename from src/components/TokenLogo.vue rename to src/components/AssetLogos/TokenLogo.vue index 6ccbf22e0..24a1af626 100644 --- a/src/components/TokenLogo.vue +++ b/src/components/AssetLogos/TokenLogo.vue @@ -1,26 +1,30 @@ - - - - diff --git a/src/components/CreateSimpleToken.vue b/src/components/CreateSimpleToken.vue deleted file mode 100644 index d9f826862..000000000 --- a/src/components/CreateSimpleToken.vue +++ /dev/null @@ -1,217 +0,0 @@ - - - - - diff --git a/src/components/CreateToken.vue b/src/components/CreateToken.vue deleted file mode 100644 index f70bc41e0..000000000 --- a/src/components/CreateToken.vue +++ /dev/null @@ -1,110 +0,0 @@ - - - - - diff --git a/src/components/ReceiveToken.vue b/src/components/ReceiveToken.vue index e2cc6c6df..6f80ce930 100644 --- a/src/components/ReceiveToken.vue +++ b/src/components/ReceiveToken.vue @@ -34,9 +34,9 @@ import { state, getter, mutation } from '../store/decorators'; import { svgSaveAs, IMAGE_EXTENSIONS } from '../util/image'; import WalletAccount from './Account/WalletAccount.vue'; +import TokenLogo from './AssetLogos/TokenLogo.vue'; import NotificationMixin from './mixins/NotificationMixin'; import QrCode from './QrCode/QrCode.vue'; -import TokenLogo from './TokenLogo.vue'; import WalletBase from './WalletBase.vue'; import type { RouteNames } from '../consts'; diff --git a/src/components/Wallet.vue b/src/components/Wallet.vue index a5f856004..0afb60709 100644 --- a/src/components/Wallet.vue +++ b/src/components/Wallet.vue @@ -4,14 +4,6 @@ - - - @@ -149,10 +141,6 @@ export default class Wallet extends Mixins(AccountActionsMixin, OperationsMixin, this.$emit('swap', asset); } - handleCreateToken(): void { - this.navigate({ name: RouteNames.CreateToken }); - } - handleSwitchAccount(): void { this.navigate({ name: RouteNames.WalletConnection }); } diff --git a/src/components/WalletAssetDetails.vue b/src/components/WalletAssetDetails.vue index 435826557..68dab6a20 100644 --- a/src/components/WalletAssetDetails.vue +++ b/src/components/WalletAssetDetails.vue @@ -1,271 +1,29 @@ - - - - diff --git a/src/components/WalletAssets.vue b/src/components/WalletAssets.vue index 2febd75e2..c71285a11 100644 --- a/src/components/WalletAssets.vue +++ b/src/components/WalletAssets.vue @@ -15,6 +15,7 @@ >

{{ t('walletSend.addressError') }}

+

+ {{ t('sbtDetails.noReceiverAccess') }} +

import { FPNumber, Operation } from '@sora-substrate/sdk'; +import { getAssetBalance } from '@sora-substrate/sdk/build/assets'; import { XOR } from '@sora-substrate/sdk/build/assets/consts'; +import { + AssetTypes, + type Asset, + type AccountAsset, + type AccountBalance, + type UnlockPeriodDays, +} from '@sora-substrate/sdk/build/assets/types'; import debounce from 'lodash/fp/debounce'; -import { Component, Mixins } from 'vue-property-decorator'; +import { Component, Mixins, Watch } from 'vue-property-decorator'; import { api } from '../api'; import { RouteNames } from '../consts'; @@ -165,6 +176,7 @@ import { validateAddress, formatAddress, formatAccountAddress } from '../util'; import AccountConfirmationOption from './Account/Settings/ConfirmationOption.vue'; import AddressBookInput from './AddressBook/Input.vue'; +import TokenLogo from './AssetLogos/TokenLogo.vue'; import FormattedAmount from './FormattedAmount.vue'; import FormattedAmountWithFiatValue from './FormattedAmountWithFiatValue.vue'; import InfoLine from './InfoLine.vue'; @@ -173,14 +185,12 @@ import FormattedAmountMixin from './mixins/FormattedAmountMixin'; import NetworkFeeWarningMixin from './mixins/NetworkFeeWarningMixin'; import TransactionMixin from './mixins/TransactionMixin'; import NetworkFeeWarning from './NetworkFeeWarning.vue'; -import TokenLogo from './TokenLogo.vue'; import WalletBase from './WalletBase.vue'; import WalletFee from './WalletFee.vue'; import type { VestedTransferFeeParams, VestedTransferParams } from '../store/account/types'; import type { Route } from '../store/router/types'; import type { CodecString } from '@sora-substrate/sdk'; -import type { AccountAsset, AccountBalance, UnlockPeriodDays } from '@sora-substrate/sdk/build/assets/types'; import type { Subscription } from 'rxjs'; @Component({ @@ -218,8 +228,9 @@ export default class WalletSend extends Mixins( @state.router.previousRoute private previousRoute!: RouteNames; @state.router.previousRouteParams private previousRouteParams!: Record; - @state.router.currentRouteParams private currentRouteParams!: Record; + @state.router.currentRouteParams private currentRouteParams!: Record | string>; @state.account.accountAssets private accountAssets!: Array; + @state.account.assets assets!: Array; @state.transactions.isConfirmTxDialogDisabled private isConfirmTxDisabled!: boolean; @mutation.router.navigate private navigate!: (options: Route) => void; @@ -234,6 +245,8 @@ export default class WalletSend extends Mixins( address = ''; amount = ''; showAdditionalInfo = true; + showIsNotSbtOwnerReceiver = false; + prevAsset: Nullable = null; /* remember previous asset for routing */ withVesting = false; selectedVestingPeriod: UnlockPeriodDays = 1; vestingPercentage = '10'; @@ -241,6 +254,29 @@ export default class WalletSend extends Mixins( private assetBalance: Nullable = null; private assetBalanceSubscription: Nullable = null; + @Watch('address') + async getIsNotSbtOwnerReceiver(): Promise { + if (this.validAddress && this.asset.address) { + const regulatedAsset = this.assets.find( + (asset) => asset.address === this.asset.address && asset.type === AssetTypes.Regulated + ); + + if (regulatedAsset) { + const sbtAddress = await api.extendedAssets.getSbtAddress(regulatedAsset.address); + + const balance = await getAssetBalance(api.api, this.address, sbtAddress); + + if (this.getFPNumberFromCodec(balance.total).lte(FPNumber.ZERO)) { + this.showIsNotSbtOwnerReceiver = true; + } else { + this.showIsNotSbtOwnerReceiver = false; + } + } + } else { + this.showIsNotSbtOwnerReceiver = false; + } + } + created(): void { if (!this.currentRouteParams.asset) { this.handleBack(); @@ -372,6 +408,8 @@ export default class WalletSend extends Mixins( !this.validAddress || !this.validAmount || !this.hasEnoughXor || + this.isAccountAddress || + this.showIsNotSbtOwnerReceiver || (this.withVesting && !+this.vestingPercentage) ); } @@ -436,7 +474,11 @@ export default class WalletSend extends Mixins( } this.navigate({ name: this.previousRoute, - params: this.previousRouteParams, + params: { + ...this.previousRouteParams, + fromWalletAssets: this.currentRouteParams.fromWalletAssets, + fromSbtDetails: this.currentRouteParams.fromSbtDetails, + }, }); } diff --git a/src/components/mixins/OperationsMixin.ts b/src/components/mixins/OperationsMixin.ts index 0d7c33983..4e93641ef 100644 --- a/src/components/mixins/OperationsMixin.ts +++ b/src/components/mixins/OperationsMixin.ts @@ -26,6 +26,7 @@ const amountBasedOperations = [ Operation.Burn, Operation.Mint, Operation.Transfer, + Operation.XorlessTransfer, Operation.VestedTransfer, Operation.SwapTransferBatch, Operation.DemeterFarmingGetRewards, @@ -106,6 +107,9 @@ export default class OperationsMixin extends Mixins(NotificationMixin, NumberFor } else if (status !== TransactionStatus.Error) { status = TransactionStatus.Finalized; } + if (value.type === Operation.SetAccessExpiration) { + params.endTime = new Date(params?.endTime).toLocaleDateString(); + } if (hideAmountValues) { params.amount = HiddenValue; params.amount2 = HiddenValue; diff --git a/src/components/mixins/TransactionMixin.ts b/src/components/mixins/TransactionMixin.ts index f6e5b911a..e62dbc3bb 100644 --- a/src/components/mixins/TransactionMixin.ts +++ b/src/components/mixins/TransactionMixin.ts @@ -55,7 +55,12 @@ export default class TransactionMixin extends Mixins(LoadingMixin, OperationsMix this.showAppNotification(message, 'success'); } if (value.status === TransactionStatus.InBlock) return; - } else if (value.type === Operation.RegisterAsset && value.assetAddress) { + } else if ( + [Operation.RegisterAsset, Operation.RegisterAndRegulateAsset, Operation.IssueSoulBoundToken].includes( + value.type + ) && + value.assetAddress + ) { // If user was really fast and already added tx const alreadyExists = this.accountAssetsAddressTable[value.assetAddress]; if (!alreadyExists) { diff --git a/src/components/mixins/TranslationMixin.ts b/src/components/mixins/TranslationMixin.ts index 7a6576268..c2228540b 100644 --- a/src/components/mixins/TranslationMixin.ts +++ b/src/components/mixins/TranslationMixin.ts @@ -1,11 +1,13 @@ import dayjs from 'dayjs'; import localizedFormat from 'dayjs/plugin/localizedFormat'; +import relativeTime from 'dayjs/plugin/relativeTime'; import { Vue, Component } from 'vue-property-decorator'; import { TranslationConsts } from '../../consts'; // enable dayjs plugin dayjs.extend(localizedFormat); +dayjs.extend(relativeTime); @Component export default class TranslationMixin extends Vue { @@ -39,6 +41,23 @@ export default class TranslationMixin extends Vue { } } + getRelativeTime(date: number): string { + let startTime, endTime; + const currentTime = Date.now(); + + if (date < currentTime) { + startTime = date; + endTime = currentTime; + } else { + startTime = currentTime; + endTime = date; + } + + return startTime < endTime + ? dayjs().to(dayjs(date).locale(this.dayjsLocale)) + : dayjs().from(dayjs(date).locale(this.dayjsLocale)); + } + formatDate(date: Nullable, format = 'll LTS'): string { return dayjs(date).locale(this.dayjsLocale).format(format); } diff --git a/src/consts/index.ts b/src/consts/index.ts index 966ffd0c2..6a7733816 100644 --- a/src/consts/index.ts +++ b/src/consts/index.ts @@ -6,6 +6,7 @@ import type { FPNumber } from '@sora-substrate/sdk'; export const accountIdBasedOperations = [ Operation.SwapAndSend, Operation.Transfer, + Operation.XorlessTransfer, Operation.VestedTransfer, Operation.SwapTransferBatch, Operation.Mint, @@ -59,7 +60,6 @@ export enum RouteNames { WalletSend = 'WalletSend', Wallet = 'Wallet', WalletAssetDetails = 'WalletAssetDetails', - CreateToken = 'CreateToken', ReceiveToken = 'ReceiveToken', AddAsset = 'AddAsset', SelectAsset = 'SelectAsset', @@ -84,6 +84,7 @@ export enum WalletFilteringOptions { All = 'All', Currencies = 'Currencies', NFT = 'NFT', + SBT = 'SBT', } export enum SoraNetwork { @@ -238,7 +239,9 @@ export const TranslationConsts = { TBC: 'TBC', XYK: 'XYK', NFT: 'NFT', + SBT: 'SBT', CEX: 'CEX', + KYC: 'KYC', Polkadot: 'Polkadot', SORAScan: 'SORAScan', Subscan: 'Subscan', diff --git a/src/index.ts b/src/index.ts index 76f9d8c2d..85922ea45 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import AddAssetDetailsCard from './components/AddAsset/AddAssetDetailsCard.vue'; import AddressBookInput from './components/AddressBook/Input.vue'; import AssetList from './components/AssetList.vue'; import AssetListItem from './components/AssetListItem.vue'; +import TokenLogo from './components/AssetLogos/TokenLogo.vue'; import ConfirmDialog from './components/ConfirmDialog.vue'; import ConnectionView from './components/Connection/ConnectionView.vue'; import AccountConnectionList from './components/Connection/List/Account.vue'; @@ -41,7 +42,6 @@ import FormattedAddress from './components/shared/FormattedAddress.vue'; import SyntheticSwitcher from './components/shared/SyntheticSwitcher.vue'; import SimpleNotification from './components/SimpleNotification.vue'; import TokenAddress from './components/TokenAddress.vue'; -import TokenLogo from './components/TokenLogo.vue'; import TransactionHashView from './components/TransactionHashView.vue'; import WalletBase from './components/WalletBase.vue'; import WalletFee from './components/WalletFee.vue'; @@ -77,6 +77,7 @@ import { getAssetsSubset, } from './util'; import * as accountUtils from './util/account'; +import { IpfsStorage } from './util/ipfsStorage'; import { ScriptLoader } from './util/scriptLoader'; import { storage, runtimeStorage, settingsStorage } from './util/storage'; @@ -262,6 +263,7 @@ export { storage, runtimeStorage, settingsStorage, + IpfsStorage, getExplorerLinks, groupRewardsByAssetsList, formatAccountAddress, diff --git a/src/lang/en/index.ts b/src/lang/en/index.ts index b95f83621..2d07af177 100644 --- a/src/lang/en/index.ts +++ b/src/lang/en/index.ts @@ -54,6 +54,7 @@ export default { [Operation.SwapAndSend]: 'Swap and Send', [Operation.SwapTransferBatch]: '{ADAR} transfer', [Operation.Transfer]: 'Transfer', + [Operation.XorlessTransfer]: 'Transfer', [Operation.VestedTransfer]: 'Transfer', [Operation.AddLiquidity]: 'Add Liquidity', [Operation.RemoveLiquidity]: 'Remove Liquidity', @@ -91,9 +92,15 @@ export default { [Operation.DepositCollateral]: 'Deposit Position', [Operation.RepayVaultDebt]: 'Repay Debt', [Operation.BorrowVaultDebt]: 'Borrow Debt', + [Operation.SetAccessExpiration]: 'Set SBT Expiration', + [Operation.RegulateAsset]: 'Regulate Asset', + [Operation.RegisterAndRegulateAsset]: 'Register Asset', + [Operation.BindRegulatedAsset]: 'Bind Regulated Asset', + [Operation.IssueSoulBoundToken]: 'Issue SBT', andText: 'and', [TransactionStatus.Finalized]: { [Operation.Transfer]: '{action} {amount} {symbol} {direction} {address}', + [Operation.XorlessTransfer]: '{action} {amount} {symbol} {direction} {address}', [Operation.VestedTransfer]: '{action} {amount} {symbol} {direction} {address}', [Operation.Swap]: 'Swapped {amount} {symbol} for {amount2} {symbol2}', [Operation.SwapAndSend]: 'Swapped {amount} {symbol} for {amount2} {symbol2} and {action} {direction} {address}', @@ -134,9 +141,15 @@ export default { [Operation.DepositCollateral]: 'Deposited {amount} {symbol}', [Operation.RepayVaultDebt]: 'Repaid {amount} {symbol}', [Operation.BorrowVaultDebt]: 'Borrowed {amount} {symbol}', + [Operation.SetAccessExpiration]: 'SB token {symbol} Updated expiration to {endTime}', + [Operation.RegulateAsset]: 'Regulate Asset {symbol}', + [Operation.RegisterAndRegulateAsset]: 'Register And Regulate {symbol} asset', + [Operation.BindRegulatedAsset]: 'Bind Asset {symbol2} to SBT', + [Operation.IssueSoulBoundToken]: 'Issue Soulbound Token {symbol}', }, [TransactionStatus.Error]: { [Operation.Transfer]: 'Failed to {action} {amount} {symbol} {direction} {address}', + [Operation.XorlessTransfer]: 'Failed to send {amount} {symbol} to {address}', [Operation.VestedTransfer]: 'Failed to {action} {amount} {symbol} {direction} {address}', [Operation.Swap]: 'Failed to swap {amount} {symbol} for {amount2} {symbol2}', [Operation.SwapAndSend]: @@ -178,6 +191,11 @@ export default { [Operation.DepositCollateral]: 'Failed to deposit {amount} {symbol}', [Operation.RepayVaultDebt]: 'Failed to repaid {amount} {symbol}', [Operation.BorrowVaultDebt]: 'Failed to borrow {amount} {symbol}', + [Operation.SetAccessExpiration]: 'Failed to update expiration to {to} for {symbol}', + [Operation.RegulateAsset]: 'Failed To Regulate Asset {symbol}', + [Operation.RegisterAndRegulateAsset]: 'Failed To Register And Regulate {symbol}', + [Operation.BindRegulatedAsset]: 'Failed To Bind Asset {symbol} to SBT {symbol2}', + [Operation.IssueSoulBoundToken]: 'Failed to issue Soulbound token {symbol}', }, }, polkadotjs: { @@ -588,4 +606,22 @@ export default { enterPassword: 'Enter password', googleOnly: '{Google} accounts only', }, + sbtDetails: { + description: 'This SBT provides access to multiple tokens & pools and is attached to your account', + issuedBy: '{SBT} issued by', + kycVerification: '{KYC} Verification', + kycProvider: '{KYC} Provider', + accessPermitted: 'Access permitted', + listOperable: 'Regulated assets operable', + noPermissions: 'No additional permissions', + regulatedInsitution: 'Regulated Institution', + expiryDate: 'Expiry date', + expiryDateTooltip: "The 'Expiry Date' is the deadline for your access to regulated assets of SBT", + expiresIn: 'Expires in', + expiresInTooltip: "The 'Expires in' shows period or point of time the access is acquired", + indefiniteExp: 'open-ended', + permission: 'permission', + permissions: 'permissions', + noReceiverAccess: "You're trying to send regulated token to the user with no SBT access", + }, }; diff --git a/src/services/indexer/parser.ts b/src/services/indexer/parser.ts index 498a1ba50..03cbdfd4e 100644 --- a/src/services/indexer/parser.ts +++ b/src/services/indexer/parser.ts @@ -39,6 +39,11 @@ import type { HistoryElementVaultClose, HistoryElementVaultDepositCollateral, HistoryElementVaultDebt, + HistoryElementDefiRIssueSBT, + HistoryElementDefiRSetSBTExpiration, + HistoryElementDefiRRegulateAsset, + HistoryElementDefiRBindRegulatedAssetToSbt, + HistoryElementDefiRRegisterRegulatedAsset, HistoryElementEthBridgeIncoming, HistoryElementEthBridgeOutgoing, ClaimedRewardItem, @@ -78,7 +83,6 @@ const OperationsMap = { [insensitive(ModuleNames.Utility)]: { [insensitive(ModuleMethods.UtilityBatchAll)]: (data: HistoryElementBatchCall[]) => { if (!(Array.isArray(data) && !!data.length)) return null; - if ( !!getBatchCall(data, { module: ModuleNames.PoolXYK, method: ModuleMethods.PoolXYKInitializePool }) && !!getBatchCall(data, { module: ModuleNames.PoolXYK, method: ModuleMethods.PoolXYKDepositLiquidity }) @@ -118,6 +122,16 @@ const OperationsMap = { return Operation.StakingBondAndNominate; } + if ( + data.every( + (call) => + isModuleMethod(call, ModuleNames.DefiR, ModuleMethods.DefiRBindRegulatedAsset) || + isModuleMethod(call, ModuleNames.DefiR, ModuleMethods.DefiRIssueSoulBoundToken) + ) + ) { + return Operation.IssueSoulBoundToken; + } + return null; }, }, @@ -169,6 +183,13 @@ const OperationsMap = { [insensitive(ModuleMethods.VaultDebtPayment)]: () => Operation.RepayVaultDebt, [insensitive(ModuleMethods.VaultDebtBorrow)]: () => Operation.BorrowVaultDebt, }, + [insensitive(ModuleNames.DefiR)]: { + [insensitive(ModuleMethods.DefiRSetAccessExpiration)]: () => Operation.SetAccessExpiration, + [insensitive(ModuleMethods.DefiRRegulateAsset)]: () => Operation.RegulateAsset, + [insensitive(ModuleMethods.DefiRRegisterAndRegulateAsset)]: () => Operation.RegisterAndRegulateAsset, + [insensitive(ModuleMethods.DefiRBindRegulatedAsset)]: () => Operation.BindRegulatedAsset, + [insensitive(ModuleMethods.DefiRIssueSoulBoundToken)]: () => Operation.IssueSoulBoundToken, + }, [insensitive(ModuleNames.BridgeMultisig)]: { [insensitive(ModuleMethods.BridgeMultisigAsMulti)]: () => Operation.EthBridgeIncoming, }, @@ -715,6 +736,43 @@ const parseVaultDebtPaymentOrBorrow = async (transaction: HistoryElement, payloa return payload; }; +const parseDefiRIssueSBT = async (transaction: HistoryElement, payload: HistoryItem) => { + const data = transaction.calls[0].data as HistoryElementDefiRIssueSBT; + payload.symbol = Buffer.from(data.symbol.slice(2), 'hex').toString(); + return payload; +}; + +const parseDefiRRegisterAndRegulatedAsset = async (transaction: HistoryElement, payload: HistoryItem) => { + const data = transaction.data as HistoryElementDefiRRegisterRegulatedAsset; + payload.symbol = Buffer.from(data.symbol.slice(2), 'hex').toString(); + return payload; +}; + +const parseDefiRSetSBTExpiration = async (transaction: HistoryElement, payload: HistoryItem) => { + const data = transaction.data as HistoryElementDefiRSetSBTExpiration; + const sbtAsset = await getAssetByAddress(data.sbtAssetId); + payload.symbol = getAssetSymbol(sbtAsset); + const date = new Date(parseInt(data.newExpiresAtTime, 10)); + payload.to = date.toLocaleString('en-US'); + return payload; +}; + +const parseDefiRRegulateAsset = async (transaction: HistoryElement, payload: HistoryItem) => { + const data = transaction.data as HistoryElementDefiRRegulateAsset; + const regulateAsset = await getAssetByAddress(data.assetId); + payload.symbol = getAssetSymbol(regulateAsset); + return payload; +}; + +const parseBindRegulatedAssetToSbt = async (transaction: HistoryElement, payload: HistoryItem) => { + const data = transaction.data as HistoryElementDefiRBindRegulatedAssetToSbt; + const regulateAsset = await getAssetByAddress(data.assetId); + payload.symbol = getAssetSymbol(regulateAsset); + const sbtAsset = await getAssetByAddress(data.sbtAssetId); + payload.symbol2 = getAssetSymbol(sbtAsset); + return payload; +}; + const parseEthBridgeIncoming = async (transaction: HistoryElement, payload: HistoryItem) => { const data = transaction.data as HistoryElementEthBridgeIncoming; @@ -790,6 +848,11 @@ export default class IndexerDataParser { Operation.DepositCollateral, Operation.RepayVaultDebt, Operation.BorrowVaultDebt, + Operation.SetAccessExpiration, + Operation.RegulateAsset, + Operation.RegisterAndRegulateAsset, + Operation.BindRegulatedAsset, + Operation.IssueSoulBoundToken, /** Don't show bridge tx in wallet */ // Operation.EthBridgeIncoming, // Operation.EthBridgeOutgoing, @@ -859,6 +922,9 @@ export default class IndexerDataParser { case Operation.Transfer: { return await parseTransfer(transaction, payload); } + case Operation.XorlessTransfer: { + return await parseTransfer(transaction, payload); + } case Operation.RegisterAsset: { return await parseRegisterAsset(transaction, payload); } @@ -930,6 +996,21 @@ export default class IndexerDataParser { case Operation.BorrowVaultDebt: { return await parseVaultDebtPaymentOrBorrow(transaction, payload); } + case Operation.RegisterAndRegulateAsset: { + return await parseDefiRRegisterAndRegulatedAsset(transaction, payload); + } + case Operation.IssueSoulBoundToken: { + return await parseDefiRIssueSBT(transaction, payload); + } + case Operation.SetAccessExpiration: { + return await parseDefiRSetSBTExpiration(transaction, payload); + } + case Operation.RegulateAsset: { + return await parseDefiRRegulateAsset(transaction, payload); + } + case Operation.BindRegulatedAsset: { + return await parseBindRegulatedAssetToSbt(transaction, payload); + } case Operation.EthBridgeIncoming: { return await parseEthBridgeIncoming(transaction, payload); } diff --git a/src/services/indexer/subquery/queries/historyElements.ts b/src/services/indexer/subquery/queries/historyElements.ts index 2bd831bf6..7f881000f 100644 --- a/src/services/indexer/subquery/queries/historyElements.ts +++ b/src/services/indexer/subquery/queries/historyElements.ts @@ -447,6 +447,53 @@ const OperationFilterMap = { equalTo: ModuleMethods.VaultDebtBorrow, }, }, + // DEFI-R + [Operation.SetAccessExpiration]: { + module: { + equalTo: ModuleNames.DefiR, + }, + method: { + equalTo: ModuleMethods.DefiRSetAccessExpiration, + }, + }, + [Operation.RegulateAsset]: { + module: { + equalTo: ModuleNames.DefiR, + }, + method: { + equalTo: ModuleMethods.DefiRRegulateAsset, + }, + }, + [Operation.BindRegulatedAsset]: { + module: { + equalTo: ModuleNames.DefiR, + }, + method: { + equalTo: ModuleMethods.DefiRBindRegulatedAsset, + }, + }, + [Operation.RegisterAndRegulateAsset]: { + module: { + equalTo: ModuleNames.DefiR, + }, + method: { + equalTo: ModuleMethods.DefiRRegisterAndRegulateAsset, + }, + }, + [Operation.IssueSoulBoundToken]: { + module: { + equalTo: ModuleNames.Utility, + }, + method: { + equalTo: ModuleMethods.UtilityBatchAll, + }, + callNames: { + contains: [ + ModuleNames.DefiR + '.' + ModuleMethods.DefiRBindRegulatedAsset, + ModuleNames.DefiR + '.' + ModuleMethods.DefiRIssueSoulBoundToken, + ], + }, + }, }; const createOperationsCriteria = (operations: Array) => { diff --git a/src/services/indexer/subsquid/queries/historyElements.ts b/src/services/indexer/subsquid/queries/historyElements.ts index a2cfb80bb..3b3590302 100644 --- a/src/services/indexer/subsquid/queries/historyElements.ts +++ b/src/services/indexer/subsquid/queries/historyElements.ts @@ -306,6 +306,31 @@ const OperationFilterMap = { module_eq: ModuleNames.Vault, method_eq: ModuleMethods.VaultDebtBorrow, }, + // DEFI-R + [Operation.SetAccessExpiration]: { + module_eq: ModuleNames.DefiR, + method_eq: ModuleMethods.DefiRSetAccessExpiration, + }, + [Operation.RegulateAsset]: { + module_eq: ModuleNames.DefiR, + method_eq: ModuleMethods.DefiRRegulateAsset, + }, + [Operation.BindRegulatedAsset]: { + module_eq: ModuleNames.DefiR, + method_eq: ModuleMethods.DefiRBindRegulatedAsset, + }, + [Operation.RegisterAndRegulateAsset]: { + module_eq: ModuleNames.DefiR, + method_eq: ModuleMethods.DefiRRegisterAndRegulateAsset, + }, + [Operation.IssueSoulBoundToken]: { + module_eq: ModuleNames.Utility, + method_eq: ModuleMethods.UtilityBatchAll, + callNames_containsAny: [ + ModuleNames.DefiR + '.' + ModuleMethods.DefiRBindRegulatedAsset, + ModuleNames.DefiR + '.' + ModuleMethods.DefiRIssueSoulBoundToken, + ], + }, }; const createOperationsCriteria = (operations: Array) => { diff --git a/src/services/indexer/types/calls.ts b/src/services/indexer/types/calls.ts index 805122419..b7242a706 100644 --- a/src/services/indexer/types/calls.ts +++ b/src/services/indexer/types/calls.ts @@ -14,6 +14,7 @@ export enum ModuleNames { OrderBook = 'orderBook', Staking = 'staking', Vault = 'kensetsu', + DefiR = 'extendedAssets', } export enum ModuleMethods { @@ -59,4 +60,9 @@ export enum ModuleMethods { VaultCollateralDeposit = 'depositCollateral', VaultDebtPayment = 'repayDebt', VaultDebtBorrow = 'borrow', + DefiRSetAccessExpiration = 'setSbtExpiration', + DefiRRegulateAsset = 'regulateAsset', + DefiRRegisterAndRegulateAsset = 'registerRegulatedAsset', + DefiRBindRegulatedAsset = 'bindRegulatedAssetToSbt', + DefiRIssueSoulBoundToken = 'issueSbt', } diff --git a/src/services/indexer/types/models.ts b/src/services/indexer/types/models.ts index 448797e8f..e77f3688a 100644 --- a/src/services/indexer/types/models.ts +++ b/src/services/indexer/types/models.ts @@ -497,6 +497,36 @@ export type HistoryElementVaultDebt = { export type HistoryElementVaultClose = Required; +export type HistoryElementDefiRIssueSBT = { + description: string; + externalurl: string; + image: string; + name: string; + symbol: string; +}; + +export type HistoryElementDefiRSetSBTExpiration = { + accountId: string; + newExpiresAtTime: string; + sbtAssetId: string; +}; + +export type HistoryElementDefiRRegulateAsset = { + assetId: string; +}; + +export type HistoryElementDefiRBindRegulatedAssetToSbt = { + assetId: string; + sbtAssetId: string; +}; + +export type HistoryElementDefiRRegisterRegulatedAsset = { + name: string; + supply: string; + symbol: string; + mintable: boolean; +}; + export type HistoryElementDataBase = Nullable< | HistoryElementReferralSetReferrer | HistoryElementReferrerReserve @@ -526,6 +556,11 @@ export type HistoryElementDataBase = Nullable< | HistoryElementVaultDepositCollateral | HistoryElementVaultDebt | HistoryElementVaultClose + | HistoryElementDefiRIssueSBT + | HistoryElementDefiRSetSBTExpiration + | HistoryElementDefiRRegulateAsset + | HistoryElementDefiRBindRegulatedAssetToSbt + | HistoryElementDefiRRegisterRegulatedAsset >; export type HistoryElementBase = { diff --git a/src/store/account/actions.ts b/src/store/account/actions.ts index 2e8086174..1c96b0f89 100644 --- a/src/store/account/actions.ts +++ b/src/store/account/actions.ts @@ -268,6 +268,8 @@ const actions = defineActions({ commit.resetAccountAssetsSubscription(); if (getters.isLoggedIn) { + const sbtAssetsList = await api.extendedAssets.getAllSbtIds(); + try { const subscription = api.assets.balanceUpdated.subscribe(() => { const filtered = api.assets.accountAssets.filter( diff --git a/src/store/account/getters.ts b/src/store/account/getters.ts index 6ed05bbbe..ed122eccb 100644 --- a/src/store/account/getters.ts +++ b/src/store/account/getters.ts @@ -1,3 +1,11 @@ +import { FPNumber } from '@sora-substrate/sdk'; +import { + type Asset, + type Whitelist, + type AccountAsset, + type WhitelistArrayItem, + AssetTypes, +} from '@sora-substrate/sdk/build/assets/types'; import { AES, enc } from 'crypto-js'; import { defineGetters } from 'direct-vuex'; import isEqual from 'lodash/fp/isEqual'; @@ -10,7 +18,6 @@ import { accountGetterContext } from './../account'; import type { AccountState } from './types'; import type { AssetsTable, AccountAssetsTable, PolkadotJsAccount } from '../../types/common'; -import type { Asset, Whitelist, AccountAsset, WhitelistArrayItem } from '@sora-substrate/sdk/build/assets/types'; const toHashTable = (list: Readonly>, key: string) => { return list.reduce((result, item) => { @@ -98,6 +105,17 @@ const getters = defineGetters()({ return isEqual(formatted)(accountData); }; }, + hasSomeSbt(...args): boolean { + const { state } = accountGetterContext(args); + + return state.accountAssets.some((asset) => { + if (asset.type === AssetTypes.Soulbound) { + return !FPNumber.fromCodecValue(asset.balance.total, asset.decimals).isZero(); + } + + return false; + }); + }, }); export default getters; diff --git a/src/store/transactions/actions.ts b/src/store/transactions/actions.ts index a889c199b..9d67ae4a5 100644 --- a/src/store/transactions/actions.ts +++ b/src/store/transactions/actions.ts @@ -88,8 +88,8 @@ const actions = defineActions({ }: ExternalHistoryParams = {} ): Promise { const { state, commit } = transactionsActionContext(context); - const { externalHistory, externalHistoryUpdates } = state; + const { externalHistory, externalHistoryUpdates } = state; const indexer = getCurrentIndexer(); const operations = indexer.services.dataParser.supportedOperations; const filter = indexer.historyElementsFilter({ @@ -107,7 +107,6 @@ const actions = defineActions({ try { const response = await indexer.services.explorer.account.getHistory(variables); - if (!response) return; const { nodes, totalCount } = response; diff --git a/tests/unit/components/WalletAssetDetails.spec.ts b/tests/unit/components/AssetDetailsTransferable.spec.ts similarity index 78% rename from tests/unit/components/WalletAssetDetails.spec.ts rename to tests/unit/components/AssetDetailsTransferable.spec.ts index d21860300..f4946938c 100644 --- a/tests/unit/components/WalletAssetDetails.spec.ts +++ b/tests/unit/components/AssetDetailsTransferable.spec.ts @@ -1,4 +1,4 @@ -import WalletAssetDetails from '@/components/WalletAssetDetails.vue'; +import AssetDetailsTransferable from '@/components/AssetDetails/AssetDetailsTransferable.vue'; import { useDescribe, useShallowMount, useVuex } from '../../utils'; import { MOCK_ACCOUNT_ASSETS, MOCK_FIAT_PRICE_OBJECT, MOCK_HISTORY, MOCK_WALLET_PERMISSIONS } from '../../utils/mock'; @@ -35,15 +35,15 @@ const createStore = ({ permissions = MOCK_WALLET_PERMISSIONS, isNotXor = false } }, }); -useDescribe('WalletAssetDetails.vue', WalletAssetDetails, () => { +useDescribe('WalletAssetDetails.vue', AssetDetailsTransferable, () => { it('should be rendered correctly', () => { - const wrapper = useShallowMount(WalletAssetDetails, { store: createStore() }); + const wrapper = useShallowMount(AssetDetailsTransferable, { store: createStore() }); expect(wrapper.element).toMatchSnapshot(); }); it('should show detailed balance info when button clicked', async () => { - const wrapper = useShallowMount(WalletAssetDetails, { store: createStore() }); + const wrapper = useShallowMount(AssetDetailsTransferable, { store: createStore() }); const el = wrapper.find('.asset-details-balance'); await el.trigger('click'); @@ -54,7 +54,7 @@ useDescribe('WalletAssetDetails.vue', WalletAssetDetails, () => { it('should not show balance details button', () => { const isNotXor = true; const store = createStore({ isNotXor }); - const wrapper = useShallowMount(WalletAssetDetails, { store }); + const wrapper = useShallowMount(AssetDetailsTransferable, { store }); const btnBalanceDetails = wrapper.find('.asset-details-balance--clickable'); expect(btnBalanceDetails.exists()).toBeFalse(); @@ -62,7 +62,7 @@ useDescribe('WalletAssetDetails.vue', WalletAssetDetails, () => { it('should not show send action button', () => { const store = createStore({ permissions: { ...MOCK_WALLET_PERMISSIONS, sendAssets: false } }); - const wrapper = useShallowMount(WalletAssetDetails, { store }); + const wrapper = useShallowMount(AssetDetailsTransferable, { store }); const btn = wrapper.find('.asset-details-action.send'); expect(btn.exists()).toBeFalse(); @@ -70,7 +70,7 @@ useDescribe('WalletAssetDetails.vue', WalletAssetDetails, () => { it('should not show bridge action button', () => { const store = createStore({ permissions: { ...MOCK_WALLET_PERMISSIONS, bridgeAssets: false } }); - const wrapper = useShallowMount(WalletAssetDetails, { store }); + const wrapper = useShallowMount(AssetDetailsTransferable, { store }); const btn = wrapper.find('.asset-details-action.bridge'); expect(btn.exists()).toBeFalse(); @@ -78,7 +78,7 @@ useDescribe('WalletAssetDetails.vue', WalletAssetDetails, () => { it('should not show swap action button', () => { const store = createStore({ permissions: { ...MOCK_WALLET_PERMISSIONS, swapAssets: false } }); - const wrapper = useShallowMount(WalletAssetDetails, { store }); + const wrapper = useShallowMount(AssetDetailsTransferable, { store }); const btn = wrapper.find('.asset-details-action.swap'); expect(btn.exists()).toBeFalse(); @@ -86,7 +86,7 @@ useDescribe('WalletAssetDetails.vue', WalletAssetDetails, () => { it('should not show liquidity action button', () => { const store = createStore({ permissions: { ...MOCK_WALLET_PERMISSIONS, addLiquidity: false } }); - const wrapper = useShallowMount(WalletAssetDetails, { store }); + const wrapper = useShallowMount(AssetDetailsTransferable, { store }); const btn = wrapper.find('.asset-details-action.liquidity'); expect(btn.exists()).toBeFalse(); @@ -94,7 +94,7 @@ useDescribe('WalletAssetDetails.vue', WalletAssetDetails, () => { it('should not show bridge action button', () => { const store = createStore({ permissions: { ...MOCK_WALLET_PERMISSIONS, bridgeAssets: false } }); - const wrapper = useShallowMount(WalletAssetDetails, { store }); + const wrapper = useShallowMount(AssetDetailsTransferable, { store }); const btn = wrapper.find('.asset-details-action.bridge'); expect(btn.exists()).toBeFalse(); diff --git a/tests/unit/components/CreateToken.spec.ts b/tests/unit/components/CreateToken.spec.ts deleted file mode 100644 index 054b9c071..000000000 --- a/tests/unit/components/CreateToken.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import omit from 'lodash/fp/omit'; - -import CreateToken from '@/components/CreateToken.vue'; - -import { useDescribe, useShallowMount, useVuex } from '../../utils'; -import { MOCK_CREATE_TOKEN } from '../../utils/CreateTokenMock'; - -const createStore = () => - useVuex({ - router: { - mutations: { - navigate: jest.fn(), - }, - }, - }); - -useDescribe('CreateToken.vue', CreateToken, () => { - MOCK_CREATE_TOKEN.map((item) => - it(`[${item.title}]: should be rendered correctly`, () => { - const data = omit(['title'], item); - const wrapper = useShallowMount(CreateToken, { - store: createStore(), - data: () => data, - }); - expect(wrapper.element).toMatchSnapshot(); - }) - ); -}); diff --git a/tests/unit/components/TokenLogo.spec.ts b/tests/unit/components/TokenLogo.spec.ts index 77156f7ed..8c310cb1a 100644 --- a/tests/unit/components/TokenLogo.spec.ts +++ b/tests/unit/components/TokenLogo.spec.ts @@ -1,4 +1,4 @@ -import TokenLogo from '@/components/TokenLogo.vue'; +import TokenLogo from '@/components/AssetLogos/TokenLogo.vue'; import { useDescribe, useShallowMount, useVuex } from '../../utils'; import { MOCK_ACCOUNT_ASSETS_NFT, MOCK_WHITE_LIST, MOCK_WHITELIST_IDS_BY_SYMBOL } from '../../utils/mock'; diff --git a/tests/unit/components/__snapshots__/WalletAssetDetails.spec.ts.snap b/tests/unit/components/__snapshots__/AssetDetailsTransferable.spec.ts.snap similarity index 100% rename from tests/unit/components/__snapshots__/WalletAssetDetails.spec.ts.snap rename to tests/unit/components/__snapshots__/AssetDetailsTransferable.spec.ts.snap diff --git a/tests/unit/components/__snapshots__/CreateToken.spec.ts.snap b/tests/unit/components/__snapshots__/CreateToken.spec.ts.snap deleted file mode 100644 index b1a97423a..000000000 --- a/tests/unit/components/__snapshots__/CreateToken.spec.ts.snap +++ /dev/null @@ -1,181 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CreateToken.vue [Confirm NFT Token Step]: should be rendered correctly 1`] = ` - -
- - - - - - -
-
-`; - -exports[`CreateToken.vue [Confirm Simple Token Step]: should be rendered correctly 1`] = ` - -
- - - - - - -
-
-`; - -exports[`CreateToken.vue [Create NFT Token Step]: should be rendered correctly 1`] = ` - -
- - - - - - -
-
-`; - -exports[`CreateToken.vue [Create Step]: should be rendered correctly 1`] = ` - -
- - - - - - -
-
-`; - -exports[`CreateToken.vue [Warn Step]: should be rendered correctly 1`] = ` - -
- - - - - - -
-
-`; diff --git a/tests/unit/components/__snapshots__/WalletSend.spec.ts.snap b/tests/unit/components/__snapshots__/WalletSend.spec.ts.snap index 3ffe532d6..6faa24276 100644 --- a/tests/unit/components/__snapshots__/WalletSend.spec.ts.snap +++ b/tests/unit/components/__snapshots__/WalletSend.spec.ts.snap @@ -45,6 +45,8 @@ exports[`WalletSend.vue [Create Step: Empty Fields]: should be rendered correctl + + + + + + + +