Skip to content
20 changes: 20 additions & 0 deletions packages/extension/src/libs/market-data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ class MarketData {
}
return "0";
}
async getTokenPrice(
coingeckoID: string,
currency = "usd"
): Promise<string | null> {
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
Expand Down
19 changes: 19 additions & 0 deletions packages/extension/src/providers/ethereum/libs/abi/erc20.ts
Original file line number Diff line number Diff line change
@@ -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: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import RivetActivity from "./providers/rivet";
import EtherscanActivity from "./providers/etherscan";
export { RivetActivity, EtherscanActivity };
Original file line number Diff line number Diff line change
@@ -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 };
Original file line number Diff line number Diff line change
@@ -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<EthereumRawInfo[]> => {
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<Activity[]> => {
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);
};
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 };
Original file line number Diff line number Diff line change
@@ -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<EthereumRawInfo[]> => {
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<Activity[]> => {
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);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -18,30 +17,29 @@ 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
): Promise<AssetsType[]> => {
const supportedNetworks: Record<SupportedNetworkNames, SupportedNetwork> = {
[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,
},
};
Expand Down Expand Up @@ -72,19 +70,9 @@ export default (
marketInfo[NATIVE_TOKEN_ADDRESS] = nativeMarket[0];

const assets: AssetsType[] = [];
const tokenInfo: Record<string, CGToken> = await cacheFetch(
{
url: supportedNetworks[networkName].tokenurl,
},
TOKEN_FETCH_TTL
).then((json) => {
const tokens: CGToken[] = json.tokens;
const tObject: Record<string, CGToken> = {};
tokens.forEach((t) => {
tObject[t.address] = t;
});
return tObject;
});
const tokenInfo: Record<string, CGToken> = await getKnownNetworkTokens(
network.name
);

tokenInfo[NATIVE_TOKEN_ADDRESS] = {
chainId: (network as EvmNetwork).chainID,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import cacheFetch from "@/libs/cache-fetch";
import { NetworkNames } from "@enkryptcom/types";
import { SupportedNetworkNames } from "./types/tokenbalance-mew";
const tokenList: Record<SupportedNetworkNames, string> = {
import { CGToken, SupportedNetworkNames } from "./types/tokenbalance-mew";
const TOKEN_FETCH_TTL = 1000 * 60 * 60;
const TokenList: Record<SupportedNetworkNames, string> = {
[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<Record<string, CGToken>> => {
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<string, CGToken> = {};
tokens.forEach((t) => {
tObject[t.address] = t;
});
return tObject;
});
};
export { TokenList, getKnownNetworkTokens };
Loading