From 5e7206e103614be1fbffe993b748afa0575e4736 Mon Sep 17 00:00:00 2001 From: brettkolodny Date: Tue, 12 Jul 2022 17:21:56 -0400 Subject: [PATCH 01/10] refactor: add defined substrate types and token handler --- .../{tokenbalance-mew.ts => assetinfo-mew.ts} | 0 .../libs/assets-handlers/token-mew.ts | 140 ++++++++++++++++++ .../src/providers/ethereum/networks/bsc.ts | 4 +- .../src/providers/ethereum/networks/eth.ts | 4 +- .../src/providers/ethereum/networks/matic.ts | 4 +- .../providers/ethereum/types/erc20-token.ts | 28 ++++ .../providers/ethereum/types/evm-network.ts | 42 ++++-- .../networks/acala/types/acala-orml-asset.ts | 17 ++- .../polkadot/types/substrate-native-token.ts | 4 +- .../polkadot/types/substrate-network.ts | 4 +- .../polkadot/types/substrate-token.ts | 13 ++ packages/extension/src/types/base-network.ts | 3 +- packages/extension/src/types/base-token.ts | 21 ++- 13 files changed, 252 insertions(+), 32 deletions(-) rename packages/extension/src/providers/ethereum/libs/assets-handlers/{tokenbalance-mew.ts => assetinfo-mew.ts} (100%) create mode 100644 packages/extension/src/providers/ethereum/libs/assets-handlers/token-mew.ts create mode 100644 packages/extension/src/providers/ethereum/types/erc20-token.ts create mode 100644 packages/extension/src/providers/polkadot/types/substrate-token.ts 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 100% 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 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..d073e5ff3 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-mew.ts @@ -0,0 +1,140 @@ +import { + CGToken, + SupportedNetwork, + SupportedNetworkNames, + TokenBalance, +} from "./types/tokenbalance-mew"; +import MarketData from "@/libs/market-data"; +import cacheFetch from "@/libs/cache-fetch"; +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 TokenLists 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/"; +const TOKEN_FETCH_TTL = 1000 * 60 * 60; +export default ( + network: BaseNetwork, + address: string +): Promise => { + const supportedNetworks: Record = { + [NetworkNames.Binance]: { + tbName: "bsc", + tokenurl: TokenLists[NetworkNames.Binance], + cgPlatform: networks.bsc.coingeckoID as string, + }, + [NetworkNames.Ethereum]: { + tbName: "eth", + tokenurl: TokenLists[NetworkNames.Ethereum], + cgPlatform: networks.ethereum.coingeckoID as string, + }, + [NetworkNames.Matic]: { + tbName: "matic", + tokenurl: TokenLists[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 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; + }); + + 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]) { + console.log( + "token-mew", + toBN(balances[address].balance).toString() + ); + 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.sort((a, b) => { + // if (a.balanceUSD < b.balanceUSD) return 1; + // else if (a.balanceUSD > b.balanceUSD) return -1; + // else return 0; + // }); + 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/networks/bsc.ts b/packages/extension/src/providers/ethereum/networks/bsc.ts index 135d0ff6f..b617c1a77 100644 --- a/packages/extension/src/providers/ethereum/networks/bsc.ts +++ b/packages/extension/src/providers/ethereum/networks/bsc.ts @@ -1,6 +1,6 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; -import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; +import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; const bscOptions: EvmNetworkOptions = { name: NetworkNames.Binance, @@ -16,7 +16,7 @@ const bscOptions: EvmNetworkOptions = { gradient: "#E6007A", coingeckoID: "binancecoin", basePath: "m/44'/714'", - assetsHandler: tokenbalanceMew, + assetsInfoHandler: tokenbalanceMew, }; const bsc = new EvmNetwork(bscOptions); diff --git a/packages/extension/src/providers/ethereum/networks/eth.ts b/packages/extension/src/providers/ethereum/networks/eth.ts index d8cdc11c0..f50c6e56c 100644 --- a/packages/extension/src/providers/ethereum/networks/eth.ts +++ b/packages/extension/src/providers/ethereum/networks/eth.ts @@ -1,6 +1,6 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; -import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; +import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; import mewNFTHandler from "@/libs/nft-handlers/mew"; const ethOptions: EvmNetworkOptions = { @@ -17,7 +17,7 @@ const ethOptions: EvmNetworkOptions = { gradient: "#8247E5", coingeckoID: "ethereum", NFTHandler: mewNFTHandler, - assetsHandler: tokenbalanceMew, + assetsInfoHandler: tokenbalanceMew, }; const eth = new EvmNetwork(ethOptions); diff --git a/packages/extension/src/providers/ethereum/networks/matic.ts b/packages/extension/src/providers/ethereum/networks/matic.ts index 40a6e9644..a8567c86a 100644 --- a/packages/extension/src/providers/ethereum/networks/matic.ts +++ b/packages/extension/src/providers/ethereum/networks/matic.ts @@ -1,6 +1,6 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; -import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/tokenbalance-mew"; +import tokenbalanceMew from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; import RaribleNFTHandler from "@/libs/nft-handlers/rarible"; const maticOptions: EvmNetworkOptions = { @@ -17,7 +17,7 @@ const maticOptions: EvmNetworkOptions = { gradient: "#53CBC9", coingeckoID: "matic-network", NFTHandler: RaribleNFTHandler, - assetsHandler: tokenbalanceMew, + assetsInfoHandler: tokenbalanceMew, }; const matic = new EvmNetwork(maticOptions); 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..367103a68 --- /dev/null +++ b/packages/extension/src/providers/ethereum/types/erc20-token.ts @@ -0,0 +1,28 @@ +import { BaseToken, BaseTokenOptions, SendOptions } from "@/types/base-token"; +import EvmAPI from "../libs/api"; + +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 getUserBalance(api: EvmAPI, address: string): Promise { + // const contract = new contractsAbi + + return ""; + } + + public async send( + api: EvmAPI, + to: string, + amount: string, + options?: SendOptions + ): Promise {} +} diff --git a/packages/extension/src/providers/ethereum/types/evm-network.ts b/packages/extension/src/providers/ethereum/types/evm-network.ts index f35ce2ade..8c71f8a2f 100644 --- a/packages/extension/src/providers/ethereum/types/evm-network.ts +++ b/packages/extension/src/providers/ethereum/types/evm-network.ts @@ -28,21 +28,34 @@ export interface EvmNetworkOptions { network: BaseNetwork, address: string ) => Promise; - assetsHandler?: ( + assetsInfoHandler?: ( network: BaseNetwork, address: string ) => Promise; + tokensHandler?: ( + 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; + constructor(options: EvmNetworkOptions) { const api = async () => { const api = new API(options.node); @@ -64,17 +77,22 @@ 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; } - 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); diff --git a/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts b/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts index 338119b05..b375deeb9 100644 --- a/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts +++ b/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts @@ -1,6 +1,9 @@ -import { BaseToken, BaseTokenOptions } from "@/types/base-token"; import { ApiPromise } from "@polkadot/api"; import { OrmlTokensAccountData } from "@acala-network/types/interfaces/types-lookup"; +import { SubmittableExtrinsic } from "@polkadot/api/types"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { SubstrateToken } from "@/providers/polkadot/types/substrate-token"; +import { BaseTokenOptions } from "@/types/base-token"; type AssetType = | "token" @@ -13,7 +16,7 @@ interface AcalaOrmlAssetOptions extends BaseTokenOptions { lookupValue: string | number; } -export class AcalaOrmlAsset extends BaseToken { +export class AcalaOrmlAsset extends SubstrateToken { public assetType: AssetType; public lookupValue: string | number; @@ -29,11 +32,17 @@ export class AcalaOrmlAsset extends BaseToken { tokenLookup[this.assetType] = this.lookupValue; return api.query.tokens.accounts(address, tokenLookup).then((res) => { - return (res as unknown as OrmlTokensAccountData).free.toString(); + const balance = (res as unknown as OrmlTokensAccountData).free.toString(); + this.balanceCache = balance; + return balance; }); } - public async send(api: any, to: string, amount: string): Promise { + public async send( + api: ApiPromise, + to: string, + amount: string + ): Promise> { return (api as ApiPromise).tx.balances.transferKeepAlive(to, amount); } } diff --git a/packages/extension/src/providers/polkadot/types/substrate-native-token.ts b/packages/extension/src/providers/polkadot/types/substrate-native-token.ts index 856e751b1..af2ee7010 100644 --- a/packages/extension/src/providers/polkadot/types/substrate-native-token.ts +++ b/packages/extension/src/providers/polkadot/types/substrate-native-token.ts @@ -1,13 +1,13 @@ import { - BaseToken, BaseTokenOptions, SendOptions, TransferType, } from "@/types/base-token"; import { ApiPromise } from "@polkadot/api"; import { AccountInfoWithRefCount } from "@polkadot/types/interfaces"; +import { SubstrateToken } from "./substrate-token"; -export class SubstrateNativeToken extends BaseToken { +export class SubstrateNativeToken extends SubstrateToken { constructor(options: BaseTokenOptions) { super(options); } diff --git a/packages/extension/src/providers/polkadot/types/substrate-network.ts b/packages/extension/src/providers/polkadot/types/substrate-network.ts index 3d2663fb7..1698ddcbb 100644 --- a/packages/extension/src/providers/polkadot/types/substrate-network.ts +++ b/packages/extension/src/providers/polkadot/types/substrate-network.ts @@ -86,8 +86,8 @@ export class SubstrateNetwork extends BaseNetwork { } } - public getAllTokens(): BaseToken[] { - return this.assets; + public getAllTokens(): Promise { + return Promise.resolve(this.assets); } public async getAllTokenInfo(address: string): Promise { diff --git a/packages/extension/src/providers/polkadot/types/substrate-token.ts b/packages/extension/src/providers/polkadot/types/substrate-token.ts new file mode 100644 index 000000000..b3d8de9f3 --- /dev/null +++ b/packages/extension/src/providers/polkadot/types/substrate-token.ts @@ -0,0 +1,13 @@ +import { BaseToken, SendOptions } from "@/types/base-token"; +import { ApiPromise } from "@polkadot/api"; +import { SubmittableExtrinsic } from "@polkadot/api/types"; +import { ISubmittableResult } from "@polkadot/types/types"; + +export abstract class SubstrateToken extends BaseToken { + public abstract send( + api: ApiPromise, + to: string, + amount: string, + options?: SendOptions + ): Promise>; +} diff --git a/packages/extension/src/types/base-network.ts b/packages/extension/src/types/base-network.ts index ef208a84b..1ecfeec61 100644 --- a/packages/extension/src/types/base-network.ts +++ b/packages/extension/src/types/base-network.ts @@ -2,7 +2,6 @@ import EvmAPI from "@/providers/ethereum/libs/api"; import SubstrateAPI from "@/providers/polkadot/libs/api"; import { AssetsType, ProviderName } from "@/types/provider"; import { SignerType } from "@enkryptcom/types"; -// import { BaseToken } from "./base-token"; import { NetworkNames } from "@enkryptcom/types"; import { BaseToken } from "./base-token"; @@ -68,6 +67,6 @@ export abstract class BaseNetwork { this.api = options.api; } - public abstract getAllTokens(): BaseToken[]; + public abstract getAllTokens(address: string): Promise; public abstract getAllTokenInfo(address: string): Promise; } diff --git a/packages/extension/src/types/base-token.ts b/packages/extension/src/types/base-token.ts index 47179f975..e070b65b3 100644 --- a/packages/extension/src/types/base-token.ts +++ b/packages/extension/src/types/base-token.ts @@ -1,6 +1,5 @@ import EvmAPI from "@/providers/ethereum/libs/api"; import MarketData from "@/libs/market-data"; -import { FiatMarket } from "@/libs/market-data/types"; import { ApiPromise } from "@polkadot/api"; import { BN } from "ethereumjs-util"; @@ -17,6 +16,8 @@ export interface BaseTokenOptions { icon: string; coingeckoID?: string; existentialDeposit?: BN; + balance?: string; + price?: string; } export abstract class BaseToken { @@ -26,6 +27,8 @@ export abstract class BaseToken { public icon: string; public coingeckoID: string | undefined; public existentialDeposit: BN | undefined; + public balanceCache?: string; + public priceCache?: string; constructor(options: BaseTokenOptions) { this.name = options.name; @@ -36,10 +39,19 @@ export abstract class BaseToken { this.existentialDeposit = options.existentialDeposit; } - public async getTokenPrice(): Promise { + public async getTokenPrice(): Promise { if (this.coingeckoID) { const market = new MarketData(); - return market.getFiatValue(this.symbol); + market.getMarketData([this.coingeckoID]).then((marketData) => { + const price = marketData[0]?.current_price; + + if (price) { + this.priceCache = price.toString(); + return price.toString(); + } + + return null; + }); } return null; @@ -49,8 +61,9 @@ export abstract class BaseToken { api: EvmAPI | ApiPromise, address: string ): Promise; + public abstract send( - api: any, + api: EvmAPI | ApiPromise, to: string, amount: string, options?: SendOptions From 6b0c9cc9b66bc715c2011d8e74e514c3d758c1d9 Mon Sep 17 00:00:00 2001 From: brettkolodny Date: Tue, 12 Jul 2022 18:34:13 -0400 Subject: [PATCH 02/10] refactor: use BaseToken for substrate send screen --- .../networks/acala/types/acala-orml-asset.ts | 5 +- .../polkadot/types/substrate-native-token.ts | 5 +- .../components/send-token-select.vue | 37 +++------ .../polkadot/ui/send-transaction/index.vue | 81 +++++++++++-------- .../components/assets-select-list-item.vue | 27 +++++-- .../action/views/assets-select-list/index.vue | 4 +- 6 files changed, 92 insertions(+), 67 deletions(-) diff --git a/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts b/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts index b375deeb9..1d4bea355 100644 --- a/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts +++ b/packages/extension/src/providers/polkadot/networks/acala/types/acala-orml-asset.ts @@ -43,6 +43,9 @@ export class AcalaOrmlAsset extends SubstrateToken { to: string, amount: string ): Promise> { - return (api as ApiPromise).tx.balances.transferKeepAlive(to, amount); + const currencyId: Record = {}; + currencyId[this.assetType] = this.lookupValue; + + return (api as ApiPromise).tx.currencies.transfer(to, currencyId, amount); } } diff --git a/packages/extension/src/providers/polkadot/types/substrate-native-token.ts b/packages/extension/src/providers/polkadot/types/substrate-native-token.ts index af2ee7010..5e2f468c9 100644 --- a/packages/extension/src/providers/polkadot/types/substrate-native-token.ts +++ b/packages/extension/src/providers/polkadot/types/substrate-native-token.ts @@ -15,7 +15,10 @@ export class SubstrateNativeToken extends SubstrateToken { public async getUserBalance(api: ApiPromise, address: any): Promise { return api.query.system .account(address) - .then(({ data }) => data.free.toString()); + .then(({ data }) => { + this.balanceCache = data.free.toString(); + return data.free.toString(); + }); } public async send( diff --git a/packages/extension/src/providers/polkadot/ui/send-transaction/components/send-token-select.vue b/packages/extension/src/providers/polkadot/ui/send-transaction/components/send-token-select.vue index d2d0c0040..658d5bf62 100644 --- a/packages/extension/src/providers/polkadot/ui/send-transaction/components/send-token-select.vue +++ b/packages/extension/src/providers/polkadot/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 }}

@@ -14,26 +15,6 @@ - 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/onboard/new-wallet.vue b/packages/extension/src/ui/onboard/new-wallet.vue index 33f1599ea..b4d26d177 100644 --- a/packages/extension/src/ui/onboard/new-wallet.vue +++ b/packages/extension/src/ui/onboard/new-wallet.vue @@ -7,7 +7,6 @@ popular ones, like Ethereum and Polkadot with its Parachains are already built in.

-

As a user you can always add custom network.

And as a developer you can submit an update to our open-source codebase so Enkrypt can start supporting your chain too.