UNPKG

@gateway.fm/gtw-dvf-client-js

Version:

DVF client js lib with gateway.fm rpc endpoints

259 lines (215 loc) 7.56 kB
#!/usr/bin/env node /* 1. Create new Ethereum wallet on the network indicated by RPC_URL 2. Loads it with Eth 3. Saves the private key in config.json */ const _ = require('lodash') const fs = require('fs') const readline = require('readline') const Web3 = require('web3') const P = require('aigle') const logMyIP = require('./helpers/logMyIP') const spawnProcess = require('./helpers/spawnProcess') const request = require('./helpers/request') const saveAsJson = require('./helpers/saveAsJson') const RPC_URL = process.argv[2] const API_KEY = process.argv[3] const useTor = process.env.USE_TOR === 'true' const createNewAccount = process.env.CREATE_NEW_ACCOUNT === 'true' const useExistingAccount = process.env.USE_EXISTING_ACCOUNT === 'true' const waitForBalance = process.env.WAIT_FOR_BALANCE === 'true' const API_URL = process.env.API_URL || 'https://rpc.gateway.fm/v1/starkex/stg' const DATA_API_URL = process.env.DATA_API_URL || API_URL if (!RPC_URL) { console.error('Error: RPC_URL not set') console.error('\nusage: ./0.setup.js RPC_URL') console.error('\n you need an Ethereum node to connect to. You can use Infura by following instructions here: https://ethereumico.io/knowledge-base/infura-api-key-guide ') console.error('\n if you get an error when requesting Eth from a faucet, set USE_TOR=true env var to make requests via a TOR (using https://www.npmjs.com/package/tor-request)') console.error(' NOTE: tor executable needs to be on your path for this to work (it will be started/stopped automatically)') console.error(' tor can be installed via brew on MacOS or using your distros package manager if you are using linux') process.exit(1) } if (!API_KEY) { console.error('Error: API_KEY not set') console.error('\nusage: ./0.setup.js API_KEY') console.error('\n you need an API_KEY key. You can use crate an API_KEY from the admin dashboard in admin.gateway.fm by creating a project') console.error('\n then createing an api key.') process.exit(1) } const configFileName = process.env.CONFIG_FILE_NAME || 'config.json' const configFilePath = `${__dirname}/${configFileName}` const ethRequestOptsForUrl = { 'https://faucet.ropsten.be': (address) => `https://faucet.ropsten.be/donate/${address}`, // This one gives on only 0.5 eth 'https://ropsten.faucet.b9lab.com': (address) => { return { uri: 'https://ropsten.faucet.b9lab.com/tap', method: 'POST', json: true, body: { toWhom: address } } }, 'https://faucet.metamask.io': (address) => { return { uri: 'https://faucet.metamask.io', method: 'POST', form: address } } } const getBalanceInEth = async (web3, account) => { return web3.utils.fromWei( await web3.eth.getBalance(account.address), 'ether' ) } const checkBalance = async (web3, account, requiredBalance) => { console.log('checking balance') console.log('requiredBalance (ETH):', requiredBalance) const balance = await getBalanceInEth(web3, account) console.log('account balance (ETH):', balance) if (balance < requiredBalance) { throw new Error(`unsufficient balance: ${balance}, requiredBalance: ${requiredBalance}`) } } const requestEth = (serviceUrl, address) => { console.log(`Requesting Eth from: ${serviceUrl}`) return request(useTor, ethRequestOptsForUrl[serviceUrl](address)) .then(({ response, body }) => { // ropsten.faucet.b9lab.com still responds with 200 if rate limiting kicks // in, so we need to parse the error from the body. if (_.get(body, 'txHash.errorMessage')) throw { response, body } console.log(`Request for Eth from ${serviceUrl} succeeded! Response body:`, body) console.log('Please allow some time for the transaction to be validated.') return true }) .catch(data => { console.error(`Request for Eth from ${serviceUrl} failed!`, { error: data.error, statusCode: data.response && data.response.statusCode, body: data.body }) return false }) } const maybeSpawnTor = async () => { if (!useTor) return console.log('Starting TOR...') const torProcess = spawnProcess({ command: ['tor'], waitForLogOnInit: /.*Bootstrapped 100% \(done\): Done.*/, log: false }) await logMyIP(true) return torProcess } const maybeKillTor = async (torProcess) => { if (!useTor) return console.log('Killing TOR...') await torProcess.kill(null, 'SIGINT') console.log('TOR killed.') } const getEth = async (account) => { const torProcess = await maybeSpawnTor() let gotEth = await requestEth('https://faucet.metamask.io', account.address) if (!gotEth) { gotEth = await requestEth('https://faucet.ropsten.be', account.address) } await maybeKillTor(torProcess) return gotEth } const go = async (configPath) => { const web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL)) let account if (configPath) { console.log(`using existing config at: ${configPath}`) const config = require(configPath) if (!(config.account && config.account.address)) throw new Error('account.address not defined in config') account = config.account } else { account = web3.eth.accounts.create() console.log('Created new Ethereum account:', account.address) saveAsJson(configFilePath, { RPC_URL, ETH_PRIVATE_KEY: account.privateKey, API_URL, DATA_API_URL, API_KEY: "Bearer "+API_KEY, account }) console.log(`Created ./${configFileName}`) } const hasSufficientBalanceOrThrow = () => checkBalance(web3, account, 1) await hasSufficientBalanceOrThrow() // If not enough balance, try to get some. .catch(async () => { const gotEth = await getEth(account) if (!gotEth) { console.error('attempts to get Eth failed!') process.exit(1) } }) if (waitForBalance) { await P.retry( { times: 360, interval: 1000 }, hasSufficientBalanceOrThrow ) } // For some reason the process hangs here sometimes when using tor. process.exit() } const ask = question => { return new P((resolve, reject) => { try { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) rl.question(question, answer => { rl.close() resolve(answer) }) } catch (error) { reject(error) } }) } ;(async () => { if (fs.existsSync(configFilePath)) { if (useExistingAccount) { await go(configFilePath) } else if (createNewAccount) { await go() } else { let answer // For non-interactive mode (reponds to the prompt below) if (process.argv.length >= 4) { switch (process.argv[3]) { case '--yes': answer = 'yes' break case '--no': answer = 'no' break default: break } } answer = answer || await ask( `The ./${configFileName} file exits, do you want to use this config? If you choose 'yes', existing ./${configFileName} will not be modified and Eth will be added to the account found in this config. If you chooce 'no', a new account will be created, Eth added to it and the ./${configFileName} file overwritten (yes/no): `, ) await (answer === 'yes' ? go(configFilePath) : go() ) } } else { await go() } })().catch(error => { console.error(error) process.exit(1) })