diff --git a/packages/extension/src/libs/market-data/index.ts b/packages/extension/src/libs/market-data/index.ts index df572b993..fde2311fb 100644 --- a/packages/extension/src/libs/market-data/index.ts +++ b/packages/extension/src/libs/market-data/index.ts @@ -35,6 +35,26 @@ class MarketData { } return "0"; } + async getTokenPrice( + coingeckoID: string, + currency = "usd" + ): Promise { + const urlParams = new URLSearchParams(); + urlParams.append("ids", coingeckoID); + urlParams.append("vs_currencies", currency); + + return cacheFetch( + { + url: `${COINGECKO_ENDPOINT}simple/price?include_market_cap=false&include_24hr_vol=false&include_24hr_change=false&include_last_updated_at=false&${urlParams.toString()}`, + }, + REFRESH_DELAY + ).then((json) => { + if (json[coingeckoID] && json[coingeckoID][currency] !== undefined) { + return json[coingeckoID][currency].toString(); + } + return null; + }); + } async getMarketInfoByContracts( contracts: string[], platformId: string diff --git a/packages/extension/src/providers/ethereum/libs/abi/erc20.ts b/packages/extension/src/providers/ethereum/libs/abi/erc20.ts index 92edb05a6..8ee9c26f9 100644 --- a/packages/extension/src/providers/ethereum/libs/abi/erc20.ts +++ b/packages/extension/src/providers/ethereum/libs/abi/erc20.ts @@ -1,4 +1,23 @@ export default [ + { + constant: true, + inputs: [ + { + name: "_owner", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + name: "balance", + type: "uint256", + }, + ], + payable: false, + stateMutability: "view", + type: "function", + }, { constant: false, inputs: [ diff --git a/packages/extension/src/providers/ethereum/libs/activity-handlers/index.ts b/packages/extension/src/providers/ethereum/libs/activity-handlers/index.ts new file mode 100644 index 000000000..78276daf5 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/activity-handlers/index.ts @@ -0,0 +1,3 @@ +import RivetActivity from "./providers/rivet"; +import EtherscanActivity from "./providers/etherscan"; +export { RivetActivity, EtherscanActivity }; diff --git a/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/configs.ts b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/configs.ts new file mode 100644 index 000000000..b7975d380 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/configs.ts @@ -0,0 +1,16 @@ +import { NetworkNames } from "@enkryptcom/types"; + +const NetworkEndpoints = { + [NetworkNames.Ethereum]: "https://api.etherscan.io/", + [NetworkNames.Ropsten]: "https://api-ropsten.etherscan.io/", + [NetworkNames.Rinkeby]: "https://api-rinkeby.etherscan.io/", + [NetworkNames.Goerli]: "https://api-goerli.etherscan.io/", + [NetworkNames.Kovan]: "https://api-kovan.etherscan.io/", + [NetworkNames.Binance]: "https://api.bscscan.com/", + [NetworkNames.Matic]: "https://api.polygonscan.com/", + [NetworkNames.Moonbeam]: "https://api-moonbeam.moonscan.io/", + [NetworkNames.Moonriver]: "https://api-moonriver.moonscan.io/", + [NetworkNames.KaruraEVM]: "https://blockscout.karura.network/", +}; + +export { NetworkEndpoints }; diff --git a/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/index.ts b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/index.ts new file mode 100644 index 000000000..f3b1f8db3 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/index.ts @@ -0,0 +1,80 @@ +import cacheFetch from "@/libs/cache-fetch"; +import { EvmNetwork } from "@/providers/ethereum/types/evm-network"; +import { Activity, ActivityStatus, EthereumRawInfo } from "@/types/activity"; +import { BaseNetwork } from "@/types/base-network"; +import { numberToHex } from "web3-utils"; +import { decodeTx } from "../../../transaction/decoder"; +import { NetworkEndpoints } from "./configs"; +import { EtherscanTxType } from "./types"; +const TTL = 30000; +const getAddressActivity = async ( + address: string, + endpoint: string +): Promise => { + return cacheFetch( + { + url: `${endpoint}api?module=account&action=txlist&address=${address}`, + }, + TTL + ).then((res) => { + if (res.status === "0") return []; + const results = res.result as EtherscanTxType[]; + const newResults = results.reverse().map((tx) => { + const rawTx: EthereumRawInfo = { + blockHash: tx.blockHash, + blockNumber: numberToHex(tx.blockNumber), + contractAddress: tx.contractAddress, + data: tx.input, + effectiveGasPrice: numberToHex(tx.gasPrice), + from: tx.from, + to: tx.to === "" ? null : tx.to, + gas: numberToHex(tx.gas), + gasUsed: numberToHex(tx.gasUsed), + nonce: numberToHex(tx.nonce), + status: tx.isError === "0" ? "0x1" : "0x0", + transactionHash: tx.hash, + value: numberToHex(tx.value), + timestamp: parseInt(tx.timeStamp) * 1000, + }; + return rawTx; + }); + return newResults.slice(0, 50) as EthereumRawInfo[]; + }); +}; +export default async ( + network: BaseNetwork, + address: string +): Promise => { + address = address.toLowerCase(); + const enpoint = + NetworkEndpoints[network.name as keyof typeof NetworkEndpoints]; + const activities = await getAddressActivity(address, enpoint); + const Promises = activities.map((activity) => { + return decodeTx(activity, network as EvmNetwork).then((txData) => { + return { + from: activity.from, + to: activity.contractAddress + ? activity.contractAddress + : txData.tokenTo!, + isIncoming: activity.from !== address, + network: network.name, + rawInfo: activity, + status: + activity.status === "0x1" + ? ActivityStatus.success + : ActivityStatus.failed, + timestamp: activity.timestamp ? activity.timestamp : 0, + value: txData.tokenValue, + transactionHash: activity.transactionHash, + token: { + decimals: txData.tokenDecimals, + icon: txData.tokenImage, + name: txData.tokenName, + symbol: txData.tokenSymbol, + price: txData.currentPriceUSD.toString(), + }, + }; + }); + }); + return Promise.all(Promises); +}; diff --git a/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/types.ts b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/types.ts new file mode 100644 index 000000000..d27860237 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/etherscan/types.ts @@ -0,0 +1,16 @@ +export interface EtherscanTxType { + blockNumber: string; + timeStamp: string; + hash: string; + nonce: string; + blockHash: string; + from: string; + to: string; + contractAddress: string; + value: string; + gas: string; + gasPrice: string; + isError: string; + input: string; + gasUsed: string; +} diff --git a/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/rivet/configs.ts b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/rivet/configs.ts new file mode 100644 index 000000000..790142b0b --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/rivet/configs.ts @@ -0,0 +1,11 @@ +import { NetworkNames } from "@enkryptcom/types"; + +const NetworkEndpoints = { + [NetworkNames.Ethereum]: "https://nodes.mewapi.io/rpc/eth", + [NetworkNames.Ropsten]: "https://nodes.mewapi.io/rpc/rop", + [NetworkNames.Rinkeby]: "https://nodes.mewapi.io/rpc/rinkeby", + [NetworkNames.Goerli]: "https://nodes.mewapi.io/rpc/goerli", + [NetworkNames.EthereumClassic]: "https://nodes.mewapi.io/rpc/etc", +}; + +export { NetworkEndpoints }; diff --git a/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/rivet/index.ts b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/rivet/index.ts new file mode 100644 index 000000000..2787823c5 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/activity-handlers/providers/rivet/index.ts @@ -0,0 +1,86 @@ +import { EvmNetwork } from "@/providers/ethereum/types/evm-network"; +import { Activity, ActivityStatus, EthereumRawInfo } from "@/types/activity"; +import { BaseNetwork } from "@/types/base-network"; +import { decodeTx } from "../../../transaction/decoder"; +import { NetworkEndpoints } from "./configs"; +const getAddressActivity = async ( + address: string, + endpoint: string +): Promise => { + const transactions = fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + id: 0, + method: "flume_getTransactionsByParticipant", + params: [address], + }), + }) + .then((res) => res.json()) + .then((res) => res.result.items as EthereumRawInfo[]); + + const transactionsReceipts = fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + id: 0, + method: "flume_getTransactionReceiptsByParticipant", + params: [address], + }), + }) + .then((res) => res.json()) + .then((res) => res.result.items as EthereumRawInfo[]); + return Promise.all([transactions, transactionsReceipts]).then((responses) => { + let allInfo = responses[0].reverse().map((item) => { + const receipt = responses[1].find( + (r) => r.transactionHash === (item as any).hash + ); + if (receipt) return { ...item, ...receipt, data: (item as any).input }; + return null; + }); + allInfo = allInfo.filter((i) => i !== null); + return allInfo.slice(0, 50) as EthereumRawInfo[]; + }); +}; +export default async ( + network: BaseNetwork, + address: string +): Promise => { + address = address.toLowerCase(); + const enpoint = + NetworkEndpoints[network.name as keyof typeof NetworkEndpoints]; + const activities = await getAddressActivity(address, enpoint); + + const Promises = activities.map((activity) => { + return decodeTx(activity, network as EvmNetwork).then((txData) => { + return { + from: activity.from, + to: activity.contractAddress + ? activity.contractAddress + : txData.tokenTo!, + isIncoming: activity.from !== address, + network: network.name, + rawInfo: activity, + status: + activity.status === "0x1" + ? ActivityStatus.success + : ActivityStatus.failed, + timestamp: activity.timestamp ? activity.timestamp : 0, + value: txData.tokenValue, + transactionHash: activity.transactionHash, + token: { + decimals: txData.tokenDecimals, + icon: txData.tokenImage, + name: txData.tokenName, + symbol: txData.tokenSymbol, + price: txData.currentPriceUSD.toString(), + }, + }; + }); + }); + return Promise.all(Promises); +}; diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/tokenbalance-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts similarity index 89% rename from packages/extension/src/providers/ethereum/libs/assets-handlers/tokenbalance-mew.ts rename to packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts index e0dca88c4..f21fa21fa 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/tokenbalance-mew.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts @@ -6,7 +6,6 @@ import { TokenBalance, } from "./types/tokenbalance-mew"; import MarketData from "@/libs/market-data"; -import cacheFetch from "@/libs/cache-fetch"; import { fromBase } from "@/libs/utils/units"; import { toBN } from "web3-utils"; import BigNumber from "bignumber.js"; @@ -18,12 +17,11 @@ import API from "@/providers/ethereum/libs/api"; import Sparkline from "@/libs/sparkline"; import { BaseNetwork } from "@/types/base-network"; import { EvmNetwork } from "../../types/evm-network"; -import TokenLists from "./token-lists"; +import { getKnownNetworkTokens, TokenList } from "./token-lists"; import networks from "../../networks"; import { NetworkNames } from "@enkryptcom/types"; import { NATIVE_TOKEN_ADDRESS } from "../common"; const API_ENPOINT = "https://tokenbalance.mewapi.io/"; -const TOKEN_FETCH_TTL = 1000 * 60 * 60; export default ( network: BaseNetwork, address: string @@ -31,17 +29,17 @@ export default ( const supportedNetworks: Record = { [NetworkNames.Binance]: { tbName: "bsc", - tokenurl: TokenLists[NetworkNames.Binance], + tokenurl: TokenList[NetworkNames.Binance], cgPlatform: networks.bsc.coingeckoID as string, }, [NetworkNames.Ethereum]: { tbName: "eth", - tokenurl: TokenLists[NetworkNames.Ethereum], + tokenurl: TokenList[NetworkNames.Ethereum], cgPlatform: networks.ethereum.coingeckoID as string, }, [NetworkNames.Matic]: { tbName: "matic", - tokenurl: TokenLists[NetworkNames.Matic], + tokenurl: TokenList[NetworkNames.Matic], cgPlatform: networks.matic.coingeckoID as string, }, }; @@ -72,19 +70,9 @@ export default ( marketInfo[NATIVE_TOKEN_ADDRESS] = nativeMarket[0]; const assets: AssetsType[] = []; - const tokenInfo: Record = await cacheFetch( - { - url: supportedNetworks[networkName].tokenurl, - }, - TOKEN_FETCH_TTL - ).then((json) => { - const tokens: CGToken[] = json.tokens; - const tObject: Record = {}; - tokens.forEach((t) => { - tObject[t.address] = t; - }); - return tObject; - }); + const tokenInfo: Record = await getKnownNetworkTokens( + network.name + ); tokenInfo[NATIVE_TOKEN_ADDRESS] = { chainId: (network as EvmNetwork).chainID, diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts index 21041dc5a..910a41fe1 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts @@ -1,10 +1,30 @@ +import cacheFetch from "@/libs/cache-fetch"; import { NetworkNames } from "@enkryptcom/types"; -import { SupportedNetworkNames } from "./types/tokenbalance-mew"; -const tokenList: Record = { +import { CGToken, SupportedNetworkNames } from "./types/tokenbalance-mew"; +const TOKEN_FETCH_TTL = 1000 * 60 * 60; +const TokenList: Record = { [NetworkNames.Binance]: "https://tokens.coingecko.com/binance-smart-chain/all.json", [NetworkNames.Ethereum]: "https://tokens.coingecko.com/ethereum/all.json", [NetworkNames.Matic]: "https://tokens.coingecko.com/polygon-pos/all.json", }; -export default tokenList; +const getKnownNetworkTokens = async ( + networkName: NetworkNames +): Promise> => { + if (!TokenList[networkName as SupportedNetworkNames]) return {}; + return cacheFetch( + { + url: TokenList[networkName as SupportedNetworkNames], + }, + TOKEN_FETCH_TTL + ).then((json) => { + const tokens: CGToken[] = json.tokens; + const tObject: Record = {}; + tokens.forEach((t) => { + tObject[t.address] = t; + }); + return tObject; + }); +}; +export { TokenList, getKnownNetworkTokens }; diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/token-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-mew.ts new file mode 100644 index 000000000..852627db7 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-mew.ts @@ -0,0 +1,118 @@ +import { + CGToken, + SupportedNetwork, + SupportedNetworkNames, + TokenBalance, +} from "./types/tokenbalance-mew"; +import MarketData from "@/libs/market-data"; +import { toBN } from "web3-utils"; +import API from "@/providers/ethereum/libs/api"; +import { BaseNetwork } from "@/types/base-network"; +import { EvmNetwork } from "../../types/evm-network"; +import { getKnownNetworkTokens, TokenList } from "./token-lists"; +import networks from "../../networks"; +import { NetworkNames } from "@enkryptcom/types"; +import { NATIVE_TOKEN_ADDRESS } from "../common"; +import { Erc20Token } from "../../types/erc20-token"; +const API_ENPOINT = "https://tokenbalance.mewapi.io/"; + +export default ( + network: BaseNetwork, + address: string +): Promise => { + const supportedNetworks: Record = { + [NetworkNames.Binance]: { + tbName: "bsc", + tokenurl: TokenList[NetworkNames.Binance], + cgPlatform: networks.bsc.coingeckoID as string, + }, + [NetworkNames.Ethereum]: { + tbName: "eth", + tokenurl: TokenList[NetworkNames.Ethereum], + cgPlatform: networks.ethereum.coingeckoID as string, + }, + [NetworkNames.Matic]: { + tbName: "matic", + tokenurl: TokenList[NetworkNames.Matic], + cgPlatform: networks.matic.coingeckoID as string, + }, + }; + if (!Object.keys(supportedNetworks).includes(network.name)) + throw new Error("TOKENBALANCE-MEW: network not supported"); + const networkName = network.name as SupportedNetworkNames; + const query = `${API_ENPOINT}${supportedNetworks[networkName].tbName}?address=${address}`; + return fetch(query) + .then((res) => res.json()) + .then(async (json) => { + if (json.error) throw new Error(`TOKENBALANCE-MEW: ${json.error}`); + else { + const balances: Record = ( + json.result as TokenBalance[] + ).reduce((obj, cur) => ({ ...obj, [cur.contract]: cur }), {}); + + const marketData = new MarketData(); + const nativeMarket = await marketData.getMarketData( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + [network.coingeckoID!] //it wont be null since all supported networks have coingeckoID + ); + const marketInfo = await marketData.getMarketInfoByContracts( + Object.keys(balances).filter( + (contract) => contract !== NATIVE_TOKEN_ADDRESS + ), + supportedNetworks[networkName].cgPlatform + ); + marketInfo[NATIVE_TOKEN_ADDRESS] = nativeMarket[0]; + + const assets: Erc20Token[] = []; + const tokenInfo: Record = await getKnownNetworkTokens( + network.name + ); + tokenInfo[NATIVE_TOKEN_ADDRESS] = { + chainId: (network as EvmNetwork).chainID, + name: network.name_long, + decimals: 18, + address: NATIVE_TOKEN_ADDRESS, + logoURI: network.icon, + symbol: network.currencyName, + }; + const unknownTokens: string[] = []; + let nativeAsset: Erc20Token | null = null; + for (const [address, market] of Object.entries(marketInfo)) { + if (market && tokenInfo[address]) { + const asset = new Erc20Token({ + balance: toBN(balances[address].balance).toString(), + icon: market.image, + name: market.name, + symbol: market.symbol, + contract: address, + decimals: tokenInfo[address].decimals, + price: market.current_price.toString(), + }); + if (address !== NATIVE_TOKEN_ADDRESS) assets.push(asset); + else nativeAsset = asset; + } else { + unknownTokens.push(address); + } + } + assets.unshift(nativeAsset as Erc20Token); + if (unknownTokens.length && network.api) { + const api = (await network.api()) as API; + const promises = unknownTokens.map((t) => api.getTokenInfo(t)); + await Promise.all(promises).then((tokenInfo) => { + tokenInfo.forEach((tInfo, idx) => { + const asset = new Erc20Token({ + balance: toBN(balances[unknownTokens[idx]].balance).toString(), + icon: nativeMarket[0]?.image || "", + name: tInfo.name, + symbol: tInfo.symbol, + decimals: tInfo.decimals, + contract: address, + }); + assets.push(asset); + }); + }); + } + return assets; + } + }); +}; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/data-decoder.ts b/packages/extension/src/providers/ethereum/libs/transaction/data-decoder.ts index 596ba63ba..4b5de93e5 100644 --- a/packages/extension/src/providers/ethereum/libs/transaction/data-decoder.ts +++ b/packages/extension/src/providers/ethereum/libs/transaction/data-decoder.ts @@ -45,12 +45,20 @@ class DataDecode { try { const params = getParams(sig[0]); const decoded = rawDecode(params, hexToBuffer(this.valueData)); - return { + const decodedData: DataDecodeResponse = { decoded: true, values: decoded.map((a) => toHex(a)), function: sig[0], isToken: this.isTokenAction, }; + if (this.functionSig === tokenSigs.transfer) { + decodedData.tokenValue = decodedData.values[1]; + decodedData.tokenTo = decodedData.values[0]; + } else if (this.functionSig === tokenSigs.transferFrom) { + decodedData.tokenValue = decodedData.values[2]; + decodedData.tokenTo = decodedData.values[1]; + } + return decodedData; } catch (e) { return { decoded: false, diff --git a/packages/extension/src/providers/ethereum/libs/transaction/decoder.ts b/packages/extension/src/providers/ethereum/libs/transaction/decoder.ts index 9360124d7..8c0ace14b 100644 --- a/packages/extension/src/providers/ethereum/libs/transaction/decoder.ts +++ b/packages/extension/src/providers/ethereum/libs/transaction/decoder.ts @@ -1,30 +1,32 @@ import { EvmNetwork } from "../../types/evm-network"; import { DecodedTx, EthereumTransaction } from "./types"; -import TokenLists from "../assets-handlers/token-lists"; -import cacheFetch from "@/libs/cache-fetch"; import { - CGToken, - SupportedNetworkNames, -} from "../assets-handlers/types/tokenbalance-mew"; + getKnownNetworkTokens, + TokenList, +} from "../assets-handlers/token-lists"; +import { SupportedNetworkNames } from "../assets-handlers/types/tokenbalance-mew"; import DataDecode from "./data-decoder"; import { bufferToHex } from "@enkryptcom/utils"; import type EvmApi from "../api"; import MarketData from "@/libs/market-data"; +import { EthereumRawInfo } from "@/types/activity"; const decodeTx = async ( - tx: EthereumTransaction, + tx: EthereumTransaction | EthereumRawInfo, network: EvmNetwork ): Promise => { const isContractCreation = tx.to ? false : true; + let tokenTo = tx.to || null; let tokenName: string = network.currencyName; let tokenValue: string = tx.value && tx.value != "0x" ? tx.value : "0x0"; let tokenDecimals: number = network.decimals; let tokenImage: string = network.icon; + let tokenSymbol: string = network.currencyName; let CGToken: string | undefined = network.coingeckoID; let currentPriceUSD = 0; const marketData = new MarketData(); const dataDecoder = new DataDecode({ data: tx.data as string, - to: tx.to, + to: tx.to!, value: tx.value as string, }); const setInfoFromNetwork = (): Promise => { @@ -32,36 +34,42 @@ const decodeTx = async ( return api.getTokenInfo(tx.to as string).then((tokenInfo) => { tokenName = tokenInfo.name; tokenDecimals = tokenInfo.decimals; - tokenValue = dataDecoder.decode().values[1]; + tokenSymbol = tokenInfo.symbol; + const decodedInfo = dataDecoder.decode(); + if (decodedInfo.tokenValue) { + tokenValue = decodedInfo.tokenValue; + tokenTo = decodedInfo.tokenTo!; + } CGToken = undefined; currentPriceUSD = 0; }); }); }; if (tokenValue === "0x0" && dataDecoder.isTokenAction) { - if (TokenLists[network.name as SupportedNetworkNames]) { - await cacheFetch({ - url: TokenLists[network.name as SupportedNetworkNames], - }).then((json) => { - const tokens: CGToken[] = json.tokens; - const curToken = tokens.find((t) => t.address === tx.to?.toLowerCase()); - if (curToken) { - tokenName = curToken.name; - tokenImage = curToken.logoURI; - tokenDecimals = curToken.decimals; - tokenValue = dataDecoder.decode().values[1]; - return marketData - .getMarketInfoByContracts([tx.to!], network.coingeckoID!) - .then((marketInfo) => { - if (marketInfo[tx.to!]) { - currentPriceUSD = marketInfo[tx.to!]!.current_price; - CGToken = marketInfo[tx.to!]!.id; - } - }); - } else { - return setInfoFromNetwork(); + if (TokenList[network.name as SupportedNetworkNames]) { + const knownTokens = await getKnownNetworkTokens(network.name); + const curToken = knownTokens[tx.to?.toLowerCase() || ""]; + if (curToken) { + tokenName = curToken.name; + tokenImage = curToken.logoURI; + tokenDecimals = curToken.decimals; + tokenSymbol = curToken.symbol; + const decodedInfo = dataDecoder.decode(); + if (decodedInfo.tokenValue) { + tokenValue = decodedInfo.tokenValue; + tokenTo = decodedInfo.tokenTo!; } - }); + await marketData + .getMarketInfoByContracts([tx.to!], network.coingeckoID!) + .then((marketInfo) => { + if (marketInfo[tx.to!]) { + currentPriceUSD = marketInfo[tx.to!]!.current_price; + CGToken = marketInfo[tx.to!]!.id; + } + }); + } else { + await setInfoFromNetwork(); + } } else { await setInfoFromNetwork(); } @@ -74,12 +82,14 @@ const decodeTx = async ( return { isContractCreation, dataHex: bufferToHex(dataDecoder.data), - toAddress: tx.to, + toAddress: tx.to!, decodedHex: dataDecoder.decode().values, tokenDecimals, tokenImage, tokenName, tokenValue, + tokenSymbol, + tokenTo, currentPriceUSD, }; }; diff --git a/packages/extension/src/providers/ethereum/libs/transaction/types.ts b/packages/extension/src/providers/ethereum/libs/transaction/types.ts index f4f30618c..bcf581963 100644 --- a/packages/extension/src/providers/ethereum/libs/transaction/types.ts +++ b/packages/extension/src/providers/ethereum/libs/transaction/types.ts @@ -47,8 +47,10 @@ export interface DecodedTx { currentPriceUSD: number; tokenValue: string; tokenDecimals: number; + tokenSymbol: string; tokenName: string; tokenImage: string; + tokenTo: string | null; dataHex: string; decodedHex?: string[]; } @@ -66,6 +68,8 @@ export interface DataDecodeResponse { decoded: boolean; values: string[]; function?: string; + tokenValue?: string; + tokenTo?: string; isToken: boolean; } diff --git a/packages/extension/src/providers/ethereum/networks/bsc.ts b/packages/extension/src/providers/ethereum/networks/bsc.ts index 135d0ff6f..3dced02bf 100644 --- a/packages/extension/src/providers/ethereum/networks/bsc.ts +++ b/packages/extension/src/providers/ethereum/networks/bsc.ts @@ -1,6 +1,8 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; -import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; +import assetsInfoHandler from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; +import tokensHandler from "@/providers/ethereum/libs/assets-handlers/token-mew"; +import { EtherscanActivity } from "../libs/activity-handlers"; const bscOptions: EvmNetworkOptions = { name: NetworkNames.Binance, @@ -16,7 +18,9 @@ const bscOptions: EvmNetworkOptions = { gradient: "#E6007A", coingeckoID: "binancecoin", basePath: "m/44'/714'", - assetsHandler: tokenbalanceMew, + assetsInfoHandler, + tokensHandler, + activityHandler: EtherscanActivity, }; const bsc = new EvmNetwork(bscOptions); diff --git a/packages/extension/src/providers/ethereum/networks/etc.ts b/packages/extension/src/providers/ethereum/networks/etc.ts index 72f97e5d8..0f3d931f2 100644 --- a/packages/extension/src/providers/ethereum/networks/etc.ts +++ b/packages/extension/src/providers/ethereum/networks/etc.ts @@ -1,5 +1,6 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; +import { RivetActivity } from "../libs/activity-handlers"; const etcOptions: EvmNetworkOptions = { name: NetworkNames.EthereumClassic, @@ -10,11 +11,12 @@ const etcOptions: EvmNetworkOptions = { chainID: 61, isTestNetwork: false, currencyName: "ETC", - node: "wss://www.ethercluster.com/ws-etc", + node: "wss://nodes.mewapi.io/ws/etc", icon: require("./icons/etc.svg"), gradient: "#53CBC9", basePath: "m/44'/61'/0'/0", coingeckoID: "ethereum-classic", + activityHandler: RivetActivity, }; const etc = new EvmNetwork(etcOptions); diff --git a/packages/extension/src/providers/ethereum/networks/eth.ts b/packages/extension/src/providers/ethereum/networks/eth.ts index ec76614f5..4766e7075 100644 --- a/packages/extension/src/providers/ethereum/networks/eth.ts +++ b/packages/extension/src/providers/ethereum/networks/eth.ts @@ -1,7 +1,9 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; -import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; +import assetsInfoHandler from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; +import tokensHandler from "@/providers/ethereum/libs/assets-handlers/token-mew"; import mewNFTHandler from "@/libs/nft-handlers/mew"; +import { EtherscanActivity } from "../libs/activity-handlers"; const ethOptions: EvmNetworkOptions = { name: NetworkNames.Ethereum, @@ -17,7 +19,9 @@ const ethOptions: EvmNetworkOptions = { gradient: "#8247E5", coingeckoID: "ethereum", NFTHandler: mewNFTHandler, - assetsHandler: tokenbalanceMew, + assetsInfoHandler, + tokensHandler, + activityHandler: EtherscanActivity, }; const eth = new EvmNetwork(ethOptions); diff --git a/packages/extension/src/providers/ethereum/networks/goerli.ts b/packages/extension/src/providers/ethereum/networks/goerli.ts index f99c2ecff..5b5395a13 100644 --- a/packages/extension/src/providers/ethereum/networks/goerli.ts +++ b/packages/extension/src/providers/ethereum/networks/goerli.ts @@ -1,5 +1,6 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; +import { RivetActivity } from "../libs/activity-handlers"; const goerliOptions: EvmNetworkOptions = { name: NetworkNames.Goerli, @@ -14,6 +15,7 @@ const goerliOptions: EvmNetworkOptions = { icon: require("./icons/eth.svg"), gradient: "#C4C4C4", coingeckoID: "ethereum", + activityHandler: RivetActivity, }; const goerli = new EvmNetwork(goerliOptions); diff --git a/packages/extension/src/providers/ethereum/networks/karura.ts b/packages/extension/src/providers/ethereum/networks/karura.ts index 266439cd7..0e1d2ce7b 100644 --- a/packages/extension/src/providers/ethereum/networks/karura.ts +++ b/packages/extension/src/providers/ethereum/networks/karura.ts @@ -1,4 +1,5 @@ import { NetworkNames } from "@enkryptcom/types"; +import { EtherscanActivity } from "../libs/activity-handlers"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; const karuraOptions: EvmNetworkOptions = { @@ -14,6 +15,7 @@ const karuraOptions: EvmNetworkOptions = { icon: require("./icons/karura-evm.svg"), gradient: "#FF4C3B", coingeckoID: "karura", + activityHandler: EtherscanActivity, }; const karura = new EvmNetwork(karuraOptions); diff --git a/packages/extension/src/providers/ethereum/networks/kov.ts b/packages/extension/src/providers/ethereum/networks/kov.ts index 8b30bfe34..2a32cf535 100644 --- a/packages/extension/src/providers/ethereum/networks/kov.ts +++ b/packages/extension/src/providers/ethereum/networks/kov.ts @@ -1,5 +1,6 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; +import { EtherscanActivity } from "../libs/activity-handlers"; const kovOptions: EvmNetworkOptions = { name: NetworkNames.Kovan, @@ -13,6 +14,7 @@ const kovOptions: EvmNetworkOptions = { node: "wss://nodes.mewapi.io/ws/kovan", icon: require("./icons/eth.svg"), gradient: "#E6007A", + activityHandler: EtherscanActivity, }; const kov = new EvmNetwork(kovOptions); diff --git a/packages/extension/src/providers/ethereum/networks/matic.ts b/packages/extension/src/providers/ethereum/networks/matic.ts index 40a6e9644..aa8e6afda 100644 --- a/packages/extension/src/providers/ethereum/networks/matic.ts +++ b/packages/extension/src/providers/ethereum/networks/matic.ts @@ -1,7 +1,9 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; -import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; +import assetsInfoHandler from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; +import tokensHandler from "@/providers/ethereum/libs/assets-handlers/token-mew"; import RaribleNFTHandler from "@/libs/nft-handlers/rarible"; +import { EtherscanActivity } from "../libs/activity-handlers"; const maticOptions: EvmNetworkOptions = { name: NetworkNames.Matic, @@ -17,7 +19,9 @@ const maticOptions: EvmNetworkOptions = { gradient: "#53CBC9", coingeckoID: "matic-network", NFTHandler: RaribleNFTHandler, - assetsHandler: tokenbalanceMew, + assetsInfoHandler, + tokensHandler, + activityHandler: EtherscanActivity, }; const matic = new EvmNetwork(maticOptions); diff --git a/packages/extension/src/providers/ethereum/networks/moonbeam.ts b/packages/extension/src/providers/ethereum/networks/moonbeam.ts index 35f9a42f7..5051cfa4c 100644 --- a/packages/extension/src/providers/ethereum/networks/moonbeam.ts +++ b/packages/extension/src/providers/ethereum/networks/moonbeam.ts @@ -1,4 +1,5 @@ import { NetworkNames } from "@enkryptcom/types"; +import { EtherscanActivity } from "../libs/activity-handlers"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; const moonbeamOptions: EvmNetworkOptions = { @@ -14,6 +15,7 @@ const moonbeamOptions: EvmNetworkOptions = { icon: require("./icons/moonbeam.svg"), gradient: "#8247E5", coingeckoID: "moonbeam", + activityHandler: EtherscanActivity, }; const moonbeam = new EvmNetwork(moonbeamOptions); diff --git a/packages/extension/src/providers/ethereum/networks/moonriver.ts b/packages/extension/src/providers/ethereum/networks/moonriver.ts index 941def5b9..e6f103a0b 100644 --- a/packages/extension/src/providers/ethereum/networks/moonriver.ts +++ b/packages/extension/src/providers/ethereum/networks/moonriver.ts @@ -1,4 +1,5 @@ import { NetworkNames } from "@enkryptcom/types"; +import { EtherscanActivity } from "../libs/activity-handlers"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; const moonriverOptions: EvmNetworkOptions = { @@ -14,6 +15,7 @@ const moonriverOptions: EvmNetworkOptions = { icon: require("./icons/moonriver.svg"), gradient: "#f2b606", coingeckoID: "moonriver", + activityHandler: EtherscanActivity, }; const moonriver = new EvmNetwork(moonriverOptions); diff --git a/packages/extension/src/providers/ethereum/networks/rin.ts b/packages/extension/src/providers/ethereum/networks/rin.ts index 83a07f636..6d4243c08 100644 --- a/packages/extension/src/providers/ethereum/networks/rin.ts +++ b/packages/extension/src/providers/ethereum/networks/rin.ts @@ -1,4 +1,5 @@ import { NetworkNames } from "@enkryptcom/types"; +import { RivetActivity } from "../libs/activity-handlers"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; const rinOptions: EvmNetworkOptions = { @@ -13,6 +14,7 @@ const rinOptions: EvmNetworkOptions = { node: "wss://nodes.mewapi.io/ws/rinkeby", icon: require("./icons/eth.svg"), gradient: "#C4C4C4", + activityHandler: RivetActivity, }; const rin = new EvmNetwork(rinOptions); diff --git a/packages/extension/src/providers/ethereum/networks/rop.ts b/packages/extension/src/providers/ethereum/networks/rop.ts index 17480ef61..9dfbe0864 100644 --- a/packages/extension/src/providers/ethereum/networks/rop.ts +++ b/packages/extension/src/providers/ethereum/networks/rop.ts @@ -1,5 +1,6 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; +import { RivetActivity } from "../libs/activity-handlers"; const ropOptions: EvmNetworkOptions = { name: NetworkNames.Ropsten, @@ -14,6 +15,7 @@ const ropOptions: EvmNetworkOptions = { icon: require("./icons/eth.svg"), basePath: "m/44'/1'/0'/0", gradient: "#E6007A", + activityHandler: RivetActivity, }; const rop = new EvmNetwork(ropOptions); diff --git a/packages/extension/src/providers/ethereum/types/erc20-token.ts b/packages/extension/src/providers/ethereum/types/erc20-token.ts new file mode 100644 index 000000000..ca2b9e52f --- /dev/null +++ b/packages/extension/src/providers/ethereum/types/erc20-token.ts @@ -0,0 +1,37 @@ +import { BaseToken, BaseTokenOptions } from "@/types/base-token"; +import { BN } from "ethereumjs-util"; +import { numberToHex } from "web3-utils"; +import erc20 from "../libs/abi/erc20"; +import EvmAPI from "../libs/api"; +import { NATIVE_TOKEN_ADDRESS } from "../libs/common"; + +interface Erc20TokenOptions extends BaseTokenOptions { + contract: string; +} + +export class Erc20Token extends BaseToken { + public contract: string; + + constructor(options: Erc20TokenOptions) { + super(options); + this.contract = options.contract; + } + + public async getLatestUserBalance( + api: EvmAPI, + address: string + ): Promise { + if (this.contract === NATIVE_TOKEN_ADDRESS) return api.getBalance(address); + else { + const contract = new api.web3.eth.Contract(erc20 as any, this.contract); + return contract.methods + .balanceOf(address) + .call() + .then((val: BN) => numberToHex(val)); + } + } + + public async send(): Promise { + throw new Error("EVM-send is not implemented here"); + } +} diff --git a/packages/extension/src/providers/ethereum/types/evm-network.ts b/packages/extension/src/providers/ethereum/types/evm-network.ts index f35ce2ade..a016aa3f2 100644 --- a/packages/extension/src/providers/ethereum/types/evm-network.ts +++ b/packages/extension/src/providers/ethereum/types/evm-network.ts @@ -1,5 +1,6 @@ import { formatFloatingPointValue } from "@/libs/utils/number-formatter"; import { fromBase } from "@/libs/utils/units"; +import { Activity } from "@/types/activity"; import { BaseNetwork } from "@/types/base-network"; import { BaseToken } from "@/types/base-token"; import { NFTCollection } from "@/types/nft"; @@ -28,21 +29,43 @@ export interface EvmNetworkOptions { network: BaseNetwork, address: string ) => Promise; - assetsHandler?: ( + assetsInfoHandler?: ( network: BaseNetwork, address: string ) => Promise; + tokensHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; + activityHandler: ( + network: BaseNetwork, + address: string + ) => Promise; } export class EvmNetwork extends BaseNetwork { public chainID: number; - private assetsHandler: - | ((network: BaseNetwork, address: string) => Promise) - | undefined; - private NFTHandler: - | ((network: BaseNetwork, address: string) => Promise) - | undefined; + private assetsInfoHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; + + tokensHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; + + private NFTHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; + + private activityHandler: ( + network: BaseNetwork, + address: string + ) => Promise; + constructor(options: EvmNetworkOptions) { const api = async () => { const api = new API(options.node); @@ -64,17 +87,23 @@ export class EvmNetwork extends BaseNetwork { super(baseOptions); this.chainID = options.chainID; - this.assetsHandler = options.assetsHandler; + this.assetsInfoHandler = options.assetsInfoHandler; + this.tokensHandler = options.tokensHandler; this.NFTHandler = options.NFTHandler; + this.activityHandler = options.activityHandler; } - public getAllTokens(): BaseToken[] { - return []; + public getAllTokens(address: string): Promise { + if (this.tokensHandler) { + return this.tokensHandler(this, address); + } + + return Promise.resolve([]); } public async getAllTokenInfo(address: string): Promise { - if (this.assetsHandler) { - return this.assetsHandler(this, address); + if (this.assetsInfoHandler) { + return this.assetsInfoHandler(this, address); } else { const api = await this.api(); const balance = await (api as API).getBalance(address); @@ -98,4 +127,7 @@ export class EvmNetwork extends BaseNetwork { return [nativeAsset]; } } + public getAllActivity(address: string): Promise { + return this.activityHandler(this, address); + } } diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue index 1a1cb52a0..665b98442 100644 --- a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue @@ -6,7 +6,8 @@
{{ token.name }}

- {{ token.balancef }} {{ token.symbol }} + {{ balance ? $filters.formatFloatingPointValue(balance).value : "~" }} + {{ token.symbol }}

@@ -17,20 +18,28 @@ diff --git a/packages/extension/src/ui/action/views/network-activity/components/network-activity-total.vue b/packages/extension/src/ui/action/views/network-activity/components/network-activity-total.vue index 59a63b391..1f1e089de 100644 --- a/packages/extension/src/ui/action/views/network-activity/components/network-activity-total.vue +++ b/packages/extension/src/ui/action/views/network-activity/components/network-activity-total.vue @@ -7,12 +7,6 @@ - - - - - diff --git a/packages/extension/src/ui/action/views/network-assets/index.vue b/packages/extension/src/ui/action/views/network-assets/index.vue index 7e8866ce1..8dade0fdf 100644 --- a/packages/extension/src/ui/action/views/network-assets/index.vue +++ b/packages/extension/src/ui/action/views/network-assets/index.vue @@ -11,10 +11,7 @@ :symbol="network.currencyName" /> - + { - console.log("depositAction"); -}; -const buyAction = () => { - console.log("buyAction"); -}; const updateAssets = () => { isLoading.value = true; props.network diff --git a/packages/extension/src/ui/action/views/network-nfts/components/network-nfts-category-sort-menu.vue b/packages/extension/src/ui/action/views/network-nfts/components/network-nfts-category-sort-menu.vue index 9c771763c..c543631ce 100644 --- a/packages/extension/src/ui/action/views/network-nfts/components/network-nfts-category-sort-menu.vue +++ b/packages/extension/src/ui/action/views/network-nfts/components/network-nfts-category-sort-menu.vue @@ -11,12 +11,6 @@ - - - - - - - -