import { createConnection , TextDocuments , Diagnostic , DiagnosticSeverity , ProposedFeatures , InitializeParams , DidChangeConfigurationNotification , CompletionItem , CompletionItemKind , TextDocumentPositionParams , TextDocumentSyncKind , InitializeResult , CodeLens , CodeLensParams , CodeAction , CodeActionKind , CodeActionParams , CodeActionContext , Command , WorkspaceEdit , HoverParams , Hover , MarkedString , MarkupContent , MarkupKind } from 'vscode-languageserver' ; import { TextDocument , Range , TextEdit } from 'vscode-languageserver-textdocument' ; let connection = createConnection ( ProposedFeatures . all ) ; let documents = new TextDocuments ( TextDocument ) ; let hasConfigurationCapability = false ; let hasWorkspaceFolderCapability = false ; let hasDiagnosticRelatedInformationCapability = false ; const NAME = 'Ethereum DeFi Language Support' ; const DIAGNOSTIC_TYPE_NOT_VALID_ADDRESS = 'NotValidAddress' ; const DIAGNOSTIC_TYPE_NOT_CHECKSUM_ADDRESS = 'NotChecksumAddress' ; const DIAGNOSTIC_TYPE_CONVERT_TO_ENS_NAME = 'ConvertENSName' ; const DIAGNOSTIC_TYPE_CONVERT_FROM_ENS_NAME = 'ConvertFromENSName' ; const DIAGNOSTIC_TYPE_GENERATE_ABI = 'GenerateABI' ; const CODE_LENS_TYPE_ETH_ADDRESS = 'EthAddress' ; const CODE_LENS_TYPE_ETH_PRIVATE_KEY = 'EthPrivateKey' ; const MAINNET = 'mainnet' ; const ROPSTEN = 'ropsten' ; const KOVAN = 'kovan' ; const RINKEBY = 'rinkeby' ; const GOERLI = 'goerli' ; const NETWORKS = [ MAINNET , ROPSTEN , KOVAN , RINKEBY , GOERLI ] ; const DEFAULT_TOKEN_LIST_JSON_URL = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org' ; var Web3 = require ( 'web3' ) ; var web3 = new Web3 ( ) ; var ENS = require ( 'ethereum-ens' ) ; var Wallet = require ( 'ethereumjs-wallet' ) ; var EthUtil = require ( 'ethereumjs-util' ) ; const { indexOfRegex , lastIndexOfRegex } = require ( 'index-of-regex' ) ; var request = require ( "request-promise" ) ; var BigNumber = require ( 'big-number' ) ; var web3provider ; var ens ; var amberdataApiKeySetting = "" ; var etherscanApiKeySetting = "" ; var cacheDurationSetting ; var tokenListUrlSetting = "" ; let ensCache = new Map ( ) ; let ensReverseCache = new Map ( ) ; let tokenCache = new Map ( ) ; let topTokensCache = new Map ( ) ; let addressMarkdownCache = new Map ( ) ; let abiCache = new Map ( ) ; connection . onInitialize ( ( params ) => { let capabilities = params . capabilities ; hasConfigurationCapability = ! ! ( capabilities . workspace && ! ! capabilities . workspace . configuration ) ; hasWorkspaceFolderCapability = ! ! ( capabilities . workspace && ! ! capabilities . workspace . workspaceFolders ) ; hasDiagnosticRelatedInformationCapability = ! ! ( capabilities . textDocument && capabilities . textDocument . publishDiagnostics && capabilities . textDocument . publishDiagnostics . relatedInformation ) ; const result = { capabilities : { textDocumentSync : TextDocumentSyncKind . Incremental , completionProvider : { resolveProvider : true } , codeLensProvider : { resolveProvider : true } , codeActionProvider : { codeActionKinds : [ CodeActionKind . QuickFix ] } , hoverProvider : { workDoneProgress : false } } } ; if ( hasWorkspaceFolderCapability ) { result . capabilities . workspace = { workspaceFolders : { supported : true } } ; } return result ; } ) ; connection . onInitialized ( ( ) => { if ( hasConfigurationCapability ) { connection . client . register ( DidChangeConfigurationNotification . type , undefined ) ; } if ( hasWorkspaceFolderCapability ) { connection . workspace . onDidChangeWorkspaceFolders ( _event => { connection . console . log ( 'Workspace folder change event received.' ) ; } ) ; } } ) ; interface DefiSettings { maxNumberOfProblems : number ; infuraProjectId : string ; infuraProjectSecret : string ; amberdataApiKey : string ; etherscanApiKey : string ; cacheDuration : number ; tokenListJsonUrl : string ; } const defaultSettings = { maxNumberOfProblems : 100 , infuraProjectId : "" , infuraProjectSecret : "" , amberdataApiKey : "" , etherscanApiKey : "" , cacheDuration : 60000 , tokenListJsonUrl : DEFAULT_TOKEN_LIST_JSON_URL } ; let globalSettings = defaultSettings ; let documentSettings = new Map ( ) ; connection . onDidChangeConfiguration ( change => { if ( hasConfigurationCapability ) { documentSettings . clear ( ) ; } else { globalSettings = < DefiSettings > ( ( change . settings . defi || defaultSettings ) ) ; } ensCache . clear ( ) ; ensReverseCache . clear ( ) ; tokenCache . clear ( ) ; topTokensCache . clear ( ) ; addressMarkdownCache . clear ( ) ; documents . all ( ) . forEach ( validateTextDocument ) ; } ) ; function getDocumentSettings ( resource ) { if ( ! hasConfigurationCapability ) { return Promise . resolve ( globalSettings ) ; } let result = documentSettings . get ( resource ) ; if ( ! result ) { result = connection . workspace . getConfiguration ( { scopeUri : resource , section : 'defi' } ) ; documentSettings . set ( resource , result ) ; } return result ; } documents . onDidClose ( e => { documentSettings . delete ( e . document . uri ) ; } ) ; documents . onDidChangeContent ( change => { validateTextDocument ( change . document ) ; } ) ; async function validateTextDocument ( textDocument ) { let settings = await getDocumentSettings ( textDocument . uri ) ; if ( settings . infuraProjectId === "" || settings . infuraProjectId === "" ) { connection . console . warn ( "Infura project ID and/or secret has not been set. Obtain them from https://infura.io/ and set them in the VS Code settings by searching for \"Infura\"." ) ; } else { web3provider = new Web3 . providers . HttpProvider ( 'https://:' + settings . infuraProjectSecret + '@mainnet.infura.io/v3/' + settings . infuraProjectId ) ; ens = new ENS ( web3provider ) ; connection . console . info ( "Using Infura for web3." ) ; } if ( settings . amberdataApiKey === "" ) { connection . console . warn ( "Amberdata.io API key has not been set. Obtain one from https://amberdata.io/ and set the in them VS Code settings by searching for \"Amberdata\"." ) ; } else { amberdataApiKeySetting = settings . amberdataApiKey ; if ( web3provider === undefined || ens === undefined ) { web3provider = new Web3 . providers . HttpProvider ( 'https://rpc.web3api.io?x-api-key=' + amberdataApiKeySetting ) ; ens = new ENS ( web3provider ) ; connection . console . info ( "Using Amberdata for web3." ) ; } connection . console . info ( "Using Amberdata for address and token data." ) ; } if ( settings . etherscanApiKey === "" ) { connection . console . warn ( "Etherscan.io API key has not been set. Obtain one from https://etherscan.io/ and set the in them VS Code settings by searching for \"Etherscan\"." ) ; } else { etherscanApiKeySetting = settings . etherscanApiKey ; connection . console . info ( "Using Etherscan for contract ABIs." ) ; } if ( settings . tokenListJsonUrl === "" ) { connection . console . error ( "No Token List has been configured.  Configure a Token List URL in VS Code settings for Ethereum DeFi." ) ; } else { tokenListUrlSetting = settings . tokenListJsonUrl ; connection . console . info ( "Using Token List from " + tokenListUrlSetting ) ; } if ( web3provider === undefined ) { connection . console . error ( "No web3 connection has been configured.  Configure Infura and/or Amberdata keys in VS Code settings for Ethereum DeFi." ) ; } if ( settings . cacheDuration === undefined || settings . cacheDuration < 0 ) { cacheDurationSetting = 60000 ; } else { cacheDurationSetting = settings . cacheDuration ; } let diagnostics = [ ] ; let possibleEthereumAddresses = findPossibleEthereumAddresses ( textDocument ) ; let problems = 0 ; for ( var i = 0 ; i < possibleEthereumAddresses . length ; i ++ ) { let element = possibleEthereumAddresses [ i ] ; if ( problems < settings . maxNumberOfProblems ) { problems ++ ; if ( ! isValidEthereumAddress ( element . content ) ) { addDiagnostic ( element , `${ element . content } is not a valid Ethereum address` , 'The string appears to be an Ethereum address but fails checksum.' , DiagnosticSeverity . Error , DIAGNOSTIC_TYPE_NOT_VALID_ADDRESS ) ; } else { var checksumAddress = web3 . utils . toChecksumAddress ( element . content ) ; if ( element . content != checksumAddress ) { addDiagnostic ( element , `${ element . content } is not a checksum address` , 'Use a checksum address as a best practice to ensure the address is valid.' , DiagnosticSeverity . Warning , DIAGNOSTIC_TYPE_NOT_CHECKSUM_ADDRESS + checksumAddress ) ; } let ensName = await reverseENSLookup ( element . content ) ; if ( ensName !== "" ) { addDiagnostic ( element , `${ element . content } can be converted to its ENS name \"${ ensName }\"` , 'Convert the Ethereum address to its ENS name.' , DiagnosticSeverity . Hint , DIAGNOSTIC_TYPE_CONVERT_TO_ENS_NAME + ensName ) ; } if ( await isContract ( element . content ) ) { addDiagnostic ( element , `Generate contract ABI` , 'Highlight this address and use the tooltip to generate contract ABI from Etherscan' , DiagnosticSeverity . Hint , DIAGNOSTIC_TYPE_GENERATE_ABI + element . content ) ; } } } } let possibleEnsNames = findPossibleENSNames ( textDocument ) ; for ( var i = 0 ; i < possibleEnsNames . length ; i ++ ) { let element = possibleEnsNames [ i ] ; if ( problems < settings . maxNumberOfProblems ) { let ensAddress = await ENSLookup ( element . content ) ; if ( ensAddress !== "" ) { problems ++ ; addDiagnostic ( element , `${ element . content } can be converted to its Ethereum address` , 'Convert the ENS name to its Ethereum address.' , DiagnosticSeverity . Hint , DIAGNOSTIC_TYPE_CONVERT_FROM_ENS_NAME + ensAddress ) ; } } } connection . sendDiagnostics ( { uri : textDocument . uri , diagnostics } ) ; function addDiagnostic ( element , message , details , severity , code ) { let diagnostic = { severity : severity , range : element . range , message : message , source : NAME , code : code } ; if ( hasDiagnosticRelatedInformationCapability ) { diagnostic . relatedInformation = [ { location : { uri : textDocument . uri , range : Object . assign ( { } , diagnostic . range ) } , message : details } ] ; } diagnostics . push ( diagnostic ) ; } } async function isContract ( address ) { if ( web3provider === undefined ) { return false ; } let result = false ; let web3connection = new Web3 ( web3provider ) ; let code = await web3connection . eth . getCode ( address ) . then ( function ( code ) { if ( code !== "0x" ) { result = true ; } } ) . catch ( ( e ) => connection . console . log ( "Could not check whether address " + address + " is a contract due to error: " + e ) ) ; return result ; } connection . onDidChangeWatchedFiles ( _change => { connection . console . log ( 'We received an file change event' ) ; } ) ; export interface Token { name : string ; symbol : string ; address : string ; marketData : MarketData | undefined ; lastUpdated : Number ; } export interface MarketData { description : string | undefined ; marketCap : string | undefined ; price : string | undefined ; circulatingSupply : string | undefined ; totalSupply : string | undefined ; tradeVolume : string | undefined ; } async function getTopTokens ( ) { if ( topTokensCache . size > 0 ) { for ( var entry of topTokensCache . entries ( ) ) { var value = entry [ 1 ] ; if ( value . lastUpdated > ( Date . now ( ) - cacheDurationSetting ) ) { return topTokensCache ; } } } let topTokensMap = new Map ( ) ; var tokensListOpts = { method : 'GET' , url : tokenListUrlSetting } ; await request ( tokensListOpts , async function ( error , response , body ) { if ( error ) { connection . console . log ( "Request error getting tokens list: " + error ) ; return ; } var result = JSON . parse ( body ) ; if ( result !== undefined && result . tokens !== undefined ) { result . tokens . forEach ( ( element ) => { if ( element . chainId !== undefined && element . chainId == "1" ) { let token = { name : element . name , symbol : element . symbol , address : getChecksumAddress ( element . address ) , marketData : undefined , lastUpdated : Date . now ( ) } ; topTokensMap . set ( token . address , token ) ; } } ) ; } } ) . catch ( ( error ) => { connection . console . log ( "Error getting tokens list: " + error ) ; } ) ; topTokensCache = topTokensMap ; return topTokensMap ; } connection . onCompletion ( async ( _textDocumentPosition ) : Promise < CompletionItem [ ] > => { let completionItems = [ ] ; let tokens = await getTopTokens ( ) ; for ( var entry of tokens . entries ( ) ) { var address = entry [ 0 ] , token = entry [ 1 ] ; let textEdit = { range : { start : _textDocumentPosition . position , end : _textDocumentPosition . position } , newText : getChecksumAddress ( token . address ) } ; let completionItem = { label : `Token: ${ token . name } (${ token . symbol })` , kind : CompletionItemKind . Value , data : token , textEdit : textEdit } ; completionItems . push ( completionItem ) ; } { let snippet = "const token = new Token(ChainId.MAINNET, '0xc0FFee0000000000000000000000000000000000', 18, 'HOT', 'Caffeine')\n" ; let imports = "import { ChainId, Token, TokenAmount, Pair, Trade, TradeType, Route } from '@uniswap/sdk'\n" ; insertSnippet ( _textDocumentPosition , snippet , completionItems , imports , "DeFi: Uniswap SDK - Token" , 0 ) ; } { let snippet = "const HOT = new Token(ChainId.MAINNET, '0xc0FFee0000000000000000000000000000000000', 18, 'HOT', 'Caffeine')\n" + "const NOT = new Token(ChainId.MAINNET, '0xDeCAf00000000000000000000000000000000000', 18, 'NOT', 'Caffeine')\n" + "\n" + "const pair = new Pair(new TokenAmount(HOT, '2000000000000000000'), new TokenAmount(NOT, '1000000000000000000'))\n" ; let imports = "import { ChainId, Token, TokenAmount, Pair, Trade, TradeType, Route } from '@uniswap/sdk'\n" ; insertSnippet ( _textDocumentPosition , snippet , completionItems , imports , "DeFi: Uniswap SDK - Pair" , 1 ) ; } { let snippet = "const HOT = new Token(ChainId.MAINNET, '0xc0FFee0000000000000000000000000000000000', 18, 'HOT', 'Caffeine')\n" + "const NOT = new Token(ChainId.MAINNET, '0xDeCAf00000000000000000000000000000000000', 18, 'NOT', 'Caffeine')\n" + "const HOT_NOT = new Pair(new TokenAmount(HOT, '2000000000000000000'), new TokenAmount(NOT, '1000000000000000000'))\n" + "\n" + "const route = new Route([HOT_NOT], NOT)\n" ; let imports = "import { ChainId, Token, TokenAmount, Pair, Trade, TradeType, Route } from '@uniswap/sdk'\n" ; insertSnippet ( _textDocumentPosition , snippet , completionItems , imports , "DeFi: Uniswap SDK - Route" , 2 ) ; } { let snippet = "const HOT = new Token(ChainId.MAINNET, '0xc0FFee0000000000000000000000000000000000', 18, 'HOT', 'Caffeine')\n" + "const NOT = new Token(ChainId.MAINNET, '0xDeCAf00000000000000000000000000000000000', 18, 'NOT', 'Caffeine')\n" + "const HOT_NOT = new Pair(new TokenAmount(HOT, '2000000000000000000'), new TokenAmount(NOT, '1000000000000000000'))\n" + "const NOT_TO_HOT = new Route([HOT_NOT], NOT)\n" + "\n" + "const trade = new Trade(NOT_TO_HOT, new TokenAmount(NOT, '1000000000000000'), TradeType.EXACT_INPUT)\n" ; let imports = "import { ChainId, Token, TokenAmount, Pair, Trade, TradeType, Route } from '@uniswap/sdk'\n" ; insertSnippet ( _textDocumentPosition , snippet , completionItems , imports , "DeFi: Uniswap SDK - Trade" , 3 ) ; } { let snippet = "const ptokens = new pTokens({\n" + "	pbtc: {\n" + "		ethPrivateKey: 'Eth private key',\n" + "		ethProvider: 'Eth provider',\n" + "		btcNetwork: 'testnet',  //'testnet' or 'bitcoin', default 'testnet'\n" + "		defaultEndpoint: 'https://......' //optional\n" + "	}\n" + "})\n" ; let imports = "import { pTokens } from 'ptokens'\n" ; insertSnippet ( _textDocumentPosition , snippet , completionItems , imports , "DeFi: pTokens SDK" , 4 ) ; } { let snippet = "if (window.web3) {\n" + "	const ptokens = new pTokens({\n" + "		pbtc: {\n" + "			ethProvider: window.web3.currentProvider,\n" + "			btcNetwork: 'bitcoin'\n" + "		}\n" + "	})\n" + "} else {\n" + "	console.log('No web3 detected')\n" + "}\n" ; let imports = "import { pTokens } from 'ptokens'\n" ; insertSnippet ( _textDocumentPosition , snippet , completionItems , imports , "DeFi: pTokens SDK - web3" , 5 ) ; } { let snippet = "const depositAddress = await ptokens.pbtc.getDepositAddress(ethAddress)\n" + "console.log(depositAddress.toString())\n" + "\n" + "//fund the BTC address just generated (not ptokens.js stuff)\n" + "\n" + "depositAddress.waitForDeposit()\n" + "	.once('onBtcTxBroadcasted', tx => ... )\n" + "	.once('onBtcTxConfirmed', tx => ...)\n" + "	.once('onNodeReceivedTx', tx => ...)\n" + "	.once('onNodeBroadcastedTx', tx => ...)\n" + "	.once('onEthTxConfirmed', tx => ...)\n" + "	.then(res => ...))\n" ; let imports = "import { pTokens } from 'ptokens'\n" ; insertSnippet ( _textDocumentPosition , snippet , completionItems , imports , "DeFi: pTokens SDK - pBTC deposit address" , 6 ) ; } { let snippet = "const ERC_20_ABI = [{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"balance\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"}];" ; insertSnippet ( _textDocumentPosition , snippet , completionItems , undefined , "DeFi: ERC20 Contract ABI" , 7 ) ; } return completionItems ; } ) ; function insertSnippet ( _textDocumentPosition , snippetText , completionItems , imports , label , sortOrder ) { let textEdit = { range : { start : _textDocumentPosition . position , end : _textDocumentPosition . position } , newText : snippetText } ; let completionItem = { label : label , kind : CompletionItemKind . Snippet , data : undefined , textEdit : textEdit , sortText : String ( sortOrder ) } ; let textDocument = documents . get ( _textDocumentPosition . textDocument . uri ) ; let textDocumentContents = textDocument ?. getText ( ) ; if ( imports !== undefined && ( textDocumentContents === undefined || ! String ( textDocumentContents ) . includes ( imports ) ) ) { let additionalTextEdit = { range : { start : { line : 0 , character : 0 } , end : { line : 0 , character : 0 } } , newText : imports } ; completionItem . additionalTextEdits = [ additionalTextEdit ] ; } completionItems . push ( completionItem ) ; } function getChecksumAddress ( address ) { try { return web3 . utils . toChecksumAddress ( address ) ; } catch ( e ) { connection . console . log ( "Error getting checksum address: " + e ) ; return address ; } } connection . onCompletionResolve ( async ( item ) : Promise < CompletionItem > => { if ( < Token > item . data !== undefined ) { let token = item . data ; item . detail = `${ token . address }` ; let markdown = { kind : MarkupKind . Markdown , value : await getMarkdownForToken ( token ) } ; item . documentation = markdown ; } return item ; } ) ; export interface StringLocation { range : Range ; content : string ; } function numberFormat ( amount , decimals , unknownIfZero ) { if ( unknownIfZero && Number ( amount ) == 0 ) { return "?" ; } const numberFormat = new Intl . NumberFormat ( 'en-US' , { minimumFractionDigits : decimals } ) ; try { return numberFormat . format ( Number ( amount ) ) ; } catch ( e ) { return amount ; } } function dollarFormat ( amount , unknownIfZero ) { if ( unknownIfZero && Number ( amount ) == 0 ) { return "?" ; } const dollarFormat = new Intl . NumberFormat ( 'en-US' , { style : 'currency' , currency : 'USD' , minimumFractionDigits : 2 } ) ; const subDollarFormat = new Intl . NumberFormat ( 'en-US' , { style : 'currency' , currency : 'USD' , minimumFractionDigits : 8 } ) ; try { if ( Number ( amount ) < 0.01 ) { return subDollarFormat . format ( Number ( amount ) ) ; } else { return dollarFormat . format ( Number ( amount ) ) ; } } catch ( e ) { return amount ; } } async function getMarkdownForToken ( token ) { if ( token . marketData === undefined || token . lastUpdated <= ( Date . now ( ) - cacheDurationSetting ) ) { var tokenMarketOptions = { method : 'GET' , url : 'https://api.coingecko.com/api/v3/coins/ethereum/contract/' + token . address } ; await request ( tokenMarketOptions , async function ( error , response , body ) { if ( error ) { connection . console . log ( "Request error getting token market information from Coingecko: " + error ) ; return ; } var result = JSON . parse ( body ) ; if ( result !== undefined && result . market_data !== undefined ) { let marketData = { description : result . description . en , price : result . market_data . current_price . usd , tradeVolume : result . market_data . total_volume . usd , circulatingSupply : result . market_data . circulating_supply , totalSupply : result . market_data . total_supply , marketCap : result . market_data . market_cap . usd } ; token . marketData = marketData ; } } ) . catch ( ( error ) => { connection . console . log ( "Error getting token market information from Coingecko: " + error ) ; } ) ; } var buf = `**Token: ${ token . name } (${ token . symbol })**\n\n` ; if ( token . marketData !== undefined ) { if ( token . marketData . price !== undefined ) { buf += `**Price:** ${ dollarFormat ( token . marketData . price , true ) } USD  \n` ; } if ( token . marketData . marketCap !== undefined ) { buf += `**Market Cap:** ${ dollarFormat ( token . marketData . marketCap , true ) } USD  \n` ; } if ( token . marketData . circulatingSupply !== undefined ) { buf += `**Circulating Supply:** ${ numberFormat ( token . marketData . circulatingSupply , 0 , true ) }  \n` ; } if ( token . marketData . totalSupply !== undefined ) { buf += `**Total Supply:** ${ numberFormat ( token . marketData . totalSupply , 0 , true ) }  \n` ; } if ( token . marketData . tradeVolume !== undefined ) { buf += `**Trading Volume (Daily):** ${ dollarFormat ( token . marketData . tradeVolume , true ) } USD  \n` ; } if ( token . marketData . description !== undefined ) { buf += `\n${ token . marketData . description }  \n` ; } } return buf ; } function findPossibleEthereumAddresses ( textDocument ) { let text = textDocument . getText ( ) ; let pattern = /0x[0-9a-fA-F]{40}\b/g ; let m ; let problems = 0 ; let locations = [ ] ; while ( ( m = pattern . exec ( text ) ) && problems < 100 ) { let location = { range : { start : textDocument . positionAt ( m . index ) , end : textDocument . positionAt ( m . index + m [ 0 ] . length ) } , content : m [ 0 ] } ; locations . push ( location ) ; } return locations ; } function findPossibleENSNames ( textDocument ) { let text = textDocument . getText ( ) ; let pattern = /["'][\w\d.]+[.][\w\d]{3}["']/g ; let m ; let problems = 0 ; let locations = [ ] ; while ( ( m = pattern . exec ( text ) ) && problems < 100 ) { m [ 0 ] = m [ 0 ] . substring ( 1 , m [ 0 ] . length - 1 ) ; let location = { range : { start : textDocument . positionAt ( m . index + 1 ) , end : textDocument . positionAt ( m . index + 1 + m [ 0 ] . length ) } , content : m [ 0 ] } ; locations . push ( location ) ; } return locations ; } function isValidEthereumAddress ( address ) { return web3 . utils . isAddress ( address ) ; } function findPossiblePrivateKeys ( textDocument ) { let text = textDocument . getText ( ) ; let pattern = /[0-9a-fA-F]{64}\b/g ; let m ; let problems = 0 ; let locations = [ ] ; while ( ( m = pattern . exec ( text ) ) && problems < 100 ) { let location = { range : { start : textDocument . positionAt ( m . index ) , end : textDocument . positionAt ( m . index + m [ 0 ] . length ) } , content : normalizeHex ( m [ 0 ] ) } ; locations . push ( location ) ; } return locations ; } function isPrivateKey ( possiblePrivateKey ) { try { var privateKeyBuffer = EthUtil . toBuffer ( possiblePrivateKey ) ; Wallet . fromPrivateKey ( privateKeyBuffer ) ; } catch ( e ) { return false ; } return true ; } function normalizeHex ( hex ) { if ( ! hex . startsWith ( "0x" ) ) { return "0x" + hex ; } return hex ; } function toPublicKey ( privateKey ) { return web3 . eth . accounts . privateKeyToAccount ( privateKey ) . address ; } connection . onCodeLens ( async ( _params ) : Promise < CodeLens [ ] > => { let textDocument = documents . get ( _params . textDocument . uri ) ; if ( typeof textDocument !== 'undefined' ) { let codeLenses = [ ] ; let possibleEthereumAddresses = findPossibleEthereumAddresses ( textDocument ) ; possibleEthereumAddresses . forEach ( ( element ) => { if ( isValidEthereumAddress ( element . content ) ) { pushEthereumAddressCodeLenses ( CODE_LENS_TYPE_ETH_ADDRESS , element . range , element . content , codeLenses ) ; } } ) ; let possiblePublicKeys = findPossiblePrivateKeys ( textDocument ) ; possiblePublicKeys . forEach ( ( element ) => { if ( isPrivateKey ( element . content ) ) { pushEthereumAddressCodeLenses ( CODE_LENS_TYPE_ETH_PRIVATE_KEY , element . range , toPublicKey ( element . content ) , codeLenses ) ; } } ) ; let possibleEnsNames = findPossibleENSNames ( textDocument ) ; for ( let i = 0 ; i < possibleEnsNames . length ; i ++ ) { let element = possibleEnsNames [ i ] ; let ensAddress = await ENSLookup ( element . content ) ; if ( ensAddress !== "" ) { pushEthereumAddressCodeLenses ( CODE_LENS_TYPE_ETH_ADDRESS , element . range , ensAddress , codeLenses ) ; } } possibleEnsNames . forEach ( async ( element ) => { } ) ; return codeLenses ; } else { return [ ] ; } } ) ; function pushEthereumAddressCodeLenses ( codeLensType , range , content , codeLenses ) { NETWORKS . forEach ( ( network ) => { let codeLens = { range : range , data : [ codeLensType , network , content ] } ; codeLenses . push ( codeLens ) ; } ) ; } connection . onCodeLensResolve ( async ( codeLens ) : Promise < CodeLens > => { let codeLensType = codeLens . data [ 0 ] ; let network = codeLens . data [ 1 ] ; let codeLensData = codeLens . data [ 2 ] ; let address = codeLensData . toString ( ) ; if ( network === MAINNET ) { let ensName = await reverseENSLookup ( address ) ; let token = await getToken ( address ) ; let prefix = "" ; if ( ensName != "" ) { prefix += ensName + " | " ; } let isToken = false ; if ( token !== undefined ) { isToken = true ; prefix += getTokenName ( token ) + " | " ; } if ( codeLensType === CODE_LENS_TYPE_ETH_ADDRESS ) { codeLens . command = Command . create ( prefix + "Ethereum " + ( isToken ? "token" : "address" ) + " (mainnet): " + address , "etherscan.show.url" , "https://etherscan.io/address/" + address ) ; } else if ( codeLensType === CODE_LENS_TYPE_ETH_PRIVATE_KEY ) { codeLens . command = Command . create ( prefix + "Private key for Ethereum " + ( isToken ? "token" : "address" ) + " (mainnet): " + address , "etherscan.show.url" , "https://etherscan.io/address/" + address ) ; } } else if ( network === ROPSTEN ) { codeLens . command = Command . create ( "(ropsten)" , "etherscan.show.url" , "https://ropsten.etherscan.io/address/" + address ) ; } else if ( network === KOVAN ) { codeLens . command = Command . create ( "(kovan)" , "etherscan.show.url" , "https://kovan.etherscan.io/address/" + address ) ; } else if ( network === RINKEBY ) { codeLens . command = Command . create ( "(rinkeby)" , "etherscan.show.url" , "https://rinkeby.etherscan.io/address/" + address ) ; } else if ( network === GOERLI ) { codeLens . command = Command . create ( "(goerli)" , "etherscan.show.url" , "https://goerli.etherscan.io/address/" + address ) ; } return codeLens ; } ) ; function getTokenName ( token ) { return `${ token . name } (${ token . symbol })` ; } connection . onCodeAction ( async ( _params ) : Promise < CodeAction [ ] > => { let codeActions = [ ] ; let textDocument = documents . get ( _params . textDocument . uri ) ; if ( textDocument === undefined ) { return codeActions ; } let context = _params . context ; let diagnostics = context . diagnostics ; codeActions = await getCodeActions ( diagnostics , textDocument , _params ) ; return codeActions ; } ) ; async function reverseENSLookup ( address ) { if ( ens === undefined ) { return "" ; } let cached = ensReverseCache . get ( address ) ; if ( cached !== undefined ) { return cached ; } let result = "" ; await ens . reverse ( address ) . name ( ) . then ( async function ( name ) { connection . console . log ( "Found ENS name for " + address + " is: " + name ) ; let addr = await ENSLookup ( name ) ; if ( web3 . utils . toChecksumAddress ( address ) == web3 . utils . toChecksumAddress ( addr ) ) { ensReverseCache . set ( address , name ) ; result = name ; } } ) . catch ( e => connection . console . log ( "Could not reverse lookup ENS name for " + address + " due to error: " + e ) ) ; return result ; } async function ENSLookup ( name ) { if ( ens === undefined ) { return "" ; } let cached = ensCache . get ( name ) ; if ( cached !== undefined ) { return cached ; } let result = "" ; await ens . resolver ( name ) . addr ( ) . then ( function ( addr ) { connection . console . log ( "ENS resolved address is " + addr ) ; if ( addr != "0x0000000000000000000000000000000000000000" ) { ensCache . set ( name , addr ) ; result = addr ; } } ) . catch ( e => connection . console . log ( "Could not do lookup ENS address for resolved name " + name + " due to error: " + e ) ) ; return result ; } async function getCodeActions ( diagnostics , textDocument , params ) { let codeActions = [ ] ; for ( let i = 0 ; i < diagnostics . length ; i ++ ) { let diagnostic = diagnostics [ i ] ; if ( String ( diagnostic . code ) . startsWith ( DIAGNOSTIC_TYPE_NOT_CHECKSUM_ADDRESS ) ) { let title = "Convert to checksum address" ; let range = diagnostic . range ; let replacement = String ( diagnostic . code ) . substring ( DIAGNOSTIC_TYPE_NOT_CHECKSUM_ADDRESS . length ) ; codeActions . push ( getQuickFix ( diagnostic , title , range , replacement , textDocument ) ) ; } else if ( String ( diagnostic . code ) . startsWith ( DIAGNOSTIC_TYPE_CONVERT_TO_ENS_NAME ) ) { let replacement = String ( diagnostic . code ) . substring ( DIAGNOSTIC_TYPE_CONVERT_TO_ENS_NAME . length ) ; let title = `Convert to ENS name \"${ replacement }\"` ; let range = diagnostic . range ; codeActions . push ( getQuickFix ( diagnostic , title , range , replacement , textDocument ) ) ; } else if ( String ( diagnostic . code ) . startsWith ( DIAGNOSTIC_TYPE_CONVERT_FROM_ENS_NAME ) ) { let replacement = String ( diagnostic . code ) . substring ( DIAGNOSTIC_TYPE_CONVERT_FROM_ENS_NAME . length ) ; let title = `Convert to Ethereum address` ; let range = diagnostic . range ; codeActions . push ( getQuickFix ( diagnostic , title , range , replacement , textDocument ) ) ; } else if ( String ( diagnostic . code ) . startsWith ( DIAGNOSTIC_TYPE_GENERATE_ABI ) ) { let address = String ( diagnostic . code ) . substring ( DIAGNOSTIC_TYPE_GENERATE_ABI . length ) ; let abi = await getContractAbi ( address ) ; if ( abi !== undefined ) { let title = `Generate contract ABI` ; let range = diagnostic . range ; codeActions . push ( getRefactorExtract ( diagnostic , title , range , abi , textDocument ) ) ; } } } return codeActions ; } function getQuickFix ( diagnostic , title , range , replacement , textDocument ) { let textEdit = { range : range , newText : replacement } ; let workspaceEdit = { changes : { [ textDocument . uri ] : [ textEdit ] } } ; let codeAction = { title : title , kind : CodeActionKind . QuickFix , edit : workspaceEdit , diagnostics : [ diagnostic ] } ; return codeAction ; } function getRefactorExtract ( diagnostic , title , range , insert , textDocument ) { range . start . line += 1 ; range . start . character = 0 ; range . end . line += 1 ; range . end . character = 0 ; let textEdit = { range : range , newText : "\nconst CONTRACT_ABI = " + insert + "\n\n" } ; let workspaceEdit = { changes : { [ textDocument . uri ] : [ textEdit ] } } ; let codeAction = { title : title , kind : CodeActionKind . RefactorExtract , edit : workspaceEdit , diagnostics : [ diagnostic ] } ; return codeAction ; } connection . onHover ( async ( _params ) : Promise < Hover > => { let textDocument = documents . get ( _params . textDocument . uri ) ; let position = _params . position ; let hover = { contents : "" } ; if ( textDocument !== undefined ) { var start = { line : position . line , character : 0 , } ; var end = { line : position . line + 1 , character : 0 , } ; var text = textDocument . getText ( { start , end } ) ; var index = textDocument . offsetAt ( position ) - textDocument . offsetAt ( start ) ; var word = getWord ( text , index ) ; let buf = "" ; if ( isValidEthereumAddress ( word ) ) { buf = await getHoverMarkdownForAddress ( word ) ; } else { let normalized = normalizeHex ( word ) ; if ( isPrivateKey ( normalized ) ) { buf = await getHoverMarkdownForAddress ( toPublicKey ( normalized ) ) ; } else { let address = await ENSLookup ( word ) ; if ( address != "" ) { buf = await getHoverMarkdownForAddress ( address ) ; } } } hover . contents = buf ; } return hover ; } ) ; async function getHoverMarkdownForAddress ( address ) { var result = await getMarkdownForTokenAddress ( address ) ; if ( result === "" ) { result = await getMarkdownForRegularAddress ( address ) ; } return result ; } async function getContractAbi ( address ) { let cached = abiCache . get ( address ) ; if ( cached !== undefined && cached [ 0 ] > ( Date . now ( ) - cacheDurationSetting ) ) { return cached [ 1 ] ; } let abi = undefined ; if ( etherscanApiKeySetting !== "" ) { var options = { method : 'GET' , url : 'https://api.etherscan.io/api?module=contract&action=getabi&address=' + address + '&apikey=' + etherscanApiKeySetting } ; await request ( options , async function ( error , response , body ) { if ( error ) { connection . console . log ( "Request error while getting ABI for " + address + ": " + error ) ; return ; } var result = JSON . parse ( body ) ; if ( result !== undefined && result . status !== undefined && result . status === "1" && result . result !== undefined ) { abi = result . result ; abiCache . set ( address , [ Date . now ( ) , result . result ] ) ; } } ) . catch ( ( error ) => { connection . console . log ( "Error getting ABI for " + address + ": " + error ) ; } ) ; } return abi ; } async function getToken ( address ) { return ( await getTopTokens ( ) ) . get ( address ) ; } async function getMarkdownForTokenAddress ( address ) { let token = await getToken ( address ) ; if ( token !== undefined ) { return getMarkdownForToken ( token ) ; } return "" ; } async function getMarkdownForRegularAddress ( address ) { let cached = addressMarkdownCache . get ( address ) ; if ( cached !== undefined && cached [ 0 ] > ( Date . now ( ) - cacheDurationSetting ) ) { return cached [ 1 ] ; } let buf = "**Ethereum Address**: " + address + "\n\n" ; if ( web3provider === undefined ) { return buf ; } let ensName = await reverseENSLookup ( address ) ; if ( ensName != "" ) { buf += "**ENS Name**: " + ensName + "\n\n" ; } var web3connection = new Web3 ( web3provider ) ; let balance = await web3connection . eth . getBalance ( address ) ; if ( balance > BigNumber ( 0 ) ) { buf += "**Ether Balance**:\n\n" + "    " + web3 . utils . fromWei ( balance ) + " ETH" ; } if ( amberdataApiKeySetting !== "" ) { if ( balance > BigNumber ( 0 ) ) { var options1 = { method : 'GET' , url : 'https://web3api.io/api/v2/addresses/' + address + '/account-balances/latest' , qs : { includePrice : 'true' , currency : 'usd' } , headers : { 'x-amberdata-blockchain-id' : 'ethereum-mainnet' , 'x-api-key' : amberdataApiKeySetting } } ; await request ( options1 , async function ( error , response , body ) { if ( error ) { connection . console . log ( "Request error getting ETH balance: " + error ) ; return ; } var result = JSON . parse ( body ) ; if ( result !== undefined && result . payload !== undefined && result . payload . price !== undefined && result . payload . value !== undefined ) { var total = result . payload . price . value . total ; var quote = result . payload . price . value . quote ; buf += " (" + dollarFormat ( total , false ) + " USD @ " + dollarFormat ( quote , false ) + ")" ; } } ) . catch ( ( error ) => { connection . console . log ( "Error getting ETH balance: " + error ) ; } ) ; buf += "\n\n" ; } var options = { method : 'GET' , url : 'https://web3api.io/api/v2/addresses/' + address + '/tokens' , qs : { direction : 'descending' , includePrice : 'true' , currency : 'usd' , sortType : 'amount' , page : '0' , size : '5' } , headers : { 'x-amberdata-blockchain-id' : 'ethereum-mainnet' , 'x-api-key' : amberdataApiKeySetting } } ; await request ( options , async function ( error , response , body ) { if ( error ) { connection . console . log ( "Request error getting token balances: " + error ) ; return ; } var result = JSON . parse ( body ) ; if ( result !== undefined && result . payload !== undefined && result . payload . records !== undefined && result . payload . records . length > 0 ) { buf += "**Tokens**:\n\n" ; result . payload . records . forEach ( ( element ) => { var symbol = element . symbol ; var amount = BigNumber ( element . amount ) . divide ( BigNumber ( 10 ) . power ( BigNumber ( element . decimals ) ) ) ; buf += "    " + numberFormat ( amount , 0 , false ) + " " + symbol ; if ( element . price != null ) { var quote = dollarFormat ( element . price . amount . quote , false ) ; var totalValue = dollarFormat ( element . price . amount . total , false ) ; buf += " (" + totalValue + " USD @ " + quote + ") \n" ; } else { buf += " \n" ; } } ) ; } } ) . catch ( ( error ) => { connection . console . log ( "Error getting token balances: " + error ) ; } ) ; } addressMarkdownCache . set ( address , [ Date . now ( ) , buf ] ) ; return buf ; } function getWord ( text , index ) { var beginSubstring = text . substring ( 0 , index ) ; var endSubstring = text . substring ( index , text . length ) ; var boundaryRegex = /[^0-9a-zA-Z.]{1}/g ; var first = lastIndexOfRegex ( beginSubstring , boundaryRegex ) + 1 ; var last = index + indexOfRegex ( endSubstring , boundaryRegex ) ; return text . substring ( first !== - 1 ? first : 0 , last !== - 1 ? last : text . length - 1 ) ; } documents . listen ( connection ) ; connection . listen ( ) ;