diff --git a/src/composables/currencies.ts b/src/composables/currencies.ts index da146ccd2..3e58ee30f 100644 --- a/src/composables/currencies.ts +++ b/src/composables/currencies.ts @@ -66,7 +66,7 @@ export function useCurrencies({ function getCoinGeckoCoinIdList() { return protocolsInUse.value.map( - (protocol) => ProtocolAdapterFactory.getAdapter(protocol).getCoinGeckoCoinId(), + (protocol) => ProtocolAdapterFactory.getAdapter(protocol).coinGeckoCoinId, ).join(','); } diff --git a/src/constants/common.ts b/src/constants/common.ts index 404fd104d..666a17510 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -13,6 +13,7 @@ export const PROTOCOLS = { aeternity: 'aeternity', bitcoin: 'bitcoin', ethereum: 'ethereum', + solana: 'solana', } as const; export const PROTOCOL_LIST = Object.values(PROTOCOLS); diff --git a/src/icons/coin/bitcoin.svg b/src/icons/coin/bitcoin.svg index 0b74984b5..9b9b3925f 100644 --- a/src/icons/coin/bitcoin.svg +++ b/src/icons/coin/bitcoin.svg @@ -1,11 +1,4 @@ - - - - - - - - - - \ No newline at end of file + + + diff --git a/src/icons/coin/dogecoin.svg b/src/icons/coin/dogecoin.svg index f849ffd03..6dc531adc 100644 --- a/src/icons/coin/dogecoin.svg +++ b/src/icons/coin/dogecoin.svg @@ -1,133 +1,126 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/coin/ethereum.svg b/src/icons/coin/ethereum.svg index d9c14d318..e698fe42f 100644 --- a/src/icons/coin/ethereum.svg +++ b/src/icons/coin/ethereum.svg @@ -1,15 +1,8 @@ - - - - - - - - - - - - - + + + + + + diff --git a/src/icons/coin/solana.svg b/src/icons/coin/solana.svg new file mode 100644 index 000000000..9ceb89463 --- /dev/null +++ b/src/icons/coin/solana.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/popup/components/AssetIcon.vue b/src/popup/components/AssetIcon.vue index 12d8ab78b..953421cc9 100644 --- a/src/popup/components/AssetIcon.vue +++ b/src/popup/components/AssetIcon.vue @@ -36,6 +36,7 @@ import { AE_AVATAR_URL } from '@/protocols/aeternity/config'; import AeternityIcon from '@/icons/coin/aeternity.svg?vue-component'; import BitcoinIcon from '@/icons/coin/bitcoin.svg?vue-component'; import EthereumIcon from '@/icons/coin/ethereum.svg?vue-component'; +import SolanaIcon from '@/icons/coin/solana.svg?vue-component'; import LexonTokenIcon from '@/icons/tokens/ct_xtk8rSz9suPb6D6VLquyfVji25FcnFRDjn3dnn5mmvHsPiESt.svg?vue-component'; const SIZES = [ICON_SIZES.sm, ICON_SIZES.rg, ICON_SIZES.md, ICON_SIZES.lg, ICON_SIZES.xxl] as const; @@ -46,6 +47,7 @@ const COIN_ICONS: Record = { [PROTOCOLS.aeternity]: AeternityIcon, [PROTOCOLS.bitcoin]: BitcoinIcon, [PROTOCOLS.ethereum]: EthereumIcon, + [PROTOCOLS.solana]: SolanaIcon, }; const ASSET_ICONS: Dictionary = { diff --git a/src/popup/components/Modals/AccountCreate.vue b/src/popup/components/Modals/AccountCreate.vue index b42912935..2c2b36a63 100644 --- a/src/popup/components/Modals/AccountCreate.vue +++ b/src/popup/components/Modals/AccountCreate.vue @@ -58,7 +58,7 @@ export default defineComponent({ setup(props) { const { addRawAccount, setActiveAccountByGlobalIdx } = useAccounts(); const { isOnline } = useConnection(); - const { openModal } = useModals(); + const { openModal, openDefaultModal } = useModals(); const { setLoaderVisible } = useUi(); async function createAccount(protocol: Protocol) { @@ -82,7 +82,7 @@ export default defineComponent({ break; default: - throw new Error(`createAccount not implemented for protocol: ${protocol}`); + openDefaultModal({ icon: 'alert', msg: `Account creation not possible for protocol: ${protocol}` }); } setLoaderVisible(false); props.resolve(); diff --git a/src/popup/components/ProtocolIcon.vue b/src/popup/components/ProtocolIcon.vue index c95da560c..2fafff127 100644 --- a/src/popup/components/ProtocolIcon.vue +++ b/src/popup/components/ProtocolIcon.vue @@ -16,9 +16,11 @@ import { } from 'vue'; import { ICON_SIZES, PROTOCOLS } from '@/constants'; import type { Protocol } from '@/types'; + import AeternityLogo from '@/icons/logo/aeternity.svg?vue-component'; import BitcoinIcon from '@/icons/coin/bitcoin.svg?vue-component'; import EthereumIcon from '@/icons/coin/ethereum.svg?vue-component'; +import SolanaIcon from '@/icons/coin/solana.svg?vue-component'; const SIZES = [ICON_SIZES.xs, ICON_SIZES.md, ICON_SIZES.rg, ICON_SIZES.lg, ICON_SIZES.xl] as const; @@ -41,6 +43,7 @@ export default defineComponent({ [PROTOCOLS.aeternity]: AeternityLogo, [PROTOCOLS.bitcoin]: BitcoinIcon, [PROTOCOLS.ethereum]: EthereumIcon, + [PROTOCOLS.solana]: SolanaIcon, }; const selectedIcon = computed((): Component => iconsMap[props.protocol]); diff --git a/src/popup/components/ProtocolSpecificView.vue b/src/popup/components/ProtocolSpecificView.vue index dca17aa30..bc92cdc35 100644 --- a/src/popup/components/ProtocolSpecificView.vue +++ b/src/popup/components/ProtocolSpecificView.vue @@ -27,6 +27,14 @@ import { defineComponent, ref, } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; +import { + onIonViewDidEnter, + onIonViewDidLeave, + onIonViewWillEnter, + onIonViewWillLeave, +} from '@ionic/vue'; + import type { WalletRouteMeta, Protocol, @@ -41,14 +49,8 @@ import { ProtocolAdapterFactory } from '@/lib/ProtocolAdapterFactory'; import aeternityViews from '@/protocols/aeternity/views'; import bitcoinViews from '@/protocols/bitcoin/views'; import ethereumViews from '@/protocols/ethereum/views'; +import solanaViews from '@/protocols/solana/views'; -import { useRoute, useRouter } from 'vue-router'; -import { - onIonViewDidEnter, - onIonViewDidLeave, - onIonViewWillEnter, - onIonViewWillLeave, -} from '@ionic/vue'; import InfoBox from './InfoBox.vue'; /** @@ -58,6 +60,7 @@ const views: Record = { aeternity: aeternityViews, bitcoin: bitcoinViews, ethereum: ethereumViews, + solana: solanaViews, }; export default defineComponent({ diff --git a/src/popup/pages/Assets/AssetDetailsInfo.vue b/src/popup/pages/Assets/AssetDetailsInfo.vue index bc4be1188..fef553ca6 100644 --- a/src/popup/pages/Assets/AssetDetailsInfo.vue +++ b/src/popup/pages/Assets/AssetDetailsInfo.vue @@ -231,7 +231,7 @@ export default defineComponent({ const tokenBalance = computed(() => sharedAssetDetails.tokenBalance); const isCoin = computed(() => isAssetCoin(assetContractId.value)); const decimals = computed(() => assetData.value?.decimals || tokenBalance.value?.decimals); - const coinGeckoLinkUrl = computed(() => `https://www.coingecko.com/en/coins/${adapter.value.getCoinGeckoCoinId()}`); + const coinGeckoLinkUrl = computed(() => `https://www.coingecko.com/en/coins/${adapter.value.coinGeckoCoinId}`); const coinGeckoLinkLabel = computed(() => coinGeckoLinkUrl.value.replace('https://', '').replace('en/coins', '...')); const poolShare = computed(() => { diff --git a/src/protocols/BaseProtocolAdapter.ts b/src/protocols/BaseProtocolAdapter.ts index 58e3173fd..b91bd4196 100644 --- a/src/protocols/BaseProtocolAdapter.ts +++ b/src/protocols/BaseProtocolAdapter.ts @@ -39,6 +39,8 @@ export abstract class BaseProtocolAdapter { abstract coinPrecision: number; + abstract coinGeckoCoinId: string; + /** * Defines if the protocol supports fungible tokens (token contracts). */ @@ -57,8 +59,6 @@ export abstract class BaseProtocolAdapter { abstract getUrlTokenKey(): string; - abstract getCoinGeckoCoinId(): string; - abstract getDefaultCoin( marketData?: MarketData, convertedBalance?: number | BigNumber, diff --git a/src/protocols/aeternity/libs/AeternityAdapter.ts b/src/protocols/aeternity/libs/AeternityAdapter.ts index 2455274f4..2089027d5 100644 --- a/src/protocols/aeternity/libs/AeternityAdapter.ts +++ b/src/protocols/aeternity/libs/AeternityAdapter.ts @@ -81,6 +81,8 @@ export class AeternityAdapter extends BaseProtocolAdapter { override coinPrecision = AE_COIN_PRECISION; + override coinGeckoCoinId = AE_COINGECKO_COIN_ID; + override hasTokensSupport = true; override mdwToNodeApproxDelayTime = AE_MDW_TO_NODE_APPROX_DELAY_TIME; @@ -128,10 +130,6 @@ export class AeternityAdapter extends BaseProtocolAdapter { return AE_SYMBOL; } - override getCoinGeckoCoinId() { - return AE_COINGECKO_COIN_ID; - } - override getDefaultCoin( marketData?: MarketData, convertedBalance?: number, diff --git a/src/protocols/bitcoin/libs/BitcoinAdapter.ts b/src/protocols/bitcoin/libs/BitcoinAdapter.ts index 37a557f04..079b94478 100644 --- a/src/protocols/bitcoin/libs/BitcoinAdapter.ts +++ b/src/protocols/bitcoin/libs/BitcoinAdapter.ts @@ -69,6 +69,8 @@ export class BitcoinAdapter extends BaseProtocolAdapter { override coinPrecision = BTC_COIN_PRECISION; + override coinGeckoCoinId = BTC_COINGECKO_COIN_ID; + override hasTokensSupport = false; override mdwToNodeApproxDelayTime = 0; @@ -115,10 +117,6 @@ export class BitcoinAdapter extends BaseProtocolAdapter { return BTC_NETWORK_DEFAULT_SETTINGS[networkType]; } - override getCoinGeckoCoinId() { - return BTC_COINGECKO_COIN_ID; - } - override getDefaultCoin( marketData?: MarketData, convertedBalance?: number, diff --git a/src/protocols/ethereum/libs/EthereumAdapter.ts b/src/protocols/ethereum/libs/EthereumAdapter.ts index fb487b5cd..bdfb124be 100644 --- a/src/protocols/ethereum/libs/EthereumAdapter.ts +++ b/src/protocols/ethereum/libs/EthereumAdapter.ts @@ -84,6 +84,8 @@ export class EthereumAdapter extends BaseProtocolAdapter { override coinPrecision = ETH_COIN_PRECISION; + override coinGeckoCoinId = ETH_COINGECKO_COIN_ID; + override hasTokensSupport = true; override mdwToNodeApproxDelayTime = ETH_MDW_TO_NODE_APPROX_DELAY_TIME; @@ -124,10 +126,6 @@ export class EthereumAdapter extends BaseProtocolAdapter { return 9; } - override getCoinGeckoCoinId() { - return ETH_COINGECKO_COIN_ID; - } - override getExplorer() { const { ethActiveNetworkPredefinedSettings } = useEthNetworkSettings(); return new EtherscanExplorer(ethActiveNetworkPredefinedSettings.value.explorerUrl!); @@ -492,7 +490,7 @@ export class EthereumAdapter extends BaseProtocolAdapter { }; } - async constructAndSignTx( + override async constructAndSignTx( amount: number, recipient: string, options: Record, diff --git a/src/protocols/registerAdapters.ts b/src/protocols/registerAdapters.ts index 2deb5c6c1..b61556ba6 100644 --- a/src/protocols/registerAdapters.ts +++ b/src/protocols/registerAdapters.ts @@ -4,12 +4,14 @@ import { ProtocolAdapterFactory } from '@/lib/ProtocolAdapterFactory'; import { AeternityAdapter } from '@/protocols/aeternity/libs/AeternityAdapter'; import { BitcoinAdapter } from '@/protocols/bitcoin/libs/BitcoinAdapter'; import { EthereumAdapter } from '@/protocols/ethereum/libs/EthereumAdapter'; +import { SolanaAdapter } from '@/protocols/solana/libs/SolanaAdapter'; import { BaseProtocolAdapter } from './BaseProtocolAdapter'; const protocolAdapters: Record> = { [PROTOCOLS.aeternity]: AeternityAdapter, [PROTOCOLS.bitcoin]: BitcoinAdapter, [PROTOCOLS.ethereum]: EthereumAdapter, + [PROTOCOLS.solana]: SolanaAdapter, }; Object.entries(protocolAdapters).forEach(([protocol, adapter]) => { diff --git a/src/protocols/solana/config.ts b/src/protocols/solana/config.ts new file mode 100644 index 000000000..319118ed7 --- /dev/null +++ b/src/protocols/solana/config.ts @@ -0,0 +1,8 @@ +export const SOL_PROTOCOL_NAME = 'Solana'; + +export const SOL_CONTRACT_ID = 'solana'; + +export const SOL_COIN_SYMBOL = 'SOL'; +export const SOL_COIN_PRECISION = 18; // Amount of decimals + +export const SOL_COINGECKO_COIN_ID = 'solana'; diff --git a/src/protocols/solana/libs/SolanaAdapter.ts b/src/protocols/solana/libs/SolanaAdapter.ts new file mode 100644 index 000000000..60bac2d54 --- /dev/null +++ b/src/protocols/solana/libs/SolanaAdapter.ts @@ -0,0 +1,164 @@ +/* eslint-disable class-methods-use-this */ + +import { PROTOCOLS } from '@/constants'; +import type { + AdapterNetworkSettingList, + IAccount, + ICoin, + IFetchTransactionResult, + INetworkProtocolSettings, + IToken, + ITokenBalance, + ITransaction, + ITransferResponse, + MarketData, +} from '@/types'; +import { BaseProtocolAdapter } from '@/protocols/BaseProtocolAdapter'; +import { + SOL_COIN_PRECISION, + SOL_COIN_SYMBOL, + SOL_COINGECKO_COIN_ID, + SOL_CONTRACT_ID, + SOL_PROTOCOL_NAME, +} from '@/protocols/solana/config'; + +export class SolanaAdapter extends BaseProtocolAdapter { + override protocol = PROTOCOLS.solana; + + override protocolName = SOL_PROTOCOL_NAME; + + override coinName = SOL_PROTOCOL_NAME; + + override coinSymbol = SOL_COIN_SYMBOL; + + override coinContractId = SOL_CONTRACT_ID; + + override coinPrecision = SOL_COIN_PRECISION; + + override coinGeckoCoinId = SOL_COINGECKO_COIN_ID; + + override hasTokensSupport = true; + + override mdwToNodeApproxDelayTime = 0; // TODO + + private networkSettings: AdapterNetworkSettingList = [ + // TODO + ]; + + override getAccountPrefix() { + return ''; // TODO + } + + override getAmountPrecision(): number { + return 0; // TODO + } + + override getExplorer() { + return {} as any; // TODO + } + + override getUrlTokenKey() { + return SOL_CONTRACT_ID; + } + + override getDefaultCoin( + marketData?: MarketData, + convertedBalance?: number, + ): ICoin { + return { + ...(marketData?.[PROTOCOLS.solana]! || {} as MarketData), + protocol: PROTOCOLS.solana, + contractId: this.coinContractId, + symbol: this.coinSymbol, + decimals: this.coinPrecision, + name: this.coinName, + convertedBalance, + }; + } + + override getNetworkSettings() { + return this.networkSettings; + } + + override getNetworkTypeDefaultValues(): INetworkProtocolSettings { + return {} as any; // TODO + } + + override async fetchBalance(): Promise { + return '0'; // TODO + } + + override isAccountAddressValid() { + return false; // TODO + } + + override isValidAddressOrNameEncoding() { + return false; // TODO + } + + override async isAccountUsed(): Promise { + return false; // TODO + } + + override getHdWalletAccountFromMnemonicSeed() { + return {} as any; // TODO + } + + override resolveAccountRaw(): IAccount | null { + return null; // TODO + } + + override async discoverLastUsedAccountIndex(): Promise { + return 0; // TODO + } + + override async fetchAvailableTokens(): Promise { + return []; // TODO + } + + override async fetchAccountTokenBalances(): Promise { + return []; // TODO + } + + override async fetchTokenInfo(): Promise { + return undefined; // TODO + } + + override async transferPreparedTransaction(): Promise { + return {} as any; // TODO + } + + override async transferToken(): Promise { + return {} as any; // TODO + } + + override async fetchTransactionByHash(): Promise { + return {} as any; // TODO + } + + override async fetchAccountTransactions(): Promise { + return { + regularTransactions: [], // TODO + paginationParams: {}, // TODO + }; + } + + override async fetchAccountAssetTransactions(): Promise { + return { + regularTransactions: [], // TODO + paginationParams: {}, // TODO + }; + } + + override async constructAndSignTx(): Promise { + return {} as any; // TODO + } + + override async spend(): Promise { + return {} as any; // TODO + } + + override async waitTransactionMined(): Promise { + return null; // TODO + } +} diff --git a/src/protocols/solana/views/index.ts b/src/protocols/solana/views/index.ts new file mode 100644 index 000000000..4523380d8 --- /dev/null +++ b/src/protocols/solana/views/index.ts @@ -0,0 +1,12 @@ +import type { ProtocolViewsConfig } from '@/types'; + +const protocolViews: ProtocolViewsConfig = { + AccountDetails: null, + AccountDetailsTokens: null, + AccountDetailsNames: null, + TransactionDetails: null, + TransferReceiveModal: null, + TransferSendModal: null, +}; + +export default protocolViews;