From 195287d1a27fd1db29726ebbe5e186721b20a606 Mon Sep 17 00:00:00 2001 From: Evgeny Boxer Date: Wed, 5 Aug 2020 11:29:50 +1000 Subject: [PATCH] chore: add typings to snxjs --- src/ducks/wallet/walletDetails.ts | 8 +- .../Synths/Overview/SynthInfo/SynthInfo.tsx | 5 +- .../components/OrderBookCard/TradeHistory.tsx | 5 +- src/typings/missing-types.d.ts | 1 - src/typings/synthetix-js.d.ts | 78 +++++++++++++++ src/typings/window.d.ts | 21 ++++ .../{networkUtils.js => networkUtils.ts} | 64 +++++++++---- .../{snxJSConnector.js => snxJSConnector.ts} | 95 +++++++++++++++---- 8 files changed, 227 insertions(+), 50 deletions(-) create mode 100644 src/typings/synthetix-js.d.ts create mode 100644 src/typings/window.d.ts rename src/utils/{networkUtils.js => networkUtils.ts} (68%) rename src/utils/{snxJSConnector.js => snxJSConnector.ts} (71%) diff --git a/src/ducks/wallet/walletDetails.ts b/src/ducks/wallet/walletDetails.ts index 0566cfa5..c130e00f 100644 --- a/src/ducks/wallet/walletDetails.ts +++ b/src/ducks/wallet/walletDetails.ts @@ -4,7 +4,7 @@ import { LOCAL_STORAGE_KEYS } from 'constants/storage'; import { setSigner } from 'utils/snxJSConnector'; import { getAddress } from 'utils/formatters'; -import { defaultNetwork } from 'utils/networkUtils'; +import { defaultNetwork, NetworkId } from 'utils/networkUtils'; import { RootState } from 'ducks/types'; export type WalletDetailsSliceState = { @@ -15,7 +15,7 @@ export type WalletDetailsSliceState = { walletPaginatorIndex: number; availableWallets: string[]; derivationPath: string | null; - networkId: number; + networkId: NetworkId; networkName: string; }; @@ -57,7 +57,7 @@ export const walletDetailsSlice = createSlice({ updateNetworkSettings: ( state, action: PayloadAction<{ - networkId: number; + networkId: NetworkId; networkName: string; }> ) => { @@ -71,7 +71,7 @@ export const walletDetailsSlice = createSlice({ action: PayloadAction<{ signerOptions: { type: string; - networkId: number; + networkId: NetworkId; derivationPath: string; networkName: string; }; diff --git a/src/pages/Synths/Overview/SynthInfo/SynthInfo.tsx b/src/pages/Synths/Overview/SynthInfo/SynthInfo.tsx index eb977a3e..765ff0a3 100644 --- a/src/pages/Synths/Overview/SynthInfo/SynthInfo.tsx +++ b/src/pages/Synths/Overview/SynthInfo/SynthInfo.tsx @@ -22,9 +22,10 @@ import { subtitleLargeCSS } from 'components/Typography/General'; import { getNetworkId } from 'ducks/wallet/walletDetails'; import { getEtherscanTokenLink } from 'utils/explorers'; import { getDecimalPlaces } from 'utils/formatters'; +import { NetworkId } from 'utils/networkUtils'; type StateProps = { - networkId: number; + networkId: NetworkId; }; type Props = { @@ -47,8 +48,8 @@ export const SynthInfo: FC = ({ synth, networkId }) => { const assetDesc = synth.desc.replace(/^Inverse /, ''); const assetSymbol = synth.desc !== synth.asset ? ` (${synth.asset})` : ''; - // @ts-ignore const { snxJS } = snxJSConnector; + // @ts-ignore const contractAddress = snxJS[synth.name].contract.address; const synthSign = USD_SIGN; diff --git a/src/pages/Trade/components/OrderBookCard/TradeHistory.tsx b/src/pages/Trade/components/OrderBookCard/TradeHistory.tsx index 2c287b36..f55bbd08 100644 --- a/src/pages/Trade/components/OrderBookCard/TradeHistory.tsx +++ b/src/pages/Trade/components/OrderBookCard/TradeHistory.tsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import styled from 'styled-components'; import { useTranslation } from 'react-i18next'; import Tooltip from '@material-ui/core/Tooltip'; +import { CellProps } from 'react-table'; import { getNetworkId } from 'ducks/wallet/walletDetails'; @@ -26,10 +27,10 @@ import { formatCurrency, formatCurrencyWithSign, } from 'utils/formatters'; -import { CellProps } from 'react-table'; +import { NetworkId } from 'utils/networkUtils'; type StateProps = { - networkId: number; + networkId: NetworkId; }; type Props = { diff --git a/src/typings/missing-types.d.ts b/src/typings/missing-types.d.ts index f981e64e..479883f0 100644 --- a/src/typings/missing-types.d.ts +++ b/src/typings/missing-types.d.ts @@ -1,2 +1 @@ -declare module 'synthetix-js'; declare module 'synthetix-data'; diff --git a/src/typings/synthetix-js.d.ts b/src/typings/synthetix-js.d.ts new file mode 100644 index 00000000..637b84e0 --- /dev/null +++ b/src/typings/synthetix-js.d.ts @@ -0,0 +1,78 @@ +declare module 'synthetix-js' { + import { JsonRpcSigner, Web3Provider } from 'ethers/providers'; + import { ethers } from 'ethers'; + import { BigNumberish } from 'ethers/utils'; + + export interface ContractSettings { + networkId: 1 | 3 | 4 | 42; + signer?: JsonRpcSignerWithNextAddress; + } + + export type JsonRpcSignerWithNextAddress = JsonRpcSigner & { + getNextAddresses: () => Promise; + }; + export type Synths = { + name: string; + asset: string; + category: string; + sign: string; + desc: string; + aggregator?: string; + subclass?: string; + exchange?: string; + index?: { symbol: string; name: string; units: number }[]; + inverted?: { entryPoint: number; upperLimit: number; lowerLimit: number }; + }[]; + + export type Signers = { + Metamask: JsonRpcSignerWithNextAddress; + Ledger: JsonRpcSignerWithNextAddress; + Coinbase: JsonRpcSignerWithNextAddress; + WalletConnect: JsonRpcSignerWithNextAddress; + Portis: JsonRpcSignerWithNextAddress; + }; + + export class SynthetixJs { + constructor(contractSettings: ContractSettings); + + contractSettings: { + synths: Synths; + signer: JsonRpcSignerWithNextAddress; + provider: Web3Provider; + }; + utils: typeof ethers.utils; + ethers: { utils: typeof ethers.utils }; + static signers: Signers; + binaryOptionsUtils: any; + Exchanger: { + feeRateForExchange: ( + quoteCurrencyKey: string, + baseCurrencyKey: string + ) => Promise; + maxSecsLeftInWaitingPeriod: (address: string, currencyKey: string) => Promise; + }; + SystemStatus: { + synthSuspension: ( + currencyKey: string + ) => Promise<{ + suspended: boolean; + }>; + }; + Synthetix: { + contract: any; + exchange: ( + quoteCurrencyKey: string, + amount: string, + baseCurrencyKey: string, + gasProps?: { + gasPrice: number; + gasLimit: number; + } + ) => Promise; + }; + sUSD: { + contract: any; + approve: any; + }; + } +} diff --git a/src/typings/window.d.ts b/src/typings/window.d.ts new file mode 100644 index 00000000..f73846fe --- /dev/null +++ b/src/typings/window.d.ts @@ -0,0 +1,21 @@ +import { NetworkId } from 'utils/networkUtils'; + +declare global { + interface Window { + web3?: { + eth?: { + net: { + getId: () => NetworkId; + }; + }; + version: { + getNetwork(cb: (err: Error | undefined, networkId: string) => void): void; + network: NetworkId; + }; + }; + ethereum?: { + on: (event: string, cb: () => void) => void; + networkVersion: NetworkId; + }; + } +} diff --git a/src/utils/networkUtils.js b/src/utils/networkUtils.ts similarity index 68% rename from src/utils/networkUtils.js rename to src/utils/networkUtils.ts index 55743772..5a4da3ae 100644 --- a/src/utils/networkUtils.js +++ b/src/utils/networkUtils.ts @@ -1,16 +1,14 @@ import throttle from 'lodash/throttle'; +export type NetworkId = 1 | 3 | 4 | 42; + export const GWEI_UNIT = 1000000000; -export const SUPPORTED_NETWORKS = { +export const SUPPORTED_NETWORKS: Record = { 1: 'MAINNET', 3: 'ROPSTEN', 4: 'RINKEBY', 42: 'KOVAN', - MAINNET: 1, - ROPSTEN: 3, - RINKEBY: 4, - KOVAN: 42, }; export const DEFAULT_GAS_LIMIT = { @@ -25,7 +23,7 @@ export const DEFAULT_GAS_LIMIT = { export const INFURA_PROJECT_ID = process.env.REACT_APP_INFURA_PROJECT_ID; -export const INFURA_JSON_RPC_URLS = { +export const INFURA_JSON_RPC_URLS: Record = { 1: `https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`, 3: `https://ropsten.infura.io/v3/${INFURA_PROJECT_ID}`, 4: `https://rinkeby.infura.io/v3/${INFURA_PROJECT_ID}`, @@ -34,7 +32,15 @@ export const INFURA_JSON_RPC_URLS = { export const PORTIS_APP_ID = '26e198be-a8bb-4240-ad78-ae88579085bc'; -export const SUPPORTED_WALLETS_MAP = { +export type WalletType = + | 'METAMASK' + | 'TREZOR' + | 'LEDGER' + | 'COINBASE' + | 'WALLET_CONNECT' + | 'PORTIS'; + +export const SUPPORTED_WALLETS_MAP: Record = { METAMASK: 'Metamask', TREZOR: 'Trezor', LEDGER: 'Ledger', @@ -44,24 +50,32 @@ export const SUPPORTED_WALLETS_MAP = { }; export const SUPPORTED_WALLETS = Object.values(SUPPORTED_WALLETS_MAP); -export const hasWeb3 = () => { - return window.web3; -}; +export const hasWeb3 = () => !!window.web3; -export const defaultNetwork = { name: 'MAINNET', networkId: 1 }; +export const defaultNetwork: { name: string; networkId: NetworkId } = { + name: 'MAINNET', + networkId: 1, +}; export async function getEthereumNetwork() { - if (!window.web3) return defaultNetwork; - let networkId = 1; + if (!hasWeb3()) { + return defaultNetwork; + } + + let networkId: NetworkId = 1; + try { if (window.web3?.eth?.net) { networkId = await window.web3.eth.net.getId(); - return { name: SUPPORTED_NETWORKS[networkId], networkId: Number(networkId) }; + + return { name: SUPPORTED_NETWORKS[networkId], networkId: Number(networkId) as NetworkId }; } else if (window.web3?.version?.network) { - networkId = Number(window.web3.version.network); + networkId = Number(window.web3.version.network) as NetworkId; + return { name: SUPPORTED_NETWORKS[networkId], networkId }; } else if (window.ethereum?.networkVersion) { - networkId = Number(window.ethereum?.networkVersion); + networkId = Number(window.ethereum?.networkVersion) as NetworkId; + return { name: SUPPORTED_NETWORKS[networkId], networkId }; } return defaultNetwork; @@ -71,12 +85,20 @@ export async function getEthereumNetwork() { } } -export const getTransactionPrice = (gasPrice, gasLimit, ethPrice) => { +export const getTransactionPrice = ( + gasPrice: number | null, + gasLimit: number | null, + ethPrice: number | null +) => { if (!gasPrice || !gasLimit || !ethPrice) return 0; + return (gasPrice * ethPrice * gasLimit) / GWEI_UNIT; }; -const getPriceLimit = (networkInfo, gasPriceLimit) => { +const getPriceLimit = ( + networkInfo: { fast: number; average: number; safeLow: number }, + gasPriceLimit: number +) => { const fast = networkInfo.fast / 10; const average = networkInfo.average / 10; const slow = networkInfo.safeLow / 10; @@ -113,16 +135,16 @@ export const getGasInfo = async () => { } }; -export function onMetamaskAccountChange(cb) { +export function onMetamaskAccountChange(cb: () => void) { if (!window.ethereum) return; const listener = throttle(cb, 1000); window.ethereum.on('accountsChanged', listener); } -export function onMetamaskNetworkChange(cb) { +export function onMetamaskNetworkChange(cb: () => void) { if (!window.ethereum) return; const listener = throttle(cb, 1000); window.ethereum.on('networkChanged', listener); } -export const isMainNet = (networkId) => networkId === SUPPORTED_NETWORKS.MAINNET; +export const isMainNet = (networkId: NetworkId) => networkId === 1; diff --git a/src/utils/snxJSConnector.js b/src/utils/snxJSConnector.ts similarity index 71% rename from src/utils/snxJSConnector.js rename to src/utils/snxJSConnector.ts index 4a71cf8c..a70b1a1c 100644 --- a/src/utils/snxJSConnector.js +++ b/src/utils/snxJSConnector.ts @@ -1,4 +1,5 @@ -import { SynthetixJs } from 'synthetix-js'; +import { SynthetixJs, ContractSettings } from 'synthetix-js'; + import { ethers } from 'ethers'; import { getEthereumNetwork, @@ -6,14 +7,34 @@ import { INFURA_PROJECT_ID, SUPPORTED_WALLETS_MAP, PORTIS_APP_ID, + NetworkId, } from './networkUtils'; import { synthSummaryUtilContract } from './contracts/synthSummaryUtilContract'; import binaryOptionsMarketDataContract from './contracts/binaryOptionsMarketDataContract'; -let snxJSConnector = { +type SnxJSConnector = { + initialized: boolean; + snxJS: SynthetixJs; + synths: SynthetixJs['contractSettings']['synths']; + provider: SynthetixJs['contractSettings']['provider']; + signer: SynthetixJs['contractSettings']['signer']; + signers: typeof SynthetixJs.signers; + utils: SynthetixJs['utils']; + ethers: typeof ethers; + ethersUtils: SynthetixJs['ethers']['utils']; + synthSummaryUtilContract: ethers.Contract; + binaryOptionsMarketDataContract: ethers.Contract; + setContractSettings: (contractSettings: ContractSettings) => void; + binaryOptionsUtils: SynthetixJs['binaryOptionsUtils']; + contractSettings: ContractSettings; +}; + +// @ts-ignore +const snxJSConnector: SnxJSConnector = { initialized: false, signers: SynthetixJs.signers, - setContractSettings: function (contractSettings) { + + setContractSettings: function (contractSettings: ContractSettings) { this.initialized = true; this.snxJS = new SynthetixJs(contractSettings); this.synths = this.snxJS.contractSettings.synths; @@ -37,7 +58,7 @@ let snxJSConnector = { }, }; -const connectToMetamask = async (networkId, networkName) => { +const connectToMetamask = async (networkId: NetworkId, networkName: string) => { const walletState = { walletType: SUPPORTED_WALLETS_MAP.METAMASK, unlocked: false, @@ -68,7 +89,7 @@ const connectToMetamask = async (networkId, networkName) => { } }; -const connectToCoinbase = async (networkId, networkName) => { +const connectToCoinbase = async (networkId: NetworkId, networkName: string) => { const walletState = { walletType: SUPPORTED_WALLETS_MAP.COINBASE, unlocked: false, @@ -99,21 +120,24 @@ const connectToCoinbase = async (networkId, networkName) => { } }; -const connectToHardwareWallet = (networkId, networkName, walletType) => { - return { - walletType, - unlocked: true, - networkId, - networkName: networkName.toLowerCase(), - }; -}; +const connectToHardwareWallet = ( + networkId: NetworkId, + networkName: string, + walletType: string +) => ({ + walletType, + unlocked: true, + networkId, + networkName: networkName.toLowerCase(), +}); -const connectToWalletConnect = async (networkId, networkName) => { +const connectToWalletConnect = async (networkId: NetworkId, networkName: string) => { const walletState = { walletType: SUPPORTED_WALLETS_MAP.WALLET_CONNECT, unlocked: false, }; try { + // @ts-ignore await snxJSConnector.signer.provider._web3Provider.enable(); const accounts = await snxJSConnector.signer.getNextAddresses(); if (accounts && accounts.length > 0) { @@ -134,7 +158,7 @@ const connectToWalletConnect = async (networkId, networkName) => { } }; -const connectToPortis = async (networkId, networkName) => { +const connectToPortis = async (networkId: NetworkId, networkName: string) => { const walletState = { walletType: SUPPORTED_WALLETS_MAP.PORTIS, unlocked: false, @@ -159,7 +183,17 @@ const connectToPortis = async (networkId, networkName) => { } }; -const getSignerConfig = ({ type, networkId, derivationPath, networkName }) => { +const getSignerConfig = ({ + type, + networkId, + derivationPath, + networkName, +}: { + type: string; + networkId: NetworkId; + derivationPath: string; + networkName: string; +}) => { if (type === SUPPORTED_WALLETS_MAP.LEDGER) { const DEFAULT_LEDGER_DERIVATION_PATH = "44'/60'/0'/"; return { derivationPath: derivationPath || DEFAULT_LEDGER_DERIVATION_PATH }; @@ -186,25 +220,46 @@ const getSignerConfig = ({ type, networkId, derivationPath, networkName }) => { return {}; }; -export const setSigner = ({ type, networkId, derivationPath, networkName }) => { +export const setSigner = ({ + type, + networkId, + derivationPath, + networkName, +}: { + type: string; + networkId: NetworkId; + derivationPath: string; + networkName: string; +}) => { + // @ts-ignore const signer = new snxJSConnector.signers[type]( getSignerConfig({ type, networkId, derivationPath, networkName }) ); + snxJSConnector.setContractSettings({ networkId, signer, }); }; -export const connectToWallet = async ({ wallet, derivationPath }) => { - const { name, networkId } = await getEthereumNetwork(); - if (!name) { +export const connectToWallet = async ({ + wallet, + derivationPath, +}: { + wallet: string; + derivationPath: string; +}) => { + const ethereumNetwork = await getEthereumNetwork(); + if (!ethereumNetwork) { return { walletType: '', unlocked: false, unlockError: 'Network not supported', }; } + + const { name, networkId } = ethereumNetwork; + setSigner({ type: wallet, networkId, derivationPath, networkName: name }); switch (wallet) {